mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
284 - Armor/Weapon Feature Improvements (#292)
* Added parsing of effect values from Item data model. Almost finished with itemConfig. * Added the last to itemConfig * Fixed armor * ContextMenu localization fixes * Better tooltips for tagify * Corrected resource logic
This commit is contained in:
parent
eae4f12910
commit
b3e7c6b9b2
51 changed files with 3043 additions and 2310 deletions
12
lang/en.json
12
lang/en.json
|
|
@ -486,14 +486,10 @@
|
|||
"name": "Impenetrable",
|
||||
"description": "Once per short rest, when you would mark your last Hit Point, you can instead mark a Stress."
|
||||
},
|
||||
"magic": {
|
||||
"name": "Magic",
|
||||
"magical": {
|
||||
"name": "Magical",
|
||||
"description": "You can't mark an Armor Slot to reduce physical damage."
|
||||
},
|
||||
"painful": {
|
||||
"name": "Painful",
|
||||
"description": "Each time you mark an Armor Slot, you must mark a Stress."
|
||||
},
|
||||
"physical": {
|
||||
"name": "Physical",
|
||||
"description": "You can't mark an Armor Slot to reduce magic damage."
|
||||
|
|
@ -897,6 +893,10 @@
|
|||
"name": "Scary",
|
||||
"description": "On a successful attack, the target must mark a Stress."
|
||||
},
|
||||
"selfCorrecting": {
|
||||
"name": "Self Correcting",
|
||||
"description": "When you roll a 1 on a damage die, it deals 6 damage instead."
|
||||
},
|
||||
"serrated": {
|
||||
"name": "Serrated",
|
||||
"description": "When you roll a 1 on a damage die, it deals 8 damage instead."
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ import { damageKeyToNumber, getDamageLabel } from '../../helpers/utils.mjs';
|
|||
const { DialogV2, ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class DamageReductionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
constructor(resolve, reject, actor, damage) {
|
||||
constructor(resolve, reject, actor, damage, damageType) {
|
||||
super({});
|
||||
|
||||
this.resolve = resolve;
|
||||
|
|
@ -11,23 +11,30 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
this.actor = actor;
|
||||
this.damage = damage;
|
||||
|
||||
const maxArmorMarks = Math.min(
|
||||
const canApplyArmor = actor.system.armorApplicableDamageTypes[damageType];
|
||||
const maxArmorMarks = canApplyArmor
|
||||
? Math.min(
|
||||
actor.system.armorScore - actor.system.armor.system.marks.value,
|
||||
actor.system.rules.maxArmorMarked.total
|
||||
);
|
||||
actor.system.rules.damageReduction.maxArmorMarked.total
|
||||
)
|
||||
: 0;
|
||||
|
||||
const armor = [...Array(maxArmorMarks).keys()].reduce((acc, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
return acc;
|
||||
}, {});
|
||||
const stress = [...Array(actor.system.rules.maxArmorMarked.stressExtra ?? 0).keys()].reduce((acc, _) => {
|
||||
const stress = [...Array(actor.system.rules.damageReduction.maxArmorMarked.stressExtra ?? 0).keys()].reduce(
|
||||
(acc, _) => {
|
||||
acc[foundry.utils.randomID()] = { selected: false };
|
||||
return acc;
|
||||
}, {});
|
||||
},
|
||||
{}
|
||||
);
|
||||
this.marks = { armor, stress };
|
||||
|
||||
this.availableStressReductions = Object.keys(actor.system.rules.stressDamageReduction).reduce((acc, key) => {
|
||||
const dr = actor.system.rules.stressDamageReduction[key];
|
||||
this.availableStressReductions = Object.keys(actor.system.rules.damageReduction.stressDamageReduction).reduce(
|
||||
(acc, key) => {
|
||||
const dr = actor.system.rules.damageReduction.stressDamageReduction[key];
|
||||
if (dr.enabled) {
|
||||
if (acc === null) acc = {};
|
||||
|
||||
|
|
@ -41,7 +48,9 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
}
|
||||
|
||||
return acc;
|
||||
}, null);
|
||||
},
|
||||
null
|
||||
);
|
||||
}
|
||||
|
||||
get title() {
|
||||
|
|
@ -90,7 +99,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
|
||||
context.armorScore = this.actor.system.armorScore;
|
||||
context.armorMarks = currentMarks;
|
||||
context.basicMarksUsed = selectedArmorMarks.length === this.actor.system.rules.maxArmorMarked.total;
|
||||
context.basicMarksUsed =
|
||||
selectedArmorMarks.length === this.actor.system.rules.damageReduction.maxArmorMarked.total;
|
||||
|
||||
const stressReductionStress = this.availableStressReductions
|
||||
? stressReductions.reduce((acc, red) => acc + red.cost, 0)
|
||||
|
|
@ -122,12 +132,15 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
getDamageInfo = () => {
|
||||
const selectedArmorMarks = Object.values(this.marks.armor).filter(x => x.selected);
|
||||
const selectedStressMarks = Object.values(this.marks.stress).filter(x => x.selected);
|
||||
const stressReductions = Object.values(this.availableStressReductions ?? {}).filter(red => red.selected);
|
||||
const stressReductions = this.availableStressReductions
|
||||
? Object.values(this.availableStressReductions).filter(red => red.selected)
|
||||
: [];
|
||||
const currentMarks =
|
||||
this.actor.system.armor.system.marks.value + selectedArmorMarks.length + selectedStressMarks.length;
|
||||
|
||||
const currentDamage =
|
||||
this.damage - selectedArmorMarks.length - selectedStressMarks.length - stressReductions.length;
|
||||
const armorMarkReduction =
|
||||
selectedArmorMarks.length * this.actor.system.rules.damageReduction.increasePerArmorMark;
|
||||
const currentDamage = this.damage - armorMarkReduction - selectedStressMarks.length - stressReductions.length;
|
||||
|
||||
return { selectedArmorMarks, selectedStressMarks, stressReductions, currentMarks, currentDamage };
|
||||
};
|
||||
|
|
@ -216,11 +229,11 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
|
|||
await super.close({});
|
||||
}
|
||||
|
||||
static async armorStackQuery({actorId, damage}) {
|
||||
static async armorStackQuery({ actorId, damage, type }) {
|
||||
return new Promise(async (resolve, reject) => {
|
||||
const actor = await fromUuid(actorId);
|
||||
if (!actor || !actor?.isOwner) reject();
|
||||
new DamageReductionDialog(resolve, reject, actor, damage).render({ force: true });
|
||||
})
|
||||
new DamageReductionDialog(resolve, reject, actor, damage, type).render({ force: true });
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
|||
|
||||
switch (partId) {
|
||||
case 'settings':
|
||||
context.features = this.document.system.features.map(x => x.value);
|
||||
context.features = this.document.system.armorFeatures.map(x => x.value);
|
||||
break;
|
||||
}
|
||||
|
||||
|
|
@ -47,6 +47,6 @@ export default class ArmorSheet extends DHBaseItemSheet {
|
|||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||
*/
|
||||
static async #onFeatureSelect(selectedOptions) {
|
||||
await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) });
|
||||
await this.document.update({ 'system.armorFeatures': selectedOptions.map(x => ({ value: x.value })) });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
|||
super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
case 'settings':
|
||||
context.features = this.document.system.features.map(x => x.value);
|
||||
context.features = this.document.system.weaponFeatures.map(x => x.value);
|
||||
context.systemFields.attack.fields = this.document.system.attack.schema.fields;
|
||||
break;
|
||||
}
|
||||
|
|
@ -45,6 +45,6 @@ export default class WeaponSheet extends DHBaseItemSheet {
|
|||
* @param {Array<Object>} selectedOptions - The currently selected tag objects.
|
||||
*/
|
||||
static async #onFeatureSelect(selectedOptions) {
|
||||
await this.document.update({ 'system.features': selectedOptions.map(x => ({ value: x.value })) });
|
||||
await this.document.update({ 'system.weaponFeatures': selectedOptions.map(x => ({ value: x.value })) });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -59,13 +59,13 @@ export const damageTypes = {
|
|||
id: 'physical',
|
||||
label: 'DAGGERHEART.CONFIG.DamageType.physical.name',
|
||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation',
|
||||
icon: ["fa-hand-fist"]
|
||||
icon: ['fa-hand-fist']
|
||||
},
|
||||
magical: {
|
||||
id: 'magical',
|
||||
label: 'DAGGERHEART.CONFIG.DamageType.magical.name',
|
||||
abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation',
|
||||
icon: ["fa-wand-sparkles"]
|
||||
icon: ['fa-wand-sparkles']
|
||||
}
|
||||
};
|
||||
|
||||
|
|
|
|||
File diff suppressed because it is too large
Load diff
|
|
@ -161,7 +161,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
updateSource['range'] = parent?.system?.attack?.range;
|
||||
updateSource['roll'] = {
|
||||
useDefault: true
|
||||
}
|
||||
};
|
||||
} else {
|
||||
if (parent?.system?.trait) {
|
||||
updateSource['roll'] = {
|
||||
|
|
@ -337,7 +337,8 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
const resources = config.costs
|
||||
.filter(c => c.enabled !== false)
|
||||
.map(c => {
|
||||
return { type: c.type, value: (c.total ?? c.value) * -1 };
|
||||
const resource = this.actor.system.resources[c.type];
|
||||
return { type: c.type, value: (c.total ?? c.value) * (resource.hasOwnProperty('maxTotal') ? 1 : -1) };
|
||||
});
|
||||
|
||||
await this.actor.modifyResource(resources);
|
||||
|
|
@ -382,15 +383,21 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
|
|||
const realCosts = this.getRealCosts(costs),
|
||||
hasFearCost = realCosts.findIndex(c => c.type === 'fear');
|
||||
if (hasFearCost > -1) {
|
||||
const fearCost = realCosts.splice(hasFearCost, 1);
|
||||
const fearCost = realCosts.splice(hasFearCost, 1)[0];
|
||||
if (
|
||||
!game.user.isGM ||
|
||||
fearCost[0].total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||
fearCost.total > game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear)
|
||||
)
|
||||
return false;
|
||||
}
|
||||
|
||||
/* maxTotal is a sign that the resource is inverted, IE it counts upwards instead of down */
|
||||
const resources = this.actor.system.resources;
|
||||
return realCosts.reduce(
|
||||
(a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value),
|
||||
(a, c) =>
|
||||
a && resources[c.type].hasOwnProperty('maxTotal')
|
||||
? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].maxTotal
|
||||
: resources[c.type]?.value >= (c.total ?? c.value),
|
||||
true
|
||||
);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -100,12 +100,19 @@ export default class DhCharacter extends BaseDataActor {
|
|||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
damageReduction: new fields.SchemaField({
|
||||
physical: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
magical: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
severe: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
major: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
roll: new fields.SchemaField({
|
||||
attack: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
primaryWeapon: new fields.SchemaField({
|
||||
attack: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
spellcast: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
action: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
hopeOrFear: new fields.NumberField({ integer: true, initial: 0 })
|
||||
|
|
@ -113,11 +120,16 @@ export default class DhCharacter extends BaseDataActor {
|
|||
damage: new fields.SchemaField({
|
||||
all: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
physical: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
magic: new fields.NumberField({ integer: true, initial: 0 })
|
||||
magic: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
primaryWeapon: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ integer: true }),
|
||||
extraDice: new fields.NumberField({ integer: true })
|
||||
})
|
||||
})
|
||||
}),
|
||||
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
||||
rules: new fields.SchemaField({
|
||||
damageReduction: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
bonus: new fields.NumberField({ required: true, integer: true, initial: 0 }),
|
||||
|
|
@ -128,6 +140,10 @@ export default class DhCharacter extends BaseDataActor {
|
|||
major: stressDamageReductionRule(),
|
||||
minor: stressDamageReductionRule()
|
||||
}),
|
||||
increasePerArmorMark: new fields.NumberField({ integer: true, initial: 1 }),
|
||||
magical: new fields.BooleanField({ initial: false }),
|
||||
physical: new fields.BooleanField({ initial: false })
|
||||
}),
|
||||
strangePatterns: new fields.NumberField({
|
||||
integer: true,
|
||||
min: 1,
|
||||
|
|
@ -135,6 +151,18 @@ export default class DhCharacter extends BaseDataActor {
|
|||
nullable: true,
|
||||
initial: null
|
||||
}),
|
||||
weapon: new fields.SchemaField({
|
||||
/* Unimplemented
|
||||
-> Should remove the lowest damage dice from weapon damage
|
||||
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
||||
*/
|
||||
dropLowestDamageDice: new fields.BooleanField({ initial: false }),
|
||||
/* Unimplemented
|
||||
-> Should flip any lowest possible dice rolls for weapon damage to highest
|
||||
-> Reflect this in the chat message somehow so players get feedback that their choice is helping them.
|
||||
*/
|
||||
flipMinDiceValue: new fields.BooleanField({ intial: false })
|
||||
}),
|
||||
runeWard: new fields.BooleanField({ initial: false })
|
||||
})
|
||||
};
|
||||
|
|
@ -282,6 +310,13 @@ export default class DhCharacter extends BaseDataActor {
|
|||
);
|
||||
}
|
||||
|
||||
get armorApplicableDamageTypes() {
|
||||
return {
|
||||
physical: !this.rules.damageReduction.magical,
|
||||
magical: !this.rules.damageReduction.physical
|
||||
};
|
||||
}
|
||||
|
||||
static async unequipBeforeEquip(itemToEquip) {
|
||||
const primary = this.primaryWeapon,
|
||||
secondary = this.secondaryWeapon;
|
||||
|
|
@ -348,6 +383,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
}
|
||||
|
||||
const armor = this.armor;
|
||||
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0; // Bonuses to armorScore won't have been applied yet. Need to solve in documentPreparation somehow
|
||||
this.damageThresholds = {
|
||||
major: armor
|
||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
||||
|
|
@ -372,9 +408,9 @@ export default class DhCharacter extends BaseDataActor {
|
|||
experience.total = experience.value + experience.bonus;
|
||||
}
|
||||
|
||||
this.rules.maxArmorMarked.total = this.rules.maxArmorMarked.value + this.rules.maxArmorMarked.bonus;
|
||||
this.rules.damageReduction.maxArmorMarked.total =
|
||||
this.rules.damageReduction.maxArmorMarked.value + this.rules.damageReduction.maxArmorMarked.bonus;
|
||||
|
||||
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0;
|
||||
this.resources.hitPoints.maxTotal = (this.class.value?.system?.hitPoints ?? 0) + this.resources.hitPoints.bonus;
|
||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
||||
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
|
||||
|
|
|
|||
|
|
@ -22,7 +22,7 @@ export default class DHArmor extends BaseDataItem {
|
|||
tier: new fields.NumberField({ required: true, integer: true, initial: 1, min: 1 }),
|
||||
equipped: new fields.BooleanField({ initial: false }),
|
||||
baseScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
features: new fields.ArrayField(
|
||||
armorFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
|
|
@ -44,25 +44,22 @@ export default class DHArmor extends BaseDataItem {
|
|||
};
|
||||
}
|
||||
|
||||
get featureInfo() {
|
||||
return this.feature ? CONFIG.DH.ITEM.armorFeatures[this.feature] : null;
|
||||
}
|
||||
|
||||
async _preUpdate(changes, options, user) {
|
||||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.system.features) {
|
||||
const removed = this.features.filter(x => !changes.system.features.includes(x));
|
||||
const added = changes.system.features.filter(x => !this.features.includes(x));
|
||||
if (changes.system.armorFeatures) {
|
||||
const removed = this.armorFeatures.filter(x => !changes.system.armorFeatures.includes(x));
|
||||
const added = changes.system.armorFeatures.filter(x => !this.armorFeatures.includes(x));
|
||||
|
||||
const effectIds = [];
|
||||
const actionIds = [];
|
||||
for (var feature of removed) {
|
||||
for (var effectId of feature.effectIds) {
|
||||
await this.parent.effects.get(effectId).delete();
|
||||
}
|
||||
|
||||
changes.system.actions = this.actions.filter(x => !feature.actionIds.includes(x._id));
|
||||
effectIds.push(...feature.effectIds);
|
||||
actionIds.push(...feature.actionIds);
|
||||
}
|
||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', effectIds);
|
||||
changes.system.actions = this.actions.filter(x => !actionIds.includes(x._id));
|
||||
|
||||
for (var feature of added) {
|
||||
const featureData = armorFeatures[feature.value];
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
type: 'weapon',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
// hasInitialAction: true
|
||||
});
|
||||
}
|
||||
|
|
@ -26,8 +26,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
//SETTINGS
|
||||
secondary: new fields.BooleanField({ initial: false }),
|
||||
burden: new fields.StringField({ required: true, choices: CONFIG.DH.GENERAL.burden, initial: 'oneHanded' }),
|
||||
|
||||
features: new fields.ArrayField(
|
||||
weaponFeatures: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.StringField({
|
||||
required: true,
|
||||
|
|
@ -59,7 +58,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
{
|
||||
value: {
|
||||
multiplier: 'prof',
|
||||
dice: "d8"
|
||||
dice: 'd8'
|
||||
}
|
||||
}
|
||||
]
|
||||
|
|
@ -78,17 +77,19 @@ export default class DHWeapon extends BaseDataItem {
|
|||
const allowed = await super._preUpdate(changes, options, user);
|
||||
if (allowed === false) return false;
|
||||
|
||||
if (changes.system?.features) {
|
||||
const removed = this.features.filter(x => !changes.system.features.includes(x));
|
||||
const added = changes.system.features.filter(x => !this.features.includes(x));
|
||||
if (changes.system?.weaponFeatures) {
|
||||
const removed = this.weaponFeatures.filter(x => !changes.system.weaponFeatures.includes(x));
|
||||
const added = changes.system.weaponFeatures.filter(x => !this.weaponFeatures.includes(x));
|
||||
|
||||
const removedEffectsUpdate = [];
|
||||
const removedActionsUpdate = [];
|
||||
for (let weaponFeature of removed) {
|
||||
for (var effectId of weaponFeature.effectIds) {
|
||||
await this.parent.effects.get(effectId).delete();
|
||||
removedEffectsUpdate.push(...weaponFeature.effectIds);
|
||||
removedActionsUpdate.push(...weaponFeature.actionIds);
|
||||
}
|
||||
|
||||
changes.system.actions = this.actions.filter(x => !weaponFeature.actionIds.includes(x._id));
|
||||
}
|
||||
await this.parent.deleteEmbeddedDocuments('ActiveEffect', removedEffectsUpdate);
|
||||
changes.system.actions = this.actions.filter(x => !removedActionsUpdate.includes(x._id));
|
||||
|
||||
for (let weaponFeature of added) {
|
||||
const featureData = CONFIG.DH.ITEM.weaponFeatures[weaponFeature.value];
|
||||
|
|
@ -102,18 +103,38 @@ export default class DHWeapon extends BaseDataItem {
|
|||
]);
|
||||
weaponFeature.effectIds = embeddedItems.map(x => x.id);
|
||||
}
|
||||
|
||||
const newActions = [];
|
||||
if (featureData.actions?.length > 0) {
|
||||
const newActions = featureData.actions.map(action => {
|
||||
const cls = actionsTypes[action.type];
|
||||
return new cls(
|
||||
{ ...action, _id: foundry.utils.randomID(), name: game.i18n.localize(action.name) },
|
||||
{ parent: this }
|
||||
for (let action of featureData.actions) {
|
||||
const embeddedEffects = await this.parent.createEmbeddedDocuments(
|
||||
'ActiveEffect',
|
||||
(action.effects ?? []).map(effect => ({
|
||||
...effect,
|
||||
transfer: false,
|
||||
name: game.i18n.localize(effect.name),
|
||||
description: game.i18n.localize(effect.description)
|
||||
}))
|
||||
);
|
||||
});
|
||||
const cls = actionsTypes[action.type];
|
||||
newActions.push(
|
||||
new cls(
|
||||
{
|
||||
...action,
|
||||
_id: foundry.utils.randomID(),
|
||||
name: game.i18n.localize(action.name),
|
||||
description: game.i18n.localize(action.description),
|
||||
effects: embeddedEffects.map(x => ({ _id: x.id }))
|
||||
},
|
||||
{ parent: this }
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
changes.system.actions = [...this.actions, ...newActions];
|
||||
weaponFeature.actionIds = newActions.map(x => x._id);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,7 +25,11 @@ export default class DhActiveEffect extends ActiveEffect {
|
|||
}
|
||||
|
||||
static applyField(model, change, field) {
|
||||
change.value = Roll.safeEval(Roll.replaceFormulaData(change.value, change.effect.parent));
|
||||
const isItemTarget = change.value.toLowerCase().startsWith('item.');
|
||||
change.value = isItemTarget ? change.value.slice(5) : change.value;
|
||||
change.value = Roll.safeEval(
|
||||
Roll.replaceFormulaData(change.value, isItemTarget ? change.effect.parent : model)
|
||||
);
|
||||
super.applyField(model, change, field);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ import { emitAsGM, emitAsOwner, GMUpdateEvent, socketEvent } from '../systemRegi
|
|||
import DamageReductionDialog from '../applications/dialogs/damageReductionDialog.mjs';
|
||||
import { LevelOptionType } from '../data/levelTier.mjs';
|
||||
import DHFeature from '../data/item/feature.mjs';
|
||||
import { damageKeyToNumber, getDamageKey } from '../helpers/utils.mjs';
|
||||
|
||||
export default class DhpActor extends Actor {
|
||||
/**
|
||||
|
|
@ -455,14 +456,32 @@ export default class DhpActor extends Actor {
|
|||
cls.create(msg.toObject());
|
||||
}
|
||||
|
||||
async takeDamage(damage, type) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, damage, type) === false) return null;
|
||||
#canReduceDamage(hpDamage, type) {
|
||||
const availableStress = this.system.resources.stress.maxTotal - this.system.resources.stress.value;
|
||||
|
||||
const canUseArmor =
|
||||
this.system.armor &&
|
||||
this.system.armor.system.marks.value < this.system.armorScore &&
|
||||
this.system.armorApplicableDamageTypes[type];
|
||||
const canUseStress = Object.keys(this.system.rules.damageReduction.stressDamageReduction).reduce((acc, x) => {
|
||||
const rule = this.system.rules.damageReduction.stressDamageReduction[x];
|
||||
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
|
||||
return acc;
|
||||
}, false);
|
||||
|
||||
return canUseArmor || canUseStress;
|
||||
}
|
||||
|
||||
async takeDamage(baseDamage, type) {
|
||||
if (Hooks.call(`${CONFIG.DH.id}.preTakeDamage`, this, baseDamage, type) === false) return null;
|
||||
|
||||
if (this.type === 'companion') {
|
||||
await this.modifyResource([{ value: 1, type: 'stress' }]);
|
||||
return;
|
||||
}
|
||||
|
||||
const flatReduction = this.system.bonuses.damageReduction[type];
|
||||
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
|
||||
const hpDamage = this.convertDamageToThreshold(damage);
|
||||
|
||||
if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null;
|
||||
|
|
@ -471,12 +490,12 @@ export default class DhpActor extends Actor {
|
|||
|
||||
const updates = [{ value: hpDamage, type: 'hitPoints' }];
|
||||
|
||||
if (
|
||||
this.type === 'character' &&
|
||||
this.system.armor &&
|
||||
this.system.armor.system.marks.value < this.system.armorScore
|
||||
) {
|
||||
const armorStackResult = await this.owner.query('armorStack', { actorId: this.uuid, damage: hpDamage });
|
||||
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage, type)) {
|
||||
const armorStackResult = await this.owner.query('armorStack', {
|
||||
actorId: this.uuid,
|
||||
damage: hpDamage,
|
||||
type: type
|
||||
});
|
||||
if (armorStackResult) {
|
||||
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
|
||||
updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
|
||||
|
|
|
|||
|
|
@ -90,10 +90,12 @@ export default class DHItem extends foundry.documents.Item {
|
|||
ok: {
|
||||
label: title,
|
||||
callback: (event, button, dialog) => {
|
||||
Object.defineProperty(prevEvent, "shiftKey", {
|
||||
get() { return event.shiftKey; },
|
||||
Object.defineProperty(prevEvent, 'shiftKey', {
|
||||
get() {
|
||||
return event.shiftKey;
|
||||
}
|
||||
});
|
||||
return this.system.actionsList.find(a => a._id === button.form.elements.actionId.value)
|
||||
return this.system.actionsList.find(a => a._id === button.form.elements.actionId.value);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
|
|
|||
|
|
@ -159,7 +159,8 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) =>
|
|||
return {
|
||||
value: key,
|
||||
name: game.i18n.localize(option.label),
|
||||
src: option.src
|
||||
src: option.src,
|
||||
description: option.description
|
||||
};
|
||||
}),
|
||||
maxTags: maxTags,
|
||||
|
|
@ -173,11 +174,12 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) =>
|
|||
},
|
||||
templates: {
|
||||
tag(tagData) {
|
||||
return `<tag title="${tagData.title || tagData.value}"
|
||||
return `<tag
|
||||
contenteditable='false'
|
||||
spellcheck='false'
|
||||
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
|
||||
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
|
||||
data-tooltip="${tagData.description || tagData.name}"
|
||||
${this.getAttributes(tagData)}>
|
||||
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
|
||||
<div>
|
||||
|
|
@ -190,6 +192,8 @@ export const tagifyElement = (element, options, onChange, tagifyOptions = {}) =>
|
|||
});
|
||||
|
||||
tagifyElement.on('add', event => {
|
||||
if (event.detail.data.__isValid === 'not allowed') return;
|
||||
|
||||
const input = event.detail.tagify.DOM.originalInput;
|
||||
const currentList = input.value ? JSON.parse(input.value) : [];
|
||||
onChange([...currentList, event.detail.data], { option: event.detail.data.value, removed: false }, input);
|
||||
|
|
@ -233,19 +237,23 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false
|
|||
return nativeReplaceFormulaData(formula, data, { missing, warn });
|
||||
};
|
||||
|
||||
export const getDamageLabel = damage => {
|
||||
export const getDamageKey = damage => {
|
||||
switch (damage) {
|
||||
case 3:
|
||||
return game.i18n.localize('DAGGERHEART.GENERAL.Damage.severe');
|
||||
return 'severe';
|
||||
case 2:
|
||||
return game.i18n.localize('DAGGERHEART.GENERAL.Damage.major');
|
||||
return 'major';
|
||||
case 1:
|
||||
return game.i18n.localize('DAGGERHEART.GENERAL.Damage.minor');
|
||||
return 'minor';
|
||||
case 0:
|
||||
return game.i18n.localize('DAGGERHEART.GENERAL.Damage.none');
|
||||
return 'none';
|
||||
}
|
||||
};
|
||||
|
||||
export const getDamageLabel = damage => {
|
||||
return game.i18n.localize(`DAGGERHEART.GENERAL.Damage.${getDamageKey(damage)}`);
|
||||
};
|
||||
|
||||
export const damageKeyToNumber = key => {
|
||||
switch (key) {
|
||||
case 'severe':
|
||||
|
|
|
|||
|
|
@ -45,7 +45,13 @@ export const registerSocketHooks = () => {
|
|||
await game.settings.set(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.Resources.Fear,
|
||||
Math.max(0, Math.min(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear, data.update))
|
||||
Math.max(
|
||||
0,
|
||||
Math.min(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
|
||||
data.update
|
||||
)
|
||||
)
|
||||
);
|
||||
/* Hooks.callAll(socketEvent.DhpFearUpdate);
|
||||
await game.socket.emit(`system.${CONFIG.DH.id}`, { action: socketEvent.DhpFearUpdate }); */
|
||||
|
|
@ -74,7 +80,7 @@ export const emitAsGM = async (eventName, callback, update, uuid = null) => {
|
|||
}
|
||||
});
|
||||
} else return callback(update);
|
||||
}
|
||||
};
|
||||
|
||||
export const emitAsOwner = (eventName, userId, args) => {
|
||||
if (userId === game.user.id) return;
|
||||
|
|
@ -87,4 +93,4 @@ export const emitAsOwner = (eventName, userId, args) => {
|
|||
}
|
||||
});
|
||||
return false;
|
||||
}
|
||||
};
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue