Merge branch 'v14-Dev' into feature/tag-team-partial-rendering

This commit is contained in:
WBHarry 2026-03-22 02:02:56 +01:00
commit df7d8bcd23
102 changed files with 1990 additions and 771 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.changeEffects };
CONFIG.Combat.documentClass = documents.DhpCombat; CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.Combat.dataModels = { base: models.DhCombat }; CONFIG.Combat.dataModels = { base: models.DhCombat };
@ -211,6 +212,7 @@ Hooks.once('init', () => {
SYSTEM.id, SYSTEM.id,
applications.sheetConfigs.ActiveEffectConfig, applications.sheetConfigs.ActiveEffectConfig,
{ {
types: ['base', 'beastform', 'horde'],
makeDefault: true, makeDefault: true,
label: sheetLabel('DOCUMENT.ActiveEffect') label: sheetLabel('DOCUMENT.ActiveEffect')
} }
@ -268,7 +270,6 @@ Hooks.on('setup', () => {
...damageThresholds, ...damageThresholds,
'proficiency', 'proficiency',
'evasion', 'evasion',
'armorScore',
'scars', 'scars',
'levelData.level.current' 'levelData.level.current'
] ]

View file

@ -451,7 +451,7 @@
"text": "Are you sure you want to delete {name}?" "text": "Are you sure you want to delete {name}?"
}, },
"DamageReduction": { "DamageReduction": {
"armorMarks": "Armor Marks", "maxUseableArmor": "Useable Armor Slots",
"armorWithStress": "Spend 1 stress to use an extra mark", "armorWithStress": "Spend 1 stress to use an extra mark",
"thresholdImmunities": "Threshold Immunities", "thresholdImmunities": "Threshold Immunities",
"stress": "Stress", "stress": "Stress",
@ -794,6 +794,11 @@
"bruiser": "for each Bruiser adversary.", "bruiser": "for each Bruiser adversary.",
"solo": "for each Solo adversary." "solo": "for each Solo adversary."
}, },
"ArmorInteraction": {
"none": { "label": "Ignores Armor" },
"active": { "label": "Active w/ Armor" },
"inactive": { "label": "Inactive w/ Armor" }
},
"ArmorFeature": { "ArmorFeature": {
"burning": { "burning": {
"name": "Burning", "name": "Burning",
@ -1882,6 +1887,17 @@
"name": "Healing Roll" "name": "Healing Roll"
} }
}, },
"ChangeTypes": {
"armor": {
"newArmorEffect": "Armor Effect",
"FIELDS": {
"interaction": {
"label": "Armor Interaction",
"hint": "Does the character wearing armor suppress this effect?"
}
}
}
},
"Duration": { "Duration": {
"passive": "Passive", "passive": "Passive",
"temporary": "Temporary" "temporary": "Temporary"
@ -2291,6 +2307,7 @@
"duality": "Duality", "duality": "Duality",
"dualityDice": "Duality Dice", "dualityDice": "Duality Dice",
"dualityRoll": "Duality Roll", "dualityRoll": "Duality Roll",
"effect": "Effect",
"enabled": "Enabled", "enabled": "Enabled",
"evasion": "Evasion", "evasion": "Evasion",
"equipment": "Equipment", "equipment": "Equipment",
@ -3097,6 +3114,9 @@
"knowTheTide": "Know The Tide gained a token", "knowTheTide": "Know The Tide gained a token",
"lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}" "lackingItemTransferPermission": "User {user} lacks owner permission needed to transfer items to {target}"
}, },
"Progress": {
"migrationLabel": "Performing system migration. Please wait and do not close Foundry."
},
"Sidebar": { "Sidebar": {
"actorDirectory": { "actorDirectory": {
"tier": "Tier {tier} {type}", "tier": "Tier {tier} {type}",

View file

@ -1,4 +1,4 @@
import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs'; import { damageKeyToNumber, getArmorSources, getDamageLabel } from '../../helpers/utils.mjs';
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
@ -10,6 +10,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.reject = reject; this.reject = reject;
this.actor = actor; this.actor = actor;
this.damage = damage; this.damage = damage;
this.damageType = damageType; this.damageType = damageType;
this.rulesDefault = game.settings.get( this.rulesDefault = game.settings.get(
CONFIG.DH.id, CONFIG.DH.id,
@ -20,14 +21,20 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.rulesDefault this.rulesDefault
); );
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true); const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
const availableArmor = actor.system.armorScore - actor.system.armor.system.marks.value; const armor = orderedArmorSources.reduce((acc, { document }) => {
const maxArmorMarks = canApplyArmor ? availableArmor : 0; const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
acc.push({
effect: document,
marks: [...Array(max).keys()].reduce((acc, _, index) => {
const spent = index < current;
acc[foundry.utils.randomID()] = { selected: false, disabled: spent, spent };
return acc;
}, {})
});
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = { selected: false };
return acc; return acc;
}, {}); }, []);
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce( const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
(acc, _) => { (acc, _) => {
acc[foundry.utils.randomID()] = { selected: false }; acc[foundry.utils.randomID()] = { selected: false };
@ -121,13 +128,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
context.thresholdImmunities = context.thresholdImmunities =
Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null; Object.keys(this.thresholdImmunities).length > 0 ? this.thresholdImmunities : null;
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage } = const { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor } =
this.getDamageInfo(); this.getDamageInfo();
context.armorScore = this.actor.system.armorScore; context.armorScore = this.actor.system.armorScore.max;
context.armorMarks = currentMarks; context.armorMarks = currentMarks;
context.basicMarksUsed =
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.value;
const stressReductionStress = this.availableStressReductions const stressReductionStress = this.availableStressReductions
? stressReductions.reduce((acc, red) => acc + red.cost, 0) ? stressReductions.reduce((acc, red) => acc + red.cost, 0)
@ -141,16 +146,30 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
} }
: null; : null;
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value; context.maxArmorUsed = maxArmorUsed;
context.marks = { context.availableArmor = availableArmor;
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => { context.basicMarksUsed = availableArmor === 0 || selectedStressMarks.length;
const mark = this.marks.armor[key];
if (!this.rulesOn || index + 1 <= maxArmor) acc[key] = mark;
return acc; const armorSources = [];
}, {}), for (const source of this.marks.armor) {
const parent = source.effect.origin
? await foundry.utils.fromUuid(source.effect.origin)
: source.effect.parent;
const useEffectName = parent.type === 'armor' || parent instanceof Actor;
const label = useEffectName ? source.effect.name : parent.name;
armorSources.push({
label: label,
uuid: source.effect.uuid,
marks: source.marks
});
}
context.marks = {
armor: armorSources,
stress: this.marks.stress stress: this.marks.stress
}; };
context.usesStressArmor = Object.keys(context.marks.stress).length;
context.availableStressReductions = this.availableStressReductions; context.availableStressReductions = this.availableStressReductions;
context.damage = getDamageLabel(this.damage); context.damage = getDamageLabel(this.damage);
@ -167,27 +186,31 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
} }
getDamageInfo = () => { getDamageInfo = () => {
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected); const selectedArmorMarks = this.marks.armor.flatMap(x => Object.values(x.marks).filter(x => x.selected));
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected); const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
const stressReductions = this.availableStressReductions const stressReductions = this.availableStressReductions
? Object.values(this.availableStressReductions).filter(red => red.selected) ? Object.values(this.availableStressReductions).filter(red => red.selected)
: []; : [];
const currentMarks = const currentMarks = this.actor.system.armorScore.value + selectedArmorMarks.length;
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
const maxArmorUsed = this.actor.system.rules.damageReduction.maxArmorMarked.value + selectedStressMarks.length;
const availableArmor =
maxArmorUsed -
this.marks.armor.reduce((acc, source) => {
acc += Object.values(source.marks).filter(x => x.selected).length;
return acc;
}, 0);
const armorMarkReduction = const armorMarkReduction =
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark; selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
let currentDamage = Math.max( let currentDamage = Math.max(this.damage - armorMarkReduction - stressReductions.length, 0);
this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length,
0
);
if (this.reduceSeverity) { if (this.reduceSeverity) {
currentDamage = Math.max(currentDamage - this.reduceSeverity, 0); currentDamage = Math.max(currentDamage - this.reduceSeverity, 0);
} }
if (this.thresholdImmunities[currentDamage]) currentDamage = 0; if (this.thresholdImmunities[currentDamage]) currentDamage = 0;
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage }; return { selectedStressMarks, stressReductions, currentMarks, currentDamage, maxArmorUsed, availableArmor };
}; };
static toggleRules() { static toggleRules() {
@ -195,13 +218,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value; const maxArmor = this.actor.system.rules.damageReduction.maxArmorMarked.value;
this.marks = { this.marks = {
armor: Object.keys(this.marks.armor).reduce((acc, key, index) => { armor: this.marks.armor.map((mark, index) => {
const mark = this.marks.armor[key];
const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor; const keepSelectValue = !this.rulesOn || index + 1 <= maxArmor;
acc[key] = { ...mark, selected: keepSelectValue ? mark.selected : false }; return { ...mark, selected: keepSelectValue ? mark.selected : false };
}),
return acc;
}, {}),
stress: this.marks.stress stress: this.marks.stress
}; };
@ -209,8 +229,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
} }
static setMarks(_, target) { static setMarks(_, target) {
const currentMark = this.marks[target.dataset.type][target.dataset.key]; const currentMark = foundry.utils.getProperty(this.marks, target.dataset.path);
const { selectedStressMarks, stressReductions, currentMarks, currentDamage } = this.getDamageInfo(); const { selectedStressMarks, stressReductions, currentDamage, availableArmor } = this.getDamageInfo();
if (!currentMark.selected && currentDamage === 0) { if (!currentMark.selected && currentDamage === 0) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.damageAlreadyNone'));
@ -218,12 +238,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
} }
if (this.rulesOn) { if (this.rulesOn) {
if (!currentMark.selected && currentMarks === this.actor.system.armorScore) { if (target.dataset.type === 'armor' && !currentMark.selected && !availableArmor) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks')); ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.noAvailableArmorMarks'));
return; return;
} }
} }
const stressUsed = selectedStressMarks.length;
if (target.dataset.type === 'armor' && stressUsed) {
const updateResult = this.updateStressArmor(target.dataset.id, !currentMark.selected);
if (updateResult === false) return;
}
if (currentMark.selected) { if (currentMark.selected) {
const currentDamageLabel = getDamageLabel(currentDamage); const currentDamageLabel = getDamageLabel(currentDamage);
for (let reduction of stressReductions) { for (let reduction of stressReductions) {
@ -232,8 +258,16 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
} }
} }
if (target.dataset.type === 'armor' && selectedStressMarks.length > 0) { if (target.dataset.type === 'stress' && currentMark.armorMarkId) {
selectedStressMarks.forEach(mark => (mark.selected = false)); for (const source of this.marks.armor) {
const match = Object.keys(source.marks).find(key => key === currentMark.armorMarkId);
if (match) {
source.marks[match].selected = false;
break;
}
}
currentMark.armorMarkId = null;
} }
} }
@ -241,6 +275,25 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.render(); this.render();
} }
updateStressArmor(armorMarkId, select) {
let stressMarkKey = null;
if (select) {
stressMarkKey = Object.keys(this.marks.stress).find(
key => this.marks.stress[key].selected && !this.marks.stress[key].armorMarkId
);
} else {
stressMarkKey = Object.keys(this.marks.stress).find(
key => this.marks.stress[key].armorMarkId === armorMarkId
);
if (!stressMarkKey)
stressMarkKey = Object.keys(this.marks.stress).find(key => this.marks.stress[key].selected);
}
if (!stressMarkKey) return false;
this.marks.stress[stressMarkKey].armorMarkId = select ? armorMarkId : null;
}
static useStressReduction(_, target) { static useStressReduction(_, target) {
const damageValue = Number(target.dataset.reduction); const damageValue = Number(target.dataset.reduction);
const stressReduction = this.availableStressReductions[damageValue]; const stressReduction = this.availableStressReductions[damageValue];
@ -279,11 +332,18 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
} }
static async takeDamage() { static async takeDamage() {
const { selectedArmorMarks, selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo(); const { selectedStressMarks, stressReductions, currentDamage } = this.getDamageInfo();
const armorSpent = selectedArmorMarks.length + selectedStressMarks.length; const armorChanges = this.marks.armor.reduce((acc, source) => {
const stressSpent = selectedStressMarks.length + stressReductions.reduce((acc, red) => acc + red.cost, 0); const amount = Object.values(source.marks).filter(x => x.selected).length;
if (amount) acc.push({ uuid: source.effect.uuid, amount });
this.resolve({ modifiedDamage: currentDamage, armorSpent, stressSpent }); return acc;
}, []);
const stressSpent =
selectedStressMarks.filter(x => x.armorMarkId).length +
stressReductions.reduce((acc, red) => acc + red.cost, 0);
this.resolve({ modifiedDamage: currentDamage, armorChanges, stressSpent });
await this.close(true); await this.close(true);
} }

View file

@ -203,7 +203,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
const msg = { const msg = {
user: game.user.id, user: game.user.id,
system: { system: {
moves: moves, moves: moves.map(move => ({ ...move, actions: Array.from(move.actions) })),
actor: this.actor.uuid actor: this.actor.uuid
}, },
speaker: cls.getSpeaker(), speaker: cls.getSpeaker(),

View file

@ -3,7 +3,6 @@ export { default as ActionSettingsConfig } from './action-settings-config.mjs';
export { default as CharacterSettings } from './character-settings.mjs'; export { default as CharacterSettings } from './character-settings.mjs';
export { default as AdversarySettings } from './adversary-settings.mjs'; export { default as AdversarySettings } from './adversary-settings.mjs';
export { default as CompanionSettings } from './companion-settings.mjs'; export { default as CompanionSettings } from './companion-settings.mjs';
export { default as SettingActiveEffectConfig } from './setting-active-effect-config.mjs';
export { default as SettingFeatureConfig } from './setting-feature-config.mjs'; export { default as SettingFeatureConfig } from './setting-feature-config.mjs';
export { default as EnvironmentSettings } from './environment-settings.mjs'; export { default as EnvironmentSettings } from './environment-settings.mjs';
export { default as ActiveEffectConfig } from './activeEffectConfig.mjs'; export { default as ActiveEffectConfig } from './activeEffectConfig.mjs';

View file

@ -24,9 +24,12 @@ export default class DHActionConfig extends DHActionBaseConfig {
const effectData = this._addEffectData.bind(this)(); const effectData = this._addEffectData.bind(this)();
const data = this.action.toObject(); const data = this.action.toObject();
const [created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { const created = await game.system.api.documents.DhActiveEffect.createDialog(effectData, {
parent: this.action.item,
render: false render: false
}); });
if (!created) return;
data.effects.push({ _id: created._id }); data.effects.push({ _id: created._id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
this.action.item.effects.get(created._id).sheet.render(true); this.action.item.effects.get(created._id).sheet.render(true);

View file

@ -55,7 +55,7 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
static async editEffect(event) { static async editEffect(event) {
const id = event.target.closest('[data-effect-id]')?.dataset?.effectId; const id = event.target.closest('[data-effect-id]')?.dataset?.effectId;
const updatedEffect = await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure( const updatedEffect = await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(
this.getEffectDetails(id) this.getEffectDetails(id)
); );
if (!updatedEffect) return; if (!updatedEffect) return;

View file

@ -150,6 +150,14 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
minLength: 0 minLength: 0
}); });
}); });
htmlElement
.querySelector('.armor-change-checkbox')
?.addEventListener('change', this.armorChangeToggle.bind(this));
htmlElement
.querySelector('.armor-damage-thresholds-checkbox')
?.addEventListener('change', this.armorDamageThresholdToggle.bind(this));
} }
async _prepareContext(options) { async _prepareContext(options) {
@ -186,21 +194,66 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
})); }));
break; break;
case 'changes': case 'changes':
const fields = this.document.system.schema.fields.changes.element.fields; const singleTypes = ['armor'];
partContext.changes = await Promise.all( const typedChanges = context.source.changes.reduce((acc, change, index) => {
foundry.utils if (singleTypes.includes(change.type)) {
.deepClone(context.source.changes) acc[change.type] = { ...change, index };
.map((c, i) => this._prepareChangeContext(c, i, fields)) }
); return acc;
}, {});
partContext.changes = partContext.changes.filter(c => !!c);
partContext.typedChanges = typedChanges;
break; break;
} }
return partContext; return partContext;
} }
_prepareChangeContext(change, index, fields) { armorChangeToggle(event) {
if (event.target.checked) {
this.addArmorChange();
} else {
this.removeTypedChange(event.target.dataset.index);
}
}
/* Could be generalised if needed later */
addArmorChange() {
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
const changes = Object.values(submitData.system?.changes ?? {});
changes.push(game.system.api.data.activeEffects.changeTypes.armor.getInitialValue());
return this.submit({ updateData: { system: { changes } } });
}
removeTypedChange(indexString) {
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
const changes = Object.values(submitData.system.changes);
const index = Number(indexString);
changes.splice(index, 1);
return this.submit({ updateData: { system: { changes } } });
}
armorDamageThresholdToggle(event) {
const submitData = this._processFormData(null, this.form, new FormDataExtended(this.form));
const changes = Object.values(submitData.system?.changes ?? {});
const index = Number(event.target.dataset.index);
if (event.target.checked) {
changes[index].value.damageThresholds = { major: 0, severe: 0 };
} else {
changes[index].value.damageThresholds = null;
}
return this.submit({ updateData: { system: { changes } } });
}
/** @inheritdoc */
_renderChange(context) {
const { change, index, defaultPriority } = context;
if (!(change.type in CONFIG.DH.GENERAL.baseActiveEffectModes)) return null;
const changeTypesSchema = this.document.system.schema.fields.changes.element.types;
const fields = context.fields ?? (changeTypesSchema[change.type] ?? changeTypesSchema.add).fields;
if (typeof change.value !== 'string') change.value = JSON.stringify(change.value); if (typeof change.value !== 'string') change.value = JSON.stringify(change.value);
const defaultPriority = game.system.api.documents.DhActiveEffect.CHANGE_TYPES[change.type]?.defaultPriority;
Object.assign( Object.assign(
change, change,
['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => { ['key', 'type', 'value', 'priority'].reduce((paths, fieldName) => {
@ -220,7 +273,11 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
change, change,
index, index,
defaultPriority, defaultPriority,
fields fields,
types: Object.keys(CONFIG.DH.GENERAL.baseActiveEffectModes).reduce((r, key) => {
r[key] = CONFIG.DH.GENERAL.baseActiveEffectModes[key].label;
return r;
}, {})
} }
) )
); );
@ -247,4 +304,34 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
return submitData; return submitData;
} }
/** @inheritDoc */
_processSubmitData(event, form, submitData, options) {
if (this.options.isSetting) {
// Settings should update source instead
this.document.updateSource(submitData);
this.render();
} else {
return super._processSubmitData(event, form, submitData, options);
}
}
/** Creates an active effect config for a setting */
static async configureSetting(effect, options = {}) {
const document = new CONFIG.ActiveEffect.documentClass({ ...foundry.utils.duplicate(effect), _id: effect.id });
return new Promise(resolve => {
const app = new this({ document, ...options, isSetting: true });
app.addEventListener(
'close',
() => {
const newEffect = app.document.toObject(true);
newEffect.id = newEffect._id;
delete newEffect._id;
resolve(newEffect);
},
{ once: true }
);
app.render({ force: true });
});
}
} }

View file

@ -1,223 +0,0 @@
import autocomplete from 'autocompleter';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class SettingActiveEffectConfig extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(effect) {
super({});
this.effect = foundry.utils.deepClone(effect);
this.changeChoices = game.system.api.applications.sheetConfigs.ActiveEffectConfig.getChangeChoices();
}
static DEFAULT_OPTIONS = {
classes: ['daggerheart', 'sheet', 'dh-style', 'active-effect-config', 'standard-form'],
tag: 'form',
position: {
width: 560
},
form: {
submitOnChange: false,
closeOnSubmit: false,
handler: SettingActiveEffectConfig.#onSubmit
},
actions: {
editImage: SettingActiveEffectConfig.#editImage,
addChange: SettingActiveEffectConfig.#addChange,
deleteChange: SettingActiveEffectConfig.#deleteChange
}
};
static PARTS = {
header: { template: 'systems/daggerheart/templates/sheets/activeEffect/header.hbs' },
tabs: { template: 'templates/generic/tab-navigation.hbs' },
details: { template: 'systems/daggerheart/templates/sheets/activeEffect/details.hbs', scrollable: [''] },
settings: { template: 'systems/daggerheart/templates/sheets/activeEffect/settings.hbs' },
changes: {
template: 'systems/daggerheart/templates/sheets/activeEffect/changes.hbs',
scrollable: ['ol[data-changes]']
},
footer: { template: 'systems/daggerheart/templates/sheets/global/tabs/tab-form-footer.hbs' }
};
static TABS = {
sheet: {
tabs: [
{ id: 'details', icon: 'fa-solid fa-book' },
{ id: 'settings', icon: 'fa-solid fa-bars', label: 'DAGGERHEART.GENERAL.Tabs.settings' },
{ id: 'changes', icon: 'fa-solid fa-gears' }
],
initial: 'details',
labelPrefix: 'EFFECT.TABS'
}
};
/**@inheritdoc */
async _onFirstRender(context, options) {
await super._onFirstRender(context, options);
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.source = this.effect;
context.fields = game.system.api.documents.DhActiveEffect.schema.fields;
context.systemFields = game.system.api.data.activeEffects.BaseEffect._schema.fields;
return context;
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const changeChoices = this.changeChoices;
htmlElement.querySelectorAll('.effect-change-input').forEach(element => {
autocomplete({
input: element,
fetch: function (text, update) {
if (!text) {
update(changeChoices);
} else {
text = text.toLowerCase();
var suggestions = changeChoices.filter(n => n.label.toLowerCase().includes(text));
update(suggestions);
}
},
render: function (item, search) {
const label = game.i18n.localize(item.label);
const matchIndex = label.toLowerCase().indexOf(search);
const beforeText = label.slice(0, matchIndex);
const matchText = label.slice(matchIndex, matchIndex + search.length);
const after = label.slice(matchIndex + search.length, label.length);
const element = document.createElement('li');
element.innerHTML =
`${beforeText}${matchText ? `<strong>${matchText}</strong>` : ''}${after}`.replaceAll(
' ',
'&nbsp;'
);
if (item.hint) {
element.dataset.tooltip = game.i18n.localize(item.hint);
}
return element;
},
renderGroup: function (label) {
const itemElement = document.createElement('div');
itemElement.textContent = game.i18n.localize(label);
return itemElement;
},
onSelect: function (item) {
element.value = `system.${item.value}`;
},
click: e => e.fetch(),
customize: function (_input, _inputRect, container) {
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
},
minLength: 0
});
});
}
async _preparePartContext(partId, context) {
if (partId in context.tabs) context.tab = context.tabs[partId];
switch (partId) {
case 'details':
context.statuses = CONFIG.statusEffects.map(s => ({ value: s.id, label: game.i18n.localize(s.name) }));
context.isActorEffect = false;
context.isItemEffect = true;
const useGeneric = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects;
if (!useGeneric) {
context.statuses = [
...context.statuses,
Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
value: status.id,
label: game.i18n.localize(status.name)
}))
];
}
break;
case 'changes':
context.modes = Object.entries(CONST.ACTIVE_EFFECT_MODES).reduce((modes, [key, value]) => {
modes[value] = game.i18n.localize(`EFFECT.MODE_${key}`);
return modes;
}, {});
context.priorities = ActiveEffectConfig.DEFAULT_PRIORITIES;
break;
}
return context;
}
static async #onSubmit(_event, _form, formData) {
this.data = foundry.utils.expandObject(formData.object);
this.close();
}
/**
* Edit a Document image.
* @this {DocumentSheetV2}
* @type {ApplicationClickAction}
*/
static async #editImage(_event, target) {
if (target.nodeName !== 'IMG') {
throw new Error('The editImage action is available only for IMG elements.');
}
const attr = target.dataset.edit;
const current = foundry.utils.getProperty(this.effect, attr);
const fp = new FilePicker.implementation({
current,
type: 'image',
callback: path => (target.src = path),
position: {
top: this.position.top + 40,
left: this.position.left + 10
}
});
await fp.browse();
}
/**
* Add a new change to the effect's changes array.
* @this {ActiveEffectConfig}
* @type {ApplicationClickAction}
*/
static async #addChange() {
const { changes, ...rest } = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const updatedChanges = Object.values(changes ?? {});
updatedChanges.push({});
this.effect = { ...rest, changes: updatedChanges };
this.render();
}
/**
* Delete a change from the effect's changes array.
* @this {ActiveEffectConfig}
* @type {ApplicationClickAction}
*/
static async #deleteChange(event) {
const submitData = foundry.utils.expandObject(new FormDataExtended(this.form).object);
const updatedChanges = Object.values(submitData.changes);
const row = event.target.closest('li');
const index = Number(row.dataset.index) || 0;
updatedChanges.splice(index, 1);
this.effect = { ...submitData, changes: updatedChanges };
this.render();
}
static async configure(effect, options = {}) {
return new Promise(resolve => {
const app = new this(effect, options);
app.addEventListener('close', () => resolve(app.data), { once: true });
app.render({ force: true });
});
}
}

View file

@ -147,7 +147,7 @@ export default class SettingFeatureConfig extends HandlebarsApplicationMixin(App
const effectIndex = this.move.effects.findIndex(x => x.id === id); const effectIndex = this.move.effects.findIndex(x => x.id === id);
const effect = this.move.effects[effectIndex]; const effect = this.move.effects[effectIndex];
const updatedEffect = const updatedEffect =
await game.system.api.applications.sheetConfigs.SettingActiveEffectConfig.configure(effect); await game.system.api.applications.sheetConfigs.ActiveEffectConfig.configureSetting(effect);
if (!updatedEffect) return; if (!updatedEffect) return;
await this.updateMove({ await this.updateMove({

View file

@ -67,9 +67,9 @@ export default function DHTokenConfigMixin(Base) {
changes.height = tokenSize; changes.height = tokenSize;
} }
const deletions = { actorId: _del, actorLink: _del }; // const deletions = { actorId: _del };
const mergeOptions = { inplace: false, performDeletions: true }; // const mergeOptions = { inplace: false, performDeletions: true, actorLink: false };
this._preview.updateSource(foundry.utils.mergeObject(changes, deletions, mergeOptions)); this._preview.updateSource(changes);
if (this._preview?.object?.destroyed === false) { if (this._preview?.object?.destroyed === false) {
this._preview.object.initializeSources(); this._preview.object.initializeSources();

View file

@ -3,7 +3,7 @@ import DhDeathMove from '../../dialogs/deathMove.mjs';
import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs'; import { CharacterLevelup, LevelupViewMode } from '../../levelup/_module.mjs';
import DhCharacterCreation from '../../characterCreation/characterCreation.mjs'; import DhCharacterCreation from '../../characterCreation/characterCreation.mjs';
import FilterMenu from '../../ux/filter-menu.mjs'; import FilterMenu from '../../ux/filter-menu.mjs';
import { getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs'; import { getArmorSources, getDocFromElement, getDocFromElementSync } from '../../../helpers/utils.mjs';
/**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */ /**@typedef {import('@client/applications/_types.mjs').ApplicationClickAction} ApplicationClickAction */
@ -34,7 +34,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
cancelBeastform: CharacterSheet.#cancelBeastform, cancelBeastform: CharacterSheet.#cancelBeastform,
toggleResourceManagement: CharacterSheet.#toggleResourceManagement, toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
useDowntime: this.useDowntime, useDowntime: this.useDowntime,
viewParty: CharacterSheet.#viewParty viewParty: CharacterSheet.#viewParty,
toggleArmorMangement: CharacterSheet.#toggleArmorManagement
}, },
window: { window: {
resizable: true, resizable: true,
@ -638,12 +639,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
async updateArmorMarks(event) { async updateArmorMarks(event) {
const armor = this.document.system.armor; const inputValue = Number(event.currentTarget.value);
if (!armor) return; const { value, max } = this.document.system.armorScore;
const changeValue = Math.min(inputValue - value, max - value);
const maxMarks = this.document.system.armorScore; event.currentTarget.value = inputValue < 0 ? 0 : value + changeValue;
const value = Math.min(Math.max(Number(event.currentTarget.value), 0), maxMarks); this.document.system.updateArmorValue({ value: changeValue });
await armor.update({ 'system.marks.value': value });
} }
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -803,10 +804,13 @@ export default class CharacterSheet extends DHBaseActorSheet {
* Toggles ArmorScore resource value. * Toggles ArmorScore resource value.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleArmor(_, button, element) { static async #toggleArmor(_, button, _element) {
const ArmorValue = Number.parseInt(button.dataset.value); const { value, max } = this.document.system.armorScore;
const newValue = this.document.system.armor.system.marks.value >= ArmorValue ? ArmorValue - 1 : ArmorValue; const inputValue = Number.parseInt(button.dataset.value);
await this.document.system.armor.update({ 'system.marks.value': newValue }); const newValue = value >= inputValue ? inputValue - 1 : inputValue;
const changeValue = Math.min(newValue - value, max - value);
this.document.system.updateArmorValue({ value: changeValue });
} }
/** /**
@ -932,6 +936,99 @@ export default class CharacterSheet extends DHBaseActorSheet {
}); });
} }
static async #toggleArmorManagement(_event, target) {
const existingTooltip = document.body.querySelector('.locked-tooltip .armor-management-container');
if (existingTooltip) {
game.tooltip.dismissLockedTooltips();
return;
}
const armorSources = getArmorSources(this.document)
.filter(s => !s.disabled)
.toReversed()
.map(({ name, document, data }) => ({
...data,
uuid: document.uuid,
name
}));
if (!armorSources.length) return;
const useResourcePips = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.appearance
).useResourcePips;
const html = document.createElement('div');
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/armorManagement.hbs`,
{
sources: armorSources,
useResourcePips
}
);
game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, {
html,
locked: true,
cssClass: 'bordered-tooltip',
direction: 'DOWN'
});
html.querySelectorAll('.armor-slot').forEach(element => {
element.addEventListener('click', CharacterSheet.armorSourcePipUpdate);
});
}
static async armorSourceInput(event) {
const effect = await foundry.utils.fromUuid(event.target.dataset.uuid);
const value = Math.max(Math.min(Number.parseInt(event.target.value), effect.system.armorData.max), 0);
event.target.value = value;
const progressBar = event.target.closest('.status-bar.armor-slots').querySelector('progress');
progressBar.value = value;
}
/** Update specific armor source */
static async armorSourcePipUpdate(event) {
const target = event.target.closest('.armor-slot');
const { uuid, value } = target.dataset;
const document = await foundry.utils.fromUuid(uuid);
let inputValue = Number.parseInt(value);
let decreasing = false;
let newCurrent = 0;
if (document.type === 'armor') {
decreasing = document.system.armor.current >= inputValue;
newCurrent = decreasing ? inputValue - 1 : inputValue;
await document.update({ 'system.armor.current': newCurrent });
} else if (document.system.armorData) {
const { current } = document.system.armorData;
decreasing = current >= inputValue;
newCurrent = decreasing ? inputValue - 1 : inputValue;
const newChanges = document.system.changes.map(change => ({
...change,
value: change.type === 'armor' ? { ...change.value, current: newCurrent } : change.value
}));
await document.update({ 'system.changes': newChanges });
} else {
return;
}
const container = target.closest('.slot-bar');
for (const armorSlot of container.querySelectorAll('.armor-slot i')) {
const index = Number.parseInt(armorSlot.dataset.index);
if (decreasing && index >= newCurrent) {
armorSlot.classList.remove('fa-shield');
armorSlot.classList.add('fa-shield-halved');
} else if (!decreasing && index < newCurrent) {
armorSlot.classList.add('fa-shield');
armorSlot.classList.remove('fa-shield-halved');
}
}
}
static async #toggleResourceManagement(event, button) { static async #toggleResourceManagement(event, button) {
event.stopPropagation(); event.stopPropagation();
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container'); const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
@ -965,7 +1062,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
); );
const target = button.closest('.resource-section'); const target = button.closest('.resource-section');
game.tooltip.dismissLockedTooltips(); game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, { game.tooltip.activate(target, {
html, html,

View file

@ -189,11 +189,14 @@ export default class Party extends DHBaseActorSheet {
* Toggles a armor slot resource value. * Toggles a armor slot resource value.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}
*/ */
static async #toggleArmorSlot(_, target, element) { static async #toggleArmorSlot(_, target) {
const armorItem = await foundry.utils.fromUuid(target.dataset.itemUuid); const actor = game.actors.get(target.dataset.actorId);
const armorValue = Number.parseInt(target.dataset.value); const { value, max } = actor.system.armorScore;
const newValue = armorItem.system.marks.value >= armorValue ? armorValue - 1 : armorValue; const inputValue = Number.parseInt(target.dataset.value);
await armorItem.update({ 'system.marks.value': newValue }); const newValue = value >= inputValue ? inputValue - 1 : inputValue;
const changeValue = Math.min(newValue - value, max - value);
await actor.system.updateArmorValue({ value: changeValue });
this.render(); this.render();
} }

View file

@ -749,11 +749,13 @@ export default function DHApplicationMixin(Base) {
const cls = const cls =
type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass); type === 'action' ? game.system.api.models.actions.actionsTypes.base : getDocumentClass(documentClass);
const data = { const data = {
name: cls.defaultName({ type, parent }), name: cls.defaultName({ type, parent }),
type, type,
system: systemData system: systemData
}; };
if (inVault) data['system.inVault'] = true; if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true; if (disabled) data.disabled = true;
if (type === 'domainCard' && parent?.system.domains?.length) { if (type === 'domainCard' && parent?.system.domains?.length) {

View file

@ -160,7 +160,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
inactives: [] inactives: []
}; };
for (const effect of this.actor.allApplicableEffects()) { for (const effect of this.actor.allApplicableEffects({ noTransferArmor: true })) {
const list = effect.active ? context.effects.actives : context.effects.inactives; const list = effect.active ? context.effects.actives : context.effects.inactives;
list.push(effect); list.push(effect);
} }

View file

@ -47,6 +47,15 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
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.armorChange.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

@ -7,3 +7,4 @@ export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs'; export { default as DhHotbar } from './hotbar.mjs';
export { default as DhSceneNavigation } from './sceneNavigation.mjs'; export { default as DhSceneNavigation } from './sceneNavigation.mjs';
export { ItemBrowser } from './itemBrowser.mjs'; export { ItemBrowser } from './itemBrowser.mjs';
export { default as DhProgress } from './progress.mjs';

View file

@ -0,0 +1,27 @@
export default class DhProgress {
#notification;
constructor({ max, label = '' }) {
this.max = max;
this.label = label;
this.#notification = ui.notifications.info(this.label, { progress: true });
}
updateMax(newMax) {
this.max = newMax;
}
advance({ by = 1, label = this.label } = {}) {
if (this.value === this.max) return;
this.value = (this.value ?? 0) + Math.abs(by);
this.#notification.update({ message: label, pct: this.value / this.max });
}
close({ label = '' } = {}) {
this.#notification.update({ message: label, pct: 1 });
}
static createMigrationProgress(max = 0) {
return new DhProgress({ max, label: game.i18n.localize('DAGGERHEART.UI.Progress.migrationLabel') });
}
}

View file

@ -974,7 +974,7 @@ export const tagTeamRollTypes = {
} }
}; };
export const activeEffectModes = { export const baseActiveEffectModes = {
custom: { custom: {
id: 'custom', id: 'custom',
priority: 0, priority: 0,
@ -1012,6 +1012,21 @@ export const activeEffectModes = {
} }
}; };
export const activeEffectModes = {
armor: {
id: 'armor',
priority: 20,
label: 'TYPES.ActiveEffect.armor'
},
...baseActiveEffectModes
};
export const activeEffectArmorInteraction = {
none: { id: 'none', label: 'DAGGERHEART.CONFIG.ArmorInteraction.none.label' },
active: { id: 'active', label: 'DAGGERHEART.CONFIG.ArmorInteraction.active.label' },
inactive: { id: 'inactive', label: 'DAGGERHEART.CONFIG.ArmorInteraction.inactive.label' }
};
export const activeEffectDurations = { export const activeEffectDurations = {
temporary: { temporary: {
id: 'temporary', id: 'temporary',

View file

@ -489,15 +489,18 @@ export const weaponFeatures = {
description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.barrier.effects.barrier.description',
img: 'icons/skills/melee/shield-block-bash-blue.webp', img: 'icons/skills/melee/shield-block-bash-blue.webp',
changes: [ changes: [
{
key: 'system.armorScore',
mode: 2,
value: 'ITEM.@system.tier + 1'
},
{ {
key: 'system.evasion', key: 'system.evasion',
mode: 2, mode: 2,
value: '-1' value: '-1'
},
{
key: 'Armor',
type: 'armor',
typeData: {
type: 'armor',
max: 'ITEM.@system.tier + 1'
}
} }
] ]
} }
@ -789,11 +792,6 @@ export const weaponFeatures = {
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description', description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
img: 'icons/skills/melee/sword-shield-stylized-white.webp', img: 'icons/skills/melee/sword-shield-stylized-white.webp',
changes: [ changes: [
{
key: 'system.armorScore',
mode: 2,
value: '1'
},
{ {
key: 'system.bonuses.damage.primaryWeapon.bonus', key: 'system.bonuses.damage.primaryWeapon.bonus',
mode: 2, mode: 2,
@ -808,6 +806,22 @@ export const weaponFeatures = {
type: 'withinRange' type: 'withinRange'
} }
} }
},
{
name: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.doubleDuty.effects.doubleDuty.description',
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
changes: [
{
key: 'Armor',
type: 'armor',
value: 0,
typeData: {
type: 'armor',
max: 1
}
}
]
} }
] ]
}, },
@ -1191,9 +1205,13 @@ export const weaponFeatures = {
img: 'icons/skills/melee/shield-block-gray-orange.webp', img: 'icons/skills/melee/shield-block-gray-orange.webp',
changes: [ changes: [
{ {
key: 'system.armorScore', key: 'Armor',
mode: 2, type: 'armor',
value: 'ITEM.@system.tier' value: 0,
typeData: {
type: 'armor',
max: 'ITEM.@system.tier'
}
} }
] ]
} }

View file

@ -298,17 +298,19 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
static async getEffects(actor, effectParent) { static async getEffects(actor, effectParent) {
if (!actor) return []; if (!actor) return [];
return Array.from(await actor.allApplicableEffects()).filter(effect => { return Array.from(await actor.allApplicableEffects({ noTransferArmor: true, noSelfArmor: true })).filter(
/* Effects on weapons only ever apply for the weapon itself */ effect => {
if (effect.parent.type === 'weapon') { /* Effects on weapons only ever apply for the weapon itself */
/* Unless they're secondary - then they apply only to other primary weapons */ if (effect.parent.type === 'weapon') {
if (effect.parent.system.secondary) { /* Unless they're secondary - then they apply only to other primary weapons */
if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false; if (effect.parent.system.secondary) {
} else if (effectParent?.id !== effect.parent.id) return false; if (effectParent?.type !== 'weapon' || effectParent?.system.secondary) return false;
} } else if (effectParent?.id !== effect.parent.id) return false;
}
return !effect.isSuppressed; return !effect.isSuppressed;
}); }
);
} }
/** /**

View file

@ -1,6 +1,7 @@
import BaseEffect from './baseEffect.mjs'; import BaseEffect from './baseEffect.mjs';
import BeastformEffect from './beastformEffect.mjs'; import BeastformEffect from './beastformEffect.mjs';
import HordeEffect from './hordeEffect.mjs'; import HordeEffect from './hordeEffect.mjs';
export { changeTypes, changeEffects } from './changeTypes/_module.mjs';
export { BaseEffect, BeastformEffect, HordeEffect }; export { BaseEffect, BeastformEffect, HordeEffect };

View file

@ -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) * "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 { export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; 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 { return {
...super.defineSchema(), ...super.defineSchema(),
changes: new fields.ArrayField( changes: new fields.ArrayField(
new fields.SchemaField({ new fields.TypedSchemaField(
key: new fields.StringField({ required: true }), { ...changeTypes, ...baseChanges },
type: new fields.StringField({ { initial: baseChanges.add.getInitialValue() }
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()
})
), ),
duration: new fields.SchemaField({ duration: new fields.SchemaField({
type: new fields.StringField({ type: new fields.StringField({
@ -86,6 +101,23 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
return true; 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() { static getDefaultObject() {
return { return {
name: 'New Effect', 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);
}
} }

View file

@ -0,0 +1,9 @@
import Armor from './armor.mjs';
export const changeEffects = {
armor: Armor.changeEffect
};
export const changeTypes = {
armor: Armor
};

View 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)
);
}
}

View file

@ -6,6 +6,7 @@ import DhCreature from './creature.mjs';
import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs'; import { attributeField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
import { ActionField } from '../fields/actionField.mjs'; import { ActionField } from '../fields/actionField.mjs';
import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs'; import DHCharacterSettings from '../../applications/sheets-configs/character-settings.mjs';
import { getArmorSources } from '../../helpers/utils.mjs';
export default class DhCharacter extends DhCreature { export default class DhCharacter extends DhCreature {
/**@override */ /**@override */
@ -41,17 +42,16 @@ export default class DhCharacter extends DhCreature {
label: 'DAGGERHEART.GENERAL.proficiency' label: 'DAGGERHEART.GENERAL.proficiency'
}), }),
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }), evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
armorScore: new fields.NumberField({ integer: true, initial: 0, label: 'DAGGERHEART.GENERAL.armorScore' }),
damageThresholds: new fields.SchemaField({ damageThresholds: new fields.SchemaField({
severe: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
}),
major: new fields.NumberField({ major: new fields.NumberField({
integer: true, integer: true,
initial: 0, initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold' label: 'DAGGERHEART.GENERAL.DamageThresholds.majorThreshold'
}),
severe: new fields.NumberField({
integer: true,
initial: 0,
label: 'DAGGERHEART.GENERAL.DamageThresholds.severeThreshold'
}) })
}), }),
experiences: new fields.TypedObjectField( experiences: new fields.TypedObjectField(
@ -465,6 +465,101 @@ export default class DhCharacter extends DhCreature {
} }
} }
async updateArmorValue({ value: armorChange = 0, clear = false }) {
if (armorChange === 0 && !clear) return;
const increasing = armorChange >= 0;
let remainingChange = Math.abs(armorChange);
const orderedSources = getArmorSources(this.parent).filter(s => !s.disabled);
const handleArmorData = (embeddedUpdates, doc, armorData) => {
let usedArmorChange = 0;
if (clear) {
usedArmorChange -= armorData.current;
} else {
if (increasing) {
const remainingArmor = armorData.max - armorData.current;
usedArmorChange = Math.min(remainingChange, remainingArmor);
remainingChange -= usedArmorChange;
} else {
const changeChange = Math.min(armorData.current, remainingChange);
usedArmorChange -= changeChange;
remainingChange -= changeChange;
}
}
if (!usedArmorChange) return usedArmorChange;
else {
if (!embeddedUpdates[doc.id]) embeddedUpdates[doc.id] = { doc: doc, updates: [] };
return usedArmorChange;
}
};
const armorUpdates = [];
const effectUpdates = [];
for (const { document: armorSource } of orderedSources) {
const usedArmorChange = handleArmorData(
armorSource.type === 'armor' ? armorUpdates : effectUpdates,
armorSource.parent,
armorSource.type === 'armor' ? armorSource.system.armor : armorSource.system.armorData
);
if (!usedArmorChange) continue;
if (armorSource.type === 'armor') {
armorUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
'system.armor.current': armorSource.system.armor.current + usedArmorChange
});
} else {
effectUpdates[armorSource.parent.id].updates.push({
'_id': armorSource.id,
'system.changes': armorSource.system.changes.map(change => ({
...change,
value:
change.type === 'armor'
? {
...change.value,
current: armorSource.system.armorChange.value.current + usedArmorChange
}
: change.value
}))
});
}
if (remainingChange === 0 && !clear) break;
}
const armorUpdateValues = Object.values(armorUpdates);
for (const [index, { doc, updates }] of armorUpdateValues.entries())
await doc.updateEmbeddedDocuments('Item', updates, { render: index === armorUpdateValues.length - 1 });
const effectUpdateValues = Object.values(effectUpdates);
for (const [index, { doc, updates }] of effectUpdateValues.entries())
await doc.updateEmbeddedDocuments('ActiveEffect', updates, {
render: index === effectUpdateValues.length - 1
});
}
async updateArmorEffectValue({ uuid, value }) {
const source = await foundry.utils.fromUuid(uuid);
if (source.type === 'armor') {
await source.update({
'system.armor.current': source.system.armor.current + value
});
} else {
const effectValue = source.system.armorChange.value;
await source.update({
'system.changes': [
{
...source.system.armorChange,
value: { ...effectValue, current: effectValue.current + value }
}
]
});
}
}
get sheetLists() { get sheetLists() {
const ancestryFeatures = [], const ancestryFeatures = [],
communityFeatures = [], communityFeatures = [],
@ -588,6 +683,10 @@ export default class DhCharacter extends DhCreature {
prepareBaseData() { prepareBaseData() {
super.prepareBaseData(); super.prepareBaseData();
this.armorScore = {
max: this.armor?.system.armor.max ?? 0,
value: this.armor?.system.armor.current ?? 0
};
this.evasion += this.class.value?.system?.evasion ?? 0; this.evasion += this.class.value?.system?.evasion ?? 0;
const currentLevel = this.levelData.level.current; const currentLevel = this.levelData.level.current;
@ -637,14 +736,12 @@ export default class DhCharacter extends DhCreature {
} }
} }
const armor = this.armor;
this.armorScore = armor ? armor.system.baseScore : 0;
this.damageThresholds = { this.damageThresholds = {
major: armor major: this.armor
? armor.system.baseThresholds.major + this.levelData.level.current ? this.armor.system.baseThresholds.major + this.levelData.level.current
: this.levelData.level.current, : this.levelData.level.current,
severe: armor severe: this.armor
? armor.system.baseThresholds.severe + this.levelData.level.current ? this.armor.system.baseThresholds.severe + this.levelData.level.current
: this.levelData.level.current * 2 : this.levelData.level.current * 2
}; };
@ -679,9 +776,8 @@ export default class DhCharacter extends DhCreature {
this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait; this.attack.roll.trait = this.rules.attack.roll.trait ?? this.attack.roll.trait;
this.resources.armor = { this.resources.armor = {
...this.armorScore,
label: 'DAGGERHEART.GENERAL.armor', label: 'DAGGERHEART.GENERAL.armor',
value: this.armor?.system?.marks?.value ?? 0,
max: this.armorScore,
isReversed: true isReversed: true
}; };
@ -757,7 +853,6 @@ export default class DhCharacter extends DhCreature {
static migrateData(source) { static migrateData(source) {
if (typeof source.scars === 'object') source.scars = 0; if (typeof source.scars === 'object') source.scars = 0;
if (source.resources?.hope?.max) source.scars = Math.max(6 - source.resources.hope.max, 0);
return super.migrateData(source); return super.migrateData(source);
} }

View file

@ -19,7 +19,14 @@ 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 }), armor: new fields.SchemaField({
current: new fields.NumberField({ integer: true, min: 0, initial: 0 }),
max: new fields.NumberField({ required: true, integer: true, initial: 0 })
}),
baseThresholds: new fields.SchemaField({
major: new fields.NumberField({ integer: true, initial: 0 }),
severe: 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({
@ -28,14 +35,7 @@ export default class DHArmor extends AttachableItem {
effectIds: new fields.ArrayField(new fields.StringField({ required: true })), effectIds: new fields.ArrayField(new fields.StringField({ required: true })),
actionIds: new fields.ArrayField(new fields.StringField({ required: true })) actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
}) })
), )
marks: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true })
}),
baseThresholds: new fields.SchemaField({
major: new fields.NumberField({ integer: true, initial: 0 }),
severe: new fields.NumberField({ integer: true, initial: 0 })
})
}; };
} }
@ -151,13 +151,20 @@ export default class DHArmor extends AttachableItem {
} }
} }
/** @inheritDoc */
static migrateDocumentData(source) {
if (!source.system.armor) {
source.system.armor = { current: source.system.marks?.value ?? 0, max: source.system.baseScore ?? 0 };
}
}
/** /**
* Generates a list of localized tags based on this item's type-specific properties. * Generates a list of localized tags based on this item's type-specific properties.
* @returns {string[]} An array of localized tag strings. * @returns {string[]} An array of localized tag strings.
*/ */
_getTags() { _getTags() {
const tags = [ const tags = [
`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`, `${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`,
`${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}`
]; ];
@ -169,9 +176,7 @@ export default class DHArmor extends AttachableItem {
* @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects. * @returns {(string | { value: string, icons: string[] })[]} An array of localized strings and damage label objects.
*/ */
_getLabels() { _getLabels() {
const labels = []; const labels = [`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.armor.max}`];
if (this.baseScore)
labels.push(`${game.i18n.localize('DAGGERHEART.ITEMS.Armor.baseScore')}: ${this.baseScore}`);
return labels; return labels;
} }

View file

@ -222,9 +222,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation); const autoSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation);
const armorChanged = const armorChanged =
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value; changed.system?.armor?.current !== undefined && changed.system.armor.current !== this.armor.current;
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') { if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
const armorData = getScrollTextData(this.parent.parent, changed.system.marks, 'armor'); const armorChangeValue = changed.system.armor.current - this.armor.current;
const armorData = getScrollTextData(
this.parent.parent,
{ value: armorChangeValue + this.parent.parent.system.armorScore.value },
'armor'
);
options.scrollingTextData = [armorData]; options.scrollingTextData = [armorData];
} }

View file

@ -143,6 +143,7 @@ export default class DHRoll extends Roll {
return foundry.applications.handlebars.renderTemplate(template, { return foundry.applications.handlebars.renderTemplate(template, {
...chatData, ...chatData,
parent: chatData.parent, parent: chatData.parent,
targetMode: chatData.targetMode,
metagamingSettings metagamingSettings
}); });
} }

View file

@ -1,5 +1,5 @@
import { itemAbleRollParse } from '../helpers/utils.mjs'; import { itemAbleRollParse } from '../helpers/utils.mjs';
import { RefreshType, socketEvent } from '../systemRegistration/socket.mjs'; import { RefreshType } from '../systemRegistration/socket.mjs';
export default class DhActiveEffect extends foundry.documents.ActiveEffect { export default class DhActiveEffect extends foundry.documents.ActiveEffect {
/* -------------------------------------------- */ /* -------------------------------------------- */
@ -8,6 +8,8 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
/**@override */ /**@override */
get isSuppressed() { get isSuppressed() {
if (this.system.isSuppressed === true) return true;
// If this is a copied effect from an attachment, never suppress it // If this is a copied effect from an attachment, never suppress it
// (These effects have attachmentSource metadata) // (These effects have attachmentSource metadata)
if (this.flags?.daggerheart?.attachmentSource) { if (this.flags?.daggerheart?.attachmentSource) {
@ -15,7 +17,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;
} }
@ -76,7 +78,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
throw new Error('The array of sub-types to restrict to must not be empty.'); throw new Error('The array of sub-types to restrict to must not be empty.');
} }
const creatableEffects = ['base']; const creatableEffects = types || ['base'];
const documentTypes = this.TYPES.filter(type => creatableEffects.includes(type)).map(type => { const documentTypes = this.TYPES.filter(type => creatableEffects.includes(type)).map(type => {
const labelKey = `TYPES.ActiveEffect.${type}`; const labelKey = `TYPES.ActiveEffect.${type}`;
const label = game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type; const label = game.i18n.has(labelKey) ? game.i18n.localize(labelKey) : type;
@ -161,9 +163,9 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
super.applyChangeField(model, change, field); super.applyChangeField(model, change, field);
} }
_applyLegacy(actor, change, changes) { static _applyChangeUnguided(actor, change, changes, options) {
change.value = DhActiveEffect.getChangeValue(actor, change, change.effect); change.value = DhActiveEffect.getChangeValue(actor, change, change.effect);
super._applyLegacy(actor, change, changes); super._applyChangeUnguided(actor, change, changes, options);
} }
static getChangeValue(model, change, effect) { static getChangeValue(model, change, effect) {

View file

@ -598,8 +598,7 @@ export default class DhpActor extends Actor {
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value; const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
const canUseArmor = const canUseArmor =
this.system.armor && this.system.armorScore.value < this.system.armorScore.max &&
this.system.armor.system.marks.value < this.system.armorScore &&
type.every(t => this.system.armorApplicableDamageTypes[t] === true); type.every(t => this.system.armorApplicableDamageTypes[t] === true);
const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => { const canUseStress = Object.keys(stressDamageReduction).reduce((acc, x) => {
const rule = stressDamageReduction[x]; const rule = stressDamageReduction[x];
@ -639,12 +638,7 @@ export default class DhpActor extends Actor {
const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id); const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
if (hpDamage?.value) { if (hpDamage?.value) {
hpDamage.value = this.convertDamageToThreshold(hpDamage.value); hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
if ( if (this.type === 'character' && !isDirect && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
this.type === 'character' &&
!isDirect &&
this.system.armor &&
this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)
) {
const armorSlotResult = await this.owner.query( const armorSlotResult = await this.owner.query(
'armorSlot', 'armorSlot',
{ {
@ -657,12 +651,10 @@ export default class DhpActor extends Actor {
} }
); );
if (armorSlotResult) { if (armorSlotResult) {
const { modifiedDamage, armorSpent, stressSpent } = armorSlotResult; const { modifiedDamage, armorChanges, stressSpent } = armorSlotResult;
updates.find(u => u.key === 'hitPoints').value = modifiedDamage; updates.find(u => u.key === 'hitPoints').value = modifiedDamage;
if (armorSpent) { for (const armorChange of armorChanges) {
const armorUpdate = updates.find(u => u.key === 'armor'); updates.push({ value: armorChange.amount, key: 'armor', uuid: armorChange.uuid });
if (armorUpdate) armorUpdate.value += armorSpent;
else updates.push({ value: armorSpent, key: 'armor' });
} }
if (stressSpent) { if (stressSpent) {
const stressUpdate = updates.find(u => u.key === 'stress'); const stressUpdate = updates.find(u => u.key === 'stress');
@ -809,12 +801,8 @@ export default class DhpActor extends Actor {
); );
break; break;
case 'armor': case 'armor':
if (this.system.armor?.system?.marks) { if (!r.uuid) this.system.updateArmorValue(r);
updates.armor.resources['system.marks.value'] = Math.max( else this.system.updateArmorEffectValue(r);
Math.min(valueFunc(this.system.armor.system.marks, r), this.system.armorScore),
0
);
}
break; break;
default: default:
if (this.system.resources?.[r.key]) { if (this.system.resources?.[r.key]) {
@ -1030,4 +1018,20 @@ export default class DhpActor extends Actor {
return allTokens; return allTokens;
} }
/**@inheritdoc */
*allApplicableEffects({ noSelfArmor, noTransferArmor } = {}) {
for (const effect of this.effects) {
if (!noSelfArmor || effect.type !== 'armor') yield effect;
}
for (const item of this.items) {
for (const effect of item.effects) {
if (effect.transfer && (!noTransferArmor || effect.type !== 'armor')) yield effect;
}
}
}
applyActiveEffects(phase) {
super.applyActiveEffects(phase);
}
} }

View file

@ -230,4 +230,14 @@ export default class DHItem extends foundry.documents.Item {
async _preDelete() { async _preDelete() {
this.deleteTriggers(); this.deleteTriggers();
} }
/** @inheritDoc */
static migrateData(source) {
const documentClass = game.system.api.data.items[`DH${source.type?.capitalize()}`];
if (documentClass?.migrateDocumentData) {
documentClass.migrateDocumentData(source);
}
return super.migrateData(source);
}
} }

View file

@ -49,7 +49,8 @@ export default class RegisterHandlebarsHelpers {
} }
static damageSymbols(damageParts) { static damageSymbols(damageParts) {
const symbols = [...new Set(damageParts.map(x => x.type))].map(p => CONFIG.DH.GENERAL.damageTypes[p].icon); const allTypes = [...new Set([...damageParts].flatMap(x => Array.from(x.type)))];
const symbols = allTypes.map(p => CONFIG.DH.GENERAL.damageTypes[p].icon);
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`)); return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
} }

View file

@ -743,3 +743,67 @@ export function getUnusedDamageTypes(parts) {
return acc; return acc;
}, []); }, []);
} }
/** Returns resolved armor sources ordered by application order */
export function getArmorSources(actor) {
const rawArmorSources = Array.from(actor.allApplicableEffects()).filter(x => x.system.armorData);
if (actor.system.armor) rawArmorSources.push(actor.system.armor);
const data = rawArmorSources.map(doc => {
// Get the origin item. Since the actor is already loaded, it should already be cached
// Consider the relative function versions if this causes an issue
const isItem = doc instanceof Item;
const origin = isItem ? doc : doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc.parent;
return {
origin,
name: origin.name,
document: doc,
data: doc.system.armor ?? doc.system.armorData,
disabled: !!doc.disabled || !!doc.isSuppressed
};
});
return sortBy(data, ({ origin }) => {
switch (origin?.type) {
case 'class':
case 'subclass':
case 'ancestry':
case 'community':
case 'feature':
case 'domainCard':
return 2;
case 'loot':
case 'consumable':
return 3;
case 'character':
return 4;
case 'weapon':
return 5;
case 'armor':
return 6;
default:
return 1;
}
});
}
/**
* Returns an array sorted by a function that returns a thing to compare, or an array to compare in order
* Similar to lodash's sortBy function.
*/
export function sortBy(arr, fn) {
const directCompare = (a, b) => (a < b ? -1 : a > b ? 1 : 0);
const cmp = (a, b) => {
const resultA = fn(a);
const resultB = fn(b);
if (Array.isArray(resultA) && Array.isArray(resultB)) {
for (let idx = 0; idx < Math.min(resultA.length, resultB.length); idx++) {
const result = directCompare(resultA[idx], resultB[idx]);
if (result !== 0) return result;
}
return 0;
}
return directCompare(resultA, resultB);
};
return arr.sort(cmp);
}

View file

@ -48,6 +48,7 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/ui/chat/parts/button-part.hbs', 'systems/daggerheart/templates/ui/chat/parts/button-part.hbs',
'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs', 'systems/daggerheart/templates/ui/itemBrowser/itemContainer.hbs',
'systems/daggerheart/templates/scene/dh-config.hbs', 'systems/daggerheart/templates/scene/dh-config.hbs',
'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs' 'systems/daggerheart/templates/settings/appearance-settings/diceSoNiceTab.hbs',
'systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs'
]); ]);
}; };

View file

@ -246,6 +246,101 @@ export async function runMigrations() {
lastMigrationVersion = '1.6.0'; lastMigrationVersion = '1.6.0';
} }
if (foundry.utils.isNewerVersion('2.0.0', lastMigrationVersion)) {
const progress = game.system.api.applications.ui.DhProgress.createMigrationProgress(0);
const progressBuffer = 50;
//#region Data Setup
const lockedPacks = [];
const itemPacks = game.packs.filter(x => x.metadata.type === 'Item');
const actorPacks = game.packs.filter(x => x.metadata.type === 'Actor');
const getIndexes = async (packs, type) => {
const indexes = [];
for (const pack of packs) {
const indexValues = pack.index.values().reduce((acc, index) => {
if (!type || index.type === type) acc.push(index.uuid);
return acc;
}, []);
if (indexValues.length && pack.locked) {
lockedPacks.push(pack.collection);
await pack.configure({ locked: false });
}
indexes.push(...indexValues);
}
return indexes;
};
const itemEntries = await getIndexes(itemPacks);
const characterEntries = await getIndexes(actorPacks, 'character');
const worldItems = game.items;
const worldCharacters = game.actors.filter(x => x.type === 'character');
/* The async fetches are the mainstay of time. Leaving 1 progress for the sync logic */
const newMax = itemEntries.length + characterEntries.length + progressBuffer;
progress.updateMax(newMax);
const compendiumItems = [];
for (const entry of itemEntries) {
const item = await foundry.utils.fromUuid(entry);
compendiumItems.push(item);
progress.advance();
}
const compendiumCharacters = [];
for (const entry of characterEntries) {
const character = await foundry.utils.fromUuid(entry);
compendiumCharacters.push(character);
progress.advance();
}
//#endregion
/* Migrate existing effects modifying armor, creating new Armor Effects instead */
const migrateEffects = async entity => {
for (const effect of entity.effects) {
if (effect.system.changes.every(x => x.key !== 'system.armorScore')) continue;
effect.update({
'system.changes': effect.system.changes.map(change => ({
...change,
type: change.key === 'system.armorScore' ? 'armor' : change.type,
value: change.key === 'system.armorScore' ? { current: 0, max: change.value } : change.value
}))
});
}
};
/* Migrate existing armors effects */
const migrateItems = async items => {
for (const item of items) {
await migrateEffects(item);
}
};
await migrateItems([...compendiumItems, ...worldItems]);
progress.advance({ by: progressBuffer / 2 });
for (const actor of [...compendiumCharacters, ...worldCharacters]) {
await migrateEffects(actor);
await migrateItems(actor.items);
}
progress.advance({ by: progressBuffer / 2 });
for (let packId of lockedPacks) {
const pack = game.packs.get(packId);
await pack.configure({ locked: true });
}
progress.close();
lastMigrationVersion = '2.0.0';
}
//#endregion //#endregion
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion); await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);

View file

@ -92,34 +92,28 @@
"name": "Armorer", "name": "Armorer",
"type": "base", "type": "base",
"system": { "system": {
"rangeDependence": { "changes": [
"enabled": false, {
"type": "withinRange", "type": "armor",
"target": "hostile", "phase": "initial",
"range": "melee" "priority": 20,
} "value": {
"max": "1",
"interaction": "active"
}
}
]
}, },
"_id": "cED730OjuMW5haJR", "_id": "tJw2JIPcT9hEMRXg",
"img": "icons/tools/hand/hammer-and-nail.webp", "img": "icons/tools/hand/hammer-and-nail.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "1",
"priority": null
}
],
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">While youre wearing armor, gain a +1 bonus to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Armor Score</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</p>", "description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">While youre wearing armor, gain a +1 bonus to your </span><span style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\" class=\"tooltip-convert\">Armor Score</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</span></p>",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,
@ -129,7 +123,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!cy8GjBPGc9w9RaGO.cED730OjuMW5haJR" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!cy8GjBPGc9w9RaGO.tJw2JIPcT9hEMRXg"
} }
], ],
"ownership": { "ownership": {

View file

@ -19,7 +19,52 @@
} }
}, },
"flags": {}, "flags": {},
"effects": [], "effects": [
{
"name": "Bare Bones",
"type": "base",
"system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "3 + @system.traits.strength.value",
"interaction": "inactive",
"damageThresholds": {
"major": "9 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )",
"severe": "19 + (@tier - 1) * 5 + max(0, (@tier -2) * 2 )"
}
}
}
]
},
"_id": "FCsgz7Tdsw6QUzBs",
"img": "icons/magic/control/buff-strength-muscle-damage-orange.webp",
"disabled": false,
"start": null,
"duration": {
"value": null,
"units": "seconds",
"expiry": null,
"expired": false
},
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.565);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">You have a base Armor Score of 3 + your Strength.</span></p>",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"showIcon": 1,
"folder": null,
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"_key": "!items.effects!l5D9kq901JDESaXw.FCsgz7Tdsw6QUzBs"
}
],
"ownership": { "ownership": {
"default": 0, "default": 0,
"MQSznptE5yLT7kj8": 3 "MQSznptE5yLT7kj8": 3

View file

@ -105,7 +105,7 @@
}, },
"effects": [ "effects": [
{ {
"_id": "LdcT1nrkd5ORCU4n", "_id": "ptYT10JZ2WJHvFMd",
"onSave": false "onSave": false
} }
], ],
@ -252,7 +252,7 @@
"img": "icons/magic/defensive/shield-barrier-glowing-triangle-blue.webp", "img": "icons/magic/defensive/shield-barrier-glowing-triangle-blue.webp",
"origin": "Compendium.daggerheart.domains.Item.YtZzYBtR0yLPPA93", "origin": "Compendium.daggerheart.domains.Item.YtZzYBtR0yLPPA93",
"transfer": false, "transfer": false,
"_id": "LdcT1nrkd5ORCU4n", "_id": "ptYT10JZ2WJHvFMd",
"type": "base", "type": "base",
"system": { "system": {
"rangeDependence": { "rangeDependence": {
@ -263,10 +263,12 @@
}, },
"changes": [ "changes": [
{ {
"key": "system.armorScore", "type": "armor",
"value": 1, "phase": "initial",
"priority": null, "priority": 20,
"type": "add" "value": {
"max": "1"
}
} }
], ],
"duration": { "duration": {
@ -298,7 +300,7 @@
}, },
"showIcon": 1, "showIcon": 1,
"folder": null, "folder": null,
"_key": "!items.effects!YtZzYBtR0yLPPA93.LdcT1nrkd5ORCU4n" "_key": "!items.effects!YtZzYBtR0yLPPA93.ptYT10JZ2WJHvFMd"
} }
], ],
"ownership": { "ownership": {

View file

@ -93,32 +93,25 @@
"name": "Valor-Touched", "name": "Valor-Touched",
"type": "base", "type": "base",
"system": { "system": {
"rangeDependence": { "changes": [
"enabled": false, {
"type": "withinRange", "type": "armor",
"target": "hostile", "phase": "initial",
"range": "melee" "priority": 20,
} "value": {
"max": "1"
}
}
]
}, },
"_id": "H9lgIqqp1imSNOv9", "_id": "Ma8Zp005QYKPWIEN",
"img": "icons/magic/control/control-influence-rally-purple.webp", "img": "icons/magic/control/control-influence-rally-purple.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "1",
"priority": null
}
],
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "<ul><li class=\"vertical-card-list-found\"><p>+1 bonus to your Armor Score</p></li><li class=\"vertical-card-list-found\"><p>When you mark 1 or more Hit Points without marking an Armor Slot, clear an Armor Slot.</p></li></ul>", "description": "<ul><li class=\"vertical-card-list-found\"><p>+1 bonus to your Armor Score</p></li><li class=\"vertical-card-list-found\"><p>When you mark 1 or more Hit Points without marking an Armor Slot, clear an Armor Slot.</p></li></ul>",
"origin": null, "origin": null,
@ -130,7 +123,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!k1AtYd3lSchIymBr.H9lgIqqp1imSNOv9" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!k1AtYd3lSchIymBr.Ma8Zp005QYKPWIEN"
} }
], ],
"ownership": { "ownership": {

View file

@ -5,6 +5,10 @@
"_id": "LzLOJ9EVaHWAjoq9", "_id": "LzLOJ9EVaHWAjoq9",
"img": "icons/equipment/chest/breastplate-banded-steel-gold.webp", "img": "icons/equipment/chest/breastplate-banded-steel-gold.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "crIbCb9NZ4K0VpoU", "_id": "crIbCb9NZ4K0VpoU",
"img": "icons/equipment/chest/breastplate-layered-steel-grey.webp", "img": "icons/equipment/chest/breastplate-layered-steel-grey.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "epkAmlZVk7HOfUUT", "_id": "epkAmlZVk7HOfUUT",
"img": "icons/equipment/chest/breastplate-purple.webp", "img": "icons/equipment/chest/breastplate-purple.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "itSOp2GCyem0f7oM", "_id": "itSOp2GCyem0f7oM",
"img": "icons/equipment/chest/breastplate-layered-leather-blue.webp", "img": "icons/equipment/chest/breastplate-layered-leather-blue.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -1,75 +0,0 @@
{
"folder": "tI3bfr6Sgi16Z7zm",
"name": "Bare Bones",
"type": "armor",
"_id": "ITAjcigTcUw5pMCN",
"img": "icons/magic/control/buff-strength-muscle-damage.webp",
"system": {
"description": "<p></p><p class=\"Body-Foundation\">When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:</p><ul><li class=\"vertical-card-list-found\"><em><strong>Tier 1:</strong></em> 9/19</li><li class=\"vertical-card-list-found\"><em><strong>Tier 2:</strong></em> 11/24</li><li class=\"vertical-card-list-found\"><em><strong>Tier 3:</strong></em> 13/31</li><li class=\"vertical-card-list-found\"><em><strong>Tier 4:</strong></em> 15/38</li></ul>",
"actions": {},
"attached": [],
"tier": 1,
"equipped": false,
"baseScore": 3,
"armorFeatures": [],
"marks": {
"value": 0
},
"baseThresholds": {
"major": 9,
"severe": 19
}
},
"effects": [
{
"name": "Bare Bones",
"type": "base",
"system": {
"rangeDependence": {
"enabled": false,
"type": "withinRange",
"target": "hostile",
"range": "melee"
}
},
"_id": "8ze88zUwdkQSKKJq",
"img": "icons/magic/control/buff-strength-muscle-damage.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "@system.traits.strength.value",
"priority": 21
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "<p></p><p class=\"Body-Foundation\">When you choose not to equip armor, you have a base Armor Score of 3 + your Strength and use the following as your base damage thresholds:</p><ul><li class=\"vertical-card-list-found\"><em><strong>Tier 1:</strong></em> 9/19</li><li class=\"vertical-card-list-found\"><em><strong>Tier 2:</strong></em> 11/24</li><li class=\"vertical-card-list-found\"><em><strong>Tier 3:</strong></em> 13/31</li><li class=\"vertical-card-list-found\"><em><strong>Tier 4:</strong></em> 15/38</li></ul>",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"_key": "!items.effects!ITAjcigTcUw5pMCN.8ze88zUwdkQSKKJq"
}
],
"sort": 0,
"ownership": {
"default": 0,
"MQSznptE5yLT7kj8": 3
},
"flags": {},
"_key": "!items!ITAjcigTcUw5pMCN"
}

View file

@ -5,6 +5,10 @@
"_id": "WuoVwZA53XRAIt6d", "_id": "WuoVwZA53XRAIt6d",
"img": "icons/equipment/chest/breastplate-layered-gold.webp", "img": "icons/equipment/chest/breastplate-layered-gold.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "mNN6pvcsS10ChrWF", "_id": "mNN6pvcsS10ChrWF",
"img": "icons/equipment/chest/breastplate-collared-steel-grey.webp", "img": "icons/equipment/chest/breastplate-collared-steel-grey.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "haULhuEg37zUUvhb", "_id": "haULhuEg37zUUvhb",
"img": "icons/equipment/chest/breastplate-scale-grey.webp", "img": "icons/equipment/chest/breastplate-scale-grey.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "vMJxEWz1srfwMsoj", "_id": "vMJxEWz1srfwMsoj",
"img": "icons/equipment/chest/robe-collared-blue.webp", "img": "icons/equipment/chest/robe-collared-blue.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "mdQ69eFHyAQUDmE7", "_id": "mdQ69eFHyAQUDmE7",
"img": "icons/equipment/chest/breastplate-rivited-red.webp", "img": "icons/equipment/chest/breastplate-rivited-red.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": { "actions": {
"J1MCpcfXByKaSSgx": { "J1MCpcfXByKaSSgx": {

View file

@ -5,6 +5,10 @@
"_id": "hAY6UgdGT7dj22Pr", "_id": "hAY6UgdGT7dj22Pr",
"img": "icons/equipment/chest/robe-layered-red.webp", "img": "icons/equipment/chest/robe-layered-red.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 7
},
"description": "", "description": "",
"actions": { "actions": {
"8PD5JQuS05IA6HJT": { "8PD5JQuS05IA6HJT": {

View file

@ -5,6 +5,10 @@
"_id": "Q6LxmtFetDDkoZVZ", "_id": "Q6LxmtFetDDkoZVZ",
"img": "icons/equipment/chest/breastplate-sculpted-green.webp", "img": "icons/equipment/chest/breastplate-sculpted-green.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "bcQUh4QG3qFX0Vx6", "_id": "bcQUh4QG3qFX0Vx6",
"img": "icons/equipment/chest/breastplate-layered-gilded-orange.webp", "img": "icons/equipment/chest/breastplate-layered-gilded-orange.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": { "actions": {
"L8mHf4A8SylyxsMH": { "L8mHf4A8SylyxsMH": {

View file

@ -5,6 +5,10 @@
"_id": "7emTSt6nhZuTlvt5", "_id": "7emTSt6nhZuTlvt5",
"img": "icons/equipment/chest/breastplate-layered-steel.webp", "img": "icons/equipment/chest/breastplate-layered-steel.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "UdUJNa31WxFW2noa", "_id": "UdUJNa31WxFW2noa",
"img": "icons/equipment/chest/breastplate-collared-steel.webp", "img": "icons/equipment/chest/breastplate-collared-steel.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "yJFp1bfpecDcStVK", "_id": "yJFp1bfpecDcStVK",
"img": "icons/equipment/chest/vest-leather-tattered-white.webp", "img": "icons/equipment/chest/vest-leather-tattered-white.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 3
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "dvyQeUVRLc9y6rnt", "_id": "dvyQeUVRLc9y6rnt",
"img": "icons/equipment/chest/breastplate-gorget-steel.webp", "img": "icons/equipment/chest/breastplate-gorget-steel.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": { "actions": {
"IzM88FIxQ35P5VB2": { "IzM88FIxQ35P5VB2": {

View file

@ -5,6 +5,10 @@
"_id": "K5WkjS0NGqHYmhU3", "_id": "K5WkjS0NGqHYmhU3",
"img": "icons/equipment/chest/breastplate-metal-scaled-grey.webp", "img": "icons/equipment/chest/breastplate-metal-scaled-grey.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "9f7RozpPTqrzJS1m", "_id": "9f7RozpPTqrzJS1m",
"img": "icons/equipment/chest/breastplate-cuirass-steel-grey.webp", "img": "icons/equipment/chest/breastplate-cuirass-steel-grey.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "jphnMZjnS2FkOH3s", "_id": "jphnMZjnS2FkOH3s",
"img": "icons/equipment/chest/breastplate-quilted-brown.webp", "img": "icons/equipment/chest/breastplate-quilted-brown.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "t91M61pSCMKStTNt", "_id": "t91M61pSCMKStTNt",
"img": "icons/equipment/chest/breastplate-banded-simple-leather-brown.webp", "img": "icons/equipment/chest/breastplate-banded-simple-leather-brown.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "tzZntboNtHL5C6VM", "_id": "tzZntboNtHL5C6VM",
"img": "icons/equipment/chest/breastplate-layered-leather-brown-silver.webp", "img": "icons/equipment/chest/breastplate-layered-leather-brown-silver.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "nibfdNtp2PtxvbVz", "_id": "nibfdNtp2PtxvbVz",
"img": "icons/equipment/chest/breastplate-layered-leather-brown.webp", "img": "icons/equipment/chest/breastplate-layered-leather-brown.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 3
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "EsIN5OLKe9ZYFNXZ", "_id": "EsIN5OLKe9ZYFNXZ",
"img": "icons/equipment/chest/breastplate-banded-blue.webp", "img": "icons/equipment/chest/breastplate-banded-blue.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 7
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "SXWjUR2aUR6bYvdl", "_id": "SXWjUR2aUR6bYvdl",
"img": "icons/equipment/chest/breastplate-layered-steel-blue-gold.webp", "img": "icons/equipment/chest/breastplate-layered-steel-blue-gold.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 7
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "c6tMXz4rPf9ioQrf", "_id": "c6tMXz4rPf9ioQrf",
"img": "icons/equipment/chest/breastplate-layered-leather-blue-gold.webp", "img": "icons/equipment/chest/breastplate-layered-leather-blue-gold.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "Tptgl5WOj76TyFn7", "_id": "Tptgl5WOj76TyFn7",
"img": "icons/equipment/chest/breastplate-layered-gilded-black.webp", "img": "icons/equipment/chest/breastplate-layered-gilded-black.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "AQzU2RsqS5V5bd1v", "_id": "AQzU2RsqS5V5bd1v",
"img": "icons/equipment/chest/coat-collared-red.webp", "img": "icons/equipment/chest/coat-collared-red.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "tN8kAeBvNKM3EBFo", "_id": "tN8kAeBvNKM3EBFo",
"img": "icons/equipment/chest/breastplate-banded-leather-purple.webp", "img": "icons/equipment/chest/breastplate-banded-leather-purple.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": { "actions": {
"QRTnCYxJfuJHdnyV": { "QRTnCYxJfuJHdnyV": {

View file

@ -5,6 +5,10 @@
"_id": "P4qAEDJUoNLgVRsA", "_id": "P4qAEDJUoNLgVRsA",
"img": "icons/magic/symbols/rune-sigil-red-orange.webp", "img": "icons/magic/symbols/rune-sigil-red-orange.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": { "actions": {
"37KLF2bim9nRdPTU": { "37KLF2bim9nRdPTU": {

View file

@ -5,6 +5,10 @@
"_id": "tHlBUDQC24YMZqd6", "_id": "tHlBUDQC24YMZqd6",
"img": "icons/equipment/chest/breastplate-layered-leather-black.webp", "img": "icons/equipment/chest/breastplate-layered-leather-black.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 4
},
"description": "", "description": "",
"actions": { "actions": {
"Nn33zCIcWe6LQMDH": { "Nn33zCIcWe6LQMDH": {

View file

@ -5,6 +5,10 @@
"_id": "8X16lJQ3xltTwynm", "_id": "8X16lJQ3xltTwynm",
"img": "icons/equipment/chest/breastplate-layered-leather-green.webp", "img": "icons/equipment/chest/breastplate-layered-leather-green.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 8
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "QjwsIhXKqnlvRBMv", "_id": "QjwsIhXKqnlvRBMv",
"img": "icons/equipment/chest/breastplate-banded-steel-studded.webp", "img": "icons/equipment/chest/breastplate-banded-steel-studded.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": {}, "actions": {},
"attached": [], "attached": [],

View file

@ -5,6 +5,10 @@
"_id": "PSW3BxCGmtLeWOxM", "_id": "PSW3BxCGmtLeWOxM",
"img": "icons/equipment/chest/robe-layered-purple.webp", "img": "icons/equipment/chest/robe-layered-purple.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 5
},
"description": "", "description": "",
"actions": { "actions": {
"Ch6IhuPewBeseGez": { "Ch6IhuPewBeseGez": {

View file

@ -5,6 +5,10 @@
"_id": "OvzgUTYy2RCN85vV", "_id": "OvzgUTYy2RCN85vV",
"img": "icons/equipment/chest/breastplate-collared-steel-green.webp", "img": "icons/equipment/chest/breastplate-collared-steel-green.webp",
"system": { "system": {
"armor": {
"current": 0,
"max": 6
},
"description": "", "description": "",
"actions": { "actions": {
"sY3W5JYspN5Du5ag": { "sY3W5JYspN5Du5ag": {

View file

@ -1,12 +0,0 @@
{
"type": "Item",
"folder": null,
"name": "Special",
"color": null,
"sorting": "a",
"_id": "tI3bfr6Sgi16Z7zm",
"description": "",
"sort": 0,
"flags": {},
"_key": "!folders!tI3bfr6Sgi16Z7zm"
}

View file

@ -113,26 +113,26 @@
"name": "Protective", "name": "Protective",
"description": "<p>Add the item's Tier to your Armor Score</p>", "description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp", "img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [ "_id": "7285CRGdZfHCEtT2",
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "i5HfkF5aKQuUCTEG",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier"
}
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -143,7 +143,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!hiEOGF2reabGLUoi.i5HfkF5aKQuUCTEG" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!hiEOGF2reabGLUoi.7285CRGdZfHCEtT2"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,25 +113,25 @@
"name": "Barrier", "name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp", "img": "icons/skills/melee/shield-block-bash-blue.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier + 1"
},
{
"key": "system.evasion",
"mode": 2,
"value": "-1"
}
],
"_id": "87gedjJZGdFY81Mt", "_id": "87gedjJZGdFY81Mt",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"key": "system.evasion",
"type": "add",
"value": -1,
"phase": "initial",
"priority": 0
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null "units": "seconds",
"expiry": null,
"expired": false
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -142,7 +142,49 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!OfOzQbs4hg6QbfTG.87gedjJZGdFY81Mt" "_key": "!items.effects!OfOzQbs4hg6QbfTG.87gedjJZGdFY81Mt"
},
{
"name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp",
"_id": "J0f7zqqOr61ADpdy",
"type": "base",
"system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier + 1"
}
}
]
},
"disabled": false,
"duration": {
"value": null,
"units": "seconds",
"expiry": null,
"expired": false
},
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!OfOzQbs4hg6QbfTG.J0f7zqqOr61ADpdy"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,26 +113,26 @@
"name": "Protective", "name": "Protective",
"description": "<p>Add the item's Tier to your Armor Score</p>", "description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp", "img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [ "_id": "pZCrWd7zLTarvEQK",
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "cXWSV50apzaNQkdA",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier"
}
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -143,7 +143,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!DlinEBGZfIlvreO3.cXWSV50apzaNQkdA" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!DlinEBGZfIlvreO3.pZCrWd7zLTarvEQK"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,25 +113,25 @@
"name": "Barrier", "name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp", "img": "icons/skills/melee/shield-block-bash-blue.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier + 1"
},
{
"key": "system.evasion",
"mode": 2,
"value": "-1"
}
],
"_id": "tkNEA1PO3jEFhKCa", "_id": "tkNEA1PO3jEFhKCa",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"key": "system.evasion",
"type": "add",
"value": -1,
"phase": "initial",
"priority": 0
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null "units": "seconds",
"expiry": null,
"expired": false
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -142,7 +142,49 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!bxt3NsbMqTSdI5ab.tkNEA1PO3jEFhKCa" "_key": "!items.effects!bxt3NsbMqTSdI5ab.tkNEA1PO3jEFhKCa"
},
{
"name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp",
"_id": "XugJeHJdnC6IymSa",
"type": "base",
"system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier + 1"
}
}
]
},
"disabled": false,
"duration": {
"value": null,
"units": "seconds",
"expiry": null,
"expired": false
},
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!bxt3NsbMqTSdI5ab.XugJeHJdnC6IymSa"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,20 +113,26 @@
"name": "Protective", "name": "Protective",
"description": "Add your character's Tier to your Armor Score", "description": "Add your character's Tier to your Armor Score",
"img": "icons/skills/melee/shield-block-gray-orange.webp", "img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [ "_id": "vnR4Zhnb0rOqwrFw",
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier"
}
],
"_id": "qTxADRsQnKiYfOiQ",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier"
}
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null "units": "seconds",
"expiry": null,
"expired": false
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -137,7 +143,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!ijWppQzSOqVCb3rE.qTxADRsQnKiYfOiQ" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!ijWppQzSOqVCb3rE.vnR4Zhnb0rOqwrFw"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,26 +113,26 @@
"name": "Protective", "name": "Protective",
"description": "<p>Add the item's Tier to your Armor Score</p>", "description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp", "img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [ "_id": "EixxJrRHyc6kj3Wg",
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "Z2p00q5h6x6seXys",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier"
}
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -143,7 +143,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!A28WL9E2lJ3iLZHW.Z2p00q5h6x6seXys" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!A28WL9E2lJ3iLZHW.EixxJrRHyc6kj3Wg"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,25 +113,25 @@
"name": "Barrier", "name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp", "img": "icons/skills/melee/shield-block-bash-blue.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier + 1"
},
{
"key": "system.evasion",
"mode": 2,
"value": "-1"
}
],
"_id": "lBJMzxdGO2nJdTQS", "_id": "lBJMzxdGO2nJdTQS",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"key": "system.evasion",
"type": "add",
"value": -1,
"phase": "initial",
"priority": 0
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null "units": "seconds",
"expiry": null,
"expired": false
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -142,7 +142,49 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!MaJIROht7A9LxIZx.lBJMzxdGO2nJdTQS" "_key": "!items.effects!MaJIROht7A9LxIZx.lBJMzxdGO2nJdTQS"
},
{
"name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp",
"_id": "1fgUIaXl6VQrhP7j",
"type": "base",
"system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier + 1"
}
}
]
},
"disabled": false,
"duration": {
"value": null,
"units": "seconds",
"expiry": null,
"expired": false
},
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!MaJIROht7A9LxIZx.1fgUIaXl6VQrhP7j"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,26 +113,26 @@
"name": "Protective", "name": "Protective",
"description": "<p>Add the item's Tier to your Armor Score</p>", "description": "<p>Add the item's Tier to your Armor Score</p>",
"img": "icons/skills/melee/shield-block-gray-orange.webp", "img": "icons/skills/melee/shield-block-gray-orange.webp",
"changes": [ "_id": "eV4lFIpQMiKERj4U",
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier",
"priority": null
}
],
"_id": "M70a81e0Mg66jHRL",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier"
}
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -143,7 +143,10 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"_key": "!items.effects!mxwWKDujgsRcZWPT.M70a81e0Mg66jHRL" "start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!mxwWKDujgsRcZWPT.eV4lFIpQMiKERj4U"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,32 +113,31 @@
"name": "Double Duty", "name": "Double Duty",
"description": "+1 to Armor Score; +1 to primary weapon damage within Melee range", "description": "+1 to Armor Score; +1 to primary weapon damage within Melee range",
"img": "icons/skills/melee/sword-shield-stylized-white.webp", "img": "icons/skills/melee/sword-shield-stylized-white.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "1"
},
{
"key": "system.bonuses.damage.primaryWeapon.bonus",
"mode": 2,
"value": "1"
}
],
"system": { "system": {
"rangeDependence": { "rangeDependence": {
"enabled": true, "enabled": true,
"range": "melee", "range": "melee",
"target": "hostile", "target": "hostile",
"type": "withinRange" "type": "withinRange"
} },
"changes": [
{
"key": "system.bonuses.damage.primaryWeapon.bonus",
"type": "add",
"value": 1,
"phase": "initial",
"priority": 0
}
]
}, },
"_id": "d3TJtlpoHBCztbom", "_id": "d3TJtlpoHBCztbom",
"type": "base", "type": "base",
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null "units": "seconds",
"expiry": null,
"expired": false
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -149,7 +148,49 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!vzyzFwLUniWZV1rt.d3TJtlpoHBCztbom" "_key": "!items.effects!vzyzFwLUniWZV1rt.d3TJtlpoHBCztbom"
},
{
"name": "Double Duty",
"description": "+1 to Armor Score; +1 to primary weapon damage within Melee range",
"img": "icons/skills/melee/sword-shield-stylized-white.webp",
"system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "1"
}
}
]
},
"_id": "mvUY9LGfwICak7cE",
"type": "base",
"disabled": false,
"duration": {
"value": null,
"units": "seconds",
"expiry": null,
"expired": false
},
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!vzyzFwLUniWZV1rt.mvUY9LGfwICak7cE"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -113,25 +113,25 @@
"name": "Barrier", "name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion", "description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp", "img": "icons/skills/melee/shield-block-bash-blue.webp",
"changes": [
{
"key": "system.armorScore",
"mode": 2,
"value": "ITEM.@system.tier + 1"
},
{
"key": "system.evasion",
"mode": 2,
"value": "-1"
}
],
"_id": "8r0TcKWXboFV0eqS", "_id": "8r0TcKWXboFV0eqS",
"type": "base", "type": "base",
"system": {}, "system": {
"changes": [
{
"key": "system.evasion",
"type": "add",
"value": -1,
"phase": "initial",
"priority": 0
}
]
},
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null "units": "seconds",
"expiry": null,
"expired": false
}, },
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
@ -142,7 +142,49 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!C9aWpK1shVMWP4m5.8r0TcKWXboFV0eqS" "_key": "!items.effects!C9aWpK1shVMWP4m5.8r0TcKWXboFV0eqS"
},
{
"name": "Barrier",
"description": "Gain Weapon Tier + 1 to Armor Score; -1 to Evasion",
"img": "icons/skills/melee/shield-block-bash-blue.webp",
"_id": "tLRc4UAnGuIq7er3",
"type": "base",
"system": {
"changes": [
{
"type": "armor",
"phase": "initial",
"priority": 20,
"value": {
"max": "ITEM.@system.tier + 1"
}
}
]
},
"disabled": false,
"duration": {
"value": null,
"units": "seconds",
"expiry": null,
"expired": false
},
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null
},
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!items.effects!C9aWpK1shVMWP4m5.tLRc4UAnGuIq7er3"
} }
], ],
"sort": 0, "sort": 0,

View file

@ -35,7 +35,10 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
align-items: center; align-items: center;
width: 100%;
&.full-width {
width: 100%;
}
} }
.padded { .padded {
@ -45,6 +48,7 @@
.armor-title { .armor-title {
margin: 0; margin: 0;
white-space: nowrap; white-space: nowrap;
width: 100%;
} }
.resources-container { .resources-container {
@ -62,12 +66,17 @@
.mark-selection { .mark-selection {
display: flex; display: flex;
align-items: center; flex-direction: column;
width: 100%; width: 100%;
margin: 0; margin: 0;
h4 {
margin: 0;
}
.mark-selection-inner { .mark-selection-inner {
display: flex; display: flex;
justify-content: center;
gap: 8px; gap: 8px;
.mark-container { .mark-container {
@ -91,6 +100,19 @@
opacity: 0.2; opacity: 0.2;
} }
&.spent {
::after {
position: absolute;
content: '/';
color: red;
font-weight: 700;
font-size: 1.8em;
left: -1px;
top: -7px;
rotate: 13deg;
}
}
.fa-shield { .fa-shield {
position: relative; position: relative;
right: 0.5px; right: 0.5px;

View file

@ -31,5 +31,70 @@
text-align: center; text-align: center;
} }
} }
.armor-change-container {
padding-top: 0;
padding-bottom: 4px;
row-gap: 0;
legend {
display: flex;
align-items: center;
padding-left: 3px;
}
header {
padding: 0;
left: -0.25rem; // TODO: Find why this header is offset 0.25rem to the right so this can be removed.
}
header,
ol {
grid-template-columns: 5rem 7rem 12rem 4rem;
}
.damage-thresholds-container {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
.damage-threshold-title {
display: flex;
align-items: center;
justify-content: center;
gap: 8px;
&::before,
&::after {
content: '';
flex: 1;
height: 2px;
}
&::before {
background: linear-gradient(90deg, rgba(0, 0, 0, 0) 0%, light-dark(@dark-blue, @golden) 100%);
}
&::after {
background: linear-gradient(90deg, light-dark(@dark-blue, @golden) 0%, rgba(0, 0, 0, 0) 100%);
}
span {
font-size: var(--font-size-18);
}
}
.form-group {
flex-direction: column;
gap: 0;
label {
color: inherit;
line-height: 16px;
}
}
}
}
} }
} }

View file

@ -276,6 +276,23 @@
} }
} }
.slot-label {
.slot-value-container {
position: relative;
display: flex;
align-items: center;
justify-content: center;
width: 100%;
i {
position: absolute;
right: 0;
font-size: 12px;
color: light-dark(@beige, @dark-blue);
}
}
}
.status-value { .status-value {
padding: 0 5px; padding: 0 5px;
} }
@ -298,6 +315,12 @@
border-radius: 3px; border-radius: 3px;
background: light-dark(@dark-blue, @golden); background: light-dark(@dark-blue, @golden);
clip-path: none; clip-path: none;
cursor: pointer;
display: flex;
align-items: center;
gap: 4px;
border: 1px solid transparent;
transition: all 0.3s ease;
h4 { h4 {
font-weight: bold; font-weight: bold;
@ -306,6 +329,20 @@
color: light-dark(@beige, @dark-blue); color: light-dark(@beige, @dark-blue);
font-size: var(--font-size-12); font-size: var(--font-size-12);
} }
i {
font-size: 12px;
color: light-dark(@beige, @dark-blue);
}
&:hover {
background: light-dark(@light-black, @dark-blue);
border: 1px solid light-dark(@dark-blue, @golden);
h4, i {
color: light-dark(@dark-blue, @golden);
}
}
} }
.slot-value { .slot-value {
position: absolute; position: absolute;
@ -343,7 +380,7 @@
} }
} }
} }
.slot-label { .slot-label {
display: flex; display: flex;
align-items: center; align-items: center;
color: light-dark(@beige, @dark-blue); color: light-dark(@beige, @dark-blue);
@ -355,6 +392,17 @@
font-size: var(--font-size-12); font-size: var(--font-size-12);
flex-wrap: wrap; flex-wrap: wrap;
justify-content: center; justify-content: center;
border: 1px solid transparent;
transition: all 0.3s ease;
&:hover {
background: light-dark(@light-black, @dark-blue);
border: 1px solid light-dark(@dark-blue, @golden);
.label, .value, i {
color: light-dark(@dark-blue, @golden);
}
}
.label { .label {
padding-right: 1px; padding-right: 1px;

View file

@ -1,4 +1,5 @@
@import './tooltip/tooltip.less'; @import './tooltip/tooltip.less';
@import './tooltip/armorManagement.less';
@import './tooltip/battlepoints.less'; @import './tooltip/battlepoints.less';
@import './tooltip/bordered-tooltip.less'; @import './tooltip/bordered-tooltip.less';
@import './tooltip/domain-cards.less'; @import './tooltip/domain-cards.less';

View file

@ -1,7 +1,19 @@
@import '../../utils/fonts.less';
@import '../../utils/colors.less';
.bordered-tooltip.locked-tooltip .daggerheart.armor-management-container { .bordered-tooltip.locked-tooltip .daggerheart.armor-management-container {
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 16px; gap: 10px;
padding-bottom: 10px;
h3 {
font-family: @font-subtitle;
margin: 0;
border: none;
font-weight: normal;
font-size: var(--font-size-20);
}
.armor-source-container { .armor-source-container {
display: flex; display: flex;
@ -10,16 +22,16 @@
gap: 4px; gap: 4px;
.armor-source-label { .armor-source-label {
font-size: var(--font-size-24); font-family: @font-body;
font-weight: bold; margin: 0;
} }
.status-bar { .status-bar {
display: flex; display: flex;
justify-content: center; justify-content: center;
position: relative; position: relative;
width: 80px; width: 100%;
height: 20px; height: 30px;
.status-value { .status-value {
position: absolute; position: absolute;
@ -27,8 +39,8 @@
padding: 0 5px; padding: 0 5px;
font-size: 1rem; font-size: 1rem;
align-items: center; align-items: center;
width: 80px; width: 100%;
height: 20px; height: 30px;
justify-content: center; justify-content: center;
text-align: center; text-align: center;
z-index: 2; z-index: 2;
@ -36,13 +48,13 @@
input[type='number'] { input[type='number'] {
background: transparent; background: transparent;
font-size: 1rem; font-size: 1.2rem;
width: 30px; width: 30px;
height: 15px;
text-align: center; text-align: center;
border: none; border: none;
outline: 2px solid transparent; outline: 2px solid transparent;
color: @beige; color: @beige;
font-family: @font-body;
&.bar-input { &.bar-input {
padding: 0; padding: 0;
@ -50,6 +62,7 @@
backdrop-filter: none; backdrop-filter: none;
background: transparent; background: transparent;
transition: all 0.3s ease; transition: all 0.3s ease;
height: 25px;
&:hover, &:hover,
&:focus { &:focus {
@ -60,14 +73,16 @@
} }
.bar-label { .bar-label {
font-family: @font-body;
width: 40px; width: 40px;
font-size: 1.2rem;
} }
} }
.progress-bar { .progress-bar {
position: absolute; position: absolute;
appearance: none; appearance: none;
width: 80px; width: 100%;
height: 20px; height: 30px;
border: 1px solid light-dark(@dark-blue, @golden); border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px; border-radius: 6px;
z-index: 1; z-index: 1;
@ -97,4 +112,30 @@
} }
} }
} }
.slot-bar {
display: flex;
flex-wrap: wrap;
gap: 4px;
padding: 5px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
z-index: 1;
background: @dark-blue;
align-items: center;
justify-content: center;
color: light-dark(@dark-blue, @golden);
min-height: 30px;
width: 100%;
.armor-slot {
cursor: pointer;
transition: all 0.3s ease;
font-size: var(--font-size-12);
.fa-shield-halved {
color: light-dark(@dark-blue-40, @golden-40);
}
}
}
} }

View file

@ -5,7 +5,7 @@
"version": "2.0.0", "version": "2.0.0",
"compatibility": { "compatibility": {
"minimum": "14.355", "minimum": "14.355",
"verified": "14.356", "verified": "14.357",
"maximum": "14" "maximum": "14"
}, },
"authors": [ "authors": [

View file

@ -7,53 +7,57 @@
<div class="section-container padded"> <div class="section-container padded">
<div class="resources-container"> <div class="resources-container">
<div class="resource-container"> <div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.armorMarks"}}</h4> <h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.maxUseableArmor"}}</h4>
<div class="markers-subtitle">{{armorMarks}}/{{armorScore}}</div> <div class="markers-subtitle">{{availableArmor}}</div>
</div> </div>
{{#if this.stress}}
<div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stress"}}</h4>
<div class="markers-subtitle">{{this.stress.value}}/{{this.stress.max}}</div>
</div>
{{/if}}
</div> </div>
</div> </div>
<div class="section-container"> <div class="section-container full-width">
<h4 class="mark-selection divider"> {{#each marks.armor as |source|}}
<div class="mark-selection-inner"> <div class="mark-selection">
{{#each marks.armor}} <h4 class="divider">{{source.label}}</h4>
<div <div class="mark-selection-inner">
class="mark-container {{#if this.selected}}selected{{/if}}" {{#each source.marks}}
data-action="setMarks" data-key="{{@key}}" data-type="armor" <a
> class="mark-container {{#if this.selected}}selected{{/if}} {{#if this.spent}}spent{{/if}} {{#if this.disabled}}inactive{{/if}}"
<i class="fa-solid fa-shield"></i> data-action="setMarks" data-type="armor" data-path="{{concat "armor." @../key ".marks." @key}}" data-id="{{@key}}"
</div> {{#if this.disabled}}disabled{{/if}}
{{/each}} >
<i class="fa-solid fa-shield"></i>
</a>
{{/each}}
</div>
</div>
{{/each}}
{{#if usesStressArmor}}
<div class="mark-selection">
<h4 class="divider">{{localize "Stress"}}</h4>
<div class="mark-selection-inner">
{{#each marks.stress}} {{#each marks.stress}}
<div <div
class="mark-container {{#if this.selected}}selected{{/if}} {{#if (not @root.basicMarksUsed)}}inactive{{/if}}" class="mark-container {{#if this.selected}}selected{{/if}} {{#if (not @root.basicMarksUsed)}}inactive{{/if}}"
{{#if @root.basicMarksUsed}}data-action="setMarks"{{/if}} data-key="{{@key}}" data-type="stress" data-tooltip="{{#if @root.basicMarksUsed}}{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.armorWithStress"}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.unncessaryStress"}}{{/if}}" {{#if @root.basicMarksUsed}}data-action="setMarks"{{/if}} data-type="stress" data-path="{{concat "stress." @key}}" data-tooltip="{{#if @root.basicMarksUsed}}{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.armorWithStress"}}{{else}}{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.unncessaryStress"}}{{/if}}"
> >
<i class="fa-solid fa-bolt"></i> <i class="fa-solid fa-bolt"></i>
</div> </div>
{{/each}} {{/each}}
</div>
</div> </div>
</h4> {{/if}}
<div class="markers-subtitle bold">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.usedMarks"}}</div>
</div> </div>
{{#if availableStressReductions}} {{#if availableStressReductions}}
<div class="resources-container"> <div class="resources-container">
<div class="resource-container"> <div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}</h4> <h4 class="armor-title divider">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stressReduction"}}</h4>
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#each availableStressReductions}} {{#each availableStressReductions}}
<div class="section-container"> <div class="section-container">
<h4 class="chip-container divider"> <h4 class="chip-container">
<div class="chip-inner-container selectable {{#if (or this.any (eq this.from @root.currentDamage))}}active{{/if}} {{#if this.selected}}selected{{/if}}" data-action="useStressReduction" data-reduction="{{@key}}"> <div class="chip-inner-container selectable {{#if (or this.any (eq this.from @root.currentDamage))}}active{{/if}} {{#if this.selected}}selected{{/if}}" data-action="useStressReduction" data-reduction="{{@key}}">
{{#if this.any}} {{#if this.any}}
{{localize "DAGGERHEART.GENERAL.any"}} {{localize "DAGGERHEART.GENERAL.any"}}
@ -74,7 +78,7 @@
{{#if reduceSeverity}} {{#if reduceSeverity}}
<div class="resources-container"> <div class="resources-container">
<div class="resource-container"> <div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.reduceSeverity" nr=reduceSeverity}}</h4> <h4 class="armor-title divider">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.reduceSeverity" nr=reduceSeverity}}</h4>
</div> </div>
</div> </div>
{{/if}} {{/if}}
@ -82,7 +86,7 @@
{{#if thresholdImmunities}} {{#if thresholdImmunities}}
<div class="resources-container"> <div class="resources-container">
<div class="resource-container"> <div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}</h4> <h4 class="armor-title divider">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.thresholdImmunities"}}</h4>
</div> </div>
</div> </div>
{{/if}} {{/if}}

View file

@ -3,7 +3,9 @@
<input type="text" class="effect-change-input" name="{{change.keyPath}}" value="{{change.key}}" /> <input type="text" class="effect-change-input" name="{{change.keyPath}}" value="{{change.key}}" />
</div> </div>
<div class="type"> <div class="type">
{{formInput fields.type name=change.typePath value=change.type localize=true}} <select name="{{change.typePath}}">
{{selectOptions types selected=change.type localize=true}}
</select>
</div> </div>
<div class="value"> <div class="value">
{{formInput fields.value name=change.valuePath value=change.value elementType="input"}} {{formInput fields.value name=change.valuePath value=change.value elementType="input"}}

View file

@ -13,4 +13,11 @@
{{{change}}} {{{change}}}
{{/each}} {{/each}}
</ol> </ol>
<fieldset class="armor-change-container">
<legend>{{localize "DAGGERHEART.GENERAL.armor"}} <input type="checkbox" class="armor-change-checkbox" data-index="{{typedChanges.armor.index}}" {{checked typedChanges.armor}} /></legend>
{{#if typedChanges.armor}}
{{> "systems/daggerheart/templates/sheets/activeEffect/typeChanges/armorChange.hbs" typedChanges.armor fields=@root.systemFields.changes.element.types.armor.fields}}
{{/if}}
</fieldset>
</section> </section>

View file

@ -0,0 +1,28 @@
<header>
<div>{{localize "EFFECT.FIELDS.changes.element.value.label"}}</div>
<div>{{localize "DAGGERHEART.GENERAL.max"}}</div>
<div>{{localize "DAGGERHEART.EFFECTS.ChangeTypes.armor.FIELDS.interaction.label"}}</div>
<div>{{localize "EFFECT.FIELDS.changes.element.priority.label"}}</div>
</header>
<ol class="scrollable">
<li data-index="{{index}}">
<input type="hidden" name="{{concat "system.changes." index ".type"}}" value="{{type}}" />
<input type="hidden" name="{{concat "system.changes." index ".phase"}}" value="{{phase}}" />
{{formInput fields.value.fields.current name=(concat "system.changes." index ".value.current") value=value.current data-dtype="Number"}}
{{formInput fields.value.fields.max name=(concat "system.changes." index ".value.max") value=value.max data-dtype="Number"}}
{{formInput fields.value.fields.interaction name=(concat "system.changes." index ".value.interaction") value=value.interaction localize=true}}
{{formInput fields.priority name=(concat "system.changes." index ".priority") value=priority}}
</li>
</ol>
<div class="damage-thresholds-container">
<div class="damage-threshold-title">
<span>{{localize "DAGGERHEART.GENERAL.DamageThresholds.title"}}</span>
<input type="checkbox" class="armor-damage-thresholds-checkbox" data-index="{{index}}" {{checked value.damageThresholds}} />
</div>
{{#if value.damageThresholds}}
<div class="flexrow">
{{formGroup fields.value.fields.damageThresholds.fields.major name=(concat "system.changes." index ".value.damageThresholds.major") value=value.damageThresholds.major localize=true }}
{{formGroup fields.value.fields.damageThresholds.fields.severe name=(concat "system.changes." index ".value.damageThresholds.severe") value=value.damageThresholds.severe localize=true }}
</div>
{{/if}}
</div>

View file

@ -18,6 +18,7 @@
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=true
hideResources=true hideResources=true
disabled=true
}} }}
</div> </div>
</section> </section>

View file

@ -30,14 +30,14 @@
</div> </div>
</div> </div>
{{#if document.system.armor.system.marks}} {{#if document.system.armorScore.max}}
<div class="status-bar armor-slots"> <div class="status-bar armor-slots">
{{#if useResourcePips}} {{#if useResourcePips}}
<div class='slot-value'> <div class='slot-value'>
<div class="slot-bar"> <div class="slot-bar">
{{#times document.system.armorScore}} {{#times document.system.armorScore.max}}
<a class='armor-slot' data-action='toggleArmor' data-value="{{add this 1}}"> <a class='armor-slot' data-action='toggleArmor' data-value="{{add this 1}}">
{{#if (gte ../document.system.armor.system.marks.value (add this 1))}} {{#if (gte ../document.system.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
{{else}} {{else}}
<i class="fa-solid fa-shield-halved"></i> <i class="fa-solid fa-shield-halved"></i>
@ -45,25 +45,29 @@
</a> </a>
{{/times}} {{/times}}
</div> </div>
<div class="slot-label"> <a class="slot-label" data-action="toggleArmorMangement">
<span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span> <span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span>
<span class="value">{{document.system.armor.system.marks.value}} / {{document.system.armorScore}}</span> <div class="slot-value-container">
</div> <span class="value">{{document.system.armorScore.value}} / {{document.system.armorScore.max}}</span>
<i class="fa-solid fa-gear"></i>
</div>
</a>
</div> </div>
{{else}} {{else}}
<div class='status-value'> <div class='status-value'>
<input class="bar-input armor-marks-input" value="{{document.system.armor.system.marks.value}}" type="number"> <input class="bar-input armor-marks-input" value="{{document.system.armorScore.value}}" type="number">
<span>/</span> <span>/</span>
<span class="bar-label">{{document.system.armorScore}}</span> <span class="bar-label">{{document.system.armorScore.max}}</span>
</div> </div>
<progress <progress
class='progress-bar stress-color' class='progress-bar stress-color'
value='{{document.system.armor.system.marks.value}}' value='{{document.system.armorScore.value}}'
max='{{document.system.armorScore}}' max='{{document.system.armorScore.max}}'
></progress> ></progress>
<div class="status-label"> <a class="status-label" data-action="toggleArmorMangement">
<h4>{{localize "DAGGERHEART.GENERAL.armorSlots"}}</h4> <h4>{{localize "DAGGERHEART.GENERAL.armorSlots"}}</h4>
</div> <i class="fa-solid fa-gear"></i>
</a>
{{/if}} {{/if}}
</div> </div>
{{else}} {{else}}

View file

@ -52,13 +52,12 @@
</div> </div>
</div> </div>
{{#if actor.system.armor.system.marks}} {{#if actor.system.armorScore.max}}
<div class="slot-section"> <div class="slot-section">
<div class="slot-bar"> <div class="slot-bar">
{{#times actor.system.armorScore}} {{#times actor.system.armorScore.max}}
<a class='armor-slot' <a class='armor-slot' data-action='toggleArmorSlot' data-actor-id="{{actor.id}}" data-value="{{add this 1}}">
data-action='toggleArmorSlot' data-actor-id="{{actor.uuid}}" data-item-uuid="{{actor.system.armor.uuid}}" data-value="{{add this 1}}"> {{#if (gte actor.system.armorScore.value (add this 1))}}
{{#if (gte actor.system.armor.system.marks.value (add this 1))}}
<i class="fa-solid fa-shield"></i> <i class="fa-solid fa-shield"></i>
{{else}} {{else}}
<i class="fa-solid fa-shield-halved"></i> <i class="fa-solid fa-shield-halved"></i>
@ -68,7 +67,7 @@
</div> </div>
<div class="slot-label"> <div class="slot-label">
<span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span> <span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span>
<span class="value">{{actor.system.armor.system.marks.value}} / {{actor.system.armorScore}}</span> <span class="value">{{actor.system.armorScore.value}} / {{actor.system.armorScore.max}}</span>
</div> </div>
</div> </div>
{{/if}} {{/if}}

View file

@ -9,7 +9,7 @@
</h3> </h3>
<h3> <h3>
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}: {{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{source.system.baseScore}} {{source.system.armor.max}}
<span>-</span> <span>-</span>
{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}: {{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}:
{{source.system.baseThresholds.major}} {{source.system.baseThresholds.major}}

Some files were not shown because too many files have changed in this diff Show more