mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-05 20:34:15 +02:00
Merged with main
This commit is contained in:
commit
a6e9f2cac2
10 changed files with 120 additions and 73 deletions
|
|
@ -1,5 +1,3 @@
|
|||
import { slugify } from '../../helpers/utils.mjs';
|
||||
|
||||
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||
|
||||
export default class CompendiumBrowserSettings extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||
|
|
@ -55,9 +53,9 @@ export default class CompendiumBrowserSettings extends HandlebarsApplicationMixi
|
|||
const { type, label, packageType, packageName: basePackageName, id: baseId } = pack.metadata;
|
||||
if (!CompendiumBrowserSettings.#browserPackTypes.includes(type)) return acc;
|
||||
|
||||
const id = slugify(baseId);
|
||||
const id = baseId.slugify();
|
||||
const isWorldPack = packageType === 'world';
|
||||
const packageName = isWorldPack ? 'world' : slugify(basePackageName);
|
||||
const packageName = isWorldPack ? 'world' : basePackageName.slugify();
|
||||
const sourceChecked =
|
||||
!excludedSourceData[packageName] ||
|
||||
!excludedSourceData[packageName].excludedDocumentTypes.includes(type);
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
@ -718,7 +776,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
? {
|
||||
'system.linkedClass.uuid': {
|
||||
key: 'system.linkedClass.uuid',
|
||||
value: this.document.system.class.value._stats.compendiumSource
|
||||
value: this.document.system.class.value?._stats.compendiumSource
|
||||
}
|
||||
}
|
||||
: undefined,
|
||||
|
|
|
|||
|
|
@ -443,10 +443,12 @@ export const typeConfig = {
|
|||
const list = [];
|
||||
for (const item of items.filter(item => item.system.linkedClass)) {
|
||||
const linkedClass = await foundry.utils.fromUuid(item.system.linkedClass);
|
||||
list.push({
|
||||
value: linkedClass.uuid,
|
||||
label: linkedClass.name
|
||||
});
|
||||
if (linkedClass) {
|
||||
list.push({
|
||||
value: linkedClass.uuid,
|
||||
label: linkedClass.name
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return list.reduce((a, c) => {
|
||||
|
|
|
|||
|
|
@ -1,5 +1,3 @@
|
|||
import { slugify } from '../helpers/utils.mjs';
|
||||
|
||||
export default class CompendiumBrowserSettings extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -26,11 +24,11 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode
|
|||
const pack = game.packs.get(item.pack);
|
||||
if (!pack) return false;
|
||||
|
||||
const packageName = pack.metadata.packageType === 'world' ? 'world' : slugify(pack.metadata.packageName);
|
||||
const packageName = pack.metadata.packageType === 'world' ? 'world' : pack.metadata.packageName.slugify();
|
||||
const excludedSourceData = this.excludedSources[packageName];
|
||||
if (excludedSourceData && excludedSourceData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
||||
|
||||
const packName = slugify(item.pack);
|
||||
const packName = item.pack.slugify();
|
||||
const excludedPackData = this.excludedPacks[packName];
|
||||
if (excludedPackData && excludedPackData.excludedDocumentTypes.includes(pack.metadata.type)) return true;
|
||||
|
||||
|
|
|
|||
|
|
@ -56,38 +56,30 @@ export default class DHSubclass extends BaseDataItem {
|
|||
if (allowed === false) return;
|
||||
|
||||
if (this.actor?.type === 'character') {
|
||||
const dataUuid = data.uuid ?? data._stats.compendiumSource ?? `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'));
|
||||
return false;
|
||||
} else {
|
||||
const multiclass = this.actor.items.find(x => x.type === 'class' && x.system.isMulticlass);
|
||||
if (!multiclass) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingMulticlass'));
|
||||
return false;
|
||||
}
|
||||
const { value: actorClass, subclass: existingSubclass } = this.actor.system.class;
|
||||
const { value: multiclass, subclass: existingMultisubclass } = this.actor.system.multiclass;
|
||||
if (!actorClass && !multiclass) {
|
||||
ui.notifications.warn('DAGGERHEART.UI.Notifications.missingClass', { localize: true });
|
||||
return false;
|
||||
}
|
||||
if (existingSubclass && existingMultisubclass) {
|
||||
ui.notifications.warn('DAGGERHEART.UI.Notifications.subclassesAlreadyPresent', { localize: true });
|
||||
return false;
|
||||
}
|
||||
if (existingSubclass && !multiclass) {
|
||||
ui.notifications.warn('DAGGERHEART.UI.Notifications.missingMulticlass', { localize: true });
|
||||
return false;
|
||||
}
|
||||
|
||||
if (multiclass.system.subclasses.every(x => x.uuid !== dataUuid)) {
|
||||
ui.notifications.error(
|
||||
game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInMulticlass')
|
||||
);
|
||||
return false;
|
||||
}
|
||||
|
||||
await this.updateSource({ isMulticlass: true });
|
||||
}
|
||||
} else {
|
||||
const actorClass = this.actor.items.find(x => x.type === 'class' && !x.system.isMulticlass);
|
||||
if (!actorClass) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if ((await actorClass.system.fetchSubclasses()).every(x => x.uuid !== dataUuid)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
||||
return false;
|
||||
}
|
||||
const match = [multiclass, actorClass].find(
|
||||
c => c && (c._stats.compendiumSource ?? c.uuid) === this.linkedClass
|
||||
);
|
||||
if (!match) {
|
||||
const key = multiclass ? 'subclassNotInMulticlass' : 'subclassNotInClass';
|
||||
ui.notifications.warn(`DAGGERHEART.UI.Notifications.${key}`, { localize: true });
|
||||
return false;
|
||||
} else if (match.system.isMulticlass) {
|
||||
await this.updateSource({ isMulticlass: true });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -153,10 +153,13 @@ export default class DhpActor extends Actor {
|
|||
async updateLevel(newLevel) {
|
||||
if (!['character', 'companion'].includes(this.type) || newLevel === this.system.levelData.level.changed) return;
|
||||
|
||||
const tiers = Object.values(game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers);
|
||||
const maxLevel = tiers.reduce((acc, tier) => Math.max(acc, tier.levels.end), 0);
|
||||
const multiclassMinLevel = Math.min(
|
||||
maxLevel,
|
||||
...tiers.filter(t => t.options.multiclass).map(t => t.levels.start)
|
||||
);
|
||||
if (newLevel > this.system.levelData.level.current) {
|
||||
const maxLevel = Object.values(
|
||||
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers
|
||||
).reduce((acc, tier) => Math.max(acc, tier.levels.end), 0);
|
||||
if (newLevel > maxLevel) {
|
||||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.tooHighLevel'));
|
||||
}
|
||||
|
|
@ -231,18 +234,19 @@ export default class DhpActor extends Actor {
|
|||
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.multiclassOrigin
|
||||
);
|
||||
const subclassFeatures = this.items.filter(
|
||||
x => x.system.originItemType === 'subclass' && x.system.multiclassOrigin
|
||||
// Remove multiclass if we're removing a multiclass feature or if we're below the multiclass minimum level
|
||||
// Multclasses cannot be manually removed on the sheet, so this allows recovering in the case of errors
|
||||
if (multiclass || newLevel < multiclassMinLevel) {
|
||||
const multiclassItems = this.items.filter(
|
||||
x =>
|
||||
x.uuid === multiclass?.itemUuid ||
|
||||
x.system.isMulticlass ||
|
||||
(['class', 'subclass'].includes(x.system.originItemType) && x.system.multiclassOrigin)
|
||||
);
|
||||
|
||||
this.deleteEmbeddedDocuments(
|
||||
'Item',
|
||||
[multiclassItem, ...multiclassFeatures, ...subclassFeatures].map(x => x.id)
|
||||
multiclassItems.map(x => x.id)
|
||||
);
|
||||
|
||||
this.update({
|
||||
|
|
@ -281,6 +285,7 @@ export default class DhpActor extends Actor {
|
|||
|
||||
async levelUp(levelupData) {
|
||||
const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
|
||||
const getStatsWithSource = document => ({ ...(document._stats ?? {}), compendiumSource: document.uuid });
|
||||
|
||||
const levelups = {};
|
||||
for (var levelKey of Object.keys(levelupData)) {
|
||||
|
|
@ -393,8 +398,8 @@ export default class DhpActor extends Actor {
|
|||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...multiclassData,
|
||||
uuid: multiclassItem.uuid,
|
||||
_stats: multiclassItem._stats,
|
||||
uuid: multiclassItem.uuid, // todo: replace with setting an id and using keepId
|
||||
_stats: getStatsWithSource(multiclassItem),
|
||||
system: {
|
||||
...multiclassData.system,
|
||||
features: multiclassData.system.features.filter(x => x.type !== 'hope'),
|
||||
|
|
@ -407,8 +412,8 @@ export default class DhpActor extends Actor {
|
|||
await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...subclassData,
|
||||
uuid: subclassItem.uuid,
|
||||
_stats: subclassItem._stats,
|
||||
uuid: subclassItem.uuid, // todo: replace with setting an id and using keepId
|
||||
_stats: getStatsWithSource(subclassItem),
|
||||
system: {
|
||||
...subclassData.system,
|
||||
isMulticlass: true
|
||||
|
|
@ -428,8 +433,8 @@ export default class DhpActor extends Actor {
|
|||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...cardData,
|
||||
uuid: cardItem.uuid,
|
||||
_stats: cardItem._stats,
|
||||
uuid: cardItem.uuid, // todo: replace with setting an id and using keepId
|
||||
_stats: getStatsWithSource(cardItem),
|
||||
system: {
|
||||
...cardData.system,
|
||||
inVault: true
|
||||
|
|
@ -450,8 +455,7 @@ export default class DhpActor extends Actor {
|
|||
const embeddedItem = await this.createEmbeddedDocuments('Item', [
|
||||
{
|
||||
...cardData,
|
||||
uuid: cardItem.uuid,
|
||||
_stats: cardItem._stats,
|
||||
_stats: getStatsWithSource(cardItem),
|
||||
system: {
|
||||
...cardData.system,
|
||||
inVault: true
|
||||
|
|
|
|||
|
|
@ -452,10 +452,6 @@ export async function createEmbeddedItemsWithEffects(actor, baseData) {
|
|||
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) {
|
||||
|
|
|
|||
|
|
@ -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>
|
||||
|
|
|
|||
|
|
@ -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}}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue