mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-06 04:44:16 +02:00
Merge branch 'main' into adjust-domain-card-size
This commit is contained in:
commit
d0ffbe77be
58 changed files with 1065 additions and 399 deletions
|
|
@ -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();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -57,6 +57,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
|
||||
let returnMessage = game.i18n.localize('DAGGERHEART.UI.Chat.deathMove.avoidScar');
|
||||
if (config.roll.fate.value <= this.actor.system.levelData.level.current) {
|
||||
const maxHope = this.actor.system.resources.hope.max + this.actor.system.scars;
|
||||
const newScarAmount = this.actor.system.scars + 1;
|
||||
await this.actor.update({
|
||||
system: {
|
||||
|
|
@ -64,7 +65,7 @@ export default class DhDeathMove extends HandlebarsApplicationMixin(ApplicationV
|
|||
}
|
||||
});
|
||||
|
||||
if (newScarAmount >= this.actor.system.resources.hope.max) {
|
||||
if (newScarAmount >= maxHope) {
|
||||
await this.actor.setDeathMoveDefeated(CONFIG.DH.GENERAL.defeatedConditionChoices.dead.id);
|
||||
return game.i18n.format('DAGGERHEART.UI.Chat.deathMove.journeysEnd', { scars: newScarAmount });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -124,7 +124,9 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
|
|||
const animationDuration = 500;
|
||||
const scene = game.scenes.get(game.user.viewedScene);
|
||||
/* getDependentTokens returns already removed tokens with id = null. Need to filter that until it's potentially fixed from Foundry */
|
||||
const activeTokens = actors.flatMap(member => member.getDependentTokens({ scenes: scene }).filter(x => x._id));
|
||||
const activeTokens = actors.flatMap(member =>
|
||||
member.getDependentTokens({ scenes: scene }).filter(x => x._id && !x._destroyed)
|
||||
);
|
||||
const { x: actorX, y: actorY } = this.document;
|
||||
if (activeTokens.length > 0) {
|
||||
for (let token of activeTokens) {
|
||||
|
|
|
|||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ export default class DHActionBaseConfig extends DaggerheartSheet(ApplicationV2)
|
|||
};
|
||||
}
|
||||
|
||||
if (this.action.parent.metadata?.isQuantifiable) {
|
||||
if (this.action.parent.metadata.isInventoryItem) {
|
||||
options.quantity = {
|
||||
label: 'DAGGERHEART.GENERAL.itemQuantity',
|
||||
group: 'Global'
|
||||
|
|
|
|||
|
|
@ -175,6 +175,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
|
|||
const partContext = await super._preparePartContext(partId, context);
|
||||
switch (partId) {
|
||||
case 'details':
|
||||
partContext.isItemEffect = partContext.isItemEffect || this.options.isSetting;
|
||||
const useGeneric = game.settings.get(
|
||||
CONFIG.DH.id,
|
||||
CONFIG.DH.SETTINGS.gameSettings.appearance
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
},
|
||||
onClick: async (event, target) => {
|
||||
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
|
||||
|
|
@ -335,7 +393,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async target => {
|
||||
onClick: async (_, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||
if (actorLoadout.available) return doc.update({ 'system.inVault': false });
|
||||
|
|
@ -349,7 +407,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && doc.system.inVault;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
const actorLoadout = doc.actor.system.loadoutSlot;
|
||||
if (!actorLoadout.available) {
|
||||
|
|
@ -388,7 +446,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !doc.system.inVault;
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ 'system.inVault': true })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
|
|
@ -414,7 +472,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc.isOwner && doc && !doc.system.equipped;
|
||||
},
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
},
|
||||
{
|
||||
label: 'unequip',
|
||||
|
|
@ -423,7 +481,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc.isOwner && doc && doc.system.equipped;
|
||||
},
|
||||
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
onClick: (event, target) => CharacterSheet.#toggleEquipItem.call(this, event, target)
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
|
|
@ -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,
|
||||
|
|
|
|||
|
|
@ -26,7 +26,6 @@ export default class Party extends DHBaseActorSheet {
|
|||
actions: {
|
||||
openDocument: Party.#openDocument,
|
||||
deletePartyMember: Party.#deletePartyMember,
|
||||
deleteItem: Party.#deleteItem,
|
||||
toggleHope: Party.#toggleHope,
|
||||
toggleHitPoints: Party.#toggleHitPoints,
|
||||
toggleStress: Party.#toggleStress,
|
||||
|
|
@ -509,23 +508,4 @@ export default class Party extends DHBaseActorSheet {
|
|||
const newMembersList = currentMembers.filter(uuid => uuid !== doc.uuid);
|
||||
await this.document.update({ 'system.partyMembers': newMembersList });
|
||||
}
|
||||
|
||||
static async #deleteItem(event, target) {
|
||||
const doc = await getDocFromElement(target.closest('.inventory-item'));
|
||||
if (!event.shiftKey) {
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
window: {
|
||||
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
|
||||
type: game.i18n.localize('TYPES.Actor.party'),
|
||||
name: doc.name
|
||||
})
|
||||
},
|
||||
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: doc.name })
|
||||
});
|
||||
|
||||
if (!confirmed) return;
|
||||
}
|
||||
|
||||
this.document.deleteEmbeddedDocuments('Item', [doc.id]);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -424,7 +424,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const target = element.closest('[data-item-uuid]');
|
||||
return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: true })
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: true })
|
||||
},
|
||||
{
|
||||
label: 'enableEffect',
|
||||
|
|
@ -433,7 +433,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const target = element.closest('[data-item-uuid]');
|
||||
return target.dataset.disabled && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).update({ disabled: false })
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).update({ disabled: false })
|
||||
}
|
||||
].map(option => ({
|
||||
...option,
|
||||
|
|
@ -478,7 +478,9 @@ export default function DHApplicationMixin(Base) {
|
|||
(doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
|
||||
);
|
||||
},
|
||||
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
|
||||
onClick: async (_, target) => {
|
||||
return (await getDocFromElement(target)).sheet.render({ force: true });
|
||||
}
|
||||
}
|
||||
];
|
||||
|
||||
|
|
@ -493,7 +495,7 @@ export default function DHApplicationMixin(Base) {
|
|||
!foundry.utils.isEmpty(doc?.damage?.parts);
|
||||
return doc?.isOwner && hasDamage;
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target),
|
||||
action = doc?.system?.attack ?? doc;
|
||||
const config = action.prepareConfig(event);
|
||||
|
|
@ -513,7 +515,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
|
||||
},
|
||||
callback: async (target, event) => (await getDocFromElement(target)).use(event)
|
||||
onClick: async (event, target) => (await getDocFromElement(target)).use(event)
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -521,7 +523,7 @@ export default function DHApplicationMixin(Base) {
|
|||
options.push({
|
||||
label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
|
||||
icon: 'fa-solid fa-message',
|
||||
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
onClick: async (_, target) => (await getDocFromElement(target)).toChat(this.document.uuid)
|
||||
});
|
||||
|
||||
if (deletable)
|
||||
|
|
@ -533,7 +535,7 @@ export default function DHApplicationMixin(Base) {
|
|||
const doc = getDocFromElementSync(target);
|
||||
return doc?.isOwner !== false && target.dataset.itemType !== 'beastform';
|
||||
},
|
||||
callback: async (target, event) => {
|
||||
onClick: async (event, target) => {
|
||||
const doc = await getDocFromElement(target);
|
||||
if (event.shiftKey) return doc.delete();
|
||||
else return doc.deleteDialog();
|
||||
|
|
|
|||
|
|
@ -126,7 +126,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
|
|||
options.push({
|
||||
name: 'CONTROLS.CommonDelete',
|
||||
icon: '<i class="fa-solid fa-trash"></i>',
|
||||
callback: async target => {
|
||||
onClick: async (_, target) => {
|
||||
const feature = await getDocFromElement(target);
|
||||
if (!feature) return;
|
||||
const confirmed = await foundry.applications.api.DialogV2.confirm({
|
||||
|
|
|
|||
|
|
@ -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) => {
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ export default class BeastformEffect extends BaseEffect {
|
|||
static migrateData(source) {
|
||||
if (!source.characterTokenData.tokenSize.height) source.characterTokenData.tokenSize.height = 1;
|
||||
if (!source.characterTokenData.tokenSize.width) source.characterTokenData.tokenSize.width = 1;
|
||||
if (!source.characterTokenData.tokenSize.depth) source.characterTokenData.tokenSize.depth = 1;
|
||||
|
||||
return super.migrateData(source);
|
||||
}
|
||||
|
|
@ -52,7 +53,8 @@ export default class BeastformEffect extends BaseEffect {
|
|||
if (this.parent.parent.type === 'character') {
|
||||
const baseUpdate = {
|
||||
height: this.characterTokenData.tokenSize.height,
|
||||
width: this.characterTokenData.tokenSize.width
|
||||
width: this.characterTokenData.tokenSize.width,
|
||||
depth: this.characterTokenData.tokenSize.depth
|
||||
};
|
||||
const update = {
|
||||
...baseUpdate,
|
||||
|
|
|
|||
|
|
@ -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',
|
||||
|
|
@ -840,12 +860,13 @@ export default class DhCharacter extends DhCreature {
|
|||
const newHopeMax = this.resources.hope.max + diff;
|
||||
const newHopeValue = Math.min(newHopeMax, this.resources.hope.value);
|
||||
if (newHopeValue != this.resources.hope.value) {
|
||||
if (!changes.system.resources.hope) changes.system.resources.hope = { value: 0 };
|
||||
|
||||
changes.system.resources.hope = {
|
||||
...changes.system.resources.hope,
|
||||
value: changes.system.resources.hope.value + newHopeValue
|
||||
};
|
||||
changes.system = foundry.utils.mergeObject(changes.system ?? {}, {
|
||||
resources: {
|
||||
hope: {
|
||||
value: (changes.system?.resources?.hope?.value ?? 0) + newHopeMax
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -11,11 +11,13 @@ export default class CompendiumBrowserSettings extends foundry.abstract.DataMode
|
|||
})
|
||||
),
|
||||
excludedPacks: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
excludedDocumentTypes: new fields.ArrayField(
|
||||
new fields.StringField({ required: true, choices: CONST.SYSTEM_SPECIFIC_COMPENDIUM_TYPES })
|
||||
)
|
||||
})
|
||||
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;
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ export default class CostField extends fields.ArrayField {
|
|||
static calcCosts(costs) {
|
||||
const resources = CostField.getResources.call(this, costs);
|
||||
let filteredCosts = costs;
|
||||
if (this.parent?.metadata.isQuantifiable && this.parent.consumeOnUse === false) {
|
||||
if (this.parent?.isInventoryItem && this.parent.consumeOnUse === false) {
|
||||
filteredCosts = filteredCosts.filter(c => c.key !== 'quantity');
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -141,16 +141,6 @@ export default class DHArmor extends AttachableItem {
|
|||
}
|
||||
}
|
||||
|
||||
_onUpdate(a, b, c) {
|
||||
super._onUpdate(a, b, c);
|
||||
|
||||
if (this.actor?.type === 'character') {
|
||||
for (const party of this.actor.parties) {
|
||||
party.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static migrateDocumentData(source) {
|
||||
if (!source.system.armor) {
|
||||
|
|
|
|||
|
|
@ -51,7 +51,8 @@ export default class DHBeastform extends BaseDataItem {
|
|||
}),
|
||||
scale: new fields.NumberField({ nullable: false, min: 0.2, max: 3, step: 0.05, initial: 1 }),
|
||||
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
|
||||
depth: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
|
||||
}),
|
||||
mainTrait: new fields.StringField({
|
||||
required: true,
|
||||
|
|
@ -192,7 +193,8 @@ export default class DHBeastform extends BaseDataItem {
|
|||
tokenSize: {
|
||||
scale: this.parent.parent.prototypeToken.texture.scaleX,
|
||||
height: this.parent.parent.prototypeToken.height,
|
||||
width: this.parent.parent.prototypeToken.width
|
||||
width: this.parent.parent.prototypeToken.width,
|
||||
depth: this.parent.parent.prototypeToken.depth
|
||||
}
|
||||
},
|
||||
advantageOn: this.advantageOn,
|
||||
|
|
@ -211,10 +213,12 @@ export default class DHBeastform extends BaseDataItem {
|
|||
: null;
|
||||
const width = autoTokenSize ?? this.tokenSize.width;
|
||||
const height = autoTokenSize ?? this.tokenSize.height;
|
||||
const depth = autoTokenSize ?? this.tokenSize.depth;
|
||||
|
||||
const prototypeTokenUpdate = {
|
||||
height,
|
||||
width,
|
||||
depth,
|
||||
texture: {
|
||||
src: this.tokenImg,
|
||||
scaleX: this.tokenSize.scale,
|
||||
|
|
|
|||
|
|
@ -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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -112,10 +112,22 @@ export default class DhpActor extends Actor {
|
|||
this.updateSource(update);
|
||||
}
|
||||
|
||||
/** Perform a render, debounced in order to prevent overloading repeat render requests */
|
||||
renderDebounced = foundry.utils.debounce(options => {
|
||||
return this.render(options);
|
||||
}, 10);
|
||||
|
||||
_onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId) {
|
||||
super._onUpdateDescendantDocuments(parent, collection, documents, changes, options, userId);
|
||||
for (const party of this.parties) {
|
||||
party.renderDebounced({ parts: ['partyMembers'] });
|
||||
}
|
||||
}
|
||||
|
||||
_onUpdate(changes, options, userId) {
|
||||
super._onUpdate(changes, options, userId);
|
||||
for (const party of this.parties) {
|
||||
party.render({ parts: ['partyMembers'] });
|
||||
party.renderDebounced({ parts: ['partyMembers'] });
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -134,17 +146,20 @@ export default class DhpActor extends Actor {
|
|||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
for (const party of this.parties) {
|
||||
party.render({ parts: ['partyMembers'] });
|
||||
party.renderDebounced({ parts: ['partyMembers'] });
|
||||
}
|
||||
}
|
||||
|
||||
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'));
|
||||
}
|
||||
|
|
@ -219,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({
|
||||
|
|
@ -269,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)) {
|
||||
|
|
@ -381,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'),
|
||||
|
|
@ -395,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
|
||||
|
|
@ -416,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
|
||||
|
|
@ -438,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
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class DhScene extends Scene {
|
|||
const prototype = tokenDoc.actor?.prototypeToken ?? tokenDoc;
|
||||
this.#sizeSyncBatch.set(tokenDoc.id, {
|
||||
size: tokenSize,
|
||||
prototypeSize: { width: prototype.width, height: prototype.height },
|
||||
prototypeSize: { width: prototype.width, height: prototype.height, depth: prototype.depth },
|
||||
position: { x: tokenDoc.x, y: tokenDoc.y, elevation: tokenDoc.elevation }
|
||||
});
|
||||
this.#processSyncBatch();
|
||||
|
|
@ -36,11 +36,13 @@ export default class DhScene extends Scene {
|
|||
const tokenSize = tokenSizes[size];
|
||||
const width = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.width;
|
||||
const height = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.height;
|
||||
const depth = size !== CONFIG.DH.ACTOR.tokenSize.custom.id ? tokenSize : prototypeSize.depth;
|
||||
const updatedPosition = DHToken.getSnappedPositionInSquareGrid(this.grid, position, width, height);
|
||||
return {
|
||||
_id,
|
||||
width,
|
||||
height,
|
||||
depth,
|
||||
...updatedPosition
|
||||
};
|
||||
});
|
||||
|
|
|
|||
|
|
@ -66,7 +66,8 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
|||
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||
document.updateSource({
|
||||
width: tokenSize,
|
||||
height: tokenSize
|
||||
height: tokenSize,
|
||||
depth: tokenSize
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -90,7 +91,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
|
|||
) {
|
||||
const tokenSizes = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).tokenSizes;
|
||||
const tokenSize = tokenSizes[update.system.size];
|
||||
if (tokenSize !== this.width || tokenSize !== this.height) {
|
||||
if (tokenSize !== this.width || tokenSize !== this.height || tokenSize !== this.depth) {
|
||||
this.parent?.syncTokenDimensions(this, update.system.size);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@ export default class DhTokenManager {
|
|||
if (tokenSize && actor.system.size !== CONFIG.DH.ACTOR.tokenSize.custom.id) {
|
||||
tokenData.width = tokenSize;
|
||||
tokenData.height = tokenSize;
|
||||
tokenData.depth = tokenSize;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -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) {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue