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.dataModels = {
pc: models.DhpPC,
pc: models.DhCharacter,
adversary: models.DhpAdversary,
environment: models.DhpEnvironment
};
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.DhpEnvironment, { types: ['environment'], makeDefault: true });

View file

@ -110,8 +110,14 @@
},
"VariantRules": {
"ActionTokens": {
"Name": "Action Tokens",
"Hint": "Give each player action tokens to use in combat"
"label": "Action Tokens",
"hint": "Give each player action tokens to use in combat"
},
"FIELDS": {
"useCoins": {
"label": "Use Coins",
"hint": "test"
}
}
},
"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 DhpClassSheet } from './sheets/items/class.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
.filter(exp => exp.data.length > 0)
.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 = {
values: experienceIncreaseValues,
@ -328,10 +331,12 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
break;
case 'experience':
if (!advancement[choiceKey]) advancement[choiceKey] = [];
const data = checkbox.data.map(
data =>
this.actor.system.experiences.find(x => x.id === data)?.description ?? ''
);
const data = checkbox.data.map(data => {
const experience = Object.keys(this.actor.system.experiences).find(
x => x === data
);
return this.actor.system.experiences[experience]?.description ?? '';
});
advancement[choiceKey].push({ data: data, value: checkbox.value });
break;
}
@ -354,8 +359,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion.value,
new: this.actor.system.evasion.value + (advancement.evasion ?? 0)
old: this.actor.system.evasion,
new: this.actor.system.evasion + (advancement.evasion ?? 0)
}
},
traits:
@ -421,8 +426,9 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
if (experienceIncreaseTagify) {
tagifyElement(
experienceIncreaseTagify,
this.actor.system.experiences.reduce((acc, experience) => {
acc[experience.id] = { label: experience.description };
Object.keys(this.actor.system.experiences).reduce((acc, id) => {
const experience = this.actor.system.experiences[id];
acc[id] = { label: experience.description };
return acc;
}, {}),

View file

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

View file

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

View file

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

View file

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

View file

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

View file

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

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 };
}
async dualityRoll(modifier, shiftKey, bonusDamage = []) {
async dualityRoll(modifier, shiftKey) {
let hopeDice = 'd12',
fearDice = 'd12',
advantageDice = null,
disadvantageDice = null,
bonusDamageString = '';
disadvantageDice = null;
const modifiers =
modifier.value !== null
@ -195,12 +194,9 @@ export default class DhpActor extends Actor {
: [];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => {
new RollSelectionDialog(
this.system.experiences,
bonusDamage,
this.system.resources.hope.value,
resolve
).render(true);
new RollSelectionDialog(this.system.experiences, this.system.resources.hope.value, resolve).render(
true
);
});
const result = await dialogClosed;
(hopeDice = result.hope),
@ -214,7 +210,6 @@ export default class DhpActor extends Actor {
title: x.description
})
);
bonusDamageString = result.bonusDamage;
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 },
advantage: { dice: advantageDice, value: advantage },
disadvantage: { dice: disadvantageDice, value: disadvantage },
modifiers: modifiers,
bonusDamageString
modifiers: modifiers
};
}

View file

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

View file

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

View file

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

View file

@ -12,10 +12,10 @@
</div>
<div class="attribute-image">
{{#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}}">
{{#if (not (eq attribute.base 0))}}<option value="">{{attribute.base}}</option>{{/if}}
<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.value 0))}}<option value="">{{attribute.value}}</option>{{/if}}
{{#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}}
</select>
{{else}}

View file

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

View file

@ -1,12 +1,12 @@
<fieldset class="left-main-container experience-container">
<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">
<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>
{{/each}}
{{#times (subtract 5 document.system.experiences.length)}}
{{#times (subtract 5 (length document.system.experiences))}}
<div class="experience-row">
<input type="text" class="disabled-experience" disabled />
<div class="experience-value empty"></div>

View file

@ -11,39 +11,39 @@
<div class="proficiency-container-visual-element"></div>
</div>
<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 right {{#if (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 weaponBurden "twoHanded")}}active{{/if}}"></i>
</div>
</legend>
<div class="active-item-container">
<h2 class="weapons-label-row">
{{localize "DAGGERHEART.Sheets.PC.Weapons.PrimaryTitle"}}
{{#if weapons.primary}}
<img class="damage-roll" data-action="attackRoll" data-weapon="{{weapons.primary.uuid}}" src="icons/svg/d12-grey.svg" />
{{#if primaryWeapon}}
<img class="damage-roll" data-action="attackRoll" data-weapon="{{primaryWeapon.uuid}}" src="icons/svg/d12-grey.svg" />
{{/if}}
</h2>
<div class="flexrow">
<input value="{{weapons.primary.name}}" type="text" />
<input value="{{localize weapons.primary.trait}}" type="text" />
<input value="{{localize weapons.primary.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.name}}" type="text" />
<input value="{{localize primaryWeapon.trait}}" type="text" />
<input value="{{localize primaryWeapon.range.label}}" type="text" />
<input value="{{primaryWeapon.damage.value}} {{#if primaryWeapon}}({{localize primaryWeapon.damage.type.abbreviation}}){{/if}}" type="text" />
</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 class="active-item-container">
<h2 class="weapons-label-row">
{{localize "DAGGERHEART.Sheets.PC.Weapons.SecondaryTitle"}}
{{#if weapons.secondary}}
<img class="damage-roll" data-action="damageRoll" data-value="{{weapons.secondary.damage.value}}" src="icons/svg/d12-grey.svg" />
{{#if secondaryWeapon}}
<img class="damage-roll" data-action="damageRoll" data-value="{{secondaryWeapon.damage.value}}" src="icons/svg/d12-grey.svg" />
{{/if}}
</h2>
<div class="flexrow">
<input value="{{weapons.secondary.name}}" type="text" />
<input value="{{localize weapons.secondary.trait}}" type="text" />
<input value="{{localize weapons.secondary.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.name}}" type="text" />
<input value="{{localize secondaryWeapon.trait}}" type="text" />
<input value="{{localize secondaryWeapon.range.label}}" type="text" />
<input value="{{secondaryWeapon.damage.label}} {{#if secondaryWeapon}}({{localize secondaryWeapon.damage.type.abbreviation}}){{/if}}" type="text" />
</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>
</fieldset>

View file

@ -87,7 +87,7 @@
</div>
<div class="body-section flex3">
{{> "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 }}
</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.disadvantage}}selected{{/if}}" data-action="setDisadvantage">Disadvantage</button>
</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)}}
<div class="form-group">
<label>Hope</label>
@ -49,24 +33,6 @@
</div>
</div>
{{/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>
<footer>
<button data-action="finish">Roll</button>