mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-15 05:01:08 +01:00
Feature/116-implementation-of-pseudo-documents (#125)
* FEAT: add baseDataModel logic * FEAT: new PseudoDocumentsField FIX: BasePseudoDocument 's getEmbeddedDocument * FEAT: PseudoDocument class * FEAT: add TypedPseudoDocument REFACTOR: PreudoDocument FIX: Typos Bug * FIX: CONFIG types * FEAT: basic PseudoDocumentSheet * FIX: remove schema ADD: input of example --------- Co-authored-by: Joaquin Pereyra <joaquinpereyra98@users.noreply.github.com> Co-authored-by: WBHarry <williambjrklund@gmail.com>
This commit is contained in:
parent
3a0a4673ad
commit
f840dc2553
41 changed files with 844 additions and 190 deletions
1
daggerheart.d.ts
vendored
1
daggerheart.d.ts
vendored
|
|
@ -1,4 +1,3 @@
|
||||||
import './module/_types';
|
|
||||||
import '@client/global.mjs';
|
import '@client/global.mjs';
|
||||||
import Canvas from '@client/canvas/board.mjs';
|
import Canvas from '@client/canvas/board.mjs';
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -19,6 +19,7 @@ globalThis.SYSTEM = SYSTEM;
|
||||||
|
|
||||||
Hooks.once('init', () => {
|
Hooks.once('init', () => {
|
||||||
CONFIG.daggerheart = SYSTEM;
|
CONFIG.daggerheart = SYSTEM;
|
||||||
|
|
||||||
game.system.api = {
|
game.system.api = {
|
||||||
applications,
|
applications,
|
||||||
models,
|
models,
|
||||||
|
|
|
||||||
0
module/_types.d.ts
vendored
0
module/_types.d.ts
vendored
|
|
@ -13,3 +13,5 @@ export { default as DhpArmor } from './sheets/items/armor.mjs';
|
||||||
export { default as DhpChatMessage } from './chatMessage.mjs';
|
export { default as DhpChatMessage } from './chatMessage.mjs';
|
||||||
export { default as DhpEnvironment } from './sheets/environment.mjs';
|
export { default as DhpEnvironment } from './sheets/environment.mjs';
|
||||||
export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs';
|
export { default as DhActiveEffectConfig } from './sheets/activeEffectConfig.mjs';
|
||||||
|
|
||||||
|
export * as pseudoDocumentSheet from './sheets/pseudo-documents/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -4,7 +4,7 @@ const { ApplicationV2 } = foundry.applications.api;
|
||||||
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
constructor(action) {
|
constructor(action) {
|
||||||
super({});
|
super({});
|
||||||
|
|
||||||
this.action = action;
|
this.action = action;
|
||||||
this.openSection = null;
|
this.openSection = null;
|
||||||
}
|
}
|
||||||
|
|
@ -59,8 +59,8 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
context.openSection = this.openSection;
|
context.openSection = this.openSection;
|
||||||
context.tabs = this._getTabs();
|
context.tabs = this._getTabs();
|
||||||
context.config = SYSTEM;
|
context.config = SYSTEM;
|
||||||
if(!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
|
if (!!this.action.effects) context.effects = this.action.effects.map(e => this.action.item.effects.get(e._id));
|
||||||
if(this.action.damage?.hasOwnProperty('includeBase')) context.hasBaseDamage = !!this.action.parent.damage;
|
if (this.action.damage?.hasOwnProperty('includeBase')) context.hasBaseDamage = !!this.action.parent.damage;
|
||||||
context.getRealIndex = this.getRealIndex.bind(this);
|
context.getRealIndex = this.getRealIndex.bind(this);
|
||||||
return context;
|
return context;
|
||||||
}
|
}
|
||||||
|
|
@ -86,10 +86,10 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
static async updateForm(event, _, formData) {
|
static async updateForm(event, _, formData) {
|
||||||
const submitData = this._prepareSubmitData(event, formData),
|
const submitData = this._prepareSubmitData(event, formData),
|
||||||
data = foundry.utils.expandObject(foundry.utils.mergeObject(this.action.toObject(), submitData)),
|
data = foundry.utils.expandObject(foundry.utils.mergeObject(this.action.toObject(), submitData)),
|
||||||
newActions = this.action.parent.actions.map(x => x.toObject()); // Find better way
|
newActions = this.action.parent.actions.map(x => x.toObject()); // Find better way
|
||||||
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
|
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
|
||||||
const updates = await this.action.parent.parent.update({ 'system.actions': newActions });
|
const updates = await this.action.parent.parent.update({ 'system.actions': newActions });
|
||||||
if(!updates) return;
|
if (!updates) return;
|
||||||
this.action = updates.system.actions[this.action.index];
|
this.action = updates.system.actions[this.action.index];
|
||||||
this.render();
|
this.render();
|
||||||
}
|
}
|
||||||
|
|
@ -97,7 +97,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
static addElement(event) {
|
static addElement(event) {
|
||||||
const data = this.action.toObject(),
|
const data = this.action.toObject(),
|
||||||
key = event.target.closest('.action-category-data').dataset.key;
|
key = event.target.closest('.action-category-data').dataset.key;
|
||||||
if ( !this.action[key] ) return;
|
if (!this.action[key]) return;
|
||||||
data[key].push({});
|
data[key].push({});
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
@ -109,16 +109,16 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
data[key].splice(index, 1);
|
data[key].splice(index, 1);
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static addDamage(event) {
|
static addDamage(event) {
|
||||||
if ( !this.action.damage.parts ) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject();
|
const data = this.action.toObject();
|
||||||
data.damage.parts.push({});
|
data.damage.parts.push({});
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeDamage(event) {
|
static removeDamage(event) {
|
||||||
if ( !this.action.damage.parts ) return;
|
if (!this.action.damage.parts) return;
|
||||||
const data = this.action.toObject(),
|
const data = this.action.toObject(),
|
||||||
index = event.target.dataset.index;
|
index = event.target.dataset.index;
|
||||||
data.damage.parts.splice(index, 1);
|
data.damage.parts.splice(index, 1);
|
||||||
|
|
@ -126,15 +126,15 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addEffect(event) {
|
static async addEffect(event) {
|
||||||
if ( !this.action.effects ) return;
|
if (!this.action.effects) return;
|
||||||
const effectData = this._addEffectData.bind(this)(),
|
const effectData = this._addEffectData.bind(this)(),
|
||||||
[created] = await this.action.item.createEmbeddedDocuments("ActiveEffect", [effectData], { render: false }),
|
[created] = await this.action.item.createEmbeddedDocuments('ActiveEffect', [effectData], { render: false }),
|
||||||
data = this.action.toObject();
|
data = this.action.toObject();
|
||||||
data.effects.push( { '_id': created._id } )
|
data.effects.push({ _id: created._id });
|
||||||
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The data for a newly created applied effect.
|
* The data for a newly created applied effect.
|
||||||
* @returns {object}
|
* @returns {object}
|
||||||
* @protected
|
* @protected
|
||||||
|
|
@ -149,14 +149,12 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
|
||||||
}
|
}
|
||||||
|
|
||||||
static removeEffect(event) {
|
static removeEffect(event) {
|
||||||
if ( !this.action.effects ) return;
|
if (!this.action.effects) return;
|
||||||
const index = event.target.dataset.index,
|
const index = event.target.dataset.index,
|
||||||
effectId = this.action.effects[index]._id;
|
effectId = this.action.effects[index]._id;
|
||||||
this.constructor.removeElement.bind(this)(event);
|
this.constructor.removeElement.bind(this)(event);
|
||||||
this.action.item.deleteEmbeddedDocuments("ActiveEffect", [effectId]);
|
this.action.item.deleteEmbeddedDocuments('ActiveEffect', [effectId]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static editEffect(event) {
|
static editEffect(event) {}
|
||||||
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,7 @@ export default function DHItemMixin(Base) {
|
||||||
editAction: this.editAction,
|
editAction: this.editAction,
|
||||||
removeAction: this.removeAction
|
removeAction: this.removeAction
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
static TABS = {
|
static TABS = {
|
||||||
description: {
|
description: {
|
||||||
|
|
@ -49,7 +49,7 @@ export default function DHItemMixin(Base) {
|
||||||
icon: null,
|
icon: null,
|
||||||
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
|
label: 'DAGGERHEART.Sheets.Feature.Tabs.Settings'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
||||||
async _prepareContext(_options) {
|
async _prepareContext(_options) {
|
||||||
const context = await super._prepareContext(_options);
|
const context = await super._prepareContext(_options);
|
||||||
|
|
@ -67,8 +67,8 @@ export default function DHItemMixin(Base) {
|
||||||
|
|
||||||
static async selectActionType() {
|
static async selectActionType() {
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/daggerheart/templates/views/actionType.hbs",
|
'systems/daggerheart/templates/views/actionType.hbs',
|
||||||
{types: SYSTEM.ACTIONS.actionTypes}
|
{ types: SYSTEM.ACTIONS.actionTypes }
|
||||||
),
|
),
|
||||||
title = 'Select Action Type',
|
title = 'Select Action Type',
|
||||||
type = 'form',
|
type = 'form',
|
||||||
|
|
@ -76,21 +76,22 @@ export default function DHItemMixin(Base) {
|
||||||
return Dialog.prompt({
|
return Dialog.prompt({
|
||||||
title,
|
title,
|
||||||
label: title,
|
label: title,
|
||||||
content, type,
|
content,
|
||||||
|
type,
|
||||||
callback: html => {
|
callback: html => {
|
||||||
const form = html[0].querySelector("form"),
|
const form = html[0].querySelector('form'),
|
||||||
fd = new foundry.applications.ux.FormDataExtended(form);
|
fd = new foundry.applications.ux.FormDataExtended(form);
|
||||||
foundry.utils.mergeObject(data, fd.object, { inplace: true });
|
foundry.utils.mergeObject(data, fd.object, { inplace: true });
|
||||||
// if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name);
|
// if (!data.name?.trim()) data.name = game.i18n.localize(SYSTEM.ACTIONS.actionTypes[data.type].name);
|
||||||
return data;
|
return data;
|
||||||
},
|
},
|
||||||
rejectClose: false
|
rejectClose: false
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
static async addAction() {
|
static async addAction() {
|
||||||
const actionType = await DHItemSheetV2.selectActionType(),
|
const actionType = await DHItemSheetV2.selectActionType(),
|
||||||
actionIndexes = this.document.system.actions.map(x => x._id.split('-')[2]).sort((a, b) => a - b)
|
actionIndexes = this.document.system.actions.map(x => x._id.split('-')[2]).sort((a, b) => a - b);
|
||||||
try {
|
try {
|
||||||
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
|
const cls = actionsTypes[actionType?.type] ?? actionsTypes.attack,
|
||||||
action = new cls(
|
action = new cls(
|
||||||
|
|
@ -105,10 +106,12 @@ export default function DHItemMixin(Base) {
|
||||||
parent: this.document
|
parent: this.document
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
|
await this.document.update({ 'system.actions': [...this.document.system.actions, action] });
|
||||||
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render(true);
|
await new DHActionConfig(this.document.system.actions[this.document.system.actions.length - 1]).render(
|
||||||
|
true
|
||||||
|
);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.log(error)
|
console.log(error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -125,5 +128,5 @@ export default function DHItemMixin(Base) {
|
||||||
)
|
)
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import DHItemSheetV2 from '../item.mjs'
|
import DHItemSheetV2 from '../item.mjs';
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
export default class ConsumableSheet extends DHItemSheetV2(ItemSheetV2) {
|
export default class ConsumableSheet extends DHItemSheetV2(ItemSheetV2) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import DHItemSheetV2 from '../item.mjs'
|
import DHItemSheetV2 from '../item.mjs';
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
export default class DomainCardSheet extends DHItemSheetV2(ItemSheetV2) {
|
export default class DomainCardSheet extends DHItemSheetV2(ItemSheetV2) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import DHItemSheetV2 from '../item.mjs'
|
import DHItemSheetV2 from '../item.mjs';
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
|
export default class FeatureSheet extends DHItemSheetV2(ItemSheetV2) {
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import DHItemSheetV2 from '../item.mjs'
|
import DHItemSheetV2 from '../item.mjs';
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
export default class MiscellaneousSheet extends DHItemSheetV2(ItemSheetV2) {
|
export default class MiscellaneousSheet extends DHItemSheetV2(ItemSheetV2) {
|
||||||
|
|
|
||||||
|
|
@ -1,10 +1,10 @@
|
||||||
import DHItemSheetV2 from '../item.mjs'
|
import DHItemSheetV2 from '../item.mjs';
|
||||||
|
|
||||||
const { ItemSheetV2 } = foundry.applications.sheets;
|
const { ItemSheetV2 } = foundry.applications.sheets;
|
||||||
export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
|
export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
|
||||||
static DEFAULT_OPTIONS = {
|
static DEFAULT_OPTIONS = {
|
||||||
classes: ['weapon']
|
classes: ['weapon']
|
||||||
}
|
};
|
||||||
|
|
||||||
static PARTS = {
|
static PARTS = {
|
||||||
header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' },
|
header: { template: 'systems/daggerheart/templates/sheets/items/weapon/header.hbs' },
|
||||||
|
|
@ -18,5 +18,5 @@ export default class WeaponSheet extends DHItemSheetV2(ItemSheetV2) {
|
||||||
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
|
template: 'systems/daggerheart/templates/sheets/items/weapon/settings.hbs',
|
||||||
scrollable: ['.settings']
|
scrollable: ['.settings']
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
1
module/applications/sheets/pseudo-documents/_module.mjs
Normal file
1
module/applications/sheets/pseudo-documents/_module.mjs
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
export {default as PseudoDocumentSheet }from "./pseudo-documents-sheet.mjs";
|
||||||
|
|
@ -0,0 +1,66 @@
|
||||||
|
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
|
||||||
|
|
||||||
|
export default class PseudoDocumentSheet extends HandlebarsApplicationMixin(ApplicationV2) {
|
||||||
|
constructor(options) {
|
||||||
|
super(options);
|
||||||
|
this.#pseudoDocument = options.document;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The UUID of the associated pseudo-document
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get pseudoUuid() {
|
||||||
|
return this.pseudoDocument.uuid;
|
||||||
|
}
|
||||||
|
|
||||||
|
#pseudoDocument;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The pseudo-document instance this sheet represents
|
||||||
|
* @type {object}
|
||||||
|
*/
|
||||||
|
get pseudoDocument() {
|
||||||
|
return this.#pseudoDocument;
|
||||||
|
}
|
||||||
|
|
||||||
|
static DEFAULT_OPTIONS = {
|
||||||
|
tag: 'form',
|
||||||
|
classes: ['daggerheart', 'sheet'],
|
||||||
|
position: { width: 600 },
|
||||||
|
form: {
|
||||||
|
handler: PseudoDocumentSheet.#onSubmitForm,
|
||||||
|
submitOnChange: true,
|
||||||
|
closeOnSubmit: false
|
||||||
|
},
|
||||||
|
dragDrop: [{ dragSelector: null, dropSelector: null }],
|
||||||
|
};
|
||||||
|
|
||||||
|
static PARTS = {
|
||||||
|
header: { template: 'systems/daggerheart/templates/sheets/pseudo-documents/header.hbs' },
|
||||||
|
};
|
||||||
|
|
||||||
|
/** @inheritDoc */
|
||||||
|
async _prepareContext(options) {
|
||||||
|
const context = await super._prepareContext(options);
|
||||||
|
const document = this.pseudoDocument;
|
||||||
|
return Object.assign(context, {
|
||||||
|
document,
|
||||||
|
source: document._source,
|
||||||
|
editable: this.isEditable,
|
||||||
|
user: game.user,
|
||||||
|
rootId: this.id,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Form submission handler
|
||||||
|
* @param {SubmitEvent | Event} event - The originating form submission or input change event
|
||||||
|
* @param {HTMLFormElement} form - The form element that was submitted
|
||||||
|
* @param {foundry.applications.ux.FormDataExtended} formData - Processed data for the submitted form
|
||||||
|
*/
|
||||||
|
static async #onSubmitForm(event, form, formData) {
|
||||||
|
const submitData = foundry.utils.expandObject(formData.object);
|
||||||
|
await this.pseudoDocument.update(submitData);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -2,42 +2,42 @@ export const actionTypes = {
|
||||||
attack: {
|
attack: {
|
||||||
id: 'attack',
|
id: 'attack',
|
||||||
name: 'DAGGERHEART.Actions.Types.Attack.Name',
|
name: 'DAGGERHEART.Actions.Types.Attack.Name',
|
||||||
icon: "fa-swords"
|
icon: 'fa-swords'
|
||||||
},
|
},
|
||||||
spellcast: {
|
spellcast: {
|
||||||
id: 'spellcast',
|
id: 'spellcast',
|
||||||
name: 'DAGGERHEART.Actions.Types.Spellcast.Name',
|
name: 'DAGGERHEART.Actions.Types.Spellcast.Name',
|
||||||
icon: "fa-book-sparkles"
|
icon: 'fa-book-sparkles'
|
||||||
},
|
},
|
||||||
healing: {
|
healing: {
|
||||||
id: 'healing',
|
id: 'healing',
|
||||||
name: 'DAGGERHEART.Actions.Types.Healing.Name',
|
name: 'DAGGERHEART.Actions.Types.Healing.Name',
|
||||||
icon: "fa-kit-medical"
|
icon: 'fa-kit-medical'
|
||||||
},
|
},
|
||||||
resource: {
|
resource: {
|
||||||
id: 'resource',
|
id: 'resource',
|
||||||
name: 'DAGGERHEART.Actions.Types.Resource.Name',
|
name: 'DAGGERHEART.Actions.Types.Resource.Name',
|
||||||
icon: "fa-honey-pot"
|
icon: 'fa-honey-pot'
|
||||||
},
|
},
|
||||||
damage: {
|
damage: {
|
||||||
id: 'damage',
|
id: 'damage',
|
||||||
name: 'DAGGERHEART.Actions.Types.Damage.Name',
|
name: 'DAGGERHEART.Actions.Types.Damage.Name',
|
||||||
icon: "fa-bone-break"
|
icon: 'fa-bone-break'
|
||||||
},
|
},
|
||||||
summon: {
|
summon: {
|
||||||
id: 'summon',
|
id: 'summon',
|
||||||
name: 'DAGGERHEART.Actions.Types.Summon.Name',
|
name: 'DAGGERHEART.Actions.Types.Summon.Name',
|
||||||
icon: "fa-ghost"
|
icon: 'fa-ghost'
|
||||||
},
|
},
|
||||||
effect: {
|
effect: {
|
||||||
id: 'effect',
|
id: 'effect',
|
||||||
name: 'DAGGERHEART.Actions.Types.Effect.Name',
|
name: 'DAGGERHEART.Actions.Types.Effect.Name',
|
||||||
icon: "fa-person-rays"
|
icon: 'fa-person-rays'
|
||||||
},
|
},
|
||||||
macro: {
|
macro: {
|
||||||
id: 'macro',
|
id: 'macro',
|
||||||
name: 'DAGGERHEART.Actions.Types.Macro.Name',
|
name: 'DAGGERHEART.Actions.Types.Macro.Name',
|
||||||
icon: "fa-scroll"
|
icon: 'fa-scroll'
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -336,4 +336,4 @@ export const rollTypes = {
|
||||||
id: 'ability',
|
id: 'ability',
|
||||||
label: 'DAGGERHEART.RollTypes.ability.name'
|
label: 'DAGGERHEART.RollTypes.ability.name'
|
||||||
}
|
}
|
||||||
}
|
};
|
||||||
|
|
|
||||||
17
module/config/pseudoConfig.mjs
Normal file
17
module/config/pseudoConfig.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
||||||
|
import { pseudoDocuments } from "../data/_module.mjs";
|
||||||
|
import { pseudoDocumentSheet } from "../applications/_module.mjs";
|
||||||
|
|
||||||
|
//CONFIG.daggerheart.pseudoDocuments
|
||||||
|
export default {
|
||||||
|
sheetClass: pseudoDocumentSheet.PseudoDocumentSheet,
|
||||||
|
feature: {
|
||||||
|
label: "DAGGERHEART.Feature.Label",
|
||||||
|
documentClass: pseudoDocuments.feature.BaseFeatureData,
|
||||||
|
types: {
|
||||||
|
weapon: {
|
||||||
|
label: "DAGGERHEART.Feature.Weapon.Label",
|
||||||
|
documentClass: pseudoDocuments.feature.WeaponFeature,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
@ -5,6 +5,7 @@ import * as ITEM from './itemConfig.mjs';
|
||||||
import * as SETTINGS from './settingsConfig.mjs';
|
import * as SETTINGS from './settingsConfig.mjs';
|
||||||
import * as EFFECTS from './effectConfig.mjs';
|
import * as EFFECTS from './effectConfig.mjs';
|
||||||
import * as ACTIONS from './actionConfig.mjs';
|
import * as ACTIONS from './actionConfig.mjs';
|
||||||
|
import pseudoDocuments from "./pseudoConfig.mjs";
|
||||||
|
|
||||||
export const SYSTEM_ID = 'daggerheart';
|
export const SYSTEM_ID = 'daggerheart';
|
||||||
|
|
||||||
|
|
@ -16,5 +17,6 @@ export const SYSTEM = {
|
||||||
ITEM,
|
ITEM,
|
||||||
SETTINGS,
|
SETTINGS,
|
||||||
EFFECTS,
|
EFFECTS,
|
||||||
ACTIONS
|
ACTIONS,
|
||||||
|
pseudoDocuments
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -7,3 +7,5 @@ export * as actors from './actor/_module.mjs';
|
||||||
export * as items from './item/_module.mjs';
|
export * as items from './item/_module.mjs';
|
||||||
export { actionsTypes } from './action/_module.mjs';
|
export { actionsTypes } from './action/_module.mjs';
|
||||||
export * as messages from './chat-message/_modules.mjs';
|
export * as messages from './chat-message/_modules.mjs';
|
||||||
|
export * as fields from './fields/_module.mjs';
|
||||||
|
export * as pseudoDocuments from './pseudo-documents/_module.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,14 @@
|
||||||
import { DHAttackAction, DHBaseAction, DHDamageAction, DHEffectAction, DHHealingAction, DHMacroAction, DHResourceAction, DHSpellCastAction, DHSummonAction } from "./action.mjs";
|
import {
|
||||||
|
DHAttackAction,
|
||||||
|
DHBaseAction,
|
||||||
|
DHDamageAction,
|
||||||
|
DHEffectAction,
|
||||||
|
DHHealingAction,
|
||||||
|
DHMacroAction,
|
||||||
|
DHResourceAction,
|
||||||
|
DHSpellCastAction,
|
||||||
|
DHSummonAction
|
||||||
|
} from './action.mjs';
|
||||||
|
|
||||||
export const actionsTypes = {
|
export const actionsTypes = {
|
||||||
base: DHBaseAction,
|
base: DHBaseAction,
|
||||||
|
|
@ -10,4 +20,4 @@ export const actionsTypes = {
|
||||||
summon: DHSummonAction,
|
summon: DHSummonAction,
|
||||||
effect: DHEffectAction,
|
effect: DHEffectAction,
|
||||||
macro: DHMacroAction
|
macro: DHMacroAction
|
||||||
}
|
};
|
||||||
|
|
|
||||||
|
|
@ -334,8 +334,14 @@ export class DHResourceAction extends DHBaseAction {
|
||||||
...extraDefineSchema('target'),
|
...extraDefineSchema('target'),
|
||||||
...extraDefineSchema('effects'),
|
...extraDefineSchema('effects'),
|
||||||
resource: new fields.SchemaField({
|
resource: new fields.SchemaField({
|
||||||
type: new fields.StringField({ choices: [], blank: true, required: false, initial: "", label: "Resource" }),
|
type: new fields.StringField({
|
||||||
value: new fields.NumberField({ initial: 0, label: "Value" })
|
choices: [],
|
||||||
|
blank: true,
|
||||||
|
required: false,
|
||||||
|
initial: '',
|
||||||
|
label: 'Resource'
|
||||||
|
}),
|
||||||
|
value: new fields.NumberField({ initial: 0, label: 'Value' })
|
||||||
})
|
})
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,4 +1,4 @@
|
||||||
import FormulaField from "../fields/formulaField.mjs";
|
import FormulaField from '../fields/formulaField.mjs';
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
||||||
|
|
@ -6,27 +6,33 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
||||||
/** @override */
|
/** @override */
|
||||||
static defineSchema() {
|
static defineSchema() {
|
||||||
return {
|
return {
|
||||||
multiplier: new fields.StringField({ choices: SYSTEM.GENERAL.multiplierTypes, initial: 'proficiency', label: 'Multiplier' }),
|
multiplier: new fields.StringField({
|
||||||
|
choices: SYSTEM.GENERAL.multiplierTypes,
|
||||||
|
initial: 'proficiency',
|
||||||
|
label: 'Multiplier'
|
||||||
|
}),
|
||||||
dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Formula' }),
|
dice: new fields.StringField({ choices: SYSTEM.GENERAL.diceTypes, initial: 'd6', label: 'Formula' }),
|
||||||
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
|
bonus: new fields.NumberField({ nullable: true, initial: null, label: 'Bonus' }),
|
||||||
custom: new fields.SchemaField({
|
custom: new fields.SchemaField({
|
||||||
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
|
enabled: new fields.BooleanField({ label: 'Custom Formula' }),
|
||||||
formula: new FormulaField( { label: 'Formula' } )
|
formula: new FormulaField({ label: 'Formula' })
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
getFormula(actor) {
|
getFormula(actor) {
|
||||||
return this.custom.enabled ? this.custom.formula : `${(actor.system[this.multiplier] ?? 1)}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`;
|
return this.custom.enabled
|
||||||
|
? this.custom.formula
|
||||||
|
: `${actor.system[this.multiplier] ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export class DHDamageField extends fields.SchemaField {
|
export class DHDamageField extends fields.SchemaField {
|
||||||
constructor(hasBase, options, context={}) {
|
constructor(hasBase, options, context = {}) {
|
||||||
const damageFields = {
|
const damageFields = {
|
||||||
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
|
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData))
|
||||||
}
|
};
|
||||||
if(hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true })
|
if (hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true });
|
||||||
super(damageFields, options, context);
|
super(damageFields, options, context);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -44,6 +50,6 @@ export class DHDamageData extends DHActionDiceData {
|
||||||
nullable: false,
|
nullable: false,
|
||||||
required: true
|
required: true
|
||||||
})
|
})
|
||||||
}
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,2 +1,3 @@
|
||||||
export { default as FormulaField } from "./formulaField.mjs";
|
export { default as FormulaField } from './formulaField.mjs';
|
||||||
export { default as ForeignDocumentUUIDField } from "./foreignDocumentUUIDField.mjs";
|
export { default as ForeignDocumentUUIDField } from './foreignDocumentUUIDField.mjs';
|
||||||
|
export { default as PseudoDocumentsField } from './pseudoDocumentsField.mjs';
|
||||||
|
|
|
||||||
|
|
@ -1,8 +1,7 @@
|
||||||
import { actionsTypes } from "../action/_module.mjs";
|
import { actionsTypes } from '../action/_module.mjs';
|
||||||
|
|
||||||
// Temporary Solution
|
// Temporary Solution
|
||||||
export default class ActionField extends foundry.data.fields.ObjectField {
|
export default class ActionField extends foundry.data.fields.ObjectField {
|
||||||
|
|
||||||
getModel(value) {
|
getModel(value) {
|
||||||
return actionsTypes[value.type] ?? actionsTypes.attack;
|
return actionsTypes[value.type] ?? actionsTypes.attack;
|
||||||
}
|
}
|
||||||
|
|
@ -11,10 +10,10 @@ export default class ActionField extends foundry.data.fields.ObjectField {
|
||||||
|
|
||||||
/** @override */
|
/** @override */
|
||||||
_cleanType(value, options) {
|
_cleanType(value, options) {
|
||||||
if ( !(typeof value === "object") ) value = {};
|
if (!(typeof value === 'object')) value = {};
|
||||||
|
|
||||||
const cls = this.getModel(value);
|
const cls = this.getModel(value);
|
||||||
if ( cls ) return cls.cleanData(value, options);
|
if (cls) return cls.cleanData(value, options);
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -23,7 +22,7 @@ export default class ActionField extends foundry.data.fields.ObjectField {
|
||||||
/** @override */
|
/** @override */
|
||||||
initialize(value, model, options = {}) {
|
initialize(value, model, options = {}) {
|
||||||
const cls = this.getModel(value);
|
const cls = this.getModel(value);
|
||||||
if ( cls ) return new cls(value, { parent: model, ...options });
|
if (cls) return new cls(value, { parent: model, ...options });
|
||||||
return foundry.utils.deepClone(value);
|
return foundry.utils.deepClone(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -36,6 +35,6 @@ export default class ActionField extends foundry.data.fields.ObjectField {
|
||||||
*/
|
*/
|
||||||
migrateSource(sourceData, fieldData) {
|
migrateSource(sourceData, fieldData) {
|
||||||
const cls = this.getModel(fieldData);
|
const cls = this.getModel(fieldData);
|
||||||
if ( cls ) cls.migrateDataSafe(fieldData);
|
if (cls) cls.migrateDataSafe(fieldData);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -16,78 +16,78 @@
|
||||||
* Special case StringField which represents a formula.
|
* Special case StringField which represents a formula.
|
||||||
*/
|
*/
|
||||||
export default class FormulaField extends foundry.data.fields.StringField {
|
export default class FormulaField extends foundry.data.fields.StringField {
|
||||||
|
/**
|
||||||
|
* @param {FormulaFieldOptions} [options] - Options which configure the behavior of the field
|
||||||
|
* @param {foundry.data.types.DataFieldContext} [context] - Additional context which describes the field
|
||||||
|
*/
|
||||||
|
constructor(options, context) {
|
||||||
|
super(options, context);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/** @inheritDoc */
|
||||||
* @param {FormulaFieldOptions} [options] - Options which configure the behavior of the field
|
static get _defaults() {
|
||||||
* @param {foundry.data.types.DataFieldContext} [context] - Additional context which describes the field
|
return foundry.utils.mergeObject(super._defaults, {
|
||||||
*/
|
deterministic: false
|
||||||
constructor(options, context) {
|
});
|
||||||
super(options, context);
|
}
|
||||||
}
|
|
||||||
|
|
||||||
/** @inheritDoc */
|
/* -------------------------------------------- */
|
||||||
static get _defaults() {
|
|
||||||
return foundry.utils.mergeObject(super._defaults, {
|
|
||||||
deterministic: false
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @inheritDoc */
|
||||||
|
_validateType(value) {
|
||||||
|
const roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, '1'));
|
||||||
|
roll.evaluateSync({ strict: false });
|
||||||
|
if (this.options.deterministic && !roll.isDeterministic)
|
||||||
|
throw new Error(`must not contain dice terms: ${value}`);
|
||||||
|
super._validateType(value);
|
||||||
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/* -------------------------------------------- */
|
||||||
_validateType(value) {
|
/* Active Effect Integration */
|
||||||
const roll = new Roll(value.replace(/@([a-z.0-9_-]+)/gi, "1"));
|
/* -------------------------------------------- */
|
||||||
roll.evaluateSync({ strict: false });
|
|
||||||
if (this.options.deterministic && !roll.isDeterministic) throw new Error(`must not contain dice terms: ${value}`);
|
|
||||||
super._validateType(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
/* Active Effect Integration */
|
_castChangeDelta(delta) {
|
||||||
/* -------------------------------------------- */
|
return this._cast(delta).trim();
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_castChangeDelta(delta) {
|
|
||||||
return this._cast(delta).trim();
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeAdd(value, delta, model, change) {
|
||||||
|
if (!value) return delta;
|
||||||
|
const operator = delta.startsWith('-') ? '-' : '+';
|
||||||
|
delta = delta.replace(/^[+-]/, '').trim();
|
||||||
|
return `${value} ${operator} ${delta}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_applyChangeAdd(value, delta, model, change) {
|
|
||||||
if (!value) return delta;
|
|
||||||
const operator = delta.startsWith("-") ? "-" : "+";
|
|
||||||
delta = delta.replace(/^[+-]/, "").trim();
|
|
||||||
return `${value} ${operator} ${delta}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeMultiply(value, delta, model, change) {
|
||||||
|
if (!value) return delta;
|
||||||
|
const terms = new Roll(value).terms;
|
||||||
|
if (terms.length > 1) return `(${value}) * ${delta}`;
|
||||||
|
return `${value} * ${delta}`;
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_applyChangeMultiply(value, delta, model, change) {
|
|
||||||
if (!value) return delta;
|
|
||||||
const terms = new Roll(value).terms;
|
|
||||||
if (terms.length > 1) return `(${value}) * ${delta}`;
|
|
||||||
return `${value} * ${delta}`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeUpgrade(value, delta, model, change) {
|
||||||
|
if (!value) return delta;
|
||||||
|
const terms = new Roll(value).terms;
|
||||||
|
if (terms.length === 1 && terms[0].fn === 'max') return value.replace(/\)$/, `, ${delta})`);
|
||||||
|
return `max(${value}, ${delta})`;
|
||||||
|
}
|
||||||
|
|
||||||
/** @override */
|
/* -------------------------------------------- */
|
||||||
_applyChangeUpgrade(value, delta, model, change) {
|
|
||||||
if (!value) return delta;
|
|
||||||
const terms = new Roll(value).terms;
|
|
||||||
if ((terms.length === 1) && (terms[0].fn === "max")) return value.replace(/\)$/, `, ${delta})`);
|
|
||||||
return `max(${value}, ${delta})`;
|
|
||||||
}
|
|
||||||
|
|
||||||
/* -------------------------------------------- */
|
/** @override */
|
||||||
|
_applyChangeDowngrade(value, delta, model, change) {
|
||||||
/** @override */
|
if (!value) return delta;
|
||||||
_applyChangeDowngrade(value, delta, model, change) {
|
const terms = new Roll(value).terms;
|
||||||
if (!value) return delta;
|
if (terms.length === 1 && terms[0].fn === 'min') return value.replace(/\)$/, `, ${delta})`);
|
||||||
const terms = new Roll(value).terms;
|
return `min(${value}, ${delta})`;
|
||||||
if ((terms.length === 1) && (terms[0].fn === "min")) return value.replace(/\)$/, `, ${delta})`);
|
}
|
||||||
return `min(${value}, ${delta})`;
|
}
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
|
||||||
56
module/data/fields/pseudoDocumentsField.mjs
Normal file
56
module/data/fields/pseudoDocumentsField.mjs
Normal file
|
|
@ -0,0 +1,56 @@
|
||||||
|
import PseudoDocument from '../pseudo-documents/base/pseudoDocument.mjs';
|
||||||
|
|
||||||
|
const { TypedObjectField, TypedSchemaField } = foundry.data.fields;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @typedef _PseudoDocumentsFieldOptions
|
||||||
|
* @property {Number} [max] - The maximum amount of elements (default: `Infinity`)
|
||||||
|
* @property {String[]} [validTypes] - Allowed pseudo-documents types (default: `[]`)
|
||||||
|
* @property {Function} [validateKey] - callback for validate keys of the object;
|
||||||
|
|
||||||
|
* @typedef {foundry.data.types.DataFieldOptions & _PseudoDocumentsFieldOptions} PseudoDocumentsFieldOptions
|
||||||
|
*/
|
||||||
|
export default class PseudoDocumentsField extends TypedObjectField {
|
||||||
|
/**
|
||||||
|
* @param {PseudoDocument} model - The PseudoDocument of each entry in this collection.
|
||||||
|
* @param {PseudoDocumentsFieldOptions} [options] - Options which configure the behavior of the field
|
||||||
|
* @param {foundry.data.types.DataFieldContext} [context] - Additional context which describes the field
|
||||||
|
*/
|
||||||
|
constructor(model, options = {}, context = {}) {
|
||||||
|
options.validateKey ||= key => foundry.data.validators.isValidId(key);
|
||||||
|
if (!foundry.utils.isSubclass(model, PseudoDocument)) throw new Error('The model must be a PseudoDocument');
|
||||||
|
|
||||||
|
const allTypes = model.TYPES;
|
||||||
|
|
||||||
|
const filteredTypes = options.validTypes
|
||||||
|
? Object.fromEntries(
|
||||||
|
Object.entries(allTypes).filter(([key]) => options.validTypes.includes(key))
|
||||||
|
)
|
||||||
|
: allTypes;
|
||||||
|
|
||||||
|
const field = new TypedSchemaField(filteredTypes);
|
||||||
|
super(field, options, context);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static get _defaults() {
|
||||||
|
return Object.assign(super._defaults, {
|
||||||
|
max: Infinity,
|
||||||
|
validTypes: []
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
_validateType(value, options = {}) {
|
||||||
|
if (Object.keys(value).length > this.max) throw new Error(`cannot have more than ${this.max} elements`);
|
||||||
|
return super._validateType(value, options);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
initialize(value, model, options = {}) {
|
||||||
|
if (!value) return;
|
||||||
|
value = super.initialize(value, model, options);
|
||||||
|
const collection = new foundry.utils.Collection(Object.values(value).map(d => [d._id, d]));
|
||||||
|
return collection;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -10,27 +10,27 @@ import DHSubclass from './subclass.mjs';
|
||||||
import DHWeapon from './weapon.mjs';
|
import DHWeapon from './weapon.mjs';
|
||||||
|
|
||||||
export {
|
export {
|
||||||
DHAncestry,
|
DHAncestry,
|
||||||
DHArmor,
|
DHArmor,
|
||||||
DHClass,
|
DHClass,
|
||||||
DHCommunity,
|
DHCommunity,
|
||||||
DHConsumable,
|
DHConsumable,
|
||||||
DHDomainCard,
|
DHDomainCard,
|
||||||
DHFeature,
|
DHFeature,
|
||||||
DHMiscellaneous,
|
DHMiscellaneous,
|
||||||
DHSubclass,
|
DHSubclass,
|
||||||
DHWeapon
|
DHWeapon
|
||||||
}
|
};
|
||||||
|
|
||||||
export const config = {
|
export const config = {
|
||||||
ancestry: DHAncestry,
|
ancestry: DHAncestry,
|
||||||
armor: DHArmor,
|
armor: DHArmor,
|
||||||
class: DHClass,
|
class: DHClass,
|
||||||
community: DHCommunity,
|
community: DHCommunity,
|
||||||
consumable: DHConsumable,
|
consumable: DHConsumable,
|
||||||
domainCard: DHDomainCard,
|
domainCard: DHDomainCard,
|
||||||
feature: DHFeature,
|
feature: DHFeature,
|
||||||
miscellaneous: DHMiscellaneous,
|
miscellaneous: DHMiscellaneous,
|
||||||
subclass: DHSubclass,
|
subclass: DHSubclass,
|
||||||
weapon: DHWeapon,
|
weapon: DHWeapon
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -5,6 +5,7 @@
|
||||||
* @property {string} type - The system type that this data model represents.
|
* @property {string} type - The system type that this data model represents.
|
||||||
* @property {boolean} hasDescription - Indicates whether items of this type have description field
|
* @property {boolean} hasDescription - Indicates whether items of this type have description field
|
||||||
* @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field
|
* @property {boolean} isQuantifiable - Indicates whether items of this type have quantity field
|
||||||
|
* @property {Record<string,string>} embedded - Record of document names of pseudo-documents and the path to the collection
|
||||||
*/
|
*/
|
||||||
|
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
|
|
@ -16,7 +17,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
||||||
label: "Base Item",
|
label: "Base Item",
|
||||||
type: "base",
|
type: "base",
|
||||||
hasDescription: false,
|
hasDescription: false,
|
||||||
isQuantifiable: false
|
isQuantifiable: false,
|
||||||
|
embedded: {},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
import DHAction from "../action/action.mjs";
|
import DHAction from '../action/action.mjs';
|
||||||
import BaseDataItem from "./base.mjs";
|
import BaseDataItem from './base.mjs';
|
||||||
|
|
||||||
export default class DHDomainCard extends BaseDataItem {
|
export default class DHDomainCard extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
|
|
|
||||||
|
|
@ -6,9 +6,9 @@ export default class DHFeature extends BaseDataItem {
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
static get metadata() {
|
static get metadata() {
|
||||||
return foundry.utils.mergeObject(super.metadata, {
|
return foundry.utils.mergeObject(super.metadata, {
|
||||||
label: "TYPES.Item.feature",
|
label: 'TYPES.Item.feature',
|
||||||
type: "feature",
|
type: 'feature',
|
||||||
hasDescription: true,
|
hasDescription: true
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -17,7 +17,7 @@ export default class DHFeature extends BaseDataItem {
|
||||||
const fields = foundry.data.fields;
|
const fields = foundry.data.fields;
|
||||||
return {
|
return {
|
||||||
...super.defineSchema(),
|
...super.defineSchema(),
|
||||||
|
|
||||||
//A type of feature seems unnecessary
|
//A type of feature seems unnecessary
|
||||||
type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }),
|
type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }),
|
||||||
|
|
||||||
|
|
@ -26,7 +26,7 @@ export default class DHFeature extends BaseDataItem {
|
||||||
choices: SYSTEM.ITEM.actionTypes,
|
choices: SYSTEM.ITEM.actionTypes,
|
||||||
initial: SYSTEM.ITEM.actionTypes.passive.id
|
initial: SYSTEM.ITEM.actionTypes.passive.id
|
||||||
}),
|
}),
|
||||||
//TODO: remove featureType field
|
//TODO: remove featureType field
|
||||||
featureType: new fields.SchemaField({
|
featureType: new fields.SchemaField({
|
||||||
type: new fields.StringField({
|
type: new fields.StringField({
|
||||||
choices: SYSTEM.ITEM.valueTypes,
|
choices: SYSTEM.ITEM.valueTypes,
|
||||||
|
|
@ -75,9 +75,10 @@ export default class DHFeature extends BaseDataItem {
|
||||||
{ nullable: true, initial: null }
|
{ nullable: true, initial: null }
|
||||||
),
|
),
|
||||||
dataField: new fields.StringField({}),
|
dataField: new fields.StringField({}),
|
||||||
appliesOn: new fields.StringField({
|
appliesOn: new fields.StringField(
|
||||||
choices: SYSTEM.EFFECTS.applyLocations,
|
{
|
||||||
},
|
choices: SYSTEM.EFFECTS.applyLocations
|
||||||
|
},
|
||||||
{ nullable: true, initial: null }
|
{ nullable: true, initial: null }
|
||||||
),
|
),
|
||||||
applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), {
|
applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), {
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,7 @@
|
||||||
import BaseDataItem from './base.mjs';
|
import BaseDataItem from './base.mjs';
|
||||||
import FormulaField from '../fields/formulaField.mjs';
|
import FormulaField from '../fields/formulaField.mjs';
|
||||||
|
import PseudoDocumentsField from '../fields/pseudoDocumentsField.mjs';
|
||||||
|
import BaseFeatureData from '../pseudo-documents/feature/baseFeatureData.mjs';
|
||||||
import ActionField from '../fields/actionField.mjs';
|
import ActionField from '../fields/actionField.mjs';
|
||||||
|
|
||||||
export default class DHWeapon extends BaseDataItem {
|
export default class DHWeapon extends BaseDataItem {
|
||||||
|
|
@ -9,7 +11,10 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
label: 'TYPES.Item.weapon',
|
label: 'TYPES.Item.weapon',
|
||||||
type: 'weapon',
|
type: 'weapon',
|
||||||
hasDescription: true,
|
hasDescription: true,
|
||||||
isQuantifiable: true
|
isQuantifiable: true,
|
||||||
|
embedded: {
|
||||||
|
feature: 'featureTest'
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -35,6 +40,12 @@ export default class DHWeapon extends BaseDataItem {
|
||||||
})
|
})
|
||||||
}),
|
}),
|
||||||
feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }),
|
feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, blank: true }),
|
||||||
|
featureTest: new PseudoDocumentsField(BaseFeatureData, {
|
||||||
|
required: true,
|
||||||
|
nullable: true,
|
||||||
|
max: 1,
|
||||||
|
validTypes: ['weapon']
|
||||||
|
}),
|
||||||
// actions: new fields.ArrayField(new fields.EmbeddedDataField(DHAttackAction))
|
// actions: new fields.ArrayField(new fields.EmbeddedDataField(DHAttackAction))
|
||||||
actions: new fields.ArrayField(new ActionField())
|
actions: new fields.ArrayField(new ActionField())
|
||||||
};
|
};
|
||||||
|
|
|
||||||
2
module/data/pseudo-documents/_module.mjs
Normal file
2
module/data/pseudo-documents/_module.mjs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as base } from './base/pseudoDocument.mjs';
|
||||||
|
export * as feature from './feature/_module.mjs';
|
||||||
213
module/data/pseudo-documents/base/base.mjs
Normal file
213
module/data/pseudo-documents/base/base.mjs
Normal file
|
|
@ -0,0 +1,213 @@
|
||||||
|
/**
|
||||||
|
* @typedef {object} PseudoDocumentMetadata
|
||||||
|
* @property {string} name - The document name of this pseudo-document
|
||||||
|
* @property {Record<string, string>} embedded - Record of document names and their collection paths
|
||||||
|
* @property {typeof foundry.applications.api.ApplicationV2} [sheetClass] - The class used to render this pseudo-document
|
||||||
|
* @property {string} defaultArtwork - The default image used for newly created documents
|
||||||
|
*/
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @class Base class for pseudo-documents
|
||||||
|
* @extends {foundry.abstract.DataModel}
|
||||||
|
*/
|
||||||
|
export default class BasePseudoDocument extends foundry.abstract.DataModel {
|
||||||
|
/**
|
||||||
|
* Pseudo-document metadata.
|
||||||
|
* @returns {PseudoDocumentMetadata}
|
||||||
|
*/
|
||||||
|
static get metadata() {
|
||||||
|
return {
|
||||||
|
name: '',
|
||||||
|
embedded: {},
|
||||||
|
defaultArtwork: foundry.documents.Item.DEFAULT_ICON,
|
||||||
|
sheetClass: CONFIG.daggerheart.pseudoDocuments.sheetClass,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static LOCALIZATION_PREFIXES = ['DOCUMENT'];
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static defineSchema() {
|
||||||
|
const { fields } = foundry.data;
|
||||||
|
|
||||||
|
return {
|
||||||
|
_id: new fields.DocumentIdField({ initial: () => foundry.utils.randomID() }),
|
||||||
|
name: new fields.StringField({ required: true, blank: false, textSearch: true }),
|
||||||
|
img: new fields.FilePathField({ categories: ['IMAGE'], initial: this.metadata.defaultArtwork }),
|
||||||
|
description: new fields.HTMLField({ textSearch: true })
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Instance Properties */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The id of this pseudo-document.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get id() {
|
||||||
|
return this._id;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The uuid of this document.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get uuid() {
|
||||||
|
let parent = this.parent;
|
||||||
|
while (!(parent instanceof BasePseudoDocument) && !(parent instanceof foundry.abstract.Document))
|
||||||
|
parent = parent.parent;
|
||||||
|
return [parent.uuid, this.constructor.metadata.name, this.id].join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The parent document of this pseudo-document.
|
||||||
|
* @type {foundry.abstract.Document}
|
||||||
|
*/
|
||||||
|
get document() {
|
||||||
|
let parent = this;
|
||||||
|
while (!(parent instanceof foundry.abstract.Document)) parent = parent.parent;
|
||||||
|
return parent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Item to which this PseudoDocument belongs, if applicable.
|
||||||
|
* @type {foundry.documents.Item|null}
|
||||||
|
*/
|
||||||
|
get item() {
|
||||||
|
return this.parent?.parent instanceof Item ? this.parent.parent : null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Actor to which this PseudoDocument's item belongs, if the item is embedded.
|
||||||
|
* @type {foundry.documents.Actor|null}
|
||||||
|
*/
|
||||||
|
get actor() {
|
||||||
|
return this.item?.parent ?? null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The property path to this pseudo document relative to its parent document.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
get fieldPath() {
|
||||||
|
const fp = this.schema.fieldPath;
|
||||||
|
let path = fp.slice(0, fp.lastIndexOf('element') - 1);
|
||||||
|
|
||||||
|
if (this.parent instanceof BasePseudoDocument) {
|
||||||
|
path = [this.parent.fieldPath, this.parent.id, path].join('.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Embedded Document Methods */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Retrieve an embedded pseudo-document.
|
||||||
|
* @param {string} embeddedName The document name of the embedded pseudo-document.
|
||||||
|
* @param {string} id The id of the embedded pseudo-document.
|
||||||
|
* @param {object} [options] Retrieval options.
|
||||||
|
* @param {boolean} [options.strinct] Throw an error if the embedded pseudo-document does not exist?
|
||||||
|
* @returns {PseudoDocument|null}
|
||||||
|
*/
|
||||||
|
getEmbeddedDocument(embeddedName, id, { strict = false } = {}) {
|
||||||
|
const embeds = this.constructor.metadata.embedded ?? {};
|
||||||
|
if (embeddedName in embeds) {
|
||||||
|
return foundry.utils.getProperty(this, embeds[embeddedName]).get(id, { strict }) ?? null;
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* CRUD Operations */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Does this pseudo-document exist in the document's source?
|
||||||
|
* @type {boolean}
|
||||||
|
*/
|
||||||
|
get isSource() {
|
||||||
|
const source = foundry.utils.getProperty(this.document._source, this.fieldPath);
|
||||||
|
if (foundry.utils.getType(source) !== 'Object') {
|
||||||
|
throw new Error('Source is not an object!');
|
||||||
|
}
|
||||||
|
return this.id in source;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create a new instance of this pseudo-document.
|
||||||
|
* @param {object} [data] The data used for the creation.
|
||||||
|
* @param {object} operation The context of the update operation.
|
||||||
|
* @param {foundry.abstract.Document} operation.parent The parent of this document.
|
||||||
|
* @returns {Promise<foundry.abstract.Document>} A promise that resolves to the updated document.
|
||||||
|
*/
|
||||||
|
static async create(data = {}, { parent, ...operation } = {}) {
|
||||||
|
if (!parent) {
|
||||||
|
throw new Error('A parent document must be specified for the creation of a pseudo-document!');
|
||||||
|
}
|
||||||
|
const id =
|
||||||
|
operation.keepId && foundry.data.validators.isValidId(data._id) ? data._id : foundry.utils.randomID();
|
||||||
|
|
||||||
|
const fieldPath = parent.system.constructor.metadata.embedded?.[this.metadata.name];
|
||||||
|
if (!fieldPath) {
|
||||||
|
throw new Error(
|
||||||
|
`A ${parent.documentName} of type '${parent.type}' does not support ${this.metadata.name}!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const update = { [`system.${fieldPath}.${id}`]: { ...data, _id: id } };
|
||||||
|
const updatedParent = await parent.update(update, operation);
|
||||||
|
return foundry.utils.getProperty(updatedParent, `system.${fieldPath}.${id}`);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete this pseudo-document.
|
||||||
|
* @param {object} [operation] The context of the operation.
|
||||||
|
* @returns {Promise<foundry.abstract.Document>} A promise that resolves to the updated document.
|
||||||
|
*/
|
||||||
|
async delete(operation = {}) {
|
||||||
|
if (!this.isSource) throw new Error('You cannot delete a non-source pseudo-document!');
|
||||||
|
const update = { [`${this.fieldPath}.-=${this.id}`]: null };
|
||||||
|
return this.document.update(update, operation);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Duplicate this pseudo-document.
|
||||||
|
* @returns {Promise<foundry.abstract.Document>} A promise that resolves to the updated document.
|
||||||
|
*/
|
||||||
|
async duplicate() {
|
||||||
|
if (!this.isSource) throw new Error('You cannot duplicate a non-source pseudo-document!');
|
||||||
|
const docData = foundry.utils.mergeObject(this.toObject(), {
|
||||||
|
name: game.i18n.format('DOCUMENT.CopyOf', { name: this.name })
|
||||||
|
});
|
||||||
|
return this.constructor.create(docData, { parent: this.document });
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Update this pseudo-document.
|
||||||
|
* @param {object} [change] The change to perform.
|
||||||
|
* @param {object} [operation] The context of the operation.
|
||||||
|
* @returns {Promise<foundry.abstract.Document>} A promise that resolves to the updated document.
|
||||||
|
*/
|
||||||
|
async update(change = {}, operation = {}) {
|
||||||
|
if (!this.isSource) throw new Error('You cannot update a non-source pseudo-document!');
|
||||||
|
const path = [this.fieldPath, this.id].join('.');
|
||||||
|
const update = { [path]: change };
|
||||||
|
return this.document.update(update, operation);
|
||||||
|
}
|
||||||
|
}
|
||||||
59
module/data/pseudo-documents/base/pseudoDocument.mjs
Normal file
59
module/data/pseudo-documents/base/pseudoDocument.mjs
Normal file
|
|
@ -0,0 +1,59 @@
|
||||||
|
import BasePseudoDocument from './base.mjs';
|
||||||
|
import SheetManagementMixin from './sheetManagementMixin.mjs';
|
||||||
|
|
||||||
|
/** @extends BasePseudoDocument */
|
||||||
|
export default class PseudoDocument extends SheetManagementMixin(BasePseudoDocument) {
|
||||||
|
static get TYPES() {
|
||||||
|
const { types } = CONFIG.daggerheart.pseudoDocuments[this.metadata.name];
|
||||||
|
const typeEntries = Object.entries(types).map(([key, { documentClass }]) => [key, documentClass]);
|
||||||
|
return (this._TYPES ??= Object.freeze(Object.fromEntries(typeEntries)));
|
||||||
|
}
|
||||||
|
|
||||||
|
static _TYPES;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of this shape.
|
||||||
|
* @type {string}
|
||||||
|
*/
|
||||||
|
static TYPE = '';
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
static getTypesChoices(validTypes) {
|
||||||
|
const { types } = CONFIG.daggerheart.pseudoDocuments[model.metadata.name];
|
||||||
|
const typeEntries = Object.entries(types)
|
||||||
|
.map(([key, { label }]) => [key, label])
|
||||||
|
.filter(([key]) => !validTypes || validTypes.includes(key));
|
||||||
|
|
||||||
|
return Object.entries(typeEntries);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/** @override */
|
||||||
|
static defineSchema() {
|
||||||
|
const { fields } = foundry.data;
|
||||||
|
|
||||||
|
return Object.assign(super.defineSchema(), {
|
||||||
|
type: new fields.StringField({
|
||||||
|
required: true,
|
||||||
|
blank: false,
|
||||||
|
initial: this.TYPE,
|
||||||
|
validate: value => value === this.TYPE,
|
||||||
|
validationError: `must be equal to "${this.TYPE}"`
|
||||||
|
})
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @inheritdoc */
|
||||||
|
static async create(data = {}, { parent, ...operation } = {}) {
|
||||||
|
data = foundry.utils.deepClone(data);
|
||||||
|
if (!data.type) data.type = Object.keys(this.TYPES)[0];
|
||||||
|
if (!(data.type in this.TYPES)) {
|
||||||
|
throw new Error(
|
||||||
|
`The '${data.type}' type is not a valid type for a '${this.metadata.documentName}' pseudo-document!`
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return super.create(data, { parent, ...operation });
|
||||||
|
}
|
||||||
|
}
|
||||||
158
module/data/pseudo-documents/base/sheetManagementMixin.mjs
Normal file
158
module/data/pseudo-documents/base/sheetManagementMixin.mjs
Normal file
|
|
@ -0,0 +1,158 @@
|
||||||
|
import BasePseudoDocument from './base.mjs';
|
||||||
|
const { ApplicationV2 } = foundry.applications.api;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A mixin that adds sheet management capabilities to pseudo-documents
|
||||||
|
* @template {typeof BasePseudoDocument} T
|
||||||
|
* @param {T} Base
|
||||||
|
* @returns {T & typeof PseudoDocumentWithSheets}
|
||||||
|
*/
|
||||||
|
export default function SheetManagementMixin(Base) {
|
||||||
|
class PseudoDocumentWithSheets extends Base {
|
||||||
|
/**
|
||||||
|
* Reference to the sheet of this pseudo-document.
|
||||||
|
* @type {ApplicationV2|null}
|
||||||
|
*/
|
||||||
|
get sheet() {
|
||||||
|
if (this._sheet) return this._sheet;
|
||||||
|
const cls = this.constructor.metadata.sheetClass ?? ApplicationV2;
|
||||||
|
|
||||||
|
if (!foundry.utils.isSubclass(cls, ApplicationV2)) {
|
||||||
|
return void ui.notifications.error(
|
||||||
|
'Daggerheart | Error on PseudoDocument | sheetClass must be ApplicationV2'
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const sheet = new cls({ document: this });
|
||||||
|
this._sheet = sheet;
|
||||||
|
return sheet;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Static Properties */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Set of apps what should be re-render.
|
||||||
|
* @type {Set<ApplicationV2>}
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_apps = new Set();
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Existing sheets of a specific type for a specific document.
|
||||||
|
* @type {ApplicationV2 | null}
|
||||||
|
*/
|
||||||
|
_sheet = null;
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Display Methods */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Render all the Application instances which are connected to this PseudoDocument.
|
||||||
|
* @param {ApplicationRenderOptions} [options] Rendering options.
|
||||||
|
*/
|
||||||
|
render(options) {
|
||||||
|
for (const app of this._apps ?? []) {
|
||||||
|
app.render({ window: { title: app.title }, ...options });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Register an application to respond to updates to a certain document.
|
||||||
|
* @param {ApplicationV2} app Application to update.
|
||||||
|
* @internal
|
||||||
|
*/
|
||||||
|
_registerApp(app) {
|
||||||
|
this._apps.add(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Remove an application from the render registry.
|
||||||
|
* @param {ApplicationV2} app Application to stop watching.
|
||||||
|
*/
|
||||||
|
_unregisterApp(app) {
|
||||||
|
this._apps.delete(app);
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Drag and Drop */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Serialize salient information for this PseudoDocument when dragging it.
|
||||||
|
* @returns {object} An object of drag data.
|
||||||
|
*/
|
||||||
|
toDragData() {
|
||||||
|
const dragData = { type: this.documentName, data: this.toObject() };
|
||||||
|
if (this.id) dragData.uuid = this.uuid;
|
||||||
|
return dragData;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
/* Dialog Methods */
|
||||||
|
/* -------------------------------------------- */
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Spawn a dialog for creating a new PseudoDocument.
|
||||||
|
* @param {object} [data] Data to pre-populate the document with.
|
||||||
|
* @param {object} context
|
||||||
|
* @param {foundry.documents.Item} context.parent A parent for the document.
|
||||||
|
* @param {string[]|null} [context.types] A list of types to restrict the choices to, or null for no restriction.
|
||||||
|
* @returns {Promise<BasePseudoDocument|null>}
|
||||||
|
*/
|
||||||
|
static async createDialog(data = {}, { parent, types = null, ...options } = {}) {
|
||||||
|
// TODO
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Present a Dialog form to confirm deletion of this PseudoDocument.
|
||||||
|
* @param {object} [options] - Additional options passed to `DialogV2.confirm`;
|
||||||
|
* @returns {Promise<foundry.abstract.Document>} A Promise which resolves to the deleted PseudoDocument.
|
||||||
|
*/
|
||||||
|
async deleteDialog(options = {}) {
|
||||||
|
const type = game.i18n.localize(this.constructor.metadata.label);
|
||||||
|
const content = options.content ?? `<p>
|
||||||
|
<strong>${game.i18n.localize("AreYouSure")}</strong>
|
||||||
|
${game.i18n.format("SIDEBAR.DeleteWarning", { type })}
|
||||||
|
</p>`;
|
||||||
|
|
||||||
|
return foundry.applications.api.DialogV2.confirm({
|
||||||
|
content,
|
||||||
|
yes: { callback: () => this.delete(operation) },
|
||||||
|
window: {
|
||||||
|
icon: "fa-solid fa-trash",
|
||||||
|
title: `${game.i18n.format("DOCUMENT.Delete", { type })}: ${this.name}`
|
||||||
|
},
|
||||||
|
...options
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets the default new name for a Document
|
||||||
|
* @param {object} collection - Collection of Documents
|
||||||
|
* @returns {string}
|
||||||
|
*/
|
||||||
|
static defaultName(collection) {
|
||||||
|
const documentName = this.metadata.name;
|
||||||
|
const takenNames = new Set();
|
||||||
|
for (const document of collection) takenNames.add(document.name);
|
||||||
|
|
||||||
|
const config = CONFIG.daggerheart.pseudoDocuments[documentName];
|
||||||
|
const baseName = game.i18n.localize(config.label);
|
||||||
|
let name = baseName;
|
||||||
|
let index = 1;
|
||||||
|
while (takenNames.has(name)) name = `${baseName} (${++index})`;
|
||||||
|
return name;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return PseudoDocumentWithSheets;
|
||||||
|
}
|
||||||
2
module/data/pseudo-documents/feature/_module.mjs
Normal file
2
module/data/pseudo-documents/feature/_module.mjs
Normal file
|
|
@ -0,0 +1,2 @@
|
||||||
|
export { default as BaseFeatureData } from './baseFeatureData.mjs';
|
||||||
|
export { default as WeaponFeature } from './weaponFeature.mjs';
|
||||||
24
module/data/pseudo-documents/feature/baseFeatureData.mjs
Normal file
24
module/data/pseudo-documents/feature/baseFeatureData.mjs
Normal file
|
|
@ -0,0 +1,24 @@
|
||||||
|
import PseudoDocument from '../base/pseudoDocument.mjs';
|
||||||
|
|
||||||
|
export default class BaseFeatureData extends PseudoDocument {
|
||||||
|
/**@inheritdoc */
|
||||||
|
static get metadata() {
|
||||||
|
return foundry.utils.mergeObject(
|
||||||
|
super.metadata,
|
||||||
|
{
|
||||||
|
name: 'feature',
|
||||||
|
embedded: {},
|
||||||
|
//sheetClass: null //TODO: define feature-sheet
|
||||||
|
},
|
||||||
|
{ inplace: false }
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static defineSchema() {
|
||||||
|
const { fields } = foundry.data;
|
||||||
|
const schema = super.defineSchema();
|
||||||
|
return Object.assign(schema, {
|
||||||
|
subtype: new fields.StringField({ initial: 'test' })
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
6
module/data/pseudo-documents/feature/weaponFeature.mjs
Normal file
6
module/data/pseudo-documents/feature/weaponFeature.mjs
Normal file
|
|
@ -0,0 +1,6 @@
|
||||||
|
import BaseFeatureData from './baseFeatureData.mjs';
|
||||||
|
|
||||||
|
export default class WeaponFeature extends BaseFeatureData {
|
||||||
|
/**@override */
|
||||||
|
static TYPE = 'weapon';
|
||||||
|
}
|
||||||
|
|
@ -1,12 +1,18 @@
|
||||||
export default class DhpItem extends Item {
|
export default class DhpItem extends Item {
|
||||||
prepareData() {
|
/** @inheritdoc */
|
||||||
super.prepareData();
|
getEmbeddedDocument(embeddedName, id, { invalid = false, strict = false } = {}) {
|
||||||
|
const systemEmbeds = this.system.constructor.metadata.embedded ?? {};
|
||||||
|
if (embeddedName in systemEmbeds) {
|
||||||
|
const path = `system.${systemEmbeds[embeddedName]}`;
|
||||||
|
return foundry.utils.getProperty(this, path).get(id) ?? null;
|
||||||
|
}
|
||||||
|
return super.getEmbeddedDocument(embeddedName, id, { invalid, strict });
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @inheritDoc */
|
/** @inheritDoc */
|
||||||
prepareEmbeddedDocuments() {
|
prepareEmbeddedDocuments() {
|
||||||
super.prepareEmbeddedDocuments();
|
super.prepareEmbeddedDocuments();
|
||||||
for ( const action of this.system.actions ?? [] ) action.prepareData();
|
for (const action of this.system.actions ?? []) action.prepareData();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -33,10 +39,6 @@ export default class DhpItem extends Item {
|
||||||
return ['weapon', 'armor', 'miscellaneous', 'consumable'].includes(this.type);
|
return ['weapon', 'armor', 'miscellaneous', 'consumable'].includes(this.type);
|
||||||
}
|
}
|
||||||
|
|
||||||
_onUpdate(data, options, userId) {
|
|
||||||
super._onUpdate(data, options, userId);
|
|
||||||
}
|
|
||||||
|
|
||||||
static async createDialog(data = {}, { parent = null, pack = null, ...options } = {}) {
|
static async createDialog(data = {}, { parent = null, pack = null, ...options } = {}) {
|
||||||
const documentName = this.metadata.name;
|
const documentName = this.metadata.name;
|
||||||
const types = game.documentTypes[documentName].filter(t => t !== CONST.BASE_DOCUMENT_TYPE);
|
const types = game.documentTypes[documentName].filter(t => t !== CONST.BASE_DOCUMENT_TYPE);
|
||||||
|
|
@ -99,8 +101,8 @@ export default class DhpItem extends Item {
|
||||||
|
|
||||||
async selectActionDialog() {
|
async selectActionDialog() {
|
||||||
const content = await foundry.applications.handlebars.renderTemplate(
|
const content = await foundry.applications.handlebars.renderTemplate(
|
||||||
"systems/daggerheart/templates/views/actionSelect.hbs",
|
'systems/daggerheart/templates/views/actionSelect.hbs',
|
||||||
{actions: this.system.actions}
|
{ actions: this.system.actions }
|
||||||
),
|
),
|
||||||
title = 'Select Action',
|
title = 'Select Action',
|
||||||
type = 'div',
|
type = 'div',
|
||||||
|
|
@ -108,26 +110,27 @@ export default class DhpItem extends Item {
|
||||||
return Dialog.prompt({
|
return Dialog.prompt({
|
||||||
title,
|
title,
|
||||||
// label: title,
|
// label: title,
|
||||||
content, type,
|
content,
|
||||||
|
type,
|
||||||
callback: html => {
|
callback: html => {
|
||||||
const form = html[0].querySelector("form"),
|
const form = html[0].querySelector('form'),
|
||||||
fd = new foundry.applications.ux.FormDataExtended(form);
|
fd = new foundry.applications.ux.FormDataExtended(form);
|
||||||
return this.system.actions.find(a => a._id === fd.object.actionId);
|
return this.system.actions.find(a => a._id === fd.object.actionId);
|
||||||
},
|
},
|
||||||
rejectClose: false
|
rejectClose: false
|
||||||
})
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
async use(event) {
|
async use(event) {
|
||||||
const actions = this.system.actions
|
const actions = this.system.actions;
|
||||||
let response;
|
let response;
|
||||||
if(actions?.length) {
|
if (actions?.length) {
|
||||||
let action = actions[0];
|
let action = actions[0];
|
||||||
if(actions.length > 1 && !event?.shiftKey) {
|
if (actions.length > 1 && !event?.shiftKey) {
|
||||||
// Actions Choice Dialog
|
// Actions Choice Dialog
|
||||||
action = await this.selectActionDialog();
|
action = await this.selectActionDialog();
|
||||||
}
|
}
|
||||||
if(action) response = action.use(event);
|
if (action) response = action.use(event);
|
||||||
// Check Target
|
// Check Target
|
||||||
// If action.roll => Roll Dialog
|
// If action.roll => Roll Dialog
|
||||||
// Else If action.cost => Cost Dialog
|
// Else If action.cost => Cost Dialog
|
||||||
|
|
|
||||||
|
|
@ -465,7 +465,7 @@ div.daggerheart.views.multiclass {
|
||||||
.form-group {
|
.form-group {
|
||||||
display: flex;
|
display: flex;
|
||||||
align-items: center;
|
align-items: center;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: 0.5rem;
|
||||||
label {
|
label {
|
||||||
flex: 2;
|
flex: 2;
|
||||||
}
|
}
|
||||||
|
|
@ -480,8 +480,8 @@ div.daggerheart.views.multiclass {
|
||||||
|
|
||||||
.data-form-array {
|
.data-form-array {
|
||||||
border: 1px solid var(--color-fieldset-border);
|
border: 1px solid var(--color-fieldset-border);
|
||||||
padding: .5rem;
|
padding: 0.5rem;
|
||||||
margin-bottom: .5rem;
|
margin-bottom: 0.5rem;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -157,7 +157,8 @@
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dh-icon, dh-icon > img {
|
dh-icon,
|
||||||
|
dh-icon > img {
|
||||||
width: 32px;
|
width: 32px;
|
||||||
height: 32px;
|
height: 32px;
|
||||||
display: flex;
|
display: flex;
|
||||||
|
|
|
||||||
3
templates/sheets/pseudo-documents/header.hbs
Normal file
3
templates/sheets/pseudo-documents/header.hbs
Normal file
|
|
@ -0,0 +1,3 @@
|
||||||
|
<header>
|
||||||
|
<input type="text" name="name" value="{{document.name}}">
|
||||||
|
</header>
|
||||||
Loading…
Add table
Add a link
Reference in a new issue