Merge branch 'main' into feature/336-damage-targeted-resources

This commit is contained in:
Dapoolp 2025-07-18 02:31:20 +02:00
commit b3984a8e5f
57 changed files with 937 additions and 309 deletions

View file

@ -140,6 +140,7 @@ Hooks.once('init', () => {
CONFIG.Combat.documentClass = documents.DhpCombat;
CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.ui.resources = applications.ui.DhFearTracker;

View file

@ -252,8 +252,9 @@
"title": "{actor} - Death Move"
},
"Downtime": {
"downtimeHeader": "Downtime Moves ({current}/{max})",
"longRest": {
"title": "Long Rest",
"moves": "Long Rest Moves ({current}/{max})",
"clearStress": {
"description": "Describe how you blow off steam or pull yourself together, and clear all marked Stress.",
"name": "Clear Stress"
@ -270,7 +271,6 @@
"description": "Describe how you patch yourself up and remove all marked Hit Points. You may also do this on an ally instead.",
"name": "Tend to Wounds"
},
"title": "Long Rest",
"workOnAProject": {
"description": "Establish or continue work on a project.",
"name": "Work on a Project"
@ -278,6 +278,7 @@
},
"shortRest": {
"title": "Short Rest",
"moves": "Short Rest Moves ({current}/{max})",
"tendToWounds": {
"name": "Tend to Wounds",
"description": "Describe how you hastily patch yourself up, then clear a number of Hit Points equal to 1d4 + your tier. You can do this to an ally instead."
@ -294,7 +295,8 @@
"name": "Prepare",
"description": "Describe how you prepare yourself for the path ahead, then gain a Hope. If you choose to Prepare with one or more members of your party, you each gain 2 Hope."
}
}
},
"takeDowntime": "Take Downtime"
},
"HUD": {
"tokenHUD": {
@ -410,7 +412,11 @@
"rerollDice": "Reroll Dice"
}
},
"CLASS": {
"Feature": {
"rallyDice": "Bardic Rally Dice"
}
},
"CONFIG": {
"ActionType": {
"passive": "Passive",
@ -778,7 +784,7 @@
"WeaponFeature": {
"barrier": {
"name": "Barrier",
"description": "+{armorScore} to Armor Score; -1 to Evasion"
"description": "Gain your character's Tier + 1 to Armor Score; -1 to Evasion"
},
"bonded": {
"name": "Bonded",
@ -894,7 +900,7 @@
},
"paired": {
"name": "Paired",
"description": "+{bonusDamage} to primary weapon damage to targets within Melee range"
"description": "Add your character's Tier + 1 to primary weapon damage against targets within Melee range"
},
"parry": {
"name": "Parry",
@ -914,7 +920,7 @@
},
"protective": {
"name": "Protective",
"description": "+{tier} to Armor Score"
"description": "Add your character's Tier to your Armor Score"
},
"quick": {
"name": "Quick",
@ -963,10 +969,6 @@
"timebending": {
"name": "Timebending",
"description": "You can choose the target of your attack after making your attack roll."
},
"versatile": {
"name": "Versatile",
"description": "This weapon can also be used with these statistics—{characterTrait}, {range}, {damage}."
}
}
},
@ -1015,6 +1017,30 @@
"singular": "Adversary",
"plural": "Adversaries"
},
"Bonuses": {
"rest": {
"shortRest": {
"shortRestMoves": {
"label": "Short Rest: Bonus Short Rest Moves",
"hint": "The number of extra Short Rest Moves the character can take during a Short Rest."
},
"longRestMoves": {
"label": "Short Rest: Bonus Long Rest Moves",
"hint": "The number of extra Long Rest Moves the character can take during a Short Rest."
}
},
"longRest": {
"shortRestMoves": {
"label": "Long Rest: Bonus Short Rest Moves",
"hint": "The number of extra Short Rest Moves the character can take during a Long Rest."
},
"longRestMoves": {
"label": "Long Rest: Bonus Long Rest Moves",
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
}
}
}
},
"Character": {
"singular": "Character",
"plural": "Characters"
@ -1328,8 +1354,8 @@
},
"DomainCard": {
"type": "Type",
"foundation": "Foundation",
"recallCost": "Recall Cost",
"foundationTitle": "Foundation",
"specializationTitle": "Specialization",
"masteryTitle": "Mastery"
},
@ -1540,7 +1566,18 @@
"noAvailableArmorMarks": "You have no more available armor marks",
"notEnoughStress": "You don't have enough stress",
"damageIgnore": "{character} did not take damage",
"featureIsMissing": "Feature is missing"
"featureIsMissing": "Feature is missing",
"actionIsMissing": "Action is missing",
"attackIsMissing": "Attack is missing",
"unownedActionMacro": "Cannot make a Use macro for an Action not on your character",
"unownedAttackMacro": "Cannot make a Use macro for an Attack that doesn't belong to one of your characters",
"featureNotHope": "This feature is used as something else than a Hope feature and cannot be used here.",
"featureNotClass": "This feature is used as something else than a Class feature and cannot be used here.",
"featureNotPrimary": "This feature is used as something else than a Primary feature and cannot be used here.",
"featureNotSecondary": "This feature is used as something else than a Secondary feature and cannot be used here.",
"featureNotFoundation": "This feature is used as something else than a Foundation feature and cannot be used here.",
"featureNotSpecialization": "This feature is used as something else than a Specialization feature and cannot be used here.",
"featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here."
},
"Tooltip": {
"openItemWorld": "Open Item World",

View file

@ -506,9 +506,7 @@ export default class DhCharacterCreation extends HandlebarsApplicationMixin(Appl
name: this.setup.ancestryName ?? this.setup.primaryAncestry.name,
system: {
...this.setup.primaryAncestry.system,
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid],
primaryFeature: primaryAncestryFeature.uuid,
secondaryFeature: secondaryAncestryFeature.uuid
features: [primaryAncestryFeature.uuid, secondaryAncestryFeature.uuid]
}
};

View file

@ -89,6 +89,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
if (this.roll) {
context.roll = this.roll;
context.rollType = this.roll?.constructor.name;
context.rallyDie = this.roll.rallyChoices;
context.experiences = Object.keys(this.config.data.experiences).map(id => ({
id,
...this.config.data.experiences[id]

View file

@ -7,8 +7,22 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
this.actor = actor;
this.shortrest = shortrest;
const options = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
this.moveData = shortrest ? options.shortRest : options.longRest;
this.moveData = foundry.utils.deepClone(
game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves
);
this.nrChoices = {
shortRest: {
max:
(shortrest ? this.moveData.shortRest.nrChoices : 0) +
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].shortMoves
},
longRest: {
max:
(!shortrest ? this.moveData.longRest.nrChoices : 0) +
actor.system.bonuses.rest[`${shortrest ? 'short' : 'long'}Rest`].longMoves
}
};
this.nrChoices.total = { max: this.nrChoices.shortRest.max + this.nrChoices.longRest.max };
}
get title() {
@ -17,8 +31,8 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'views', 'downtime'],
position: { width: 680, height: 'auto' },
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'downtime'],
position: { width: 'auto', height: 'auto' },
actions: {
selectMove: this.selectMove,
takeDowntime: this.takeDowntime
@ -29,7 +43,7 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
static PARTS = {
application: {
id: 'downtime',
template: 'systems/daggerheart/templates/dialogs/downtime.hbs'
template: 'systems/daggerheart/templates/dialogs/downtime/downtime.hbs'
}
};
@ -37,46 +51,83 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
super._attachPartListeners(partId, htmlElement, options);
htmlElement
.querySelectorAll('.activity-image')
.querySelectorAll('.activity-container')
.forEach(element => element.addEventListener('contextmenu', this.deselectMove.bind(this)));
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.title = game.i18n.localize(
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
);
context.selectedActivity = this.selectedActivity;
context.moveData = this.moveData;
context.nrCurrentChoices = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
context.disabledDowntime = context.nrCurrentChoices < context.moveData.nrChoices;
context.nrCurrentChoices = Object.values(this.moveData).reduce((acc, category) => {
acc += Object.values(category.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
return acc;
}, 0);
context.nrChoices = {
...this.nrChoices,
shortRest: {
...this.nrChoices.shortRest,
current: Object.values(this.moveData.shortRest.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0)
},
longRest: {
...this.nrChoices.longRest,
current: Object.values(this.moveData.longRest.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0)
}
};
context.nrChoices.total = {
...this.nrChoices.total,
current: context.nrChoices.shortRest.current + context.nrChoices.longRest.current
};
context.shortRestMoves = this.nrChoices.shortRest.max > 0 ? this.moveData.shortRest : null;
context.longRestMoves = this.nrChoices.longRest.max > 0 ? this.moveData.longRest : null;
context.disabledDowntime = context.nrChoices.total.current < context.nrChoices.total.max;
return context;
}
static selectMove(_, button) {
const nrSelected = Object.values(this.moveData.moves).reduce((acc, x) => acc + (x.selected ?? 0), 0);
if (nrSelected === this.moveData.nrChoices) {
static selectMove(_, target) {
const nrSelected = Object.values(this.moveData[target.dataset.category].moves).reduce(
(acc, x) => acc + (x.selected ?? 0),
0
);
if (nrSelected === this.nrChoices[target.dataset.category].max) {
ui.notifications.error(game.i18n.localize('DAGGERHEART.UI.Notifications.noMoreMoves'));
return;
}
const move = button.dataset.move;
this.moveData.moves[move].selected = this.moveData.moves[move].selected
? this.moveData.moves[move].selected + 1
const move = target.dataset.move;
this.moveData[target.dataset.category].moves[move].selected = this.moveData[target.dataset.category].moves[move]
.selected
? this.moveData[target.dataset.category].moves[move].selected + 1
: 1;
this.render();
}
deselectMove(event) {
const move = event.currentTarget.dataset.move;
this.moveData.moves[move].selected = this.moveData.moves[move].selected
? this.moveData.moves[move].selected - 1
const button = event.target.closest('.activity-container');
const move = button.dataset.move;
this.moveData[button.dataset.category].moves[move].selected = this.moveData[button.dataset.category].moves[move]
.selected
? this.moveData[button.dataset.category].moves[move].selected - 1
: 0;
this.render();
}
static async takeDowntime() {
const moves = Object.values(this.moveData.moves).filter(x => x.selected);
const moves = Object.values(this.moveData).flatMap(category => {
return Object.values(category.moves)
.filter(x => x.selected)
.flatMap(move => [...Array(move.selected).keys()].map(_ => move));
});
const cls = getDocumentClass('ChatMessage');
const msg = new cls({

View file

@ -67,7 +67,7 @@ export default class ResourceDiceDialog extends HandlebarsApplicationMixin(Appli
static async rerollDice() {
const max = itemAbleRollParse(this.item.system.resource.max, this.actor, this.item);
const diceFormula = `${max}d${this.item.system.resource.dieFaces}`;
const diceFormula = `${max}${this.item.system.resource.dieFaces}`;
const roll = await new Roll(diceFormula).evaluate();
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
this.rollValues = roll.terms[0].results.map(x => ({ value: x.result, used: false }));

View file

@ -4,13 +4,14 @@ import DHActionConfig from '../../sheets-configs/action-config.mjs';
const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class DhSettingsActionView extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(resolve, reject, title, name, img, description, actions) {
constructor(resolve, reject, title, name, icon, img, description, actions) {
super({});
this.resolve = resolve;
this.reject = reject;
this.viewTitle = title;
this.name = name;
this.icon = icon;
this.img = img;
this.description = description;
this.actions = actions;
@ -23,7 +24,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'setting', 'dh-style'],
position: { width: '400', height: 'auto' },
position: { width: 440, height: 'auto' },
actions: {
editImage: this.onEditImage,
addItem: this.addItem,
@ -46,6 +47,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.name = this.name;
context.icon = this.icon;
context.img = this.img;
context.description = this.description;
context.enrichedDescription = await foundry.applications.ux.TextEditor.enrichHTML(context.description);
@ -55,8 +57,9 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
}
static async updateData(event, element, formData) {
const { name, img, description } = foundry.utils.expandObject(formData.object);
const { name, icon, description } = foundry.utils.expandObject(formData.object);
this.name = name;
this.icon = icon;
this.description = description;
this.render();
@ -65,6 +68,7 @@ export default class DhSettingsActionView extends HandlebarsApplicationMixin(App
static async saveForm(event) {
this.resolve({
name: this.name,
icon: this.icon,
img: this.img,
description: this.description,
actions: this.actions

View file

@ -76,6 +76,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
reject,
game.i18n.localize('DAGGERHEART.SETTINGS.Homebrew.downtimeMoves'),
move.name,
move.icon,
move.img,
move.description,
move.actions
@ -87,6 +88,7 @@ export default class DhHomebrewSettings extends HandlebarsApplicationMixin(Appli
await this.settings.updateSource({
[`restMoves.${type}.moves.${id}`]: {
name: data.name,
icon: data.icon,
img: data.img,
description: data.description
}

View file

@ -88,6 +88,9 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
element.value = `system.${item.value}`;
},
click: e => e.fetch(),
customize: function (_input, _inputRect, container) {
container.style.zIndex = foundry.applications.api.ApplicationV2._maxZ;
},
minLength: 0
});
});

View file

@ -28,7 +28,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
useAction: this.useAction,
toggleResourceDice: this.toggleResourceDice,
handleResourceDice: this.handleResourceDice,
toChat: this.toChat
toChat: this.toChat,
useDowntime: this.useDowntime
},
window: {
resizable: true
@ -752,6 +753,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
}
}
static useDowntime(_, button) {
new game.system.api.applications.dialogs.Downtime(this.document, button.dataset.type === 'shortRest').render(
true
);
}
async _onDragStart(event) {
const item = this.getItem(event);

View file

@ -23,7 +23,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
actions: {
openSettings: DHBaseActorSheet.#openSettings
},
dragDrop: []
dragDrop: [{ dragSelector: '.inventory-item[data-type="attack"]', dropSelector: null }]
};
/**@type {typeof DHBaseActorSettings}*/
@ -49,4 +49,27 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
static async #openSettings() {
await this.settingSheet.render({ force: true });
}
/* -------------------------------------------- */
/* Application Drag/Drop */
/* -------------------------------------------- */
/**
* On dragStart on the item.
* @param {DragEvent} event - The drag event
*/
async _onDragStart(event) {
const attackItem = event.currentTarget.closest('.inventory-item[data-type="attack"]');
if (attackItem) {
const attackData = {
type: 'Attack',
actorUuid: this.document.uuid,
img: this.document.system.attack.img,
fromInternal: true
};
event.dataTransfer.setData('text/plain', JSON.stringify(attackData));
event.dataTransfer.setDragImage(attackItem.querySelector('img'), 60, 0);
}
}
}

View file

@ -30,7 +30,8 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
},
dragDrop: [
{ dragSelector: null, dropSelector: '.tab.features .drop-section' },
{ dragSelector: '.feature-item', dropSelector: null }
{ dragSelector: '.feature-item', dropSelector: null },
{ dragSelector: '.action-item', dropSelector: null }
]
};
@ -258,6 +259,23 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
const featureData = { type: 'Item', data: { ...feature.toObject(), _id: null }, fromInternal: true };
event.dataTransfer.setData('text/plain', JSON.stringify(featureData));
event.dataTransfer.setDragImage(featureItem.querySelector('img'), 60, 0);
} else {
const actionItem = event.currentTarget.closest('.action-item');
if (actionItem) {
const action = this.document.system.actions[actionItem.dataset.index];
if (!action) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.actionIsMissing'));
return;
}
const actionData = {
type: 'Action',
data: { ...action.toObject(), id: action.id, itemUuid: this.document.uuid },
fromInternal: true
};
event.dataTransfer.setData('text/plain', JSON.stringify(actionData));
event.dataTransfer.setDragImage(actionItem.querySelector('img'), 60, 0);
}
}
}

View file

@ -63,22 +63,8 @@ export default class AncestrySheet extends DHHeritageSheet {
event.stopPropagation();
const target = button.closest('.feature-item');
const feature = this.document.system[`${target.dataset.type}Feature`];
const featureExists = feature && Object.keys(feature).length > 0;
if (featureExists) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
type: game.i18n.localize(`TYPES.Item.feature`),
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
});
if (!confirmed) return;
}
if (featureExists && target.dataset.type === 'primary') await feature.update({ 'system.primary': null });
if (feature) await feature.update({ 'system.subType': null });
await this.document.update({
'system.features': this.document.system.features.filter(x => x && x.uuid !== feature.uuid).map(x => x.uuid)
});
@ -94,15 +80,18 @@ export default class AncestrySheet extends DHHeritageSheet {
*/
async _onDrop(event) {
event.stopPropagation();
event.preventDefault();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const subType = event.target.closest('.primary-feature') ? 'primary' : 'secondary';
await item.update({ 'system.subType': subType });
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes[subType]) {
const error = subType === 'primary' ? 'featureNotPrimary' : 'featureNotSecondary';
ui.notifications.warn(game.i18n.localize(`DAGGERHEART.UI.Notifications.${error}`));
return;
}
await item.update({ 'system.subType': subType });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});

View file

@ -78,6 +78,7 @@ export default class ClassSheet extends DHBaseItemSheet {
/* -------------------------------------------- */
async _onDrop(event) {
event.stopPropagation();
const data = TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
const target = event.target.closest('fieldset.drop-section');
@ -87,12 +88,24 @@ export default class ClassSheet extends DHBaseItemSheet {
});
} else if (item.type === 'feature') {
if (target.classList.contains('hope-feature')) {
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.hope) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotHope'));
return;
}
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.hope });
await this.document.update({
'system.hopeFeatures': [...this.document.system.hopeFeatures.map(x => x.uuid), item.uuid]
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
} else if (target.classList.contains('class-feature')) {
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.class) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotClass'));
return;
}
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.class });
await this.document.update({
'system.classFeatures': [...this.document.system.classFeatures.map(x => x.uuid), item.uuid]
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
}
} else if (item.type === 'weapon') {
@ -177,28 +190,25 @@ export default class ClassSheet extends DHBaseItemSheet {
doc.sheet.render({ force: true });
}
getActionPath(type) {
return type === 'hope' ? 'hopeFeatures' : 'classFeatures';
}
static async addFeature(_, target) {
const actionPath = this.getActionPath(target.dataset.type);
const feature = await game.items.documentClass.create({
type: 'feature',
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') })
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }),
system: {
subType:
target.dataset.type === 'hope'
? CONFIG.DH.ITEM.featureSubTypes.hope
: CONFIG.DH.ITEM.featureSubTypes.class
}
});
await this.document.update({
[`system.${actionPath}`]: [
...this.document.system[actionPath].filter(x => x).map(x => x.uuid),
feature.uuid
]
[`system.features`]: [...this.document.system.features.filter(x => x).map(x => x.uuid), feature.uuid]
});
}
static async editFeature(_, button) {
const target = button.closest('.feature-item');
const actionPath = this.getActionPath(button.dataset.type);
const feature = this.document.system[actionPath].find(x => x?.id === target.dataset.featureId);
const feature = this.document.system.features.find(x => x?.id === target.dataset.featureId);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
@ -210,10 +220,16 @@ export default class ClassSheet extends DHBaseItemSheet {
static async deleteFeature(event, button) {
event.stopPropagation();
const target = button.closest('.feature-item');
const actionPath = this.getActionPath(button.dataset.type);
const feature = this.document.system.features.find(
feature => feature && feature.id === target.dataset.featureId
);
if (feature) {
await feature.update({ 'system.subType': null });
}
await this.document.update({
[`system.${actionPath}`]: this.document.system[actionPath]
[`system.features`]: this.document.system.features
.filter(feature => feature && feature.id !== target.dataset.featureId)
.map(x => x.uuid)
});

View file

@ -40,28 +40,46 @@ export default class SubclassSheet extends DHBaseItemSheet {
static async addFeature(_, target) {
const feature = await game.items.documentClass.create({
type: 'feature',
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') })
name: game.i18n.format('DOCUMENT.New', { type: game.i18n.localize('TYPES.Item.feature') }),
system: {
subType:
target.dataset.type === 'foundation'
? CONFIG.DH.ITEM.featureSubTypes.foundation
: target.dataset.type === 'specialization'
? CONFIG.DH.ITEM.featureSubTypes.specialization
: CONFIG.DH.ITEM.featureSubTypes.mastery
}
});
await this.document.update({
[`system.${target.dataset.type}`]: feature.uuid
[`system.features`]: [...this.document.system.features.map(x => x.uuid), feature.uuid]
});
}
static async editFeature(_, button) {
const feature = this.document.system[button.dataset.type];
const feature = this.document.system.features.find(x => x.id === button.dataset.feature);
if (!feature) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
return;
}
if (feature) {
await feature.update({ 'system.subType': null });
}
feature.sheet.render(true);
}
static async deleteFeature(event, button) {
static async deleteFeature(event, target) {
event.stopPropagation();
const feature = this.document.system.features.find(feature => feature.id === target.dataset.feature);
if (feature) {
await feature.update({ 'system.subType': null });
}
await this.document.update({
[`system.${button.dataset.type}`]: null
[`system.features`]: this.document.system.features
.filter(feature => feature && feature.id !== target.dataset.feature)
.map(x => x.uuid)
});
}
@ -82,18 +100,45 @@ export default class SubclassSheet extends DHBaseItemSheet {
}
async _onDrop(event) {
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
if (data.fromInternal) return;
const item = await fromUuid(data.uuid);
if (item?.type === 'feature') {
const dropSection = event.target.closest('.drop-section');
if (this.document.system[dropSection.dataset.type]) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.notifications.featureIsFull'));
return;
}
const target = event.target.closest('fieldset.drop-section');
if (item.type === 'feature') {
if (target.dataset.type === 'foundation') {
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.foundation) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotFoundation'));
return;
}
await this.document.update({ [`system.${dropSection.dataset.type}`]: item.uuid });
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.foundation });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
} else if (target.dataset.type === 'specialization') {
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.specialization) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotSpecialization'));
return;
}
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.specialization });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
} else if (target.dataset.type === 'mastery') {
if (item.system.subType && item.system.subType !== CONFIG.DH.ITEM.featureSubTypes.mastery) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureNotMastery'));
return;
}
await item.update({ 'system.subType': CONFIG.DH.ITEM.featureSubTypes.mastery });
await this.document.update({
'system.features': [...this.document.system.features.map(x => x.uuid), item.uuid]
});
}
}
}
}

View file

@ -2,3 +2,4 @@ export { default as DhChatLog } from './chatLog.mjs';
export { default as DhCombatTracker } from './combatTracker.mjs';
export * as DhCountdowns from './countdowns.mjs';
export { default as DhFearTracker } from './fearTracker.mjs';
export { default as DhHotbar } from './hotbar.mjs';

View file

@ -0,0 +1,129 @@
export default class DhHotbar extends foundry.applications.ui.Hotbar {
constructor(options) {
super(options);
this.setupHooks();
}
static async useItem(uuid) {
const item = await fromUuid(uuid);
if (!item) {
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
format: {
name: game.i18n.localize('Document'),
identifier: uuid
}
});
}
await item.use({});
}
static async useAction(itemUuid, actionId) {
const item = await foundry.utils.fromUuid(itemUuid);
if (!item) {
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
format: {
name: game.i18n.localize('Document'),
identifier: itemUuid
}
});
}
const action = item.system.actions.find(x => x.id === actionId);
if (!action) {
return ui.notifications.warn('DAGGERHEART.UI.Notifications.actionIsMissing');
}
await action.use({});
}
static async useAttack(actorUuid) {
const actor = await foundry.utils.fromUuid(actorUuid);
if (!actor) {
return ui.notifications.warn('WARNING.ObjectDoesNotExist', {
format: {
name: game.i18n.localize('Document'),
identifier: actorUuid
}
});
}
const attack = actor.system.attack;
if (!attack) {
return ui.notifications.warn('DAGGERHEART.UI.Notifications.attackIsMissing');
}
await attack.use({});
}
setupHooks() {
Hooks.on('hotbarDrop', (bar, data, slot) => {
if (data.type === 'Item') {
const item = foundry.utils.fromUuidSync(data.uuid);
if (item.uuid.startsWith('Compendium') || !item.isOwned || !item.isOwner) return true;
switch (item.type) {
case 'ancestry':
case 'community':
case 'class':
case 'subclass':
return true;
default:
this.createItemMacro(item, slot);
return false;
}
} else if (data.type === 'Action') {
const item = foundry.utils.fromUuidSync(data.data.itemUuid);
if (item.uuid.startsWith('Compendium')) return true;
if (!item.isOwned || !item.isOwner) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedActionMacro'));
return false;
}
this.createActionMacro(data, slot);
return false;
} else if (data.type === 'Attack') {
const actor = foundry.utils.fromUuidSync(data.actorUuid);
if (actor.uuid.startsWith('Compendium')) return true;
if (!actor.isOwner) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.unownedAttackMacro'));
return false;
}
this.createAttackMacro(data, slot);
return false;
}
});
}
async createItemMacro(data, slot) {
const macro = await Macro.implementation.create({
name: `${game.i18n.localize('Display')} ${name}`,
type: CONST.MACRO_TYPES.SCRIPT,
img: data.img,
command: `await game.system.api.applications.ui.DhHotbar.useItem("${data.uuid}");`
});
await game.user.assignHotbarMacro(macro, slot);
}
async createActionMacro(data, slot) {
const macro = await Macro.implementation.create({
name: `${game.i18n.localize('Display')} ${name}`,
type: CONST.MACRO_TYPES.SCRIPT,
img: data.data.img,
command: `await game.system.api.applications.ui.DhHotbar.useAction("${data.data.itemUuid}", "${data.data.id}");`
});
await game.user.assignHotbarMacro(macro, slot);
}
async createAttackMacro(data, slot) {
const macro = await Macro.implementation.create({
name: `${game.i18n.localize('Display')} ${name}`,
type: CONST.MACRO_TYPES.SCRIPT,
img: data.img,
command: `await game.system.api.applications.ui.DhHotbar.useAttack("${data.actorUuid}");`
});
await game.user.assignHotbarMacro(macro, slot);
}
}

View file

@ -411,7 +411,7 @@ export const levelupData = {
};
export const subclassFeatureLabels = {
1: 'DAGGERHEART.ITEMS.DomainCard.foundation',
1: 'DAGGERHEART.ITEMS.DomainCard.foundationTitle',
2: 'DAGGERHEART.ITEMS.DomainCard.specializationTitle',
3: 'DAGGERHEART.ITEMS.DomainCard.masteryTitle'
};

View file

@ -135,6 +135,7 @@ export const defaultRestOptions = {
tendToWounds: {
id: 'tendToWounds',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.name'),
icon: 'fa-solid fa-bandage',
img: 'icons/magic/life/cross-worn-green.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.tendToWounds.description'),
actions: [
@ -158,6 +159,7 @@ export const defaultRestOptions = {
clearStress: {
id: 'clearStress',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.name'),
icon: 'fa-regular fa-face-surprise',
img: 'icons/magic/perception/eye-ringed-green.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.clearStress.description'),
actions: [
@ -181,6 +183,7 @@ export const defaultRestOptions = {
repairArmor: {
id: 'repairArmor',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
icon: 'fa-solid fa-hammer',
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.description'),
actions: []
@ -188,6 +191,7 @@ export const defaultRestOptions = {
prepare: {
id: 'prepare',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.name'),
icon: 'fa-solid fa-dumbbell',
img: 'icons/skills/trades/academics-merchant-scribe.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.prepare.description'),
actions: []
@ -197,6 +201,7 @@ export const defaultRestOptions = {
tendToWounds: {
id: 'tendToWounds',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.name'),
icon: 'fa-solid fa-bandage',
img: 'icons/magic/life/cross-worn-green.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.tendToWounds.description'),
actions: []
@ -204,6 +209,7 @@ export const defaultRestOptions = {
clearStress: {
id: 'clearStress',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.name'),
icon: 'fa-regular fa-face-surprise',
img: 'icons/magic/perception/eye-ringed-green.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.clearStress.description'),
actions: []
@ -211,6 +217,7 @@ export const defaultRestOptions = {
repairArmor: {
id: 'repairArmor',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.name'),
icon: 'fa-solid fa-hammer',
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.repairArmor.description'),
actions: []
@ -218,6 +225,7 @@ export const defaultRestOptions = {
prepare: {
id: 'prepare',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.name'),
icon: 'fa-solid fa-dumbbell',
img: 'icons/skills/trades/academics-merchant-scribe.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.prepare.description'),
actions: []
@ -225,19 +233,12 @@ export const defaultRestOptions = {
workOnAProject: {
id: 'workOnAProject',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.name'),
icon: 'fa-solid fa-diagram-project',
img: 'icons/skills/social/thumbsup-approval-like.webp',
description: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.longRest.workOnAProject.description'),
actions: []
}
}),
custom: {
id: 'customActivity',
name: '',
img: 'icons/skills/trades/academics-investigation-puzzles.webp',
description: '',
namePlaceholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.namePlaceholder',
placeholder: 'DAGGERHEART.APPLICATIONS.Downtime.custom.placeholder'
}
})
};
export const deathMoves = {

View file

@ -439,7 +439,7 @@ export const weaponFeatures = {
{
key: 'system.bonuses.damage.primaryWeapon.bonus',
mode: 2,
value: '@system.levelData.levels.current'
value: '@system.levelData.level.current'
}
]
}
@ -1261,15 +1261,6 @@ export const weaponFeatures = {
timebending: {
label: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.timebending.description'
},
versatile: {
label: 'DAGGERHEART.CONFIG.WeaponFeature.versatile.name',
description: 'DAGGERHEART.CONFIG.WeaponFeature.versatile.description'
// versatile: {
// characterTrait: '',
// range: '',
// damage: ''
// }
}
};
@ -1322,7 +1313,12 @@ export const featureTypes = {
export const featureSubTypes = {
primary: 'primary',
secondary: 'secondary'
secondary: 'secondary',
hope: 'hope',
class: 'class',
foundation: 'foundation',
specialization: 'specialization',
mastery: 'mastery'
};
export const actionTypes = {

View file

@ -87,6 +87,8 @@ export default class DhCharacter extends BaseDataActor {
value: new ForeignDocumentUUIDField({ type: 'Item', nullable: true }),
subclass: new ForeignDocumentUUIDField({ type: 'Item', nullable: true })
}),
advantageSources: new fields.ArrayField(new fields.StringField()),
disadvantageSources: new fields.ArrayField(new fields.StringField()),
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
roll: new fields.SchemaField({
@ -102,7 +104,7 @@ export default class DhCharacter extends BaseDataActor {
physical: bonusField('DAGGERHEART.GENERAL.Damage.physicalDamage'),
magical: bonusField('DAGGERHEART.GENERAL.Damage.magicalDamage'),
primaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon'),
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.primaryWeapon')
secondaryWeapon: bonusField('DAGGERHEART.GENERAL.Damage.secondaryWeapon')
}),
healing: bonusField('DAGGERHEART.GENERAL.Healing.healingAmount'),
range: new fields.SchemaField({
@ -121,6 +123,47 @@ export default class DhCharacter extends BaseDataActor {
initial: 0,
label: 'DAGGERHEART.GENERAL.Range.other'
})
}),
rally: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.CLASS.Feature.rallyDice'
}),
rest: new fields.SchemaField({
shortRest: new fields.SchemaField({
shortMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.shortRestMoves.hint'
}),
longMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.shortRest.longRestMoves.hint'
})
}),
longRest: new fields.SchemaField({
shortMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.shortRestMoves.hint'
}),
longMoves: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0,
label: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.label',
hint: 'DAGGERHEART.GENERAL.Bonuses.rest.longRest.longRestMoves.hint'
})
})
})
}),
companion: new ForeignDocumentUUIDField({ type: 'Actor', nullable: true, initial: null }),
@ -244,23 +287,23 @@ export default class DhCharacter extends BaseDataActor {
features = [];
for (let item of this.parent.items) {
if (item.system.type === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.ancestry.id) {
ancestryFeatures.push(item);
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.community.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.community.id) {
communityFeatures.push(item);
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.class.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.class.id) {
classFeatures.push(item);
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.subclass.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.subclass.id) {
const subclassState = this.class.subclass.system.featureState;
const identifier = item.system.identifier;
const subType = item.system.subType;
if (
identifier === 'foundationFeature' ||
(identifier === 'specializationFeature' && subclassState >= 2) ||
(identifier === 'masterFeature' && subclassState >= 3)
subType === CONFIG.DH.ITEM.featureSubTypes.foundation ||
(subType === CONFIG.DH.ITEM.featureSubTypes.specialization && subclassState >= 2) ||
(subType === CONFIG.DH.ITEM.featureSubTypes.mastery && subclassState >= 3)
) {
subclassFeatures.push(item);
}
} else if (item.system.type === CONFIG.DH.ITEM.featureTypes.companion.id) {
} else if (item.system.originItemType === CONFIG.DH.ITEM.featureTypes.companion.id) {
companionFeatures.push(item);
} else if (item.type === 'feature' && !item.system.type) {
features.push(item);

View file

@ -135,8 +135,8 @@ export const registerCountdownHooks = () => {
if (application) {
foundry.applications.instances.get(application)?.render();
} else {
foundry.applications.instances.get('narrative-countdowns').render();
foundry.applications.instances.get('encounter-countdowns').render();
foundry.applications.instances.get('narrative-countdowns')?.render();
foundry.applications.instances.get('encounter-countdowns')?.render();
}
return false;

View file

@ -25,8 +25,11 @@ const stressDamageReductionRule = localizationPath =>
const bonusField = label =>
new fields.SchemaField({
bonus: new fields.NumberField({ integer: true, initial: 0, label }),
dice: new fields.ArrayField(new fields.StringField())
bonus: new fields.NumberField({ integer: true, initial: 0, label: `${game.i18n.localize(label)} Value` }),
dice: new fields.ArrayField(
new fields.StringField(),
{ label: `${game.i18n.localize(label)} Dice` }
)
});
export { attributeField, resourceField, stressDamageReductionRule, bonusField };

View file

@ -53,11 +53,14 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
}),
diceStates: new fields.TypedObjectField(
new fields.SchemaField({
value: new fields.NumberField({ integer: true, nullable: true, initial: null }),
value: new fields.NumberField({ integer: true, initial: 1, min: 1 }),
used: new fields.BooleanField({ initial: false })
})
),
dieFaces: new fields.StringField({ initial: '4' })
dieFaces: new fields.StringField({
choices: CONFIG.DH.GENERAL.diceTypes,
initial: CONFIG.DH.GENERAL.diceTypes.d4
})
},
{ nullable: true, initial: null }
);

View file

@ -69,6 +69,7 @@ export default class DHBeastform extends BaseDataItem {
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
await beastformEffect.updateSource({
changes: [...beastformEffect.changes, { key: 'system.advantageSources', mode: 2, value: this.advantageOn }],
system: {
characterTokenData: {
tokenImg: this.parent.parent.prototypeToken.texture.src,

View file

@ -27,8 +27,7 @@ export default class DHClass extends BaseDataItem {
label: 'DAGGERHEART.GENERAL.hitPoints.plural'
}),
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
hopeFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
classFeatures: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
subclasses: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
inventory: new fields.SchemaField({
take: new ForeignDocumentUUIDArrayField({ type: 'Item', required: false }),
@ -52,12 +51,18 @@ export default class DHClass extends BaseDataItem {
};
}
get hopeFeature() {
return this.hopeFeatures.length > 0 ? this.hopeFeatures[0] : null;
get hopeFeatures() {
return (
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.hope) ??
(this.features.filter(x => !x).length > 0 ? {} : null)
);
}
get features() {
return [...this.hopeFeatures.filter(x => x), ...this.classFeatures.filter(x => x)];
get classFeatures() {
return (
this.features.filter(x => x?.system?.subType === CONFIG.DH.ITEM.featureSubTypes.class) ??
(this.features.filter(x => !x).length > 0 ? {} : null)
);
}
async _preCreate(data, options, user) {

View file

@ -29,7 +29,6 @@ export default class DHDomainCard extends BaseDataItem {
required: true,
initial: CONFIG.DH.DOMAIN.cardTypes.ability.id
}),
foundation: new fields.BooleanField({ initial: false }),
inVault: new fields.BooleanField({ initial: false }),
actions: new fields.ArrayField(new ActionField())
};

View file

@ -1,4 +1,4 @@
import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import ForeignDocumentUUIDArrayField from '../fields/foreignDocumentUUIDArrayField.mjs';
import BaseDataItem from './base.mjs';
export default class DHSubclass extends BaseDataItem {
@ -22,20 +22,22 @@ export default class DHSubclass extends BaseDataItem {
nullable: true,
initial: null
}),
foundationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
specializationFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
masteryFeature: new ForeignDocumentUUIDField({ type: 'Item' }),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
featureState: new fields.NumberField({ required: true, initial: 1, min: 1 }),
isMulticlass: new fields.BooleanField({ initial: false })
};
}
get features() {
return [
{ ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' },
{ ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' },
{ ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' }
];
get foundationFeatures() {
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.foundation);
}
get specializationFeatures() {
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.specialization);
}
get masteryFeatures() {
return this.features.filter(x => x.system.subType === CONFIG.DH.ITEM.featureSubTypes.mastery);
}
async _preCreate(data, options, user) {

View file

@ -54,6 +54,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
moves: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
icon: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],
@ -70,6 +71,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
moves: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({ required: true }),
icon: new fields.StringField({ required: true }),
img: new fields.FilePathField({
initial: 'icons/magic/life/cross-worn-green.webp',
categories: ['IMAGE'],

View file

@ -39,11 +39,13 @@ export default class D20Roll extends DHRoll {
}
get hasAdvantage() {
return this.options.roll.advantage === this.constructor.ADV_MODE.ADVANTAGE;
const adv = this.options.roll.advantage.type ?? this.options.roll.advantage;
return adv === this.constructor.ADV_MODE.ADVANTAGE;
}
get hasDisadvantage() {
return this.options.roll.advantage === this.constructor.ADV_MODE.DISADVANTAGE;
const adv = this.options.roll.advantage.type ?? this.options.roll.advantage;
return adv === this.constructor.ADV_MODE.DISADVANTAGE;
}
static applyKeybindings(config) {
@ -91,7 +93,7 @@ export default class D20Roll extends DHRoll {
configureModifiers() {
this.applyAdvantage();
this.baseTerms = foundry.utils.deepClone(this.terms);
this.baseTerms = foundry.utils.deepClone(this.dice);
this.options.roll.modifiers = this.applyBaseBonus();

View file

@ -4,9 +4,12 @@ import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
export default class DualityRoll extends D20Roll {
_advantageFaces = 6;
_advantageNumber = 1;
_rallyIndex;
constructor(formula, data = {}, options = {}) {
super(formula, data, options);
this.rallyChoices = this.setRallyChoices();
}
static messageType = 'dualityRoll';
@ -51,6 +54,35 @@ export default class DualityRoll extends D20Roll {
this._advantageFaces = this.getFaces(faces);
}
get advantageNumber() {
return this._advantageNumber;
}
set advantageNumber(value) {
this._advantageNumber = Number(value);
}
setRallyChoices() {
return this.data?.parent?.effects.reduce((a,c) => {
const change = c.changes.find(ch => ch.key === 'system.bonuses.rally');
if(change) a.push({ value: c.id, label: change.value });
return a;
}, []);
}
get dRally() {
if(!this.rallyFaces) return null;
if(this.hasDisadvantage || this.hasAdvantage)
return this.dice[3];
else
return this.dice[2];
}
get rallyFaces() {
const rallyChoice = this.rallyChoices?.find(r => r.value === this._rallyIndex)?.label;
return rallyChoice ? this.getFaces(rallyChoice) : null;
}
get isCritical() {
if (!this.dHope._evaluated || !this.dFear._evaluated) return;
return this.dHope.total === this.dFear.total;
@ -66,10 +98,6 @@ export default class DualityRoll extends D20Roll {
return this.dHope.total < this.dFear.total;
}
get hasBarRally() {
return null;
}
get totalLabel() {
const label = this.withHope
? 'DAGGERHEART.GENERAL.hope'
@ -98,24 +126,20 @@ export default class DualityRoll extends D20Roll {
}
applyAdvantage() {
const dieFaces = this.advantageFaces,
bardRallyFaces = this.hasBarRally,
advDie = new foundry.dice.terms.Die({ faces: dieFaces });
if (this.hasAdvantage || this.hasDisadvantage || bardRallyFaces)
this.terms.push(new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }));
if (bardRallyFaces) {
const rallyDie = new foundry.dice.terms.Die({ faces: bardRallyFaces });
if (this.hasAdvantage) {
this.terms.push(
new foundry.dice.terms.PoolTerm({
terms: [advDie.formula, rallyDie.formula],
modifiers: ['kh']
})
);
} else if (this.hasDisadvantage) {
this.terms.push(advDie, new foundry.dice.terms.OperatorTerm({ operator: '+' }), rallyDie);
}
} else if (this.hasAdvantage || this.hasDisadvantage) this.terms.push(advDie);
if (this.hasAdvantage || this.hasDisadvantage) {
const dieFaces = this.advantageFaces,
advDie = new foundry.dice.terms.Die({ faces: dieFaces, number: this.advantageNumber });
if(this.advantageNumber > 1) advDie.modifiers = ['kh'];
this.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
advDie
);
}
if(this.rallyFaces)
this.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
new foundry.dice.terms.Die({ faces: this.rallyFaces })
);
}
applyBaseBonus() {
@ -138,6 +162,7 @@ export default class DualityRoll extends D20Roll {
static postEvaluate(roll, config = {}) {
super.postEvaluate(roll, config);
config.roll.hope = {
dice: roll.dHope.denomination,
value: roll.dHope.total
@ -146,12 +171,19 @@ export default class DualityRoll extends D20Roll {
dice: roll.dFear.denomination,
value: roll.dFear.total
};
config.roll.rally = {
dice: roll.dRally?.denomination,
value: roll.dRally?.total
};
config.roll.result = {
duality: roll.withHope ? 1 : roll.withFear ? -1 : 0,
total: roll.dHope.total + roll.dFear.total,
label: roll.totalLabel
};
if(roll._rallyIndex && roll.data?.parent)
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type);
}
}

View file

@ -55,10 +55,24 @@ export default class DhActiveEffect extends ActiveEffect {
}
static applyField(model, change, field) {
change.value = itemAbleRollParse(change.value, model, change.effect.parent);
change.value = this.effectSafeEval(itemAbleRollParse(change.value, model, change.effect.parent));
super.applyField(model, change, field);
}
/* Altered Foundry safeEval to allow non-numeric returns */
static effectSafeEval(expression) {
let result;
try {
// eslint-disable-next-line no-new-func
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
result = evl(Roll.MATH_PROXY);
} catch (err) {
return expression;
}
return result;
}
async toChat(origin) {
const cls = getDocumentClass('ChatMessage');
const systemData = {

View file

@ -16,7 +16,7 @@ export default class DHToken extends TokenDocument {
});
bars.sort((a, b) => a.label.compare(b.label));
const invalidAttributes = ['gold', 'levelData', 'rules.damageReduction.maxArmorMarked.value'];
const invalidAttributes = ['gold', 'levelData', 'actions', 'rules.damageReduction.maxArmorMarked.value'];
const values = attributes.value.reduce((acc, v) => {
const a = v.join('.');
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
@ -32,4 +32,26 @@ export default class DHToken extends TokenDocument {
return bars.concat(values);
}
static _getTrackedAttributesFromSchema(schema, _path = []) {
const attributes = { bar: [], value: [] };
for (const [name, field] of Object.entries(schema.fields)) {
const p = _path.concat([name]);
if (field instanceof foundry.data.fields.NumberField) attributes.value.push(p);
if (field instanceof foundry.data.fields.ArrayField) attributes.value.push(p);
const isSchema = field instanceof foundry.data.fields.SchemaField;
const isModel = field instanceof foundry.data.fields.EmbeddedDataField;
if (isSchema || isModel) {
const schema = isModel ? field.model.schema : field;
const isBar = schema.has && schema.has('value') && schema.has('max');
if (isBar) attributes.bar.push(p);
else {
const inner = this.getTrackedAttributes(schema, p);
attributes.bar.push(...inner.bar);
attributes.value.push(...inner.value);
}
}
}
return attributes;
}
}

View file

@ -21,14 +21,79 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
this.tooltip.innerHTML = html;
options.direction = this._determineItemTooltipDirection(element);
}
} else {
const shortRest = element.dataset.tooltip?.startsWith('#shortRest#');
const longRest = element.dataset.tooltip?.startsWith('#longRest#');
if (shortRest || longRest) {
const key = element.dataset.tooltip.slice(shortRest ? 11 : 10);
const downtimeOptions = shortRest
? CONFIG.DH.GENERAL.defaultRestOptions.shortRest()
: CONFIG.DH.GENERAL.defaultRestOptions.longRest();
const move = downtimeOptions[key];
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
{
move: move
}
);
this.tooltip.innerHTML = html;
options.direction = this._determineItemTooltipDirection(
element,
this.constructor.TOOLTIP_DIRECTIONS.UP
);
}
const isAdvantage = element.dataset.tooltip?.startsWith('#advantage#');
const isDisadvantage = element.dataset.tooltip?.startsWith('#disadvantage#');
if (isAdvantage || isDisadvantage) {
const actorUuid = element.dataset.tooltip.slice(isAdvantage ? 11 : 14);
const actor = await foundry.utils.fromUuid(actorUuid);
if (actor) {
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/advantage.hbs`,
{
sources: isAdvantage ? actor.system.advantageSources : actor.system.disadvantageSources
}
);
this.tooltip.innerHTML = html;
}
}
}
super.activate(element, { ...options, html: html });
}
_determineItemTooltipDirection(element) {
_determineItemTooltipDirection(element, prefered = this.constructor.TOOLTIP_DIRECTIONS.LEFT) {
const pos = element.getBoundingClientRect();
const dirs = this.constructor.TOOLTIP_DIRECTIONS;
return dirs[pos.x - this.tooltip.offsetWidth < 0 ? 'DOWN' : 'LEFT'];
switch (prefered) {
case this.constructor.TOOLTIP_DIRECTIONS.LEFT:
return dirs[
pos.x - this.tooltip.offsetWidth < 0
? this.constructor.TOOLTIP_DIRECTIONS.DOWN
: this.constructor.TOOLTIP_DIRECTIONS.LEFT
];
case this.constructor.TOOLTIP_DIRECTIONS.UP:
return dirs[
pos.y - this.tooltip.offsetHeight < 0
? this.constructor.TOOLTIP_DIRECTIONS.RIGHT
: this.constructor.TOOLTIP_DIRECTIONS.UP
];
case this.constructor.TOOLTIP_DIRECTIONS.RIGHT:
return dirs[
pos.x + this.tooltip.offsetWidth > document.body.clientWidth
? this.constructor.TOOLTIP_DIRECTIONS.DOWN
: this.constructor.TOOLTIP_DIRECTIONS.RIGHT
];
case this.constructor.TOOLTIP_DIRECTIONS.DOWN:
return dirs[
pos.y + this.tooltip.offsetHeight > document.body.clientHeight
? this.constructor.TOOLTIP_DIRECTIONS.LEFT
: this.constructor.TOOLTIP_DIRECTIONS.DOWN
];
}
}
}

View file

@ -25,6 +25,7 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/settings/components/settings-item-line.hbs',
'systems/daggerheart/templates/ui/chat/parts/damage-chat.hbs',
'systems/daggerheart/templates/ui/chat/parts/target-chat.hbs',
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs'
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
'systems/daggerheart/templates/dialogs/downtime/activities.hbs'
]);
};

View file

@ -100,6 +100,10 @@
font-size: 14px;
line-height: 17px;
}
.advantage-chip-tooltip {
pointer-events: all;
}
}
.advantage-chip {

View file

@ -1,81 +1,75 @@
@import '../../utils/spacing.less';
@import '../../utils/colors.less';
.daggerheart.views {
.theme-light .daggerheart.dh-style.views.downtime {
.downtime-container .activity-container .activity-selected-marker {
background-image: url(../assets/parchments/dh-parchment-light.png);
}
}
.daggerheart.dh-style.views.downtime {
font-family: @font-body;
.downtime-container {
.downtime-header {
margin: 0;
color: light-dark(@dark-blue, @golden);
text-align: center;
.activities-grouping {
width: 280px;
}
.activity-container {
display: flex;
align-items: center;
padding: 8px;
.activities-container {
width: 100%;
.activity-title {
flex: 1;
.activity-container {
display: flex;
align-items: center;
justify-content: space-between;
padding: 8px;
.activity-title-text {
font-size: 24px;
font-weight: bold;
}
.activity-image {
width: 80px;
position: relative;
.activity-inner-container {
display: flex;
justify-content: center;
margin-right: 8px;
border: 2px solid black;
border-radius: 50%;
cursor: pointer;
align-items: center;
gap: 4px;
.activity-select-label {
position: absolute;
top: -9px;
font-size: 14px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
color: light-dark(@beige, @dark);
background-image: url(../assets/parchments/dh-parchment-light.png);
padding: 0 8px;
line-height: 1;
font-weight: bold;
.activity-marker {
font-size: 8px;
flex: none;
color: light-dark(#18162e, #f3c267);
margin-right: 4px;
}
img {
border-radius: 50%;
}
.activity-select-section {
display: flex;
align-items: center;
gap: 4px;
&:hover,
&.selected {
filter: drop-shadow(0 0 6px gold);
.activity-icon {
min-width: 24px;
text-align: center;
}
}
}
.custom-name-input {
font-size: 24px;
.activity-selected-marker {
font-size: 14px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
color: light-dark(@dark, @beige);
background-image: url(../assets/parchments/dh-parchment-dark.png);
padding: 0 8px;
line-height: 1;
font-weight: bold;
padding: 0;
background: transparent;
color: rgb(239, 230, 216);
}
}
.activity-body {
flex: 1;
font-style: italic;
}
}
}
&.downtime {
.activity-text-area {
resize: none;
footer {
margin-top: 8px;
display: flex;
gap: 8px;
button {
flex: 1;
font-family: 'Montserrat', sans-serif;
}
}
}

View file

@ -7,12 +7,22 @@
height: 40px;
width: 100%;
.feature-tab {
border: none;
.navigation-container {
display: flex;
align-items: center;
gap: 8px;
a {
color: light-dark(@dark-blue, @golden);
font-family: @font-body;
.navigation-inner-container {
flex: 1;
.feature-tab {
border: none;
a {
color: light-dark(@dark-blue, @golden);
font-family: @font-body;
}
}
}
}
}

View file

@ -193,5 +193,14 @@
}
}
}
.character-downtime-container {
display: flex;
gap: 2px;
button {
flex: 1;
}
}
}
}

View file

@ -10,7 +10,6 @@
border-color: light-dark(@dark, @beige);
border-radius: 6px;
background-image: url('../assets/parchments/dh-parchment-dark.png');
z-index: 200;
max-height: 400px !important;
width: fit-content !important;
overflow-y: auto;

View file

@ -4,6 +4,20 @@
align-items: center;
gap: 4px;
.tooltip-title-container {
width: 100%;
display: flex;
align-items: center;
gap: 16px;
.tooltip-image {
height: 40px;
width: 40px;
border-radius: 6px;
border: 1px solid @golden;
}
}
.tooltip-title {
margin: 0;
text-align: center;

View file

@ -3,14 +3,14 @@
{{#times (rollParsed item.system.resource.max actor item numerical=true)}}
{{#with (ifThen (lookup ../diceStates this) (lookup ../diceStates this) this) as | state |}}
<div class="resource-item" data-dice="{{#if ../../this}}{{../this}}{{else}}{{state}}{{/if}}">
<input type="number" data-dtype="Number" name={{concat "diceStates." (ifThen ../../this ../this state) ".value" }} value="{{state.value}}" />
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/d" (ifThen ../../item.system.resource.dieFaces ../../item.system.resource.dieFaces ../item.system.resource.dieFaces) ".svg"}}" />
<input type="number" data-dtype="Number" name={{concat "diceStates." (ifThen ../../this ../this state) ".value" }} value="{{state.value}}" min="1" />
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" (ifThen ../../item.system.resource.dieFaces ../../item.system.resource.dieFaces ../item.system.resource.dieFaces) ".svg"}}" />
</div>
{{/with}}
{{/times}}
</div>
<footer>
<button data-action="save">{{localize 'Save'}}</button>
<button data-action="rerollDice">{{localize "DAGGERHEART.APPLICATIONS.ResourceDice.rerollDice"}}</button>
<button type="button" data-action="save">{{localize 'Save'}}</button>
<button type="button" data-action="rerollDice">{{localize "DAGGERHEART.APPLICATIONS.ResourceDice.rerollDice"}}</button>
</footer>
</section>

View file

@ -88,7 +88,7 @@
{{/if}}
{{/each}}
</fieldset>
<fieldset class="modifier-container one-column">
<fieldset class="modifier-container {{#if (eq @root.rollType 'DualityRoll')}}two-columns{{else}}one-column{{/if}}">
<legend>Modifiers</legend>
<div class="nest-inputs">
<button class="advantage-chip flex1 {{#if (eq advantage 1)}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="1">
@ -98,6 +98,9 @@
<span><i class="fa-regular fa-circle"></i></span>
{{/if}}
<span class="label">{{localize "DAGGERHEART.GENERAL.Advantage.full"}}</span>
{{#if @root.rollConfig.data.advantageSources.length}}
<span class="advantage-chip-tooltip" data-tooltip="{{concat "#advantage#" @root.rollConfig.source.actor}}"><i class="fa-solid fa-circle-info"></i></span>
{{/if}}
</button>
<button class="disadvantage-chip flex1 {{#if (eq advantage -1)}}selected{{/if}}" data-action="updateIsAdvantage" data-advantage="-1">
{{#if (eq advantage -1)}}
@ -106,14 +109,31 @@
<span><i class="fa-regular fa-circle"></i></span>
{{/if}}
<span class="label">{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}</span>
{{#if @root.rollConfig.data.disadvantageSources.length}}
<span class="advantage-chip-tooltip" data-tooltip="{{concat "#disadvantage#" @root.rollConfig.source.actor}}"><i class="fa-solid fa-circle-info"></i></span>
{{/if}}
</button>
{{#unless (eq @root.rollType 'D20Roll')}}
<select name="roll.dice.advantageFaces">
</div>
{{#unless (eq @root.rollType 'D20Roll')}}
<div class="nest-inputs">
<select name="roll.dice.advantageNumber"{{#unless advantage}} disabled{{/unless}}>
{{#times 10}}
<option value="{{add this 1}}" {{#if (eq @root.roll.advantageNumber (add this 1))}} selected{{/if}}>{{add this 1}}</option>
{{/times}}
</select>
<select name="roll.dice.advantageFaces"{{#unless advantage}} disabled{{/unless}}>
{{selectOptions diceOptions selected=@root.roll.dAdvantage.denomination}}
</select>
{{/unless}}
</div>
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="Situational Bonus">
</div>
{{/unless}}
{{#if @root.rallyDie.length}}
<span class="formula-label">{{localize "DAGGERHEART.CLASS.Feature.rallyDice"}}</span>
<select name="roll.dice._rallyIndex">
{{selectOptions @root.rallyDie blank="" selected=@root.roll._rallyIndex}}
</select>
{{/if}}
{{#if (eq @root.rollType 'DualityRoll')}}<span class="formula-label">Situational Bonus</span>{{/if}}
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="{{#if (eq @root.rollType 'DualityRoll')}}Ex: 1d6 + 5{{else}}Situational Bonus{{/if}}">
</fieldset>
{{/unless}}
<span class="formula-label"><b>Formula:</b> {{@root.formula}}</span>

View file

@ -1,24 +0,0 @@
<div>
<div class="downtime-container">
<h2 class="downtime-header">{{localize "DAGGERHEART.APPLICATIONS.Downtime.downtimeHeader" current=nrCurrentChoices max=moveData.nrChoices}}</h2>
{{#each moveData.moves as |move key|}}
<div class="activity-container">
<div class="activity-title">
<div class="activity-image {{#if this.selected}}selected{{/if}}" data-action="selectMove" data-move="{{key}}">
{{#if this.selected}}<div class="activity-select-label">{{move.selected}}</div>{{/if}}
<img src="{{move.img}}" />
</div>
<span class="activity-title-text">{{localize this.name}}</span>
</div>
<div class="activity-body">
{{localize this.description}}
</div>
</div>
{{/each}}
</div>
<footer class="flexrow">
<button type="button" data-action="close">{{localize "DAGGERHEART.Application.Cancel"}}</button>
<button type="button" data-action="takeDowntime" {{#if this.disabledDowntime}}disabled{{/if}}>{{localize "DAGGERHEART.Application.Downtime.TakeDowntime"}}</button>
</footer>
</div>

View file

@ -0,0 +1,18 @@
<fieldset class="one-column activities-grouping">
<legend>{{localize (concat "DAGGERHEART.APPLICATIONS.Downtime." category ".moves") max=nrChoices.max current=nrChoices.current}}</legend>
<div class="activities-container">
{{#each moves as |move key|}}
<a class="activity-container" data-action="selectMove" data-category="{{../category}}" data-move="{{key}}" data-tooltip="{{concat "#" ../category "#" key}}">
<div class="activity-inner-container">
<i class="{{#if move.selected}}fa-solid{{else}}fa-regular{{/if}} fa-circle activity-marker"></i>
<div class="activity-select-section">
<i class="{{move.icon}} activity-icon"></i>
<div>{{move.name}}</div>
</div>
</div>
{{#if move.selected}}<div class="activity-selected-marker">{{move.selected}}</div>{{/if}}
</a>
{{/each}}
</div>
</fieldset>

View file

@ -0,0 +1,14 @@
<div>
<header class="dialog-header">
<h1>{{title}}</h1>
</header>
<div class="downtime-container {{#if (and shortRestMoves.moves longRestMoves.moves)}}two-columns even{{else}}one-column{{/if}}">
{{#if shortRestMoves.moves}}{{> "systems/daggerheart/templates/dialogs/downtime/activities.hbs" moves=shortRestMoves.moves category='shortRest' nrChoices=nrChoices.shortRest}}{{/if}}
{{#if longRestMoves.moves}}{{> "systems/daggerheart/templates/dialogs/downtime/activities.hbs" moves=longRestMoves.moves category='longRest' nrChoices=nrChoices.longRest}}{{/if}}
</div>
<footer>
<button type="button" data-action="close">{{localize "Cancel"}}</button>
<button type="button" data-action="takeDowntime" {{#if disabledDowntime}}disabled{{/if}}>{{localize "DAGGERHEART.APPLICATIONS.Downtime.takeDowntime"}}</button>
</footer>
</div>

View file

@ -6,7 +6,7 @@
</div>
{{formGroup settingFields.schema.fields.actionPoints value=settingFields._source.actionPoints localize=true}}
s {{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
{{formGroup settingFields.schema.fields.hordeDamage value=settingFields._source.hordeDamage localize=true}}
<footer class="form-footer">
<button data-action="reset">

View file

@ -1,18 +1,17 @@
<div>
<div class="form-group">
<label>{{localize "Icon"}}</label>
<div class="form-field">
<input type="text" name="icon" value="{{icon}}" />
</div>
</div>
<fieldset>
<legend>{{localize "Description"}}</legend>
<prose-mirror name="description" value="{{description}}" toggled=true>
<prose-mirror name="description" value="{{description}}">
{{{ enrichedDescription }}}
</prose-mirror>
</fieldset>
{{!-- <fieldset>
<legend>{{localize "Actions"}} <a data-action="addItem"><i class="fa-solid fa-plus"></i></a></legend>
<div class="settings-items">
{{#each this.actions as |action index|}}
{{> "systems/daggerheart/templates/settings/components/settings-item-line.hbs" action type="actions" id=index }}
{{/each}}
</div>
</fieldset> --}}
</div>

View file

@ -116,5 +116,14 @@
{{/each}}
</div>
{{> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
{{#> 'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
<div class="character-downtime-container">
<button data-action="useDowntime" data-type="shortRest" data-tooltip="{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}">
<i class="fa-solid fa-chair"></i>
</button>
<button data-action="useDowntime" data-type="longRest" data-tooltip="{{localize "DAGGERHEART.APPLICATIONS.Downtime.longRest.title"}}">
<i class="fa-solid fa-bed"></i>
</button>
</div>
{{/'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs'}}
</header>

View file

@ -10,7 +10,7 @@
<a class="item-resource" data-action="toggleResourceDice" data-dice="{{#if ../../this}}{{../this}}{{else}}{{state}}{{/if}}">
<div class="item-dice-resource">
<label>{{ifThen state.value state.value '?'}}</label>
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/d" (ifThen ../../item.system.resource.dieFaces ../../item.system.resource.dieFaces ../item.system.resource.dieFaces) ".svg"}}" />
<img src="{{concat "systems/daggerheart/assets/icons/dice/hope/" (ifThen ../../item.system.resource.dieFaces ../../item.system.resource.dieFaces ../item.system.resource.dieFaces) ".svg"}}" />
{{#if state.used}}<i class="fa-solid fa-x"></i>{{/if}}
</div>
</a>

View file

@ -19,7 +19,7 @@
{{formGroup systemFields.resource.fields.value value=source.system.resource.value localize=true}}
{{formGroup systemFields.resource.fields.max value=source.system.resource.max localize=true}}
{{else}}
{{formGroup systemFields.resource.fields.dieFaces value=source.system.resource.dieFaces localize=true}}
{{formGroup systemFields.resource.fields.dieFaces value=source.system.resource.dieFaces localize=true blank=false}}
{{formGroup systemFields.resource.fields.max value=source.system.resource.max label="DAGGERHEART.ITEMS.FIELDS.resource.amount.label" localize=true}}
{{/if}}
</div>

View file

@ -1,11 +1,18 @@
<section class='tab-navigation'>
<line-div></line-div>
<nav class='feature-tab sheet-tabs tabs' data-group='primary'>
{{#each tabs as |tab|}}
<a class='{{tab.id}} {{tab.cssClass}}' data-action='tab' data-group='{{tab.group}}' data-tab='{{tab.id}}'>
{{localize tab.label}}
</a>
{{/each}}
</nav>
<line-div></line-div>
<div class='navigation-container'>
<div class="navigation-inner-container">
<line-div></line-div>
<nav class='feature-tab sheet-tabs tabs' data-group='primary'>
{{#each tabs as |tab|}}
<a class='{{tab.id}} {{tab.cssClass}}' data-action='tab' data-group='{{tab.group}}' data-tab='{{tab.id}}'>
{{localize tab.label}}
</a>
{{/each}}
</nav>
<line-div></line-div>
</div>
{{#if @partial-block}}
{{> @partial-block }}
{{/if}}
</div>
</section>

View file

@ -8,8 +8,6 @@
<span>{{localize "DAGGERHEART.GENERAL.type"}}</span>
{{formField systemFields.type value=source.system.type localize=true}}
<span>{{localize "DAGGERHEART.ITEMS.DomainCard.foundation"}}</span>
{{formField systemFields.foundation value=source.system.foundation }}
<span>{{localize "DAGGERHEART.GENERAL.Domain.single"}}</span>
{{formField systemFields.domain value=source.system.domain localize=true}}
<span>{{localize "DAGGERHEART.GENERAL.level"}}</span>

View file

@ -3,42 +3,42 @@
data-tab='{{tabs.features.id}}'
data-group='{{tabs.features.group}}'
>
<fieldset class="drop-section" data-type="foundationFeature">
<fieldset class="drop-section" data-type="foundation">
<legend>
{{localize "DAGGERHEART.GENERAL.Tabs.foundation"}}
<a {{#if source.system.foundationFeature}}disabled{{/if}}><i data-action="addFeature" data-type="foundationFeature" class="fa-solid fa-plus icon-button {{#if source.system.foundationFeature}}disabled{{/if}}"></i></a>
<a><i data-action="addFeature" data-type="foundation" class="fa-solid fa-plus icon-button"></i></a>
</legend>
<div class="feature-list">
{{#if source.system.foundationFeature}}
{{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='foundationFeature' feature=source.system.foundationFeature}}
{{/if}}
{{#each source.system.foundationFeatures as | feature | }}
{{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='foundation' feature=feature}}
{{/each}}
</div>
</fieldset>
<fieldset class="drop-section" data-type="specializationFeature">
<fieldset class="drop-section" data-type="specialization">
<legend>
{{localize "DAGGERHEART.GENERAL.Tabs.specialization"}}
<a {{#if source.system.specializationFeature}}disabled{{/if}}><i data-action="addFeature" data-type="specializationFeature" class="fa-solid fa-plus icon-button {{#if source.system.specializationFeature}}disabled{{/if}}"></i></a>
<a><i data-action="addFeature" data-type="specialization" class="fa-solid fa-plus icon-button"></i></a>
</legend>
<div class="feature-list">
{{#if source.system.specializationFeature}}
{{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='specializationFeature' feature=source.system.specializationFeature}}
{{/if}}
{{#each source.system.specializationFeatures as | feature |}}
{{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='specialization' feature=feature}}
{{/each}}
</div>
</fieldset>
<fieldset class="drop-section" data-type="masteryFeature">
<fieldset class="drop-section" data-type="mastery">
<legend>
{{localize "DAGGERHEART.GENERAL.Tabs.mastery"}}
<a {{#if source.system.masteryFeature}}disabled{{/if}}><i data-action="addFeature" data-type="masteryFeature" class="fa-solid fa-plus icon-button {{#if source.system.masteryFeature}}disabled{{/if}}"></i></a>
<a><i data-action="addFeature" data-type="mastery" class="fa-solid fa-plus icon-button"></i></a>
</legend>
<div class="feature-list">
{{#if source.system.masteryFeature}}
{{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='masteryFeature' feature=source.system.masteryFeature}}
{{/if}}
{{#each source.system.masteryFeatures as | feature |}}
{{> 'systems/daggerheart/templates/sheets/global/partials/feature-section-item.hbs' type='mastery' feature=feature}}
{{/each}}
</div>
</fieldset>
</section>

View file

@ -16,6 +16,11 @@
{{localize "DAGGERHEART.GENERAL.Disadvantage.full"}}
</div>
{{/if}}
{{#if roll.rally.dice}}
<div class="duality-modifier">
{{localize "DAGGERHEART.CLASS.Feature.rallyDice"}} {{roll.rally.dice}}
</div>
{{/if}}
</div>
<div class="dice-result">
<div class="dice-formula">{{roll.formula}}</div>
@ -38,7 +43,7 @@
<div class="dice-title">{{localize "DAGGERHEART.GENERAL.hope"}}</div>
<div class="dice-inner-container hope" title="{{localize "DAGGERHEART.GENERAL.hope"}}">
<div class="dice-wrapper">
<img class="dice" src="../icons/svg/d12-grey.svg"/>
<img class="dice" src="../icons/svg/{{roll.hope.dice}}-grey.svg"/>
</div>
<div class="dice-value">{{roll.hope.value}}</div>
</div>
@ -49,7 +54,7 @@
<div class="dice-title">{{localize "DAGGERHEART.GENERAL.fear"}}</div>
<div class="dice-inner-container fear" title="{{localize "DAGGERHEART.GENERAL.fear"}}">
<div class="dice-wrapper">
<img class="dice" src="../icons/svg/d12-grey.svg"/>
<img class="dice" src="../icons/svg/{{roll.fear.dice}}-grey.svg"/>
</div>
<div class="dice-value">{{roll.fear.value}}</div>
</div>
@ -72,7 +77,7 @@
<div class="dice-container">
<div class="dice-inner-container {{#if (eq roll.advantage.type 1)}}advantage{{else}}disadvantage{{/if}}">
<div class="dice-wrapper">
<img class="dice" src="../icons/svg/d6-grey.svg"/>
<img class="dice" src="../icons/svg/{{roll.advantage.dice}}-grey.svg"/>
</div>
<div class="dice-value">{{roll.advantage.value}}</div>
</div>
@ -82,6 +87,30 @@
</div>
</div>
{{/if}}
{{#if roll.rally.dice}}
<div class="dice">
<header class="part-header flexrow">
<span class="part-formula">
<span>1{{roll.rally.dice}}</span>
</span>
<span class="part-total">{{roll.rally.value}}</span>
</header>
<div class="flexrow">
<ol class="dice-rolls">
<li class="roll die {{roll.rally.dice}}">
<div class="dice-container">
<div class="dice-inner-container">
<div class="dice-wrapper">
<img class="dice" src="../icons/svg/{{roll.rally.dice}}-grey.svg"/>
</div>
<div class="dice-value">{{roll.rally.value}}</div>
</div>
</div>
</li>
</ol>
</div>
</div>
{{/if}}
{{#each roll.extra as | extra | }}
<div class="dice">
<header class="part-header flexrow">

View file

@ -0,0 +1,5 @@
<div class="daggerheart dh-style tooltip">
{{#each sources as | source |}}
<div>{{{source}}}</div>
{{/each}}
</div>

View file

@ -0,0 +1,7 @@
<div class="daggerheart dh-style tooltip">
<div class="tooltip-title-container">
<img class="tooltip-image" src="{{move.img}}" />
<h2 class="tooltip-title">{{move.name}}</h2>
</div>
<div class="tooltip-description">{{{move.description}}}</div>
</div>