Merge branch 'main' into #24/Updating-Adversaries

This commit is contained in:
WBHarry 2025-05-24 22:24:44 +02:00
commit 0c50233da3
8 changed files with 37 additions and 194 deletions

View file

@ -2,4 +2,5 @@ node_modules
package-lock.json package-lock.json
package.json package.json
.github .github
*.hbs *.hbs
styles/daggerheart.css

View file

@ -1,5 +1,3 @@
import { MappingField } from './fields.mjs';
export default class DhpAdversary extends foundry.abstract.TypeDataModel { export default class DhpAdversary extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;

View file

@ -1,6 +1,5 @@
import { getTier } from '../helpers/utils.mjs'; import { getTier } from '../helpers/utils.mjs';
import DaggerheartAction from './action.mjs'; import DaggerheartAction from './action.mjs';
import { MappingField } from './fields.mjs';
import DhpEffect from './interface/effects.mjs'; import DhpEffect from './interface/effects.mjs';
export default class DhpFeature extends DhpEffect { export default class DhpFeature extends DhpEffect {
@ -26,7 +25,7 @@ export default class DhpFeature extends DhpEffect {
initial: Object.keys(SYSTEM.ACTOR.featureProperties).find(x => x === 'spellcastingTrait') initial: Object.keys(SYSTEM.ACTOR.featureProperties).find(x => x === 'spellcastingTrait')
}), }),
max: new fields.NumberField({ initial: 1, integer: true }), max: new fields.NumberField({ initial: 1, integer: true }),
numbers: new MappingField( numbers: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
value: new fields.NumberField({ integer: true }), value: new fields.NumberField({ integer: true }),
used: new fields.BooleanField({ initial: false }) used: new fields.BooleanField({ initial: false })
@ -45,7 +44,7 @@ export default class DhpFeature extends DhpEffect {
multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }), multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }),
disabled: new fields.BooleanField({ initial: false }), disabled: new fields.BooleanField({ initial: false }),
description: new fields.HTMLField({}), description: new fields.HTMLField({}),
effects: new MappingField( effects: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
type: new fields.StringField({ choices: SYSTEM.EFFECTS.effectTypes }), type: new fields.StringField({ choices: SYSTEM.EFFECTS.effectTypes }),
valueType: new fields.StringField({ choices: SYSTEM.EFFECTS.valueTypes }), valueType: new fields.StringField({ choices: SYSTEM.EFFECTS.valueTypes }),
@ -63,7 +62,7 @@ export default class DhpFeature extends DhpEffect {
{ choices: SYSTEM.EFFECTS.applyLocations }, { choices: SYSTEM.EFFECTS.applyLocations },
{ nullable: true, initial: null } { nullable: true, initial: null }
), ),
applyLocationChoices: new MappingField(new fields.StringField({}), { applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), {
nullable: true, nullable: true,
initial: null initial: null
}), }),
@ -97,10 +96,4 @@ export default class DhpFeature extends DhpEffect {
} }
} }
} }
// prepareDerivedData(){
// if(this.featureType.type === SYSTEM.ITEM.valueTypes.dice.id){
// this.featureType.numbers = ;
// }
// }
} }

View file

@ -1,109 +0,0 @@
export class MappingField extends foundry.data.fields.ObjectField {
constructor(model, options) {
if (!(model instanceof foundry.data.fields.DataField)) {
throw new Error('MappingField must have a DataField as its contained element');
}
super(options);
/**
* The embedded DataField definition which is contained in this field.
* @type {DataField}
*/
this.model = model;
}
/* -------------------------------------------- */
/** @inheritdoc */
static get _defaults() {
return foundry.utils.mergeObject(super._defaults, {
initialKeys: null,
initialValue: null,
initialKeysOnly: false
});
}
/* -------------------------------------------- */
/** @inheritdoc */
_cleanType(value, options) {
Object.entries(value).forEach(([k, v]) => (value[k] = this.model.clean(v, options)));
return value;
}
/* -------------------------------------------- */
/** @inheritdoc */
getInitialValue(data) {
let keys = this.initialKeys;
const initial = super.getInitialValue(data);
if (!keys || !foundry.utils.isEmpty(initial)) return initial;
if (!(keys instanceof Array)) keys = Object.keys(keys);
for (const key of keys) initial[key] = this._getInitialValueForKey(key);
return initial;
}
/* -------------------------------------------- */
/**
* Get the initial value for the provided key.
* @param {string} key Key within the object being built.
* @param {object} [object] Any existing mapping data.
* @returns {*} Initial value based on provided field type.
*/
_getInitialValueForKey(key, object) {
const initial = this.model.getInitialValue();
return this.initialValue?.(key, initial, object) ?? initial;
}
/* -------------------------------------------- */
/** @override */
_validateType(value, options = {}) {
if (foundry.utils.getType(value) !== 'Object') throw new Error('must be an Object');
const errors = this._validateValues(value, options);
if (!foundry.utils.isEmpty(errors)) throw new foundry.data.fields.ModelValidationError(errors);
}
/* -------------------------------------------- */
/**
* Validate each value of the object.
* @param {object} value The object to validate.
* @param {object} options Validation options.
* @returns {Object<Error>} An object of value-specific errors by key.
*/
_validateValues(value, options) {
const errors = {};
for (const [k, v] of Object.entries(value)) {
const error = this.model.validate(v, options);
if (error) errors[k] = error;
}
return errors;
}
/* -------------------------------------------- */
/** @override */
initialize(value, model, options = {}) {
if (!value) return value;
const obj = {};
const initialKeys = this.initialKeys instanceof Array ? this.initialKeys : Object.keys(this.initialKeys ?? {});
const keys = this.initialKeysOnly ? initialKeys : Object.keys(value);
for (const key of keys) {
const data = value[key] ?? this._getInitialValueForKey(key, value);
obj[key] = this.model.initialize(data, model, options);
}
return obj;
}
/* -------------------------------------------- */
/** @inheritdoc */
_getField(path) {
if (path.length === 0) return this;
else if (path.length === 1) return this.model;
path.shift();
return this.model._getField(path);
}
}

View file

@ -1,11 +1,10 @@
import DaggerheartAction from '../action.mjs'; import DaggerheartAction from '../action.mjs';
import { MappingField } from '../fields.mjs';
export default class DhpEffects extends foundry.abstract.TypeDataModel { export default class DhpEffects extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
effects: new MappingField( effects: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
type: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.effectTypes) }), type: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.effectTypes) }),
valueType: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.valueTypes) }), valueType: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.valueTypes) }),
@ -23,7 +22,7 @@ export default class DhpEffects extends foundry.abstract.TypeDataModel {
{ choices: Object.keys(SYSTEM.EFFECTS.applyLocations) }, { choices: Object.keys(SYSTEM.EFFECTS.applyLocations) },
{ nullable: true, initial: null } { nullable: true, initial: null }
), ),
applyLocationChoices: new MappingField(new fields.StringField({}), { applyLocationChoices: new fields.TypedObjectField(new fields.StringField({}), {
nullable: true, nullable: true,
initial: null initial: null
}), }),

View file

@ -1,5 +1,4 @@
import { getPathValue, getTier } from '../helpers/utils.mjs'; import { getPathValue, getTier } from '../helpers/utils.mjs';
import { MappingField } from './fields.mjs';
const fields = foundry.data.fields; const fields = foundry.data.fields;
@ -17,38 +16,21 @@ const attributeField = () =>
}); });
const levelUpTier = () => ({ const levelUpTier = () => ({
attributes: new MappingField(new fields.BooleanField()), attributes: new fields.TypedObjectField(new fields.BooleanField()),
hitPointSlots: new MappingField(new fields.BooleanField()), hitPointSlots: new fields.TypedObjectField(new fields.BooleanField()),
stressSlots: new MappingField(new fields.BooleanField()), stressSlots: new fields.TypedObjectField(new fields.BooleanField()),
experiences: new MappingField(new fields.ArrayField(new fields.StringField({}))), experiences: new fields.TypedObjectField(new fields.ArrayField(new fields.StringField({}))),
proficiency: new MappingField(new fields.BooleanField()), proficiency: new fields.TypedObjectField(new fields.BooleanField()),
armorOrEvasionSlot: new MappingField(new fields.StringField({})), armorOrEvasionSlot: new fields.TypedObjectField(new fields.StringField({})),
majorDamageThreshold2: new MappingField(new fields.BooleanField()), subclass: new fields.TypedObjectField(
severeDamageThreshold2: new MappingField(new fields.BooleanField()),
severeDamageThreshold3: new MappingField(new fields.BooleanField()),
severeDamageThreshold4: new MappingField(new fields.BooleanField()),
subclass: new MappingField(
new fields.SchemaField({ new fields.SchemaField({
multiclass: new fields.BooleanField(), multiclass: new fields.BooleanField(),
feature: new fields.StringField({}) feature: new fields.StringField({})
}) })
), ),
multiclass: new MappingField(new fields.BooleanField()) multiclass: new fields.TypedObjectField(new fields.BooleanField())
}); });
// const weapon = () => new fields.SchemaField({
// name: new fields.StringField({}),
// trait: new fields.StringField({}),
// range: new fields.StringField({}),
// damage: new fields.SchemaField({
// value: new fields.StringField({}),
// type: new fields.StringField({}),
// }),
// feature: new fields.StringField({}),
// img: new fields.StringField({}),
// uuid: new fields.StringField({}),
// }, { initial: null, nullable: true });
export default class DhpPC extends foundry.abstract.TypeDataModel { export default class DhpPC extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
return { return {
@ -92,16 +74,7 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
min: new fields.NumberField({ initial: 1, integer: true }), min: new fields.NumberField({ initial: 1, integer: true }),
max: new fields.NumberField({ initial: 6, integer: true }) max: new fields.NumberField({ initial: 6, integer: true })
}), }),
damageThresholds: new fields.SchemaField({
minor: new fields.NumberField({ initial: 0, integer: true }),
major: new fields.NumberField({ initial: 0, integer: true }),
severe: new fields.NumberField({ initial: 0, integer: true })
}),
evasion: new fields.NumberField({ initial: 0, integer: true }), evasion: new fields.NumberField({ initial: 0, integer: true }),
// armor: new fields.SchemaField({
// value: new fields.NumberField({ initial: 0, integer: true }),
// customValue: new fields.NumberField({ initial: null, nullable: true }),
// }),
experiences: new fields.ArrayField( experiences: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
id: new fields.StringField({ required: true }), id: new fields.StringField({ required: true }),
@ -130,7 +103,7 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
levelData: new fields.SchemaField({ levelData: new fields.SchemaField({
currentLevel: new fields.NumberField({ initial: 1, integer: true }), currentLevel: new fields.NumberField({ initial: 1, integer: true }),
changedLevel: new fields.NumberField({ initial: 1, integer: true }), changedLevel: new fields.NumberField({ initial: 1, integer: true }),
levelups: new MappingField( levelups: new fields.TypedObjectField(
new fields.SchemaField({ new fields.SchemaField({
level: new fields.NumberField({ required: true, integer: true }), level: new fields.NumberField({ required: true, integer: true }),
tier1: new fields.SchemaField({ tier1: new fields.SchemaField({
@ -389,21 +362,35 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
this.evasion = this.class?.system?.evasion ?? 0; this.evasion = this.class?.system?.evasion ?? 0;
// this.armor.value = this.activeArmor?.baseScore ?? 0; // this.armor.value = this.activeArmor?.baseScore ?? 0;
this.damageThresholds = this.class?.system?.damageThresholds ?? { minor: 0, major: 0, severe: 0 }; this.damageThresholds = this.computeDamageThresholds();
this.applyLevels(); this.applyLevels();
this.applyEffects(); this.applyEffects();
} }
computeDamageThresholds() {
// TODO: missing weapon features and domain cards calculation
if (!this.armor) {
return {
major: this.levelData.currentLevel,
severe: this.levelData.currentLevel * 2
};
}
const {
baseThresholds: { major = 0, severe = 0 }
} = this.armor.system;
return {
major: major + this.levelData.currentLevel,
severe: severe + this.levelData.currentLevel
};
}
applyLevels() { applyLevels() {
let healthBonus = 0, let healthBonus = 0,
stressBonus = 0, stressBonus = 0,
proficiencyBonus = 0, proficiencyBonus = 0,
evasionBonus = 0, evasionBonus = 0,
armorBonus = 0, armorBonus = 0;
minorThresholdBonus = 0,
majorThresholdBonus = 0,
severeThresholdBonus = 0;
let experienceBonuses = {}; let experienceBonuses = {};
let advancementFirst = null, let advancementFirst = null,
advancementSecond = null; advancementSecond = null;
@ -439,11 +426,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
armorBonus += Object.keys(tierData.armorOrEvasionSlot).filter( armorBonus += Object.keys(tierData.armorOrEvasionSlot).filter(
x => tierData.armorOrEvasionSlot[x] === 'armor' x => tierData.armorOrEvasionSlot[x] === 'armor'
).length; ).length;
majorThresholdBonus += Object.keys(tierData.majorDamageThreshold2).length * 2;
severeThresholdBonus += Object.keys(tierData.severeDamageThreshold2).length * 2;
severeThresholdBonus += Object.keys(tierData.severeDamageThreshold3).length * 3;
severeThresholdBonus += Object.keys(tierData.severeDamageThreshold4).length * 4;
} }
} }
} }
@ -456,9 +438,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
max: this.armor ? this.armor.system.marks.max + armorBonus : 0, max: this.armor ? this.armor.system.marks.max + armorBonus : 0,
value: this.armor ? this.armor.system.marks.value : 0 value: this.armor ? this.armor.system.marks.value : 0
}; };
this.damageThresholds.minor += minorThresholdBonus;
this.damageThresholds.major += majorThresholdBonus;
this.damageThresholds.severe += severeThresholdBonus;
this.experiences = this.experiences.map(x => ({ ...x, value: x.value + (experienceBonuses[x.id] ?? 0) })); this.experiences = this.experiences.map(x => ({ ...x, value: x.value + (experienceBonuses[x.id] ?? 0) }));
@ -495,20 +474,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
this.domainData.maxLoadout = Math.min(this.domainData.maxLoadout + 1, 5); this.domainData.maxLoadout = Math.min(this.domainData.maxLoadout + 1, 5);
this.domainData.maxCards += 1; this.domainData.maxCards += 1;
} }
switch (tier) {
case 'tier1':
this.damageThresholds.severe += 2;
break;
case 'tier2':
this.damageThresholds.major += 1;
this.damageThresholds.severe += 3;
break;
case 'tier3':
this.damageThresholds.major += 2;
this.damageThresholds.severe += 4;
break;
}
} }
} }

View file

@ -5,7 +5,7 @@
"version": "0.0.1", "version": "0.0.1",
"compatibility": { "compatibility": {
"minimum": "13", "minimum": "13",
"verified": "13.342", "verified": "13.344",
"maximum": "13" "maximum": "13"
}, },
"authors": [ "authors": [
@ -13,7 +13,7 @@
"name": "Darrington Press LLC" "name": "Darrington Press LLC"
}, },
{ {
"name": "HarryFurAlle" "name": "WBHarry"
}, },
{ {
"name": "cptn-cosmo" "name": "cptn-cosmo"

View file

@ -2,11 +2,7 @@
<legend class="legend">{{localize "DAGGERHEART.Sheets.PC.Health.Title"}}</legend> <legend class="legend">{{localize "DAGGERHEART.Sheets.PC.Health.Title"}}</legend>
<div class="threshold-container"> <div class="threshold-container">
<div class="threshold-box">
{{document.system.damageThresholds.minor}}
</div>
<div class="threshold-spacer"> <div class="threshold-spacer">
<i class="fa-solid fa-caret-left"></i>
<div class="health-category">{{localize "DAGGERHEART.Sheets.PC.Health.Minor"}}</div> <div class="health-category">{{localize "DAGGERHEART.Sheets.PC.Health.Minor"}}</div>
</div> </div>