mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-13 20:21:06 +01:00
Merged with refactor/84-data-models-structure
This commit is contained in:
commit
2c6501e349
67 changed files with 2043 additions and 3091 deletions
|
|
@ -1,11 +1,9 @@
|
|||
export { default as DhpPC } from './pc.mjs';
|
||||
export { default as DhClass } from './item/class.mjs';
|
||||
export { default as DhSubclass } from './item/subclass.mjs';
|
||||
export { default as DhCombat } from './combat.mjs';
|
||||
export { default as DhCombatant } from './combatant.mjs';
|
||||
export { default as DhpAdversary } from './adversary.mjs';
|
||||
export { default as DhpEnvironment } from './environment.mjs';
|
||||
|
||||
export * as actors from './actor/_module.mjs';
|
||||
export * as items from './item/_module.mjs';
|
||||
export * as messages from './chat-message/_modules.mjs';
|
||||
export * as fields from './fields/_module.mjs';
|
||||
|
|
|
|||
11
module/data/actor/_module.mjs
Normal file
11
module/data/actor/_module.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import DhCharacter from './character.mjs';
|
||||
import DhAdversary from './adversary.mjs';
|
||||
import DhEnvironment from './environment.mjs';
|
||||
|
||||
export { DhCharacter, DhAdversary, DhEnvironment };
|
||||
|
||||
export const config = {
|
||||
character: DhCharacter,
|
||||
adversary: DhAdversary,
|
||||
environment: DhEnvironment
|
||||
};
|
||||
68
module/data/actor/adversary.mjs
Normal file
68
module/data/actor/adversary.mjs
Normal file
|
|
@ -0,0 +1,68 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const resourceField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: 0, integer: true })
|
||||
});
|
||||
|
||||
export default class DhpAdversary extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Adversary'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.adversary',
|
||||
type: 'adversary'
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
tier: new fields.StringField({
|
||||
required: true,
|
||||
choices: SYSTEM.GENERAL.tiers,
|
||||
initial: SYSTEM.GENERAL.tiers.tier1.id
|
||||
}),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
choices: SYSTEM.ACTOR.adversaryTypes,
|
||||
initial: SYSTEM.ACTOR.adversaryTypes.standard.id
|
||||
}),
|
||||
motivesAndTactics: new fields.HTMLField(),
|
||||
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
major: new fields.NumberField({ required: true, initial: 0, integer: true }),
|
||||
severe: new fields.NumberField({ required: true, initial: 0, integer: true })
|
||||
}),
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(),
|
||||
stress: resourceField()
|
||||
}),
|
||||
attack: new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
modifier: new fields.NumberField({ required: true, integer: true, initial: 0 }),
|
||||
range: new fields.StringField({
|
||||
required: true,
|
||||
choices: SYSTEM.GENERAL.range,
|
||||
initial: SYSTEM.GENERAL.range.melee.id
|
||||
}),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField(),
|
||||
type: new fields.StringField({
|
||||
required: true,
|
||||
choices: SYSTEM.GENERAL.damageTypes,
|
||||
initial: SYSTEM.GENERAL.damageTypes.physical.id
|
||||
})
|
||||
})
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField(),
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
})
|
||||
)
|
||||
/* Features waiting on pseudo-document data model addition */
|
||||
};
|
||||
}
|
||||
}
|
||||
34
module/data/actor/base.mjs
Normal file
34
module/data/actor/base.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
/**
|
||||
* Describes metadata about the actor data model type
|
||||
* @typedef {Object} ActorDataModelMetadata
|
||||
* @property {string} label - A localizable label used on application.
|
||||
* @property {string} type - The system type that this data model represents.
|
||||
*/
|
||||
export default class BaseDataActor extends foundry.abstract.TypeDataModel {
|
||||
/** @returns {ActorDataModelMetadata}*/
|
||||
static get metadata() {
|
||||
return {
|
||||
label: 'Base Actor',
|
||||
type: 'base'
|
||||
};
|
||||
}
|
||||
|
||||
/** @inheritDoc */
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
description: new fields.HTMLField({ required: true, nullable: true })
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
|
||||
* @param {object} [options] - Options which modify the getRollData method.
|
||||
* @returns {object}
|
||||
*/
|
||||
getRollData() {
|
||||
const data = { ...this };
|
||||
return data;
|
||||
}
|
||||
}
|
||||
240
module/data/actor/character.mjs
Normal file
240
module/data/actor/character.mjs
Normal file
|
|
@ -0,0 +1,240 @@
|
|||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import { LevelOptionType } from '../levelTier.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const attributeField = () =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
|
||||
});
|
||||
|
||||
const resourceField = max =>
|
||||
new foundry.data.fields.SchemaField({
|
||||
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new foundry.data.fields.NumberField({ initial: max, integer: true })
|
||||
});
|
||||
|
||||
export default class DhCharacter extends BaseDataActor {
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.character',
|
||||
type: 'character'
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(6),
|
||||
stress: resourceField(6),
|
||||
hope: resourceField(6)
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField(),
|
||||
strength: attributeField(),
|
||||
finesse: attributeField(),
|
||||
instinct: attributeField(),
|
||||
presence: attributeField(),
|
||||
knowledge: attributeField()
|
||||
}),
|
||||
proficiency: new fields.NumberField({ initial: 1, integer: true }),
|
||||
evasion: new fields.NumberField({ initial: 0, integer: true }),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
description: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
[foundry.utils.randomID()]: { description: '', value: 2 },
|
||||
[foundry.utils.randomID()]: { description: '', value: 2 }
|
||||
}
|
||||
}
|
||||
),
|
||||
gold: new fields.SchemaField({
|
||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
||||
handfulls: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
pronouns: new fields.StringField({}),
|
||||
scars: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.HTMLField()
|
||||
})
|
||||
),
|
||||
story: new fields.HTMLField(),
|
||||
class: new fields.SchemaField({
|
||||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
multiclass: new fields.SchemaField({
|
||||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhPCLevelData)
|
||||
};
|
||||
}
|
||||
|
||||
get ancestry() {
|
||||
return this.parent.items.find(x => x.type === 'ancestry') ?? null;
|
||||
}
|
||||
|
||||
get community() {
|
||||
return this.parent.items.find(x => x.type === 'community') ?? null;
|
||||
}
|
||||
|
||||
get domains() {
|
||||
const classDomains = this.class ? this.class.system.domains : [];
|
||||
const multiclassDomains = this.multiclass ? this.multiclass.system.domains : [];
|
||||
return [...classDomains, ...multiclassDomains];
|
||||
}
|
||||
|
||||
get domainCards() {
|
||||
const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
|
||||
const loadout = domainCards.filter(x => !x.system.inVault);
|
||||
const vault = domainCards.filter(x => x.system.inVault);
|
||||
|
||||
return {
|
||||
loadout: loadout,
|
||||
vault: vault,
|
||||
total: [...loadout, ...vault]
|
||||
};
|
||||
}
|
||||
|
||||
get armor() {
|
||||
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
|
||||
}
|
||||
|
||||
get primaryWeapon() {
|
||||
return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && !x.system.secondary);
|
||||
}
|
||||
|
||||
get secondaryWeapon() {
|
||||
return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && x.system.secondary);
|
||||
}
|
||||
|
||||
get getWeaponBurden() {
|
||||
return this.primaryWeapon?.system?.burden === burden.twoHanded.value ||
|
||||
(this.primaryWeapon && this.secondaryWeapon)
|
||||
? burden.twoHanded.value
|
||||
: this.primaryWeapon || this.secondaryWeapon
|
||||
? burden.oneHanded.value
|
||||
: null;
|
||||
}
|
||||
|
||||
get refreshableFeatures() {
|
||||
return this.parent.items.reduce(
|
||||
(acc, x) => {
|
||||
if (x.type === 'feature' && x.system.refreshData?.type === 'feature' && x.system.refreshData?.type) {
|
||||
acc[x.system.refreshData.type].push(x);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ shortRest: [], longRest: [] }
|
||||
);
|
||||
}
|
||||
|
||||
static async unequipBeforeEquip(itemToEquip) {
|
||||
const primary = this.primaryWeapon,
|
||||
secondary = this.secondaryWeapon;
|
||||
if (itemToEquip.system.secondary) {
|
||||
if (primary && primary.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
|
||||
await primary.update({ 'system.equipped': false });
|
||||
}
|
||||
|
||||
if (secondary) {
|
||||
await secondary.update({ 'system.equipped': false });
|
||||
}
|
||||
} else {
|
||||
if (secondary && itemToEquip.system.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
|
||||
await secondary.update({ 'system.equipped': false });
|
||||
}
|
||||
|
||||
if (primary) {
|
||||
await primary.update({ 'system.equipped': false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
for (var attributeKey in this.traits) {
|
||||
const attribute = this.traits[attributeKey];
|
||||
/* Levleup handling */
|
||||
}
|
||||
|
||||
const armor = this.armor;
|
||||
this.damageThresholds = {
|
||||
major: armor
|
||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
||||
: this.levelData.level.current,
|
||||
severe: armor
|
||||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||
: this.levelData.level.current * 2
|
||||
};
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.resources.hope.max -= Object.keys(this.scars).length;
|
||||
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
|
||||
}
|
||||
}
|
||||
|
||||
class DhPCLevelData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
}),
|
||||
levelups: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
achievements: new fields.SchemaField(
|
||||
{
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true }),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
selections: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
optionKey: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
checkboxNr: new fields.NumberField({ required: true, integer: true }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
minCost: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
secondaryData: new fields.StringField(),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
}
|
||||
}
|
||||
35
module/data/actor/environment.mjs
Normal file
35
module/data/actor/environment.mjs
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
import { environmentTypes } from '../../config/actorConfig.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
|
||||
export default class DhEnvironment extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Environment'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.environment',
|
||||
type: 'environment'
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
tier: new fields.StringField({
|
||||
required: true,
|
||||
choices: SYSTEM.GENERAL.tiers,
|
||||
initial: SYSTEM.GENERAL.tiers.tier1.id
|
||||
}),
|
||||
type: new fields.StringField({ choices: environmentTypes }),
|
||||
impulses: new fields.HTMLField(),
|
||||
difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }),
|
||||
potentialAdversaries: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
label: new fields.StringField(),
|
||||
adversaries: new fields.TypedObjectField(new ForeignDocumentUUIDField({ type: 'Actor' }))
|
||||
})
|
||||
)
|
||||
/* Features pending datamodel rework */
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -1,52 +0,0 @@
|
|||
export default class DhpAdversary extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
resources: new fields.SchemaField({
|
||||
health: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
min: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
stress: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
min: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
}),
|
||||
tier: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.tiers), integer: false }),
|
||||
type: new fields.StringField({
|
||||
choices: Object.keys(SYSTEM.ACTOR.adversaryTypes),
|
||||
integer: false,
|
||||
initial: Object.keys(SYSTEM.ACTOR.adversaryTypes).find(x => x === 'standard')
|
||||
}),
|
||||
description: new fields.StringField({}),
|
||||
motivesAndTactics: new fields.ArrayField(new fields.StringField({})),
|
||||
attackModifier: new fields.NumberField({ integer: true, nullabe: true, initial: null }),
|
||||
attack: new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
range: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.range), integer: false }),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false })
|
||||
})
|
||||
}),
|
||||
difficulty: new fields.NumberField({ initial: 1, integer: true }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
major: new fields.NumberField({ initial: 0, integer: true }),
|
||||
severe: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
id: new fields.StringField({ required: true }),
|
||||
name: new fields.StringField(),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
}
|
||||
|
|
@ -1,22 +0,0 @@
|
|||
export default class DhpEnvironment extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
resources: new fields.SchemaField({}),
|
||||
tier: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.tiers), integer: false }),
|
||||
type: new fields.StringField({
|
||||
choices: Object.keys(SYSTEM.ACTOR.adversaryTypes),
|
||||
integer: false,
|
||||
initial: Object.keys(SYSTEM.ACTOR.adversaryTypes).find(x => x === 'standard')
|
||||
}),
|
||||
description: new fields.StringField({}),
|
||||
toneAndFeel: new fields.StringField({}),
|
||||
difficulty: new fields.NumberField({ initial: 1, integer: true }),
|
||||
potentialAdversaries: new fields.StringField({})
|
||||
};
|
||||
}
|
||||
|
||||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
}
|
||||
|
|
@ -56,7 +56,7 @@ export default class DHClass extends BaseDataItem {
|
|||
const allowed = await super._preCreate(data, options, user);
|
||||
if (allowed === false) return;
|
||||
|
||||
if (this.actor?.type === 'pc') {
|
||||
if (this.actor?.type === 'character') {
|
||||
const path = data.system.isMulticlass ? 'system.multiclass.value' : 'system.class.value';
|
||||
if (foundry.utils.getProperty(this.actor, path)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.ClassAlreadySelected'));
|
||||
|
|
@ -67,7 +67,7 @@ export default class DHClass extends BaseDataItem {
|
|||
|
||||
_onCreate(data, options, userId) {
|
||||
super._onCreate(data, options, userId);
|
||||
if (options.parent?.type === 'pc') {
|
||||
if (options.parent?.type === 'character') {
|
||||
const path = `system.${data.system.isMulticlass ? 'multiclass.value' : 'class.value'}`;
|
||||
options.parent.update({ [path]: `${options.parent.uuid}.Item.${data._id}` });
|
||||
}
|
||||
|
|
@ -76,7 +76,7 @@ export default class DHClass extends BaseDataItem {
|
|||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
if (options.parent?.type === 'pc') {
|
||||
if (options.parent?.type === 'character') {
|
||||
const path = `system.${this.isMulticlass ? 'multiclass' : 'class'}`;
|
||||
options.parent.update({
|
||||
[`${path}.value`]: null
|
||||
|
|
|
|||
|
|
@ -1,13 +1,13 @@
|
|||
import DaggerheartAction from "../action.mjs";
|
||||
import BaseDataItem from "./base.mjs";
|
||||
import DaggerheartAction from '../action.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
|
||||
export default class DHDomainCard extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: "TYPES.Item.domainCard",
|
||||
type: "domainCard",
|
||||
hasDescription: true,
|
||||
label: 'TYPES.Item.domainCard',
|
||||
type: 'domainCard',
|
||||
hasDescription: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
@ -19,10 +19,37 @@ export default class DHDomainCard extends BaseDataItem {
|
|||
domain: new fields.StringField({ choices: SYSTEM.DOMAIN.domains, required: true, blank: true }),
|
||||
level: new fields.NumberField({ initial: 1, integer: true }),
|
||||
recallCost: new fields.NumberField({ initial: 0, integer: true }),
|
||||
type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, required: true, blank: true}),
|
||||
type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, required: true, blank: true }),
|
||||
foundation: new fields.BooleanField({ initial: false }),
|
||||
inVault: new fields.BooleanField({ initial: false }),
|
||||
actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction))
|
||||
};
|
||||
}
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
const allowed = await super._preCreate(data, options, user);
|
||||
if (allowed === false) return;
|
||||
|
||||
if (this.actor?.type === 'character') {
|
||||
if (!this.actor.system.class.value) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.NoClassSelected'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (!this.actor.system.domains.find(x => x === item.system.domain)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.LacksDomain'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.actor.system.domainCards.total.length === 5) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.MaxLoadoutReached'));
|
||||
return false;
|
||||
}
|
||||
|
||||
if (this.actor.system.domainCards.total.find(x => x.name === item.name)) {
|
||||
ui.notifications.error(game.i18n.localize('DAGGERHEART.Item.Errors.DuplicateDomainCard'));
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
const allowed = await super._preCreate(data, options, user);
|
||||
if (allowed === false) return;
|
||||
|
||||
if (this.actor?.type === 'pc') {
|
||||
if (this.actor?.type === 'character') {
|
||||
const path = data.system.isMulticlass ? 'system.multiclass' : 'system.class';
|
||||
const classData = foundry.utils.getProperty(this.actor, path);
|
||||
if (!classData.value) {
|
||||
|
|
@ -52,7 +52,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
_onCreate(data, options, userId) {
|
||||
super._onCreate(data, options, userId);
|
||||
|
||||
if (options.parent?.type === 'pc') {
|
||||
if (options.parent?.type === 'character') {
|
||||
const path = `system.${data.system.isMulticlass ? 'multiclass.subclass' : 'class.subclass'}`;
|
||||
options.parent.update({ [path]: `${options.parent.uuid}.Item.${data._id}` });
|
||||
}
|
||||
|
|
@ -61,7 +61,7 @@ export default class DHSubclass extends BaseDataItem {
|
|||
_onDelete(options, userId) {
|
||||
super._onDelete(options, userId);
|
||||
|
||||
if (options.parent?.type === 'pc') {
|
||||
if (options.parent?.type === 'character') {
|
||||
const path = `system.${this.isMulticlass ? 'multiclass.subclass' : 'class.subclass'}`;
|
||||
options.parent.update({ [path]: null });
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,413 +0,0 @@
|
|||
import { getPathValue } from '../helpers/utils.mjs';
|
||||
import ForeignDocumentUUIDField from './fields/foreignDocumentUUIDField.mjs';
|
||||
import { LevelOptionType } from './levelTier.mjs';
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
const attributeField = () =>
|
||||
new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
base: new fields.NumberField({ initial: 0, integer: true }),
|
||||
tierMarked: new fields.BooleanField({ required: true, initial: false })
|
||||
});
|
||||
|
||||
const resourceField = max =>
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
min: new fields.NumberField({ initial: 0, integer: true }),
|
||||
baseMax: new fields.NumberField({ initial: max, integer: true })
|
||||
});
|
||||
|
||||
export default class DhpPC extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
resources: new fields.SchemaField({
|
||||
hitPoints: resourceField(6),
|
||||
stress: resourceField(6),
|
||||
hope: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: -1, integer: true }), // FIXME. Logic is gte and needs -1 in PC/Hope. Change to 0
|
||||
min: new fields.NumberField({ initial: 0, integer: true })
|
||||
})
|
||||
}),
|
||||
bonuses: new fields.SchemaField({
|
||||
damage: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
type: new fields.StringField({ nullable: true }),
|
||||
initiallySelected: new fields.BooleanField(),
|
||||
hopeIncrease: new fields.StringField({ initial: null, nullable: true }),
|
||||
description: new fields.StringField({})
|
||||
})
|
||||
)
|
||||
}),
|
||||
traits: new fields.SchemaField({
|
||||
agility: attributeField(),
|
||||
strength: attributeField(),
|
||||
finesse: attributeField(),
|
||||
instinct: attributeField(),
|
||||
presence: attributeField(),
|
||||
knowledge: attributeField()
|
||||
}),
|
||||
proficiency: new fields.SchemaField({
|
||||
base: new fields.NumberField({ required: true, initial: 1, integer: true }),
|
||||
bonus: new fields.NumberField({ required: true, initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
experiences: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
id: new fields.StringField({ required: true }),
|
||||
description: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null })
|
||||
}),
|
||||
{
|
||||
initial: [
|
||||
{ id: foundry.utils.randomID(), description: '', value: 2 },
|
||||
{ id: foundry.utils.randomID(), description: '', value: 2 }
|
||||
]
|
||||
}
|
||||
),
|
||||
gold: new fields.SchemaField({
|
||||
coins: new fields.NumberField({ initial: 0, integer: true }),
|
||||
handfulls: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bags: new fields.NumberField({ initial: 0, integer: true }),
|
||||
chests: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
pronouns: new fields.StringField({}),
|
||||
domainData: new fields.SchemaField({
|
||||
maxLoadout: new fields.NumberField({ initial: 2, integer: true }),
|
||||
maxCards: new fields.NumberField({ initial: 2, integer: true })
|
||||
}),
|
||||
story: new fields.SchemaField({
|
||||
background: new fields.HTMLField(),
|
||||
appearance: new fields.HTMLField(),
|
||||
connections: new fields.HTMLField(),
|
||||
scars: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.HTMLField()
|
||||
})
|
||||
)
|
||||
}),
|
||||
description: new fields.StringField({}),
|
||||
//Temporary until new FoundryVersion fix --> See Armor.Mjs DataPreparation
|
||||
armorMarks: new fields.SchemaField({
|
||||
max: new fields.NumberField({ initial: 6, integer: true }),
|
||||
value: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
class: new fields.SchemaField({
|
||||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
multiclass: new fields.SchemaField({
|
||||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhPCLevelData)
|
||||
};
|
||||
}
|
||||
|
||||
get tier() {
|
||||
return this.#getTier(this.levelData.currentLevel);
|
||||
}
|
||||
|
||||
get ancestry() {
|
||||
return this.parent.items.find(x => x.type === 'ancestry') ?? null;
|
||||
}
|
||||
|
||||
get multiclassSubclass() {
|
||||
return this.parent.items.find(x => x.type === 'subclass' && x.system.multiclass) ?? null;
|
||||
}
|
||||
|
||||
get subclass() {
|
||||
return this.parent.items.find(x => x.type === 'subclass' && !x.system.multiclass) ?? null;
|
||||
}
|
||||
|
||||
get subclassFeatures() {
|
||||
const subclass = this.subclass;
|
||||
const multiclass = this.multiclassSubclass;
|
||||
const subclassItems = this.parent.items.filter(x => x.type === 'feature' && x.system.type === 'subclass');
|
||||
return {
|
||||
subclass: !subclass
|
||||
? {}
|
||||
: {
|
||||
foundation: subclassItems.filter(x =>
|
||||
subclass.system.foundationFeature.abilities.some(ability => ability.uuid === x.uuid)
|
||||
),
|
||||
specialization: subclassItems.filter(x =>
|
||||
subclass.system.specializationFeature.abilities.some(ability => ability.uuid === x.uuid)
|
||||
),
|
||||
mastery: subclassItems.filter(x =>
|
||||
subclass.system.masteryFeature.abilities.some(ability => ability.uuid === x.uuid)
|
||||
)
|
||||
},
|
||||
multiclassSubclass: !multiclass
|
||||
? {}
|
||||
: {
|
||||
foundation: subclassItems.filter(x =>
|
||||
multiclass.system.foundationFeature.abilities.some(ability => ability.uuid === x.uuid)
|
||||
),
|
||||
specialization: subclassItems.filter(x =>
|
||||
multiclass.system.specializationFeature.abilities.some(ability => ability.uuid === x.uuid)
|
||||
),
|
||||
mastery: subclassItems.filter(x =>
|
||||
multiclass.system.masteryFeature.abilities.some(ability => ability.uuid === x.uuid)
|
||||
)
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
get community() {
|
||||
return this.parent.items.find(x => x.type === 'community') ?? null;
|
||||
}
|
||||
|
||||
get classFeatures() {
|
||||
return this.parent.items.filter(
|
||||
x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.class.id && !x.system.multiclass
|
||||
);
|
||||
}
|
||||
|
||||
get multiclassFeatures() {
|
||||
return this.parent.items.filter(
|
||||
x => x.type === 'feature' && x.system.type === SYSTEM.ITEM.featureTypes.class.id && x.system.multiclass
|
||||
);
|
||||
}
|
||||
|
||||
get domains() {
|
||||
const classDomains = this.class ? this.class.system.domains : [];
|
||||
const multiclassDomains = this.multiclass ? this.multiclass.system.domains : [];
|
||||
return [...classDomains, ...multiclassDomains];
|
||||
}
|
||||
|
||||
get domainCards() {
|
||||
const domainCards = this.parent.items.filter(x => x.type === 'domainCard');
|
||||
const loadout = domainCards.filter(x => !x.system.inVault);
|
||||
const vault = domainCards.filter(x => x.system.inVault);
|
||||
|
||||
return {
|
||||
loadout: loadout,
|
||||
vault: vault,
|
||||
total: [...loadout, ...vault]
|
||||
};
|
||||
}
|
||||
|
||||
get armor() {
|
||||
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
|
||||
}
|
||||
|
||||
get equippedWeapons() {
|
||||
const primaryWeapon = this.parent.items.find(
|
||||
x => x.type === 'weapon' && x.system.equipped && !x.system.secondary
|
||||
);
|
||||
const secondaryWeapon = this.parent.items.find(
|
||||
x => x.type === 'weapon' && x.system.equipped && x.system.secondary
|
||||
);
|
||||
return {
|
||||
primary: this.#weaponData(primaryWeapon),
|
||||
secondary: this.#weaponData(secondaryWeapon),
|
||||
burden: this.getBurden(primaryWeapon, secondaryWeapon)
|
||||
};
|
||||
}
|
||||
|
||||
static async unequipBeforeEquip(itemToEquip) {
|
||||
const equippedWeapons = this.equippedWeapons;
|
||||
|
||||
if (itemToEquip.system.secondary) {
|
||||
if (equippedWeapons.primary && equippedWeapons.primary.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
|
||||
await this.parent.items.get(equippedWeapons.primary.id).update({ 'system.equipped': false });
|
||||
}
|
||||
|
||||
if (equippedWeapons.secondary) {
|
||||
await this.parent.items.get(equippedWeapons.secondary.id).update({ 'system.equipped': false });
|
||||
}
|
||||
} else {
|
||||
if (equippedWeapons.secondary && itemToEquip.system.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
|
||||
await this.parent.items.get(equippedWeapons.secondary.id).update({ 'system.equipped': false });
|
||||
}
|
||||
|
||||
if (equippedWeapons.primary) {
|
||||
await this.parent.items.get(equippedWeapons.primary.id).update({ 'system.equipped': false });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
get effects() {
|
||||
return this.parent.items.reduce((acc, item) => {
|
||||
const effects = item.system.effectData;
|
||||
if (effects && !item.system.disabled) {
|
||||
for (var key in effects) {
|
||||
const effect = effects[key];
|
||||
for (var effectEntry of effect) {
|
||||
if (!acc[key]) acc[key] = [];
|
||||
acc[key].push({ name: item.name, value: effectEntry });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
get refreshableFeatures() {
|
||||
return this.parent.items.reduce(
|
||||
(acc, x) => {
|
||||
if (x.type === 'feature' && x.system.refreshData?.type === 'feature' && x.system.refreshData?.type) {
|
||||
acc[x.system.refreshData.type].push(x);
|
||||
}
|
||||
|
||||
return acc;
|
||||
},
|
||||
{ shortRest: [], longRest: [] }
|
||||
);
|
||||
}
|
||||
|
||||
//Should not be done in data?
|
||||
//TODO: REMOVE THIS
|
||||
#weaponData(weapon) {
|
||||
return weapon
|
||||
? {
|
||||
id: weapon.id,
|
||||
name: weapon.name,
|
||||
trait: game.i18n.localize(CONFIG.daggerheart.ACTOR.abilities[weapon.system.trait].label),
|
||||
range: CONFIG.daggerheart.GENERAL.range[weapon.system.range],
|
||||
damage: {
|
||||
value: weapon.system.damage.value,
|
||||
type: CONFIG.daggerheart.GENERAL.damageTypes[weapon.system.damage.type]
|
||||
},
|
||||
burden: weapon.system.burden,
|
||||
feature: CONFIG.daggerheart.ITEM.weaponFeatures[weapon.system.feature],
|
||||
img: weapon.img,
|
||||
uuid: weapon.uuid
|
||||
}
|
||||
: null;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
this.resources.hitPoints.max = this.resources.hitPoints.baseMax + this.resources.hitPoints.bonus;
|
||||
this.resources.stress.max = this.resources.stress.baseMax + this.resources.stress.bonus;
|
||||
this.evasion.value = (this.class?.system?.evasion ?? 0) + this.evasion.bonus;
|
||||
this.proficiency.value = this.proficiency.base + this.proficiency.bonus;
|
||||
|
||||
for (var attributeKey in this.traits) {
|
||||
const attribute = this.traits[attributeKey];
|
||||
attribute.value = attribute.base + attribute.bonus;
|
||||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
this.resources.hope.max = 6 - this.story.scars.length;
|
||||
if (this.resources.hope.value >= this.resources.hope.max) {
|
||||
this.resources.hope.value = Math.max(this.resources.hope.max - 1, 0);
|
||||
}
|
||||
|
||||
const armor = this.armor;
|
||||
this.damageThresholds = {
|
||||
major: armor
|
||||
? armor.system.baseThresholds.major + this.levelData.level.current
|
||||
: this.levelData.level.current,
|
||||
severe: armor
|
||||
? armor.system.baseThresholds.severe + this.levelData.level.current
|
||||
: this.levelData.level.current * 2
|
||||
};
|
||||
|
||||
this.applyEffects();
|
||||
}
|
||||
|
||||
applyEffects() {
|
||||
const effects = this.effects;
|
||||
for (var key in effects) {
|
||||
const effectType = effects[key];
|
||||
for (var effect of effectType) {
|
||||
switch (key) {
|
||||
case SYSTEM.EFFECTS.effectTypes.health.id:
|
||||
this.resources.hitPoints.bonus += effect.value.valueData.value;
|
||||
break;
|
||||
case SYSTEM.EFFECTS.effectTypes.stress.id:
|
||||
this.resources.stress.bonus += effect.value.valueData.value;
|
||||
break;
|
||||
case SYSTEM.EFFECTS.effectTypes.damage.id:
|
||||
this.bonuses.damage.push({
|
||||
value: getPathValue(effect.value.valueData.value, this),
|
||||
type: 'physical',
|
||||
description: effect.name,
|
||||
hopeIncrease: effect.value.valueData.hopeIncrease,
|
||||
initiallySelected: effect.value.initiallySelected,
|
||||
appliesOn: effect.value.appliesOn
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getBurden(primary, secondary) {
|
||||
const twoHanded =
|
||||
primary?.system?.burden === 'twoHanded' ||
|
||||
secondary?.system?.burden === 'twoHanded' ||
|
||||
(primary?.system?.burden === 'oneHanded' && secondary?.system?.burden === 'oneHanded');
|
||||
const oneHanded =
|
||||
!twoHanded && (primary?.system?.burden === 'oneHanded' || secondary?.system?.burden === 'oneHanded');
|
||||
|
||||
return twoHanded ? 'twoHanded' : oneHanded ? 'oneHanded' : null;
|
||||
}
|
||||
|
||||
#getTier(level) {
|
||||
if (level >= 8) return 3;
|
||||
else if (level >= 5) return 2;
|
||||
else if (level >= 2) return 1;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
|
||||
class DhPCLevelData extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
return {
|
||||
level: new fields.SchemaField({
|
||||
current: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
changed: new fields.NumberField({ required: true, integer: true, initial: 1 })
|
||||
}),
|
||||
levelups: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
achievements: new fields.SchemaField(
|
||||
{
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({ required: true }),
|
||||
modifier: new fields.NumberField({ required: true, integer: true })
|
||||
})
|
||||
),
|
||||
domainCards: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
uuid: new fields.StringField({ required: true }),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
),
|
||||
proficiency: new fields.NumberField({ integer: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
selections: new fields.ArrayField(
|
||||
new fields.SchemaField({
|
||||
tier: new fields.NumberField({ required: true, integer: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
optionKey: new fields.StringField({ required: true }),
|
||||
type: new fields.StringField({ required: true, choices: LevelOptionType }),
|
||||
checkboxNr: new fields.NumberField({ required: true, integer: true }),
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
minCost: new fields.NumberField({ integer: true }),
|
||||
amount: new fields.NumberField({ integer: true }),
|
||||
data: new fields.ArrayField(new fields.StringField({ required: true })),
|
||||
secondaryData: new fields.StringField(),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
}
|
||||
}
|
||||
|
|
@ -1,11 +1,14 @@
|
|||
export default class DhVariantRules extends foundry.abstract.DataModel {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Settings.VariantRules'];
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
actionTokens: new fields.SchemaField({
|
||||
enabled: new fields.BooleanField({ required: true, initial: false }),
|
||||
tokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
|
||||
})
|
||||
}),
|
||||
useCoins: new fields.BooleanField({ initial: false })
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue