Improved Character datamodel

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

View file

@ -54,12 +54,12 @@ Hooks.once('init', () => {
CONFIG.Actor.documentClass = documents.DhpActor; CONFIG.Actor.documentClass = documents.DhpActor;
CONFIG.Actor.dataModels = { CONFIG.Actor.dataModels = {
pc: models.DhpPC, pc: models.DhCharacter,
adversary: models.DhpAdversary, adversary: models.DhpAdversary,
environment: models.DhpEnvironment environment: models.DhpEnvironment
}; };
Actors.unregisterSheet('core', foundry.appv1.sheets.ActorSheet); Actors.unregisterSheet('core', foundry.appv1.sheets.ActorSheet);
Actors.registerSheet(SYSTEM.id, applications.DhpPCSheet, { types: ['pc'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhCharacterSheet, { types: ['pc'], makeDefault: true });
Actors.registerSheet(SYSTEM.id, applications.DhpAdversarySheet, { types: ['adversary'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhpAdversarySheet, { types: ['adversary'], makeDefault: true });
Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true }); Actors.registerSheet(SYSTEM.id, applications.DhpEnvironment, { types: ['environment'], makeDefault: true });

View file

@ -110,8 +110,14 @@
}, },
"VariantRules": { "VariantRules": {
"ActionTokens": { "ActionTokens": {
"Name": "Action Tokens", "label": "Action Tokens",
"Hint": "Give each player action tokens to use in combat" "hint": "Give each player action tokens to use in combat"
},
"FIELDS": {
"useCoins": {
"label": "Use Coins",
"hint": "test"
}
} }
}, },
"DualityRollColor": { "DualityRollColor": {

View file

@ -1,4 +1,4 @@
export { default as DhpPCSheet } from './sheets/pc.mjs'; export { default as DhCharacterSheet } from './sheets/character.mjs';
export { default as DhpAdversarySheet } from './sheets/adversary.mjs'; export { default as DhpAdversarySheet } from './sheets/adversary.mjs';
export { default as DhpClassSheet } from './sheets/items/class.mjs'; export { default as DhpClassSheet } from './sheets/items/class.mjs';
export { default as DhpSubclass } from './sheets/items/subclass.mjs'; export { default as DhpSubclass } from './sheets/items/subclass.mjs';

View file

@ -149,7 +149,10 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
const experienceIncreaseValues = experienceIncreases const experienceIncreaseValues = experienceIncreases
.filter(exp => exp.data.length > 0) .filter(exp => exp.data.length > 0)
.flatMap(exp => .flatMap(exp =>
exp.data.map(data => this.actor.system.experiences.find(x => x.id === data).description) exp.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(x => x === data);
return this.actor.system.experiences[experience].description;
})
); );
context.experienceIncreases = { context.experienceIncreases = {
values: experienceIncreaseValues, values: experienceIncreaseValues,
@ -328,10 +331,12 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
break; break;
case 'experience': case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = []; if (!advancement[choiceKey]) advancement[choiceKey] = [];
const data = checkbox.data.map( const data = checkbox.data.map(data => {
data => const experience = Object.keys(this.actor.system.experiences).find(
this.actor.system.experiences.find(x => x.id === data)?.description ?? '' x => x === data
); );
return this.actor.system.experiences[experience]?.description ?? '';
});
advancement[choiceKey].push({ data: data, value: checkbox.value }); advancement[choiceKey].push({ data: data, value: checkbox.value });
break; break;
} }
@ -354,8 +359,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0) new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
}, },
evasion: { evasion: {
old: this.actor.system.evasion.value, old: this.actor.system.evasion,
new: this.actor.system.evasion.value + (advancement.evasion ?? 0) new: this.actor.system.evasion + (advancement.evasion ?? 0)
} }
}, },
traits: traits:
@ -421,8 +426,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
if (experienceIncreaseTagify) { if (experienceIncreaseTagify) {
tagifyElement( tagifyElement(
experienceIncreaseTagify, experienceIncreaseTagify,
this.actor.system.experiences.reduce((acc, experience) => { Object.keys(this.actor.system.experiences).reduce((acc, id) => {
acc[experience.id] = { label: experience.description }; const experience = this.actor.system.experiences[id];
acc[id] = { label: experience.description };
return acc; return acc;
}, {}), }, {}),

View file

@ -1,7 +1,7 @@
const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api; const { ApplicationV2, HandlebarsApplicationMixin } = foundry.applications.api;
export default class RollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) { export default class RollSelectionDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(experiences, bonusDamage, hopeResource, resolve, isNpc) { constructor(experiences, hopeResource, resolve) {
super({}, {}); super({}, {});
this.experiences = experiences; this.experiences = experiences;
@ -17,23 +17,13 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
fear: ['d12'], fear: ['d12'],
advantage: null, advantage: null,
disadvantage: null, disadvantage: null,
bonusDamage: bonusDamage.reduce((acc, x) => {
if (x.appliesOn === SYSTEM.EFFECTS.applyLocations.attackRoll.id) {
acc.push({
...x,
hopeUses: 0
});
}
return acc;
}, []),
hopeResource: hopeResource hopeResource: hopeResource
}; };
} }
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
id: 'roll-selection', //Having an id causes a new instance to overwrite previous. id: 'roll-selection',
classes: ['daggerheart', 'views', 'roll-selection'], classes: ['daggerheart', 'views', 'roll-selection'],
position: { position: {
width: 400, width: 400,
@ -41,8 +31,6 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
}, },
actions: { actions: {
selectExperience: this.selectExperience, selectExperience: this.selectExperience,
decreaseHopeUse: this.decreaseHopeUse,
increaseHopeUse: this.increaseHopeUse,
setAdvantage: this.setAdvantage, setAdvantage: this.setAdvantage,
setDisadvantage: this.setDisadvantage, setDisadvantage: this.setDisadvantage,
finish: this.finish finish: this.finish
@ -76,9 +64,8 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
context.disadvantage = this.data.disadvantage; context.disadvantage = this.data.disadvantage;
context.experiences = this.experiences.map(x => ({ context.experiences = this.experiences.map(x => ({
...x, ...x,
selected: this.selectedExperiences.find(selected => selected.id === x.id) selected: this.selectedExperiences.includes(x.id)
})); }));
context.bonusDamage = this.data.bonusDamage;
context.hopeResource = this.data.hopeResource + 1; context.hopeResource = this.data.hopeResource + 1;
context.hopeUsed = this.getHopeUsed(); context.hopeUsed = this.getHopeUsed();
@ -86,15 +73,7 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
} }
static updateSelection(event, _, formData) { static updateSelection(event, _, formData) {
const { bonusDamage, ...rest } = foundry.utils.expandObject(formData.object); const { ...rest } = foundry.utils.expandObject(formData.object);
for (var index in bonusDamage) {
this.data.bonusDamage[index].initiallySelected = bonusDamage[index].initiallySelected;
if (bonusDamage[index].hopeUses) {
const value = Number.parseInt(bonusDamage[index].hopeUses);
if (!Number.isNaN(value)) this.data.bonusDamage[index].hopeUses = value;
}
}
this.data = foundry.utils.mergeObject(this.data, rest); this.data = foundry.utils.mergeObject(this.data, rest);
this.render(); this.render();
@ -104,35 +83,12 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
if (this.selectedExperiences.find(x => x.id === button.dataset.key)) { if (this.selectedExperiences.find(x => x.id === button.dataset.key)) {
this.selectedExperiences = this.selectedExperiences.filter(x => x.id !== button.dataset.key); this.selectedExperiences = this.selectedExperiences.filter(x => x.id !== button.dataset.key);
} else { } else {
this.selectedExperiences = [ this.selectedExperiences = [...this.selectedExperiences, button.dataset.key];
...this.selectedExperiences,
this.experiences.find(x => x.id === button.dataset.key)
];
} }
this.render(); this.render();
} }
getHopeUsed() {
return this.data.bonusDamage.reduce((acc, x) => acc + x.hopeUses, 0);
}
static decreaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses - 1 >= 0) {
this.data.bonusDamage[index].hopeUses -= 1;
this.render(true);
}
}
static increaseHopeUse(_, button) {
const index = Number.parseInt(button.dataset.index);
if (this.data.bonusDamage[index].hopeUses <= this.data.hopeResource + 1) {
this.data.bonusDamage[index].hopeUses += 1;
this.render(true);
}
}
static setAdvantage() { static setAdvantage() {
this.data.advantage = this.data.advantage ? null : 'd6'; this.data.advantage = this.data.advantage ? null : 'd6';
this.data.disadvantage = null; this.data.disadvantage = null;
@ -149,11 +105,10 @@ export default class RollSelectionDialog extends HandlebarsApplicationMixin(Appl
static async finish() { static async finish() {
const { diceOptions, ...rest } = this.data; const { diceOptions, ...rest } = this.data;
this.resolve({ this.resolve({
...rest, ...rest,
experiences: this.selectedExperiences, experiences: this.selectedExperiences.map(x => ({ id: x, ...this.experiences[x] }))
hopeUsed: this.getHopeUsed(),
bonusDamage: this.data.bonusDamage.reduce((acc, x) => acc.concat(` + ${1 + x.hopeUses}${x.value}`), '')
}); });
this.close(); this.close();
} }

View file

@ -362,7 +362,7 @@ export default class AdversarySheet extends DaggerheartSheet(ActorSheetV2) {
name: x.actor.name, name: x.actor.name,
img: x.actor.img, img: x.actor.img,
difficulty: x.actor.system.difficulty, difficulty: x.actor.system.difficulty,
evasion: x.actor.system.evasion.value evasion: x.actor.system.evasion
})); }));
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');

View file

@ -8,7 +8,7 @@ import DhlevelUp from '../levelup.mjs';
const { ActorSheetV2 } = foundry.applications.sheets; const { ActorSheetV2 } = foundry.applications.sheets;
const { TextEditor } = foundry.applications.ux; const { TextEditor } = foundry.applications.ux;
export default class PCSheet extends DaggerheartSheet(ActorSheetV2) { export default class CharacterSheet extends DaggerheartSheet(ActorSheetV2) {
constructor(options = {}) { constructor(options = {}) {
super(options); super(options);
@ -248,7 +248,8 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
ancestry: ancestry[0], ancestry: ancestry[0],
community: community[0], community: community[0],
advancement: { advancement: {
...this.mapAdvancementFeatures(this.document, SYSTEM) // Removed until we have features again
// ...this.mapAdvancementFeatures(this.document, SYSTEM)
} }
}; };
@ -265,8 +266,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
vault: vault.map(x => ({ vault: vault.map(x => ({
...x, ...x,
uuid: x.uuid, uuid: x.uuid,
sendToLoadoutDisabled: sendToLoadoutDisabled: this.document.system.domainCards.loadout.length >= 5
this.document.system.domainCards.loadout.length >= this.document.system.domainData.maxLoadout
})) }))
}; };
@ -584,26 +584,21 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const weapon = await fromUuid(button.dataset.weapon); const weapon = await fromUuid(button.dataset.weapon);
const damage = { const damage = {
value: `${this.document.system.proficiency.value}${weapon.system.damage.value}`, value: `${this.document.system.proficiency.value}${weapon.system.damage.value}`,
type: weapon.system.damage.type, type: weapon.system.damage.type
bonusDamage: this.document.system.bonuses.damage
}; };
const modifier = this.document.system.traits[weapon.system.trait].value; const modifier = this.document.system.traits[weapon.system.trait].value;
const { roll, hope, fear, advantage, disadvantage, modifiers, bonusDamageString } = const { roll, hope, fear, advantage, disadvantage, modifiers } = await this.document.dualityRoll(
await this.document.dualityRoll( { title: game.i18n.localize(abilities[weapon.system.trait].label), value: modifier },
{ title: game.i18n.localize(abilities[weapon.system.trait].label), value: modifier }, event.shiftKey
event.shiftKey, );
damage.bonusDamage
);
damage.value = damage.value.concat(bonusDamageString);
const targets = Array.from(game.user.targets).map(x => ({ const targets = Array.from(game.user.targets).map(x => ({
id: x.id, id: x.id,
name: x.actor.name, name: x.actor.name,
img: x.actor.img, img: x.actor.img,
difficulty: x.actor.system.difficulty, difficulty: x.actor.system.difficulty,
evasion: x.actor.system.evasion.value evasion: x.actor.system.evasion
})); }));
const systemData = { const systemData = {
@ -659,7 +654,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
static async moveDomainCard(_, button) { static async moveDomainCard(_, button) {
const toVault = button.dataset.action === 'sendToVault'; const toVault = button.dataset.action === 'sendToVault';
if (!toVault && this.document.system.domainCards.loadout.length >= this.document.system.domainData.maxLoadout) { if (!toVault && this.document.system.domainCards.loadout.length >= 5) {
return; return;
} }
@ -860,15 +855,13 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
} }
async experienceDescriptionChange(event) { async experienceDescriptionChange(event) {
const newExperiences = [...this.document.system.experiences]; const path = `system.experiences.${event.currentTarget.dataset.experience}.description`;
newExperiences[event.currentTarget.dataset.index].description = event.currentTarget.value; await this.document.update({ [path]: event.currentTarget.value });
await this.document.update({ 'system.experiences': newExperiences });
} }
async experienceValueChange(event) { async experienceValueChange(event) {
const newExperiences = [...this.document.system.experiences]; const path = `system.experiences.${event.currentTarget.dataset.index}.value`;
newExperiences[event.currentTarget.dataset.index].value = event.currentTarget.value; await this.document.update({ [path]: event.currentTarget.value });
await this.document.update({ 'system.experiences': newExperiences });
} }
static setStoryEditor(_, button) { static setStoryEditor(_, button) {
@ -1080,18 +1073,12 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
const itemObject = await fromUuid(item.uuid); const itemObject = await fromUuid(item.uuid);
switch (target) { switch (target) {
case 'weapon-section': case 'weapon-section':
if ( if (itemObject.system.secondary && this.document.system.getWeaponBurden === 'twoHanded') {
itemObject.system.secondary &&
this.document.system.equippedWeapons.burden === 'twoHanded'
) {
ui.notifications.info( ui.notifications.info(
game.i18n.localize('DAGGERHEART.Notification.Info.SecondaryEquipWhileTwohanded') game.i18n.localize('DAGGERHEART.Notification.Info.SecondaryEquipWhileTwohanded')
); );
return; return;
} else if ( } else if (itemObject.system.burden === 'twoHanded' && this.document.system.secondaryWeapon) {
itemObject.system.burden === 'twoHanded' &&
this.document.system.equippedWeapons.secondary
) {
ui.notifications.info( ui.notifications.info(
game.i18n.localize('DAGGERHEART.Notification.Info.TwohandedEquipWhileSecondary') game.i18n.localize('DAGGERHEART.Notification.Info.TwohandedEquipWhileSecondary')
); );
@ -1154,7 +1141,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
return; return;
} }
if (this.document.system.domainCards.total.length === this.document.system.domainData.maxCards) { if (this.document.system.domainCards.total.length === 5) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.MaxLoadoutReached')); ui.notifications.error(game.i18n.localize('DAGGERHEART.Notification.Error.MaxLoadoutReached'));
return; return;
} }
@ -1164,7 +1151,7 @@ export default class PCSheet extends DaggerheartSheet(ActorSheetV2) {
return; return;
} }
if (this.document.system.domainCards.loadout.length >= this.document.system.domainData.maxLoadout) { if (this.document.system.domainCards.loadout.length >= 5) {
itemData.system.inVault = true; itemData.system.inVault = true;
} }

View file

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

View file

@ -1,45 +1,28 @@
import { getPathValue } from '../helpers/utils.mjs'; import { burden } from '../config/generalConfig.mjs';
import ForeignDocumentUUIDField from './fields/foreignDocumentUUIDField.mjs'; import ForeignDocumentUUIDField from './fields/foreignDocumentUUIDField.mjs';
import { LevelOptionType } from './levelTier.mjs'; import { LevelOptionType } from './levelTier.mjs';
const fields = foundry.data.fields;
const attributeField = () => const attributeField = () =>
new fields.SchemaField({ new foundry.data.fields.SchemaField({
bonus: new fields.NumberField({ initial: 0, integer: true }), value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
base: new fields.NumberField({ initial: 0, integer: true }), tierMarked: new foundry.data.fields.BooleanField({ initial: false })
tierMarked: new fields.BooleanField({ required: true, initial: false })
}); });
const resourceField = max => const resourceField = max =>
new fields.SchemaField({ new foundry.data.fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }), value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }), max: new foundry.data.fields.NumberField({ initial: max, integer: true })
min: new fields.NumberField({ initial: 0, integer: true }),
baseMax: new fields.NumberField({ initial: max, integer: true })
}); });
export default class DhpPC extends foundry.abstract.TypeDataModel { export default class DhCharacter extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields;
return { return {
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField(6), hitPoints: resourceField(6),
stress: resourceField(6), stress: resourceField(6),
hope: new fields.SchemaField({ hope: resourceField(6)
value: new fields.NumberField({ initial: -1, integer: true }), // FIXME. Logic is gte and needs -1 in PC/Hope. Change to 0
min: new fields.NumberField({ initial: 0, integer: true })
})
}),
bonuses: new fields.SchemaField({
damage: new fields.ArrayField(
new fields.SchemaField({
value: new fields.NumberField({ integer: true, initial: 0 }),
type: new fields.StringField({ nullable: true }),
initiallySelected: new fields.BooleanField(),
hopeIncrease: new fields.StringField({ initial: null, nullable: true }),
description: new fields.StringField({})
})
)
}), }),
traits: new fields.SchemaField({ traits: new fields.SchemaField({
agility: attributeField(), agility: attributeField(),
@ -49,24 +32,18 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
presence: attributeField(), presence: attributeField(),
knowledge: attributeField() knowledge: attributeField()
}), }),
proficiency: new fields.SchemaField({ proficiency: new fields.NumberField({ initial: 1, integer: true }),
base: new fields.NumberField({ required: true, initial: 1, integer: true }), evasion: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ required: true, initial: 0, integer: true }) experiences: new fields.TypedObjectField(
}),
evasion: new fields.SchemaField({
bonus: new fields.NumberField({ initial: 0, integer: true })
}),
experiences: new fields.ArrayField(
new fields.SchemaField({ new fields.SchemaField({
id: new fields.StringField({ required: true }),
description: new fields.StringField({}), description: new fields.StringField({}),
value: new fields.NumberField({ integer: true, nullable: true, initial: null }) value: new fields.NumberField({ integer: true, nullable: true, initial: null })
}), }),
{ {
initial: [ initial: {
{ id: foundry.utils.randomID(), description: '', value: 2 }, [foundry.utils.randomID()]: { description: '', value: 2 },
{ id: foundry.utils.randomID(), description: '', value: 2 } [foundry.utils.randomID()]: { description: '', value: 2 }
] }
} }
), ),
gold: new fields.SchemaField({ gold: new fields.SchemaField({
@ -76,27 +53,14 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
chests: new fields.NumberField({ initial: 0, integer: true }) chests: new fields.NumberField({ initial: 0, integer: true })
}), }),
pronouns: new fields.StringField({}), pronouns: new fields.StringField({}),
domainData: new fields.SchemaField({ scars: new fields.TypedObjectField(
maxLoadout: new fields.NumberField({ initial: 2, integer: true }), new fields.SchemaField({
maxCards: new fields.NumberField({ initial: 2, integer: true }) name: new fields.StringField({}),
}), description: new fields.HTMLField()
story: new fields.SchemaField({ })
background: new fields.HTMLField(), ),
appearance: new fields.HTMLField(), story: new fields.HTMLField(),
connections: new fields.HTMLField(), description: new fields.HTMLField(),
scars: new fields.ArrayField(
new fields.SchemaField({
name: new fields.StringField({}),
description: new fields.HTMLField()
})
)
}),
description: new fields.StringField({}),
//Temporary until new FoundryVersion fix --> See Armor.Mjs DataPreparation
armorMarks: new fields.SchemaField({
max: new fields.NumberField({ initial: 6, integer: true }),
value: new fields.NumberField({ initial: 0, integer: true })
}),
class: new fields.SchemaField({ class: new fields.SchemaField({
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }), value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }) subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
@ -109,10 +73,6 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
}; };
} }
get tier() {
return this.#getTier(this.levelData.currentLevel);
}
get ancestry() { get ancestry() {
return this.parent.items.find(x => x.type === 'ancestry') ?? null; return this.parent.items.find(x => x.type === 'ancestry') ?? null;
} }
@ -197,57 +157,21 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped); return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
} }
get equippedWeapons() { get primaryWeapon() {
const primaryWeapon = this.parent.items.find( return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && !x.system.secondary);
x => x.type === 'weapon' && x.system.equipped && !x.system.secondary
);
const secondaryWeapon = this.parent.items.find(
x => x.type === 'weapon' && x.system.equipped && x.system.secondary
);
return {
primary: this.#weaponData(primaryWeapon),
secondary: this.#weaponData(secondaryWeapon),
burden: this.getBurden(primaryWeapon, secondaryWeapon)
};
} }
static async unequipBeforeEquip(itemToEquip) { get secondaryWeapon() {
const equippedWeapons = this.equippedWeapons; return this.parent.items.find(x => x.type === 'weapon' && x.system.equipped && x.system.secondary);
if (itemToEquip.system.secondary) {
if (equippedWeapons.primary && equippedWeapons.primary.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
await this.parent.items.get(equippedWeapons.primary.id).update({ 'system.equipped': false });
}
if (equippedWeapons.secondary) {
await this.parent.items.get(equippedWeapons.secondary.id).update({ 'system.equipped': false });
}
} else {
if (equippedWeapons.secondary && itemToEquip.system.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
await this.parent.items.get(equippedWeapons.secondary.id).update({ 'system.equipped': false });
}
if (equippedWeapons.primary) {
await this.parent.items.get(equippedWeapons.primary.id).update({ 'system.equipped': false });
}
}
} }
get effects() { get getWeaponBurden() {
return this.parent.items.reduce((acc, item) => { return this.primaryWeapon?.system?.burden === burden.twoHanded.value ||
const effects = item.system.effectData; (this.primaryWeapon && this.secondaryWeapon)
if (effects && !item.system.disabled) { ? burden.twoHanded.value
for (var key in effects) { : this.primaryWeapon || this.secondaryWeapon
const effect = effects[key]; ? burden.oneHanded.value
for (var effectEntry of effect) { : null;
if (!acc[key]) acc[key] = [];
acc[key].push({ name: item.name, value: effectEntry });
}
}
}
return acc;
}, {});
} }
get refreshableFeatures() { get refreshableFeatures() {
@ -263,43 +187,32 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
); );
} }
//Should not be done in data? static async unequipBeforeEquip(itemToEquip) {
//TODO: REMOVE THIS const primary = this.primaryWeapon,
#weaponData(weapon) { secondary = this.secondaryWeapon;
return weapon if (itemToEquip.system.secondary) {
? { if (primary && primary.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
id: weapon.id, await primary.update({ 'system.equipped': false });
name: weapon.name, }
trait: game.i18n.localize(CONFIG.daggerheart.ACTOR.abilities[weapon.system.trait].label),
range: CONFIG.daggerheart.GENERAL.range[weapon.system.range],
damage: {
value: weapon.system.damage.value,
type: CONFIG.daggerheart.GENERAL.damageTypes[weapon.system.damage.type]
},
burden: weapon.system.burden,
feature: CONFIG.daggerheart.ITEM.weaponFeatures[weapon.system.feature],
img: weapon.img,
uuid: weapon.uuid
}
: null;
}
prepareBaseData() { if (secondary) {
this.resources.hitPoints.max = this.resources.hitPoints.baseMax + this.resources.hitPoints.bonus; await secondary.update({ 'system.equipped': false });
this.resources.stress.max = this.resources.stress.baseMax + this.resources.stress.bonus; }
this.evasion.value = (this.class?.system?.evasion ?? 0) + this.evasion.bonus; } else {
this.proficiency.value = this.proficiency.base + this.proficiency.bonus; if (secondary && itemToEquip.system.burden === SYSTEM.GENERAL.burden.twoHanded.value) {
await secondary.update({ 'system.equipped': false });
}
for (var attributeKey in this.traits) { if (primary) {
const attribute = this.traits[attributeKey]; await primary.update({ 'system.equipped': false });
attribute.value = attribute.base + attribute.bonus; }
} }
} }
prepareDerivedData() { prepareBaseData() {
this.resources.hope.max = 6 - this.story.scars.length; for (var attributeKey in this.traits) {
if (this.resources.hope.value >= this.resources.hope.max) { const attribute = this.traits[attributeKey];
this.resources.hope.value = Math.max(this.resources.hope.max - 1, 0); /* Levleup handling */
} }
const armor = this.armor; const armor = this.armor;
@ -311,57 +224,18 @@ export default class DhpPC extends foundry.abstract.TypeDataModel {
? armor.system.baseThresholds.severe + this.levelData.level.current ? armor.system.baseThresholds.severe + this.levelData.level.current
: this.levelData.level.current * 2 : this.levelData.level.current * 2
}; };
this.applyEffects();
} }
applyEffects() { prepareDerivedData() {
const effects = this.effects; this.resources.hope.max -= Object.keys(this.scars).length;
for (var key in effects) { this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
const effectType = effects[key];
for (var effect of effectType) {
switch (key) {
case SYSTEM.EFFECTS.effectTypes.health.id:
this.resources.hitPoints.bonus += effect.value.valueData.value;
break;
case SYSTEM.EFFECTS.effectTypes.stress.id:
this.resources.stress.bonus += effect.value.valueData.value;
break;
case SYSTEM.EFFECTS.effectTypes.damage.id:
this.bonuses.damage.push({
value: getPathValue(effect.value.valueData.value, this),
type: 'physical',
description: effect.name,
hopeIncrease: effect.value.valueData.hopeIncrease,
initiallySelected: effect.value.initiallySelected,
appliesOn: effect.value.appliesOn
});
}
}
}
}
getBurden(primary, secondary) {
const twoHanded =
primary?.system?.burden === 'twoHanded' ||
secondary?.system?.burden === 'twoHanded' ||
(primary?.system?.burden === 'oneHanded' && secondary?.system?.burden === 'oneHanded');
const oneHanded =
!twoHanded && (primary?.system?.burden === 'oneHanded' || secondary?.system?.burden === 'oneHanded');
return twoHanded ? 'twoHanded' : oneHanded ? 'oneHanded' : null;
}
#getTier(level) {
if (level >= 8) return 3;
else if (level >= 5) return 2;
else if (level >= 2) return 1;
else return 0;
} }
} }
class DhPCLevelData extends foundry.abstract.DataModel { class DhPCLevelData extends foundry.abstract.DataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields;
return { return {
level: new fields.SchemaField({ level: new fields.SchemaField({
current: new fields.NumberField({ required: true, integer: true, initial: 1 }), current: new fields.NumberField({ required: true, integer: true, initial: 1 }),

View file

@ -1,11 +1,14 @@
export default class DhVariantRules extends foundry.abstract.DataModel { export default class DhVariantRules extends foundry.abstract.DataModel {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.Settings.VariantRules'];
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;
return { return {
actionTokens: new fields.SchemaField({ actionTokens: new fields.SchemaField({
enabled: new fields.BooleanField({ required: true, initial: false }), enabled: new fields.BooleanField({ required: true, initial: false }),
tokens: new fields.NumberField({ required: true, integer: true, initial: 3 }) tokens: new fields.NumberField({ required: true, integer: true, initial: 3 })
}) }),
useCoins: new fields.BooleanField({ initial: false })
}; };
} }

View file

@ -173,12 +173,11 @@ export default class DhpActor extends Actor {
return { roll, dice: dice[0], modifiers, advantageState: advantage === true ? 1 : advantage === false ? 2 : 0 }; return { roll, dice: dice[0], modifiers, advantageState: advantage === true ? 1 : advantage === false ? 2 : 0 };
} }
async dualityRoll(modifier, shiftKey, bonusDamage = []) { async dualityRoll(modifier, shiftKey) {
let hopeDice = 'd12', let hopeDice = 'd12',
fearDice = 'd12', fearDice = 'd12',
advantageDice = null, advantageDice = null,
disadvantageDice = null, disadvantageDice = null;
bonusDamageString = '';
const modifiers = const modifiers =
modifier.value !== null modifier.value !== null
@ -195,12 +194,9 @@ export default class DhpActor extends Actor {
: []; : [];
if (!shiftKey) { if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => { const dialogClosed = new Promise((resolve, _) => {
new RollSelectionDialog( new RollSelectionDialog(this.system.experiences, this.system.resources.hope.value, resolve).render(
this.system.experiences, true
bonusDamage, );
this.system.resources.hope.value,
resolve
).render(true);
}); });
const result = await dialogClosed; const result = await dialogClosed;
(hopeDice = result.hope), (hopeDice = result.hope),
@ -214,7 +210,6 @@ export default class DhpActor extends Actor {
title: x.description title: x.description
}) })
); );
bonusDamageString = result.bonusDamage;
const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope); const automateHope = await game.settings.get(SYSTEM.id, SYSTEM.SETTINGS.gameSettings.Automation.Hope);
@ -268,8 +263,7 @@ export default class DhpActor extends Actor {
fear: { dice: fearDice, value: fear }, fear: { dice: fearDice, value: fear },
advantage: { dice: advantageDice, value: advantage }, advantage: { dice: advantageDice, value: advantage },
disadvantage: { dice: disadvantageDice, value: disadvantage }, disadvantage: { dice: disadvantageDice, value: disadvantage },
modifiers: modifiers, modifiers: modifiers
bonusDamageString
}; };
} }

View file

@ -11,6 +11,7 @@ export default class RegisterHandlebarsHelpers {
includes: this.includes, includes: this.includes,
debug: this.debug, debug: this.debug,
signedNumber: this.signedNumber, signedNumber: this.signedNumber,
length: this.length,
switch: this.switch, switch: this.switch,
case: this.case case: this.case
}); });
@ -82,6 +83,10 @@ export default class RegisterHandlebarsHelpers {
return number >= 0 ? `+${number}` : number; return number >= 0 ? `+${number}` : number;
} }
static length(obj) {
return Object.keys(obj).length;
}
static switch(value, options) { static switch(value, options) {
this.switch_value = value; this.switch_value = value;
this.switch_break = false; this.switch_break = false;

View file

@ -163,10 +163,7 @@
"name": "Daggerheart", "name": "Daggerheart",
"sorting": "m", "sorting": "m",
"color": "#08718c", "color": "#08718c",
"packs": [ "packs": ["adversaries", "environments"],
"adversaries",
"environments"
],
"folders": [ "folders": [
{ {
"name": "Character Options", "name": "Character Options",
@ -186,12 +183,7 @@
"name": "Items", "name": "Items",
"sorting": "m", "sorting": "m",
"color": "#000000", "color": "#000000",
"packs": [ "packs": ["weapons", "armors", "consumables", "general-items"]
"weapons",
"armors",
"consumables",
"general-items"
]
} }
] ]
} }
@ -211,7 +203,9 @@
}, },
"documentTypes": { "documentTypes": {
"Actor": { "Actor": {
"pc": {}, "character": {
"htmlFields": ["story", "description", "scars.*.description"]
},
"adversary": {}, "adversary": {},
"environment": {} "environment": {}
}, },

View file

@ -8,6 +8,8 @@
</div> </div>
</div> </div>
{{formGroup settingFields.schema.fields.useCoins value=settingFields._source.useCoins localize=true }}
<footer class="form-footer"> <footer class="form-footer">
<button data-action="reset"> <button data-action="reset">
<i class="fa-solid fa-arrow-rotate-left"></i> <i class="fa-solid fa-arrow-rotate-left"></i>

View file

@ -12,10 +12,10 @@
</div> </div>
<div class="attribute-image"> <div class="attribute-image">
{{#if ../editAttributes}} {{#if ../editAttributes}}
<select class="attribute-value{{#if (lt attribute.base 0)}} negative{{/if}}{{#if (and (not attribute.base) (not ../abilityScoresFinished))}} unselected{{/if}}" data-attribute="{{key}}"> <select class="attribute-value{{#if (lt attribute.value 0)}} negative{{/if}}{{#if (and (not attribute.value) (not ../abilityScoresFinished))}} unselected{{/if}}" data-attribute="{{key}}">
{{#if (not (eq attribute.base 0))}}<option value="">{{attribute.base}}</option>{{/if}} {{#if (not (eq attribute.value 0))}}<option value="">{{attribute.value}}</option>{{/if}}
{{#each ../abilityScoreArray as |option|}} {{#each ../abilityScoreArray as |option|}}
<option value="{{option.value}}"{{#if (eq option.value attribute.base)}} selected="selected"{{/if}}>{{option.name}}</option> <option value="{{option.value}}">{{option.name}}</option>
{{/each}} {{/each}}
</select> </select>
{{else}} {{else}}

View file

@ -4,7 +4,7 @@
<div class="defense-row"> <div class="defense-row">
<div class="defense-section"> <div class="defense-section">
<div class="defense-container"> <div class="defense-container">
<div class="defense-value">{{document.system.evasion.value}}</div> <div class="defense-value">{{document.system.evasion}}</div>
<img src="systems/daggerheart/assets/AttributeShield.svg" /> <img src="systems/daggerheart/assets/AttributeShield.svg" />
<div class="defense-banner">{{localize "DAGGERHEART.Sheets.PC.Defense.Evasion"}}</div> <div class="defense-banner">{{localize "DAGGERHEART.Sheets.PC.Defense.Evasion"}}</div>
</div> </div>
@ -16,16 +16,18 @@
<img src="systems/daggerheart/assets/AttributeShield.svg" /> <img src="systems/daggerheart/assets/AttributeShield.svg" />
<div class="defense-banner">{{localize "DAGGERHEART.Sheets.PC.Defense.Armor"}}</div> <div class="defense-banner">{{localize "DAGGERHEART.Sheets.PC.Defense.Armor"}}</div>
</div> </div>
<div class="armor-marks"> {{#if document.system.armor.system.marks}}
{{#times (subtract 12 document.system.armorMarks.max)}} <div class="armor-marks">
<input class="mark disabled-mark" type="checkbox" disabled /> {{#times (subtract 12 document.system.armor.system.marks.max)}}
{{/times}} <input class="mark disabled-mark" type="checkbox" disabled />
{{#times document.system.armorMarks.max}} {{/times}}
{{#with (add this 1)}} {{#times document.system.armor.system.marks.max}}
<input class="mark" type="checkbox" data-action="toggleMarks" data-value="{{this}}" {{ checked (gte ../../document.system.armor.system.marks.value this) }} /> {{#with (add this 1)}}
{{/with}} <input class="mark" type="checkbox" data-action="toggleMarks" data-value="{{this}}" {{ checked (gte ../../document.system.armor.system.marks.value this) }} />
{{/times}} {{/with}}
</div> {{/times}}
</div>
{{/if}}
</div> </div>
</div> </div>
</fieldset> </fieldset>

View file

@ -1,12 +1,12 @@
<fieldset class="left-main-container experience-container"> <fieldset class="left-main-container experience-container">
<legend class="legend">{{localize "DAGGERHEART.Sheets.PC.Experience.Title"}}</legend> <legend class="legend">{{localize "DAGGERHEART.Sheets.PC.Experience.Title"}}</legend>
{{#each document.system.experiences as |experience index|}} {{#each document.system.experiences as |experience id|}}
<div class="experience-row"> <div class="experience-row">
<input class="experience-description" data-index={{index}} value="{{experience.description}}" type="text" /> <input class="experience-description" data-experience={{id}} value="{{experience.description}}" type="text" />
<div class="experience-value">{{experience.value}}</div> <div class="experience-value">{{experience.value}}</div>
</div> </div>
{{/each}} {{/each}}
{{#times (subtract 5 document.system.experiences.length)}} {{#times (subtract 5 (length document.system.experiences))}}
<div class="experience-row"> <div class="experience-row">
<input type="text" class="disabled-experience" disabled /> <input type="text" class="disabled-experience" disabled />
<div class="experience-value empty"></div> <div class="experience-value empty"></div>

View file

@ -11,39 +11,39 @@
<div class="proficiency-container-visual-element"></div> <div class="proficiency-container-visual-element"></div>
</div> </div>
<div class="weapons-burden"> <div class="weapons-burden">
<i class="fa-solid fa-hand weapons-burden-icon left {{#if (or (eq weapons.burden "oneHanded") (eq weapons.burden "twoHanded"))}}active{{/if}}"></i> <i class="fa-solid fa-hand weapons-burden-icon left {{#if (or (eq weaponBurden "oneHanded") (eq weaponBurden "twoHanded"))}}active{{/if}}"></i>
<i class="fa-solid fa-hand weapons-burden-icon right {{#if (eq weapons.burden "twoHanded")}}active{{/if}}"></i> <i class="fa-solid fa-hand weapons-burden-icon right {{#if (eq weaponBurden "twoHanded")}}active{{/if}}"></i>
</div> </div>
</legend> </legend>
<div class="active-item-container"> <div class="active-item-container">
<h2 class="weapons-label-row"> <h2 class="weapons-label-row">
{{localize "DAGGERHEART.Sheets.PC.Weapons.PrimaryTitle"}} {{localize "DAGGERHEART.Sheets.PC.Weapons.PrimaryTitle"}}
{{#if weapons.primary}} {{#if primaryWeapon}}
<img class="damage-roll" data-action="attackRoll" data-weapon="{{weapons.primary.uuid}}" src="icons/svg/d12-grey.svg" /> <img class="damage-roll" data-action="attackRoll" data-weapon="{{primaryWeapon.uuid}}" src="icons/svg/d12-grey.svg" />
{{/if}} {{/if}}
</h2> </h2>
<div class="flexrow"> <div class="flexrow">
<input value="{{weapons.primary.name}}" type="text" /> <input value="{{primaryWeapon.name}}" type="text" />
<input value="{{localize weapons.primary.trait}}" type="text" /> <input value="{{localize primaryWeapon.trait}}" type="text" />
<input value="{{localize weapons.primary.range.label}}" type="text" /> <input value="{{localize primaryWeapon.range.label}}" type="text" />
<input value="{{weapons.primary.damage.value}} {{#if weapons.primary}}({{localize weapons.primary.damage.type.abbreviation}}){{/if}}" type="text" /> <input value="{{primaryWeapon.damage.value}} {{#if primaryWeapon}}({{localize primaryWeapon.damage.type.abbreviation}}){{/if}}" type="text" />
</div> </div>
<input value="{{localize weapons.primary.feature.label}} {{#if weapons.primary.feature}}({{localize weapons.primary.feature.description}}){{/if}}" type="text" /> <input value="{{localize primaryWeapon.feature.label}} {{#if primaryWeapon.feature}}({{localize primaryWeapon.feature.description}}){{/if}}" type="text" />
</div> </div>
<div class="active-item-container"> <div class="active-item-container">
<h2 class="weapons-label-row"> <h2 class="weapons-label-row">
{{localize "DAGGERHEART.Sheets.PC.Weapons.SecondaryTitle"}} {{localize "DAGGERHEART.Sheets.PC.Weapons.SecondaryTitle"}}
{{#if weapons.secondary}} {{#if secondaryWeapon}}
<img class="damage-roll" data-action="damageRoll" data-value="{{weapons.secondary.damage.value}}" src="icons/svg/d12-grey.svg" /> <img class="damage-roll" data-action="damageRoll" data-value="{{secondaryWeapon.damage.value}}" src="icons/svg/d12-grey.svg" />
{{/if}} {{/if}}
</h2> </h2>
<div class="flexrow"> <div class="flexrow">
<input value="{{weapons.secondary.name}}" type="text" /> <input value="{{secondaryWeapon.name}}" type="text" />
<input value="{{localize weapons.secondary.trait}}" type="text" /> <input value="{{localize secondaryWeapon.trait}}" type="text" />
<input value="{{localize weapons.secondary.range.label}}" type="text" /> <input value="{{localize secondaryWeapon.range.label}}" type="text" />
<input value="{{weapons.secondary.damage.label}} {{#if weapons.secondary}}({{localize weapons.secondary.damage.type.abbreviation}}){{/if}}" type="text" /> <input value="{{secondaryWeapon.damage.label}} {{#if secondaryWeapon}}({{localize secondaryWeapon.damage.type.abbreviation}}){{/if}}" type="text" />
</div> </div>
<input value="{{localize weapons.secondary.feature.name}} {{#if weapons.secondary.feature}}({{localize weapons.secondary.feature.description}}){{/if}}" style="text-overflow: ellipsis;" type="text" /> <input value="{{localize secondaryWeapon.feature.name}} {{#if secondaryWeapon.feature}}({{localize secondaryWeapon.feature.description}}){{/if}}" style="text-overflow: ellipsis;" type="text" />
</div> </div>
</fieldset> </fieldset>

View file

@ -87,7 +87,7 @@
</div> </div>
<div class="body-section flex3"> <div class="body-section flex3">
{{> "systems/daggerheart/templates/sheets/parts/attributes.hbs" }} {{> "systems/daggerheart/templates/sheets/parts/attributes.hbs" }}
{{> "systems/daggerheart/templates/sheets/parts/weapons.hbs" weapons=document.system.equippedWeapons proficiency=document.system.proficiency.value }} {{> "systems/daggerheart/templates/sheets/parts/weapons.hbs" primaryWeapon=document.system.primaryWeapon secondaryWeapon=document.system.secondaryWeapon weaponBurden=document.system.getWeaponBurden proficiency=document.system.proficiency.value }}
{{> "systems/daggerheart/templates/sheets/parts/armor.hbs" armor=document.system.armor }} {{> "systems/daggerheart/templates/sheets/parts/armor.hbs" armor=document.system.armor }}
</div> </div>
</div> </div>

View file

@ -15,22 +15,6 @@
<button class="disadvantage flex1 {{#if this.advantage}}selected{{/if}}" data-action="setAdvantage">Advantage</button> <button class="disadvantage flex1 {{#if this.advantage}}selected{{/if}}" data-action="setAdvantage">Advantage</button>
<button class="disadvantage flex1 {{#if this.disadvantage}}selected{{/if}}" data-action="setDisadvantage">Disadvantage</button> <button class="disadvantage flex1 {{#if this.disadvantage}}selected{{/if}}" data-action="setDisadvantage">Disadvantage</button>
</div> </div>
{{!-- <div class="form-group">
<label>Advantage</label>
<div class="form-fields">
<select name="advantage" {{#if this.disadvantage}}disabled{{/if}}>
{{selectOptions this.diceOptions selected=this.advantage valueAttr="value" labelAttr="name" localize=true blank=""}}
</select>
</div>
</div>
<div class="form-group">
<label>Disadvantage</label>
<div class="form-fields">
<select name="disadvantage" {{#if this.advantage}}disabled{{/if}}>
{{selectOptions this.diceOptions selected=this.disadvantage valueAttr="value" labelAttr="name" localize=true blank=""}}
</select>
</div>
</div> --}}
{{#if (not this.isNpc)}} {{#if (not this.isNpc)}}
<div class="form-group"> <div class="form-group">
<label>Hope</label> <label>Hope</label>
@ -49,24 +33,6 @@
</div> </div>
</div> </div>
{{/if}} {{/if}}
{{#each this.bonusDamage as |damage index|}}
<div class="form-group">
<label><strong>{{damage.description}}</strong></label>
<div class="form-fields">
<label>Enabled</label>
<input style="align-self: baseline;" type="checkbox" name="bonusDamage.{{index}}.initiallySelected" {{checked damage.initiallySelected}} />
{{#if (and damage.initiallySelected damage.hopeIncrease)}}
<label>Hope</label>
<div class="hope-container">
<i data-action="decreaseHopeUse" data-index="{{index}}" class="fa-solid fa-caret-left icon-button {{#if (eq damage.hopeUses 0)}}disabled{{/if}}"></i>
<div>{{damage.hopeUses}}</div>
<i data-action="increaseHopeUse" data-index="{{index}}" class="fa-solid fa-caret-right icon-button {{#if (eq ../hopeUsed ../hopeResource)}}disabled{{/if}}"></i>
</div>
{{/if}}
</div>
</div>
{{/each}}
</div> </div>
<footer> <footer>
<button data-action="finish">Roll</button> <button data-action="finish">Roll</button>