From 9189a95ea3b150919e0a5e24e6cc291aa2d284c7 Mon Sep 17 00:00:00 2001
From: WBHarry <89362246+WBHarry@users.noreply.github.com>
Date: Wed, 9 Jul 2025 02:18:26 +0200
Subject: [PATCH] Bugfix - Companion Levelup Features (#303)
* Fixed so that features gained from companion levleup are granted properly to its partner
* Fixed localization error I noticed
---
lang/en.json | 3 +-
module/applications/levelup/levelup.mjs | 2 +-
.../applications/sheets/actors/character.mjs | 16 +--
module/data/levelData.mjs | 11 ++-
module/data/levelTier.mjs | 9 +-
module/dice/dhRoll.mjs | 1 +
module/documents/actor.mjs | 97 +++++++++++--------
7 files changed, 79 insertions(+), 60 deletions(-)
diff --git a/lang/en.json b/lang/en.json
index 797781d8..b5ed3b3f 100755
--- a/lang/en.json
+++ b/lang/en.json
@@ -107,7 +107,8 @@
"sendToChat": "Send To Chat",
"toLoadout": "Send to Loadout",
"toVault": "Send to Vault",
- "unequip": "Unequip"
+ "unequip": "Unequip",
+ "useItem": "Use Item"
},
"faith": "Faith",
"levelUp": "You can level up",
diff --git a/module/applications/levelup/levelup.mjs b/module/applications/levelup/levelup.mjs
index eb120ac5..93910fe7 100644
--- a/module/applications/levelup/levelup.mjs
+++ b/module/applications/levelup/levelup.mjs
@@ -128,7 +128,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
context.showTabs = this.tabGroups.primary !== 'summary';
break;
- const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level;
+
const actorArmor = this.actor.system.armor;
const levelKeys = Object.keys(this.levelup.levels);
let achivementProficiency = 0;
diff --git a/module/applications/sheets/actors/character.mjs b/module/applications/sheets/actors/character.mjs
index c24bcfec..e70774f5 100644
--- a/module/applications/sheets/actors/character.mjs
+++ b/module/applications/sheets/actors/character.mjs
@@ -202,7 +202,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
return [
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem',
+ name: 'DAGGERHEART.ACTORS.Character.contextMenu.useItem',
icon: '',
condition: el => {
const item = getItem(el);
@@ -211,7 +211,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: (button, event) => CharacterSheet.useItem.call(this, event, button)
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip',
+ name: 'DAGGERHEART.ACTORS.Character.contextMenu.equip',
icon: '',
condition: el => {
const item = getItem(el);
@@ -220,7 +220,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: CharacterSheet.#toggleEquipItem.bind(this)
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip',
+ name: 'DAGGERHEART.ACTORS.Character.contextMenu.unequip',
icon: '',
condition: el => {
const item = getItem(el);
@@ -229,7 +229,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: CharacterSheet.#toggleEquipItem.bind(this)
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout',
+ name: 'DAGGERHEART.ACTORS.Character.contextMenu.toLoadout',
icon: '',
condition: el => {
const item = getItem(el);
@@ -238,7 +238,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: target => getItem(target).update({ 'system.inVault': false })
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault',
+ name: 'DAGGERHEART.ACTORS.Character.contextMenu.toVault',
icon: '',
condition: el => {
const item = getItem(el);
@@ -247,17 +247,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: target => getItem(target).update({ 'system.inVault': true })
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat',
+ name: 'DAGGERHEART.ACTORS.Character.contextMenu.sendToChat',
icon: '',
callback: CharacterSheet.toChat.bind(this)
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit',
+ name: 'CONTROLS.CommonEdit',
icon: '',
callback: target => getItem(target).sheet.render({ force: true })
},
{
- name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete',
+ name: 'CONTROLS.CommonDelete',
icon: '',
callback: async el => {
const item = getItem(el);
diff --git a/module/data/levelData.mjs b/module/data/levelData.mjs
index 2432a313..669077ee 100644
--- a/module/data/levelData.mjs
+++ b/module/data/levelData.mjs
@@ -43,7 +43,12 @@ export default class DhLevelData extends foundry.abstract.DataModel {
data: new fields.ArrayField(new fields.StringField({ required: true })),
secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })),
itemUuid: new fields.DocumentUUIDField({ required: true }),
- featureIds: new fields.ArrayField(new fields.StringField())
+ features: new fields.ArrayField(
+ new fields.SchemaField({
+ onPartner: new fields.BooleanField(),
+ id: new fields.StringField()
+ })
+ )
})
)
})
@@ -51,10 +56,6 @@ export default class DhLevelData extends foundry.abstract.DataModel {
};
}
- get actions() {
- return Object.values(this.levelups).flatMap(level => level.selections.flatMap(s => s.actions));
- }
-
get canLevelUp() {
return this.level.current < this.level.changed;
}
diff --git a/module/data/levelTier.mjs b/module/data/levelTier.mjs
index b037d921..e9e8d47b 100644
--- a/module/data/levelTier.mjs
+++ b/module/data/levelTier.mjs
@@ -70,7 +70,8 @@ export const CompanionLevelOptionType = {
{
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.name',
img: 'icons/magic/life/heart-cross-purple-orange.webp',
- description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.description'
+ description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.description',
+ toPartner: true
}
]
},
@@ -81,7 +82,8 @@ export const CompanionLevelOptionType = {
{
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.name',
img: 'icons/equipment/shield/kite-wooden-oak-glow.webp',
- description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.description'
+ description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.description',
+ toPartner: true
}
]
},
@@ -100,7 +102,8 @@ export const CompanionLevelOptionType = {
{
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.name',
img: 'icons/magic/life/heart-red-blue.webp',
- description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.description'
+ description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.description',
+ toPartner: true
}
]
},
diff --git a/module/dice/dhRoll.mjs b/module/dice/dhRoll.mjs
index c3918a13..13246ac9 100644
--- a/module/dice/dhRoll.mjs
+++ b/module/dice/dhRoll.mjs
@@ -139,6 +139,7 @@ export default class DHRoll extends Roll {
export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
if (
+ !config.source?.actor ||
!game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope ||
config.roll.type === 'reaction'
)
diff --git a/module/documents/actor.mjs b/module/documents/actor.mjs
index 1fab0f71..cfe101fa 100644
--- a/module/documents/actor.mjs
+++ b/module/documents/actor.mjs
@@ -5,13 +5,13 @@ import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs';
export default class DhpActor extends Actor {
-
/**
* Return the first Actor active owner.
*/
get owner() {
- const user = this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, "OWNER") && u.active);;
- if(!user) return game.user.isGM ? game.user : null;
+ const user =
+ this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, 'OWNER') && u.active);
+ if (!user) return game.user.isGM ? game.user : null;
return user;
}
@@ -61,7 +61,7 @@ export default class DhpActor extends Actor {
return acc;
}, {});
- const featureIds = [];
+ const features = [];
const domainCards = [];
const experiences = [];
const subclassFeatureState = { class: null, multiclass: null };
@@ -74,7 +74,7 @@ export default class DhpActor extends Actor {
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences));
- featureIds.push(...level.selections.flatMap(x => x.featureIds));
+ features.push(...level.selections.flatMap(x => x.features));
const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) {
@@ -88,8 +88,11 @@ export default class DhpActor extends Actor {
multiclass = level.selections.find(x => x.type === 'multiclass');
});
- for (let featureId of featureIds) {
- this.items.get(featureId).delete();
+ for (let feature of features) {
+ if (feature.onPartner && !this.system.partner) continue;
+
+ const document = feature.onPartner ? this.system.partner : this;
+ document.items.get(feature.id)?.delete();
}
if (experiences.length > 0) {
@@ -153,7 +156,6 @@ export default class DhpActor extends Actor {
}
async levelUp(levelupData) {
- const actions = [];
const levelups = {};
for (var levelKey of Object.keys(levelupData)) {
const level = levelupData[levelKey];
@@ -237,7 +239,9 @@ export default class DhpActor extends Actor {
...featureData,
description: game.i18n.localize(featureData.description)
});
- const embeddedItem = await this.createEmbeddedDocuments('Item', [
+
+ const document = featureData.toPartner && this.system.partner ? this.system.partner : this;
+ const embeddedItem = await document.createEmbeddedDocuments('Item', [
{
...featureData,
name: game.i18n.localize(featureData.name),
@@ -245,9 +249,13 @@ export default class DhpActor extends Actor {
system: feature
}
]);
- addition.checkbox.featureIds = !addition.checkbox.featureIds
- ? [embeddedItem[0].id]
- : [...addition.checkbox.featureIds, embeddedItem[0].id];
+ const newFeature = {
+ onPartner: Boolean(featureData.toPartner && this.system.partner),
+ id: embeddedItem[0].id
+ };
+ addition.checkbox.features = !addition.checkbox.features
+ ? [newFeature]
+ : [...addition.checkbox.features, newFeature];
}
selections.push(addition.checkbox);
@@ -317,7 +325,6 @@ export default class DhpActor extends Actor {
await this.update({
system: {
- actions: [...this.system.actions, ...actions],
levelData: {
level: {
current: this.system.levelData.level.changed
@@ -369,16 +376,16 @@ export default class DhpActor extends Actor {
const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
return modifier !== null
? [
- {
- value: modifier,
- label: roll.label
- ? modifier >= 0
- ? `${roll.label} +${modifier}`
- : `${roll.label} ${modifier}`
- : null,
- title: roll.label
- }
- ]
+ {
+ value: modifier,
+ label: roll.label
+ ? modifier >= 0
+ ? `${roll.label} +${modifier}`
+ : `${roll.label} ${modifier}`
+ : null,
+ title: roll.label
+ }
+ ]
: [];
}
@@ -460,7 +467,7 @@ export default class DhpActor extends Actor {
if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null;
- if(!hpDamage) return;
+ if (!hpDamage) return;
const updates = [{ value: hpDamage, type: 'hitPoints' }];
@@ -469,8 +476,8 @@ export default class DhpActor extends Actor {
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(armorStackResult) {
+ const armorStackResult = await this.owner.query('armorStack', { actorId: this.uuid, damage: hpDamage });
+ if (armorStackResult) {
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
updates.push(
@@ -479,7 +486,7 @@ export default class DhpActor extends Actor {
);
}
}
-
+
await this.modifyResource(updates);
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null;
@@ -493,7 +500,7 @@ export default class DhpActor extends Actor {
async modifyResource(resources) {
if (!resources.length) return;
- if(resources.find(r => r.type === 'stress')) this.convertStressDamageToHP(resources);
+ if (resources.find(r => r.type === 'stress')) this.convertStressDamageToHP(resources);
let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } };
resources.forEach(r => {
switch (r.type) {
@@ -521,7 +528,12 @@ export default class DhpActor extends Actor {
});
Object.values(updates).forEach(async u => {
if (Object.keys(u.resources).length > 0) {
- await emitAsGM(GMUpdateEvent.UpdateDocument, u.target.update.bind(u.target), u.resources, u.target.uuid);
+ await emitAsGM(
+ GMUpdateEvent.UpdateDocument,
+ u.target.update.bind(u.target),
+ u.resources,
+ u.target.uuid
+ );
/* if (game.user.isGM) {
await u.target.update(u.resources);
} else {
@@ -540,27 +552,28 @@ export default class DhpActor extends Actor {
convertDamageToThreshold(damage) {
return damage >= this.system.damageThresholds.severe
- ? 3
- : damage >= this.system.damageThresholds.major
- ? 2
- : damage >= this.system.damageThresholds.minor
- ? 1
- : 0;
+ ? 3
+ : damage >= this.system.damageThresholds.major
+ ? 2
+ : damage >= this.system.damageThresholds.minor
+ ? 1
+ : 0;
}
convertStressDamageToHP(resources) {
const stressDamage = resources.find(r => r.type === 'stress'),
newValue = this.system.resources.stress.value + stressDamage.value;
- if(newValue <= this.system.resources.stress.maxTotal) return;
+ if (newValue <= this.system.resources.stress.maxTotal) return;
const hpDamage = resources.find(r => r.type === 'hitPoints');
- if(hpDamage) hpDamage.value++;
- else resources.push({
- type: 'hitPoints',
- value: 1
- })
+ if (hpDamage) hpDamage.value++;
+ else
+ resources.push({
+ type: 'hitPoints',
+ value: 1
+ });
}
}
export const registerDHActorHooks = () => {
CONFIG.queries.armorStack = DamageReductionDialog.armorStackQuery;
-}
\ No newline at end of file
+};