[Feature] Beastform Types (#372)

* Temp

* Finished Evolved

* Fixed hybrid

* Changed generalConfig.tiers to be number based

* Weaponhandling while in beastform

* Added unarmed strike in sidebar

* Added DamageEnricher

* Added effect enricher

* Corrected downtime buttons and actions

* Added BeastformTooltip

* Split the BeastformDialog into parts with tabs

* Added temp beastform features

* rollData change

* Improvement

* character.getRollData cleanup
This commit is contained in:
WBHarry 2025-07-20 21:56:22 +02:00 committed by GitHub
parent 867947c2c5
commit 42a705a870
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
93 changed files with 2795 additions and 538 deletions

View file

@ -3,7 +3,7 @@ import * as applications from './module/applications/_module.mjs';
import * as models from './module/data/_module.mjs';
import * as documents from './module/documents/_module.mjs';
import RegisterHandlebarsHelpers from './module/helpers/handlebarsHelper.mjs';
import { DhDualityRollEnricher, DhTemplateEnricher } from './module/enrichers/_module.mjs';
import { enricherConfig, enricherRenderSetup } from './module/enrichers/_module.mjs';
import { getCommandTarget, rollCommandToJSON } from './module/helpers/utils.mjs';
import { NarrativeCountdowns } from './module/applications/ui/countdowns.mjs';
import { DualityRollColor } from './module/data/settings/Appearance.mjs';
@ -20,6 +20,7 @@ import { placeables } from './module/canvas/_module.mjs';
import { registerRollDiceHooks } from './module/dice/dhRoll.mjs';
import { registerDHActorHooks } from './module/documents/actor.mjs';
import './node_modules/@yaireo/tagify/dist/tagify.css';
import { renderDamageButton } from './module/enrichers/DamageEnricher.mjs';
Hooks.once('init', () => {
CONFIG.DH = SYSTEM;
@ -29,18 +30,7 @@ Hooks.once('init', () => {
documents
};
CONFIG.TextEditor.enrichers.push(
...[
{
pattern: /\[\[\/dr\s?(.*?)\]\]/g,
enricher: DhDualityRollEnricher
},
{
pattern: /^@Template\[(.*)\]$/g,
enricher: DhTemplateEnricher
}
]
);
CONFIG.TextEditor.enrichers.push(...enricherConfig);
CONFIG.statusEffects = [
...CONFIG.statusEffects.filter(x => !['dead', 'unconscious'].includes(x.id)),
@ -178,33 +168,15 @@ Hooks.on('ready', () => {
Hooks.once('dicesoniceready', () => {});
Hooks.on('renderChatMessageHTML', (_, element) => {
element
.querySelectorAll('.duality-roll-button')
.forEach(element => element.addEventListener('click', renderDualityButton));
element
.querySelectorAll('.measured-template-button')
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
enricherRenderSetup(element);
});
Hooks.on('renderJournalEntryPageProseMirrorSheet', (_, element) => {
element
.querySelectorAll('.duality-roll-button')
.forEach(element => element.addEventListener('click', renderDualityButton));
element
.querySelectorAll('.measured-template-button')
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
enricherRenderSetup(element);
});
Hooks.on('renderHandlebarsApplication', (_, element) => {
element
.querySelectorAll('.duality-roll-button')
.forEach(element => element.addEventListener('click', renderDualityButton));
element
.querySelectorAll('.measured-template-button')
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
enricherRenderSetup(element);
});
Hooks.on('chatMessage', (_, message) => {

View file

@ -13,6 +13,9 @@
"armor": "Armor",
"beastform": "Beastform"
},
"ActiveEffect": {
"beastform": "Beastform"
},
"Actor": {
"character": "Character",
"companion": "Companion",
@ -110,8 +113,25 @@
"horderHp": "Horde/HP"
},
"Character": {
"advantageSources": {
"label": "Advantage Sources",
"hint": "Add single words or short text as reminders and hints of what a character has advantage on."
},
"age": "Age",
"companionFeatures": "Companion Features",
"contextMenu": {
"consume": "Consume Item",
"equip": "Equip",
"sendToChat": "Send To Chat",
"toLoadout": "Send to Loadout",
"toVault": "Send to Vault",
"unequip": "Unequip",
"useItem": "Use Item"
},
"disadvantageSources": {
"label": "Disadvantage Sources",
"hint": "Add single words or short text as reminders and hints of what a character has disadvantage on."
},
"faith": "Faith",
"levelUp": "You can level up",
"pronouns": "Pronouns",
@ -572,6 +592,11 @@
"description": "You reduce incoming magic damage by your Armor Score before applying it to your damage thresholds."
}
},
"BeastformType": {
"normal": "Normal",
"evolved": "Evolved",
"hybrid": "Hybrid"
},
"Burden": {
"oneHanded": "One-Handed",
"twoHanded": "Two-Handed"
@ -1019,6 +1044,7 @@
},
"Advantage": {
"full": "Advantage",
"plural": "Advantages",
"short": "Adv"
},
"Adversary": {
@ -1202,6 +1228,11 @@
"hint": "The cost in stress you can pay to reduce minor damage to none."
}
}
},
"attack": {
"damage": {
"value": { "label": "Base Attack: Damage" }
}
}
},
"Tabs": {
@ -1235,14 +1266,19 @@
"recovery": "Recovery",
"setup": "Setup",
"equipment": "Equipment",
"attachments": "Attachments"
},
"Tiers": {
"singular": "Tier",
"attachments": "Attachments",
"advanced": "Advanced",
"tier1": "Tier 1",
"tier2": "Tier 2",
"tier3": "Tier 3",
"tier4": "Tier 4"
"tier4": "tier 4"
},
"Tiers": {
"singular": "Tier",
"1": "Tier 1",
"2": "Tier 2",
"3": "Tier 3",
"4": "Tier 4"
},
"Trait": {
"single": "Trait",
@ -1278,7 +1314,7 @@
"features": "Features",
"formula": "Formula",
"healing": "Healing",
"hitPoints": {
"HitPoints": {
"single": "Hit Point",
"plural": "Hit Points",
"short": "HP"
@ -1318,6 +1354,8 @@
"total": "Total",
"true": "True",
"type": "Type",
"unarmed": "Unarmed",
"unarmedStrike": "Unarmed Strike",
"unarmored": "Unarmored",
"use": "Use",
"used": "Used",
@ -1351,7 +1389,9 @@
},
"Beastform": {
"FIELDS": {
"beastformType": { "label": "Beastform Type" },
"tier": { "label": "Tier" },
"mainTrait": { "label": "Main Trait" },
"examples": { "label": "Examples" },
"advantageOn": { "label": "Gain Advantage On" },
"tokenImg": { "label": "Token Image" },
@ -1360,12 +1400,28 @@
"placeholder": "Using character dimensions",
"height": { "label": "Height" },
"width": { "label": "Width" }
},
"evolved": {
"maximumTier": { "label": "Maximum Tier" },
"mainTraitBonus": { "label": "Main Trait Bonus" }
},
"hybrid": {
"beastformOptions": { "label": "Nr Beastforms" },
"advantages": { "label": "Nr Advantages" },
"features": { "label": "Nr Features" }
}
},
"attackName": "Beast Attack",
"beastformEffect": "Beastform Transformation",
"dialogTitle": "Beastform Selection",
"tokenTitle": "Beastform Token",
"transform": "Transform",
"beastformEffect": "Beastform Transformation"
"evolve": "Evolve",
"evolvedFeatureTitle": "Evolved",
"evolvedDrag": "Drag a form here to evolve it.",
"hybridize": "Hybridize",
"hybridizeFeatureTitle": "Hybrid Features",
"hybridizeDrag": "Drag a form here to hybridize it."
},
"Class": {
"hopeFeatures": "Hope Features",
@ -1609,7 +1665,11 @@
"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."
"featureNotMastery": "This feature is used as something else than a Mastery feature and cannot be used here.",
"beastformMissingEffect": "The Beastform is missing a Beastform Effect. Cannot be used.",
"beastformToManyAdvantages": "You cannot select any more advantages.",
"beastformToManyFeatures": "You cannot select any more features.",
"beastformEquipWeapon": "You cannot use weapons while in a Beastform."
},
"Tooltip": {
"disableEffect": "Disable Effect",
@ -1624,6 +1684,7 @@
"sendToLoadout": "Send to Loadout",
"makeDeathMove": "Make a Death Move",
"rangeAndTarget": "Range & Target",
"dragApplyEffect": "Drag effect to apply it to an actor",
"appliedEvenIfSuccessful": "Applied even if save succeeded"
}
}

View file

@ -6,6 +6,10 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
this.configData = configData;
this.selected = null;
this.evolved = { form: null };
this.hybrid = { forms: {}, advantages: {}, features: {} };
this._dragDrop = this._createDragDropHandlers();
}
static DEFAULT_OPTIONS = {
@ -17,13 +21,16 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
},
actions: {
selectBeastform: this.selectBeastform,
toggleHybridFeature: this.toggleHybridFeature,
toggleHybridAdvantage: this.toggleHybridAdvantage,
submitBeastform: this.submitBeastform
},
form: {
handler: this.updateBeastform,
submitOnChange: true,
submitOnClose: false
}
},
dragDrop: [{ dragSelector: '.beastform-container', dropSelector: '.advanced-form-container' }]
};
get title() {
@ -32,36 +39,217 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
/** @override */
static PARTS = {
beastform: {
template: 'systems/daggerheart/templates/dialogs/beastformDialog.hbs'
tabs: { template: 'systems/daggerheart/templates/dialogs/beastform/tabs.hbs' },
beastformTier: { template: 'systems/daggerheart/templates/dialogs/beastform/beastformTier.hbs' },
advanced: { template: 'systems/daggerheart/templates/dialogs/beastform/advanced.hbs' },
footer: { template: 'systems/daggerheart/templates/dialogs/beastform/footer.hbs' }
};
/** @inheritdoc */
static TABS = {
primary: {
tabs: [{ id: '1' }, { id: '2' }, { id: '3' }, { id: '4' }],
initial: '1',
labelPrefix: 'DAGGERHEART.GENERAL.Tiers'
}
};
changeTab(tab, group, options) {
super.changeTab(tab, group, options);
this.render();
}
_createDragDropHandlers() {
return this.options.dragDrop.map(d => {
d.callbacks = {
dragstart: this._onDragStart.bind(this),
drop: this._onDrop.bind(this)
};
return new foundry.applications.ux.DragDrop.implementation(d);
});
}
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
this._dragDrop.forEach(d => d.bind(htmlElement));
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.beastformTiers = game.items.reduce((acc, x) => {
const tier = CONFIG.DH.GENERAL.tiers[x.system.tier];
if (x.type !== 'beastform' || tier.value > this.configData.tierLimit) return acc;
context.selected = this.selected;
context.selectedBeastformEffect = this.selected?.effects?.find?.(x => x.type === 'beastform');
if (!acc[tier.value]) acc[tier.value] = { label: game.i18n.localize(tier.label), values: {} };
acc[tier.value].values[x.uuid] = { selected: this.selected == x.uuid, value: x };
context.evolved = this.evolved;
context.hybridForms = Object.keys(this.hybrid.forms).reduce((acc, formKey) => {
if (!this.hybrid.forms[formKey]) {
acc[formKey] = null;
} else {
const data = this.hybrid.forms[formKey].toObject();
acc[formKey] = {
...data,
system: {
...data.system,
features: this.hybrid.forms[formKey].system.features.map(feature => ({
...feature.toObject(),
uuid: feature.uuid,
selected: Boolean(this.hybrid.features?.[formKey]?.[feature.uuid])
})),
advantageOn: Object.keys(data.system.advantageOn).reduce((acc, key) => {
acc[key] = {
...data.system.advantageOn[key],
selected: Boolean(this.hybrid.advantages?.[formKey]?.[key])
};
return acc;
}, {})
}
};
}
return acc;
}, {});
const maximumDragTier = Math.max(
this.selected?.system?.evolved?.maximumTier ?? 0,
this.selected?.system?.hybrid?.maximumTier ?? 0
);
const compendiumBeastforms = await game.packs.get(`daggerheart.beastforms`)?.getDocuments();
const beastformTiers = [...(compendiumBeastforms ? compendiumBeastforms : []), ...game.items].reduce(
(acc, x) => {
const tier = CONFIG.DH.GENERAL.tiers[x.system.tier];
if (x.type !== 'beastform' || tier.id > this.configData.tierLimit) return acc;
if (!acc[tier.id]) acc[tier.id] = { label: game.i18n.localize(tier.label), values: {} };
acc[tier.id].values[x.uuid] = {
selected: this.selected?.uuid == x.uuid,
value: x,
draggable:
!['evolved', 'hybrid'].includes(x.system.beastformType) && maximumDragTier
? x.system.tier <= maximumDragTier
: false
};
return acc;
}, {}); // Also get from compendium when added
context.canSubmit = this.selected;
},
{}
);
context.tier = beastformTiers[this.tabGroups.primary];
context.tierKey = this.tabGroups.primary;
context.canSubmit = this.canSubmit();
return context;
}
canSubmit() {
if (this.selected) {
switch (this.selected.system.beastformType) {
case 'normal':
return true;
case 'evolved':
return this.evolved.form;
case 'hybrid':
const selectedAdvantages = Object.values(this.hybrid.advantages).reduce(
(acc, form) => acc + Object.values(form).length,
0
);
const selectedFeatures = Object.values(this.hybrid.features).reduce(
(acc, form) => acc + Object.values(form).length,
0
);
const advantagesSelected = selectedAdvantages === this.selected.system.hybrid.advantages;
const featuresSelected = selectedFeatures === this.selected.system.hybrid.features;
return advantagesSelected && featuresSelected;
}
}
return false;
}
static updateBeastform(event, _, formData) {
this.selected = foundry.utils.mergeObject(this.selected, formData.object);
this.render();
}
static selectBeastform(_, target) {
this.selected = this.selected === target.dataset.uuid ? null : target.dataset.uuid;
static async selectBeastform(_, target) {
this.element.querySelectorAll('.beastform-container ').forEach(element => {
if (element.dataset.uuid === target.dataset.uuid && this.selected?.uuid !== target.dataset.uuid) {
element.classList.remove('inactive');
} else {
element.classList.add('inactive');
}
});
const uuid = this.selected?.uuid === target.dataset.uuid ? null : target.dataset.uuid;
this.selected = uuid ? await foundry.utils.fromUuid(uuid) : null;
if (this.selected) {
if (this.selected.system.beastformType !== 'evolved') this.evolved.form = null;
if (this.selected.system.beastformType !== 'hybrid') {
this.hybrid.forms = {};
this.hybrid.advantages = {};
this.hybrid.features = {};
} else {
this.hybrid.forms = [...Array(this.selected.system.hybrid.beastformOptions).keys()].reduce((acc, _) => {
acc[foundry.utils.randomID()] = null;
return acc;
}, {});
}
}
this.render();
}
static toggleHybridFeature(_, button) {
const current = this.hybrid.features[button.dataset.form];
if (!current) this.hybrid.features[button.dataset.form] = {};
if (this.hybrid.features[button.dataset.form][button.id])
delete this.hybrid.features[button.dataset.form][button.id];
else {
const currentFeatures = Object.values(this.hybrid.features).reduce(
(acc, form) => acc + Object.values(form).length,
0
);
if (currentFeatures === this.selected.system.hybrid.features) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyFeatures'));
return;
}
const feature = this.hybrid.forms[button.dataset.form].system.features.find(x => x.uuid === button.id);
this.hybrid.features[button.dataset.form][button.id] = feature;
}
this.render();
}
static toggleHybridAdvantage(_, button) {
const current = this.hybrid.advantages[button.dataset.form];
if (!current) this.hybrid.advantages[button.dataset.form] = {};
if (this.hybrid.advantages[button.dataset.form][button.id])
delete this.hybrid.advantages[button.dataset.form][button.id];
else {
const currentAdvantages = Object.values(this.hybrid.advantages).reduce(
(acc, form) => acc + Object.values(form).length,
0
);
if (currentAdvantages === this.selected.system.hybrid.advantages) {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.beastformToManyAdvantages'));
return;
}
const advantage = this.hybrid.forms[button.dataset.form].system.advantageOn[button.id];
this.hybrid.advantages[button.dataset.form][button.id] = advantage;
}
this.render();
}
@ -71,14 +259,60 @@ export default class BeastformDialog extends HandlebarsApplicationMixin(Applicat
/** @override */
_onClose(options = {}) {
if (!options.submitted) this.config = false;
if (!options.submitted) this.selected = null;
}
static async configure(configData) {
return new Promise(resolve => {
const app = new this(configData);
app.addEventListener('close', () => resolve(app.selected), { once: true });
app.addEventListener(
'close',
() => resolve({ selected: app.selected, evolved: app.evolved, hybrid: app.hybrid }),
{ once: true }
);
app.render({ force: true });
});
}
async _onDragStart(event) {
const target = event.currentTarget;
const abort = () => event.preventDefault();
if (!this.selected) abort();
const draggedForm = await foundry.utils.fromUuid(target.dataset.uuid);
if (['evolved', 'hybrid'].includes(draggedForm.system.beastformType)) abort();
if (this.selected.system.beastformType === 'evolved') {
if (draggedForm.system.tier > this.selected.system.evolved.maximumTier) abort();
}
if (this.selected.system.beastformType === 'hybrid') {
if (draggedForm.system.tier > this.selected.system.hybrid.maximumTier) abort();
}
event.dataTransfer.setData('text/plain', JSON.stringify(target.dataset));
event.dataTransfer.setDragImage(target, 60, 0);
}
async _onDrop(event) {
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.getDragEventData(event);
const item = await fromUuid(data.uuid);
if (!item) return;
if (event.target.closest('.advanced-form-container.evolved')) {
this.evolved.form = item;
} else {
const hybridContainer = event.target.closest('.advanced-form-container.hybridized');
if (hybridContainer) {
const existingId = Object.keys(this.hybrid.forms).find(
key => this.hybrid.forms[key]?.uuid === item.uuid
);
if (existingId) this.hybrid.forms[existingId] = null;
this.hybrid.forms[hybridContainer.id] = item;
}
}
this.render();
}
}

View file

@ -61,7 +61,7 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
static updateRollConfiguration(_event, _, formData) {
const { ...rest } = foundry.utils.expandObject(formData.object);
foundry.utils.mergeObject(this.config.roll, rest.roll)
foundry.utils.mergeObject(this.config.roll, rest.roll);
this.config.selectedRollMode = rest.selectedRollMode;
this.render();

View file

@ -114,7 +114,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
const settingsTiers = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LevelTiers).tiers;
context.tierOptions = [
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.tier1') },
{ key: 1, label: game.i18n.localize('DAGGERHEART.GENERAL.Tiers.1') },
...Object.values(settingsTiers).map(x => ({ key: x.tier, label: x.name }))
];

View file

@ -8,7 +8,7 @@ export default class AdversarySheet extends DHBaseActorSheet {
position: { width: 660, height: 766 },
window: { resizable: true },
actions: {
reactionRoll: AdversarySheet.#reactionRoll,
reactionRoll: AdversarySheet.#reactionRoll
},
window: {
resizable: true

View file

@ -619,6 +619,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
await item.update({ 'system.equipped': true });
break;
case 'weapon':
if (this.document.effects.find(x => x.type === 'beastform')) {
return ui.notifications.warn(
game.i18n.localize('DAGGERHEART.UI.Notifications.beastformEquipWeapon')
);
}
await this.document.system.constructor.unequipBeforeEquip.bind(this.document.system)(item);
await item.update({ 'system.equipped': true });

View file

@ -84,15 +84,16 @@ export default function DHApplicationMixin(Base) {
useItem: DHSheetV2.#useItem,
useAction: DHSheetV2.#useAction,
toggleEffect: DHSheetV2.#toggleEffect,
toggleExtended: DHSheetV2.#toggleExtended,
toggleExtended: DHSheetV2.#toggleExtended
},
contextMenus: [{
contextMenus: [
{
handler: DHSheetV2.#getEffectContextOptions,
selector: '[data-item-uuid][data-type="effect"]',
options: {
parentClassHooks: false,
fixed: true
},
}
},
{
handler: DHSheetV2.#getActionContextOptions,
@ -101,7 +102,8 @@ export default function DHApplicationMixin(Base) {
parentClassHooks: false,
fixed: true
}
}],
}
],
dragDrop: [],
tagifyConfigs: []
};
@ -132,13 +134,13 @@ export default function DHApplicationMixin(Base) {
/**@inheritdoc */
_syncPartState(partId, newElement, priorElement, state) {
super._syncPartState(partId, newElement, priorElement, state);
for (const el of priorElement.querySelectorAll(".extensible.extended")) {
for (const el of priorElement.querySelectorAll('.extensible.extended')) {
const { actionId, itemUuid } = el.parentElement.dataset;
const selector = `${actionId ? `[data-action-id="${actionId}"]` : `[data-item-uuid="${itemUuid}"]`} .extensible`;
const newExtensible = newElement.querySelector(selector);
if (!newExtensible) continue;
newExtensible.classList.add("extended");
newExtensible.classList.add('extended');
const descriptionElement = newExtensible.querySelector('.invetory-description');
if (descriptionElement) {
this.#prepareInventoryDescription(newExtensible, descriptionElement);
@ -209,14 +211,14 @@ export default function DHApplicationMixin(Base) {
* @param {DragEvent} event
* @protected
*/
_onDragStart(event) { }
_onDragStart(event) {}
/**
* Handle drop event.
* @param {DragEvent} event
* @protected
*/
_onDrop(event) { }
_onDrop(event) {}
/* -------------------------------------------- */
/* Context Menu */
@ -251,7 +253,7 @@ export default function DHApplicationMixin(Base) {
icon: 'fa-regular fa-lightbulb',
condition: target => getDocFromElement(target).disabled,
callback: target => getDocFromElement(target).update({ disabled: false })
},
}
].map(option => ({
...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`,
@ -269,7 +271,7 @@ export default function DHApplicationMixin(Base) {
*/
static #getActionContextOptions() {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const getAction = (target) => {
const getAction = target => {
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
return attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
@ -279,30 +281,31 @@ export default function DHApplicationMixin(Base) {
{
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
condition: this.document instanceof foundry.documents.Actor ||
condition:
this.document instanceof foundry.documents.Actor ||
(this.document instanceof foundry.documents.Item && this.document.parent),
callback: (target, event) => getAction(target).use(event),
callback: (target, event) => getAction(target).use(event)
},
{
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: (target) => getAction(target).toChat(this.document.id),
callback: target => getAction(target).toChat(this.document.id)
},
{
name: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
callback: (target) => new DHActionConfig(getAction(target)).render({ force: true })
callback: target => new DHActionConfig(getAction(target)).render({ force: true })
},
{
name: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash',
condition: (target) => {
condition: target => {
const { actionId } = target.closest('[data-action-id]').dataset;
const { attack } = this.document.system;
return attack?.id !== actionId
return attack?.id !== actionId;
},
callback: async (target) => {
const action = getAction(target)
callback: async target => {
const action = getAction(target);
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
@ -310,12 +313,14 @@ export default function DHApplicationMixin(Base) {
name: action.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: action.name })
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
name: action.name
})
});
if (!confirmed) return;
return this.document.update({
'system.actions': this.document.system.actions.filter((a) => a.id !== action.id)
'system.actions': this.document.system.actions.filter(a => a.id !== action.id)
});
}
}
@ -337,22 +342,25 @@ export default function DHApplicationMixin(Base) {
name: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square',
callback: target => getDocFromElement(target).sheet.render({ force: true })
},
}
];
if (usable) options.unshift({
if (usable)
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst',
callback: (target, event) => getDocFromElement(target).use(event),
callback: (target, event) => getDocFromElement(target).use(event)
});
if (toChat) options.unshift({
if (toChat)
options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message',
callback: (target) => getDocFromElement(target).toChat(this.document.id),
callback: target => getDocFromElement(target).toChat(this.document.id)
});
if (deletable) options.push({
if (deletable)
options.push({
name: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash',
callback: (target, event) => {
@ -360,12 +368,12 @@ export default function DHApplicationMixin(Base) {
if (event.shiftKey) return doc.delete();
else return doc.deleteDialog();
}
})
});
return options.map(option => ({
...option,
icon: `<i class="${option.icon}"></i>`
}))
}));
}
/* -------------------------------------------- */
@ -406,11 +414,14 @@ export default function DHApplicationMixin(Base) {
const description = doc.system?.description ?? doc.description;
const isAction = !!actionId;
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(description, {
descriptionElement.innerHTML = await foundry.applications.ux.TextEditor.implementation.enrichHTML(
description,
{
relativeTo: isAction ? doc.parent : doc,
rollData: doc.getRollData?.(),
secrets: isAction ? doc.parent.isOwner : doc.isOwner
});
}
);
}
/* -------------------------------------------- */
@ -427,7 +438,8 @@ export default function DHApplicationMixin(Base) {
const parent = parentIsItem && documentClass === 'Item' ? null : this.document;
if (type === 'action') {
const { type: actionType } = await foundry.applications.api.DialogV2.input({
const { type: actionType } =
(await foundry.applications.api.DialogV2.input({
window: { title: 'Select Action Type' },
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/actionTypes/actionType.hbs',
@ -436,12 +448,13 @@ export default function DHApplicationMixin(Base) {
ok: {
label: game.i18n.format('DOCUMENT.Create', {
type: game.i18n.localize('DAGGERHEART.GENERAL.Action.single')
}),
})
}
}) ?? {};
})) ?? {};
if (!actionType) return;
const cls = game.system.api.models.actions.actionsTypes[actionType]
const action = new cls({
const cls = game.system.api.models.actions.actionsTypes[actionType];
const action = new cls(
{
_id: foundry.utils.randomID(),
type: actionType,
name: game.i18n.localize(CONFIG.DH.ACTIONS.actionTypes[actionType].name),
@ -456,14 +469,13 @@ export default function DHApplicationMixin(Base) {
force: true
});
return action;
} else {
const cls = getDocumentClass(documentClass);
const data = {
name: cls.defaultName({ type, parent }),
type,
}
if (inVault) data["system.inVault"] = true;
type
};
if (inVault) data['system.inVault'] = true;
if (disabled) data.disabled = true;
const doc = await cls.create(data, { parent, renderSheet: !event.shiftKey });
@ -474,7 +486,6 @@ export default function DHApplicationMixin(Base) {
}
return doc;
}
}
/**
@ -489,7 +500,7 @@ export default function DHApplicationMixin(Base) {
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
const action = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
new DHActionConfig(action).render({ force: true })
new DHActionConfig(action).render({ force: true });
}
/**
@ -500,8 +511,8 @@ export default function DHApplicationMixin(Base) {
const doc = getDocFromElement(target);
if (doc) {
if (event.shiftKey) return doc.delete()
else return await doc.deleteDialog()
if (event.shiftKey) return doc.delete();
else return await doc.deleteDialog();
}
// TODO: REDO this
@ -524,7 +535,7 @@ export default function DHApplicationMixin(Base) {
}
return await this.document.update({
'system.actions': actions.filter((a) => a.id !== action.id)
'system.actions': actions.filter(a => a.id !== action.id)
});
}
@ -555,7 +566,7 @@ export default function DHApplicationMixin(Base) {
const { actionId } = target.closest('[data-action-id]').dataset;
const { actions, attack } = this.document.system;
doc = attack?.id === actionId ? attack : actions?.find(a => a.id === actionId);
if(this.document instanceof foundry.documents.Item && !this.document.parent) return;
if (this.document instanceof foundry.documents.Item && !this.document.parent) return;
}
await doc.use(event);
@ -573,7 +584,6 @@ export default function DHApplicationMixin(Base) {
await action.use(event);
}
/**
* Toggle a ActiveEffect
* @type {ApplicationClickAction}
@ -604,7 +614,6 @@ export default function DHApplicationMixin(Base) {
const descriptionElement = extensible?.querySelector('.invetory-description');
if (t && !!descriptionElement) this.#prepareInventoryDescription(extensible, descriptionElement);
}
}
return DHSheetV2;

View file

@ -22,7 +22,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
},
actions: {
openSettings: DHBaseActorSheet.#openSettings,
sendExpToChat: DHBaseActorSheet.#sendExpToChat,
sendExpToChat: DHBaseActorSheet.#sendExpToChat
},
contextMenus: [
{
@ -59,7 +59,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
return context;
}
/**@inheritdoc */
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
@ -81,7 +80,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
async _prepareEffectsContext(context, _options) {
context.effects = {
actives: [],
inactives: [],
inactives: []
};
for (const effect of this.actor.allApplicableEffects()) {
@ -104,7 +103,6 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
return this._getContextMenuCommonOptions.call(this, { usable: true, toChat: true });
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */

View file

@ -72,10 +72,10 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
secrets: this.item.isOwner
});
break;
case "effects":
await this._prepareEffectsContext(context, options)
case 'effects':
await this._prepareEffectsContext(context, options);
break;
case "features":
case 'features':
context.isGM = game.user.isGM;
break;
}
@ -93,7 +93,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
async _prepareEffectsContext(context, _options) {
context.effects = {
actives: [],
inactives: [],
inactives: []
};
for (const effect of this.item.effects) {
@ -113,12 +113,11 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
* @protected
*/
static #getFeatureContextOptions() {
const options = this._getContextMenuCommonOptions({ usable: true, toChat: true, deletable: false })
options.push(
{
const options = this._getContextMenuCommonOptions({ usable: true, toChat: true, deletable: false });
options.push({
name: 'CONTROLS.CommonDelete',
icon: '<i class="fa-solid fa-trash"></i>',
callback: async (target) => {
callback: async target => {
const feature = getDocFromElement(target);
if (!feature) return;
const confirmed = await foundry.applications.api.DialogV2.confirm({
@ -128,15 +127,16 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
name: feature.name
})
},
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', { name: feature.name })
content: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.text', {
name: feature.name
})
});
if (!confirmed) return;
await this.document.update({
'system.features': this.document.system.toObject().features.filter(uuid => uuid !== feature.uuid)
});
},
}
)
});
return options;
}
@ -153,7 +153,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
const actionIndex = button.closest('[data-index]').dataset.index;
const action = this.document.system.actions[actionIndex];
if(!event.shiftKey) {
if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({
window: {
title: game.i18n.format('DAGGERHEART.APPLICATIONS.DeleteConfirmation.title', {
@ -166,7 +166,6 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (!confirmed) return;
}
await this.document.update({
'system.actions': this.document.system.actions.filter((_, index) => index !== Number.parseInt(actionIndex))
});
@ -180,9 +179,9 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
const { type } = target.dataset;
const cls = foundry.documents.Item.implementation;
const feature = await cls.create({
type: 'feature',
name: cls.defaultName({ type: 'feature' }),
"system.subType": CONFIG.DH.ITEM.featureSubTypes[type]
'type': 'feature',
'name': cls.defaultName({ type: 'feature' }),
'system.subType': CONFIG.DH.ITEM.featureSubTypes[type]
});
await this.document.update({
'system.features': [...this.document.system.features, feature].map(f => f.uuid)
@ -198,9 +197,7 @@ export default class DHBaseItemSheet extends DHApplicationMixin(ItemSheetV2) {
if (!feature) return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.featureIsMissing'));
await feature.update({ 'system.subType': null });
await this.document.update({
'system.features': this.document.system.features
.map(x => x.uuid)
.filter(uuid => uuid !== feature.uuid)
'system.features': this.document.system.features.map(x => x.uuid).filter(uuid => uuid !== feature.uuid)
});
}

View file

@ -31,7 +31,7 @@ export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
},
...super.PARTS,
...super.PARTS
};
/**@inheritdoc */

View file

@ -1,4 +1,5 @@
import DHBaseItemSheet from '../api/base-item.mjs';
import Tagify from '@yaireo/tagify';
export default class BeastformSheet extends DHBaseItemSheet {
/**@inheritdoc */
@ -15,6 +16,7 @@ export default class BeastformSheet extends DHBaseItemSheet {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-features.hbs',
scrollable: ['.features']
},
advanced: { template: 'systems/daggerheart/templates/sheets/items/beastform/advanced.hbs' },
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
@ -23,9 +25,77 @@ export default class BeastformSheet extends DHBaseItemSheet {
static TABS = {
primary: {
tabs: [{ id: 'settings' }, { id: 'features' }, { id: 'effects' }],
tabs: [{ id: 'settings' }, { id: 'features' }, { id: 'advanced' }, { id: 'effects' }],
initial: 'settings',
labelPrefix: 'DAGGERHEART.GENERAL.Tabs'
}
};
_attachPartListeners(partId, htmlElement, options) {
super._attachPartListeners(partId, htmlElement, options);
const advantageOnInput = htmlElement.querySelector('.advantageon-input');
if (advantageOnInput) {
const tagifyElement = new Tagify(advantageOnInput, {
tagTextProp: 'name',
templates: {
tag(tagData) {
return `<tag
contenteditable='false'
spellcheck='false'
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
${this.getAttributes(tagData)}>
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
<div>
<span class="${this.settings.classNames.tagText}">${tagData[this.settings.tagTextProp] || tagData.value}</span>
${tagData.src ? `<img src="${tagData.src}"></i>` : ''}
</div>
</tag>`;
}
}
});
tagifyElement.on('add', this.advantageOnAdd.bind(this));
tagifyElement.on('remove', this.advantageOnRemove.bind(this));
}
}
/**@inheritdoc */
async _preparePartContext(partId, context, options) {
await super._preparePartContext(partId, context, options);
switch (partId) {
case 'settings':
context.advantageOn = JSON.stringify(
Object.keys(context.document.system.advantageOn).map(key => ({
value: key,
name: context.document.system.advantageOn[key].value
}))
);
break;
case 'effects':
context.effects.actives = context.effects.actives.map(effect => {
const data = effect.toObject();
data.id = effect.id;
if (effect.type === 'beastform') data.mandatory = true;
return data;
});
break;
}
return context;
}
async advantageOnAdd(event) {
await this.document.update({
[`system.advantageOn.${foundry.utils.randomID()}`]: { value: event.detail.data.value }
});
}
async advantageOnRemove(event) {
await this.document.update({
[`system.advantageOn.-=${event.detail.data.value}`]: null
});
}
}

View file

@ -9,7 +9,7 @@ export default class ClassSheet extends DHBaseItemSheet {
position: { width: 700 },
actions: {
removeItemFromCollection: ClassSheet.#removeItemFromCollection,
removeSuggestedItem: ClassSheet.#removeSuggestedItem,
removeSuggestedItem: ClassSheet.#removeSuggestedItem
},
tagifyConfigs: [
{

View file

@ -31,7 +31,7 @@ export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
},
...super.PARTS,
...super.PARTS
};
/**@inheritdoc */

View file

@ -212,15 +212,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
for (let target of targets) {
let damages = foundry.utils.deepClone(message.system.damage?.roll ?? message.system.roll);
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true) {
if (
message.system.onSave &&
message.system.targets.find(t => t.id === target.id)?.saved?.success === true
) {
const mod = CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1;
Object.entries(damages).forEach(([k,v]) => {
Object.entries(damages).forEach(([k, v]) => {
v.total = 0;
v.parts.forEach(part => {
part.total = Math.ceil(part.total * mod);
v.total += part.total;
})
})
});
});
}
target.actor.takeDamage(damages);
@ -294,15 +297,16 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
await actor.useAction(action);
};
actionUseButton = async (_, message) => {
actionUseButton = async (event, message) => {
const { moveIndex, actionIndex } = event.currentTarget.dataset;
const parent = await foundry.utils.fromUuid(message.system.actor);
const actionType = Object.values(message.system.moves)[0].actions[0];
const cls = CONFIG.DH.ACTIONS.actionTypes[actionType.type];
const actionType = message.system.moves[moveIndex].actions[actionIndex];
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
const action = new cls(
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
{ parent: parent }
{ parent: parent.system }
);
action.use();
action.use(event);
};
}

View file

@ -99,7 +99,7 @@ export default class DhHotbar extends foundry.applications.ui.Hotbar {
async createItemMacro(data, slot) {
const macro = await Macro.implementation.create({
name: `${game.i18n.localize('Display')} ${name}`,
name: data.name,
type: CONST.MACRO_TYPES.SCRIPT,
img: data.img,
command: `await game.system.api.applications.ui.DhHotbar.useItem("${data.uuid}");`
@ -109,7 +109,7 @@ export default class DhHotbar extends foundry.applications.ui.Hotbar {
async createActionMacro(data, slot) {
const macro = await Macro.implementation.create({
name: `${game.i18n.localize('Display')} ${name}`,
name: data.data.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}");`
@ -119,7 +119,7 @@ export default class DhHotbar extends foundry.applications.ui.Hotbar {
async createAttackMacro(data, slot) {
const macro = await Macro.implementation.create({
name: `${game.i18n.localize('Display')} ${name}`,
name: data.name,
type: CONST.MACRO_TYPES.SCRIPT,
img: data.img,
command: `await game.system.api.applications.ui.DhHotbar.useAttack("${data.actorUuid}");`

View file

@ -1,5 +1,6 @@
export const abilities = {
agility: {
id: 'agility',
label: 'DAGGERHEART.CONFIG.Traits.agility.name',
verbs: [
'DAGGERHEART.CONFIG.Traits.agility.verb.sprint',
@ -8,6 +9,7 @@ export const abilities = {
]
},
strength: {
id: 'strength',
label: 'DAGGERHEART.CONFIG.Traits.strength.name',
verbs: [
'DAGGERHEART.CONFIG.Traits.strength.verb.lift',
@ -16,6 +18,7 @@ export const abilities = {
]
},
finesse: {
id: 'finesse',
label: 'DAGGERHEART.CONFIG.Traits.finesse.name',
verbs: [
'DAGGERHEART.CONFIG.Traits.finesse.verb.control',
@ -24,6 +27,7 @@ export const abilities = {
]
},
instinct: {
id: 'instinct',
label: 'DAGGERHEART.CONFIG.Traits.instinct.name',
verbs: [
'DAGGERHEART.CONFIG.Traits.instinct.verb.perceive',
@ -32,6 +36,7 @@ export const abilities = {
]
},
presence: {
id: 'presence',
label: 'DAGGERHEART.CONFIG.Traits.presence.name',
verbs: [
'DAGGERHEART.CONFIG.Traits.presence.verb.charm',
@ -40,6 +45,7 @@ export const abilities = {
]
},
knowledge: {
id: 'knowledge',
label: 'DAGGERHEART.CONFIG.Traits.knowledge.name',
verbs: [
'DAGGERHEART.CONFIG.Traits.knowledge.verb.recall',

View file

@ -145,11 +145,11 @@ export const defaultRestOptions = {
img: 'icons/magic/life/cross-worn-green.webp',
actionType: 'action',
healing: {
type: 'health',
applyTo: healingTypes.hitPoints.id,
value: {
custom: {
enabled: true,
formula: '1d4 + 1' // should be 1d4 + {tier}. How to use the roll param?
formula: '1d4 + @tier'
}
}
}
@ -169,11 +169,11 @@ export const defaultRestOptions = {
img: 'icons/magic/perception/eye-ringed-green.webp',
actionType: 'action',
healing: {
type: 'stress',
applyTo: healingTypes.stress.id,
value: {
custom: {
enabled: true,
formula: '1d4 + 1' // should be 1d4 + {tier}. How to use the roll param?
formula: '1d4 + @tier'
}
}
}
@ -186,7 +186,23 @@ export const defaultRestOptions = {
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: []
actions: [
{
type: 'healing',
name: game.i18n.localize('DAGGERHEART.APPLICATIONS.Downtime.shortRest.repairArmor.name'),
img: 'icons/skills/trades/smithing-anvil-silver-red.webp',
actionType: 'action',
healing: {
applyTo: healingTypes.armorStack.id,
value: {
custom: {
enabled: true,
formula: '1d4 + @tier'
}
}
}
}
]
},
prepare: {
id: 'prepare',
@ -263,25 +279,21 @@ export const deathMoves = {
};
export const tiers = {
tier1: {
id: 'tier1',
label: 'DAGGERHEART.GENERAL.Tiers.tier1',
value: 1
1: {
id: 1,
label: 'DAGGERHEART.GENERAL.Tiers.1'
},
tier2: {
id: 'tier2',
label: 'DAGGERHEART.GENERAL.Tiers.tier2',
value: 2
2: {
id: 2,
label: 'DAGGERHEART.GENERAL.Tiers.2'
},
tier3: {
id: 'tier3',
label: 'DAGGERHEART.GENERAL.Tiers.tier3',
value: 3
3: {
id: 3,
label: 'DAGGERHEART.GENERAL.Tiers.3'
},
tier4: {
id: 'tier4',
label: 'DAGGERHEART.GENERAL.Tiers.tier4',
value: 4
4: {
id: 4,
label: 'DAGGERHEART.GENERAL.Tiers.4'
}
};

View file

@ -1346,3 +1346,18 @@ export const itemResourceTypes = {
label: 'DAGGERHEART.CONFIG.ItemResourceType.diceValue'
}
};
export const beastformTypes = {
normal: {
id: 'normal',
label: 'DAGGERHEART.CONFIG.BeastformType.normal'
},
evolved: {
id: 'evolved',
label: 'DAGGERHEART.CONFIG.BeastformType.evolved'
},
hybrid: {
id: 'hybrid',
label: 'DAGGERHEART.CONFIG.BeastformType.hybrid'
}
};

View file

@ -113,7 +113,7 @@ export class DHResourceData extends foundry.abstract.DataModel {
}),
value: new fields.EmbeddedDataField(DHActionDiceData),
valueAlt: new fields.EmbeddedDataField(DHActionDiceData)
}
};
}
}
@ -134,6 +134,6 @@ export class DHDamageData extends DHResourceData {
label: 'Type'
}
)
}
};
}
}

View file

@ -163,7 +163,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
}
getRollData(data = {}) {
if(!this.actor) return null;
if (!this.actor) return null;
const actorData = this.actor.getRollData(false);
// Add Roll results to RollDatas
@ -178,7 +178,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
}
async use(event, ...args) {
if(!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
if (!this.actor) throw new Error("An Action can't be used outside of an Actor context.");
const isFastForward = event.shiftKey || (!this.hasRoll && !this.hasSave);
// Prepare base Config

View file

@ -10,10 +10,10 @@ export default class DhBeastformAction extends DHBaseAction {
const abort = await this.handleActiveTransformations();
if (abort) return;
const beastformUuid = await BeastformDialog.configure(beastformConfig);
if (!beastformUuid) return;
const { selected, evolved, hybrid } = await BeastformDialog.configure(beastformConfig);
if (!selected) return;
await this.transform(beastformUuid);
await this.transform(selected, evolved, hybrid);
}
prepareBeastformConfig(config) {
@ -29,21 +29,48 @@ export default class DhBeastformAction extends DHBaseAction {
};
}
async transform(beastformUuid) {
const beastform = await foundry.utils.fromUuid(beastformUuid);
this.actor.createEmbeddedDocuments('Item', [beastform.toObject()]);
async transform(selectedForm, evolvedData, hybridData) {
const formData = evolvedData?.form ? evolvedData.form.toObject() : selectedForm.toObject();
const beastformEffect = formData.effects.find(x => x.type === 'beastform');
if (!beastformEffect) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
return;
}
if (evolvedData?.form) {
const evolvedForm = selectedForm.effects.find(x => x.type === 'beastform');
if (!evolvedForm) {
ui.notifications.error('DAGGERHEART.UI.Notifications.beastformMissingEffect');
return;
}
beastformEffect.changes = [...beastformEffect.changes, ...evolvedForm.changes];
formData.system.features = [...formData.system.features, ...selectedForm.system.features.map(x => x.uuid)];
}
if (selectedForm.system.beastformType === CONFIG.DH.ITEM.beastformTypes.hybrid.id) {
formData.system.advantageOn = Object.values(hybridData.advantages).reduce((advantages, formCategory) => {
Object.keys(formCategory).forEach(advantageKey => {
advantages[advantageKey] = formCategory[advantageKey];
});
return advantages;
}, {});
formData.system.features = [
...formData.system.features,
...Object.values(hybridData.features).flatMap(x => Object.keys(x))
];
}
this.actor.createEmbeddedDocuments('Item', [formData]);
}
async handleActiveTransformations() {
const beastformEffects = this.actor.effects.filter(x => x.type === 'beastform');
if (beastformEffects.length > 0) {
for (let effect of beastformEffects) {
await effect.delete();
}
return true;
}
return false;
const existingEffects = beastformEffects.length > 0;
await this.actor.deleteEmbeddedDocuments(
'ActiveEffect',
beastformEffects.map(x => x.id)
);
return existingEffects;
}
}

View file

@ -22,13 +22,14 @@ export default class DHDamageAction extends DHBaseAction {
formatFormulas(formulas, systemData) {
const formattedFormulas = [];
formulas.forEach(formula => {
if (isNaN(formula.formula)) formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
const same = formattedFormulas.find(f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo);
if(same)
same.formula += ` + ${formula.formula}`;
else
formattedFormulas.push(formula);
})
if (isNaN(formula.formula))
formula.formula = Roll.replaceFormulaData(formula.formula, this.getRollData(systemData));
const same = formattedFormulas.find(
f => setsEqual(f.damageTypes, formula.damageTypes) && f.applyTo === formula.applyTo
);
if (same) same.formula += ` + ${formula.formula}`;
else formattedFormulas.push(formula);
});
return formattedFormulas;
}
@ -41,12 +42,12 @@ export default class DHDamageAction extends DHBaseAction {
applyTo: p.applyTo
}));
if(!formulas.length) return;
if (!formulas.length) return;
formulas = this.formatFormulas(formulas, systemData);
const config = {
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: this.name }),
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: game.i18n.localize(this.name) }),
roll: formulas,
targets: systemData.targets.filter(t => t.hit) ?? data.targets,
hasSave: this.hasSave,

View file

@ -16,10 +16,12 @@ export default class DHHealingAction extends DHBaseAction {
async rollHealing(event, data) {
const systemData = data.system ?? data;
let formulas = [{
let formulas = [
{
formula: this.getFormulaValue(data).getFormula(this.actor),
applyTo: this.healing.applyTo
}];
}
];
const config = {
title: game.i18n.format('DAGGERHEART.UI.Chat.healingRoll.title', {

View file

@ -26,6 +26,13 @@ export default class BeastformEffect extends foundry.abstract.TypeDataModel {
};
}
async _onCreate() {
if (this.parent.parent?.type === 'character') {
this.parent.parent.system.primaryWeapon?.update?.({ 'system.equipped': false });
this.parent.parent.system.secondayWeapon?.update?.({ 'system.equipped': false });
}
}
async _preDelete() {
if (this.parent.parent.type === 'character') {
const update = {

View file

@ -18,10 +18,11 @@ export default class DhpAdversary extends BaseDataActor {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
tier: new fields.StringField({
tier: new fields.NumberField({
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.tiers,
initial: CONFIG.DH.GENERAL.tiers.tier1.id
initial: CONFIG.DH.GENERAL.tiers[1].id
}),
type: new fields.StringField({
required: true,
@ -52,7 +53,7 @@ export default class DhpAdversary extends BaseDataActor {
})
}),
resources: new fields.SchemaField({
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
stress: resourceField(0, 'DAGGERHEART.GENERAL.stress', true)
}),
attack: new ActionField({

View file

@ -3,6 +3,7 @@ import ForeignDocumentUUIDField from '../fields/foreignDocumentUUIDField.mjs';
import DhLevelData from '../levelData.mjs';
import BaseDataActor from './base.mjs';
import { attributeField, resourceField, stressDamageReductionRule, bonusField } from '../fields/actorField.mjs';
import ActionField from '../fields/actionField.mjs';
export default class DhCharacter extends BaseDataActor {
static LOCALIZATION_PREFIXES = ['DAGGERHEART.ACTORS.Character'];
@ -21,7 +22,7 @@ export default class DhCharacter extends BaseDataActor {
return {
...super.defineSchema(),
resources: new fields.SchemaField({
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.hitPoints.plural', true),
hitPoints: resourceField(0, 'DAGGERHEART.GENERAL.HitPoints.plural', true),
stress: resourceField(6, 'DAGGERHEART.GENERAL.stress', true),
hope: resourceField(6, 'DAGGERHEART.GENERAL.hope')
}),
@ -87,8 +88,45 @@ 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()),
attack: new ActionField({
initial: {
name: 'Attack',
img: 'icons/skills/melee/unarmed-punch-fist-yellow-red.webp',
_id: foundry.utils.randomID(),
systemPath: 'attack',
type: 'attack',
range: 'melee',
target: {
type: 'any',
amount: 1
},
roll: {
type: 'attack',
trait: 'strength'
},
damage: {
parts: [
{
type: ['physical'],
value: {
custom: {
enabled: true,
formula: '@system.rules.attack.damage.value'
}
}
}
]
}
}
}),
advantageSources: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.ACTORS.Character.advantageSources.label',
hint: 'DAGGERHEART.ACTORS.Character.advantageSources.hint'
}),
disadvantageSources: new fields.ArrayField(new fields.StringField(), {
label: 'DAGGERHEART.ACTORS.Character.disadvantageSources.label',
hint: 'DAGGERHEART.ACTORS.Character.disadvantageSources.hint'
}),
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
roll: new fields.SchemaField({
@ -198,6 +236,15 @@ export default class DhCharacter extends BaseDataActor {
magical: new fields.BooleanField({ initial: false }),
physical: new fields.BooleanField({ initial: false })
}),
attack: new fields.SchemaField({
damage: new fields.SchemaField({
value: new fields.StringField({
required: true,
initial: '@profd4',
label: 'DAGGERHEART.GENERAL.Rules.attack.damage.value.label'
})
})
}),
weapon: new fields.SchemaField({
/* Unimplemented
-> Should remove the lowest damage dice from weapon damage
@ -277,6 +324,24 @@ export default class DhCharacter extends BaseDataActor {
return this.parent.items.find(x => x.type === 'armor' && x.system.equipped);
}
get activeBeastform() {
return this.parent.effects.find(x => x.type === 'beastform');
}
get usedUnarmed() {
const primaryWeaponEquipped = this.primaryWeapon?.system?.equipped;
const secondaryWeaponEquipped = this.secondaryWeapon?.system?.equipped;
return !primaryWeaponEquipped && !secondaryWeaponEquipped
? {
...this.attack,
id: this.attack.id,
name: this.activeBeastform ? 'DAGGERHEART.ITEMS.Beastform.attackName' : this.attack.name,
img: this.activeBeastform ? 'icons/creatures/claws/claw-straight-brown.webp' : this.attack.img,
actor: this.parent
}
: null;
}
get sheetLists() {
const ancestryFeatures = [],
communityFeatures = [],
@ -457,9 +522,6 @@ export default class DhCharacter extends BaseDataActor {
const data = super.getRollData();
return {
...data,
...this.resources.tokens,
...this.resources.dice,
...this.bonuses,
tier: this.tier,
level: this.levelData.level.current
};

View file

@ -18,10 +18,11 @@ export default class DhEnvironment extends BaseDataActor {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
tier: new fields.StringField({
tier: new fields.NumberField({
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.tiers,
initial: CONFIG.DH.GENERAL.tiers.tier1.id
initial: CONFIG.DH.GENERAL.tiers[1].id
}),
type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }),
impulses: new fields.StringField(),

View file

@ -19,10 +19,16 @@ export default class DHBeastform extends BaseDataItem {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
tier: new fields.StringField({
beastformType: new fields.StringField({
required: true,
choices: CONFIG.DH.ITEM.beastformTypes,
initial: CONFIG.DH.ITEM.beastformTypes.normal.id
}),
tier: new fields.NumberField({
required: true,
integer: true,
choices: CONFIG.DH.GENERAL.tiers,
initial: CONFIG.DH.GENERAL.tiers.tier1.id
initial: CONFIG.DH.GENERAL.tiers[1].id
}),
tokenImg: new fields.FilePathField({
initial: 'icons/svg/mystery-man.svg',
@ -38,9 +44,40 @@ export default class DHBeastform extends BaseDataItem {
height: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true }),
width: new fields.NumberField({ integer: true, min: 1, initial: null, nullable: true })
}),
mainTrait: new fields.StringField({
required: true,
choices: CONFIG.DH.ACTOR.abilities,
initial: CONFIG.DH.ACTOR.abilities.agility.id
}),
examples: new fields.StringField(),
advantageOn: new fields.StringField(),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' })
advantageOn: new fields.TypedObjectField(
new fields.SchemaField({
value: new fields.StringField()
})
),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),
evolved: new fields.SchemaField({
maximumTier: new fields.NumberField({
integer: true,
choices: CONFIG.DH.GENERAL.tiers
}),
mainTraitBonus: new fields.NumberField({
required: true,
integer: true,
min: 0,
initial: 0
})
}),
hybrid: new fields.SchemaField({
maximumTier: new fields.NumberField({
integer: true,
choices: CONFIG.DH.GENERAL.tiers,
label: 'DAGGERHEART.ITEMS.Beastform.FIELDS.evolved.maximumTier.label'
}),
beastformOptions: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 }),
advantages: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 }),
features: new fields.NumberField({ required: true, integer: true, initial: 2, min: 2 })
})
};
}
@ -69,7 +106,16 @@ 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 }],
changes: [
...beastformEffect.changes,
{
key: 'system.advantageSources',
mode: 2,
value: Object.values(this.advantageOn)
.map(x => x.value)
.join(', ')
}
],
system: {
characterTokenData: {
tokenImg: this.parent.parent.prototypeToken.texture.src,

View file

@ -24,7 +24,7 @@ export default class DHClass extends BaseDataItem {
integer: true,
min: 1,
initial: 5,
label: 'DAGGERHEART.GENERAL.hitPoints.plural'
label: 'DAGGERHEART.GENERAL.HitPoints.plural'
}),
evasion: new fields.NumberField({ initial: 0, integer: true, label: 'DAGGERHEART.GENERAL.evasion' }),
features: new ForeignDocumentUUIDArrayField({ type: 'Item' }),

View file

@ -147,8 +147,7 @@ export default class D20Roll extends DHRoll {
const difficulty = config.roll.difficulty ?? target.difficulty ?? target.evasion;
target.hit = this.isCritical || roll.total >= difficulty;
});
} else if (config.roll.difficulty)
data.success = roll.isCritical || roll.total >= config.roll.difficulty;
} else if (config.roll.difficulty) data.success = roll.isCritical || roll.total >= config.roll.difficulty;
data.advantage = {
type: config.roll.advantage,
dice: roll.dAdvantage?.denomination,

View file

@ -11,8 +11,8 @@ export default class DamageRoll extends DHRoll {
static DefaultDialog = DamageDialog;
static async buildEvaluate(roll, config = {}, message = {}) {
if ( config.evaluate !== false ) {
for ( const roll of config.roll ) await roll.roll.evaluate();
if (config.evaluate !== false) {
for (const roll of config.roll) await roll.roll.evaluate();
}
roll._evaluated = true;
const parts = config.roll.map(r => this.postEvaluate(r));
@ -27,7 +27,7 @@ export default class DamageRoll extends DHRoll {
roll: roll.roll,
type: config.type,
modifierTotal: this.calculateTotalModifiers(roll.roll)
}
};
}
static async buildPost(roll, config, message) {
@ -46,33 +46,33 @@ export default class DamageRoll extends DHRoll {
resource.total += r.total;
resource.parts.push(r);
unified[r.applyTo] = resource;
})
});
return unified;
}
static formatGlobal(rolls) {
let formula, total;
const applyTo = new Set(rolls.flatMap(r => r.applyTo));
if(applyTo.size > 1) {
if (applyTo.size > 1) {
const data = {};
rolls.forEach(r => {
if(data[r.applyTo]) {
data[r.applyTo].formula += ` + ${r.formula}` ;
data[r.applyTo].total += r.total ;
if (data[r.applyTo]) {
data[r.applyTo].formula += ` + ${r.formula}`;
data[r.applyTo].total += r.total;
} else {
data[r.applyTo] = {
formula: r.formula,
total: r.total
}
};
}
});
formula = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.formula}`, '');
total = Object.entries(data).reduce((a, [k,v]) => a + ` ${k}: ${v.total}`, '');
formula = Object.entries(data).reduce((a, [k, v]) => a + ` ${k}: ${v.formula}`, '');
total = Object.entries(data).reduce((a, [k, v]) => a + ` ${k}: ${v.total}`, '');
} else {
formula = rolls.map(r => r.formula).join(' + ');
total = rolls.reduce((a,c) => a + c.total, 0)
total = rolls.reduce((a, c) => a + c.total, 0);
}
return {formula, total}
return { formula, total };
}
applyBaseBonus(part) {
@ -94,17 +94,17 @@ export default class DamageRoll extends DHRoll {
}
constructFormula(config) {
this.options.roll.forEach( part => {
part.roll = new Roll(part.formula);
this.constructFormulaPart(config, part)
})
this.options.roll.forEach(part => {
part.roll = new Roll(Roll.replaceFormulaData(part.formula, config.data));
this.constructFormulaPart(config, part);
});
return this.options.roll;
}
constructFormulaPart(config, part) {
part.roll.terms = Roll.parse(part.roll.formula, config.data);
if(part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id) {
part.modifiers = this.applyBaseBonus(part);
this.addModifiers(part);
part.modifiers?.forEach(m => {

View file

@ -57,13 +57,14 @@ export default class DHRoll extends Roll {
// Create Chat Message
if (config.source?.message) {
if(Object.values(config.roll)?.length) {
const pool = foundry.dice.terms.PoolTerm.fromRolls(Object.values(config.roll).flatMap(r => r.parts.map(p => p.roll)));
if (Object.values(config.roll)?.length) {
const pool = foundry.dice.terms.PoolTerm.fromRolls(
Object.values(config.roll).flatMap(r => r.parts.map(p => p.roll))
);
roll = Roll.fromTerms([pool]);
}
if (game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
} else
config.message = await this.toMessage(roll, config);
} else config.message = await this.toMessage(roll, config);
}
static postEvaluate(roll, config = {}) {
@ -76,7 +77,7 @@ export default class DHRoll extends Roll {
formula: d.formula,
results: d.results
}))
}
};
}
static async toMessage(roll, config) {

View file

@ -63,19 +63,17 @@ export default class DualityRoll extends D20Roll {
}
setRallyChoices() {
return this.data?.parent?.effects.reduce((a,c) => {
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 });
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];
if (!this.rallyFaces) return null;
if (this.hasDisadvantage || this.hasAdvantage) return this.dice[3];
else return this.dice[2];
}
get rallyFaces() {
@ -129,13 +127,13 @@ export default class DualityRoll extends D20Roll {
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'];
if (this.advantageNumber > 1) advDie.modifiers = ['kh'];
this.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
advDie
);
}
if(this.rallyFaces)
if (this.rallyFaces)
this.terms.push(
new foundry.dice.terms.OperatorTerm({ operator: this.hasDisadvantage ? '-' : '+' }),
new foundry.dice.terms.Die({ faces: this.rallyFaces })
@ -181,7 +179,7 @@ export default class DualityRoll extends D20Roll {
label: roll.totalLabel
};
if(roll._rallyIndex && roll.data?.parent)
if (roll._rallyIndex && roll.data?.parent)
roll.data.parent.deleteEmbeddedDocuments('ActiveEffect', [roll._rallyIndex]);
setDiceSoNiceForDualityRoll(roll, data.advantage.type);

View file

@ -370,6 +370,7 @@ export default class DhpActor extends Actor {
getRollData() {
const rollData = super.getRollData();
rollData.system = this.system.getRollData();
rollData.prof = this.system.proficiency ?? 1;
rollData.cast = this.system.spellcastModifier ?? 1;
return rollData;
@ -403,24 +404,28 @@ export default class DhpActor extends Actor {
Object.entries(damages).forEach(([key, damage]) => {
damage.parts.forEach(part => {
if(part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
if (part.applyTo === CONFIG.DH.GENERAL.healingTypes.hitPoints.id)
part.total = this.calculateDamage(part.total, part.damageTypes);
const update = updates.find(u => u.key === key);
if(update) {
if (update) {
update.value += part.total;
update.damageTypes.add(...new Set(part.damageTypes));
} else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) })
})
} else updates.push({ value: part.total, key, damageTypes: new Set(part.damageTypes) });
});
});
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, damages) === false) return null;
if(!updates.length) return;
if (!updates.length) return;
const hpDamage = updates.find(u => u.key === CONFIG.DH.GENERAL.healingTypes.hitPoints.id);
if(hpDamage) {
if (hpDamage) {
hpDamage.value = this.convertDamageToThreshold(hpDamage.value);
if (this.type === 'character' && this.system.armor && this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)) {
if (
this.type === 'character' &&
this.system.armor &&
this.#canReduceDamage(hpDamage.value, hpDamage.damageTypes)
) {
const armorStackResult = await this.owner.query('armorStack', {
actorId: this.uuid,
damage: hpDamage.value,
@ -437,8 +442,10 @@ export default class DhpActor extends Actor {
}
}
updates.forEach( u =>
u.value = u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value
updates.forEach(
u =>
(u.value =
u.key === 'fear' || this.system?.resources?.[u.key]?.isReversed === false ? u.value * -1 : u.value)
);
await this.modifyResource(updates);
@ -447,7 +454,6 @@ export default class DhpActor extends Actor {
}
calculateDamage(baseDamage, type) {
if (this.canResist(type, 'immunity')) return 0;
if (this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
@ -472,12 +478,12 @@ export default class DhpActor extends Actor {
}
async takeHealing(resources) {
const updates = Object.entries(resources).map(([key, value]) => (
{
const updates = Object.entries(resources).map(([key, value]) => ({
key: key,
value: !(key === 'fear' || this.system?.resources?.[key]?.isReversed === false) ? value.total * -1 : value.total
}
))
value: !(key === 'fear' || this.system?.resources?.[key]?.isReversed === false)
? value.total * -1
: value.total
}));
await this.modifyResource(updates);
}

View file

@ -16,7 +16,21 @@ export default class DHToken extends TokenDocument {
});
bars.sort((a, b) => a.label.compare(b.label));
const invalidAttributes = ['gold', 'levelData', 'actions'];
const invalidAttributes = [
'gold',
'levelData',
'actions',
'biography',
'class',
'multiclass',
'companion',
'notes',
'partner',
'description',
'impulses',
'tier',
'type'
];
const values = attributes.value.reduce((acc, v) => {
const a = v.join('.');
if (invalidAttributes.some(x => a.startsWith(x))) return acc;
@ -38,9 +52,11 @@ export default class DHToken extends TokenDocument {
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.StringField) 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');

View file

@ -1,5 +1,7 @@
export default class DhTooltipManager extends foundry.helpers.interaction.TooltipManager {
async activate(element, options = {}) {
const { TextEditor } = foundry.applications.ux;
let html = options.html;
if (element.dataset.tooltip?.startsWith('#item#')) {
const splitValues = element.dataset.tooltip.slice(6).split('#action#');
@ -10,10 +12,16 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
const item = actionId ? baseItem.system.actions.find(x => x.id === actionId) : baseItem;
if (item) {
const type = actionId ? 'action' : item.type;
const description = await TextEditor.enrichHTML(item.system.description);
for (let feature of item.system.features) {
feature.system.enrichedDescription = await TextEditor.enrichHTML(feature.system.description);
}
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
{
item: item,
description: description,
config: CONFIG.DH
}
);
@ -22,6 +30,26 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
options.direction = this._determineItemTooltipDirection(element);
}
} else {
const attack = element.dataset.tooltip?.startsWith('#attack#');
if (attack) {
const actorUuid = element.dataset.tooltip.slice(8);
const actor = await foundry.utils.fromUuid(actorUuid);
const attack = actor.system.attack;
const description = await TextEditor.enrichHTML(attack.description);
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/attack.hbs`,
{
attack: attack,
description: description,
parent: actor,
config: CONFIG.DH
}
);
this.tooltip.innerHTML = html;
}
const shortRest = element.dataset.tooltip?.startsWith('#shortRest#');
const longRest = element.dataset.tooltip?.startsWith('#longRest#');
if (shortRest || longRest) {
@ -29,11 +57,14 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
const downtimeOptions = shortRest
? CONFIG.DH.GENERAL.defaultRestOptions.shortRest()
: CONFIG.DH.GENERAL.defaultRestOptions.longRest();
const move = downtimeOptions[key];
const description = await TextEditor.enrichHTML(move.description);
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/downtime.hbs`,
{
move: move
move: move,
description: description
}
);

View file

@ -0,0 +1,80 @@
export default function DhDamageEnricher(match, _options) {
const parts = match[1].split('|').map(x => x.trim());
let value = null,
type = null;
parts.forEach(part => {
const split = part.split(':').map(x => x.toLowerCase().trim());
if (split.length === 2) {
switch (split[0]) {
case 'value':
value = split[1];
break;
case 'type':
type = split[1];
break;
}
}
});
if (!value || !value) return match[0];
return getDamageMessage(value, type, match[0]);
}
function getDamageMessage(damage, type, defaultElement) {
const typeIcons = type
.replace('[', '')
.replace(']', '')
.split(',')
.map(x => x.trim())
.map(x => {
return CONFIG.DH.GENERAL.damageTypes[x]?.icon ?? null;
})
.filter(x => x);
if (!typeIcons.length) return defaultElement;
const iconNodes = typeIcons.map(x => `<i class="fa-solid ${x}"></i>`).join('');
const dualityElement = document.createElement('span');
dualityElement.innerHTML = `
<button class="enriched-damage-button"
data-value="${damage}"
data-type="${type}"
data-tooltip="${game.i18n.localize('DAGGERHEART.GENERAL.damage')}"
>
${damage}
${iconNodes}
</button>
`;
return dualityElement;
}
export const renderDamageButton = async event => {
const button = event.currentTarget,
value = button.dataset.value,
type = button.dataset.type
.replace('[', '')
.replace(']', '')
.split(',')
.map(x => x.trim());
const config = {
event: event,
title: game.i18n.localize('Damage Roll'),
data: { bonuses: [] },
source: {},
roll: [
{
formula: value,
applyTo: CONFIG.DH.GENERAL.healingTypes.hitPoints.id,
type: type
}
]
};
CONFIG.Dice.daggerheart.DamageRoll.build(config);
};

View file

@ -8,7 +8,7 @@ export default function DhDualityRollEnricher(match, _options) {
return getDualityMessage(roll);
}
export function getDualityMessage(roll) {
function getDualityMessage(roll) {
const traitLabel =
roll.trait && abilities[roll.trait]
? game.i18n.format('DAGGERHEART.GENERAL.check', {

View file

@ -0,0 +1,19 @@
export default async function DhEffectEnricher(match, _options) {
const effect = await foundry.utils.fromUuid(match[1]);
if (!effect) return match[0];
const dualityElement = document.createElement('span');
dualityElement.innerHTML = `
<a class="flexrow enriched-effect"
data-link
draggable="true"
data-uuid="${match[1]}"
data-tooltip="${game.i18n.localize('DAGGERHEART.UI.Tooltip.dragApplyEffect')}"
>
<img src="icons/svg/aura.svg" style="width: 24px;" />
<span>${effect.name}</span>
</a>
`;
return dualityElement;
}

View file

@ -1,2 +1,43 @@
export { default as DhDualityRollEnricher } from './DualityRollEnricher.mjs';
export { default as DhTemplateEnricher } from './TemplateEnricher.mjs';
import { default as DhDamageEnricher, renderDamageButton } from './DamageEnricher.mjs';
import { default as DhDualityRollEnricher, renderDualityButton } from './DualityRollEnricher.mjs';
import { default as DhEffectEnricher } from './EffectEnricher.mjs';
import { default as DhTemplateEnricher, renderMeasuredTemplate } from './TemplateEnricher.mjs';
export { DhDamageEnricher, DhDualityRollEnricher, DhEffectEnricher, DhTemplateEnricher };
export const enricherConfig = [
{
pattern: /^@Damage\[(.*)\]$/g,
enricher: DhDamageEnricher
},
{
pattern: /\[\[\/dr\s?(.*?)\]\]/g,
enricher: DhDualityRollEnricher
},
{
pattern: /^@Effect\[(.*)\]$/g,
enricher: DhEffectEnricher
},
{
pattern: /^@Template\[(.*)\]$/g,
enricher: DhTemplateEnricher
}
];
export const enricherRenderSetup = element => {
element
.querySelectorAll('.enriched-damage-button')
.forEach(element => element.addEventListener('click', renderDamageButton));
element
.querySelectorAll('.duality-roll-button')
.forEach(element => element.addEventListener('click', renderDualityButton));
element
.querySelectorAll('.measured-template-button')
.forEach(element => element.addEventListener('click', renderMeasuredTemplate));
// element
// .querySelectorAll('.enriched-effect')
// .forEach(element => element.addEventListener('dragstart', dragEnrichedEffect));
};

View file

@ -9,7 +9,7 @@ export default class RegisterHandlebarsHelpers {
damageFormula: this.damageFormula,
damageSymbols: this.damageSymbols,
rollParsed: this.rollParsed,
hasProperty: foundry.utils.hasProperty,
hasProperty: foundry.utils.hasProperty
});
}
static add(a, b) {

View file

@ -1,80 +1,10 @@
import { diceTypes, getDiceSoNicePresets, range } from '../config/generalConfig.mjs';
import Tagify from '@yaireo/tagify';
export const loadCompendiumOptions = async compendiums => {
const compendiumValues = [];
for (var compendium of compendiums) {
const values = await getCompendiumOptions(compendium);
compendiumValues.push(values);
}
return compendiumValues;
};
const getCompendiumOptions = async compendium => {
const compendiumPack = await game.packs.get(compendium);
const values = [];
for (var value of compendiumPack.index) {
const document = await compendiumPack.getDocument(value._id);
values.push(document);
}
return values;
};
export const getWidthOfText = (txt, fontsize, allCaps, bold) => {
const text = allCaps ? txt.toUpperCase() : txt;
if (getWidthOfText.c === undefined) {
getWidthOfText.c = document.createElement('canvas');
getWidthOfText.ctx = getWidthOfText.c.getContext('2d');
}
var fontspec = `${bold ? 'bold' : ''} ${fontsize}px` + ' ' + 'Signika, sans-serif';
if (getWidthOfText.ctx.font !== fontspec) getWidthOfText.ctx.font = fontspec;
return getWidthOfText.ctx.measureText(text).width;
};
export const padArray = (arr, len, fill) => {
return arr.concat(Array(len).fill(fill)).slice(0, len);
};
export const getTier = (level, asNr) => {
switch (Math.floor((level + 1) / 3)) {
case 1:
return asNr ? 1 : 'tier1';
case 2:
return asNr ? 2 : 'tier2';
case 3:
return asNr ? 3 : 'tier3';
default:
return asNr ? 0 : 'tier0';
}
};
export const capitalize = string => {
return string.charAt(0).toUpperCase() + string.slice(1);
};
export const getPathValue = (path, entity, numeric) => {
const pathValue = foundry.utils.getProperty(entity, path);
if (pathValue) return numeric ? Number.parseInt(pathValue) : pathValue;
return numeric ? Number.parseInt(path) : path;
};
export const generateId = (title, length) => {
const id = title
.split(' ')
.map((w, i) => {
const p = w.slugify({ replacement: '', strict: true });
return i ? p.titleCase() : p;
})
.join('');
return Number.isNumeric(length) ? id.slice(0, length).padEnd(length, '0') : id;
};
export function rollCommandToJSON(text) {
if (!text) return {};
@ -311,7 +241,6 @@ export function getDocFromElement(element) {
return foundry.utils.fromUuidSync(target.dataset.itemUuid) ?? null;
}
export const itemAbleRollParse = (value, actor, item) => {
if (!value) return value;
@ -326,12 +255,6 @@ export const itemAbleRollParse = (value, actor, item) => {
export const arraysEqual = (a, b) =>
a.length === b.length &&
[...new Set([...a, ...b])].every(
v => a.filter(e => e === v).length === b.filter(e => e === v).length
);
[...new Set([...a, ...b])].every(v => a.filter(e => e === v).length === b.filter(e => e === v).length);
export const setsEqual = (a, b) =>
a.size === b.size &&
[...a].every(
value => b.has(value)
);
export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));

View file

@ -1,8 +1,9 @@
export const preloadHandlebarsTemplates = async function () {
foundry.applications.handlebars.loadTemplates({
'daggerheart.inventory-items': 'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs',
'daggerheart.inventory-item': 'systems/daggerheart/templates/sheets/global/partials/inventory-item-V2.hbs',
})
'daggerheart.inventory-items':
'systems/daggerheart/templates/sheets/global/partials/inventory-fieldset-items-V2.hbs',
'daggerheart.inventory-item': 'systems/daggerheart/templates/sheets/global/partials/inventory-item-V2.hbs'
});
return foundry.applications.handlebars.loadTemplates([
'systems/daggerheart/templates/sheets/global/tabs/tab-navigation.hbs',
'systems/daggerheart/templates/sheets/global/partials/action-item.hbs',
@ -27,6 +28,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/tooltipChips.hbs',
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs',
'systems/daggerheart/templates/dialogs/downtime/activities.hbs'
]);

View file

@ -0,0 +1,160 @@
{
"name": "Agile Scout",
"type": "beastform",
"img": "icons/creatures/mammals/goat-horned-blue.webp",
"system": {
"beastformType": "normal",
"tier": 1,
"tokenImg": "icons/creatures/mammals/goat-horned-blue.webp",
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {
"height": null,
"width": null
},
"mainTrait": "agility",
"advantageOn": {
"tXlf8FvWrgGqfaJu": {
"value": "deceit"
},
"lp1gv9iNUCpC2Fli": {
"value": "locate"
},
"GxDMKUpOeDHzyhAT": {
"value": "sneak"
}
},
"features": [
"Compendium.daggerheart.beastforms.Item.sef9mwD2eRLZ64oV",
"Compendium.daggerheart.beastforms.Item.9ryNrYWjNtOT6DXN"
],
"evolved": {
"mainTraitBonus": 0
},
"hybrid": {
"beastformOptions": 2,
"advantages": 2,
"features": 2
},
"examples": "Fox, Mouse, Weasel, etc."
},
"effects": [
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "m098fyKkAjTFZ6UJ",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [
{
"key": "system.traits.agility.value",
"mode": 2,
"value": "1",
"priority": null
},
{
"key": "system.evasion",
"mode": 2,
"value": "2",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
},
"_key": "!items.effects!6tr99y6wHaJJYy3J.m098fyKkAjTFZ6UJ"
},
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "5mi9ku2R4paP579i",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [],
"disabled": false,
"duration": {
"startTime": null,
"combat": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976985351,
"modifiedTime": 1752976985351,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_key": "!items.effects!6tr99y6wHaJJYy3J.5mi9ku2R4paP579i"
}
],
"folder": null,
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976985346,
"modifiedTime": 1752976987362,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "6tr99y6wHaJJYy3J",
"sort": 100000,
"_key": "!items!6tr99y6wHaJJYy3J"
}

View file

@ -0,0 +1,166 @@
{
"name": "Household Friend",
"type": "beastform",
"img": "icons/creatures/mammals/cat-hunched-glowing-red.webp",
"system": {
"beastformType": "normal",
"tier": 1,
"tokenImg": "icons/creatures/mammals/cat-hunched-glowing-red.webp",
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {
"height": null,
"width": null
},
"mainTrait": "instinct",
"advantageOn": {
"u0mzlWihDHITgh1x": {
"value": "climb"
},
"m53oFXA2SA5jAjWc": {
"value": "locate"
},
"XLPn5Egg9mIuLyVP": {
"value": "protect"
}
},
"features": [
"Compendium.daggerheart.beastforms.Item.0tlnxIxlIw2hl1UE",
"Compendium.daggerheart.beastforms.Item.9ryNrYWjNtOT6DXN"
],
"evolved": {
"mainTraitBonus": 0
},
"hybrid": {
"beastformOptions": 2,
"advantages": 2,
"features": 2
},
"examples": "Cat, Dog, Rabbit, etc."
},
"effects": [
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "d25tcdgssnDvekKR",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [
{
"key": "system.rules.attack.trait",
"mode": 5,
"value": "instinct",
"priority": null
},
{
"key": "system.rules.attack.range",
"mode": 5,
"value": "melee",
"priority": null
},
{
"key": "system.rules.attack.damage.value",
"mode": 5,
"value": "1d8",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
},
"_key": "!items.effects!uxBugKULjn7O1KQc.d25tcdgssnDvekKR"
},
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "idqGh9sm1zBLME1O",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [],
"disabled": false,
"duration": {
"startTime": null,
"combat": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976986453,
"modifiedTime": 1752976986453,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_key": "!items.effects!uxBugKULjn7O1KQc.idqGh9sm1zBLME1O"
}
],
"folder": null,
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976986449,
"modifiedTime": 1752976987362,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "uxBugKULjn7O1KQc",
"sort": 300000,
"_key": "!items!uxBugKULjn7O1KQc"
}

View file

@ -0,0 +1,154 @@
{
"name": "Legendary Beast",
"type": "beastform",
"img": "icons/creatures/magical/spirit-undead-horned-blue.webp",
"system": {
"beastformType": "evolved",
"tier": 3,
"tokenImg": "icons/creatures/magical/spirit-undead-horned-blue.webp",
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {
"height": null,
"width": null
},
"mainTrait": "agility",
"advantageOn": {},
"features": [],
"evolved": {
"mainTraitBonus": 1,
"maximumTier": 1
},
"hybrid": {
"beastformOptions": 2,
"advantages": 2,
"features": 2
},
"examples": ""
},
"effects": [
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "cKAoI5JqYOtGBscd",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [
{
"key": "system.bonuses.damage.physical.bonus",
"mode": 2,
"value": "6",
"priority": null
},
{
"key": "system.bonuses.damage.magical.bonus",
"mode": 2,
"value": "6",
"priority": null
},
{
"key": "system.evasion",
"mode": 2,
"value": "2",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "<p>Pick a Tier 1 Beastform option and become a larger, more powerful version of that creature. While youre in this form, you retain all traits and features from the original form and gain the following bonuses:</p><ul><li><p>- A +6 bonus to damage rolls</p></li><li><p>- A +1 bonus to the trait used by this form</p></li><li><p>- A +2 bonus to evasion</p></li></ul>",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
},
"_key": "!items.effects!mERwC7aMDoIKfZTf.cKAoI5JqYOtGBscd"
},
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "LltLvTqjhk9RseV8",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [],
"disabled": false,
"duration": {
"startTime": null,
"combat": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976989252,
"modifiedTime": 1752976989252,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_key": "!items.effects!mERwC7aMDoIKfZTf.LltLvTqjhk9RseV8"
}
],
"folder": null,
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976989245,
"modifiedTime": 1752976989245,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "mERwC7aMDoIKfZTf",
"sort": 0,
"_key": "!items!mERwC7aMDoIKfZTf"
}

View file

@ -0,0 +1,166 @@
{
"name": "Mighty Strider",
"type": "beastform",
"img": "icons/creatures/mammals/bull-horned-blue.webp",
"system": {
"beastformType": "normal",
"tier": 2,
"tokenImg": "icons/creatures/mammals/bull-horned-blue.webp",
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {
"height": null,
"width": null
},
"mainTrait": "agility",
"advantageOn": {
"Xxr01TwSerOS8qsd": {
"value": "leap"
},
"cASut9AUij2Uf4zm": {
"value": "navigate"
},
"XJie4FhCSwUCg9uN": {
"value": "sprint"
}
},
"features": [
"Compendium.daggerheart.beastforms.Item.YSolAjtv6Sfnai98",
"Compendium.daggerheart.beastforms.Item.P6tWFIZzXWyekw6r"
],
"evolved": {
"mainTraitBonus": 0
},
"hybrid": {
"beastformOptions": 2,
"advantages": 2,
"features": 2
},
"examples": "Camel, Horse, Zebra, etc."
},
"effects": [
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "1KdhXARm6rg2fg22",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [
{
"key": "system.rules.attack.damage.value",
"mode": 5,
"value": "d8 + 1",
"priority": null
},
{
"key": "system.rules.attack.trait",
"mode": 5,
"value": "agility",
"priority": null
},
{
"key": "system.rules.attack.range",
"mode": 5,
"value": "melee",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
},
"_key": "!items.effects!LF68kGAcOTZQ81GB.1KdhXARm6rg2fg22"
},
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "ngEREmS8hzNyshak",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [],
"disabled": false,
"duration": {
"startTime": null,
"combat": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976987358,
"modifiedTime": 1752976987358,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_key": "!items.effects!LF68kGAcOTZQ81GB.ngEREmS8hzNyshak"
}
],
"folder": null,
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976987354,
"modifiedTime": 1752976987362,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "LF68kGAcOTZQ81GB",
"sort": 200000,
"_key": "!items!LF68kGAcOTZQ81GB"
}

View file

@ -0,0 +1,148 @@
{
"name": "Mythic Hybrid",
"type": "beastform",
"img": "icons/creatures/magical/humanoid-silhouette-glowing-pink.webp",
"system": {
"beastformType": "hybrid",
"tier": 4,
"tokenImg": "icons/creatures/magical/humanoid-silhouette-glowing-pink.webp",
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {
"height": null,
"width": null
},
"mainTrait": "strength",
"advantageOn": {},
"features": [],
"evolved": {
"mainTraitBonus": 0
},
"hybrid": {
"beastformOptions": 3,
"advantages": 5,
"features": 3,
"maximumTier": 3
},
"examples": ""
},
"effects": [
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "dvqS0a0ur8xM2swY",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [
{
"key": "system.traits.strength.value",
"mode": 2,
"value": "3",
"priority": null
},
{
"key": "system.evasion",
"mode": 2,
"value": "2",
"priority": null
}
],
"disabled": false,
"duration": {
"startTime": null,
"combat": null,
"seconds": null,
"rounds": null,
"turns": null,
"startRound": null,
"startTurn": null
},
"description": "<p>To transform into this creature, mark 2 additional Stress. Choose any three Beastform options from Tiers 13. Choose a total of five advantages and three features from those options.</p>",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"lastModifiedBy": null
},
"_key": "!items.effects!VI1DyowECDCDdsC1.dvqS0a0ur8xM2swY"
},
{
"type": "beastform",
"name": "Beastform Transformation",
"img": "icons/creatures/abilities/paw-print-pair-purple.webp",
"_id": "xSMy4BO5PuqASEKW",
"system": {
"characterTokenData": {
"tokenImg": null,
"tokenRingImg": "icons/svg/mystery-man.svg",
"tokenSize": {}
},
"advantageOn": [],
"featureIds": [],
"effectIds": []
},
"changes": [],
"disabled": false,
"duration": {
"startTime": null,
"combat": null
},
"description": "",
"origin": null,
"tint": "#ffffff",
"transfer": true,
"statuses": [],
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976990470,
"modifiedTime": 1752976990470,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_key": "!items.effects!VI1DyowECDCDdsC1.xSMy4BO5PuqASEKW"
}
],
"folder": null,
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976990466,
"modifiedTime": 1752976990466,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "VI1DyowECDCDdsC1",
"sort": 0,
"_key": "!items!VI1DyowECDCDdsC1"
}

View file

@ -0,0 +1,34 @@
{
"type": "feature",
"name": "Agile",
"img": "icons/skills/movement/arrow-upward-blue.webp",
"system": {
"description": "<p>Your movement is silent, and you can spend a Hope to move up to Far range without rolling.</p>",
"resource": null,
"originItemType": null,
"subType": null,
"originId": null,
"actions": []
},
"effects": [],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976877948,
"modifiedTime": 1752976906072,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "sef9mwD2eRLZ64oV",
"sort": 100000,
"_key": "!items!sef9mwD2eRLZ64oV"
}

View file

@ -0,0 +1,34 @@
{
"type": "feature",
"name": "Carrier",
"img": "icons/creatures/abilities/bull-head-horns-glowing.webp",
"system": {
"description": "<p>You can carry up to two willing allies with you when you move.</p>",
"resource": null,
"originItemType": null,
"subType": null,
"originId": null,
"actions": []
},
"effects": [],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976866309,
"modifiedTime": 1752976907469,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "YSolAjtv6Sfnai98",
"sort": 200000,
"_key": "!items!YSolAjtv6Sfnai98"
}

View file

@ -0,0 +1,34 @@
{
"type": "feature",
"name": "Companion",
"img": "icons/magic/life/heart-hand-gold-green-light.webp",
"system": {
"description": "<p>When you Help an Ally, you can roll a d8 as your advantage die.</p>",
"resource": null,
"originItemType": null,
"subType": null,
"originId": null,
"actions": []
},
"effects": [],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976848672,
"modifiedTime": 1752976908157,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "0tlnxIxlIw2hl1UE",
"sort": 0,
"_key": "!items!0tlnxIxlIw2hl1UE"
}

View file

@ -0,0 +1,34 @@
{
"type": "feature",
"name": "Evolved",
"img": "icons/creatures/abilities/dragon-breath-purple.webp",
"system": {
"description": "<p>Pick a Tier 1 Beastform option and become a larger, more powerful version of that creature. While youre in this form, you retain all traits and features from the original form and gain the following bonuses:</p><ul><li>A +6 bonus to damage rolls</li><li>A +1 bonus to the trait used by this form</li><li>A +2 bonus to Evasion</li></ul>",
"resource": null,
"originItemType": null,
"subType": null,
"originId": null,
"actions": []
},
"effects": [],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976852417,
"modifiedTime": 1752976908700,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "MG21w4u5wXSGZ5WB",
"sort": 50000,
"_key": "!items!MG21w4u5wXSGZ5WB"
}

View file

@ -0,0 +1,34 @@
{
"type": "feature",
"name": "Fragile",
"img": "icons/magic/life/heart-broken-red.webp",
"system": {
"description": "<p>When you take Major or greater damage, you drop out of Beastform.</p>",
"resource": null,
"originItemType": null,
"subType": null,
"originId": null,
"actions": []
},
"effects": [],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976855944,
"modifiedTime": 1752976909267,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "9ryNrYWjNtOT6DXN",
"sort": 150000,
"_key": "!items!9ryNrYWjNtOT6DXN"
}

View file

@ -0,0 +1,34 @@
{
"type": "feature",
"name": "Trample",
"img": "icons/creatures/mammals/ox-bull-horned-glowing-orange.webp",
"system": {
"description": "<p><strong>Mark a Stress</strong> to move up to Close range in a straight line and make an attack against all targets within Melee range of the line. Targets you succeed against take [[/r d8+1]] physical damage using your Proficiency and are temporarily Vulnerable.</p>",
"resource": null,
"originItemType": null,
"subType": null,
"originId": null,
"actions": []
},
"effects": [],
"folder": "uU8bIoZvXge0rLaU",
"ownership": {
"default": 0,
"k0gmQFlvrPvlTtbh": 3
},
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976867812,
"modifiedTime": 1752976976609,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_id": "P6tWFIZzXWyekw6r",
"sort": -100000,
"_key": "!items!P6tWFIZzXWyekw6r"
}

View file

@ -0,0 +1,23 @@
{
"type": "Item",
"folder": null,
"name": "Beastform Features",
"color": null,
"sorting": "a",
"_id": "uU8bIoZvXge0rLaU",
"description": "",
"sort": 0,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.346",
"systemId": "daggerheart",
"systemVersion": "0.0.1",
"createdTime": 1752976835537,
"modifiedTime": 1752976835537,
"lastModifiedBy": "k0gmQFlvrPvlTtbh"
},
"_key": "!folders!uU8bIoZvXge0rLaU"
}

View file

@ -1,46 +0,0 @@
@import '../../utils/colors.less';
.application.daggerheart.dh-style.views.beastform-selection {
.beastforms-container {
display: flex;
flex-direction: column;
gap: 4px;
.beastforms-tier {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 4px;
.beastform-container {
position: relative;
display: flex;
justify-content: center;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
cursor: pointer;
&.inactive {
opacity: 0.4;
}
img {
width: 100%;
border-radius: 6px;
}
.beastform-title {
position: absolute;
top: 4px;
display: flex;
flex-wrap: wrap;
font-size: 16px;
margin: 0 4px;
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');
}
}
}
}
}

View file

@ -1,15 +1,208 @@
@import '../../utils/colors.less';
@import '../../utils/mixin.less';
.appTheme({
&.beastform-selection {
.beastforms-container .beastforms-tier .beastform-container .beastform-title {
background-image: url('../assets/parchments/dh-parchment-dark.png');
.theme-light .application.daggerheart.dh-style.views.beastform-selection .beastforms-outer-container {
.beastform-title {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
.advanced-container {
.advanced-forms-container {
.advanced-form-container {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
.hybrid-data-wrapper .hybrid-data-container .hybrid-data-inner-container .hybrid-data {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
}
}, {});
.form-features .form-feature {
background-image: url('../assets/parchments/dh-parchment-light.png');
}
}
}
.application.daggerheart.dh-style.views.beastform-selection {
.beastform-nav {
nav {
flex: 1;
a {
white-space: nowrap;
}
}
}
.beastform-title {
position: absolute;
top: 4px;
padding: 0 2px;
display: flex;
flex-wrap: wrap;
text-align: center;
font-size: 16px;
margin: 0 4px;
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');
}
.beastforms-tier {
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 4px;
.beastform-container {
position: relative;
display: flex;
justify-content: center;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
cursor: pointer;
width: 120px;
height: 120px;
&.inactive {
opacity: 0.4;
cursor: default;
}
&.draggable {
cursor: pointer;
filter: drop-shadow(0 0 15px light-dark(@dark-blue, @golden));
}
img {
width: 100%;
border-radius: 6px;
}
}
}
.advanced-container {
display: flex;
flex-direction: column;
align-items: center;
padding-top: 12px;
transition: width 0.3s ease;
h2 {
margin: 0;
}
.advanced-forms-container {
display: grid;
grid-template-columns: 1fr 1fr 1fr;
gap: 16px;
}
.advanced-form-container {
position: relative;
display: flex;
justify-content: center;
border: 1px solid light-dark(#18162e, #f3c267);
border-radius: 6px;
cursor: pointer;
width: 120px;
height: 120px;
align-items: center;
text-align: center;
color: light-dark(@dark, @beige);
background-image: url('../assets/parchments/dh-parchment-dark.png');
&.hybridized {
flex-direction: column;
justify-content: start;
padding-top: 4px;
height: 200px;
width: 100%;
overflow: hidden;
&.empty {
justify-content: center;
}
.beastform-title {
position: initial;
}
}
.empty-form {
display: flex;
flex-direction: column;
align-items: center;
i {
font-size: 24px;
}
}
.beastform-title-wrapper {
height: 44px;
}
.hybrid-data-wrapper {
overflow: auto;
.hybrid-data-container {
display: flex;
flex-direction: column;
gap: 2px;
padding: 0 4px;
label {
font-weight: bold;
}
.hybrid-data-inner-container {
display: flex;
justify-content: center;
flex-wrap: wrap;
gap: 4px;
.hybrid-data {
padding: 0 2px;
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');
opacity: 0.4;
&.active {
opacity: 1;
}
}
}
}
}
}
.form-features {
display: flex;
flex-direction: column;
gap: 8px;
padding: 0 16px;
margin: 8px 0;
.form-feature {
display: flex;
flex-direction: column;
gap: 4px;
padding: 0 4px;
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');
h4 {
text-align: center;
margin: 0;
}
}
}
}
footer {
margin-top: 8px;
display: flex;

View file

@ -12,7 +12,6 @@
@import './downtime/downtime-container.less';
@import './beastform/beastform-container.less';
@import './beastform/sheet.less';
@import './character-creation/creation-action-footer.less';

View file

@ -18,6 +18,7 @@
@import './actors/environment/header.less';
@import './actors/environment/sheet.less';
@import './items/beastform.less';
@import './items/class.less';
@import './items/domain-card.less';
@import './items/feature.less';

View file

@ -0,0 +1,9 @@
.application.sheet.daggerheart.dh-style.beastform {
.settings.tab {
.advantage-on-section {
display: flex;
flex-direction: column;
margin-top: 10px;
}
}
}

View file

@ -21,13 +21,8 @@
width: 80px;
}
.downtime-refresh-container {
margin-top: @fullMargin;
.action-use-button {
width: 100%;
.refresh-title {
font-weight: bold;
}
}
}
@ -365,6 +360,7 @@
width: 80px;
}
}
button {
&.inner-button {
--button-size: 1.25rem;

View file

@ -4,6 +4,27 @@
.dice-title {
display: none;
}
.message-content {
.enriched-effect {
display: flex;
align-items: center;
border: 1px solid black;
width: fit-content;
padding: 0 2px 0 0;
border-radius: 6px;
color: @dark;
background-image: url(../assets/parchments/dh-parchment-light.png);
&:hover {
text-shadow: none;
}
span {
white-space: nowrap;
}
}
}
}
fieldset.daggerheart.chat {

View file

@ -74,6 +74,22 @@
}
}
.tooltip-chips {
display: flex;
justify-content: space-around;
flex-wrap: wrap;
gap: 8px;
.tooltip-chip {
font-size: 18px;
padding: 2px 4px;
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);
}
}
.tooltip-tags {
width: 100%;
display: flex;

View file

@ -144,6 +144,15 @@
"type": "Item",
"private": false,
"flags": {}
},
{
"name": "beastforms",
"label": "Beastforms",
"system": "daggerheart",
"path": "packs/beastforms.db",
"type": "Item",
"private": false,
"flags": {}
}
],
"packFolders": [
@ -157,7 +166,7 @@
"name": "Character Options",
"sorting": "m",
"color": "#000000",
"packs": ["ancestries", "communities", "classes", "subclasses", "domains"]
"packs": ["ancestries", "communities", "classes", "subclasses", "domains", "beastforms"]
},
{
"name": "Items",

View file

@ -0,0 +1,73 @@
<div class="advanced-container">
{{#if (eq selected.system.beastformType 'evolved')}}
<h2>{{localize "DAGGERHEART.ITEMS.Beastform.evolve"}}</h2>
<div class="form-features">
{{#if selectedBeastformEffect}}
<div class="form-feature" data-tooltip="{{concat "#item#" selectedBeastformEffect.uuid}}">
<h4>{{localize "DAGGERHEART.ITEMS.Beastform.evolvedFeatureTitle"}}</h4>
<div>{{{selectedBeastformEffect.description}}}</div>
</div>
{{/if}}
</div>
<div class="advanced-form-container evolved">
{{#if evolved.form}}
<div class="beastform-title">{{concat (localize "DAGGERHEART.CONFIG.BeastformType.evolved") " " evolved.form.name}}</div>
<img src="{{evolved.form.img}}" />
{{else}}
<div class="empty-form">
<i class="fa-solid fa-plus"></i>
<label>{{localize "DAGGERHEART.ITEMS.Beastform.evolvedDrag"}}</label>
</div>
{{/if}}
</div>
{{/if}}
{{#if (eq selected.system.beastformType 'hybrid')}}
<h2>{{localize "DAGGERHEART.ITEMS.Beastform.hybridize"}}</h2>
<div class="form-features">
{{#if selectedBeastformEffect}}
<div class="form-feature" data-tooltip="{{concat "#item#" selectedBeastformEffect.uuid}}">
<h4>{{localize "DAGGERHEART.ITEMS.Beastform.hybridizeFeatureTitle"}}</h4>
<div>{{{selectedBeastformEffect.description}}}</div>
</div>
{{/if}}
</div>
<div class="advanced-forms-container">
{{#each hybridForms as | form key |}}
<div class="advanced-form-container hybridized {{#unless form}}empty{{/unless}}" id="{{key}}">
{{#if form}}
<div class="beastform-title-wrapper">
<div class="beastform-title">{{form.name}}</div>
</div>
<div class="hybrid-data-wrapper">
<div class="hybrid-data-container">
<label>{{localize "DAGGERHEART.GENERAL.features"}}</label>
<div class="hybrid-data-inner-container">
{{#each form.system.features as | feature |}}
<a data-action="toggleHybridFeature" id="{{feature.uuid}}" data-form="{{@../key}}"><div class="hybrid-data {{#if feature.selected}}active{{/if}}" data-tooltip="{{concat "#item#" feature.uuid}}">{{feature.name}}</div></a>
{{/each}}
</div>
</div>
<div class="hybrid-data-container">
<label>{{localize "DAGGERHEART.GENERAL.Advantage.plural"}}</label>
<div class="hybrid-data-inner-container">
{{#each form.system.advantageOn as | advantage id |}}
<a data-action="toggleHybridAdvantage" id="{{id}}" data-form="{{@../key}}"><div class="hybrid-data {{#if advantage.selected}}active{{/if}}">{{advantage.value}}</div></a>
{{/each}}
</div>
</div>
</div>
{{else}}
<div class="empty-form">
<i class="fa-solid fa-plus"></i>
<label>{{localize "DAGGERHEART.ITEMS.Beastform.hybridizeDrag"}}</label>
</div>
{{/if}}
</div>
{{/each}}
</div>
{{/if}}
</div>

View file

@ -0,0 +1,8 @@
<div class="beastforms-tier">
{{#each tier.values as |form uuid|}}
<div data-action="selectBeastform" data-uuid="{{uuid}}" data-tooltip="{{concat "#item#" uuid}}" class="beastform-container {{#unless form.selected}}inactive{{/unless}} {{#if form.draggable}}draggable{{/if}}">
<img src="{{form.value.img}}" />
<div class="beastform-title">{{form.value.name}}</div>
</div>
{{/each}}
</div>

View file

@ -0,0 +1,3 @@
<footer>
<button type="button" data-action="submitBeastform" {{#if (not canSubmit)}}disabled{{/if}}>{{localize "DAGGERHEART.ITEMS.Beastform.transform"}}</button>
</footer>

View file

@ -0,0 +1,11 @@
<section class='tab-navigation'>
<div class='navigation-container beastform-nav'>
<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>
</div>
</section>

View file

@ -1,18 +0,0 @@
<div>
<div class="beastforms-container">
{{#each beastformTiers as |tier tierKey|}}
<fieldset class="beastforms-tier">
<legend>{{tier.label}}</legend>
{{#each tier.values as |form uuid|}}
<div data-action="selectBeastform" data-uuid="{{uuid}}" data-tooltip="{{concat "#item#" uuid}}" class="beastform-container {{#if (and @root.canSubmit (not form.selected))}}inactive{{/if}}">
<img src="{{form.value.img}}" />
<div class="beastform-title">{{form.value.name}}</div>
</div>
{{/each}}
</fieldset>
{{/each}}
</div>
<footer>
<button type="button" data-action="submitBeastform" {{#if (not canSubmit)}}disabled{{/if}}>{{localize "DAGGERHEART.ITEMS.Beastform.transform"}}</button>
</footer>
</div>

View file

@ -19,7 +19,7 @@
<div class="fieldsets-section">
<fieldset class="flex">
<legend>{{localize "DAGGERHEART.GENERAL.hitPoints.plural"}}</legend>
<legend>{{localize "DAGGERHEART.GENERAL.HitPoints.plural"}}</legend>
{{formGroup systemFields.resources.fields.hitPoints.fields.value value=document.system.resources.hitPoints.value label=(localize "DAGGERHEART.ACTORS.Adversary.FIELDS.resources.hitPoints.value.label")}}
{{formGroup systemFields.resources.fields.hitPoints.fields.max value=document.system.resources.hitPoints.max}}
</fieldset>

View file

@ -20,7 +20,7 @@
{{#if (eq source.system.type 'horde')}}
<div class="tag">
<span>{{source.system.hordeHp}}</span>
<span>/{{localize "DAGGERHEART.GENERAL.hitPoints.short"}}</span>
<span>/{{localize "DAGGERHEART.GENERAL.HitPoints.short"}}</span>
</div>
{{/if}}
</div>

View file

@ -17,7 +17,7 @@
<progress class='progress-bar' value='{{source.system.resources.hitPoints.value}}'
max='{{source.system.resources.hitPoints.max}}'></progress>
<div class="status-label">
<h4>{{localize 'DAGGERHEART.GENERAL.attack.hitPoints.short'}}</h4>
<h4>{{localize 'DAGGERHEART.GENERAL.HitPoints.short'}}</h4>
</div>
</div>
@ -92,7 +92,7 @@
<div class="experience-section">
<div class="title">
<side-line-div class="invert"></side-line-div>
<h3>{{localize DAGGERHEART.GENERAL.experience.plural}}</h3>
<h3>{{localize "DAGGERHEART.GENERAL.experience.plural"}}</h3>
<side-line-div></side-line-div>
</div>
<div class="experience-list">
@ -113,6 +113,6 @@
</div>
<line-div></line-div>
<div class="reaction-section">
<button data-action="reactionRoll">{{localize DAGGERHEART.GENERAL.Roll.reaction}}</button>
<button data-action="reactionRoll">{{localize "DAGGERHEART.GENERAL.Roll.reaction"}}</button>
</div>
</aside>

View file

@ -47,7 +47,7 @@
<p>{{document.system.proficiency}}</p>
</div>
<div class="status-label">
<h4>{{localize "DAGGERHEART.GENERAL.proficienc"}}</h4>
<h4>{{localize "DAGGERHEART.GENERAL.proficiency"}}</h4>
</div>
</div>
@ -95,6 +95,9 @@
<side-line-div></side-line-div>
</div>
<ul class="items-sidebar-list">
{{#if document.system.usedUnarmed}}
{{> 'daggerheart.inventory-item' item=document.system.usedUnarmed type='attack' isSidebar=true}}
{{/if}}
{{#each document.items as |item|}}
{{#if item.system.equipped}}
{{> 'daggerheart.inventory-item'
@ -132,7 +135,7 @@
<div class="experience-section">
<div class="title">
<side-line-div class="invert"></side-line-div>
<h3>{{localize "DAGGERHEART.GENERAL.experience.Single"}}</h3>
<h3>{{localize "DAGGERHEART.GENERAL.experience.single"}}</h3>
<side-line-div></side-line-div>
</div>
<div class="experience-list">

View file

@ -15,13 +15,20 @@ Parameters:
- showActions {boolean} : If true show feature's actions.
--}}
<li class="inventory-item" {{#if (eq type 'action' )}}data-action-id="{{item.id}}" {{/if}}
<li class="inventory-item" {{#if (or (eq type 'action' ) (eq type 'attack'))}}data-action-id="{{item.id}}" {{/if}}
data-item-uuid="{{item.uuid}}" data-type="{{type}}" draggable="true">
<div class="inventory-item-header" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
{{!-- Image --}}
<div class="img-portait"
data-action='{{ifThen (hasProperty item "use") "useItem" (ifThen (hasProperty item "toChat") "toChat" "editDoc") }}'
{{#unless hideTooltip}}data-tooltip="#item#{{item.uuid}}" {{/unless}}>
data-action='{{ifThen (or (hasProperty item "use") (eq type 'attack')) "useItem" (ifThen (hasProperty item "toChat") "toChat" "editDoc") }}'
{{#unless hideTooltip}}
{{#if (eq type 'attack')}}
data-tooltip="#attack#{{item.actor.uuid}}"
{{else}}
data-tooltip="#item#{{item.uuid}}"
{{/if}}
{{/unless}}
>
<img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" />
<img class="roll-img" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20">
</div>
@ -30,7 +37,20 @@ Parameters:
<div class="item-label {{#if hideResources}}fullWidth{{/if}}">
{{!-- Item Name --}}
<div class="item-name">{{item.name}}</div>
<div class="item-name">{{localize item.name}}</div>
{{!-- Attack Block Start --}}
{{#if (eq type 'attack')}}
<div class="item-tags">
<div class="tag">
{{localize 'DAGGERHEART.GENERAL.unarmed'}}
</div>
<div class="tag">
{{localize 'DAGGERHEART.CONFIG.ActionType.action'}}
</div>
</div>
{{/if}}
{{!-- Attack Block End --}}
{{!-- Weapon Block Start --}}
{{#if (eq type 'weapon')}}

View file

@ -0,0 +1,207 @@
<li class="inventory-item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-type="{{type}}" draggable="true">
<img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" data-action="useItem" {{#if (not noTooltip)}}data-tooltip="{{concat "#item#" item.uuid}}"{{/if}} />
<div class="item-label-wrapper">
<div class="item-label {{#unless (and (not isSidebar) (or (eq item.system.resource.type 'simple') item.system.quantity))}}fullWidth{{/unless}}">
{{#if isCompanion}}
<a class="item-name" data-action="attackRoll">{{item.name}}</a>
{{else}}
<div class="item-name">{{localize item.name}}</div>
{{/if}}
{{#if (eq type 'weapon')}}
<div class="item-tags">
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' item.system.attack.roll.trait '.short')}}
{{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.short')}}
<span> - </span>
{{item.system.attack.damage.parts.0.value.dice}}{{#if item.system.attack.damage.parts.0.value.bonus}} + {{item.system.attack.damage.parts.0.value.bonus}}{{/if}}
{{#each item.system.attack.damage.parts.0.type as | type | }}
{{#with (lookup @root.config.GENERAL.damageTypes type)}}
<i class="fa-solid {{icon}}"></i>
{{/with}}
{{/each}}
</div>
</div>
{{else}}
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Traits.' item.system.attack.roll.trait '.name')}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Range.' item.system.attack.range '.name')}}
</div>
<div class="tag">
{{item.system.attack.damage.parts.0.value.dice}}{{#if item.system.attack.damage.parts.0.value.bonus}} + {{item.system.attack.damage.parts.0.value.bonus}}{{/if}}
(
{{#each item.system.attack.damage.parts.0.type}}
{{localize (concat 'DAGGERHEART.CONFIG.DamageType.' this '.abbreviation')}}
{{/each}}
)
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Burden.' item.system.burden)}}
</div>
{{/if}}
</div>
{{/if}}
{{#if (eq type 'armor')}}
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{item.system.baseScore}}
</div>
</div>
{{else}}
<div class="item-tags">
<div class="tag">
{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}:
{{item.system.baseScore}}
</div>
<div class="tag">
{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.base"}}:
{{item.system.baseThresholds.major}}
<span>/</span>
{{item.system.baseThresholds.severe}}
</div>
</div>
{{/if}}
{{/if}}
{{#if (eq type 'domainCard')}}
{{#if isSidebar}}
<div class="item-labels">
<div class="label">
{{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
<span> - </span>
{{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
<span> - </span>
<span class="recall-value">{{item.system.recallCost}}</span>
<i class="fa-solid fa-bolt"></i>
</div>
</div>
{{else}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.DomainCardTypes.' item.system.type)}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.GENERAL.Domain.' item.system.domain '.label')}}
</div>
<div class="tag">
<span class="recall-label">{{localize "DAGGERHEART.ITEMS.DomainCard.recallCost"}}: </span>
<span class="recall-value">{{item.system.recallCost}}</span>
</div>
</div>
{{/if}}
{{/if}}
{{#if (eq type 'effect')}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'TYPES.Item.' item.parent.type)}}
<span>: </span>
{{item.parent.name}}
</div>
<div class="tag">
{{#if item.duration.duration}}
{{localize 'DAGGERHEART.EFFECTS.Duration.temporary'}}
{{else}}
{{localize 'DAGGERHEART.EFFECTS.Duration.passive'}}
{{/if}}
</div>
{{#each item.statuses as |status|}}
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.Condition.' status '.name')}}
</div>
{{/each}}
</div>
{{/if}}
{{#if (eq type 'action')}}
<div class="item-tags">
<div class="tag">
{{localize (concat 'DAGGERHEART.ACTIONS.TYPES.' item.type '.name')}}
</div>
<div class="tag">
{{localize (concat 'DAGGERHEART.CONFIG.ActionType.' item.actionType)}}
</div>
</div>
{{/if}}
{{#if (eq type 'attack')}}
<div class="item-tags">
<div class="tag">
{{localize 'DAGGERHEART.GENERAL.unarmed'}}
</div>
<div class="tag">
{{localize 'DAGGERHEART.CONFIG.ActionType.action'}}
</div>
</div>
{{/if}}
</div>
{{#if (and (not isSidebar) (eq item.system.resource.type 'simple'))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}}
{{#if (and (not isSidebar) item.system.quantity)}}
<div class="item-resource">
<input type="number" class="inventory-item-quantity" value="{{item.system.quantity}}" step="1" />
</div>
{{/if}}
</div>
{{#unless hideControls}}
{{#if isActor}}
<div class="controls">
{{#if (eq type 'actor')}}
<a data-action="viewActor" data-potential-adversary="{{categoryAdversary}}" data-adversary="{{item.uuid}}" data-tooltip='{{localize "DAGGERHEART.UI.Tooltip.openActorWorld"}}'>
<i class="fa-solid fa-globe"></i>
</a>
{{/if}}
{{#if (eq type 'adversary')}}
<a data-action="viewAdversary" data-potential-adversary="{{categoryAdversary}}" data-adversary="{{item.uuid}}" data-tooltip='{{localize "DAGGERHEART.UI.Tooltip.openActorWorld"}}'>
<i class="fa-solid fa-globe"></i>
</a>
<a data-action='deleteAdversary' data-potential-adversary="{{categoryAdversary}}" data-adversary="{{item.uuid}}" data-tooltip='{{localize "CONTROLS.CommonDelete"}}'>
<i class='fas fa-trash'></i>
</a>
{{/if}}
</div>
{{else}}
<div class="controls">
{{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.UI.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-hands"></i>
</a>
{{/if}}
{{#if (eq type 'armor')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" data-tooltip="{{#unless item.system.equipped}}{{localize 'DAGGERHEART.UI.Tooltip.equip'}}{{else}}{{localize 'DAGGERHEART.UI.Tooltip.unequip'}}{{/unless}}">
<i class="fa-solid fa-shield"></i>
</a>
{{/if}}
{{#if (eq type 'domainCard')}}
{{#unless item.system.inVault}}
<a data-action="toggleVault" data-tooltip="{{localize 'DAGGERHEART.UI.Tooltip.sendToVault'}}">
<i class="fa-solid fa-arrow-down"></i>
</a>
{{else}}
<a data-action="toggleVault" data-tooltip="{{localize 'DAGGERHEART.UI.Tooltip.sendToLoadout'}}">
<i class="fa-solid fa-arrow-up"></i>
</a>
{{/unless}}
{{/if}}
<a data-action="toChat" data-tooltip="{{localize 'DAGGERHEART.UI.Tooltip.sendToChat'}}"><i class="fa-regular fa-message"></i></a>
<a data-action="triggerContextMenu" data-tooltip="{{localize 'DAGGERHEART.UI.Tooltip.moreOptions'}}"><i class="fa-solid fa-ellipsis-vertical"></i></a>
</div>
{{/if}}
{{else}}
<span></span>
{{/unless}}
<div class="item-description">{{#unless isSidebar}}{{{item.system.description}}}{{/unless}}</div>
{{#if (and (not isSidebar) (eq item.system.resource.type 'diceValue'))}}
{{> "systems/daggerheart/templates/sheets/global/partials/item-resource.hbs"}}
{{/if}}
{{#if featureType}}
<div class="item-buttons">
{{#each item.system.actions as | action |}}
<button type="button" data-action="useAction" data-action-id="{{action.id}}">{{action.name}}</button>
{{/each}}
</div>
{{/if}}
</li>

View file

@ -0,0 +1,29 @@
<section
class='tab {{tabs.advanced.cssClass}} {{tabs.advanced.id}}'
data-tab='{{tabs.advanced.id}}'
data-group='{{tabs.advanced.group}}'
>
{{formGroup systemFields.beastformType value=source.system.beastformType localize=true blank=false}}
{{#if (eq source.system.beastformType 'evolved')}}
<fieldset class="two-columns even">
<legend>{{localize "DAGGERHEART.CONFIG.BeastformType.evolved"}}</legend>
{{formGroup systemFields.evolved.fields.maximumTier value=source.system.evolved.maximumTier localize=true blank=false}}
{{formGroup systemFields.evolved.fields.mainTraitBonus value=source.system.evolved.mainTraitBonus localize=true}}
</fieldset>
{{/if}}
{{#if (eq source.system.beastformType 'hybrid')}}
<fieldset class="one-column">
<legend>{{localize "DAGGERHEART.CONFIG.BeastformType.hybrid"}}</legend>
{{formGroup systemFields.hybrid.fields.maximumTier value=source.system.hybrid.maximumTier localize=true blank=false}}
<div class="nest-inputs">
{{formGroup systemFields.hybrid.fields.beastformOptions value=source.system.hybrid.beastformOptions localize=true}}
{{formGroup systemFields.hybrid.fields.advantages value=source.system.hybrid.advantages localize=true}}
{{formGroup systemFields.hybrid.fields.features value=source.system.hybrid.features localize=true}}
</div>
</fieldset>
{{/if}}
</section>

View file

@ -3,12 +3,22 @@
data-tab='{{tabs.settings.id}}'
data-group='{{tabs.settings.group}}'
>
<div class="two-columns">
{{#if (eq source.system.beastformType 'evolved')}}
{{formGroup systemFields.tier value=source.system.tier localize=true}}
{{formGroup systemFields.examples value=source.system.examples localize=true}}
{{else}}
<div class="two-columns even">
{{formGroup systemFields.tier value=source.system.tier localize=true}}
{{formGroup systemFields.mainTrait value=source.system.mainTrait blank=false localize=true}}
</div>
{{formGroup systemFields.advantageOn value=source.system.advantageOn localize=true}}
{{#unless (eq source.system.beastformType 'hybrid')}}
{{formGroup systemFields.examples value=source.system.examples localize=true}}
<div class="advantage-on-section">
<label>{{localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label"}}</label>
<input class="advantageon-input" value="{{advantageOn}}" />
</div>
{{/unless}}
{{/if}}
<fieldset class="two-columns even">
<legend>{{localize "DAGGERHEART.ITEMS.Beastform.tokenTitle"}}</legend>

View file

@ -2,10 +2,12 @@
<h2 class="downtime-title-container">
<div>{{title}}</div>
</h2>
{{#each moves}}
<strong>{{this.name}}</strong>
<img class="downtime-image" src="{{this.img}}" />
<div>{{{this.description}}}</div>
{{#if (gt this.actions.length 0)}}<button class="action-use-button">{{localize "Action"}}</button>{{/if}}
{{#each moves as | move index |}}
<strong>{{move.name}}</strong>
<img class="downtime-image" src="{{move.img}}" />
<div>{{{move.description}}}</div>
{{#each move.actions as | action index |}}
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}">{{localize action.name}}</button>
{{/each}}
{{/each}}
</div>

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
{{#if item.uses.max}}
<h4 class="tooltip-sub-title">{{localize "DAGGERHEART.GENERAL.uses"}}</h4>

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
<div class="tooltip-information-section triple spaced">
<div class="tooltip-information">
@ -23,7 +23,7 @@
</div>
<div class="tooltip-information-section spaced">
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.hitPoints.plural"}}</label>
<label>{{localize "DAGGERHEART.GENERAL.HitPoints.plural"}}</label>
<div>{{item.system.resources.hitPoints.max}}</div>
</div>
<div class="tooltip-information">

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
<div class="tooltip-information-section">
<div class="tooltip-information full-width">

View file

@ -0,0 +1,29 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{attack.name}}</h2>
<img class="tooltip-image" src="{{attack.img}}" />
<div class="tooltip-description">{{{description}}}</div>
<div class="tooltip-information-section spaced">
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.Trait.single"}}</label>
{{#with (lookup config.ACTOR.abilities attack.roll.trait) as | trait |}}
<div>{{localize trait.label}}</div>
{{/with}}
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.range"}}</label>
{{#with (lookup config.GENERAL.range attack.range) as | range |}}
<div>{{localize range.label}}</div>
{{/with}}
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.damage"}}</label>
<div>{{{damageFormula attack parent}}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.damageType"}}</label>
<div>{{{damageSymbols attack.damage.parts}}}</div>
</div>
</div>
</div>

View file

@ -0,0 +1,8 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{description}}}</div>
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipChips.hbs" chips=item.system.advantageOn label=(localize "DAGGERHEART.ITEMS.Beastform.FIELDS.advantageOn.label")}}
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.features label=(localize "DAGGERHEART.GENERAL.features")}}
</div>

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
<div class="tooltip-information-section">
<div class="tooltip-information full-width">

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
<div class="tooltip-information-section">
<div class="tooltip-information">

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.actions label=(localize "DAGGERHEART.GENERAL.Action.plural") }}
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.effects label=(localize "DAGGERHEART.GENERAL.Effect.plural") }}

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.actions label=(localize "DAGGERHEART.GENERAL.Action.plural") }}
</div>

View file

@ -0,0 +1,6 @@
<h4 class="tooltip-sub-title">{{localize label}}</h4>
<div class="tooltip-chips">
{{#each chips as | chip |}}
<div class="tooltip-chip">{{ifThen chip.value chip.value chip}}</div>
{{/each}}
</div>

View file

@ -6,7 +6,7 @@
<div class="tooltip-tag-label">{{localize feature.name}}</div>
{{#if feature.img}}<img class="tooltip-tag-image" src="{{feature.img}}" />{{/if}}
</div>
<div class="tooltip-tag-description">{{{localize (ifThen feature.description feature.description feature.system.description)}}}</div>
<div class="tooltip-tag-description">{{{localize (ifThen feature.description feature.description (ifThen feature.system.enrichedDescription feature.system.enrichedDescription feature.system.description))}}}</div>
</div>
{{/each}}
</div>

View file

@ -1,7 +1,7 @@
<div class="daggerheart dh-style tooltip">
<h2 class="tooltip-title">{{item.name}}</h2>
<img class="tooltip-image" src="{{item.img}}" />
<div class="tooltip-description">{{{item.system.description}}}</div>
<div class="tooltip-description">{{{description}}}</div>
<div class="tooltip-information-section">
<div class="tooltip-information">