mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
Initial commit
This commit is contained in:
commit
aa4021d1a2
163 changed files with 26530 additions and 0 deletions
18
module/data/_module.mjs
Normal file
18
module/data/_module.mjs
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
export { default as DhpPC } from './pc.mjs';
|
||||
export { default as DhpClass } from './class.mjs';
|
||||
export { default as DhpSubclass } from './subclass.mjs';
|
||||
export { default as DhpCombat } from './combat.mjs';
|
||||
export { default as DhpCombatant } from './combatant.mjs';
|
||||
export { default as DhpAdversary } from './adversary.mjs';
|
||||
export { default as DhpFeature } from './feature.mjs';
|
||||
export { default as DhpDomainCard } from './domainCard.mjs';
|
||||
export { default as DhpAncestry } from './ancestry.mjs';
|
||||
export { default as DhpCommunity } from './community.mjs';
|
||||
export { default as DhpMiscellaneous } from './miscellaneous.mjs';
|
||||
export { default as DhpConsumable } from './consumable.mjs';
|
||||
export { default as DhpWeapon } from './weapon.mjs';
|
||||
export { default as DhpArmor } from './armor.mjs';
|
||||
export { default as DhpDualityRoll } from './dualityRoll.mjs';
|
||||
export { default as DhpAdversaryRoll } from './adversaryRoll.mjs';
|
||||
export { default as DhpAbilityUse } from './abilityUse.mjs';
|
||||
export { default as DhpEnvironment } from './environment.mjs';
|
||||
30
module/data/abilityUse.mjs
Normal file
30
module/data/abilityUse.mjs
Normal file
|
|
@ -0,0 +1,30 @@
|
|||
export default class DhpAbilityUse extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
title: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
name: new fields.StringField({}),
|
||||
description: new fields.StringField({}),
|
||||
actions: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
damage: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.StringField({}),
|
||||
}),
|
||||
healing: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
value: new fields.StringField({}),
|
||||
}),
|
||||
cost: new fields.SchemaField({
|
||||
type: new fields.StringField({ nullable: true }),
|
||||
value: new fields.NumberField({ nullable: true }),
|
||||
}),
|
||||
target: new fields.SchemaField({
|
||||
type: new fields.StringField({}),
|
||||
}),
|
||||
})),
|
||||
}
|
||||
}
|
||||
}
|
||||
38
module/data/action.mjs
Normal file
38
module/data/action.mjs
Normal file
|
|
@ -0,0 +1,38 @@
|
|||
export default class DaggerheartAction extends foundry.abstract.DataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
id: new fields.StringField({}),
|
||||
name: new fields.StringField({ initial: 'New Action' }),
|
||||
damage: new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.GENERAL.damageTypes, nullable: true, initial: null }),
|
||||
value: new fields.StringField({}),
|
||||
}),
|
||||
healing: new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.GENERAL.healingTypes, nullable: true, initial: null }),
|
||||
value: new fields.StringField(),
|
||||
}),
|
||||
conditions: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField(),
|
||||
icon: new fields.StringField(),
|
||||
description: new fields.StringField(),
|
||||
})),
|
||||
cost: new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.GENERAL.abilityCosts, nullable: true, initial: null }),
|
||||
value: new fields.NumberField({ nullable: true, initial: null }),
|
||||
}),
|
||||
target: new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.ACTIONS.targetTypes, initial: SYSTEM.ACTIONS.targetTypes.other.id })
|
||||
}),
|
||||
// uses: new fields.SchemaField({
|
||||
// nr: new fields.StringField({}),
|
||||
// refreshType: new fields.StringField({ choices: SYSTEM.GENERAL.refreshTypes, initial: SYSTEM.GENERAL.refreshTypes.session.id }),
|
||||
// refreshed: new fields.BooleanField({ initial: true }),
|
||||
// }),
|
||||
}
|
||||
}
|
||||
|
||||
use = async () => {
|
||||
console.log('Test Use');
|
||||
};
|
||||
}
|
||||
48
module/data/adversary.mjs
Normal file
48
module/data/adversary.mjs
Normal file
|
|
@ -0,0 +1,48 @@
|
|||
import { MappingField } from "./fields.mjs";
|
||||
|
||||
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({
|
||||
minor: new fields.NumberField({ initial: 0, integer: true }),
|
||||
major: new fields.NumberField({ initial: 0, integer: true }),
|
||||
severe: new fields.NumberField({ initial: 0, integer: true }),
|
||||
}),
|
||||
experiences: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null }),
|
||||
})),
|
||||
}
|
||||
}
|
||||
|
||||
get moves(){
|
||||
return this.parent.items.filter(x => x.type === 'feature');
|
||||
}
|
||||
}
|
||||
50
module/data/adversaryRoll.mjs
Normal file
50
module/data/adversaryRoll.mjs
Normal file
|
|
@ -0,0 +1,50 @@
|
|||
export default class DhpAdversaryRoll extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
roll: new fields.StringField({}),
|
||||
total: new fields.NumberField({ integer: true }),
|
||||
modifiers: new fields.ArrayField(new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
label: new fields.StringField({}),
|
||||
title: new fields.StringField({}),
|
||||
})),
|
||||
diceResults: new fields.ArrayField(new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
discarded: new fields.BooleanField({ initial: false }),
|
||||
})),
|
||||
targets: new fields.ArrayField(new fields.SchemaField({
|
||||
id: new fields.StringField({}),
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
difficulty: new fields.NumberField({ integer: true, nullable: true }),
|
||||
evasion: new fields.NumberField({ integer: true }),
|
||||
hit: new fields.BooleanField({ initial: false }),
|
||||
})),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }),
|
||||
}, { nullable: true, initial: null })
|
||||
}
|
||||
}
|
||||
|
||||
prepareDerivedData(){
|
||||
const diceKeys = Object.keys(this.diceResults);
|
||||
const highestIndex = 0;
|
||||
for(var index in diceKeys){
|
||||
const resultIndex = Number.parseInt(index);
|
||||
if(highestIndex === resultIndex) continue;
|
||||
|
||||
const current = this.diceResults[resultIndex];
|
||||
const highest = this.diceResults[highestIndex];
|
||||
|
||||
if(current.value > highest.value) this.diceResults[highestIndex].discarded = true;
|
||||
else this.diceResults[resultIndex].discarded = true;
|
||||
}
|
||||
|
||||
this.targets.forEach(target => {
|
||||
target.hit = target.difficulty ? this.total >= target.difficulty : this.total >= target.evasion;
|
||||
});
|
||||
}
|
||||
}
|
||||
11
module/data/ancestry.mjs
Normal file
11
module/data/ancestry.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import featuresSchema from "./interface/featuresSchema.mjs";
|
||||
|
||||
export default class DhpAncestry extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({}),
|
||||
abilities: featuresSchema(),
|
||||
}
|
||||
}
|
||||
}
|
||||
40
module/data/armor.mjs
Normal file
40
module/data/armor.mjs
Normal file
|
|
@ -0,0 +1,40 @@
|
|||
export default class DhpArmor extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
baseScore: new fields.NumberField({ initial: 1, integer: true }),
|
||||
feature: new fields.StringField({ choices: SYSTEM.ITEM.armorFeatures, integer: false }),
|
||||
marks: new fields.SchemaField({
|
||||
max: new fields.NumberField({ initial: 6, integer: true }),
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
}),
|
||||
description: new fields.HTMLField({}),
|
||||
}
|
||||
}
|
||||
|
||||
get featureInfo() {
|
||||
return this.feature ? CONFIG.daggerheart.ITEM.armorFeatures[this.feature] : null;
|
||||
}
|
||||
|
||||
prepareDerivedData(){
|
||||
if(this.parent.parent){
|
||||
this.applyLevels();
|
||||
}
|
||||
}
|
||||
|
||||
// Currently bugged as it double triggers. Should get fixed in an updated foundry version.
|
||||
applyLevels(){
|
||||
// let armorBonus = 0;
|
||||
// for(var level in this.parent.parent.system.levelData.levelups){
|
||||
// var levelData = this.parent.parent.system.levelData.levelups[level];
|
||||
// for(var tier in levelData){
|
||||
// var tierData = levelData[tier];
|
||||
// if(tierData){
|
||||
// armorBonus += Object.keys(tierData.armorOrEvasionSlot).filter(x => tierData.armorOrEvasionSlot[x] === 'armor').length;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
// this.marks.max += armorBonus;
|
||||
}
|
||||
}
|
||||
93
module/data/class.mjs
Normal file
93
module/data/class.mjs
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
import { getTier } from "../helpers/utils.mjs";
|
||||
import DhpFeature from "./feature.mjs";
|
||||
|
||||
export default class DhpClass extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
domains: new fields.ArrayField(new fields.StringField({})),
|
||||
classItems: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
})),
|
||||
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}),
|
||||
features: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
})),
|
||||
subclasses: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
})),
|
||||
inventory: new fields.SchemaField({
|
||||
take: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
})),
|
||||
choiceA: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
})),
|
||||
choiceB: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
})),
|
||||
extra: new fields.SchemaField({
|
||||
title: new fields.StringField({}),
|
||||
description: new fields.StringField({})
|
||||
}, { initial: null, nullable: true }),
|
||||
}),
|
||||
characterGuide: new fields.SchemaField({
|
||||
suggestedTraits: new fields.SchemaField({
|
||||
agility: new fields.NumberField({ initial: 0, integer: true }),
|
||||
strength: new fields.NumberField({ initial: 0, integer: true }),
|
||||
finesse: new fields.NumberField({ initial: 0, integer: true }),
|
||||
instinct: new fields.NumberField({ initial: 0, integer: true }),
|
||||
presence: new fields.NumberField({ initial: 0, integer: true }),
|
||||
knowledge: new fields.NumberField({ initial: 0, integer: true }),
|
||||
}),
|
||||
suggestedPrimaryWeapon: new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
}, { initial: null, nullable: true }),
|
||||
suggestedSecondaryWeapon: new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
}, { initial: null, nullable: true }),
|
||||
suggestedArmor: new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
}, { initial: null, nullable: true }),
|
||||
characterDescription: new fields.SchemaField({
|
||||
clothes: new fields.StringField({}),
|
||||
eyes: new fields.StringField({}),
|
||||
body: new fields.StringField({}),
|
||||
color: new fields.StringField({}),
|
||||
attitude: new fields.StringField({}),
|
||||
}),
|
||||
backgroundQuestions: new fields.ArrayField(new fields.StringField({}), { initial: ['', '', ''] }),
|
||||
connections: new fields.ArrayField(new fields.StringField({}), { initial: ['', '' ,''] }),
|
||||
}),
|
||||
multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
description: new fields.HTMLField({}),
|
||||
}
|
||||
}
|
||||
|
||||
get multiclassTier(){
|
||||
return getTier(this.multiclass, true);
|
||||
}
|
||||
}
|
||||
9
module/data/combat.mjs
Normal file
9
module/data/combat.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export default class DhpCombat extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
actions: new fields.NumberField({ initial: 0, integer: true }),
|
||||
activeCombatant: new fields.StringField({}),
|
||||
}
|
||||
}
|
||||
}
|
||||
8
module/data/combatant.mjs
Normal file
8
module/data/combatant.mjs
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
export default class DhpCombatant extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
active: new fields.BooleanField({ initial: false })
|
||||
}
|
||||
}
|
||||
}
|
||||
11
module/data/community.mjs
Normal file
11
module/data/community.mjs
Normal file
|
|
@ -0,0 +1,11 @@
|
|||
import featuresSchema from "./interface/featuresSchema.mjs";
|
||||
|
||||
export default class DhpCommunity extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({}),
|
||||
abilities: featuresSchema(),
|
||||
}
|
||||
}
|
||||
}
|
||||
10
module/data/consumable.mjs
Normal file
10
module/data/consumable.mjs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
export default class DhpConsumable extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({}),
|
||||
quantity: new fields.NumberField({ initial: 1, integer: true }),
|
||||
consumeOnUse: new fields.BooleanField({ initial: false }),
|
||||
}
|
||||
}
|
||||
}
|
||||
17
module/data/domainCard.mjs
Normal file
17
module/data/domainCard.mjs
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
import DaggerheartAction from "./action.mjs";
|
||||
|
||||
export default class DhpDomainCard extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
domain: new fields.StringField({ choices: SYSTEM.DOMAIN.domains, integer: false }, { required: true, initial: [] }),
|
||||
level: new fields.NumberField({ initial: 1, integer: true }),
|
||||
recallCost: new fields.NumberField({ initial: 0, integer: true }),
|
||||
type: new fields.StringField({ choices: SYSTEM.DOMAIN.cardTypes, integer: false }, { required: true, initial: [] }),
|
||||
foundation: new fields.BooleanField({ initial: false }),
|
||||
effect: new fields.HTMLField({}),
|
||||
inVault: new fields.BooleanField({ initial: false }),
|
||||
actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction)),
|
||||
}
|
||||
}
|
||||
}
|
||||
150
module/data/dualityRoll.mjs
Normal file
150
module/data/dualityRoll.mjs
Normal file
|
|
@ -0,0 +1,150 @@
|
|||
const fields = foundry.data.fields;
|
||||
const diceField = () => new fields.SchemaField({
|
||||
dice: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true}),
|
||||
});
|
||||
|
||||
export default class DhpDualityRoll extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
|
||||
return {
|
||||
roll: new fields.StringField({}),
|
||||
modifiers: new fields.ArrayField(new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
label: new fields.StringField({}),
|
||||
title: new fields.StringField({}),
|
||||
})),
|
||||
hope: diceField(),
|
||||
fear: diceField(),
|
||||
advantage: diceField(),
|
||||
disadvantage: diceField(),
|
||||
advantageSelected: new fields.NumberField({ initial: 0 }),
|
||||
targets: new fields.ArrayField(new fields.SchemaField({
|
||||
id: new fields.StringField({}),
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
difficulty: new fields.NumberField({ integer: true, nullable: true }),
|
||||
evasion: new fields.NumberField({ integer: true }),
|
||||
hit: new fields.BooleanField({ initial: false }),
|
||||
})),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }),
|
||||
bonusDamage: new fields.ArrayField(new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }),
|
||||
initiallySelected: new fields.BooleanField(),
|
||||
appliesOn: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.applyLocations) }, { nullable: true, initial: null }),
|
||||
description: new fields.StringField({}),
|
||||
hopeIncrease: new fields.StringField({ nullable: true })
|
||||
}), { nullable: true, initial: null })
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
get total() {
|
||||
const modifiers = this.modifiers.reduce((acc, x) => acc+x.value, 0);
|
||||
const advantage = this.advantage.value ?? this.disadvantage.value ? -this.disadvantage.value : 0;
|
||||
return this.hope.value + this.fear.value + advantage + modifiers;
|
||||
}
|
||||
|
||||
get totalLabel() {
|
||||
const label = this.hope.value > this.fear.value ? "DAGGERHEART.General.Hope" : this.fear.value > this.hope.value ? "DAGGERHEART.General.Fear" : "DAGGERHEART.General.CriticalSuccess";
|
||||
|
||||
return game.i18n.localize(label);
|
||||
}
|
||||
|
||||
prepareDerivedData(){
|
||||
const total = this.total;
|
||||
|
||||
this.targets.forEach(target => {
|
||||
target.hit = target.difficulty ? total >= target.difficulty : total >= target.evasion;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
//V1.3
|
||||
// const fields = foundry.data.fields;
|
||||
// const diceField = () => new fields.SchemaField({
|
||||
// dice: new fields.StringField({}),
|
||||
// value: new fields.NumberField({ integer: true}),
|
||||
// });
|
||||
|
||||
// export default class DhpDualityRoll extends foundry.abstract.TypeDataModel {
|
||||
// static defineSchema() {
|
||||
|
||||
// return {
|
||||
// roll: new fields.StringField({}),
|
||||
// modifiers: new fields.ArrayField(new fields.SchemaField({
|
||||
// value: new fields.NumberField({ integer: true }),
|
||||
// label: new fields.StringField({}),
|
||||
// title: new fields.StringField({}),
|
||||
// })),
|
||||
// hope: diceField(),
|
||||
// fear: diceField(),
|
||||
// advantage: diceField(),
|
||||
// disadvantage: diceField(),
|
||||
// advantageSelected: new fields.NumberField({ initial: 0 }),
|
||||
// targets: new fields.ArrayField(new fields.SchemaField({
|
||||
// id: new fields.StringField({}),
|
||||
// name: new fields.StringField({}),
|
||||
// img: new fields.StringField({}),
|
||||
// difficulty: new fields.NumberField({ integer: true, nullable: true }),
|
||||
// evasion: new fields.NumberField({ integer: true }),
|
||||
// hit: new fields.BooleanField({ initial: false }),
|
||||
// })),
|
||||
// damage: new fields.SchemaField({
|
||||
// value: new fields.StringField({}),
|
||||
// type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }),
|
||||
// bonusDamage: new fields.ArrayField(new fields.SchemaField({
|
||||
// value: new fields.StringField({}),
|
||||
// type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.damageTypes), integer: false }),
|
||||
// initiallySelected: new fields.BooleanField(),
|
||||
// appliesOn: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.applyLocations) }, { nullable: true, initial: null }),
|
||||
// description: new fields.StringField({}),
|
||||
// hopeIncrease: new fields.StringField({ nullable: true })
|
||||
// }), { nullable: true, initial: null })
|
||||
// })
|
||||
// }
|
||||
// }
|
||||
|
||||
// get total() {
|
||||
// const modifiers = this.modifiers.reduce((acc, x) => acc+x.value, 0);
|
||||
// const regular = {
|
||||
// normal: this.disadvantage.value ? Math.min(this.disadvantage.value, this.hope.value) + this.fear.value + modifiers : this.hope.value + this.fear.value + modifiers,
|
||||
// alternate: this.advantage.value ? this.advantage.value + this.fear.value + modifiers : null,
|
||||
// };
|
||||
|
||||
// const advantageSolve = this.advantageSelected === 0 ? null : {
|
||||
// normal: this.advantageSelected === 1 ? this.hope.value + this.fear.value + modifiers : this.advantage.value + this.fear.value + modifiers,
|
||||
// alternate: null,
|
||||
// };
|
||||
|
||||
// return advantageSolve ?? regular;
|
||||
// }
|
||||
|
||||
// get totalLabel() {
|
||||
// if(this.advantage.value && this.advantageSelected === 0) return game.i18n.localize("DAGGERHEART.Chat.DualityRoll.AdvantageChooseTitle");
|
||||
|
||||
// const hope = !this.advantage.value || this.advantageSelected === 1 ? this.hope.value : this.advantage.value;
|
||||
// const label = hope > this.fear.value ? "DAGGERHEART.General.Hope" : this.fear.value > hope ? "DAGGERHEART.General.Fear" : "DAGGERHEART.General.CriticalSuccess";
|
||||
|
||||
// return game.i18n.localize(label);
|
||||
// }
|
||||
|
||||
// get dualityDiceStates() {
|
||||
// return {
|
||||
// hope: this.hope.value > this.fear.value ? 'hope' : this.fear.value > this.hope.value ? 'fear' : 'critical',
|
||||
// alternate: this.advantage.value > this.fear.value ? 'hope' : this.fear.value > this.advantage.value ? 'fear' : 'critical',
|
||||
// }
|
||||
// }
|
||||
|
||||
// prepareDerivedData(){
|
||||
// const total = this.total;
|
||||
// if(total.alternate) return false;
|
||||
|
||||
// this.targets.forEach(target => {
|
||||
// target.hit = target.difficulty ? total.normal >= target.difficulty : total.normal >= target.evasion;
|
||||
// });
|
||||
// }
|
||||
// }
|
||||
20
module/data/environment.mjs
Normal file
20
module/data/environment.mjs
Normal file
|
|
@ -0,0 +1,20 @@
|
|||
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');
|
||||
}
|
||||
}
|
||||
77
module/data/feature.mjs
Normal file
77
module/data/feature.mjs
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { getTier } from "../helpers/utils.mjs";
|
||||
import DaggerheartAction from "./action.mjs";
|
||||
import { MappingField } from "./fields.mjs";
|
||||
import DhpEffect from "./interface/effects.mjs";
|
||||
|
||||
export default class DhpFeature extends DhpEffect {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return foundry.utils.mergeObject({}, {
|
||||
type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }),
|
||||
actionType: new fields.StringField({ choices: SYSTEM.ITEM.actionTypes, initial: SYSTEM.ITEM.actionTypes.passive.id }),
|
||||
featureType: new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.ITEM.valueTypes, initial: Object.keys(SYSTEM.ITEM.valueTypes).find(x => x === 'normal') }),
|
||||
data: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
property: new fields.StringField({ choices: SYSTEM.ACTOR.featureProperties, initial: Object.keys(SYSTEM.ACTOR.featureProperties).find(x => x === 'spellcastingTrait') }),
|
||||
max: new fields.NumberField({ initial: 1, integer: true }),
|
||||
numbers: new MappingField(new fields.SchemaField({
|
||||
value: new fields.NumberField({ integer: true }),
|
||||
used: new fields.BooleanField({ initial: false }),
|
||||
})),
|
||||
}),
|
||||
}),
|
||||
refreshData: new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.GENERAL.refreshTypes }),
|
||||
uses: new fields.NumberField({ initial: 1, integer: true }),
|
||||
refreshed: new fields.BooleanField({ initial: true })
|
||||
}, { nullable: true, initial: null }),
|
||||
multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
disabled: new fields.BooleanField({ initial: false }),
|
||||
description: new fields.HTMLField({}),
|
||||
effects: new MappingField(new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: SYSTEM.EFFECTS.effectTypes }),
|
||||
valueType: new fields.StringField({ choices: SYSTEM.EFFECTS.valueTypes }),
|
||||
parseType: new fields.StringField({ choices: SYSTEM.EFFECTS.parseTypes }),
|
||||
initiallySelected: new fields.BooleanField({ initial: true }),
|
||||
options: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.StringField({}),
|
||||
}), { nullable: true, initial: null }),
|
||||
dataField: new fields.StringField({}),
|
||||
appliesOn: new fields.StringField({ choices: SYSTEM.EFFECTS.applyLocations }, { nullable: true, initial: null }),
|
||||
applyLocationChoices: new MappingField(new fields.StringField({}), { nullable: true, initial: null }),
|
||||
valueData: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
fromValue: new fields.StringField({ initial: null, nullable: true }),
|
||||
type: new fields.StringField({ initial: null, nullable: true }),
|
||||
hopeIncrease: new fields.StringField({ initial: null, nullable: true })
|
||||
}),
|
||||
})),
|
||||
actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction)),
|
||||
});
|
||||
}
|
||||
|
||||
get multiclassTier(){
|
||||
return getTier(this.multiclass);
|
||||
}
|
||||
|
||||
async refresh(){
|
||||
if(this.refreshData){
|
||||
if(this.featureType.type === SYSTEM.ITEM.valueTypes.dice.id) {
|
||||
const update = { "system.refreshData.refreshed": true };
|
||||
Object.keys(this.featureType.data.numbers).forEach(x => update[`system.featureType.data.numbers.-=${x}`] = null);
|
||||
await this.parent.update(update);
|
||||
}
|
||||
else {
|
||||
await this.parent.update({ "system.refreshData.refreshed": true});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// prepareDerivedData(){
|
||||
// if(this.featureType.type === SYSTEM.ITEM.valueTypes.dice.id){
|
||||
// this.featureType.numbers = ;
|
||||
// }
|
||||
// }
|
||||
}
|
||||
109
module/data/fields.mjs
Normal file
109
module/data/fields.mjs
Normal file
|
|
@ -0,0 +1,109 @@
|
|||
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);
|
||||
}
|
||||
}
|
||||
75
module/data/interface/effects.mjs
Normal file
75
module/data/interface/effects.mjs
Normal file
|
|
@ -0,0 +1,75 @@
|
|||
import DaggerheartAction from "../action.mjs";
|
||||
import { MappingField } from "../fields.mjs";
|
||||
|
||||
export default class DhpEffects extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
effects: new MappingField(new fields.SchemaField({
|
||||
type: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.effectTypes) }),
|
||||
valueType: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.valueTypes) }),
|
||||
parseType: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.parseTypes) }),
|
||||
initiallySelected: new fields.BooleanField({ initial: true }),
|
||||
options: new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.StringField({}),
|
||||
}), { nullable: true, initial: null }),
|
||||
dataField: new fields.StringField({}),
|
||||
appliesOn: new fields.StringField({ choices: Object.keys(SYSTEM.EFFECTS.applyLocations) }, { nullable: true, initial: null }),
|
||||
applyLocationChoices: new MappingField(new fields.StringField({}), { nullable: true, initial: null }),
|
||||
valueData: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
fromValue: new fields.StringField({ initial: null, nullable: true }),
|
||||
type: new fields.StringField({ initial: null, nullable: true }),
|
||||
hopeIncrease: new fields.StringField({ initial: null, nullable: true })
|
||||
}),
|
||||
})),
|
||||
actions: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartAction)),
|
||||
// actions: new fields.SchemaField({
|
||||
// damage: new fields.ArrayField(new fields.SchemaField({
|
||||
// type: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.extendedDamageTypes), initial: SYSTEM.GENERAL.extendedDamageTypes.physical.id }),
|
||||
// value: new fields.StringField({}),
|
||||
// })),
|
||||
// uses: new fields.SchemaField({
|
||||
// nr: new fields.StringField({}),
|
||||
// refreshType: new fields.StringField({ choices: Object.keys(SYSTEM.GENERAL.refreshTypes), initial: SYSTEM.GENERAL.refreshTypes.session.id }),
|
||||
// refreshed: new fields.BooleanField({ initial: true }),
|
||||
// }),
|
||||
// }),
|
||||
}
|
||||
}
|
||||
|
||||
get effectData(){
|
||||
const effectValues = Object.values(this.effects);
|
||||
const effectCategories = Object.keys(SYSTEM.EFFECTS.effectTypes).reduce((acc, effectType) => {
|
||||
acc[effectType] = effectValues.reduce((acc, effect) => {
|
||||
if(effect.type === effectType){
|
||||
acc.push({ ...effect, valueData: this.#parseValues(effect.parseType, effect.valueData) });
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
return effectCategories;
|
||||
}
|
||||
|
||||
#parseValues(parseType, values){
|
||||
return Object.keys(values).reduce((acc, prop) => {
|
||||
acc[prop] = this.#parseValue(parseType, values[prop]);
|
||||
|
||||
return acc;
|
||||
}, {});
|
||||
}
|
||||
|
||||
#parseValue(parseType, value) {
|
||||
switch(parseType){
|
||||
case SYSTEM.EFFECTS.parseTypes.number.id:
|
||||
return Number.parseInt(value);
|
||||
default:
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
10
module/data/interface/featuresSchema.mjs
Normal file
10
module/data/interface/featuresSchema.mjs
Normal file
|
|
@ -0,0 +1,10 @@
|
|||
const fields = foundry.data.fields;
|
||||
|
||||
const featuresSchema = () => new fields.ArrayField(new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
img: new fields.StringField({}),
|
||||
uuid: new fields.StringField({}),
|
||||
subclassLevel: new fields.StringField({}),
|
||||
}))
|
||||
|
||||
export default featuresSchema;
|
||||
9
module/data/miscellaneous.mjs
Normal file
9
module/data/miscellaneous.mjs
Normal file
|
|
@ -0,0 +1,9 @@
|
|||
export default class DhpMiscellaneous extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({}),
|
||||
quantity: new fields.NumberField({ initial: 1, integer: true })
|
||||
}
|
||||
}
|
||||
}
|
||||
497
module/data/pc.mjs
Normal file
497
module/data/pc.mjs
Normal file
|
|
@ -0,0 +1,497 @@
|
|||
import { getPathValue, getTier } from "../helpers/utils.mjs";
|
||||
import { MappingField } from "./fields.mjs";
|
||||
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
const attributeField = () => new fields.SchemaField({
|
||||
data: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
base: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
actualValue: new fields.NumberField({ initial: 0, integer: true }),
|
||||
overrideValue: new fields.NumberField({ initial: 0, integer: true }),
|
||||
}),
|
||||
levelMarks: new fields.ArrayField(new fields.NumberField({ nullable: true, initial: null, integer: true })),
|
||||
levelMark: new fields.NumberField({ nullable: true, initial: null, integer: true }),
|
||||
});
|
||||
|
||||
const levelUpTier = () => ({
|
||||
attributes: new MappingField(new fields.BooleanField()),
|
||||
hitPointSlots: new MappingField(new fields.BooleanField()),
|
||||
stressSlots: new MappingField(new fields.BooleanField()),
|
||||
experiences: new MappingField(new fields.ArrayField(new fields.StringField({}))),
|
||||
proficiency: new MappingField(new fields.BooleanField()),
|
||||
armorOrEvasionSlot: new MappingField(new fields.StringField({})),
|
||||
majorDamageThreshold2: new MappingField(new fields.BooleanField()),
|
||||
severeDamageThreshold2: new MappingField(new fields.BooleanField()),
|
||||
severeDamageThreshold3: new MappingField(new fields.BooleanField()),
|
||||
severeDamageThreshold4: new MappingField(new fields.BooleanField()),
|
||||
subclass: new MappingField(new fields.SchemaField({
|
||||
multiclass: new fields.BooleanField(),
|
||||
feature: new fields.StringField({}),
|
||||
})),
|
||||
multiclass: new MappingField(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 {
|
||||
static defineSchema() {
|
||||
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: 6, 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: 6, integer: true }),
|
||||
}),
|
||||
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({}),
|
||||
})),
|
||||
}),
|
||||
attributes: new fields.SchemaField({
|
||||
agility: attributeField(),
|
||||
strength: attributeField(),
|
||||
finesse: attributeField(),
|
||||
instinct: attributeField(),
|
||||
presence: attributeField(),
|
||||
knowledge: attributeField(),
|
||||
}),
|
||||
proficiency: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 1, integer: true}),
|
||||
min: new fields.NumberField({ initial: 1, 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 }),
|
||||
// armor: new fields.SchemaField({
|
||||
// value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
// customValue: new fields.NumberField({ initial: null, nullable: true }),
|
||||
// }),
|
||||
experiences: new fields.ArrayField(new fields.SchemaField({
|
||||
id: new fields.StringField({ required: true }),
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
description: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, nullable: true, initial: null }),
|
||||
}), {
|
||||
initial: [
|
||||
{ id: foundry.utils.randomID(), level: 1, description: '', value: 2 },
|
||||
{ id: foundry.utils.randomID(), level: 1, 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 }),
|
||||
}),
|
||||
levelData: new fields.SchemaField({
|
||||
currentLevel: new fields.NumberField({ initial: 1, integer: true }),
|
||||
changedLevel: new fields.NumberField({ initial: 1, integer: true }),
|
||||
levelups: new MappingField(new fields.SchemaField({
|
||||
level: new fields.NumberField({ required: true, integer: true }),
|
||||
tier1: new fields.SchemaField({
|
||||
...levelUpTier()
|
||||
}),
|
||||
tier2: new fields.SchemaField({
|
||||
...levelUpTier()
|
||||
}, { nullable: true, initial: null }),
|
||||
tier3: new fields.SchemaField({
|
||||
...levelUpTier()
|
||||
}, { nullable: true, initial: null }),
|
||||
})),
|
||||
}),
|
||||
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 }),
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
get canLevelUp(){
|
||||
// return Object.values(this.levels.data).some(x => !x.completed);
|
||||
return this.levelData.currentLevel !== this.levelData.changedLevel;
|
||||
}
|
||||
|
||||
get tier(){
|
||||
return this.#getTier(this.levelData.currentLevel);
|
||||
}
|
||||
|
||||
get ancestry(){
|
||||
return this.parent.items.find(x => x.type === 'ancestry') ?? null;
|
||||
}
|
||||
|
||||
get class(){
|
||||
return this.parent.items.find(x => x.type === 'class' && !x.system.multiclass) ?? null;
|
||||
}
|
||||
|
||||
get multiclass(){
|
||||
return this.parent.items.find(x => x.type === 'class' && x.system.multiclass) ?? 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');
|
||||
}
|
||||
|
||||
get activeWeapons(){
|
||||
const primaryWeapon = this.parent.items.find(x => x.type === 'weapon' && x.system.active && !x.system.secondary);
|
||||
const secondaryWeapon = this.parent.items.find(x => x.type === 'weapon' && x.system.active && x.system.secondary);
|
||||
return {
|
||||
primary: this.#weaponData(primaryWeapon),
|
||||
secondary: this.#weaponData(secondaryWeapon),
|
||||
burden: this.getBurden(primaryWeapon, secondaryWeapon)
|
||||
};
|
||||
}
|
||||
|
||||
get inventoryWeapons(){
|
||||
const inventoryWeaponFirst = this.parent.items.find(x => x.type === 'weapon' && x.system.inventoryWeapon === 1);
|
||||
const inventoryWeaponSecond = this.parent.items.find(x => x.type === 'weapon' && x.system.inventoryWeapon === 2);
|
||||
return {
|
||||
first: this.#weaponData(inventoryWeaponFirst),
|
||||
second: this.#weaponData(inventoryWeaponSecond),
|
||||
};
|
||||
}
|
||||
|
||||
get totalAttributeMarks(){
|
||||
return Object.keys(this.levelData.levelups).reduce((nr, level) => {
|
||||
const nrAttributeMarks = Object.keys(this.levelData.levelups[level]).reduce((nr, tier) => {
|
||||
nr += Object.keys(this.levelData.levelups[level][tier]?.attributes ?? {}).length * 2;
|
||||
|
||||
return nr;
|
||||
}, 0);
|
||||
|
||||
nr.push(...Array(nrAttributeMarks).fill(Number.parseInt(level)));
|
||||
|
||||
return nr;
|
||||
}, []);
|
||||
}
|
||||
|
||||
get availableAttributeMarks(){
|
||||
const attributeMarks = Object.keys(this.attributes).flatMap(y => this.attributes[y].levelMarks);
|
||||
return this.totalAttributeMarks.reduce((acc, attribute) => {
|
||||
if(!attributeMarks.findSplice(x => x === attribute)){
|
||||
acc.push(attribute);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, []);
|
||||
}
|
||||
|
||||
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){
|
||||
acc[x.system.refreshData.type].push(x);
|
||||
}
|
||||
|
||||
return acc;
|
||||
}, { shortRest: [], longRest: [] });
|
||||
}
|
||||
|
||||
#weaponData(weapon){
|
||||
return weapon ? {
|
||||
name: weapon.name,
|
||||
trait: CONFIG.daggerheart.ACTOR.abilities[weapon.system.trait].name, //Should not be done in data?
|
||||
range: CONFIG.daggerheart.GENERAL.range[weapon.system.range],
|
||||
damage: {
|
||||
value: weapon.system.damage.value,
|
||||
type: CONFIG.daggerheart.GENERAL.damageTypes[weapon.system.damage.type],
|
||||
},
|
||||
feature: CONFIG.daggerheart.ITEM.weaponFeatures[weapon.system.feature],
|
||||
img: weapon.img,
|
||||
uuid: weapon.uuid
|
||||
} : null
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
|
||||
for(var attributeKey in this.attributes){
|
||||
const attribute = this.attributes[attributeKey];
|
||||
|
||||
attribute.levelMark = attribute.levelMarks.find(x => this.isSameTier(x)) ?? null;
|
||||
|
||||
const actualValue = attribute.data.base + attribute.levelMarks.length + attribute.data.bonus;
|
||||
attribute.data.actualValue = actualValue;
|
||||
attribute.data.value = attribute.data.overrideValue ? attribute.data.overrideValue : attribute.data.actualValue;
|
||||
}
|
||||
|
||||
this.evasion = this.class?.system?.evasion ?? 0;
|
||||
// this.armor.value = this.activeArmor?.baseScore ?? 0;
|
||||
this.damageThresholds = this.class?.system?.damageThresholds ?? { minor: 0, major: 0, severe: 0 };
|
||||
|
||||
this.applyLevels();
|
||||
this.applyEffects();
|
||||
}
|
||||
|
||||
applyLevels(){
|
||||
let healthBonus = 0, stressBonus = 0, proficiencyBonus = 0, evasionBonus = 0, armorBonus = 0, minorThresholdBonus = 0, majorThresholdBonus = 0, severeThresholdBonus = 0;
|
||||
let experienceBonuses = {};
|
||||
let advancementFirst = null, advancementSecond = null;
|
||||
for(var level in this.levelData.levelups){
|
||||
var levelData = this.levelData.levelups[level];
|
||||
for(var tier in levelData){
|
||||
var tierData = levelData[tier];
|
||||
if(tierData){
|
||||
healthBonus += Object.keys(tierData.hitPointSlots).length;
|
||||
stressBonus += Object.keys(tierData.stressSlots).length;
|
||||
proficiencyBonus += Object.keys(tierData.proficiency).length;
|
||||
advancementFirst = Object.keys(tierData.subclass).length > 0 && (level >= 5 && level <= 7) ? { ...tierData.subclass[0], tier: getTier(Number.parseInt(level), true) } : advancementFirst;
|
||||
advancementSecond = Object.keys(tierData.subclass).length > 0 && (level >= 8 && level <= 10) ? { ...tierData.subclass[0], tier: getTier(Number.parseInt(level), true) } : advancementSecond;
|
||||
|
||||
for(var index in Object.keys(tierData.experiences)){
|
||||
for(var experienceKey in tierData.experiences[index]){
|
||||
var experience = tierData.experiences[index][experienceKey];
|
||||
experienceBonuses[experience] = experienceBonuses[experience] ? experienceBonuses[experience]+1 : 1;
|
||||
}
|
||||
}
|
||||
|
||||
evasionBonus += Object.keys(tierData.armorOrEvasionSlot).filter(x => tierData.armorOrEvasionSlot[x] === 'evasion').length;
|
||||
armorBonus += Object.keys(tierData.armorOrEvasionSlot).filter(x => tierData.armorOrEvasionSlot[x] === 'armor').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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
this.resources.health.max += healthBonus;
|
||||
this.resources.stress.max += stressBonus;
|
||||
this.proficiency.value += proficiencyBonus;
|
||||
this.evasion += evasionBonus;
|
||||
this.armorMarks = {
|
||||
max: this.armor ? this.armor.system.marks.max + armorBonus : 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) }));
|
||||
|
||||
const subclassFeatures = this.subclassFeatures;
|
||||
if(advancementFirst){
|
||||
if(advancementFirst.multiclass){
|
||||
this.multiclassSubclass.system[`${advancementFirst.feature}Feature`].unlocked = true;
|
||||
this.multiclassSubclass.system[`${advancementFirst.feature}Feature`].tier = advancementFirst.tier;
|
||||
subclassFeatures.multiclassSubclass[advancementFirst.feature].forEach(x => x.system.disabled = false);
|
||||
} else {
|
||||
this.subclass.system[`${advancementFirst.feature}Feature`].unlocked = true;
|
||||
this.subclass.system[`${advancementFirst.feature}Feature`].tier = advancementFirst.tier;
|
||||
subclassFeatures.subclass[advancementFirst.feature].forEach(x => x.system.disabled = false);
|
||||
}
|
||||
}
|
||||
if(advancementSecond){
|
||||
if(advancementSecond.multiclass){
|
||||
this.multiclassSubclass.system[`${advancementSecond.feature}Feature`].unlocked = true;
|
||||
this.multiclassSubclass.system[`${advancementSecond.feature}Feature`].tier = advancementSecond.tier;
|
||||
subclassFeatures.multiclassSubclass[advancementSecond.feature].forEach(x => x.system.disabled = false);
|
||||
} else {
|
||||
this.subclass.system[`${advancementSecond.feature}Feature`].unlocked = true;
|
||||
this.subclass.system[`${advancementSecond.feature}Feature`].tier = advancementSecond.tier;
|
||||
subclassFeatures.subclass[advancementSecond.feature].forEach(x => x.system.disabled = false);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
//General progression
|
||||
for(var i = 0; i < this.levelData.currentLevel; i++){
|
||||
const tier = getTier(i+1);
|
||||
if(tier !== 'tier0'){
|
||||
this.domainData.maxLoadout = Math.min(this.domainData.maxLoadout+1, 5);
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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.health.max += effect.value.valueData.value;
|
||||
break;
|
||||
case SYSTEM.EFFECTS.effectTypes.stress.id:
|
||||
this.resources.stress.max += 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;
|
||||
}
|
||||
|
||||
isSameTier(level){
|
||||
return this.#getTier(this.levelData.currentLevel) === this.#getTier(level);
|
||||
}
|
||||
|
||||
#getTier(level){
|
||||
if(level >= 8) return 3;
|
||||
else if(level >= 5) return 2;
|
||||
else if(level >= 2) return 1;
|
||||
else return 0;
|
||||
}
|
||||
}
|
||||
34
module/data/subclass.mjs
Normal file
34
module/data/subclass.mjs
Normal file
|
|
@ -0,0 +1,34 @@
|
|||
import { getTier } from "../helpers/utils.mjs";
|
||||
import featuresSchema from "./interface/featuresSchema.mjs";
|
||||
import DaggerheartFeature from './feature.mjs';
|
||||
|
||||
export default class DhpSubclass extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
description: new fields.HTMLField({}),
|
||||
spellcastingTrait: new fields.StringField({ choices: SYSTEM.ACTOR.abilities, integer: false, nullable: true, initial: null }),
|
||||
foundationFeature: new fields.SchemaField({
|
||||
description: new fields.HTMLField({}),
|
||||
abilities: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartFeature)),
|
||||
}),
|
||||
specializationFeature: new fields.SchemaField({
|
||||
unlocked: new fields.BooleanField({ initial: false }),
|
||||
tier: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
description: new fields.HTMLField({}),
|
||||
abilities: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartFeature)),
|
||||
}),
|
||||
masteryFeature: new fields.SchemaField({
|
||||
unlocked: new fields.BooleanField({ initial: false }),
|
||||
tier: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
description: new fields.HTMLField({}),
|
||||
abilities: new fields.ArrayField(new fields.EmbeddedDataField(DaggerheartFeature)),
|
||||
}),
|
||||
multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
}
|
||||
}
|
||||
|
||||
get multiclassTier(){
|
||||
return getTier(this.multiclass);
|
||||
}
|
||||
}
|
||||
47
module/data/weapon.mjs
Normal file
47
module/data/weapon.mjs
Normal file
|
|
@ -0,0 +1,47 @@
|
|||
export default class DhpWeapon extends foundry.abstract.TypeDataModel {
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
return {
|
||||
active: new fields.BooleanField({ initial: false }),
|
||||
inventoryWeapon: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
secondary: new fields.BooleanField({ initial: false }),
|
||||
trait: new fields.StringField({ choices: SYSTEM.ACTOR.abilities, integer: false }),
|
||||
range: new fields.StringField({ choices: SYSTEM.GENERAL.range, integer: false }),
|
||||
damage: new fields.SchemaField({
|
||||
value: new fields.StringField({}),
|
||||
type: new fields.StringField({ choices: SYSTEM.GENERAL.damageTypes, integer: false }),
|
||||
}),
|
||||
burden: new fields.StringField({ choices: SYSTEM.GENERAL.burden, integer: false }),
|
||||
feature: new fields.StringField({ choices: SYSTEM.ITEM.weaponFeatures, integer: false }),
|
||||
quantity: new fields.NumberField({ initial: 1, integer: true }),
|
||||
description: new fields.HTMLField({}),
|
||||
}
|
||||
}
|
||||
|
||||
prepareDerivedData(){
|
||||
if(this.parent.parent){
|
||||
this.applyEffects();
|
||||
}
|
||||
}
|
||||
|
||||
applyEffects(){
|
||||
const effects = this.parent.parent.system.effects;
|
||||
for(var key in effects){
|
||||
const effectType = effects[key];
|
||||
for(var effect of effectType) {
|
||||
switch(key) {
|
||||
case SYSTEM.EFFECTS.effectTypes.reach.id:
|
||||
if(SYSTEM.GENERAL.range[this.range].distance < SYSTEM.GENERAL.range[effect.valueData.value].distance){
|
||||
this.range = effect.valueData.value;
|
||||
}
|
||||
|
||||
break;
|
||||
// case SYSTEM.EFFECTS.effectTypes.damage.id:
|
||||
|
||||
// if(this.damage.type === 'physical') this.damage.value = (`${this.damage.value} + ${this.parent.parent.system.levelData.currentLevel}`);
|
||||
// break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue