Bugfix - Companion Levelup Features (#303)

* Fixed so that features gained from companion levleup are granted properly to its partner

* Fixed localization error I noticed
This commit is contained in:
WBHarry 2025-07-09 02:18:26 +02:00 committed by GitHub
parent 99e41ec6f7
commit 9189a95ea3
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
7 changed files with 79 additions and 60 deletions

View file

@ -107,7 +107,8 @@
"sendToChat": "Send To Chat", "sendToChat": "Send To Chat",
"toLoadout": "Send to Loadout", "toLoadout": "Send to Loadout",
"toVault": "Send to Vault", "toVault": "Send to Vault",
"unequip": "Unequip" "unequip": "Unequip",
"useItem": "Use Item"
}, },
"faith": "Faith", "faith": "Faith",
"levelUp": "You can level up", "levelUp": "You can level up",

View file

@ -128,7 +128,7 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections }; context.tabs.advancements.progress = { selected: selections, max: currentLevel.maxSelections };
context.showTabs = this.tabGroups.primary !== 'summary'; context.showTabs = this.tabGroups.primary !== 'summary';
break; break;
const { current: currentActorLevel, changed: changedActorLevel } = this.actor.system.levelData.level;
const actorArmor = this.actor.system.armor; const actorArmor = this.actor.system.armor;
const levelKeys = Object.keys(this.levelup.levels); const levelKeys = Object.keys(this.levelup.levels);
let achivementProficiency = 0; let achivementProficiency = 0;

View file

@ -202,7 +202,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
return [ return [
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.UseItem', name: 'DAGGERHEART.ACTORS.Character.contextMenu.useItem',
icon: '<i class="fa-solid fa-burst"></i>', icon: '<i class="fa-solid fa-burst"></i>',
condition: el => { condition: el => {
const item = getItem(el); const item = getItem(el);
@ -211,7 +211,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: (button, event) => CharacterSheet.useItem.call(this, event, button) callback: (button, event) => CharacterSheet.useItem.call(this, event, button)
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Equip', name: 'DAGGERHEART.ACTORS.Character.contextMenu.equip',
icon: '<i class="fa-solid fa-hands"></i>', icon: '<i class="fa-solid fa-hands"></i>',
condition: el => { condition: el => {
const item = getItem(el); const item = getItem(el);
@ -220,7 +220,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: CharacterSheet.#toggleEquipItem.bind(this) callback: CharacterSheet.#toggleEquipItem.bind(this)
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Unequip', name: 'DAGGERHEART.ACTORS.Character.contextMenu.unequip',
icon: '<i class="fa-solid fa-hands"></i>', icon: '<i class="fa-solid fa-hands"></i>',
condition: el => { condition: el => {
const item = getItem(el); const item = getItem(el);
@ -229,7 +229,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: CharacterSheet.#toggleEquipItem.bind(this) callback: CharacterSheet.#toggleEquipItem.bind(this)
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToLoadout', name: 'DAGGERHEART.ACTORS.Character.contextMenu.toLoadout',
icon: '<i class="fa-solid fa-arrow-up"></i>', icon: '<i class="fa-solid fa-arrow-up"></i>',
condition: el => { condition: el => {
const item = getItem(el); const item = getItem(el);
@ -238,7 +238,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: target => getItem(target).update({ 'system.inVault': false }) callback: target => getItem(target).update({ 'system.inVault': false })
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.ToVault', name: 'DAGGERHEART.ACTORS.Character.contextMenu.toVault',
icon: '<i class="fa-solid fa-arrow-down"></i>', icon: '<i class="fa-solid fa-arrow-down"></i>',
condition: el => { condition: el => {
const item = getItem(el); const item = getItem(el);
@ -247,17 +247,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
callback: target => getItem(target).update({ 'system.inVault': true }) callback: target => getItem(target).update({ 'system.inVault': true })
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.SendToChat', name: 'DAGGERHEART.ACTORS.Character.contextMenu.sendToChat',
icon: '<i class="fa-regular fa-message"></i>', icon: '<i class="fa-regular fa-message"></i>',
callback: CharacterSheet.toChat.bind(this) callback: CharacterSheet.toChat.bind(this)
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Edit', name: 'CONTROLS.CommonEdit',
icon: '<i class="fa-solid fa-pen-to-square"></i>', icon: '<i class="fa-solid fa-pen-to-square"></i>',
callback: target => getItem(target).sheet.render({ force: true }) callback: target => getItem(target).sheet.render({ force: true })
}, },
{ {
name: 'DAGGERHEART.Sheets.PC.ContextMenu.Delete', name: 'CONTROLS.CommonDelete',
icon: '<i class="fa-solid fa-trash"></i>', icon: '<i class="fa-solid fa-trash"></i>',
callback: async el => { callback: async el => {
const item = getItem(el); const item = getItem(el);

View file

@ -43,7 +43,12 @@ export default class DhLevelData extends foundry.abstract.DataModel {
data: new fields.ArrayField(new fields.StringField({ required: true })), data: new fields.ArrayField(new fields.StringField({ required: true })),
secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })), secondaryData: new fields.TypedObjectField(new fields.StringField({ required: true })),
itemUuid: new fields.DocumentUUIDField({ 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() { get canLevelUp() {
return this.level.current < this.level.changed; return this.level.current < this.level.changed;
} }

View file

@ -70,7 +70,8 @@ export const CompanionLevelOptionType = {
{ {
name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.name', name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.creatureComfort.name',
img: 'icons/magic/life/heart-cross-purple-orange.webp', 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', name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.armored.name',
img: 'icons/equipment/shield/kite-wooden-oak-glow.webp', 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', name: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.name',
img: 'icons/magic/life/heart-red-blue.webp', img: 'icons/magic/life/heart-red-blue.webp',
description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.description' description: 'DAGGERHEART.APPLICATIONS.Levelup.actions.bonded.description',
toPartner: true
} }
] ]
}, },

View file

@ -139,6 +139,7 @@ export default class DHRoll extends Roll {
export const registerRollDiceHooks = () => { export const registerRollDiceHooks = () => {
Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => { Hooks.on(`${CONFIG.DH.id}.postRollDuality`, async (config, message) => {
if ( if (
!config.source?.actor ||
!game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope || !game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).hope ||
config.roll.type === 'reaction' config.roll.type === 'reaction'
) )

View file

@ -5,13 +5,13 @@ import { LevelOptionType } from '../data/levelTier.mjs';
import DHFeature from '../data/item/feature.mjs'; import DHFeature from '../data/item/feature.mjs';
export default class DhpActor extends Actor { export default class DhpActor extends Actor {
/** /**
* Return the first Actor active owner. * Return the first Actor active owner.
*/ */
get owner() { get owner() {
const user = this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, "OWNER") && u.active);; const user =
if(!user) return game.user.isGM ? game.user : null; this.hasPlayerOwner && game.users.players.find(u => this.testUserPermission(u, 'OWNER') && u.active);
if (!user) return game.user.isGM ? game.user : null;
return user; return user;
} }
@ -61,7 +61,7 @@ export default class DhpActor extends Actor {
return acc; return acc;
}, {}); }, {});
const featureIds = []; const features = [];
const domainCards = []; const domainCards = [];
const experiences = []; const experiences = [];
const subclassFeatureState = { class: null, multiclass: null }; 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); const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards); domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences)); 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'); const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) { if (subclass) {
@ -88,8 +88,11 @@ export default class DhpActor extends Actor {
multiclass = level.selections.find(x => x.type === 'multiclass'); multiclass = level.selections.find(x => x.type === 'multiclass');
}); });
for (let featureId of featureIds) { for (let feature of features) {
this.items.get(featureId).delete(); 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) { if (experiences.length > 0) {
@ -153,7 +156,6 @@ export default class DhpActor extends Actor {
} }
async levelUp(levelupData) { async levelUp(levelupData) {
const actions = [];
const levelups = {}; const levelups = {};
for (var levelKey of Object.keys(levelupData)) { for (var levelKey of Object.keys(levelupData)) {
const level = levelupData[levelKey]; const level = levelupData[levelKey];
@ -237,7 +239,9 @@ export default class DhpActor extends Actor {
...featureData, ...featureData,
description: game.i18n.localize(featureData.description) 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, ...featureData,
name: game.i18n.localize(featureData.name), name: game.i18n.localize(featureData.name),
@ -245,9 +249,13 @@ export default class DhpActor extends Actor {
system: feature system: feature
} }
]); ]);
addition.checkbox.featureIds = !addition.checkbox.featureIds const newFeature = {
? [embeddedItem[0].id] onPartner: Boolean(featureData.toPartner && this.system.partner),
: [...addition.checkbox.featureIds, embeddedItem[0].id]; id: embeddedItem[0].id
};
addition.checkbox.features = !addition.checkbox.features
? [newFeature]
: [...addition.checkbox.features, newFeature];
} }
selections.push(addition.checkbox); selections.push(addition.checkbox);
@ -317,7 +325,6 @@ export default class DhpActor extends Actor {
await this.update({ await this.update({
system: { system: {
actions: [...this.system.actions, ...actions],
levelData: { levelData: {
level: { level: {
current: this.system.levelData.level.changed 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; const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
return modifier !== null return modifier !== null
? [ ? [
{ {
value: modifier, value: modifier,
label: roll.label label: roll.label
? modifier >= 0 ? modifier >= 0
? `${roll.label} +${modifier}` ? `${roll.label} +${modifier}`
: `${roll.label} ${modifier}` : `${roll.label} ${modifier}`
: null, : null,
title: roll.label 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 (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' }]; const updates = [{ value: hpDamage, type: 'hitPoints' }];
@ -469,8 +476,8 @@ export default class DhpActor extends Actor {
this.system.armor && this.system.armor &&
this.system.armor.system.marks.value < this.system.armorScore this.system.armor.system.marks.value < this.system.armorScore
) { ) {
const armorStackResult = await this.owner.query('armorStack', {actorId: this.uuid, damage: hpDamage}); const armorStackResult = await this.owner.query('armorStack', { actorId: this.uuid, damage: hpDamage });
if(armorStackResult) { if (armorStackResult) {
const { modifiedDamage, armorSpent, stressSpent } = armorStackResult; const { modifiedDamage, armorSpent, stressSpent } = armorStackResult;
updates.find(u => u.type === 'hitPoints').value = modifiedDamage; updates.find(u => u.type === 'hitPoints').value = modifiedDamage;
updates.push( updates.push(
@ -493,7 +500,7 @@ export default class DhpActor extends Actor {
async modifyResource(resources) { async modifyResource(resources) {
if (!resources.length) return; 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: {} } }; let updates = { actor: { target: this, resources: {} }, armor: { target: this.system.armor, resources: {} } };
resources.forEach(r => { resources.forEach(r => {
switch (r.type) { switch (r.type) {
@ -521,7 +528,12 @@ export default class DhpActor extends Actor {
}); });
Object.values(updates).forEach(async u => { Object.values(updates).forEach(async u => {
if (Object.keys(u.resources).length > 0) { 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) { /* if (game.user.isGM) {
await u.target.update(u.resources); await u.target.update(u.resources);
} else { } else {
@ -540,27 +552,28 @@ export default class DhpActor extends Actor {
convertDamageToThreshold(damage) { convertDamageToThreshold(damage) {
return damage >= this.system.damageThresholds.severe return damage >= this.system.damageThresholds.severe
? 3 ? 3
: damage >= this.system.damageThresholds.major : damage >= this.system.damageThresholds.major
? 2 ? 2
: damage >= this.system.damageThresholds.minor : damage >= this.system.damageThresholds.minor
? 1 ? 1
: 0; : 0;
} }
convertStressDamageToHP(resources) { convertStressDamageToHP(resources) {
const stressDamage = resources.find(r => r.type === 'stress'), const stressDamage = resources.find(r => r.type === 'stress'),
newValue = this.system.resources.stress.value + stressDamage.value; 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'); const hpDamage = resources.find(r => r.type === 'hitPoints');
if(hpDamage) hpDamage.value++; if (hpDamage) hpDamage.value++;
else resources.push({ else
type: 'hitPoints', resources.push({
value: 1 type: 'hitPoints',
}) value: 1
});
} }
} }
export const registerDHActorHooks = () => { export const registerDHActorHooks = () => {
CONFIG.queries.armorStack = DamageReductionDialog.armorStackQuery; CONFIG.queries.armorStack = DamageReductionDialog.armorStackQuery;
} };