Improved Character datamodel

This commit is contained in:
WBHarry 2025-06-08 00:52:53 +02:00
parent 746e0f239a
commit 213a07b64c
20 changed files with 175 additions and 381 deletions

View file

@ -1,4 +1,4 @@
export { default as DhpPC } from './pc.mjs';
export { default as DhCharacter } from './character.mjs';
export { default as DhClass } from './item/class.mjs';
export { default as DhSubclass } from './item/subclass.mjs';
export { default as DhCombat } from './combat.mjs';

View file

@ -1,45 +1,28 @@
import { getPathValue } from '../helpers/utils.mjs';
import { burden } from '../config/generalConfig.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 })
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 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 })
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 DhpPC extends foundry.abstract.TypeDataModel {
export default class DhCharacter extends foundry.abstract.TypeDataModel {
static defineSchema() {
const fields = foundry.data.fields;
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({})
})
)
hope: resourceField(6)
}),
traits: new fields.SchemaField({
agility: attributeField(),
@ -49,24 +32,18 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
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(
proficiency: new fields.NumberField({ initial: 1, integer: true }),
evasion: new fields.NumberField({ initial: 0, integer: true }),
experiences: new fields.TypedObjectField(
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 }
]
initial: {
[foundry.utils.randomID()]: { description: '', value: 2 },
[foundry.utils.randomID()]: { description: '', value: 2 }
}
}
),
gold: new fields.SchemaField({
@ -76,27 +53,14 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
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 })
}),
scars: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({}),
description: new fields.HTMLField()
})
),
story: new fields.HTMLField(),
description: new fields.HTMLField(),
class: new fields.SchemaField({
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
@ -109,10 +73,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
};
}
get tier() {
return this.#getTier(this.levelData.currentLevel);
}
get ancestry() {
return this.parent.items.find(x => x.type === 'ancestry') ?? null;
}
@ -197,57 +157,21 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
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)
};
get primaryWeapon() {
return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && !x.system.secondary);
}
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 secondaryWeapon() {
return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && x.system.secondary);
}
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 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() {
@ -263,43 +187,32 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
);
}
//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;
}
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 });
}
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;
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 });
}
for (var attributeKey in this.traits) {
const attribute = this.traits[attributeKey];
attribute.value = attribute.base + attribute.bonus;
if (primary) {
await primary.update({ 'system.equipped': false });
}
}
}
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);
prepareBaseData() {
for (var attributeKey in this.traits) {
const attribute = this.traits[attributeKey];
/* Levleup handling */
}
const armor = this.armor;
@ -311,57 +224,18 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
? 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;
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 }),

View file

@ -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 })
};
}