Compare commits

...

7 commits

Author SHA1 Message Date
WBHarry
273f666784 Raised version
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-05-21 01:44:13 +02:00
WBHarry
d782b25254
[Feature] Class/Multiclass Feature Split (#1911)
* Changed so that multiclass features and multiclassSubclass features are displayed in separate fieldsets from the base class features in the character sheet

* Changed to tertiaries for class/multiclass feature divide
2026-05-21 01:38:31 +02:00
WBHarry
f4c21a6a1b
[Fix] CompendiumBrowser Pack Toggling (#1909)
* Fixed so that CompendiumBrowserSettings saves source/pack names as slugified version to avoid foundrdy not saving names with dots in the middle

* Updated excludedPacks with another layer of TypedObjectField

* Renmamed variable

* Update module/applications/dialogs/CompendiumBrowserSettings.mjs

Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>

---------

Co-authored-by: Carlos Fernandez <CarlosFdez@users.noreply.github.com>
2026-05-21 01:33:07 +02:00
WBHarry
da06381748
[Fix] Remove System Slugify (#1913) 2026-05-20 17:08:18 -04:00
Carlos Fernandez
ed53d9ed4c
[Feature] Add way to delete unbound character creation items (#1907)
* Add way to delete unbound character creation items

* Temporarily reduce functionality

* Fixed missing fetchSubclass call

* Revert "Fixed missing fetchSubclass call"

This reverts commit 4fc9ee39b6.

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
2026-05-20 22:18:40 +02:00
WBHarry
b631525b6e
Fixed so that Reaction roll chat messages do not mention 'withHope'/'withFear' (#1912) 2026-05-20 15:44:50 -04:00
WBHarry
b145f515d0
Fixed so that a non-existing class link uuid on a subclass doesn't make the subclass filter error (#1910) 2026-05-20 15:43:11 -04:00
11 changed files with 116 additions and 38 deletions

View file

@ -50,7 +50,7 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
const excludedSourceData = this.browserSettings.excludedSources;
const excludedPackData = this.browserSettings.excludedPacks;
context.typePackCollections = game.packs.reduce((acc, pack) => {
const { type, label, packageType, packageName: basePackageName, id } = pack.metadata;
const { type, label, packageType, packageName: basePackageName, name, id } = pack.metadata;
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
const isWorldPack = packageType === 'world';
@ -68,13 +68,15 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
if (!acc[type].sources[packageName])
acc[type].sources[packageName] = { label: sourceLabel, checked: sourceChecked, packs: [] };
const checked = !excludedPackData[id] || !excludedPackData[id].excludedDocumentTypes.includes(type);
const included =
!excludedPackData[packageName] ||
!excludedPackData[packageName][name]?.excludedDocumentTypes.includes(type);
acc[type].sources[packageName].packs.push({
pack: id,
name,
type,
label: id === game.system.id ? game.system.title : game.i18n.localize(label),
checked: checked
checked: included
});
return acc;
@ -106,16 +108,16 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
toggleTypedPack(event) {
event.stopPropagation();
const { type, pack } = event.target.dataset;
const currentlyExcluded = this.browserSettings.excludedPacks[pack]
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.includes(type)
const { type, source, packName } = event.target.dataset;
const currentlyExcluded = this.browserSettings.excludedPacks[source]?.[packName]
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.includes(type)
: false;
if (!this.browserSettings.excludedPacks[pack])
this.browserSettings.excludedPacks[pack] = { excludedDocumentTypes: [] };
this.browserSettings.excludedPacks[pack].excludedDocumentTypes = currentlyExcluded
? this.browserSettings.excludedPacks[pack].excludedDocumentTypes.filter(x => x !== type)
: [...(this.browserSettings.excludedPacks[pack]?.excludedDocumentTypes ?? []), type];
this.browserSettings.excludedPacks[source] ??= {};
this.browserSettings.excludedPacks[source][packName] ??= { excludedDocumentTypes: [] };
this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes = currentlyExcluded
? this.browserSettings.excludedPacks[source][packName].excludedDocumentTypes.filter(x => x !== type)
: [...(this.browserSettings.excludedPacks[source][packName]?.excludedDocumentTypes ?? []), type];
this.render();
}

View file

@ -1,6 +1,5 @@
import { DhHomebrew } from '../../data/settings/_module.mjs';
import { Resource } from '../../data/settings/Homebrew.mjs';
import { slugify } from '../../helpers/utils.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
@ -403,12 +402,12 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
const domainName = button.form.elements.domainName.value;
if (!domainName) return;
const newSlug = slugify(domainName);
const newSlug = domainName.slugify();
const existingDomains = [
...Object.values(this.settings.domains),
...Object.values(CONFIG.DH.DOMAIN.domains)
];
if (existingDomains.find(x => slugify(game.i18n.localize(x.label)) === newSlug)) {
if (existingDomains.find(x => x.id === newSlug)) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.domains.duplicateDomain'));
return;
}
@ -529,7 +528,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
const identifier = button.form.elements.identifier.value;
if (!identifier) return;
const sluggedIdentifier = slugify(identifier);
const sluggedIdentifier = identifier.slugify();
await this.settings.updateSource({
[`resources.${actorType}.resources.${sluggedIdentifier}`]: Resource.getDefaultResourceData(identifier)

View file

@ -57,6 +57,14 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
],
contextMenus: [
{
handler: CharacterSheet.#getCreationMainContextOptions,
selector: '.character-details [data-action="editDoc"]',
options: {
parentClassHooks: false,
fixed: true
}
},
{
handler: CharacterSheet.#getDomainCardContextOptions,
selector: '[data-item-uuid][data-type="domainCard"]',
@ -319,6 +327,56 @@ export default class CharacterSheet extends DHBaseActorSheet {
/* Context Menu */
/* -------------------------------------------- */
static #getCreationMainContextOptions() {
/** Returns true if the item is managed by the level up wizard. Such items shouldn't allow things like manual removal */
function isItemWizardManaged(item) {
const actor = item?.actor;
if (!actor) return false;
// If levelup automation is off in general or for this character, all items are unmanaged
// This is disabled until we have proper granted feature removal, for now this feature is to correct errors
// const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
// if (!levelupAuto) return false;
// Core items aren't part of levelup data. TODO: add some way to flag a specific character as no auto leveling
const classPair = actor.system.class;
const coreItems = [actor.system.ancestry, actor.system.community, classPair?.value, classPair?.subclass];
if (coreItems.includes(item)) return true;
const levelups = Object.values(actor.system.levelData?.levelups) ?? [];
const uuid = item.uuid;
const sourceUuid = item._stats.compendiumSource; // on older characters this may be missing
return levelups.some(data => {
if (item.type === 'subclass') {
const selectedSubclasses = data.selections.map(s => s.secondaryData?.subclass).filter(s => !!s);
return sourceUuid
? selectedSubclasses.includes(sourceUuid)
: selectedSubclasses.length && item.system.isMulticlass;
}
const matchesCard = data.achievements.domainCards.some(i => i.itemUuid === uuid);
const matchesSelection = data.selections.some(s => s.itemUuid === uuid);
return matchesCard || matchesSelection;
});
}
return [
{
label: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash',
visible: target => {
const doc = getDocFromElementSync(target);
return doc?.isOwner && !isItemWizardManaged(doc);
},
callback: async (target, event) => {
const doc = await getDocFromElement(target);
if (event.shiftKey) return doc.delete();
else return doc.deleteDialog();
}
}
];
}
/**
* Get the set of ContextMenu options for DomainCards.
* @returns {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} - The Array of context options passed to the ContextMenu instance

View file

@ -443,11 +443,13 @@ export const typeConfig = {
const list = [];
for (const item of items.filter(item => item.system.linkedClass)) {
const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass);
if (linkedClass) {
list.push({
value: linkedClass.uuid,
label: linkedClass.name
});
}
}
return list.reduce((a, c) => {
if (!a.find(i => i.value === c.value)) a.push(c);

View file

@ -577,6 +577,8 @@ export default class DhCharacter extends DhCreature {
communityFeatures = [],
classFeatures = [],
subclassFeatures = [],
multiclassFeatures = [],
multiclassSubclassFeatures = [],
companionFeatures = [],
features = [];
@ -586,9 +588,9 @@ export default class DhCharacter extends DhCreature {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
communityFeatures.push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
classFeatures.push(item);
(item.system.multiclassOrigin ? multiclassFeatures : classFeatures).push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
subclassFeatures.push(item);
(item.system.multiclassOrigin ? multiclassSubclassFeatures : subclassFeatures).push(item);
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
companionFeatures.push(item);
} else if (item.type === 'feature' && !item.system.type) {
@ -617,6 +619,24 @@ export default class DhCharacter extends DhCreature {
type: 'subclass',
values: subclassFeatures
},
...(multiclassFeatures.length
? {
multiclassFeatures: {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} - ${this.multiclass.value?.name}`,
type: 'multiclass',
values: multiclassFeatures
}
}
: {}),
...(multiclassSubclassFeatures.length
? {
multiclassSubclassFeatures: {
title: `${game.i18n.localize('DAGGERHEART.GENERAL.multiclass')} ${game.i18n.localize('TYPES.Item.subclass')} - ${this.multiclass.subclass?.name}`,
type: 'multiclassSubclass',
values: multiclassSubclassFeatures
}
}
: {}),
companionFeatures: {
title: game.i18n.localize('DAGGERHEART.ACTORS.Character.companionFeatures'),
type: 'companion',

View file

@ -11,12 +11,14 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode
})
),
excludedPacks: new fields.TypedObjectField(
new fields.TypedObjectField(
new fields.SchemaField({
excludedDocumentTypes: new fields.ArrayField(
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
)
})
)
)
};
}
@ -28,7 +30,7 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode
const excludedSourceData = this.excludedSources[packageName];
if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
const excludedPackData = this.excludedPacks[item.pack];
const excludedPackData = this.excludedPacks[packageName]?.[pack.metadata.name];
if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
return false;

View file

@ -449,14 +449,9 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) {
effects: data.effects?.map(effect => effect.toObject())
});
}
await actor.createEmbeddedDocuments('Item', effectData);
}
export const slugify = name => {
return name.toLowerCase().replaceAll(' ', '-').replaceAll('.', '');
};
export function shuffleArray(array) {
let currentIndex = array.length;
while (currentIndex != 0) {

View file

@ -2,7 +2,7 @@
"id": "daggerheart",
"title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system",
"version": "2.2.5",
"version": "2.2.6",
"compatibility": {
"minimum": "14.361",
"verified": "14.361",
@ -10,7 +10,7 @@
},
"url": "https://github.com/Foundryborne/daggerheart",
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.5/system.zip",
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.6/system.zip",
"authors": [
{
"name": "WBHarry"

View file

@ -22,7 +22,7 @@
<div class="checks-container {{#unless source.checked}}collapsed{{/unless}}">
{{#each source.packs as |pack|}}
<div class="check-container">
<input type="checkbox" class="pack-checkbox" data-type="{{pack.type}}" data-pack="{{pack.pack}}" {{checked pack.checked}} />
<input type="checkbox" class="pack-checkbox" data-type="{{pack.type}}" data-source="{{@../key}}" data-pack-name="{{pack.name}}" {{checked pack.checked}} />
<label>{{pack.label}}</label>
</div>
{{/each}}

View file

@ -64,7 +64,7 @@
{{/if}}
</div>
{{#if document.system.multiclass.value}}
{{#if (or document.system.multiclass.value document.system.multiclass.subclass)}}
<div class="multiclass">
{{#if document.system.multiclass.value}}
<span data-action="editDoc"data-item-uuid="{{document.system.multiclass.value.uuid}}">{{document.system.multiclass.value.name}}</span>

View file

@ -6,7 +6,7 @@
{{#if roll.isCritical}}
<span>{{localize "DAGGERHEART.GENERAL.criticalShort"}}</span>
{{else}}
{{#if (and roll.dHope (not (eq roll.type "reaction")))}}
{{#if (and roll.dHope (not (eq roll.options.roll.type "reaction")))}}
<span>{{localize "DAGGERHEART.GENERAL.withThing" thing=roll.totalLabel}}</span>
{{/if}}
{{/if}}