Hotfix 1.0.1 (#825)

* Updated the background image for the system

* Fixed so Weapon/Armor features are added again

* Fixed so fear is available as a resource to be deducted by actions (#757)

* Changed to use the config labels and src

* Updated Weapons

* Fixed so the decrease button of simple fear tracker is not visible when not hovered

* Fixed so armor preUpdate doesn't fail if no system changes are made

* Updated .gitignore and author details (#777)

* Add author details and name mapping for chrisryan10 (#773)

Co-authored-by: Chris Ryan <chrisr@blackhole>

* Add build to ignore for my linux dev (#775)

Co-authored-by: Chris Ryan <chrisr@blackhole>

---------

Co-authored-by: Chris Ryan <chrisr@blackhole>

* Corrected sneak attack active effect (#780)

* Fixed a spelling error (#779)

* Fix bardic rally showing in damage dialog when it should not (#783)

* update spelling (#786)

* Translating inventory descriptions (#782)

* updated credits for 1.0.1 release (#797)

* updated credits for 1.0.1 release

* further updated artwork credits

* Chagned handlebarhelper rollparsed to be more defensive (#794)

* Added missing scene refreshType (#790)

* Remove ability use buttons for not owned abilities (#795)

* [Fix] PrayerDice Fixed (#799)

* Fixed prayer dice, and wheelchair images

* Fixed -settings data sources

* Dragging features from one adversary to another (#788)

* [Fix] Levelup Fixes (#787)

* Fixed crash on experience selection. Fixed subclass error on multiclassing

* Fixed so multiclasses do not gain the hope feature for the class

* Fixed so Class/Subclass features are properly deleted on delevel

* Removed automatic deletion of features on delevel when not using levelup auto

* Fixed so custom domains can be selected in levelup when multiclassing

* Changed so encounter countdowns is a button (#804)

* Fixed so that dropping on class/subclass...creates the item on the character (#803)

* [BUG] - Importing All Adversaries/Environments (#814)

Fixes #774

Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com>

* Bug/671 reaction roll chat title (#809)

* Update Reaction Roll Chat Message Title

* Removed console log

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>

* Improve Trait tooltip display (#817)

Fixes #806

Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com>

* [BUG] - Combat Tracker d12 logo not found (#812)

Fixes #764

Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com>

* Compendium Browser (#821)

* Corrected timbending description localization (#816)

* [Fix] Compendium Item (#810)

* Corrected Emberwoven Armor

* Fixed subclass regression

* Fixed so character's with wildcard images don't break beastform (#815)

* Fix roll result based duality damage (#822)

---------

Co-authored-by: Chris Ryan <73275196+chrisryan10@users.noreply.github.com>
Co-authored-by: Chris Ryan <chrisr@blackhole>
Co-authored-by: Dapoulp <74197441+Dapoulp@users.noreply.github.com>
Co-authored-by: IrkTheImp <41175833+IrkTheImp@users.noreply.github.com>
Co-authored-by: CPTN_Cosmo <cptncosmo@gmail.com>
Co-authored-by: Josh Q. <jshqntnr13@gmail.com>
Co-authored-by: joaquinpereyra98 <24190917+joaquinpereyra98@users.noreply.github.com>
Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com>
This commit is contained in:
WBHarry 2025-08-11 02:02:06 +02:00 committed by GitHub
parent 300719c116
commit 4ffa690aec
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
90 changed files with 918 additions and 659 deletions

View file

@ -359,6 +359,11 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
context.community = { ...this.setup.community, compendium: 'communities' };
context.class = { ...this.setup.class, compendium: 'classes' };
context.subclass = { ...this.setup.subclass, compendium: 'subclasses' };
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
context.classDomains = context.class.uuid
? context.class.system.domains.map(key => game.i18n.localize(allDomainData[key].label))
: [];
context.domainCards = Object.keys(this.setup.domainCards).reduce((acc, x) => {
acc[x] = { ...this.setup.domainCards[x], compendium: 'domains' };
return acc;
@ -378,7 +383,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
uuid: suggestions.armor?.uuid,
taken: suggestions.armor?.uuid === this.equipment.armor?.uuid
},
compendium: 'armors'
compendium: 'armor'
};
context.primaryWeapon = {
...this.equipment.primaryWeapon,
@ -387,7 +392,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
uuid: suggestions.primaryWeapon?.uuid,
taken: suggestions.primaryWeapon?.uuid === this.equipment.primaryWeapon?.uuid
},
compendium: 'weapons'
compendium: 'weapon'
};
context.secondaryWeapon = {
...this.equipment.secondaryWeapon,
@ -397,7 +402,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
taken: suggestions.secondaryWeapon?.uuid === this.equipment.secondaryWeapon?.uuid
},
disabled: this.equipment.primaryWeapon?.system?.burden === burden.twoHanded.value,
compendium: 'weapons'
compendium: 'weapon'
};
context.inventory = {
take: suggestions.inventory.take,
@ -495,11 +500,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
}
static async viewCompendium(event, target) {
const type = target.dataset.compendium ?? target.dataset.type;
const type = target.dataset.compendium ?? target.dataset.type,
equipment = ['armor', 'weapon'];
const presets = {
compendium: 'daggerheart',
folder: type,
folder: equipment.includes(type) ? "equipments" : type,
render: {
noFolder: true
}
@ -511,6 +517,12 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
};
if (equipment.includes(type))
presets.filter = {
'system.tier': { key: 'system.tier', value: 1 },
'type': { key: 'type', value: type }
};
return (this.itemBrowser = await new ItemBrowser({ presets }).render({ force: true }));
}

View file

@ -51,7 +51,7 @@ export default class DhCharacterLevelUp extends LevelUpBase {
.filter(exp => exp.data.length > 0)
.flatMap(exp =>
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);

View file

@ -39,7 +39,7 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
.filter(exp => exp.data.length > 0)
.flatMap(exp =>
exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
const experience = Object.keys(this.actor.system.experiences)[data];
return this.actor.system.experiences[experience].name;
})
);

View file

@ -98,11 +98,17 @@ export default class DHAdversarySettings extends DHBaseActorSettings {
async _onDrop(event) {
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.fromInternal) return;
const item = await fromUuid(data.uuid);
if (item.type === 'feature') {
await this.actor.createEmbeddedDocuments('Item', [item]);
if (item?.type === 'feature') {
if (data.fromInternal && item.parent?.uuid === this.actor.uuid) {
return;
}
const itemData = item.toObject();
delete itemData._id;
await this.actor.createEmbeddedDocuments('Item', [itemData]);
}
}
}

View file

@ -129,6 +129,7 @@ export default function DHApplicationMixin(Base) {
const docs = [];
for (const docData of this.relatedDocs) {
if (!docData) continue;
const doc = await foundry.utils.fromUuid(docData.uuid);
docs.push(doc);
}
@ -419,7 +420,7 @@ export default function DHApplicationMixin(Base) {
: this.document.system.actions?.get(actionId);
if (!doc) return;
const description = doc.system?.description ?? doc.description;
const description = game.i18n.localize(doc.system?.description ?? doc.description);
const isAction = !!actionId;
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description,

View file

@ -181,12 +181,18 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
static async #deleteFeature(_, element) {
const target = element.closest('[data-item-uuid]');
const feature = await getDocFromElement(target);
if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
await this.document.update({
'system.features': this.document.system.features
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
.map(x => ({ ...x, item: x.item.uuid }))
});
if (!feature) {
await this.document.update({
'system.features': this.document.system.features
.filter(x => x.item)
.map(x => ({ ...x, item: x.item.uuid }))
});
} else
await this.document.update({
'system.features': this.document.system.features
.filter(x => target.dataset.type !== x.type || x.item.uuid !== feature.uuid)
.map(x => ({ ...x, item: x.item.uuid }))
});
}
/**
@ -259,21 +265,45 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (data.fromInternal) return;
const target = event.target.closest('fieldset.drop-section');
const item = await fromUuid(data.uuid);
let item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const cls = foundry.documents.Item.implementation;
if (this.document.parent?.type === 'character') {
const itemData = item.toObject();
item = await cls.create(
{
...itemData,
system: {
...itemData.system,
originItemType: this.document.type,
originId: this.document.id,
identifier: this.document.system.isMulticlass ? 'multiclass' : null
}
},
{ parent: this.document.parent }
);
}
if (target.dataset.type) {
await this.document.update({
'system.features': [...this.document.system.features, { type: target.dataset.type, item }].map(
x => ({
...x,
item: x.item?.uuid
})
)
});
await this.document.update(
{
'system.features': [...this.document.system.features, { type: target.dataset.type, item }].map(
x => ({
...x,
item: x.item?.uuid
})
)
},
{ parent: this.document.parent?.type === 'character' ? this.document.parent : undefined }
);
} else {
await this.document.update({
'system.features': [...this.document.system.features, item].map(x => x.uuid)
});
await this.document.update(
{
'system.features': [...this.document.system.features, item].map(x => x.uuid)
},
{ parent: this.document.parent?.type === 'character' ? this.document.parent : undefined }
);
}
}
}

View file

@ -121,6 +121,16 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
}
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
htmlElement
.querySelectorAll('[data-action="selectFolder"]')
.forEach(element => element.addEventListener("contextmenu", (event) => {
event.target.classList.toggle('expanded');
}))
}
/* -------------------------------------------- */
/* Rendering */
/* -------------------------------------------- */
@ -179,9 +189,20 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
}
this.items = ItemBrowser.sortBy(items, 'name');
if(target) {
target.closest('.compendium-sidebar').querySelectorAll('[data-action="selectFolder"]').forEach(element => element.classList.remove("is-selected"))
target.classList.add('is-selected');
}
this.render({ force: true });
}
_replaceHTML(result, content, options) {
if(!options.isFirstRender) delete result.sidebar;
super._replaceHTML(result, content, options);
}
static expandContent(_, target) {
const parent = target.parentElement;
parent.classList.toggle('expanded');

View file

@ -183,11 +183,11 @@ export const conditions = {
icon: 'icons/magic/control/debuff-chains-shackle-movement-red.webp',
description: 'DAGGERHEART.CONFIG.Condition.restrained.description'
},
unconcious: {
id: 'unconcious',
name: 'DAGGERHEART.CONFIG.Condition.unconcious.name',
unconscious: {
id: 'unconscious',
name: 'DAGGERHEART.CONFIG.Condition.unconscious.name',
icon: 'icons/magic/control/sleep-bubble-purple.webp',
description: 'DAGGERHEART.CONFIG.Condition.unconcious.description'
description: 'DAGGERHEART.CONFIG.Condition.unconscious.description'
},
dead: {
id: 'dead',
@ -533,6 +533,10 @@ export const getDiceSoNicePresets = async (hopeFaces, fearFaces, advantageFaces
};
export const refreshTypes = {
scene: {
id: 'session',
label: 'DAGGERHEART.GENERAL.RefreshType.scene'
},
session: {
id: 'session',
label: 'DAGGERHEART.GENERAL.RefreshType.session'

View file

@ -325,7 +325,7 @@ export const compendiumConfig = {
"equipments": {
id: "equipments",
keys: ["armors", "weapons", "consumables", "loot"],
label: "Equipments",
label: "Equipment",
type: ["armor", "weapon", "consumable", "loot"],
listType: "items"
},

View file

@ -780,7 +780,15 @@ export const weaponFeatures = {
mode: 2,
value: '1'
}
]
],
system: {
rangeDependence: {
enabled: true,
range: 'melee',
target: 'hostile',
type: 'withinRange'
}
}
}
]
},
@ -1079,7 +1087,15 @@ export const weaponFeatures = {
mode: 2,
value: 'ITEM.@system.tier + 1'
}
]
],
system: {
rangeDependence: {
enabled: true,
range: 'melee',
target: 'hostile',
type: 'withinRange'
}
}
}
]
},
@ -1208,7 +1224,7 @@ export const weaponFeatures = {
img: 'icons/skills/melee/strike-sword-slashing-red.webp',
changes: [
{
key: 'system.bonuses.roll.primaryWeapon.attack',
key: 'system.bonuses.roll.primaryWeapon.bonus',
mode: 2,
value: 1
}
@ -1357,7 +1373,7 @@ export const weaponFeatures = {
actionType: 'action',
chatDisplay: true,
name: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.actions.bendTime.description',
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.actions.bendTime.description',
img: 'icons/magic/time/clock-spinning-gold-pink.webp'
}
]

View file

@ -208,7 +208,14 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
}
async consume(config, successCost = false) {
const usefulResources = foundry.utils.deepClone(this.actor.system.resources);
const usefulResources = {
...foundry.utils.deepClone(this.actor.system.resources),
fear: {
value: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Resources.Fear),
max: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).maxFear,
reversed: false
}
};
for (var cost of config.costs) {
if (cost.keyIsID) {

View file

@ -6,7 +6,8 @@ export default class DHDamageAction extends DHBaseAction {
getFormulaValue(part, data) {
let formulaValue = part.value;
if (data.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
if (data.hasRoll && part.resultBased && data.roll.result.duality === -1) return part.valueAlt;
const isAdversary = this.actor.type === 'adversary';
if (isAdversary && this.actor.system.type === CONFIG.DH.ACTOR.adversaryTypes.horde.id) {
@ -35,7 +36,7 @@ export default class DHDamageAction extends DHBaseAction {
const systemData = data.system ?? data;
let formulas = this.damage.parts.map(p => ({
formula: this.getFormulaValue(p, data).getFormula(this.actor),
formula: this.getFormulaValue(p, systemData).getFormula(this.actor),
damageTypes: p.applyTo === 'hitPoints' && !p.type.size ? new Set(['physical']) : p.type,
applyTo: p.applyTo
}));

View file

@ -32,7 +32,7 @@ export default class BeastformEffect extends BaseEffect {
if (this.parent.parent?.type === 'character') {
this.parent.parent.system.primaryWeapon?.update?.({ 'system.equipped': false });
this.parent.parent.system.secondayWeapon?.update?.({ 'system.equipped': false });
this.parent.parent.system.secondaryWeapon?.update?.({ 'system.equipped': false });
}
}

View file

@ -351,6 +351,17 @@ export default class DhCharacter extends BaseDataActor {
return [...classDomains, ...multiclassDomains];
}
get domainData() {
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
return this.domains.map(key => {
const domain = allDomainData[key];
return {
...domain,
label: game.i18n.localize(domain.label)
};
});
}
get domainCards() {
const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
const loadout = domainCards.filter(x => !x.system.inVault);

View file

@ -60,7 +60,7 @@ export default class DHArmor extends AttachableItem {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
if (changes.system.armorFeatures) {
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));

View file

@ -134,6 +134,7 @@ export default class DHBeastform extends BaseDataItem {
this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject())
);
const tokenImages = await this.parent.parent.getTokenImages();
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
await beastformEffect.updateSource({
changes: [
@ -148,7 +149,7 @@ export default class DHBeastform extends BaseDataItem {
],
system: {
characterTokenData: {
tokenImg: this.parent.parent.prototypeToken.texture.src,
tokenImg: tokenImages[0],
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
tokenSize: {
height: this.parent.parent.prototypeToken.height,

View file

@ -33,6 +33,11 @@ export default class DHDomainCard extends BaseDataItem {
};
}
get domainLabel() {
const allDomainData = CONFIG.DH.DOMAIN.allDomains();
return game.i18n.localize(allDomainData[this.domain].label);
}
/* -------------------------------------------- */
/**@override */
@ -71,7 +76,7 @@ export default class DHDomainCard extends BaseDataItem {
_getTags() {
const tags = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
game.i18n.localize(`DAGGERHEART.GENERAL.Domain.${this.domain}.label`),
this.domainLabel,
`${game.i18n.localize('DAGGERHEART.ITEMS.DomainCard.recallCost')}: ${this.recallCost}`
];
@ -85,7 +90,7 @@ export default class DHDomainCard extends BaseDataItem {
_getLabels() {
const labels = [
game.i18n.localize(`DAGGERHEART.CONFIG.DomainCardTypes.${this.type}`),
game.i18n.localize(`DAGGERHEART.GENERAL.Domain.${this.domain}.label`),
this.domainLabel,
{
value: `${this.recallCost}`, //converts the number to a string
icons: ['fa-bolt']

View file

@ -21,7 +21,7 @@ export default class DHSubclass extends BaseDataItem {
integer: false,
nullable: true,
initial: null,
label: "DAGGERHEART.ITEMS.Subclass.spellcastingTrait"
label: 'DAGGERHEART.ITEMS.Subclass.spellcastingTrait'
}),
features: new ItemLinkFields(),
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
@ -50,7 +50,8 @@ export default class DHSubclass extends BaseDataItem {
async _preCreate(data, options, user) {
if (this.actor?.type === 'character') {
const dataUuid = data.uuid ?? data._stats?.compendiumSource ?? `Item.${data._id}`;
const dataUuid =
data.uuid ?? (data.folder ? `Compendium.daggerheart.subclasses.Item.${data._id}` : `Item.${data._id}`);
if (this.actor.system.class.subclass) {
if (this.actor.system.multiclass.subclass) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent'));

View file

@ -159,25 +159,28 @@ export default class DamageRoll extends DHRoll {
if (config.data?.parent) {
if (config.data.parent.appliedEffects) {
// Bardic Rally
mods.rally = {
label: 'DAGGERHEART.CLASS.Feature.rallyDice',
values: config.data?.parent?.appliedEffects.reduce((a, c) => {
const rallyChoices = config.data?.parent?.appliedEffects.reduce((a, c) => {
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
if (change) a.push({ value: c.id, label: change.value });
return a;
}, []),
value: null,
beforeCrit: true,
callback: part => {
const rallyFaces = config.modifiers.rally.values.find(
r => r.value === config.modifiers.rally.value
)?.label;
part.roll.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...this.parse(`1${rallyFaces}`)
);
}
};
}, [])
if(rallyChoices.length) {
mods.rally = {
label: 'DAGGERHEART.CLASS.Feature.rallyDice',
values: rallyChoices,
value: null,
beforeCrit: true,
callback: part => {
const rallyFaces = config.modifiers.rally.values.find(
r => r.value === config.modifiers.rally.value
)?.label;
part.roll.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: '+' }),
...this.parse(`1${rallyFaces}`)
);
}
};
}
}
const item = config.data.parent.items?.get(config.source.item);

View file

@ -238,7 +238,7 @@ export const registerRollDiceHooks = () => {
if (updates.length) {
const target = actor.system.partner ?? actor;
if (!['dead', 'unconcious'].some(x => actor.statuses.has(x))) {
if (!['dead', 'unconscious'].some(x => actor.statuses.has(x))) {
setTimeout(() => {
target.modifyResource(updates);
}, 50);

View file

@ -19,7 +19,7 @@ export default class DualityRoll extends D20Roll {
get title() {
return game.i18n.localize(
"DAGGERHEART.GENERAL.dualityRoll"
`DAGGERHEART.GENERAL.${this.options?.roll?.type === CONFIG.DH.ITEM.actionTypes.reaction.id ? 'reactionRoll' : 'dualityRoll'}`
);
}
@ -125,10 +125,7 @@ export default class DualityRoll extends D20Roll {
}
createBaseDice() {
if (
this.dice[0] instanceof foundry.dice.terms.Die &&
this.dice[1] instanceof foundry.dice.terms.Die
) {
if (this.dice[0] instanceof foundry.dice.terms.Die && this.dice[1] instanceof foundry.dice.terms.Die) {
this.terms = [this.terms[0], this.terms[1], this.terms[2]];
return;
}

View file

@ -84,6 +84,8 @@ export default class DhpActor extends Actor {
await this.update({ 'system.levelData.level.changed': Math.min(newLevel, maxLevel) });
} else {
const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
const usedLevel = Math.max(newLevel, 1);
if (newLevel < 1) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooLowLevel'));
@ -95,79 +97,90 @@ export default class DhpActor extends Actor {
return acc;
}, {});
const features = [];
const domainCards = [];
const experiences = [];
const subclassFeatureState = { class: null, multiclass: null };
let multiclass = null;
Object.keys(this.system.levelData.levelups)
.filter(x => x > usedLevel)
.forEach(levelKey => {
const level = this.system.levelData.levelups[levelKey];
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
const advancementCards = level.selections.filter(x => x.type === 'domainCard').map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences));
features.push(...level.selections.flatMap(x => x.features));
if (levelupAuto) {
const features = [];
const domainCards = [];
const experiences = [];
const subclassFeatureState = { class: null, multiclass: null };
let multiclass = null;
Object.keys(this.system.levelData.levelups)
.filter(x => x > usedLevel)
.forEach(levelKey => {
const level = this.system.levelData.levelups[levelKey];
const achievementCards = level.achievements.domainCards.map(x => x.itemUuid);
const advancementCards = level.selections
.filter(x => x.type === 'domainCard')
.map(x => x.itemUuid);
domainCards.push(...achievementCards, ...advancementCards);
experiences.push(...Object.keys(level.achievements.experiences));
features.push(...level.selections.flatMap(x => x.features));
const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) {
const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
const subclassState = Number(subclass.secondaryData.featureState) - 1;
subclassFeatureState[path] = subclassFeatureState[path]
? Math.min(subclassState, subclassFeatureState[path])
: subclassState;
}
const subclass = level.selections.find(x => x.type === 'subclass');
if (subclass) {
const path = subclass.secondaryData.isMulticlass === 'true' ? 'multiclass' : 'class';
const subclassState = Number(subclass.secondaryData.featureState) - 1;
subclassFeatureState[path] = subclassFeatureState[path]
? Math.min(subclassState, subclassFeatureState[path])
: subclassState;
}
multiclass = level.selections.find(x => x.type === 'multiclass');
});
multiclass = level.selections.find(x => x.type === 'multiclass');
});
for (let feature of features) {
if (feature.onPartner && !this.system.partner) continue;
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) {
const getUpdate = () => ({
'system.experiences': experiences.reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {})
});
this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
const document = feature.onPartner ? this.system.partner : this;
document.items.get(feature.id)?.delete();
}
}
if (subclassFeatureState.class) {
this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
}
if (subclassFeatureState.multiclass) {
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
}
if (multiclass) {
const multiclassSubclass = this.items.find(x => x.type === 'subclass' && x.system.isMulticlass);
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
multiclassSubclass.delete();
multiclassItem.delete();
this.update({
'system.multiclass': {
value: null,
subclass: null
if (experiences.length > 0) {
const getUpdate = () => ({
'system.experiences': experiences.reduce((acc, key) => {
acc[`-=${key}`] = null;
return acc;
}, {})
});
this.update(getUpdate());
if (this.system.companion) {
this.system.companion.update(getUpdate());
}
});
}
}
for (let domainCard of domainCards) {
const itemCard = this.items.find(x => x.uuid === domainCard);
itemCard.delete();
if (subclassFeatureState.class) {
this.system.class.subclass.update({ 'system.featureState': subclassFeatureState.class });
}
if (subclassFeatureState.multiclass) {
this.system.multiclass.subclass.update({ 'system.featureState': subclassFeatureState.multiclass });
}
if (multiclass) {
const multiclassItem = this.items.find(x => x.uuid === multiclass.itemUuid);
const multiclassFeatures = this.items.filter(
x => x.system.originItemType === 'class' && x.system.identifier === 'multiclass'
);
const subclassFeatures = this.items.filter(
x => x.system.originItemType === 'subclass' && x.system.identifier === 'multiclass'
);
this.deleteEmbeddedDocuments(
'Item',
[multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id)
);
this.update({
'system.multiclass': {
value: null,
subclass: null
}
});
}
for (let domainCard of domainCards) {
const itemCard = this.items.find(x => x.uuid === domainCard);
itemCard.delete();
}
}
await this.update({
@ -315,6 +328,7 @@ export default class DhpActor extends Actor {
...multiclassData,
system: {
...multiclassData.system,
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
domains: [multiclass.secondaryData.domain],
isMulticlass: true
}

View file

@ -69,6 +69,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
break;
}
}
if(!game.user.isGM && !this.isAuthor && !this.speakerActor?.isOwner) {
const buttons = html.querySelectorAll(".ability-card-footer > .ability-use-button");
buttons.forEach(b => b.remove());
}
}
addChatListeners(html) {

View file

@ -60,7 +60,7 @@ export default class RegisterHandlebarsHelpers {
static rollParsed(value, actor, item, numerical) {
const isNumerical = typeof numerical === 'boolean' ? numerical : false;
const result = itemAbleRollParse(value, actor.getRollData(), item);
const result = itemAbleRollParse(value, actor?.getRollData() ?? {}, item);
return isNumerical ? (!result ? 0 : Number(result)) : result;
}
@ -69,7 +69,7 @@ export default class RegisterHandlebarsHelpers {
}
static empty(object) {
if(!(typeof object === 'object')) return true;
if (!(typeof object === 'object')) return true;
return Object.keys(object).length === 0;
}
}

View file

@ -85,7 +85,12 @@ export const chunkify = (array, chunkSize, mappingFunc) => {
export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}) => {
const { maxTags } = tagifyOptions;
const options = typeof baseOptions === 'object' ? Object.values(baseOptions) : baseOptions;
const options = Array.isArray(baseOptions)
? baseOptions
: Object.keys(baseOptions).map(optionKey => ({
...baseOptions[optionKey],
id: optionKey
}));
const tagifyElement = new Tagify(element, {
tagTextProp: 'name',