mirror of
https://github.com/Foundryborne/daggerheart.git
synced 2026-01-12 03:31:07 +01:00
159 - Companion (#224)
* Initial datamodel * Fixed attack * Temp * Fixed normal levelup * Fixed showing summary of new experiences * Touchups * level sync fixes * Reworked Action storage * Companions now take stress when damaged * Fixed Feature flow * Removed retroactive companion levelup * Restored delevel on partner removal * PR fixes * Added a check for card duplicates on character
This commit is contained in:
parent
6f1529fefe
commit
b7e4169079
57 changed files with 1682 additions and 1012 deletions
|
|
@ -73,7 +73,10 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
save: new fields.SchemaField({
|
||||
trait: new fields.StringField({ nullable: true, initial: null, choices: SYSTEM.ACTOR.abilities }),
|
||||
difficulty: new fields.NumberField({ nullable: true, initial: 10, integer: true, min: 0 }),
|
||||
damageMod: new fields.StringField({ initial: SYSTEM.ACTIONS.damageOnSave.none.id, choices: SYSTEM.ACTIONS.damageOnSave })
|
||||
damageMod: new fields.StringField({
|
||||
initial: SYSTEM.ACTIONS.damageOnSave.none.id,
|
||||
choices: SYSTEM.ACTIONS.damageOnSave
|
||||
})
|
||||
}),
|
||||
target: new fields.SchemaField({
|
||||
type: new fields.StringField({
|
||||
|
|
@ -98,9 +101,12 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
initial: SYSTEM.GENERAL.healingTypes.hitPoints.id,
|
||||
label: 'Healing'
|
||||
}),
|
||||
resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }),
|
||||
resultBased: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.Actions.Settings.ResultBased.label'
|
||||
}),
|
||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||
})
|
||||
},
|
||||
extraSchemas = {};
|
||||
|
|
@ -124,7 +130,11 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
get actor() {
|
||||
return this.item instanceof DhpActor ? this.item : this.item?.actor;
|
||||
return this.item instanceof DhpActor
|
||||
? this.item
|
||||
: this.item?.parent instanceof DhpActor
|
||||
? this.item.parent
|
||||
: this.item?.actor;
|
||||
}
|
||||
|
||||
get chatTemplate() {
|
||||
|
|
@ -153,7 +163,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
return updateSource;
|
||||
}
|
||||
|
||||
getRollData(data={}) {
|
||||
getRollData(data = {}) {
|
||||
const actorData = this.actor.getRollData(false);
|
||||
|
||||
// Remove when included directly in Actor getRollData
|
||||
|
|
@ -166,11 +176,11 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
return a;
|
||||
}, {})
|
||||
: 1; */
|
||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
: 1;
|
||||
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
|
||||
? (data.costs.find(c => c.scalable)?.total ?? 1)
|
||||
: 1;
|
||||
actorData.roll = {};
|
||||
|
||||
|
||||
return actorData;
|
||||
}
|
||||
|
||||
|
|
@ -191,12 +201,14 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
// Prepare Costs
|
||||
const costsConfig = this.prepareCost();
|
||||
if(isFastForward && !this.hasCost(costsConfig)) return ui.notifications.warn("You don't have the resources to use that action.");
|
||||
if (isFastForward && !this.hasCost(costsConfig))
|
||||
return ui.notifications.warn("You don't have the resources to use that action.");
|
||||
// config = this.prepareUseCost(config)
|
||||
|
||||
// Prepare Uses
|
||||
const usesConfig = this.prepareUse();
|
||||
if(isFastForward && !this.hasUses(usesConfig)) return ui.notifications.warn("That action doesn't have remaining uses.");
|
||||
if (isFastForward && !this.hasUses(usesConfig))
|
||||
return ui.notifications.warn("That action doesn't have remaining uses.");
|
||||
// config = this.prepareUseCost(config)
|
||||
|
||||
// Prepare Roll Data
|
||||
|
|
@ -209,24 +221,24 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
costs: costsConfig,
|
||||
uses: usesConfig,
|
||||
data: actorData
|
||||
}
|
||||
|
||||
if ( Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false ) return;
|
||||
};
|
||||
|
||||
if (Hooks.call(`${SYSTEM.id}.preUseAction`, this, config) === false) return;
|
||||
|
||||
// Display configuration window if necessary
|
||||
if ( config.dialog?.configure && this.requireConfigurationDialog(config) ) {
|
||||
if (config.dialog?.configure && this.requireConfigurationDialog(config)) {
|
||||
config = await D20RollDialog.configure(config);
|
||||
if (!config) return;
|
||||
}
|
||||
|
||||
if ( this.hasRoll ) {
|
||||
if (this.hasRoll) {
|
||||
const rollConfig = this.prepareRoll(config);
|
||||
config.roll = rollConfig;
|
||||
config = await this.actor.diceRoll(config);
|
||||
if (!config) return;
|
||||
}
|
||||
|
||||
if( this.hasSave ) {
|
||||
if (this.hasSave) {
|
||||
/* config.targets.forEach((t) => {
|
||||
if(t.hit) {
|
||||
const target = game.canvas.tokens.get(t.id),
|
||||
|
|
@ -258,16 +270,16 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}) */
|
||||
}
|
||||
|
||||
if ( this.doFollowUp() ) {
|
||||
if(this.rollDamage) await this.rollDamage(event, config);
|
||||
if(this.rollHealing) await this.rollHealing(event, config);
|
||||
if(this.trigger) await this.trigger(event, config);
|
||||
if (this.doFollowUp()) {
|
||||
if (this.rollDamage) await this.rollDamage(event, config);
|
||||
if (this.rollHealing) await this.rollHealing(event, config);
|
||||
if (this.trigger) await this.trigger(event, config);
|
||||
}
|
||||
|
||||
// Consume resources
|
||||
await this.consume(config);
|
||||
|
||||
if ( Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false ) return;
|
||||
|
||||
if (Hooks.call(`${SYSTEM.id}.postUseAction`, this, config) === false) return;
|
||||
|
||||
return config;
|
||||
}
|
||||
|
|
@ -287,7 +299,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
hasHealing: !!this.healing,
|
||||
hasEffect: !!this.effects?.length,
|
||||
hasSave: this.hasSave
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
requireConfigurationDialog(config) {
|
||||
|
|
@ -317,7 +329,6 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
targets = targets.map(t => this.formatTarget(t));
|
||||
return targets;
|
||||
|
||||
}
|
||||
|
||||
prepareRange() {
|
||||
|
|
@ -326,7 +337,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
prepareRoll() {
|
||||
const roll = {
|
||||
const roll = {
|
||||
modifiers: [],
|
||||
trait: this.roll?.trait,
|
||||
label: 'Attack',
|
||||
|
|
@ -334,8 +345,8 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
difficulty: this.roll?.difficulty,
|
||||
formula: this.roll.getFormula()
|
||||
};
|
||||
if(this.roll?.type === 'diceSet') roll.lite = true;
|
||||
|
||||
if (this.roll?.type === 'diceSet') roll.lite = true;
|
||||
|
||||
return roll;
|
||||
}
|
||||
|
||||
|
|
@ -344,12 +355,14 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
async consume(config) {
|
||||
const resources = config.costs.filter(c => c.enabled !== false).map(c => {
|
||||
return { type: c.type, value: (c.total ?? c.value) * -1 };
|
||||
});
|
||||
|
||||
const resources = config.costs
|
||||
.filter(c => c.enabled !== false)
|
||||
.map(c => {
|
||||
return { type: c.type, value: (c.total ?? c.value) * -1 };
|
||||
});
|
||||
|
||||
await this.actor.modifyResource(resources);
|
||||
if(config.uses?.enabled) {
|
||||
if (config.uses?.enabled) {
|
||||
const newActions = foundry.utils.getProperty(this.item.system, this.systemPath).map(x => x.toObject());
|
||||
newActions[this.index].uses.value++;
|
||||
await this.item.update({ [`system.${this.systemPath}`]: newActions });
|
||||
|
|
@ -388,13 +401,16 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
hasCost(costs) {
|
||||
const realCosts = this.getRealCosts(costs);
|
||||
return realCosts.reduce((a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value), true);
|
||||
return realCosts.reduce(
|
||||
(a, c) => a && this.actor.system.resources[c.type]?.value >= (c.total ?? c.value),
|
||||
true
|
||||
);
|
||||
}
|
||||
/* COST */
|
||||
|
||||
/* USES */
|
||||
calcUses(uses) {
|
||||
if(!uses) return null;
|
||||
if (!uses) return null;
|
||||
return {
|
||||
...uses,
|
||||
enabled: uses.hasOwnProperty('enabled') ? uses.enabled : true
|
||||
|
|
@ -402,7 +418,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
}
|
||||
|
||||
hasUses(uses) {
|
||||
if(!uses) return true;
|
||||
if (!uses) return true;
|
||||
return (uses.hasOwnProperty('enabled') && !uses.enabled) || uses.value + 1 <= uses.max;
|
||||
}
|
||||
/* USES */
|
||||
|
|
@ -432,7 +448,7 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
/* TARGET */
|
||||
|
||||
/* RANGE */
|
||||
|
||||
|
||||
/* RANGE */
|
||||
|
||||
/* EFFECTS */
|
||||
|
|
@ -441,10 +457,10 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
let effects = this.effects;
|
||||
data.system.targets.forEach(async token => {
|
||||
if (!token.hit && !force) return;
|
||||
if(this.hasSave && token.saved.success === true) {
|
||||
effects = this.effects.filter(e => e.onSave === true)
|
||||
if (this.hasSave && token.saved.success === true) {
|
||||
effects = this.effects.filter(e => e.onSave === true);
|
||||
}
|
||||
if(!effects.length) return;
|
||||
if (!effects.length) return;
|
||||
effects.forEach(async e => {
|
||||
const actor = canvas.tokens.get(token.id)?.actor,
|
||||
effect = this.item.effects.get(e._id);
|
||||
|
|
@ -479,35 +495,42 @@ export class DHBaseAction extends foundry.abstract.DataModel {
|
|||
|
||||
/* SAVE */
|
||||
async rollSave(target, event, message) {
|
||||
if(!target?.actor) return;
|
||||
target.actor.diceRoll({
|
||||
event,
|
||||
title: 'Roll Save',
|
||||
roll: {
|
||||
trait: this.save.trait,
|
||||
difficulty: this.save.difficulty,
|
||||
type: "reaction"
|
||||
},
|
||||
data: target.actor.getRollData()
|
||||
}).then(async (result) => {
|
||||
if(result) this.updateChatMessage(message, target.id, {result: result.roll.total, success: result.roll.success});
|
||||
})
|
||||
if (!target?.actor) return;
|
||||
target.actor
|
||||
.diceRoll({
|
||||
event,
|
||||
title: 'Roll Save',
|
||||
roll: {
|
||||
trait: this.save.trait,
|
||||
difficulty: this.save.difficulty,
|
||||
type: 'reaction'
|
||||
},
|
||||
data: target.actor.getRollData()
|
||||
})
|
||||
.then(async result => {
|
||||
if (result)
|
||||
this.updateChatMessage(message, target.id, {
|
||||
result: result.roll.total,
|
||||
success: result.roll.success
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
async updateChatMessage(message, targetId, changes, chain=true) {
|
||||
async updateChatMessage(message, targetId, changes, chain = true) {
|
||||
setTimeout(async () => {
|
||||
const chatMessage = ui.chat.collection.get(message._id),
|
||||
msgTargets = chatMessage.system.targets,
|
||||
msgTarget = msgTargets.find(mt => mt.id === targetId);
|
||||
msgTarget.saved = changes;
|
||||
await chatMessage.update({'system.targets': msgTargets});
|
||||
},100);
|
||||
if(chain) {
|
||||
if(message.system.source.message) this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
|
||||
await chatMessage.update({ 'system.targets': msgTargets });
|
||||
}, 100);
|
||||
if (chain) {
|
||||
if (message.system.source.message)
|
||||
this.updateChatMessage(ui.chat.collection.get(message.system.source.message), targetId, changes, false);
|
||||
const relatedChatMessages = ui.chat.collection.filter(c => c.system.source.message === message._id);
|
||||
relatedChatMessages.forEach(c => {
|
||||
this.updateChatMessage(c, targetId, changes, false);
|
||||
})
|
||||
});
|
||||
}
|
||||
}
|
||||
/* SAVE */
|
||||
|
|
@ -525,7 +548,7 @@ export class DHDamageAction extends DHBaseAction {
|
|||
|
||||
getFormulaValue(part, data) {
|
||||
let formulaValue = part.value;
|
||||
if(this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
||||
if (this.hasRoll && part.resultBased && data.system.roll.result.duality === -1) return part.valueAlt;
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
|
|
@ -535,19 +558,19 @@ export class DHDamageAction extends DHBaseAction {
|
|||
if (!formula || formula == '') return;
|
||||
let roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
|
||||
if(isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
|
||||
|
||||
if (isNaN(formula)) formula = Roll.replaceFormulaData(formula, this.getRollData(data.system ?? data));
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.Chat.DamageRoll.Title', { damage: this.name }),
|
||||
roll: {formula},
|
||||
targets: (data.system?.targets.filter(t => t.hit) ?? data.targets),
|
||||
roll: { formula },
|
||||
targets: data.system?.targets.filter(t => t.hit) ?? data.targets,
|
||||
hasSave: this.hasSave,
|
||||
source: data.system?.source,
|
||||
event
|
||||
};
|
||||
if(this.hasSave) config.onSave = this.save.damageMod;
|
||||
if(data.system) {
|
||||
if (this.hasSave) config.onSave = this.save.damageMod;
|
||||
if (data.system) {
|
||||
config.source.message = data._id;
|
||||
config.directDamage = false;
|
||||
}
|
||||
|
|
@ -597,7 +620,8 @@ export class DHHealingAction extends DHBaseAction {
|
|||
|
||||
getFormulaValue(data) {
|
||||
let formulaValue = this.healing.value;
|
||||
if(this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1) return this.healing.valueAlt;
|
||||
if (this.hasRoll && this.healing.resultBased && data.system.roll.result.duality === -1)
|
||||
return this.healing.valueAlt;
|
||||
return formulaValue;
|
||||
}
|
||||
|
||||
|
|
@ -608,12 +632,12 @@ export class DHHealingAction extends DHBaseAction {
|
|||
if (!formula || formula == '') return;
|
||||
let roll = { formula: formula, total: formula },
|
||||
bonusDamage = [];
|
||||
|
||||
|
||||
const config = {
|
||||
title: game.i18n.format('DAGGERHEART.Chat.HealingRoll.Title', {
|
||||
healing: game.i18n.localize(SYSTEM.GENERAL.healingTypes[this.healing.type].label)
|
||||
}),
|
||||
roll: {formula},
|
||||
roll: { formula },
|
||||
targets: (data.system?.targets ?? data.targets).filter(t => t.hit),
|
||||
messageType: 'healing',
|
||||
type: this.healing.type,
|
||||
|
|
|
|||
|
|
@ -25,17 +25,20 @@ export class DHActionRollData extends foundry.abstract.DataModel {
|
|||
initial: 'above',
|
||||
label: 'Should be'
|
||||
}),
|
||||
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' }),
|
||||
treshold: new fields.NumberField({ initial: 1, integer: true, min: 1, label: 'Treshold' })
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
getFormula() {
|
||||
if(!this.type) return;
|
||||
if (!this.type) return;
|
||||
let formula = '';
|
||||
switch (this.type) {
|
||||
case 'diceSet':
|
||||
const multiplier = this.diceRolling.multiplier === 'flat' ? this.diceRolling.flatMultiplier : `@${this.diceRolling.multiplier}`;
|
||||
const multiplier =
|
||||
this.diceRolling.multiplier === 'flat'
|
||||
? this.diceRolling.flatMultiplier
|
||||
: `@${this.diceRolling.multiplier}`;
|
||||
formula = `${multiplier}${this.diceRolling.dice}cs${SYSTEM.ACTIONS.diceCompare[this.diceRolling.compare].operator}${this.diceRolling.treshold}`;
|
||||
break;
|
||||
default:
|
||||
|
|
@ -75,9 +78,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
|
|||
: `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */
|
||||
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
|
||||
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
|
||||
return this.custom.enabled
|
||||
? this.custom.formula
|
||||
: `${multiplier ?? 1}${this.dice}${bonus}`;
|
||||
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -105,9 +106,12 @@ export class DHDamageData extends foundry.abstract.DataModel {
|
|||
nullable: false,
|
||||
required: true
|
||||
}),
|
||||
resultBased: new fields.BooleanField({ initial: false, label: "DAGGERHEART.Actions.Settings.ResultBased.label" }),
|
||||
resultBased: new fields.BooleanField({
|
||||
initial: false,
|
||||
label: 'DAGGERHEART.Actions.Settings.ResultBased.label'
|
||||
}),
|
||||
value: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData),
|
||||
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,13 @@
|
|||
import DhCharacter from './character.mjs';
|
||||
import DhCompanion from './companion.mjs';
|
||||
import DhAdversary from './adversary.mjs';
|
||||
import DhEnvironment from './environment.mjs';
|
||||
|
||||
export { DhCharacter, DhAdversary, DhEnvironment };
|
||||
export { DhCharacter, DhCompanion, DhAdversary, DhEnvironment };
|
||||
|
||||
export const config = {
|
||||
character: DhCharacter,
|
||||
companion: DhCompanion,
|
||||
adversary: DhAdversary,
|
||||
environment: DhEnvironment
|
||||
};
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { burden } from '../../config/generalConfig.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import { LevelOptionType } from '../levelTier.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import BaseDataActor from './base.mjs';
|
||||
|
||||
const attributeField = () =>
|
||||
|
|
@ -96,7 +97,8 @@ export default class DhCharacter extends BaseDataActor {
|
|||
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
|
||||
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
|
||||
}),
|
||||
levelData: new fields.EmbeddedDataField(DhPCLevelData),
|
||||
actions: new fields.ArrayField(new ActionField()),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData),
|
||||
bonuses: new fields.SchemaField({
|
||||
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
damageThresholds: new fields.SchemaField({
|
||||
|
|
@ -115,6 +117,7 @@ export default class DhCharacter extends BaseDataActor {
|
|||
magic: new fields.NumberField({ integer: true, initial: 0 })
|
||||
})
|
||||
}),
|
||||
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
|
||||
rules: new fields.SchemaField({
|
||||
maxArmorMarked: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, integer: true, initial: 1 }),
|
||||
|
|
@ -154,10 +157,25 @@ export default class DhCharacter extends BaseDataActor {
|
|||
return this.parent.items.find(x => x.type === 'community') ?? null;
|
||||
}
|
||||
|
||||
get features() {
|
||||
return this.parent.items.filter(x => x.type === 'feature') ?? [];
|
||||
}
|
||||
|
||||
get companionFeatures() {
|
||||
return this.companion ? this.companion.items.filter(x => x.type === 'feature') : [];
|
||||
}
|
||||
|
||||
get needsCharacterSetup() {
|
||||
return !this.class.value || !this.class.subclass;
|
||||
}
|
||||
|
||||
get spellcastingModifiers() {
|
||||
return {
|
||||
main: this.class.subclass?.system?.spellcastingTrait,
|
||||
multiclass: this.multiclass.subclass?.system?.spellcastingTrait
|
||||
};
|
||||
}
|
||||
|
||||
get domains() {
|
||||
const classDomains = this.class.value ? this.class.value.system.domains : [];
|
||||
const multiclassDomains = this.multiclass.value ? this.multiclass.value.system.domains : [];
|
||||
|
|
@ -197,6 +215,12 @@ export default class DhCharacter extends BaseDataActor {
|
|||
: null;
|
||||
}
|
||||
|
||||
get deathMoveViable() {
|
||||
return (
|
||||
this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal
|
||||
);
|
||||
}
|
||||
|
||||
static async unequipBeforeEquip(itemToEquip) {
|
||||
const primary = this.primaryWeapon,
|
||||
secondary = this.secondaryWeapon;
|
||||
|
|
@ -307,58 +331,10 @@ export default class DhCharacter extends BaseDataActor {
|
|||
level: this.levelData.level.current
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
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.TypedObjectField(new fields.StringField({ required: true })),
|
||||
itemUuid: new fields.StringField({ required: true })
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
async _preDelete() {
|
||||
if (this.companion) {
|
||||
this.companion.updateLevel(1);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
139
module/data/actor/companion.mjs
Normal file
139
module/data/actor/companion.mjs
Normal file
|
|
@ -0,0 +1,139 @@
|
|||
import BaseDataActor from './base.mjs';
|
||||
import DhLevelData from '../levelData.mjs';
|
||||
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import { adjustDice, adjustRange } from '../../helpers/utils.mjs';
|
||||
|
||||
export default class DhCompanion extends BaseDataActor {
|
||||
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Sheets.Companion'];
|
||||
|
||||
static get metadata() {
|
||||
return foundry.utils.mergeObject(super.metadata, {
|
||||
label: 'TYPES.Actor.companion',
|
||||
type: 'companion'
|
||||
});
|
||||
}
|
||||
|
||||
static defineSchema() {
|
||||
const fields = foundry.data.fields;
|
||||
|
||||
return {
|
||||
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
|
||||
resources: new fields.SchemaField({
|
||||
stress: new fields.SchemaField({
|
||||
value: new fields.NumberField({ initial: 0, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true }),
|
||||
max: new fields.NumberField({ initial: 3, integer: true })
|
||||
}),
|
||||
hope: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
evasion: new fields.SchemaField({
|
||||
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
|
||||
bonus: new fields.NumberField({ initial: 0, integer: true })
|
||||
}),
|
||||
experiences: new fields.TypedObjectField(
|
||||
new fields.SchemaField({
|
||||
name: new fields.StringField({}),
|
||||
value: new fields.NumberField({ integer: true, initial: 0 }),
|
||||
bonus: new fields.NumberField({ integer: true, initial: 0 })
|
||||
}),
|
||||
{
|
||||
initial: {
|
||||
experience1: { value: 2 },
|
||||
experience2: { value: 2 }
|
||||
}
|
||||
}
|
||||
),
|
||||
attack: new ActionField({
|
||||
initial: {
|
||||
name: 'Attack',
|
||||
_id: foundry.utils.randomID(),
|
||||
systemPath: 'attack',
|
||||
type: 'attack',
|
||||
range: 'melee',
|
||||
target: {
|
||||
type: 'any',
|
||||
amount: 1
|
||||
},
|
||||
roll: {
|
||||
type: 'weapon',
|
||||
bonus: 0
|
||||
},
|
||||
damage: {
|
||||
parts: [
|
||||
{
|
||||
multiplier: 'flat',
|
||||
value: {
|
||||
dice: 'd6',
|
||||
multiplier: 'flat'
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}),
|
||||
actions: new fields.ArrayField(new ActionField()),
|
||||
levelData: new fields.EmbeddedDataField(DhLevelData)
|
||||
};
|
||||
}
|
||||
|
||||
get attackBonus() {
|
||||
return this.attack.roll.bonus ?? 0;
|
||||
}
|
||||
|
||||
prepareBaseData() {
|
||||
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
|
||||
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.total;
|
||||
this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
|
||||
|
||||
for (let levelKey in this.levelData.levelups) {
|
||||
const level = this.levelData.levelups[levelKey];
|
||||
for (let selection of level.selections) {
|
||||
switch (selection.type) {
|
||||
case 'hope':
|
||||
this.resources.hope += selection.value;
|
||||
break;
|
||||
case 'vicious':
|
||||
if (selection.data[0] === 'damage') {
|
||||
this.attack.damage.parts[0].value.dice = adjustDice(this.attack.damage.parts[0].value.dice);
|
||||
} else {
|
||||
this.attack.range = adjustRange(this.attack.range);
|
||||
}
|
||||
break;
|
||||
case 'stress':
|
||||
this.resources.stress.bonus += selection.value;
|
||||
break;
|
||||
case 'evasion':
|
||||
this.evasion.bonus += selection.value;
|
||||
break;
|
||||
case 'experience':
|
||||
Object.keys(this.experiences).forEach(key => {
|
||||
const experience = this.experiences[key];
|
||||
experience.bonus += selection.value;
|
||||
});
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prepareDerivedData() {
|
||||
for (var experienceKey in this.experiences) {
|
||||
var experience = this.experiences[experienceKey];
|
||||
experience.total = experience.value + experience.bonus;
|
||||
}
|
||||
|
||||
if (this.partner) {
|
||||
this.partner.system.resources.hope.max += this.resources.hope;
|
||||
}
|
||||
|
||||
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
|
||||
this.evasion.total = this.evasion.value + this.evasion.bonus;
|
||||
}
|
||||
|
||||
async _preDelete() {
|
||||
if (this.partner) {
|
||||
await this.partner.update({ 'system.companion': null });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,7 +10,7 @@ export default class DHArmor extends BaseDataItem {
|
|||
type: 'armor',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
type: 'base',
|
||||
hasDescription: false,
|
||||
isQuantifiable: false,
|
||||
isInventoryItem: false,
|
||||
isInventoryItem: false
|
||||
};
|
||||
}
|
||||
|
||||
|
|
@ -54,9 +54,9 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
const data = { ...actorRollData, item: { ...this } };
|
||||
return data;
|
||||
}
|
||||
|
||||
|
||||
async _preCreate(data, options, user) {
|
||||
if(!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return;
|
||||
if (!this.constructor.metadata.hasInitialAction || !foundry.utils.isEmpty(this.actions)) return;
|
||||
const actionType = {
|
||||
weapon: 'attack'
|
||||
}[this.constructor.metadata.type],
|
||||
|
|
@ -72,6 +72,6 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
|
|||
parent: this.parent
|
||||
}
|
||||
);
|
||||
this.updateSource({actions: [action]});
|
||||
this.updateSource({ actions: [action] });
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class DHConsumable extends BaseDataItem {
|
|||
type: 'consumable',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,4 +1,3 @@
|
|||
import { getTier } from '../../helpers/utils.mjs';
|
||||
import BaseDataItem from './base.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
|
||||
|
|
@ -17,135 +16,7 @@ export default class DHFeature extends BaseDataItem {
|
|||
const fields = foundry.data.fields;
|
||||
return {
|
||||
...super.defineSchema(),
|
||||
|
||||
//A type of feature seems unnecessary
|
||||
type: new fields.StringField({ choices: SYSTEM.ITEM.featureTypes }),
|
||||
|
||||
//TODO: remove actionType field
|
||||
actionType: new fields.StringField({
|
||||
choices: SYSTEM.ITEM.actionTypes,
|
||||
initial: SYSTEM.ITEM.actionTypes.passive.id
|
||||
}),
|
||||
//TODO: remove featureType field
|
||||
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 fields.TypedObjectField(
|
||||
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 }),
|
||||
//TODO: remove refreshed field
|
||||
refreshed: new fields.BooleanField({ initial: true })
|
||||
},
|
||||
{ nullable: true, initial: null }
|
||||
),
|
||||
//TODO: remove refreshed field
|
||||
multiclass: new fields.NumberField({ initial: null, nullable: true, integer: true }),
|
||||
disabled: new fields.BooleanField({ initial: false }),
|
||||
|
||||
//TODO: re do it completely or just remove it
|
||||
effects: new fields.TypedObjectField(
|
||||
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 fields.TypedObjectField(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 ActionField())
|
||||
};
|
||||
}
|
||||
|
||||
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 });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ export default class DHMiscellaneous extends BaseDataItem {
|
|||
type: 'miscellaneous',
|
||||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
isInventoryItem: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import BaseDataItem from './base.mjs';
|
|||
import FormulaField from '../fields/formulaField.mjs';
|
||||
import ActionField from '../fields/actionField.mjs';
|
||||
import { weaponFeatures } from '../../config/itemConfig.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
import { actionsTypes } from '../action/_module.mjs';
|
||||
|
||||
export default class DHWeapon extends BaseDataItem {
|
||||
/** @inheritDoc */
|
||||
|
|
@ -13,7 +13,7 @@ export default class DHWeapon extends BaseDataItem {
|
|||
hasDescription: true,
|
||||
isQuantifiable: true,
|
||||
isInventoryItem: true,
|
||||
hasInitialAction: true,
|
||||
hasInitialAction: true
|
||||
});
|
||||
}
|
||||
|
||||
|
|
|
|||
61
module/data/levelData.mjs
Normal file
61
module/data/levelData.mjs
Normal file
|
|
@ -0,0 +1,61 @@
|
|||
import { LevelOptionType } from './levelTier.mjs';
|
||||
|
||||
export default class DhLevelData 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 }),
|
||||
bonuses: new fields.TypedObjectField(new fields.NumberField({ integer: true, nullable: false }))
|
||||
}),
|
||||
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.TypedObjectField(new fields.StringField({ required: true })),
|
||||
itemUuid: new fields.DocumentUUIDField({ required: true }),
|
||||
featureIds: new fields.ArrayField(new fields.StringField())
|
||||
})
|
||||
)
|
||||
})
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
get actions() {
|
||||
return Object.values(this.levelups).flatMap(level => level.selections.flatMap(s => s.actions));
|
||||
}
|
||||
|
||||
get canLevelUp() {
|
||||
return this.level.current < this.level.changed;
|
||||
}
|
||||
}
|
||||
|
|
@ -58,6 +58,58 @@ class DhLevelOption extends foundry.abstract.DataModel {
|
|||
}
|
||||
}
|
||||
|
||||
export const CompanionLevelOptionType = {
|
||||
hope: {
|
||||
id: 'hope',
|
||||
label: 'Light In The Dark'
|
||||
},
|
||||
creatureComfort: {
|
||||
id: 'creatureComfort',
|
||||
label: 'Creature Comfort',
|
||||
features: [
|
||||
{
|
||||
name: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Name',
|
||||
img: 'icons/magic/life/heart-cross-purple-orange.webp',
|
||||
description: 'DAGGERHEART.LevelUp.Actions.CreatureComfort.Description'
|
||||
}
|
||||
]
|
||||
},
|
||||
armored: {
|
||||
id: 'armored',
|
||||
label: 'Armored',
|
||||
features: [
|
||||
{
|
||||
name: 'DAGGERHEART.LevelUp.Actions.Armored.Name',
|
||||
img: 'icons/equipment/shield/kite-wooden-oak-glow.webp',
|
||||
description: 'DAGGERHEART.LevelUp.Actions.Armored.Description'
|
||||
}
|
||||
]
|
||||
},
|
||||
vicious: {
|
||||
id: 'vicious',
|
||||
label: 'Viscious'
|
||||
},
|
||||
resilient: {
|
||||
id: 'resilient',
|
||||
label: 'Resilient'
|
||||
},
|
||||
bonded: {
|
||||
id: 'bonded',
|
||||
label: 'Bonded',
|
||||
features: [
|
||||
{
|
||||
name: 'DAGGERHEART.LevelUp.Actions.Bonded.Name',
|
||||
img: 'icons/magic/life/heart-red-blue.webp',
|
||||
description: 'DAGGERHEART.LevelUp.Actions.Bonded.Description'
|
||||
}
|
||||
]
|
||||
},
|
||||
aware: {
|
||||
id: 'aware',
|
||||
label: 'Aware'
|
||||
}
|
||||
};
|
||||
|
||||
export const LevelOptionType = {
|
||||
trait: {
|
||||
id: 'trait',
|
||||
|
|
@ -106,7 +158,8 @@ export const LevelOptionType = {
|
|||
multiclass: {
|
||||
id: 'multiclass',
|
||||
label: 'Multiclass'
|
||||
}
|
||||
},
|
||||
...CompanionLevelOptionType
|
||||
};
|
||||
|
||||
export const defaultLevelTiers = {
|
||||
|
|
@ -338,3 +391,80 @@ export const defaultLevelTiers = {
|
|||
}
|
||||
}
|
||||
};
|
||||
|
||||
export const defaultCompanionTier = {
|
||||
tiers: {
|
||||
2: {
|
||||
tier: 2,
|
||||
name: 'Companion Choices',
|
||||
levels: {
|
||||
start: 2,
|
||||
end: 10
|
||||
},
|
||||
initialAchievements: {},
|
||||
availableOptions: 1,
|
||||
domainCardByLevel: 0,
|
||||
options: {
|
||||
experience: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.intelligent',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.experience.id,
|
||||
value: 1,
|
||||
amount: 1
|
||||
},
|
||||
hope: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.lightInTheDark',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.hope.id,
|
||||
value: 1
|
||||
},
|
||||
creatureComfort: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.creatureComfort',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.creatureComfort.id,
|
||||
value: 1
|
||||
},
|
||||
armored: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.armored',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.armored.id,
|
||||
value: 1
|
||||
},
|
||||
vicious: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.vicious',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.vicious.id,
|
||||
value: 1,
|
||||
amount: 1
|
||||
},
|
||||
stress: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.resilient',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.stress.id,
|
||||
value: 1
|
||||
},
|
||||
bonded: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.bonded',
|
||||
checkboxSelections: 1,
|
||||
minCost: 1,
|
||||
type: CompanionLevelOptionType.bonded.id,
|
||||
value: 1
|
||||
},
|
||||
evasion: {
|
||||
label: 'DAGGERHEART.LevelUp.Options.aware',
|
||||
checkboxSelections: 3,
|
||||
minCost: 1,
|
||||
type: LevelOptionType.evasion.id,
|
||||
value: 2,
|
||||
amount: 1
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
return acc;
|
||||
}, {});
|
||||
|
||||
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.availableOptions, {
|
||||
levels[i] = DhLevelupLevel.initializeData(pcLevelData.levelups[i], tier.maxSelections[i], {
|
||||
...initialAchievements,
|
||||
experiences,
|
||||
domainCards
|
||||
|
|
@ -46,7 +46,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
name: tier.name,
|
||||
belongingLevels: belongingLevels,
|
||||
options: Object.keys(tier.options).reduce((acc, key) => {
|
||||
acc[key] = tier.options[key].toObject();
|
||||
acc[key] = tier.options[key].toObject?.() ?? tier.options[key];
|
||||
return acc;
|
||||
}, {})
|
||||
};
|
||||
|
|
@ -98,6 +98,7 @@ export class DhLevelup extends foundry.abstract.DataModel {
|
|||
case 'experience':
|
||||
case 'domainCard':
|
||||
case 'subclass':
|
||||
case 'vicious':
|
||||
return checkbox.data.length === (checkbox.amount ?? 1);
|
||||
case 'multiclass':
|
||||
const classSelected = checkbox.data.length === 1;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue