mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-06-05 20:34:15 +02:00
Merge branch 'main' into feature/granular-action-outcomes
This commit is contained in:
commit
f260d221a8
36 changed files with 540 additions and 348 deletions
|
|
@ -439,10 +439,13 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
'system.domain': { key: 'system.domain', value: this.setup.class?.system.domains ?? null }
|
||||
};
|
||||
|
||||
if (type === 'subclasses')
|
||||
if (type === 'subclasses') {
|
||||
const classItem = this.setup.class;
|
||||
const uuid = classItem?._stats.compendiumSource ?? classItem?.uuid;
|
||||
presets.filter = {
|
||||
'system.linkedClass.uuid': { key: 'system.linkedClass.uuid', value: this.setup.class?.uuid }
|
||||
'system.linkedClass': { key: 'system.linkedClass', value: uuid }
|
||||
};
|
||||
}
|
||||
|
||||
if (equipment.includes(type))
|
||||
presets.filter = {
|
||||
|
|
@ -610,7 +613,8 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
|
|||
[foundry.utils.randomID()]: {}
|
||||
};
|
||||
} else if (item.type === 'subclass' && event.target.closest('.subclass-card')) {
|
||||
if (this.setup.class.system.subclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||
const classSubclasses = await this.setup.class.system.fetchSubclasses();
|
||||
if (classSubclasses.every(subclass => subclass.uuid !== item.uuid)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,8 +209,9 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.attributes = Object.keys(this.document.system.traits).reduce((acc, key) => {
|
||||
acc[key] = {
|
||||
...this.document.system.traits[key],
|
||||
name: game.i18n.localize(CONFIG.DH.ACTOR.abilities[key].name),
|
||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x))
|
||||
label: _loc(CONFIG.DH.ACTOR.abilities[key].label),
|
||||
verbs: CONFIG.DH.ACTOR.abilities[key].verbs.map(x => game.i18n.localize(x)),
|
||||
isSpellcasting: this.document.system.spellcastModifierTrait?.key === key
|
||||
};
|
||||
|
||||
return acc;
|
||||
|
|
@ -227,7 +228,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
|
|||
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
|
||||
|
||||
context.equippedItems = sortBy(
|
||||
this.document.items.filter(i => i.system.equipped),
|
||||
this.document.items.filter(i => i.system.equipped && (i.type === 'weapon' || i.usable)),
|
||||
i => (i.type === 'weapon' ? (i.system.secondary ? 1 : 0) : 2)
|
||||
);
|
||||
|
||||
|
|
|
|||
|
|
@ -85,6 +85,14 @@ export default class Party extends DHBaseActorSheet {
|
|||
/* Prepare Context */
|
||||
/* -------------------------------------------- */
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
||||
context.showStats =
|
||||
settings.hidePartyStats === 'never' || (settings.hidePartyStats === 'players' && game.user.isGM);
|
||||
return context;
|
||||
}
|
||||
|
||||
async _preparePartContext(partId, context, options) {
|
||||
context = await super._preparePartContext(partId, context, options);
|
||||
switch (partId) {
|
||||
|
|
|
|||
|
|
@ -104,9 +104,10 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
}
|
||||
|
||||
/**@inheritdoc */
|
||||
async _prepareContext(_options) {
|
||||
const context = await super._prepareContext(_options);
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
context.domains = this.document.system.domains;
|
||||
context.subclasses = await this.document.system.fetchSubclasses();
|
||||
return context;
|
||||
}
|
||||
|
||||
|
|
@ -128,20 +129,8 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
const target = event.target.closest('fieldset.drop-section');
|
||||
if (itemType === 'subclass') {
|
||||
if (item.system.linkedClass) {
|
||||
return ui.notifications.warn(
|
||||
game.i18n.format('DAGGERHEART.UI.Notifications.subclassAlreadyLinked', {
|
||||
name: item.name,
|
||||
class: this.document.name
|
||||
})
|
||||
);
|
||||
}
|
||||
await item.update({ 'system.linkedClass': this.document.uuid });
|
||||
await this.document.update({
|
||||
'system.subclasses': [...this.document.system.subclasses.map(x => x.uuid), item.uuid]
|
||||
});
|
||||
} else if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||
|
||||
if (['feature', 'ActiveEffect'].includes(itemType)) {
|
||||
super._onDrop(event);
|
||||
} else if (this.document.parent?.type !== 'character') {
|
||||
if (itemType === 'weapon') {
|
||||
|
|
@ -200,12 +189,6 @@ export default class ClassSheet extends DHBaseItemSheet {
|
|||
static async #removeItemFromCollection(_event, element) {
|
||||
const { uuid, target } = element.dataset;
|
||||
const prop = foundry.utils.getProperty(this.document.system, target);
|
||||
|
||||
if (target === 'subclasses') {
|
||||
const subclass = await foundry.utils.fromUuid(uuid);
|
||||
await subclass?.update({ 'system.linkedClass': null });
|
||||
}
|
||||
|
||||
await this.document.update({ [`system.${target}`]: prop.filter(i => i && i.uuid !== uuid).map(x => x.uuid) });
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -40,4 +40,36 @@ export default class SubclassSheet extends DHBaseItemSheet {
|
|||
get relatedDocs() {
|
||||
return this.document.system.features.map(x => x.item);
|
||||
}
|
||||
|
||||
async _prepareContext(options) {
|
||||
const context = await super._prepareContext(options);
|
||||
if (this.document.system.linkedClass) {
|
||||
const classData = await fromUuid(this.document.system.linkedClass);
|
||||
context.class = classData ?? {
|
||||
name: _loc('DAGGERHEART.GENERAL.missingX', { x: _loc('TYPES.Item.class') }),
|
||||
missing: true
|
||||
};
|
||||
}
|
||||
return context;
|
||||
}
|
||||
|
||||
async _onDrop(event) {
|
||||
event.stopPropagation();
|
||||
const data = TextEditor.getDragEventData(event);
|
||||
const item = await fromUuid(data.uuid);
|
||||
const itemType = data.type === 'ActiveEffect' ? data.type : item.type;
|
||||
if (itemType === 'class') {
|
||||
const uuid = item._stats.compendiumSource ?? item.uuid;
|
||||
if (this.document.system.linkedClass !== uuid) {
|
||||
await this.document.update({ 'system.linkedClass': uuid });
|
||||
// Re-render all class sheets for instant feedback
|
||||
for (const app of foundry.applications.instances.values()) {
|
||||
if (app.document?.type === 'class') app.render();
|
||||
}
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
return super._onDrop(event);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -277,7 +277,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
(await foundry.applications.ux.TextEditor.implementation.enrichHTML(item.description));
|
||||
}
|
||||
|
||||
this.fieldFilter = this._createFieldFilter();
|
||||
this.fieldFilter = await this._createFieldFilter();
|
||||
|
||||
if (this.presets?.filter) {
|
||||
Object.entries(this.presets.filter).forEach(([k, v]) => {
|
||||
|
|
@ -355,12 +355,12 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
);
|
||||
}
|
||||
|
||||
_createFieldFilter() {
|
||||
async _createFieldFilter() {
|
||||
const filters = ItemBrowser.getFolderConfig(this.selectedMenu.data, 'filters');
|
||||
filters.forEach(f => {
|
||||
for (const f of filters) {
|
||||
if (typeof f.field === 'string') f.field = foundry.utils.getProperty(game, f.field);
|
||||
else if (typeof f.choices === 'function') {
|
||||
f.choices = f.choices(this.items);
|
||||
f.choices = await f.choices(this.items);
|
||||
}
|
||||
|
||||
// Clear field label so template uses our custom label parameter
|
||||
|
|
@ -370,7 +370,8 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
|
|||
|
||||
f.name ??= f.key;
|
||||
f.value = this.presets?.filter?.[f.name]?.value ?? null;
|
||||
});
|
||||
}
|
||||
|
||||
return filters;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -383,7 +383,8 @@ export const typeConfig = {
|
|||
{
|
||||
key: 'system.linkedClass',
|
||||
label: 'TYPES.Item.class',
|
||||
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||
format: linkedClass =>
|
||||
foundry.utils.fromUuidSync(linkedClass)?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
|
||||
},
|
||||
{
|
||||
key: 'system.spellcastingTrait',
|
||||
|
|
@ -393,15 +394,18 @@ export const typeConfig = {
|
|||
],
|
||||
filters: [
|
||||
{
|
||||
key: 'system.linkedClass.uuid',
|
||||
key: 'system.linkedClass',
|
||||
label: 'TYPES.Item.class',
|
||||
choices: items => {
|
||||
const list = items
|
||||
.filter(item => item.system.linkedClass)
|
||||
.map(item => ({
|
||||
value: item.system.linkedClass.uuid,
|
||||
label: item.system.linkedClass.name
|
||||
}));
|
||||
choices: async items => {
|
||||
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
|
||||
});
|
||||
}
|
||||
|
||||
return list.reduce((a, c) => {
|
||||
if (!a.find(i => i.value === c.value)) a.push(c);
|
||||
return a;
|
||||
|
|
|
|||
|
|
@ -200,7 +200,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
const features = [];
|
||||
for (let f of this.features) {
|
||||
const fBase = f.item ?? f;
|
||||
const feature = fBase.system ? fBase : await foundry.utils.fromUuid(fBase.uuid);
|
||||
const feature = fBase.pack ? await foundry.utils.fromUuid(fBase.uuid) : fBase;
|
||||
features.push(
|
||||
foundry.utils.mergeObject(
|
||||
feature.toObject(),
|
||||
|
|
|
|||
|
|
@ -30,7 +30,6 @@ export default class DHClass extends BaseDataItem {
|
|||
}),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
|
||||
features: new ItemLinkFields(),
|
||||
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
inventory: new fields.SchemaField({
|
||||
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
choiceA: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
|
||||
|
|
@ -70,6 +69,24 @@ export default class DHClass extends BaseDataItem {
|
|||
return this.features.filter(x => x.type === CONFIG.DH.ITEM.featureSubTypes.class).map(x => x.item);
|
||||
}
|
||||
|
||||
async fetchSubclasses() {
|
||||
const uuids = [this.parent.uuid, this.parent._stats?.compendiumSource].filter(u => !!u);
|
||||
const subclasses = game.items.filter(x => x.type === 'subclass' && uuids.includes(x.system.linkedClass));
|
||||
for (const pack of game.packs) {
|
||||
const indexes = await pack.getIndex({ fields: ['system.linkedClass'] });
|
||||
for (const index of indexes) {
|
||||
if (index.type !== 'subclass') continue;
|
||||
if (!uuids.includes(index.system?.linkedClass)) continue;
|
||||
if (subclasses.find(x => x.uuid === index.uuid)) continue;
|
||||
|
||||
const subclass = await foundry.utils.fromUuid(index.uuid);
|
||||
subclasses.push(subclass);
|
||||
}
|
||||
}
|
||||
|
||||
return subclasses;
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
if (this.actor?.type === 'character') {
|
||||
const levelupAuto = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).levelupAuto;
|
||||
|
|
|
|||
|
|
@ -28,7 +28,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
features: new ItemLinkFields(),
|
||||
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
|
||||
isMulticlass: new fields.BooleanField({ initial: false }),
|
||||
linkedClass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true, initial: null })
|
||||
linkedClass: new fields.DocumentUUIDField({ type: 'Item', nullable: true, initial: null })
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -83,7 +83,8 @@ export default class DHSubclass extends BaseDataItem {
|
|||
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.missingClass'));
|
||||
return false;
|
||||
}
|
||||
if (actorClass.system.subclasses.every(x => x.uuid !== dataUuid)) {
|
||||
|
||||
if ((await actorClass.system.fetchSubclasses()).every(x => x.uuid !== dataUuid)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.subclassNotInClass'));
|
||||
return false;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,5 @@
|
|||
import { defaultRestOptions } from '../../config/generalConfig.mjs';
|
||||
import { resetAndRerenderActors } from '../../helpers/utils.mjs';
|
||||
import { ActionsField } from '../fields/actionField.mjs';
|
||||
|
||||
const currencyField = (initial, label, icon) =>
|
||||
|
|
@ -209,7 +210,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
this.refreshConfig();
|
||||
this.#resetActors();
|
||||
resetAndRerenderActors();
|
||||
}
|
||||
|
||||
/** Update config values based on homebrew data. Make sure the references don't change */
|
||||
|
|
@ -230,29 +231,6 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
|
|||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a reset and non-forced re-render on all given actors (if given)
|
||||
* or all world actors and actors in all scenes to show immediate results for a changed setting.
|
||||
*/
|
||||
#resetActors() {
|
||||
const actors = new Set(
|
||||
[
|
||||
game.actors.contents,
|
||||
game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])
|
||||
].flat()
|
||||
);
|
||||
for (const actor of actors) {
|
||||
for (const app of Object.values(actor.apps)) {
|
||||
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
|
||||
element.open = false; // This triggers a save
|
||||
}
|
||||
}
|
||||
|
||||
actor.reset();
|
||||
actor.render();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export class Resource extends foundry.abstract.DataModel {
|
||||
|
|
|
|||
|
|
@ -1,3 +1,5 @@
|
|||
import { resetAndRerenderActors } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DhMetagaming extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
|
@ -6,7 +8,24 @@ export default class DhMetagaming extends foundry.abstract.DataModel {
|
|||
initial: false,
|
||||
label: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hideObserverPermissionInChat.label',
|
||||
hint: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hideObserverPermissionInChat.hint'
|
||||
}),
|
||||
hidePartyStats: new fields.StringField({
|
||||
initial: 'never',
|
||||
label: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.label',
|
||||
hint: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.hint',
|
||||
required: true,
|
||||
nullable: false,
|
||||
choices: {
|
||||
never: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.choices.never',
|
||||
players: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.choices.players',
|
||||
always: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.choices.always'
|
||||
}
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
/** Invoked by the setting when data changes */
|
||||
handleChange() {
|
||||
resetAndRerenderActors();
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -141,8 +141,8 @@ export default class DHRoll extends Roll {
|
|||
const metagamingSettings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
|
||||
const chatData = await this._prepareChatRenderContext({ flavor, isPrivate, ...options });
|
||||
return foundry.applications.handlebars.renderTemplate(template, {
|
||||
...chatData,
|
||||
roll: this,
|
||||
...chatData,
|
||||
parent: chatData.parent,
|
||||
targetMode: chatData.targetMode,
|
||||
areas: chatData.action?.areas,
|
||||
|
|
|
|||
|
|
@ -793,6 +793,26 @@ export function getArmorSources(actor) {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Triggers a reset and non-forced re-render on all given actors (if given)
|
||||
* or all world actors and actors in all scenes to show immediate results for a changed setting.
|
||||
*/
|
||||
export function resetAndRerenderActors() {
|
||||
const actors = new Set(
|
||||
[game.actors.contents, game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])].flat()
|
||||
);
|
||||
for (const actor of actors) {
|
||||
for (const app of Object.values(actor.apps)) {
|
||||
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
|
||||
element.open = false; // This triggers a save
|
||||
}
|
||||
}
|
||||
|
||||
actor.reset();
|
||||
actor.render();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns an array sorted by a function that returns a thing to compare, or an array to compare in order
|
||||
* Similar to lodash's sortBy function.
|
||||
|
|
@ -841,3 +861,11 @@ export function createShallowProxy(obj) {
|
|||
}
|
||||
});
|
||||
}
|
||||
|
||||
export function camelize(str) {
|
||||
return str
|
||||
.replace(/(?:^\w|[A-Z]|\b\w)/g, (part, index) => {
|
||||
return index === 0 ? part.toLowerCase() : part.toUpperCase();
|
||||
})
|
||||
.replace(/\s+/g, '');
|
||||
}
|
||||
|
|
|
|||
|
|
@ -91,7 +91,10 @@ const registerMenuSettings = () => {
|
|||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming, {
|
||||
scope: 'world',
|
||||
config: false,
|
||||
type: DhMetagaming
|
||||
type: DhMetagaming,
|
||||
onChange: value => {
|
||||
value.handleChange();
|
||||
}
|
||||
});
|
||||
|
||||
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, {
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue