Merge branch 'main' into bug/103-enrich-htmlfield-content-before-its-used-in-applications

This commit is contained in:
Joaquin Pereyra 2025-07-14 13:22:17 -03:00
commit d39d3d37b3
106 changed files with 1556 additions and 532 deletions

View file

@ -41,10 +41,14 @@ Hooks.once('init', () => {
]
);
CONFIG.statusEffects = Object.values(SYSTEM.GENERAL.conditions).map(x => ({
...x,
name: game.i18n.localize(x.name)
}));
CONFIG.statusEffects = [
...CONFIG.statusEffects,
...Object.values(SYSTEM.GENERAL.conditions).map(x => ({
...x,
name: game.i18n.localize(x.name),
systemEffect: true
}))
];
CONFIG.Dice.daggerheart = {
DualityDie: DualityDie,
@ -108,6 +112,8 @@ Hooks.once('init', () => {
}
);
CONFIG.Token.hudClass = applications.hud.DHTokenHUD;
CONFIG.Combat.dataModels = {
base: models.DhCombat
};

View file

@ -280,6 +280,11 @@
}
}
},
"HUD": {
"tokenHUD": {
"genericEffects": "Foundry Effects"
}
},
"Levelup": {
"actions": {
"creatureComfort": {
@ -385,6 +390,7 @@
"default": "Default Ownership"
}
},
"CONFIG": {
"ActionType": {
"passive": "Passive",
@ -673,6 +679,10 @@
"name": "Dice Set"
}
},
"SelectAction": {
"selectType": "Select Action Type",
"selectAction": "Select Action"
},
"Traits": {
"agility": {
"name": "Agility",
@ -950,6 +960,10 @@
"stress": {
"name": "Stress"
}
},
"Attachments": {
"attachHint": "Drop items here to attach them",
"transferHint": "If checked, this effect will be applied to any actor that owns this Effect's parent Item. The effect is always applied if this Item is attached to another one."
}
},
"GENERAL": {
@ -969,12 +983,21 @@
"singular": "Character",
"plural": "Characters"
},
"Cost": {
"single": "Cost",
"plural": "Costs"
},
"Damage": {
"severe": "Severe",
"major": "Major",
"minor": "Minor",
"none": "None"
},
"DamageResistance": {
"none": "None",
"resistance": "Resistance",
"immunity": "Immunity"
},
"DamageThresholds": {
"title": "Damage Thresholds",
"minor": "Minor",
@ -1074,8 +1097,10 @@
"specialization": "Specialization",
"mastery": "Mastery",
"optional": "Optional",
"recovery": "Recovery",
"setup": "Setup",
"equipment": "Equipment"
"equipment": "Equipment",
"attachments": "Attachments"
},
"Tiers": {
"singular": "Tier",
@ -1095,6 +1120,8 @@
"burden": "Burden",
"check": "{check} Check",
"criticalSuccess": "Critical Success",
"damage": "Damage",
"damageType": "Damage Type",
"description": "Description",
"duality": "Duality",
"dualityRoll": "Duality Roll",
@ -1107,17 +1134,25 @@
"inactiveEffects": "Inactive Effects",
"inventory": "Inventory",
"level": "Level",
"max": "Max",
"modifier": "Modifier",
"multiclass": "Multiclass",
"none": "None",
"quantity": "Quantity",
"range": "Range",
"recovery": "Recovery",
"scalable": "Scalable",
"stress": "Stress",
"take": "Take",
"target": "Target",
"title": "Title",
"true": "True",
"type": "Type",
"unarmored": "Unarmored",
"use": "Use"
"use": "Use",
"used": "Used",
"uses": "Uses",
"value": "Value"
},
"ITEMS": {
"Armor": {
@ -1174,6 +1209,7 @@
"spellcastingTrait": "Spellcasting Trait"
},
"Weapon": {
"weaponType": "Weapon Type",
"primaryWeapon": "Primary Weapon",
"secondaryWeapon": "Secondary Weapon"
}
@ -1181,7 +1217,8 @@
"SETTINGS": {
"Appearance": {
"FIELDS": {
"displayFear": { "label": "Fear Display" }
"displayFear": { "label": "Fear Display" },
"showGenericStatusEffects": { "label": "Show Foundry Status Effects" }
},
"fearDisplay": {
"token": "Tokens",
@ -1383,7 +1420,8 @@
"unequip": "Unequip",
"sendToVault": "Send to Vault",
"sendToLoadout": "Send to Loadout",
"makeDeathMove": "Make a Death Move"
"makeDeathMove": "Make a Death Move",
"rangeAndTarget": "Range & Target"
}
}
}

View file

@ -1,5 +1,6 @@
export * as characterCreation from './characterCreation/_module.mjs';
export * as dialogs from './dialogs/_module.mjs';
export * as hud from './hud/_module.mjs';
export * as levelup from './levelup/_module.mjs';
export * as settings from './settings/_module.mjs';
export * as sheets from './sheets/_module.mjs';

View file

@ -11,7 +11,7 @@ export default class CostSelectionDialog extends HandlebarsApplicationMixin(Appl
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'views', 'damage-selection'],
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
position: {
width: 400,
height: 'auto'

View file

@ -11,11 +11,14 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
static DEFAULT_OPTIONS = {
tag: 'form',
id: 'roll-selection',
classes: ['daggerheart', 'views', 'damage-selection'],
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
position: {
width: 400,
height: 'auto'
},
window: {
icon: 'fa-solid fa-dice'
},
actions: {
submitRoll: this.submitRoll
},
@ -34,9 +37,15 @@ export default class DamageDialog extends HandlebarsApplicationMixin(Application
}
};
get title() {
return game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
}
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.title = this.config.title;
context.title = this.config.title
? this.config.title
: game.i18n.localize('DAGGERHEART.EFFECTS.ApplyLocations.damageRoll.name');
context.extraFormula = this.config.extraFormula;
context.formula = this.roll.constructFormula(this.config);
return context;

View file

@ -11,7 +11,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
this.actor = actor;
this.damage = damage;
const canApplyArmor = actor.system.armorApplicableDamageTypes[damageType];
const canApplyArmor = damageType.every(t => actor.system.armorApplicableDamageTypes[t] === true);
const maxArmorMarks = canApplyArmor
? Math.min(
actor.system.armorScore - actor.system.armor.system.marks.value,
@ -110,7 +110,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
? {
value:
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress,
maxTotal: this.actor.system.resources.stress.maxTotal
max: this.actor.system.resources.stress.max
}
: null;
@ -197,7 +197,7 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
: 0;
const currentStress =
this.actor.system.resources.stress.value + selectedStressMarks.length + stressReductionStress;
if (currentStress + stressReduction.cost > this.actor.system.resources.stress.maxTotal) {
if (currentStress + stressReduction.cost > this.actor.system.resources.stress.max) {
ui.notifications.info(game.i18n.localize('DAGGERHEART.UI.Notifications.notEnoughStress'));
return;
}

View file

@ -23,7 +23,7 @@ export default class DamageSelectionDialog extends HandlebarsApplicationMixin(Ap
static DEFAULT_OPTIONS = {
tag: 'form',
classes: ['daggerheart', 'views', 'damage-selection'],
classes: ['daggerheart', 'dialog', 'dh-style', 'views', 'damage-selection'],
position: {
width: 400,
height: 'auto'

View file

@ -0,0 +1 @@
export { default as DHTokenHUD } from './tokenHud.mjs';

View file

@ -0,0 +1,84 @@
export default class DHTokenHUD extends TokenHUD {
static DEFAULT_OPTIONS = {
classes: ['daggerheart']
};
/** @override */
static PARTS = {
hud: {
root: true,
template: 'systems/daggerheart/templates/hud/tokenHUD.hbs'
}
};
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.systemStatusEffects = Object.keys(context.statusEffects).reduce((acc, key) => {
const effect = context.statusEffects[key];
if (effect.systemEffect) acc[key] = effect;
return acc;
}, {});
const useGeneric = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects;
context.genericStatusEffects = useGeneric
? Object.keys(context.statusEffects).reduce((acc, key) => {
const effect = context.statusEffects[key];
if (!effect.systemEffect) acc[key] = effect;
return acc;
}, {})
: null;
return context;
}
_getStatusEffectChoices() {
// Include all HUD-enabled status effects
const choices = {};
for (const status of CONFIG.statusEffects) {
if (
status.hud === false ||
(foundry.utils.getType(status.hud) === 'Object' &&
status.hud.actorTypes?.includes(this.document.actor.type) === false)
) {
continue;
}
choices[status.id] = {
_id: status._id,
id: status.id,
systemEffect: status.systemEffect,
title: game.i18n.localize(status.name ?? /** @deprecated since v12 */ status.label),
src: status.img ?? /** @deprecated since v12 */ status.icon,
isActive: false,
isOverlay: false
};
}
// Update the status of effects which are active for the token actor
const activeEffects = this.actor?.effects || [];
for (const effect of activeEffects) {
for (const statusId of effect.statuses) {
const status = choices[statusId];
if (!status) continue;
if (status._id) {
if (status._id !== effect.id) continue;
} else {
if (effect.statuses.size !== 1) continue;
}
status.isActive = true;
if (effect.getFlag('core', 'overlay')) status.isOverlay = true;
break;
}
}
// Flag status CSS class
for (const status of Object.values(choices)) {
status.cssClass = [status.isActive ? 'active' : null, status.isOverlay ? 'overlay' : null].filterJoin(' ');
}
return choices;
}
}

View file

@ -223,8 +223,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
context.achievements = {
proficiency: {
old: this.actor.system.proficiency.total,
new: this.actor.system.proficiency.total + achivementProficiency,
old: this.actor.system.proficiency,
new: this.actor.system.proficiency + achivementProficiency,
shown: achivementProficiency > 0
},
damageThresholds: {
@ -332,16 +332,16 @@ export default class DhCharacterLevelUp extends LevelUpBase {
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
},
hitPoints: {
old: this.actor.system.resources.hitPoints.maxTotal,
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
old: this.actor.system.resources.hitPoints.max,
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
},
stress: {
old: this.actor.system.resources.stress.maxTotal,
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
old: this.actor.system.resources.stress.max,
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion.total,
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
old: this.actor.system.evasion,
new: this.actor.system.evasion + (advancement.evasion ?? 0)
}
},
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
@ -349,8 +349,8 @@ export default class DhCharacterLevelUp extends LevelUpBase {
if (!acc) acc = {};
acc[traitKey] = {
label: game.i18n.localize(abilities[traitKey].label),
old: this.actor.system.traits[traitKey].total,
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
old: this.actor.system.traits[traitKey].max,
new: this.actor.system.traits[traitKey].max + advancement.trait[traitKey]
};
}
return acc;

View file

@ -122,12 +122,12 @@ export default class DhCompanionLevelUp extends BaseLevelUp {
context.advancements = {
statistics: {
stress: {
old: this.actor.system.resources.stress.maxTotal,
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
old: this.actor.system.resources.stress.max,
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion.total,
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
old: this.actor.system.evasion,
new: this.actor.system.evasion + (advancement.evasion ?? 0)
}
},
experiences:

View file

@ -157,8 +157,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
context.achievements = {
proficiency: {
old: this.actor.system.proficiency.total,
new: this.actor.system.proficiency.total + achivementProficiency,
old: this.actor.system.proficiency,
new: this.actor.system.proficiency + achivementProficiency,
shown: achivementProficiency > 0
},
damageThresholds: {
@ -265,16 +265,16 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
new: context.achievements.proficiency.new + (advancement.proficiency ?? 0)
},
hitPoints: {
old: this.actor.system.resources.hitPoints.maxTotal,
new: this.actor.system.resources.hitPoints.maxTotal + (advancement.hitPoint ?? 0)
old: this.actor.system.resources.hitPoints.max,
new: this.actor.system.resources.hitPoints.max + (advancement.hitPoint ?? 0)
},
stress: {
old: this.actor.system.resources.stress.maxTotal,
new: this.actor.system.resources.stress.maxTotal + (advancement.stress ?? 0)
old: this.actor.system.resources.stress.max,
new: this.actor.system.resources.stress.max + (advancement.stress ?? 0)
},
evasion: {
old: this.actor.system.evasion.total,
new: this.actor.system.evasion.total + (advancement.evasion ?? 0)
old: this.actor.system.evasion,
new: this.actor.system.evasion + (advancement.evasion ?? 0)
}
},
traits: Object.keys(this.actor.system.traits).reduce((acc, traitKey) => {
@ -282,8 +282,8 @@ export default class DhlevelUp extends HandlebarsApplicationMixin(ApplicationV2)
if (!acc) acc = {};
acc[traitKey] = {
label: game.i18n.localize(abilities[traitKey].label),
old: this.actor.system.traits[traitKey].total,
new: this.actor.system.traits[traitKey].total + advancement.trait[traitKey]
old: this.actor.system.traits[traitKey].value,
new: this.actor.system.traits[traitKey].value + advancement.trait[traitKey]
};
}
return acc;

View file

@ -56,10 +56,6 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
id: 'effect',
template: 'systems/daggerheart/templates/sheets-settings/action-settings/effect.hbs'
}
/* form: {
id: 'action',
template: 'systems/daggerheart/templates/config/action.hbs'
} */
};
static TABS = {
@ -161,7 +157,7 @@ export default class DHActionConfig extends DaggerheartSheet(ApplicationV2) {
container = foundry.utils.getProperty(this.action.parent, this.action.systemPath);
let newActions;
if (Array.isArray(container)) {
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject()); // Find better way
newActions = foundry.utils.getProperty(this.action.parent, this.action.systemPath).map(x => x.toObject());
if (!newActions.findSplice(x => x._id === data._id, data)) newActions.push(data);
} else newActions = data;

View file

@ -27,7 +27,12 @@ export default class CharacterSheet extends DHBaseActorSheet {
window: {
resizable: true
},
dragDrop: [],
dragDrop: [
{
dragSelector: '[data-item-id][draggable="true"]',
dropSelector: null
}
],
contextMenus: [{
handler: CharacterSheet.#getDomainCardContextOptions,
selector: '[data-item-uuid][data-type="domainCard"]',
@ -599,7 +604,59 @@ export default class CharacterSheet extends DHBaseActorSheet {
await doc?.update({ 'system.inVault': !doc.system.inVault });
}
/**
* Use a item
* @type {ApplicationClickAction}
*/
static async useItem(event, button) {
const item = this.getItem(button);
if (!item) return;
// Should dandle its actions. Or maybe they'll be separate buttons as per an Issue on the board
if (item.type === 'feature') {
item.use(event);
} else if (item instanceof ActiveEffect) {
item.toChat(this);
} else {
const wasUsed = await item.use(event);
if (wasUsed && item.type === 'weapon') {
Hooks.callAll(CONFIG.DH.HOOKS.characterAttack, {});
}
}
}
/**
* Use an action
* @type {ApplicationClickAction}
*/
static async useAction(event, button) {
const item = this.getItem(button);
if (!item) return;
const action = item.system.actions.find(x => x.id === button.dataset.actionId);
if (!action) return;
action.use(event);
}
async _onDragStart(event) {
const item = this.getItem(event);
const dragData = {
type: item.documentName,
uuid: item.uuid
};
event.dataTransfer.setData('text/plain', JSON.stringify(dragData));
super._onDragStart(event);
}
async _onDrop(event) {
// Prevent event bubbling to avoid duplicate handling
event.preventDefault();
event.stopPropagation();
super._onDrop(event);
this._onDropItem(event, TextEditor.getDragEventData(event));
}

View file

@ -1,5 +1,6 @@
export { default as DHApplicationMixin } from './application-mixin.mjs';
export { default as DHBaseItemSheet } from './base-item.mjs';
export { default as DHHeritageSheet } from './heritage-sheet.mjs';
export { default as DHItemAttachmentSheet } from './item-attachment-sheet.mjs';
export { default as DHBaseActorSheet } from './base-actor.mjs';
export { default as DHBaseActorSettings } from './actor-setting.mjs';

View file

@ -126,7 +126,7 @@ export default class DHBaseActorSheet extends DHApplicationMixin(ActorSheetV2) {
const systemData = {
name: game.i18n.localize('DAGGERHEART.GENERAL.Experience.single'),
description: `${experience.name} ${experience.total < 0 ? experience.total : `+${experience.total}`}`
description: `${experience.name} ${experience.value.signedString()}`
};
foundry.documents.ChatMessage.implementation.create({

View file

@ -0,0 +1,90 @@
export default function ItemAttachmentSheet(Base) {
return class extends Base {
static DEFAULT_OPTIONS = {
...super.DEFAULT_OPTIONS,
dragDrop: [
...(super.DEFAULT_OPTIONS.dragDrop || []),
{ dragSelector: null, dropSelector: '.attachments-section' }
],
actions: {
...super.DEFAULT_OPTIONS.actions,
removeAttachment: this.#removeAttachment
}
};
static PARTS = {
...super.PARTS,
attachments: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-attachments.hbs',
scrollable: ['.attachments']
}
};
static TABS = {
...super.TABS,
primary: {
...super.TABS?.primary,
tabs: [
...(super.TABS?.primary?.tabs || []),
{ id: 'attachments' }
],
initial: super.TABS?.primary?.initial || 'description',
labelPrefix: super.TABS?.primary?.labelPrefix || 'DAGGERHEART.GENERAL.Tabs'
}
};
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
context.attachedItems = await prepareAttachmentContext(this.document);
}
return context;
}
async _onDrop(event) {
const data = TextEditor.getDragEventData(event);
const attachmentsSection = event.target.closest('.attachments-section');
if (!attachmentsSection) return super._onDrop(event);
event.preventDefault();
event.stopPropagation();
const item = await Item.implementation.fromDropData(data);
if (!item) return;
// Call the data model's public method
await this.document.system.addAttachment(item);
}
static async #removeAttachment(event, target) {
// Call the data model's public method
await this.document.system.removeAttachment(target.dataset.uuid);
}
async _preparePartContext(partId, context) {
await super._preparePartContext(partId, context);
if (partId === 'attachments') {
// Keep this simple UI preparation in the mixin
const attachedUUIDs = this.document.system.attached;
context.attachedItems = await Promise.all(
attachedUUIDs.map(async uuid => {
const item = await fromUuid(uuid);
return {
uuid: uuid,
name: item?.name || 'Unknown Item',
img: item?.img || 'icons/svg/item-bag.svg'
};
})
);
}
return context;
}
};
}

View file

@ -1,10 +1,10 @@
import DHBaseItemSheet from '../api/base-item.mjs';
import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
export default class ArmorSheet extends DHBaseItemSheet {
export default class ArmorSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
/**@inheritdoc */
static DEFAULT_OPTIONS = {
classes: ['armor'],
dragDrop: [{ dragSelector: null, dropSelector: null }],
tagifyConfigs: [
{
selector: '.features-input',
@ -30,7 +30,8 @@ export default class ArmorSheet extends DHBaseItemSheet {
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
}
},
...super.PARTS,
};
/**@inheritdoc */

View file

@ -1,6 +1,7 @@
import DHBaseItemSheet from '../api/base-item.mjs';
import ItemAttachmentSheet from '../api/item-attachment-sheet.mjs';
export default class WeaponSheet extends DHBaseItemSheet {
export default class WeaponSheet extends ItemAttachmentSheet(DHBaseItemSheet) {
/**@inheritdoc */
static DEFAULT_OPTIONS = {
classes: ['weapon'],
@ -29,12 +30,13 @@ export default class WeaponSheet extends DHBaseItemSheet {
effects: {
template: 'systems/daggerheart/templates/sheets/global/tabs/tab-effects.hbs',
scrollable: ['.effects']
}
},
...super.PARTS,
};
/**@inheritdoc */
async _preparePartContext(partId, context) {
super._preparePartContext(partId, context);
await super._preparePartContext(partId, context);
switch (partId) {
case 'settings':
context.features = this.document.system.weaponFeatures.map(x => x.value);

View file

@ -215,7 +215,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
if (message.system.onSave && message.system.targets.find(t => t.id === target.id)?.saved?.success === true)
damage = Math.ceil(damage * (CONFIG.DH.ACTIONS.damageOnSave[message.system.onSave]?.mod ?? 1));
target.actor.takeDamage(damage, message.system.roll.type);
target.actor.takeDamage(damage, message.system.damage.damageType);
}
};

View file

@ -113,7 +113,7 @@ export const adversaryTypes = {
},
social: {
id: 'social',
label: 'DAGGERHEART.CONFIG.AdversaryTypee.social.label',
label: 'DAGGERHEART.CONFIG.AdversaryType.social.label',
description: 'DAGGERHEART.ACTORS.Adversary.social.description'
},
solo: {

View file

@ -7,3 +7,5 @@ export const encounterCountdown = {
simple: 'countdown-encounter-simple',
position: 'countdown-encounter-position'
};
export const itemAttachmentSource = 'attachmentSource';

View file

@ -59,13 +59,13 @@ export const damageTypes = {
id: 'physical',
label: 'DAGGERHEART.CONFIG.DamageType.physical.name',
abbreviation: 'DAGGERHEART.CONFIG.DamageType.physical.abbreviation',
icon: ['fa-hand-fist']
icon: 'fa-hand-fist'
},
magical: {
id: 'magical',
label: 'DAGGERHEART.CONFIG.DamageType.magical.name',
abbreviation: 'DAGGERHEART.CONFIG.DamageType.magical.abbreviation',
icon: ['fa-wand-sparkles']
icon: 'fa-wand-sparkles'
}
};

View file

@ -41,37 +41,37 @@ export const armorFeatures = {
img: 'icons/magic/control/buff-flight-wings-red.webp',
changes: [
{
key: 'system.traits.agility.bonus',
key: 'system.traits.agility.value',
mode: 2,
value: '-1'
},
{
key: 'system.traits.strength.bonus',
key: 'system.traits.strength.value',
mode: 2,
value: '-1'
},
{
key: 'system.traits.finesse.bonus',
key: 'system.traits.finesse.value',
mode: 2,
value: '-1'
},
{
key: 'system.traits.instinct.bonus',
key: 'system.traits.instinct.value',
mode: 2,
value: '-1'
},
{
key: 'system.traits.presence.bonus',
key: 'system.traits.presence.value',
mode: 2,
value: '-1'
},
{
key: 'system.traits.knowledge.bonus',
key: 'system.traits.knowledge.value',
mode: 2,
value: '-1'
},
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-1'
}
@ -89,7 +89,7 @@ export const armorFeatures = {
img: 'icons/magic/movement/abstract-ribbons-red-orange.webp',
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '1'
}
@ -125,7 +125,7 @@ export const armorFeatures = {
img: 'icons/magic/control/control-influence-crown-gold.webp',
changes: [
{
key: 'system.traits.presence.bonus',
key: 'system.traits.presence.value',
mode: 2,
value: '1'
}
@ -143,7 +143,7 @@ export const armorFeatures = {
img: 'icons/commodities/metal/ingot-worn-iron.webp',
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-1'
}
@ -372,12 +372,12 @@ export const armorFeatures = {
img: 'icons/commodities/metal/ingot-stamped-steel.webp',
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-2'
},
{
key: 'system.traits.agility.bonus',
key: 'system.traits.agility.value',
mode: 2,
value: '-1'
}
@ -395,7 +395,7 @@ export const armorFeatures = {
img: 'icons/magic/defensive/barrier-shield-dome-pink.webp',
changes: [
{
key: 'system.bonuses.damageReduction.magical',
key: 'system.resistance.magical.reduction',
mode: 2,
value: '@system.armorScore'
}
@ -413,7 +413,7 @@ export const weaponFeatures = {
{
changes: [
{
key: 'system.bonuses.armorScore',
key: 'system.armorScore',
mode: 2,
value: 'ITEM.@system.tier + 1'
}
@ -422,7 +422,7 @@ export const weaponFeatures = {
{
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-1'
}
@ -474,7 +474,7 @@ export const weaponFeatures = {
{
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-1'
}
@ -529,7 +529,7 @@ export const weaponFeatures = {
img: 'icons/magic/lightning/claws-unarmed-strike-teal.webp',
changes: [
{
key: 'system.proficiency.bonus',
key: 'system.proficiency',
mode: 2,
value: '1'
}
@ -569,7 +569,7 @@ export const weaponFeatures = {
img: 'icons/commodities/metal/mail-plate-steel.webp',
changes: [
{
key: 'system.traits.finesse.bonus',
key: 'system.traits.finesse.value',
mode: 2,
value: '-1'
}
@ -615,7 +615,7 @@ export const weaponFeatures = {
img: 'icons/skills/melee/hand-grip-sword-strike-orange.webp',
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '@system.armorScore'
}
@ -645,7 +645,7 @@ export const weaponFeatures = {
img: 'icons/skills/melee/strike-flail-spiked-pink.webp',
changes: [
{
key: 'system.traits.agility.bonus',
key: 'system.traits.agility.value',
mode: 2,
value: '-1'
}
@ -683,7 +683,7 @@ export const weaponFeatures = {
img: 'icons/skills/melee/sword-shield-stylized-white.webp',
changes: [
{
key: 'system.bonuses.armorScore',
key: 'system.armorScore',
mode: 2,
value: '1'
},
@ -777,7 +777,7 @@ export const weaponFeatures = {
img: 'icons/commodities/currency/coins-crown-stack-gold.webp',
changes: [
{
key: 'system.proficiency.bonus',
key: 'system.proficiency',
mode: 2,
value: '1'
}
@ -819,7 +819,7 @@ export const weaponFeatures = {
img: 'icons/commodities/metal/ingot-worn-iron.webp',
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-1'
}
@ -941,7 +941,7 @@ export const weaponFeatures = {
img: '',
changes: [
{
key: 'system.evasion.bonus',
key: 'system.evasion',
mode: 2,
value: '-1'
},
@ -1031,7 +1031,7 @@ export const weaponFeatures = {
img: 'icons/magic/control/hypnosis-mesmerism-eye.webp',
changes: [
{
key: 'system.traits.presence.bonus',
key: 'system.traits.presence.value',
mode: 2,
value: '2'
}
@ -1088,7 +1088,7 @@ export const weaponFeatures = {
img: 'icons/skills/melee/shield-block-gray-orange.webp',
changes: [
{
key: 'system.bonuses.armorScore',
key: 'system.armorScore',
mode: 2,
value: '1'
}
@ -1218,7 +1218,7 @@ export const weaponFeatures = {
{
key: 'system.bonuses.damage.primaryWeapon.bonus',
mode: 2,
value: '@system.traits.agility.total'
value: '@system.traits.agility.value'
}
]
}

View file

@ -76,11 +76,7 @@ export class DHActionDiceData extends foundry.abstract.DataModel {
};
}
getFormula(actor) {
/* const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : actor.system[this.multiplier]?.total;
return this.custom.enabled
? this.custom.formula
: `${multiplier ?? 1}${this.dice}${this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : ''}`; */
getFormula() {
const multiplier = this.multiplier === 'flat' ? this.flatMultiplier : `@${this.multiplier}`,
bonus = this.bonus ? (this.bonus < 0 ? ` - ${Math.abs(this.bonus)}` : ` + ${this.bonus}`) : '';
return this.custom.enabled ? this.custom.formula : `${multiplier ?? 1}${this.dice}${bonus}`;
@ -93,7 +89,6 @@ export class DHDamageField extends fields.SchemaField {
parts: new fields.ArrayField(new fields.EmbeddedDataField(DHDamageData)),
includeBase: new fields.BooleanField({ initial: false })
};
// if (hasBase) damageFields.includeBase = new fields.BooleanField({ initial: true });
super(damageFields, options, context);
}
}
@ -102,15 +97,19 @@ export class DHDamageData extends foundry.abstract.DataModel {
/** @override */
static defineSchema() {
return {
// ...super.defineSchema(),
base: new fields.BooleanField({ initial: false, readonly: true, label: 'Base' }),
type: new fields.StringField({
choices: CONFIG.DH.GENERAL.damageTypes,
initial: 'physical',
label: 'Type',
nullable: false,
required: true
}),
type: new fields.SetField(
new fields.StringField({
choices: CONFIG.DH.GENERAL.damageTypes,
initial: 'physical',
nullable: false,
required: true
}),
{
label: 'Type',
initial: 'physical',
}
),
resultBased: new fields.BooleanField({
initial: false,
label: 'DAGGERHEART.ACTIONS.Settings.resultBased.label'

View file

@ -179,16 +179,9 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
getRollData(data = {}) {
const actorData = this.actor.getRollData(false);
// Remove when included directly in Actor getRollData
actorData.prof = actorData.proficiency?.value ?? 1;
actorData.cast = actorData.spellcast?.value ?? 1;
// Add Roll results to RollDatas
actorData.result = data.roll?.total ?? 1;
/* actorData.scale = data.costs?.length
? data.costs.reduce((a, c) => {
a[c.type] = c.value;
return a;
}, {})
: 1; */
actorData.scale = data.costs?.length // Right now only return the first scalable cost.
? (data.costs.find(c => c.scalable)?.total ?? 1)
: 1;
@ -338,7 +331,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
.filter(c => c.enabled !== false)
.map(c => {
const resource = this.actor.system.resources[c.type];
return { type: c.type, value: (c.total ?? c.value) * (resource.hasOwnProperty('maxTotal') ? 1 : -1) };
return { type: c.type, value: (c.total ?? c.value) * (resource.isReversed ? 1 : -1) };
});
await this.actor.modifyResource(resources);
@ -391,12 +384,12 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
return false;
}
/* maxTotal is a sign that the resource is inverted, IE it counts upwards instead of down */
/* isReversed is a sign that the resource is inverted, IE it counts upwards instead of down */
const resources = this.actor.system.resources;
return realCosts.reduce(
(a, c) =>
a && resources[c.type].hasOwnProperty('maxTotal')
? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].maxTotal
a && resources[c.type].isReversed
? resources[c.type].value + (c.total ?? c.value) <= resources[c.type].max
: resources[c.type]?.value >= (c.total ?? c.value),
true
);
@ -439,7 +432,7 @@ export default class DHBaseAction extends foundry.abstract.DataModel {
name: actor.actor.name,
img: actor.actor.img,
difficulty: actor.actor.system.difficulty,
evasion: actor.actor.system.evasion?.total
evasion: actor.actor.system.evasion
};
}
/* TARGET */

View file

@ -10,7 +10,10 @@ export default class DHDamageAction extends DHBaseAction {
}
async rollDamage(event, data) {
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + ');
let formula = this.damage.parts.map(p => this.getFormulaValue(p, data).getFormula(this.actor)).join(' + '),
damageTypes = [...new Set(this.damage.parts.reduce((a,c) => a.concat([...c.type]), []))];
damageTypes = !damageTypes.length ? ['physical'] : damageTypes;
if (!formula || formula == '') return;
let roll = { formula: formula, total: formula },
@ -25,6 +28,7 @@ export default class DHDamageAction extends DHBaseAction {
hasSave: this.hasSave,
isCritical: data.system?.roll?.isCritical ?? false,
source: data.system?.source,
damageTypes,
event
};
if (this.hasSave) config.onSave = this.save.damageMod;

View file

@ -5,7 +5,8 @@ import BaseDataActor from './base.mjs';
const resourceField = () =>
new foundry.data.fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
max: new foundry.data.fields.NumberField({ initial: 0, integer: true })
max: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
isReversed: new foundry.data.fields.BooleanField({ initial: true })
});
export default class DhpAdversary extends BaseDataActor {
@ -22,6 +23,7 @@ export default class DhpAdversary extends BaseDataActor {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
tier: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.tiers,
@ -32,7 +34,6 @@ export default class DhpAdversary extends BaseDataActor {
choices: CONFIG.DH.ACTOR.adversaryTypes,
initial: CONFIG.DH.ACTOR.adversaryTypes.standard.id
}),
description: new fields.StringField(),
motivesAndTactics: new fields.StringField(),
notes: new fields.HTMLField(),
difficulty: new fields.NumberField({ required: true, initial: 1, integer: true }),
@ -63,6 +64,7 @@ export default class DhpAdversary extends BaseDataActor {
damage: {
parts: [
{
type: ['physical'],
value: {
multiplier: 'flat'
}
@ -74,7 +76,7 @@ export default class DhpAdversary extends BaseDataActor {
experiences: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField(),
total: new fields.NumberField({ required: true, integer: true, initial: 1 })
value: new fields.NumberField({ required: true, integer: true, initial: 1 })
})
),
bonuses: new fields.SchemaField({

View file

@ -1,5 +1,12 @@
import DHBaseActorSettings from "../../applications/sheets/api/actor-setting.mjs";
const resistanceField = () =>
new foundry.data.fields.SchemaField({
resistance: new foundry.data.fields.BooleanField({ initial: false }),
immunity: new foundry.data.fields.BooleanField({ initial: false }),
reduction: new foundry.data.fields.NumberField({ integer: true, initial: 0 })
});
/**
* Describes metadata about the actor data model type
* @typedef {Object} ActorDataModelMetadata
@ -16,6 +23,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
type: 'base',
isNPC: true,
settingSheet: null,
hasResistances: true
};
}
@ -24,6 +32,21 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
return this.constructor.metadata;
}
/** @inheritDoc */
static defineSchema() {
const fields = foundry.data.fields;
const schema = {};
if(this.metadata.isNPC)
schema.description = new fields.HTMLField({ required: true, nullable: true });
if(this.metadata.hasResistances)
schema.resistance = new fields.SchemaField({
physical: resistanceField(),
magical: resistanceField()
})
return schema;
}
/**
* Obtain a data object used to evaluate any dice rolls associated with this Item Type
* @param {object} [options] - Options which modify the getRollData method.

View file

@ -5,16 +5,15 @@ import BaseDataActor from './base.mjs';
const attributeField = () =>
new foundry.data.fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: null, integer: true }),
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
tierMarked: new foundry.data.fields.BooleanField({ initial: false })
});
const resourceField = max =>
const resourceField = (max, reverse = false) =>
new foundry.data.fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
max: new foundry.data.fields.NumberField({ initial: max, integer: true })
max: new foundry.data.fields.NumberField({ initial: max, integer: true }),
isReversed: new foundry.data.fields.BooleanField({ initial: reverse })
});
const stressDamageReductionRule = () =>
@ -36,12 +35,10 @@ export default class DhCharacter extends BaseDataActor {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
resources: new fields.SchemaField({
hitPoints: new fields.SchemaField({
value: new foundry.data.fields.NumberField({ initial: 0, integer: true }),
bonus: new foundry.data.fields.NumberField({ initial: 0, integer: true })
}),
stress: resourceField(6),
hitPoints: resourceField(0, true),
stress: resourceField(6, true),
hope: resourceField(6),
tokens: new fields.ObjectField(),
dice: new fields.ObjectField()
@ -54,18 +51,17 @@ export default class DhCharacter extends BaseDataActor {
presence: attributeField(),
knowledge: attributeField()
}),
proficiency: new fields.SchemaField({
value: new fields.NumberField({ initial: 1, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true })
}),
evasion: new fields.SchemaField({
bonus: new fields.NumberField({ initial: 0, integer: true })
proficiency: new fields.NumberField({ initial: 1, integer: true }),
evasion: new fields.NumberField({ initial: 0, integer: true }),
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
damageThresholds: new fields.SchemaField({
severe: new fields.NumberField({ integer: true, initial: 0 }),
major: new fields.NumberField({ integer: true, initial: 0 })
}),
experiences: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField(),
value: new fields.NumberField({ integer: true, initial: 0 }),
bonus: new fields.NumberField({ integer: true, initial: 0 })
value: new fields.NumberField({ integer: true, initial: 0 })
})
),
gold: new fields.SchemaField({
@ -99,15 +95,6 @@ export default class DhCharacter extends BaseDataActor {
}),
levelData: new fields.EmbeddedDataField(DhLevelData),
bonuses: new fields.SchemaField({
armorScore: new fields.NumberField({ integer: true, initial: 0 }),
damageReduction: new fields.SchemaField({
physical: new fields.NumberField({ integer: true, initial: 0 }),
magical: new fields.NumberField({ integer: true, initial: 0 })
}),
damageThresholds: new fields.SchemaField({
severe: new fields.NumberField({ integer: true, initial: 0 }),
major: new fields.NumberField({ integer: true, initial: 0 })
}),
roll: new fields.SchemaField({
attack: new fields.NumberField({ integer: true, initial: 0 }),
primaryWeapon: new fields.SchemaField({
@ -306,7 +293,7 @@ export default class DhCharacter extends BaseDataActor {
get deathMoveViable() {
return (
this.resources.hitPoints.maxTotal > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.maxTotal
this.resources.hitPoints.max > 0 && this.resources.hitPoints.value >= this.resources.hitPoints.max
);
}
@ -350,32 +337,32 @@ export default class DhCharacter extends BaseDataActor {
for (let levelKey in this.levelData.levelups) {
const level = this.levelData.levelups[levelKey];
this.proficiency.bonus += level.achievements.proficiency;
this.proficiency += level.achievements.proficiency;
for (let selection of level.selections) {
switch (selection.type) {
case 'trait':
selection.data.forEach(data => {
this.traits[data].bonus += 1;
this.traits[data].value += 1;
this.traits[data].tierMarked = selection.tier === currentTier;
});
break;
case 'hitPoint':
this.resources.hitPoints.bonus += selection.value;
this.resources.hitPoints.max += selection.value;
break;
case 'stress':
this.resources.stress.bonus += selection.value;
this.resources.stress.max += selection.value;
break;
case 'evasion':
this.evasion.bonus += selection.value;
this.evasion += selection.value;
break;
case 'proficiency':
this.proficiency.bonus = selection.value;
this.proficiency = selection.value;
break;
case 'experience':
Object.keys(this.experiences).forEach(key => {
const experience = this.experiences[key];
experience.bonus += selection.value;
experience.value += selection.value;
});
break;
}
@ -383,7 +370,7 @@ export default class DhCharacter extends BaseDataActor {
}
const armor = this.armor;
this.armorScore = this.armor ? this.armor.system.baseScore + (this.bonuses.armorScore ?? 0) : 0; // Bonuses to armorScore won't have been applied yet. Need to solve in documentPreparation somehow
this.armorScore = armor ? armor.system.baseScore : 0;
this.damageThresholds = {
major: armor
? armor.system.baseThresholds.major + this.levelData.level.current
@ -392,29 +379,12 @@ export default class DhCharacter extends BaseDataActor {
? armor.system.baseThresholds.severe + this.levelData.level.current
: this.levelData.level.current * 2
};
this.resources.hope.max -= Object.keys(this.scars).length;
this.resources.hitPoints.max = this.class.value?.system?.hitPoints ?? 0;
}
prepareDerivedData() {
this.resources.hope.max -= Object.keys(this.scars).length;
this.resources.hope.value = Math.min(this.resources.hope.value, this.resources.hope.max);
for (var traitKey in this.traits) {
var trait = this.traits[traitKey];
trait.total = (trait.value ?? 0) + trait.bonus;
}
for (var experienceKey in this.experiences) {
var experience = this.experiences[experienceKey];
experience.total = experience.value + experience.bonus;
}
this.rules.damageReduction.maxArmorMarked.total =
this.rules.damageReduction.maxArmorMarked.value + this.rules.damageReduction.maxArmorMarked.bonus;
this.resources.hitPoints.maxTotal = (this.class.value?.system?.hitPoints ?? 0) + this.resources.hitPoints.bonus;
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
this.evasion.total = (this.class?.evasion ?? 0) + this.evasion.bonus;
this.proficiency.total = this.proficiency.value + this.proficiency.bonus;
}
getRollData() {

View file

@ -20,24 +20,21 @@ export default class DhCompanion extends BaseDataActor {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
partner: new ForeignDocumentUUIDField({ type: 'Actor' }),
resources: new fields.SchemaField({
stress: new fields.SchemaField({
value: new fields.NumberField({ initial: 0, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true }),
max: new fields.NumberField({ initial: 3, integer: true })
max: new fields.NumberField({ initial: 3, integer: true }),
isReversed: new foundry.data.fields.BooleanField({ initial: true })
}),
hope: new fields.NumberField({ initial: 0, integer: true })
}),
evasion: new fields.SchemaField({
value: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
bonus: new fields.NumberField({ initial: 0, integer: true })
}),
evasion: new fields.NumberField({ required: true, min: 1, initial: 10, integer: true }),
experiences: new fields.TypedObjectField(
new fields.SchemaField({
name: new fields.StringField({}),
value: new fields.NumberField({ integer: true, initial: 0 }),
bonus: new fields.NumberField({ integer: true, initial: 0 })
value: new fields.NumberField({ integer: true, initial: 0 })
}),
{
initial: {
@ -66,10 +63,10 @@ export default class DhCompanion extends BaseDataActor {
damage: {
parts: [
{
multiplier: 'flat',
type: ['physical'],
value: {
dice: 'd6',
multiplier: 'flat'
multiplier: 'prof'
}
}
]
@ -83,13 +80,17 @@ export default class DhCompanion extends BaseDataActor {
get traits() {
return {
instinct: { total: this.attack.roll.bonus }
instinct: { value: this.attack.roll.bonus }
};
}
get proficiency() {
return this.partner?.system?.proficiency ?? 1;
}
prepareBaseData() {
const partnerSpellcastingModifier = this.partner?.system?.spellcastingModifiers?.main;
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.total;
const spellcastingModifier = this.partner?.system?.traits?.[partnerSpellcastingModifier]?.value;
this.attack.roll.bonus = spellcastingModifier ?? 0; // Needs to expand on which modifier it is that should be used because of multiclassing;
for (let levelKey in this.levelData.levelups) {
@ -107,15 +108,15 @@ export default class DhCompanion extends BaseDataActor {
}
break;
case 'stress':
this.resources.stress.bonus += selection.value;
this.resources.stress.max += selection.value;
break;
case 'evasion':
this.evasion.bonus += selection.value;
this.evasion += selection.value;
break;
case 'experience':
Object.keys(this.experiences).forEach(key => {
const experience = this.experiences[key];
experience.bonus += selection.value;
experience.value += selection.value;
});
break;
}
@ -124,17 +125,9 @@ export default class DhCompanion extends BaseDataActor {
}
prepareDerivedData() {
for (var experienceKey in this.experiences) {
var experience = this.experiences[experienceKey];
experience.total = experience.value + experience.bonus;
}
if (this.partner) {
this.partner.system.resources.hope.max += this.resources.hope;
}
this.resources.stress.maxTotal = this.resources.stress.max + this.resources.stress.bonus;
this.evasion.total = this.evasion.value + this.evasion.bonus;
}
async _preDelete() {

View file

@ -9,20 +9,21 @@ export default class DhEnvironment extends BaseDataActor {
return foundry.utils.mergeObject(super.metadata, {
label: 'TYPES.Actor.environment',
type: 'environment',
settingSheet: DHEnvironmentSettings
settingSheet: DHEnvironmentSettings,
hasResistances: false
});
}
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
tier: new fields.StringField({
required: true,
choices: CONFIG.DH.GENERAL.tiers,
initial: CONFIG.DH.GENERAL.tiers.tier1.id
}),
type: new fields.StringField({ choices: CONFIG.DH.ACTOR.environmentTypes }),
description: new fields.StringField(),
impulses: new fields.StringField(),
difficulty: new fields.NumberField({ required: true, initial: 11, integer: true }),
potentialAdversaries: new fields.TypedObjectField(

View file

@ -1,5 +1,6 @@
import DHAncestry from './ancestry.mjs';
import DHArmor from './armor.mjs';
import DHAttachableItem from './attachableItem.mjs';
import DHClass from './class.mjs';
import DHCommunity from './community.mjs';
import DHConsumable from './consumable.mjs';
@ -13,6 +14,7 @@ import DHBeastform from './beastform.mjs';
export {
DHAncestry,
DHArmor,
DHAttachableItem,
DHClass,
DHCommunity,
DHConsumable,
@ -27,6 +29,7 @@ export {
export const config = {
ancestry: DHAncestry,
armor: DHArmor,
attachableItem: DHAttachableItem,
class: DHClass,
community: DHCommunity,
consumable: DHConsumable,

View file

@ -1,8 +1,9 @@
import BaseDataItem from './base.mjs';
import AttachableItem from './attachableItem.mjs';
import ActionField from '../fields/actionField.mjs';
import { armorFeatures } from '../../config/itemConfig.mjs';
import { actionsTypes } from '../action/_module.mjs';
export default class DHArmor extends BaseDataItem {
export default class DHArmor extends AttachableItem {
/** @inheritDoc */
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
@ -44,6 +45,12 @@ export default class DHArmor extends BaseDataItem {
};
}
get customActions() {
return this.actions.filter(
action => !this.armorFeatures.some(feature => feature.actionIds.includes(action.id))
);
}
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;

View file

@ -0,0 +1,152 @@
import BaseDataItem from './base.mjs';
export default class AttachableItem extends BaseDataItem {
static defineSchema() {
const fields = foundry.data.fields;
return {
...super.defineSchema(),
attached: new fields.ArrayField(new fields.DocumentUUIDField({ type: "Item", nullable: true }))
};
}
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;
// Handle equipped status changes for attachment effects
if (changes.system?.equipped !== undefined && changes.system.equipped !== this.equipped) {
await this.#handleAttachmentEffectsOnEquipChange(changes.system.equipped);
}
}
async #handleAttachmentEffectsOnEquipChange(newEquippedStatus) {
const actor = this.parent.parent?.type === 'character' ? this.parent.parent : this.parent.parent?.parent;
const parentType = this.parent.type;
if (!actor || !this.attached?.length) {
return;
}
if (newEquippedStatus) {
// Item is being equipped - add attachment effects
for (const attachedUuid of this.attached) {
const attachedItem = await fromUuid(attachedUuid);
if (attachedItem && attachedItem.effects.size > 0) {
await this.#copyAttachmentEffectsToActor({
attachedItem,
attachedUuid,
parentType
});
}
}
} else {
// Item is being unequipped - remove attachment effects
await this.#removeAllAttachmentEffects(parentType);
}
}
async #copyAttachmentEffectsToActor({ attachedItem, attachedUuid, parentType }) {
const actor = this.parent.parent;
if (!actor || !attachedItem.effects.size > 0 || !this.equipped) {
return [];
}
const effectsToCreate = [];
for (const effect of attachedItem.effects) {
const effectData = effect.toObject();
effectData.origin = `${this.parent.uuid}:${attachedUuid}`;
const attachmentSource = {
itemUuid: attachedUuid,
originalEffectId: effect.id
};
attachmentSource[`${parentType}Uuid`] = this.parent.uuid;
effectData.flags = {
...effectData.flags,
[CONFIG.DH.id]: {
...effectData.flags?.[CONFIG.DH.id],
[CONFIG.DH.FLAGS.itemAttachmentSource]: attachmentSource
}
};
effectsToCreate.push(effectData);
}
if (effectsToCreate.length > 0) {
return await actor.createEmbeddedDocuments('ActiveEffect', effectsToCreate);
}
return [];
}
async #removeAllAttachmentEffects(parentType) {
const actor = this.parent.parent;
if (!actor) return;
const parentUuidProperty = `${parentType}Uuid`;
const effectsToRemove = actor.effects.filter(effect => {
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
return attachmentSource && attachmentSource[parentUuidProperty] === this.parent.uuid;
});
if (effectsToRemove.length > 0) {
await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id));
}
}
/**
* Public method for adding an attachment
*/
async addAttachment(droppedItem) {
const newUUID = droppedItem.uuid;
if (this.attached.includes(newUUID)) {
ui.notifications.warn(`${droppedItem.name} is already attached to this ${this.parent.type}.`);
return;
}
const updatedAttached = [...this.attached, newUUID];
await this.parent.update({
'system.attached': updatedAttached
});
// Copy effects if equipped
if (this.equipped && droppedItem.effects.size > 0) {
await this.#copyAttachmentEffectsToActor({
attachedItem: droppedItem,
attachedUuid: newUUID,
parentType: this.parent.type
});
}
}
/**
* Public method for removing an attachment
*/
async removeAttachment(attachedUuid) {
await this.parent.update({
'system.attached': this.attached.filter(uuid => uuid !== attachedUuid)
});
// Remove effects
await this.#removeAttachmentEffects(attachedUuid);
}
async #removeAttachmentEffects(attachedUuid) {
const actor = this.parent.parent;
if (!actor) return;
const parentType = this.parent.type;
const parentUuidProperty = `${parentType}Uuid`;
const effectsToRemove = actor.effects.filter(effect => {
const attachmentSource = effect.getFlag(CONFIG.DH.id, CONFIG.DH.FLAGS.itemAttachmentSource);
return attachmentSource &&
attachmentSource[parentUuidProperty] === this.parent.uuid &&
attachmentSource.itemUuid === attachedUuid;
});
if (effectsToRemove.length > 0) {
await actor.deleteEmbeddedDocuments('ActiveEffect', effectsToRemove.map(e => e.id));
}
}
}

View file

@ -32,9 +32,9 @@ export default class DHSubclass extends BaseDataItem {
get features() {
return [
{ ...this.foundationFeature.toObject(), identifier: 'foundationFeature' },
{ ...this.specializationFeature.toObject(), identifier: 'specializationFeature' },
{ ...this.masteryFeature.toObject(), identifier: 'masteryFeature' }
{ ...this.foundationFeature?.toObject(), identifier: 'foundationFeature' },
{ ...this.specializationFeature?.toObject(), identifier: 'specializationFeature' },
{ ...this.masteryFeature?.toObject(), identifier: 'masteryFeature' }
];
}

View file

@ -1,8 +1,8 @@
import BaseDataItem from './base.mjs';
import AttachableItem from './attachableItem.mjs';
import { actionsTypes } from '../action/_module.mjs';
import ActionField from '../fields/actionField.mjs';
export default class DHWeapon extends BaseDataItem {
export default class DHWeapon extends AttachableItem {
/** @inheritDoc */
static get metadata() {
return foundry.utils.mergeObject(super.metadata, {
@ -37,7 +37,7 @@ export default class DHWeapon extends BaseDataItem {
actionIds: new fields.ArrayField(new fields.StringField({ required: true }))
})
),
attack: new ActionField({
attack: new ActionField({
initial: {
name: 'Attack',
img: 'icons/skills/melee/blood-slash-foam-red.webp',
@ -56,6 +56,7 @@ export default class DHWeapon extends BaseDataItem {
damage: {
parts: [
{
type: ['physical'],
value: {
multiplier: 'prof',
dice: 'd8'
@ -73,6 +74,12 @@ export default class DHWeapon extends BaseDataItem {
return [this.attack, ...this.actions];
}
get customActions() {
return this.actions.filter(
action => !this.weaponFeatures.some(feature => feature.actionIds.includes(action.id))
);
}
async _preUpdate(changes, options, user) {
const allowed = await super._preUpdate(changes, options, user);
if (allowed === false) return false;

View file

@ -40,6 +40,10 @@ export default class DhAppearance extends foundry.abstract.DataModel {
outline: new fields.ColorField({ required: true, initial: '#ffffff' }),
edge: new fields.ColorField({ required: true, initial: '#000000' })
})
}),
showGenericStatusEffects: new fields.BooleanField({
initial: true,
label: 'DAGGERHEART.SETTINGS.Appearance.FIELDS.showGenericStatusEffects.label'
})
};
}

View file

@ -2,7 +2,7 @@ export default class DhRangeMeasurement extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
enabled: new fields.BooleanField({ required: true, initial: false, label: 'DAGGERHEART.GENERAL.enabled' }),
enabled: new fields.BooleanField({ required: true, initial: true, label: 'DAGGERHEART.GENERAL.enabled' }),
melee: new fields.NumberField({ required: true, initial: 5, label: 'DAGGERHEART.CONFIG.Range.melee.name' }),
veryClose: new fields.NumberField({
required: true,

View file

@ -1,5 +1,4 @@
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
import DHRoll from './dhRoll.mjs';
export default class D20Roll extends DHRoll {
@ -98,7 +97,7 @@ export default class D20Roll extends DHRoll {
if (this.options.data.experiences?.[m])
this.options.roll.modifiers.push({
label: this.options.data.experiences[m].name,
value: this.options.data.experiences[m].total ?? this.options.data.experiences[m].value
value: this.options.data.experiences[m].value
});
});
@ -137,12 +136,7 @@ export default class D20Roll extends DHRoll {
static async buildEvaluate(roll, config = {}, message = {}) {
if (config.evaluate !== false) await roll.evaluate();
const advantageState =
config.roll.advantage == this.ADV_MODE.ADVANTAGE
? true
: config.roll.advantage == this.ADV_MODE.DISADVANTAGE
? false
: null;
this.postEvaluate(roll, config);
}

View file

@ -14,6 +14,10 @@ export default class DamageRoll extends DHRoll {
super.postEvaluate(roll, config);
config.roll.type = config.type;
config.roll.modifierTotal = this.calculateTotalModifiers(roll);
}
static async buildPost(roll, config, message) {
await super.buildPost(roll, config, message);
if (config.source?.message) {
const chatMessage = ui.chat.collection.get(config.source.message);
chatMessage.update({ 'system.damage': config });

View file

@ -56,8 +56,8 @@ export default class DHRoll extends Roll {
// Create Chat Message
if (config.source?.message) {
if(game.modules.get('dice-so-nice')?.active) await game.dice3d.showForRoll(roll, game.user, true);
} else {
const messageData = {};
config.message = await this.toMessage(roll, config);
}
}
@ -154,7 +154,7 @@ export const registerRollDiceHooks = () => {
if (updates.length) actor.modifyResource(updates);
if (!config.roll.hasOwnProperty('success') && !config.targets.length) return;
if (!config.roll.hasOwnProperty('success') && !config.targets?.length) return;
const rollResult = config.roll.success || config.targets.some(t => t.hit),
looseSpotlight = !rollResult || config.roll.result.duality === -1;

View file

@ -1,5 +1,6 @@
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import D20Roll from './d20Roll.mjs';
import { setDiceSoNiceForDualityRoll } from '../helpers/utils.mjs';
export default class DualityRoll extends D20Roll {
_advantageFaces = 6;
@ -80,7 +81,6 @@ export default class DualityRoll extends D20Roll {
}
static getHooks(hooks) {
return [...(hooks ?? []), 'Duality'];
}
@ -123,7 +123,7 @@ export default class DualityRoll extends D20Roll {
if (!this.options.roll.trait) return;
this.options.roll.modifiers.push({
label: `DAGGERHEART.CONFIG.Traits.${this.options.roll.trait}.name`,
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.total`, this.data)
value: Roll.replaceFormulaData(`@traits.${this.options.roll.trait}.value`, this.data)
});
}
@ -142,5 +142,7 @@ export default class DualityRoll extends D20Roll {
total: roll.dHope.total + roll.dFear.total,
label: roll.totalLabel
};
setDiceSoNiceForDualityRoll(roll, config.roll.advantage.type);
}
}

View file

@ -1,16 +1,42 @@
export default class DhActiveEffect extends ActiveEffect {
get isSuppressed() {
if (['weapon', 'armor'].includes(this.parent.type)) {
// If this is a copied effect from an attachment, never suppress it
// (These effects have attachmentSource metadata)
if (this.flags?.daggerheart?.attachmentSource) {
return false;
}
// Then apply the standard suppression rules
if (['weapon', 'armor'].includes(this.parent?.type)) {
return !this.parent.system.equipped;
}
if (this.parent.type === 'domainCard') {
if (this.parent?.type === 'domainCard') {
return this.parent.system.inVault;
}
return super.isSuppressed;
}
/**
* Check if the parent item is currently attached to another item
* @returns {boolean}
*/
get isAttached() {
if (!this.parent || !this.parent.parent) return false;
// Check if this item's UUID is in any actor's armor or weapon attachment lists
const actor = this.parent.parent;
if (!actor || !actor.items) return false;
return actor.items.some(item => {
return (item.type === 'armor' || item.type === 'weapon') &&
item.system?.attached &&
Array.isArray(item.system.attached) &&
item.system.attached.includes(this.parent.uuid);
});
}
async _preCreate(data, options, user) {
const update = {};
if (!data.img) {

View file

@ -370,99 +370,19 @@ export default class DhpActor extends Actor {
}
getRollData() {
return this.system;
}
formatRollModifier(roll) {
const modifier = roll.modifier !== null ? Number.parseInt(roll.modifier) : null;
return modifier !== null
? [
{
value: modifier,
label: roll.label
? modifier >= 0
? `${roll.label} +${modifier}`
: `${roll.label} ${modifier}`
: null,
title: roll.label
}
]
: [];
}
async damageRoll(title, damage, targets, shiftKey) {
let rollString = damage.value;
let bonusDamage = damage.bonusDamage?.filter(x => x.initiallySelected) ?? [];
if (!shiftKey) {
const dialogClosed = new Promise((resolve, _) => {
new DamageSelectionDialog(rollString, bonusDamage, resolve).render(true);
});
const result = await dialogClosed;
bonusDamage = result.bonusDamage;
rollString = result.rollString;
const automateHope = await game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation.Hope);
if (automateHope && result.hopeUsed) {
await this.update({
'system.resources.hope.value': this.system.resources.hope.value - result.hopeUsed
});
}
}
const roll = new Roll(rollString);
let rollResult = await roll.evaluate();
const dice = [];
const modifiers = [];
for (var i = 0; i < rollResult.terms.length; i++) {
const term = rollResult.terms[i];
if (term.faces) {
dice.push({
type: `d${term.faces}`,
rolls: term.results.map(x => x.result),
total: term.results.reduce((acc, x) => acc + x.result, 0)
});
} else if (term.operator) {
} else if (term.number) {
const operator = i === 0 ? '' : rollResult.terms[i - 1].operator;
modifiers.push({ value: term.number, operator: operator });
}
}
const cls = getDocumentClass('ChatMessage');
const systemData = {
title: game.i18n.format('DAGGERHEART.UI.Chat.damageRoll.title', { damage: title }),
roll: rollString,
damage: {
total: rollResult.total,
type: damage.type
},
dice: dice,
modifiers: modifiers,
targets: targets
};
const msg = new cls({
type: 'damageRoll',
user: game.user.id,
sound: CONFIG.sounds.dice,
system: systemData,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/damage-roll.hbs',
systemData
),
rolls: [roll]
});
cls.create(msg.toObject());
const rollData = super.getRollData();
rollData.prof = this.system.proficiency ?? 1;
rollData.cast = this.system.spellcast ?? 1;
return rollData;
}
#canReduceDamage(hpDamage, type) {
const availableStress = this.system.resources.stress.maxTotal - this.system.resources.stress.value;
const availableStress = this.system.resources.stress.max - this.system.resources.stress.value;
const canUseArmor =
this.system.armor &&
this.system.armor.system.marks.value < this.system.armorScore &&
this.system.armorApplicableDamageTypes[type];
type.every(t => this.system.armorApplicableDamageTypes[t] === true);
const canUseStress = Object.keys(this.system.rules.damageReduction.stressDamageReduction).reduce((acc, x) => {
const rule = this.system.rules.damageReduction.stressDamageReduction[x];
if (damageKeyToNumber(x) <= hpDamage) return acc || (rule.enabled && availableStress >= rule.cost);
@ -480,11 +400,9 @@ export default class DhpActor extends Actor {
return;
}
const flatReduction = this.system.bonuses.damageReduction[type];
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
const hpDamage = this.convertDamageToThreshold(damage);
type = !Array.isArray(type) ? [type] : type;
if (Hooks.call(`${CONFIG.DH.id}.postDamageTreshold`, this, hpDamage, damage, type) === false) return null;
const hpDamage = this.calculateDamage(baseDamage, type);
if (!hpDamage) return;
@ -511,6 +429,35 @@ export default class DhpActor extends Actor {
if (Hooks.call(`${CONFIG.DH.id}.postTakeDamage`, this, damage, type) === false) return null;
}
calculateDamage(baseDamage, type) {
if (Hooks.call(`${CONFIG.DH.id}.preCalculateDamage`, this, baseDamage, type) === false) return null;
/* if(this.system.resistance[type]?.immunity) return 0;
if(this.system.resistance[type]?.resistance) baseDamage = Math.ceil(baseDamage / 2); */
if(this.canResist(type, 'immunity')) return 0;
if(this.canResist(type, 'resistance')) baseDamage = Math.ceil(baseDamage / 2);
// const flatReduction = this.system.resistance[type].reduction;
const flatReduction = this.getDamageTypeReduction(type);
const damage = Math.max(baseDamage - (flatReduction ?? 0), 0);
const hpDamage = this.convertDamageToThreshold(damage);
if (Hooks.call(`${CONFIG.DH.id}.postCalculateDamage`, this, baseDamage, type) === false) return null;
return hpDamage;
}
canResist(type, resistance) {
if(!type) return 0;
return type.every(t => this.system.resistance[t]?.[resistance] === true);
}
getDamageTypeReduction(type) {
if(!type) return 0;
const reduction = Object.entries(this.system.resistance).reduce((a, [index, value]) => type.includes(index) ? Math.min(value.reduction, a) : a, Infinity);
return reduction === Infinity ? 0 : reduction;
}
async takeHealing(resources) {
resources.forEach(r => (r.value *= -1));
await this.modifyResource(resources);
@ -538,7 +485,7 @@ export default class DhpActor extends Actor {
updates.actor.resources[`system.resources.${r.type}.value`] = Math.max(
Math.min(
this.system.resources[r.type].value + r.value,
this.system.resources[r.type].maxTotal ?? this.system.resources[r.type].max
this.system.resources[r.type].max
),
0
);
@ -553,18 +500,6 @@ export default class DhpActor extends Actor {
u.resources,
u.target.uuid
);
/* if (game.user.isGM) {
await u.target.update(u.resources);
} else {
await game.socket.emit(`system.${CONFIG.DH.id}`, {
action: socketEvent.GMUpdate,
data: {
action: GMUpdateEvent.UpdateDocument,
uuid: u.target.uuid,
update: u.resources
}
});
} */
}
});
}
@ -582,7 +517,7 @@ export default class DhpActor extends Actor {
convertStressDamageToHP(resources) {
const stressDamage = resources.find(r => r.type === 'stress'),
newValue = this.system.resources.stress.value + stressDamage.value;
if (newValue <= this.system.resources.stress.maxTotal) return;
if (newValue <= this.system.resources.stress.max) return;
const hpDamage = resources.find(r => r.type === 'hitPoints');
if (hpDamage) hpDamage.value++;
else

View file

@ -80,12 +80,16 @@ export default class DHItem extends foundry.documents.Item {
async selectActionDialog(prevEvent) {
const content = await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/dialogs/actionSelect.hbs',
{ actions: this.system.actionsList }
{
actions: this.system.actionsList,
itemName: this.name
}
),
title = 'Select Action';
title = game.i18n.localize('DAGGERHEART.CONFIG.SelectAction.selectAction');
return foundry.applications.api.DialogV2.prompt({
window: { title },
classes: ['daggerheart', 'dh-style'],
content,
ok: {
label: title,

View file

@ -2,15 +2,33 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
async activate(element, options = {}) {
let html = options.html;
if (element.dataset.tooltip?.startsWith('#item#')) {
const item = await foundry.utils.fromUuid(element.dataset.tooltip.slice(6));
const splitValues = element.dataset.tooltip.slice(6).split('#action#');
const itemUuid = splitValues[0];
const actionId = splitValues.length > 1 ? splitValues[1] : null;
const baseItem = await foundry.utils.fromUuid(itemUuid);
const item = actionId ? baseItem.system.actions.find(x => x.id === actionId) : baseItem;
if (item) {
const type = actionId ? 'action' : item.type;
html = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/${item.type}.hbs`,
item
`systems/daggerheart/templates/ui/tooltip/${type}.hbs`,
{
item: item,
config: CONFIG.DH
}
);
this.tooltip.innerHTML = html;
options.direction = this._determineItemTooltipDirection(element);
}
}
super.activate(element, { ...options, html: html });
}
_determineItemTooltipDirection(element) {
const pos = element.getBoundingClientRect();
const dirs = this.constructor.TOOLTIP_DIRECTIONS;
return dirs[pos.x - this.tooltip.offsetWidth < 0 ? 'DOWN' : 'LEFT'];
}
}

View file

@ -1,35 +1,48 @@
import { getWidthOfText } from './utils.mjs';
export default class RegisterHandlebarsHelpers {
static registerHelpers() {
Handlebars.registerHelper({
times: this.times,
add: this.add,
subtract: this.subtract,
includes: this.includes,
times: this.times,
damageFormula: this.damageFormula,
damageSymbols: this.damageSymbols,
tertiary: this.tertiary
});
}
static times(nr, block) {
var accum = '';
for (var i = 0; i < nr; ++i) accum += block.fn(i);
return accum;
}
static add(a, b) {
const aNum = Number.parseInt(a);
const bNum = Number.parseInt(b);
return (Number.isNaN(aNum) ? 0 : aNum) + (Number.isNaN(bNum) ? 0 : bNum);
}
static subtract(a, b) {
const aNum = Number.parseInt(a);
const bNum = Number.parseInt(b);
return (Number.isNaN(aNum) ? 0 : aNum) - (Number.isNaN(bNum) ? 0 : bNum);
}
static includes(list, item) {
return list.includes(item);
}
static times(nr, block) {
var accum = '';
for (var i = 0; i < nr; ++i) accum += block.fn(i);
return accum;
}
static damageFormula(attack, actor) {
const traitTotal = actor.system.traits?.[attack.roll.trait]?.value;
const instances = [
attack.damage.parts.map(x => Roll.replaceFormulaData(x.value.getFormula(), actor)).join(' + '),
traitTotal
].filter(x => x);
return instances.join(traitTotal > 0 ? ' + ' : ' - ');
}
static damageSymbols(damageParts) {
const symbols = new Set();
damageParts.forEach(part => symbols.add(...CONFIG.DH.GENERAL.damageTypes[part.type].icon));
return new Handlebars.SafeString(Array.from(symbols).map(symbol => `<i class="fa-solid ${symbol}"></i>`));
}
static tertiary(a, b) {
return a ?? b;
}
}

View file

@ -126,12 +126,10 @@ export const setDiceSoNiceForDualityRoll = (rollResult, advantageState) => {
const diceSoNicePresets = getDiceSoNicePresets();
rollResult.dice[0].options = { appearance: diceSoNicePresets.hope };
rollResult.dice[1].options = { appearance: diceSoNicePresets.fear }; //diceSoNicePresets.fear;
if (rollResult.dice[2]) {
if (advantageState === true) {
rollResult.dice[2].options = { appearance: diceSoNicePresets.advantage };
} else if (advantageState === false) {
rollResult.dice[2].options = { appearance: diceSoNicePresets.disadvantage };
}
if (rollResult.dice[2] && advantageState) {
rollResult.dice[2].options = {
appearance: advantageState === 1 ? diceSoNicePresets.advantage : diceSoNicePresets.disadvantage
};
}
};
@ -238,16 +236,7 @@ Roll.replaceFormulaData = function (formula, data = {}, { missing, warn = false
};
export const getDamageKey = damage => {
switch (damage) {
case 3:
return 'severe';
case 2:
return 'major';
case 1:
return 'minor';
case 0:
return 'none';
}
return ['none', 'minor', 'major', 'severe'][damage];
};
export const getDamageLabel = damage => {
@ -255,16 +244,12 @@ export const getDamageLabel = damage => {
};
export const damageKeyToNumber = key => {
switch (key) {
case 'severe':
return 3;
case 'major':
return 2;
case 'minor':
return 1;
case 'none':
return 0;
}
return {
'none': 0,
'minor': 1,
'major': 2,
'severe': 3
}[key];
};
export default function constructHTMLButton({

View file

@ -26,6 +26,7 @@ export const preloadHandlebarsTemplates = async function () {
'systems/daggerheart/templates/actionTypes/beastform.hbs',
'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/chat/parts/target-chat.hbs',
'systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs'
]);
};

View file

@ -60,7 +60,7 @@ const registerMenuSettings = () => {
});
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.RangeMeasurement, {
scope: 'client',
scope: 'world',
config: false,
type: DhRangeMeasurement
});

View file

@ -3,6 +3,8 @@
@import './less/dialog/index.less';
@import './less//hud/index.less';
@import './less/utils/colors.less';
@import './less/utils/fonts.less';
@ -10,4 +12,6 @@
@import './less/ui/index.less';
@import './less/ux/index.less';
@import '../node_modules/@yaireo/tagify/dist/tagify.css';

View file

@ -0,0 +1,19 @@
@import '../../utils/fonts.less';
.application.daggerheart.dh-style {
.actions-list {
display: flex;
flex-direction: column;
gap: 10px;
.action-item {
display: flex;
align-items: center;
gap: 5px;
.label {
font-family: @font-body;
}
}
}
}

View file

@ -0,0 +1,20 @@
@import '../../utils/colors.less';
.daggerheart.dialog.dh-style.views.damage-selection {
.damage-section-container {
display: flex;
flex-direction: column;
gap: 12px;
input[type='text'],
input[type='number'] {
color: light-dark(@dark, @beige);
outline: 2px solid transparent;
transition: all 0.3s ease;
&:hover {
outline: 2px solid light-dark(@dark, @beige);
}
}
}
}

View file

@ -114,15 +114,5 @@
}
}
}
.formula-label {
font-family: @font-body;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 17px;
color: light-dark(@dark, @beige);
}
}
}

View file

@ -4,6 +4,10 @@
@import './level-up/summary-container.less';
@import './level-up/tiers-container.less';
@import './actions/action-list.less';
@import './damage-selection/sheet.less';
@import './downtime/downtime-container.less';
@import './beastform/beastform-container.less';

View file

@ -1,5 +1,6 @@
@import '../utils/colors.less';
@import '../utils/fonts.less';
@import '../utils/mixin.less';
.appTheme({
&.dialog {
@ -40,4 +41,19 @@
}
}
}
.submit-btn {
width: 100%;
height: 38px;
}
.formula-label {
font-family: @font-body;
font-style: normal;
font-weight: 500;
font-size: 14px;
line-height: 17px;
color: light-dark(@dark, @beige);
}
}

View file

@ -35,15 +35,16 @@
}
}
input[type='checkbox'] {
input[type='checkbox'],
input[type='radio'] {
&:checked::after {
color: light-dark(@dark, @golden);
color: light-dark(@dark-40, @golden);
}
&:checked::before {
color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-40, @golden-40);
}
&::before {
color: light-dark(@dark, @beige);
color: light-dark(@dark-40, @golden-40);
}
}
@ -103,6 +104,40 @@
}
}
multi-select {
position: relative;
height: 34px;
.tags {
justify-content: flex-start;
margin: 5px;
height: inherit;
.tag {
box-shadow: 0 0 0 1.1em #E5E5E5 inset;
vertical-align: top;
box-sizing: border-box;
max-width: 100%;
padding: 0.3em 0 0.3em 0.5em;
color: black;
border-radius: 3px;
white-space: nowrap;
transition: .13s ease-out;
height: 22px;
font-size: .9rem;
gap: 0.5em;
z-index: 1;
.remove {
font-size: 10px;
margin-inline: auto 4.6666666667px;
}
}
}
select {
position: absolute;
height: inherit;
outline: initial;
}
}
p {
margin: 0;
}
@ -319,6 +354,17 @@
transform: translateY(-20px);
transform-origin: top;
}
.item-buttons {
grid-column: span 3;
display: flex;
gap: 8px;
flex-wrap: wrap;
button {
white-space: nowrap;
}
}
}
.application.setting.dh-style {

View file

@ -12,3 +12,4 @@
@import './inventory-fieldset-items.less';
@import './prose-mirror.less';
@import './filter-menu.less';
@import './tab-attachments.less';

View file

@ -4,8 +4,8 @@
// Theme handling
.appTheme({
background: @semi-transparent-dark-blue;
backdrop-filter: blur(9px);
background: @dark-blue-60;
backdrop-filter: blur(10px);
}, {
background: url('../assets/parchments/dh-parchment-light.png') no-repeat center;
});
@ -44,6 +44,8 @@
top: -36px;
min-height: -webkit-fill-available;
transition: opacity 0.3s ease;
padding-bottom: 20px;
margin-bottom: -36px;
.tab {
padding: 0 10px;

View file

@ -0,0 +1,7 @@
.daggerheart.dh-style {
.tab.attachments {
.attached-items {
width: 100%;
}
}
}

View file

@ -0,0 +1 @@
@import './token-hud/token-hud.less';

View file

@ -0,0 +1,10 @@
.daggerheart.placeable-hud {
.col.right {
.palette {
.palette-category-title {
grid-column: span var(--effect-columns);
font-weight: bold;
}
}
}
}

View file

@ -5,6 +5,7 @@
.tab.experiences {
.add-experience-btn {
width: 100%;
height: 38px;
margin-bottom: 12px;
}

View file

@ -10,6 +10,7 @@
.add-feature-btn {
width: 100%;
height: 38px;
margin-bottom: 12px;
}

View file

@ -10,6 +10,7 @@
.add-action-btn {
width: 100%;
height: 38px;
margin-bottom: 12px;
}

View file

@ -10,6 +10,7 @@
.add-feature-btn {
width: 100%;
height: 38px;
margin-bottom: 12px;
}

View file

@ -9,6 +9,7 @@
gap: 15px 0;
height: 100%;
width: 100%;
padding-bottom: 0;
.adversary-sidebar-sheet {
grid-row: 1 / span 2;

View file

@ -110,10 +110,11 @@
justify-content: space-evenly;
.status-bar {
display: flex;
justify-content: center;
position: relative;
width: 100px;
height: 40px;
justify-items: center;
.status-label {
position: relative;

View file

@ -9,6 +9,7 @@
gap: 15px 0;
height: 100%;
width: 100%;
padding-bottom: 0;
overflow: auto;
.character-sidebar-sheet {

View file

@ -70,10 +70,11 @@
justify-content: space-evenly;
.status-bar {
display: flex;
justify-content: center;
position: relative;
width: 100px;
height: 40px;
justify-items: center;
.status-label {
position: relative;

View file

@ -45,7 +45,9 @@
justify-content: center;
.status-number {
justify-items: center;
display: flex;
flex-direction: column;
align-items: center;
.status-value {
position: relative;
@ -85,6 +87,8 @@
}
.status-bar {
display: flex;
justify-content: center;
position: relative;
width: 100px;
height: 40px;

View file

@ -25,10 +25,12 @@
@dark-blue-10: #18162e10;
@dark-blue-40: #18162e40;
@dark-blue-50: #18162e50;
@dark-blue-60: #18162e60;
@semi-transparent-dark-blue: rgba(24, 22, 46, 0.33);
@dark: #222;
@dark-15: #22222215;
@dark-40: #22222240;
@deep-black: #0e0d15;

View file

@ -0,0 +1 @@
@import './tooltip/tooltip.less';

View file

@ -0,0 +1,106 @@
.daggerheart.dh-style.tooltip {
display: flex;
flex-direction: column;
align-items: center;
gap: 4px;
.tooltip-title {
margin: 0;
text-align: center;
}
.tooltip-image {
height: 180px;
width: 180px;
}
.tooltip-description {
font-style: italic;
}
.tooltip-sub-title {
margin: 0;
color: light-dark(@dark-blue, @beige);
}
.tooltip-information-section {
width: 100%;
display: grid;
grid-template-columns: 1fr 1fr;
gap: 4px;
&.triple {
grid-template-columns: 1fr 1fr 1fr;
}
&.border {
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
padding: 2px;
}
.tooltip-information {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
&.full-width {
grid-column: span 2;
}
label {
font-weight: bold;
}
label,
div {
white-space: nowrap;
}
}
}
.tooltip-tags {
width: 100%;
display: flex;
flex-direction: column;
gap: 4px;
.tooltip-tag {
width: 100%;
display: grid;
grid-template-columns: 80px 1fr;
align-items: start;
gap: 8px;
padding: 4px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
.tooltip-tag-label-container {
display: flex;
align-items: center;
flex-direction: column;
gap: 2px;
.tooltip-tag-image {
width: 40px;
height: 40px;
}
}
.tooltip-tag-label {
font-weight: bold;
text-align: center;
}
.tooltip-tag-description {
display: flex;
flex-wrap: wrap;
}
}
}
.spaced {
margin-bottom: 4px;
}
}

View file

@ -1,13 +1,12 @@
<form id="action-type-select">
<ul class="unlist list-select">
<header class="dialog-header">
<h1>{{itemName}}</h1>
</header>
<ul class="actions-list">
{{#each types}}
<li>
<label>
{{! TODO: remove dh-icon}}
<dh-icon class="dh-icon fas {{icon}}"></dh-icon>
<span>{{localize name}}</span>
<input type="radio" name="type" value="{{id}}" {{#if (eq @index 0)}}checked{{/if}}>
</label>
<li class="action-item">
<input type="radio" name="type" value="{{id}}" {{#if (eq @index 0)}}checked{{/if}}>
<span class="label">{{localize name}}</span>
</li>
{{/each}}
</ul>

View file

@ -1,12 +1,12 @@
<form id="item-action-select">
<ul class="unlist list-select">
<header class="dialog-header">
<h1>{{itemName}}</h1>
</header>
<ul class="actions-list">
{{#each actions}}
<li>
<label>
<dh-icon><img src="{{ img }}"></dh-icon>
<span>{{ name }}</span>
<input type="radio" name="actionId" value="{{_id}}" {{#if (eq @index 0)}}checked{{/if}}>
</label>
<li class="action-item">
<input type="radio" name="actionId" value="{{_id}}" {{#if (eq @index 0)}}checked{{/if}}>
<span class="label">{{ name }}</span>
</li>
{{/each}}
</ul>

View file

@ -8,7 +8,7 @@
{{#if this.stress}}
<div class="resource-container">
<h4 class="armor-title">{{localize "DAGGERHEART.APPLICATIONS.DamageReduction.stress"}}</h4>
<div class="markers-subtitle">{{this.stress.value}}/{{this.stress.maxTotal}}</div>
<div class="markers-subtitle">{{this.stress.value}}/{{this.stress.max}}</div>
</div>
{{/if}}
</div>

View file

@ -1,33 +1,13 @@
<div>
<section class="damage-section-container">
<header class="dialog-header">
<h1>{{title}}</h1>
</header>
<span class="formula-label"><b>Formula:</b> {{@root.formula}}</span>
<div class="form-group">
<label><strong>{{title}}</strong></label>
<div class="form-fields">
{{!-- <input type="text" value="{{formula}}" disabled /> --}}
<div>{{@root.formula}}</div>
</div>
<div>
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="Situational Bonus">
</div>
<input type="text" value="{{extraFormula}}" name="extraFormula" placeholder="Situational Bonus">
</div>
{{!-- {{#each bonusDamage as |damage index|}}
<div class="form-group">
<label><strong>{{damage.description}}</strong></label>
<div class="form-fields">
<label>Enabled</label>
<input style="align-self: baseline;" type="checkbox" name="bonusDamage.{{index}}.initiallySelected" {{checked damage.initiallySelected}} />
{{#if (and damage.initiallySelected damage.hopeIncrease)}}
<label>Hope</label>
<div class="hope-container">
<i data-action="decreaseHopeUse" data-index="{{index}}" class="fa-solid fa-caret-left icon-button {{#if (eq damage.hopeUses 0)}}disabled{{/if}}"></i>
<div>{{damage.hopeUses}}</div>
<i data-action="increaseHopeUse" data-index="{{index}}" class="fa-solid fa-caret-right icon-button {{#if (eq ../hopeUsed ../hope)}}disabled{{/if}}"></i>
</div>
{{/if}}
</div>
</div>
{{/each}} --}}
<footer>
<button data-action="submitRoll">Roll</button>
</footer>
</div>
<button class="submit-btn" data-action="submitRoll">
<i class="fa-solid fa-dice"></i>
<span class="label">Roll</span>
</button>
</section>

View file

@ -0,0 +1,78 @@
<div class="col left">
<div class="attribute elevation" data-tooltip="HUD.Elevation">
<i class="fas fa-angle-up"></i>
<input type="text" name="elevation" value="{{elevation}}" {{disabled (or locked (and isGamePaused (not isGM)))}}>
</div>
<button type="button" class="control-icon" data-action="sort" data-direction="up" data-tooltip="HUD.ToFront">
<img src="{{icons.up}}">
</button>
<button type="button" class="control-icon" data-action="sort" data-direction="down" data-tooltip="HUD.ToBack">
<img src="{{icons.down}}">
</button>
{{#if canConfigure}}
<button type="button" class="control-icon" data-action="config" data-tooltip="HUD.OpenConfig">
<i class="fa-solid fa-gear" inert></i>
</button>
{{/if}}
</div>
<div class="col middle">
<div class="attribute bar2">
{{#if displayBar2}}
<input type="text" name="bar2" value="{{bar2Data.value}}" {{#unless bar2Data.editable}}disabled{{/unless}}>
{{/if}}
</div>
<div class="attribute bar1">
{{#if displayBar1}}
<input type="text" name="bar1" value="{{bar1Data.value}}" {{#unless bar1Data.editable}}disabled{{/unless}}>
{{/if}}
</div>
</div>
<div class="col right">
{{#if isGM}}
<button type="button" class="control-icon {{visibilityClass}}" data-action="visibility" data-tooltip="HUD.ToggleVis">
<img src="{{icons.visibility}}">
</button>
{{/if}}
<button type="button" class="control-icon" data-action="togglePalette" data-palette="effects" data-tooltip="HUD.AssignStatusEffects">
<img src="{{icons.effects}}">
</button>
<div class="palette status-effects" data-palette="effects">
{{#each systemStatusEffects as |status|}}
<img class="effect-control {{status.cssClass}}" src="{{status.src}}" data-action="effect" data-status-id="{{status.id}}" {{#if status.title}}data-tooltip-text="{{status.title}}"{{/if}}>
{{/each}}
{{#if genericStatusEffects}}
<label class="palette-category-title">{{localize "DAGGERHEART.APPLICATIONS.HUD.tokenHUD.genericEffects"}}</label>
{{#each genericStatusEffects as |status|}}
<img class="effect-control {{status.cssClass}}" src="{{status.src}}" data-action="effect" data-status-id="{{status.id}}" {{#if status.title}}data-tooltip-text="{{status.title}}"{{/if}}>
{{/each}}
{{/if}}
</div>
<button type="button" class="control-icon" data-action="togglePalette" data-palette="movementActions" data-tooltip="HUD.SelectMovementAction">
<i class="{{movementActionsIcon}}" inert></i>
</button>
<div class="palette movement-actions" data-palette="movementActions">
{{#each movementActions as |action|}}
<a class="movement-action-control {{action.cssClass}}" data-action="movementAction" data-movement-action="{{action.id}}">
<span>{{#if action.icon}}<i class="{{action.icon}} fa-fw" inert></i>{{/if}} {{action.label}}</span>
</a>
{{/each}}
</div>
<button type="button" class="control-icon {{targetClass}}" data-action="target" data-tooltip="HUD.ToggleTargetState">
<i class="fa-solid fa-bullseye" inert></i>
</button>
{{#if canToggleCombat}}
<button type="button" class="control-icon {{combatClass}}" data-action="combat" data-tooltip="HUD.ToggleCombatState">
<img src="{{icons.combat}}">
</button>
{{/if}}
</div>

View file

@ -1,5 +1,6 @@
<div>
{{formGroup settingFields.schema.fields.displayFear value=settingFields._source.displayFear localize=true}}
{{formGroup settingFields.schema.fields.showGenericStatusEffects value=settingFields._source.showGenericStatusEffects localize=true}}
<fieldset>
<legend>{{localize "DAGGERHEART.SETTINGS.Menu.appearance.duality"}}</legend>

View file

@ -13,7 +13,7 @@
{{#each document.system.experiences as |experience key|}}
<li class="experience-item">
<input class="name" type="text" name="system.experiences.{{key}}.name" value="{{experience.name}}" />
<input class="modifier" type="text" name="system.experiences.{{key}}.total" value="{{experience.total}}" data-dtype="Number" />
<input class="modifier" type="text" name="system.experiences.{{key}}.value" value="{{experience.value}}" data-dtype="Number" />
<a data-action="removeExperience" data-experience="{{key}}" data-tooltip="{{localize 'CONTROLS.CommonDelete'}}"><i class="fa-solid fa-trash"></i></a>
</li>
{{/each}}

View file

@ -6,7 +6,7 @@
<fieldset class="one-column">
<legend>{{localize 'DAGGERHEART.GENERAL.basics'}}</legend>
<div class="nest-inputs">
{{formGroup systemFields.evasion.fields.value value=document.system.evasion.value localize=true}}
{{formGroup systemFields.evasion value=document.system.evasion localize=true}}
{{formGroup systemFields.resources.fields.stress.fields.value value=document.system.resources.stress.value label='Current Stress'}}
{{formGroup systemFields.resources.fields.stress.fields.max value=document.system.resources.stress.max label='Max Stress'}}
</div>

View file

@ -7,7 +7,7 @@
{{formGroup fields.origin value=source.origin rootId=rootId disabled=true}}
{{/if}}
{{#if isItemEffect}}
{{formGroup fields.transfer value=source.transfer rootId=rootId label=legacyTransfer.label hint=legacyTransfer.hint}}
{{formGroup fields.transfer value=source.transfer rootId=rootId label=legacyTransfer.label hint=(localize "DAGGERHEART.EFFECTS.Attachments.transferHint")}}
{{/if}}
{{formGroup fields.statuses value=source.statuses options=statuses rootId=rootId classes="statuses"}}

View file

@ -1,6 +1,5 @@
<aside class="adversary-sidebar-sheet">
<div
class="portrait {{#if (gte source.system.resources.hitPoints.value source.system.resources.hitPoints.maxTotal)}}death-roll{{/if}}">
<div class="portrait {{#if (gte source.system.resources.hitPoints.value source.system.resources.hitPoints.max)}}death-roll{{/if}}">
<img src="{{source.img}}" alt="{{source.name}}" data-action='editImage' data-edit="img">
<a class="death-roll-btn" data-tooltip="{{localize " DAGGERHEART.UI.Tooltip.makeDeathMove"}}"
data-action="makeDeathMove"><i class="fas fa-skull death-save"></i></a>

View file

@ -96,7 +96,7 @@
<div class="character-traits">
{{#each this.attributes as |attribute key|}}
<div class="trait" data-tooltip="{{#each attribute.verbs}}{{this}}<br>{{/each}}" data-action="rollAttribute" data-attribute="{{key}}" data-value="{{attribute.total}}">
<div class="trait" data-tooltip="{{#each attribute.verbs}}{{this}}<br>{{/each}}" data-action="rollAttribute" data-attribute="{{key}}" data-value="{{attribute.value}}">
<div class="trait-name">
<span>{{localize (concat 'DAGGERHEART.CONFIG.Traits.' key '.short')}}</span>
{{#if attribute.tierMarked}}
@ -106,10 +106,10 @@
{{/if}}
</div>
<div class="trait-value">
{{#if (gt attribute.total 0)}}
<span>+{{attribute.total}}</span>
{{#if (gt attribute.value 0)}}
<span>+{{attribute.value}}</span>
{{else}}
<span>{{attribute.total}}</span>
<span>{{attribute.value}}</span>
{{/if}}
</div>
</div>

View file

@ -12,10 +12,13 @@
<p><input class="bar-input" name="system.resources.hitPoints.value"
value="{{document.system.resources.hitPoints.value}}" type="number"></p>
<p>/</p>
<p class="bar-label">{{document.system.resources.hitPoints.maxTotal}}</p>
<p class="bar-label">{{document.system.resources.hitPoints.max}}</p>
</div>
<progress class='progress-bar' value='{{document.system.resources.hitPoints.value}}'
max='{{document.system.resources.hitPoints.maxTotal}}'></progress>
<progress
class='progress-bar'
value='{{document.system.resources.hitPoints.value}}'
max='{{document.system.resources.hitPoints.max}}'
></progress>
<div class="status-label">
<h4>HP</h4>
</div>
@ -26,10 +29,13 @@
<p><input class="bar-input" name="system.resources.stress.value"
value="{{document.system.resources.stress.value}}" type="number"></p>
<p>/</p>
<p class="bar-label">{{document.system.resources.stress.maxTotal}}</p>
<p class="bar-label">{{document.system.resources.stress.max}}</p>
</div>
<progress class='progress-bar stress-color' value='{{document.system.resources.stress.value}}'
max='{{document.system.resources.stress.maxTotal}}'></progress>
<progress
class='progress-bar stress-color'
value='{{document.system.resources.stress.value}}'
max='{{document.system.resources.stress.max}}'
></progress>
<div class="status-label">
<h4>Stress</h4>
</div>
@ -38,7 +44,7 @@
<div class="status-section">
<div class="status-number">
<div class='status-value'>
<p>{{document.system.proficiency.total}}</p>
<p>{{document.system.proficiency}}</p>
</div>
<div class="status-label">
<h4>Proficiency</h4>
@ -60,7 +66,7 @@
<div class="status-number">
<div class='status-value'>
<p>{{document.system.evasion.total}}</p>
<p>{{document.system.evasion}}</p>
</div>
<div class="status-label">
<h4>Evasion</h4>

View file

@ -11,7 +11,7 @@
<div class="status-section">
<div class="status-number">
<div class='status-value'>
<p>{{document.system.evasion.total}}</p>
<p>{{document.system.evasion}}</p>
</div>
<div class="status-label">
<h4>Evasion</h4>
@ -21,12 +21,12 @@
<div class='status-value'>
<p><input class="bar-input" name="system.resources.stress.value" value="{{document.system.resources.stress.value}}" type="number"></p>
<p>/</p>
<p class="bar-label">{{document.system.resources.stress.maxTotal}}</p>
<p class="bar-label">{{document.system.resources.stress.max}}</p>
</div>
<progress
class='progress-bar stress-color'
value='{{document.system.resources.stress.value}}'
max='{{document.system.resources.stress.maxTotal}}'
max='{{document.system.resources.stress.max}}'
></progress>
<div class="status-label">
<h4>Stress</h4>

View file

@ -1,44 +0,0 @@
<div class="temp-container standard-form">
<img class="profile" src="{{document.img}}" alt="{{document.name}}" data-action='editImage' data-edit="img">
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.ACTORS.Companion.FIELDS.partner.label"}}</label>
<select class="partner-value">
{{selectOptions playerCharacters selected=source.system.partner.uuid labelAttr="name" valueAttr="key" blank=""}}
</select>
</div>
</div>
{{formGroup systemFields.resources.fields.stress.fields.value value=source.system.resources.stress.value localize=true }}
{{formGroup systemFields.evasion.fields.value value=source.system.evasion.value localize=true }}
<div>{{localize "DAGGERHEART.GENERAL.Experience.plural"}}</div>
<div class="flexcol">
{{#each source.system.experiences as |experience key|}}
<div class="flexrow">
<input type="text" name="{{concat "system.experiences." key ".name"}}" value="{{experience.name}}" />
<div>{{numberFormat experience.value sign=true}}</div>
</div>
{{/each}}
</div>
<div class="flexrow">
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.ACTORS.Companion.FIELDS.attack.name.label"}}</label>
<input type="text" name="system.attack.name" value="{{source.system.attack.name}}" />
<button data-action="attackRoll">Attack</button>
</div>
</div>
</div>
<div class="flexrow">
<div class="form-group">
<div class="form-fields">
<label>{{localize "DAGGERHEART.GENERAL.level"}}</label>
<div>{{source.system.levelData.level.changed}}</div>
<button data-action="levelUp" {{#if (not source.system.levelData.canLevelUp)}}disabled{{/if}}>Level Up</button>
</div>
</div>
</div>
</div>

View file

@ -17,7 +17,7 @@
{{/each}}
{{else}}
{{#each values}}
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=this type=../type hideControls=../hideControls }}
{{> 'systems/daggerheart/templates/sheets/global/partials/inventory-item.hbs' item=this type=../type hideControls=../hideControls featureType=true }}
{{/each}}
{{#each adversaries as |adversary|}}

View file

@ -1,4 +1,4 @@
<li class="inventory-item" data-item-id="{{item.id}}" data-item-uuid="{{item.uuid}}" data-type="{{type}}">
<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">
{{#if isCompanion}}
@ -16,11 +16,11 @@
<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}}
{{!-- ({{localize (concat 'DAGGERHEART.CONFIG.DamageType.' item.system.attack.damage.parts.0.type '.abbreviation')}}) --}}
{{#with (lookup @root.config.GENERAL.damageTypes item.system.attack.damage.parts.0.type)}}
{{#each icon}}
<i class="fa-solid {{this}}"></i>
{{/each}}
{{/with}}
{{#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}}
@ -32,7 +32,11 @@
</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}}
({{localize (concat 'DAGGERHEART.CONFIG.DamageType.' item.system.attack.damage.parts.0.type '.abbreviation')}})
(
{{#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)}}
@ -171,4 +175,12 @@
<span></span>
{{/unless}}
<div class="item-description">{{#unless isSidebar}}{{{item.system.description}}}{{/unless}}</div>
{{#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.attachments.cssClass}} {{tabs.attachments.id}}'
data-tab='{{tabs.attachments.id}}'
data-group='{{tabs.attachments.group}}'
>
<fieldset class="one-column drop-section attachments-section">
<legend>{{localize tabs.attachments.label}}</legend>
{{#if attachedItems}}
<div class="attached-items">
{{#each attachedItems as |item|}}
<div class="inventory-item attached-item" data-uuid="{{item.uuid}}">
<img src="{{item.img}}" alt="{{item.name}}" class="item-img">
<div class="item-label">
<div class="item-name">{{item.name}}</div>
</div>
<div class="controls">
<a data-action="removeAttachment" data-uuid="{{item.uuid}}"><i class="fa-solid fa-trash remove-attachment"></i></a>
</div>
</div>
{{/each}}
</div>
{{/if}}
<div class="drop-area" data-drop-type="Item" style="width: 100%;">
<span>{{localize "DAGGERHEART.EFFECTS.Attachments.attachHint"}}</span>
</div>
</fieldset>
</section>

View file

@ -4,7 +4,7 @@
<line-div></line-div>
<h1 class='item-name'><input type='text' name='name' value='{{source.name}}' /></h1>
<div class='item-description'>
<h3>{{localize (concat 'TYPES.Item.feature' source.system.type)}}</h3>
<h3>{{localize 'TYPES.Item.feature'}}</h3>
</div>
</div>
</header>

View file

@ -15,7 +15,11 @@
{{localize (concat 'DAGGERHEART.CONFIG.Range.' source.system.attack.range '.name')}}
<span>-</span>
{{source.system.attack.damage.parts.0.value.dice}}{{#if source.system.attack.damage.parts.0.value.bonus}} + {{source.system.attack.damage.parts.0.value.bonus}}{{/if}}
({{localize (concat 'DAGGERHEART.CONFIG.DamageType.' source.system.attack.damage.parts.0.type '.abbreviation')}})
(
{{#each source.system.attack.damage.parts.0.type}}
{{localize (concat 'DAGGERHEART.CONFIG.DamageType.' this '.abbreviation')}}
{{/each}}
)
<span>-</span>
{{localize (concat 'DAGGERHEART.CONFIG.Burden.' source.system.burden)}}
</h3>

View file

@ -0,0 +1,83 @@
<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>
{{#if item.uses.max}}
<h4 class="tooltip-sub-title">{{localize "DAGGERHEART.GENERAL.uses"}}</h4>
<div class="tooltip-information-section triple spaced">
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.used"}}</label>
<div>{{item.uses.value}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.max"}}</label>
<div>{{item.uses.max}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.recovery"}}</label>
{{#with (lookup config.GENERAL.refreshTypes item.uses.recovery) as | type |}}
<div>{{localize type.label}}</div>
{{/with}}
</div>
</div>
{{/if}}
{{#if (gt item.cost.length 0)}}
<h4 class="tooltip-sub-title">{{localize "DAGGERHEART.GENERAL.Cost.plural"}}</h4>
{{#each item.cost as | cost |}}
<div class="tooltip-information-section border spaced">
<div class="tooltip-information">
<label>{{localize "Type"}}</label>
{{#with (lookup @root.config.GENERAL.abilityCosts cost.type) as | type |}}
<div>{{localize type.label}}</div>
{{/with}}
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.value"}}</label>
<div>{{cost.value}}</div>
</div>
{{#if cost.scalable}}
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.scalable"}}</label>
<div>{{localize "DAGGERHEART.GENERAL.true"}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.value"}}</label>
<div>{{cost.step}}</div>
</div>
{{/if}}
</div>
{{/each}}
{{/if}}
{{#if (or item.range item.target)}}
<h4 class="tooltip-sub-title">{{localize "DAGGERHEART.UI.Tooltip.rangeAndTarget"}}</h4>
<div class="tooltip-information-section border spaced">
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.range"}}</label>
<div>
{{#if item.range}}
{{#with (lookup @root.config.GENERAL.range item.range) as | range |}}
<div>{{localize range.label}}</div>
{{/with}}
{{else}}
<div>{{localize "DAGGERHEART.GENERAL.none"}}</div>
{{/if}}
</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.target"}}</label>
<div>
{{#if item.target.type}}
{{#with (lookup @root.config.ACTIONS.targetTypes item.target.type) as | target |}}
<div>{{@root.item.target.amount}} {{localize target.label}}</div>
{{/with}}
{{else}}
<div>{{localize "DAGGERHEART.GENERAL.none"}}</div>
{{/if}}
</div>
</div>
</div>
{{/if}}
</div>

View file

@ -0,0 +1,64 @@
<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-information-section triple spaced">
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.Tiers.singular"}}</label>
{{#with (lookup config.GENERAL.tiers item.system.tier) as | tier |}}
<div>{{localize tier.label}}</div>
{{/with}}
</div>
<div class="tooltip-information">
<label>{{localize "Type"}}</label>
{{#with (lookup config.ACTOR.adversaryTypes item.system.type) as | type |}}
<div>{{localize type.label}}</div>
{{/with}}
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.ACTORS.Adversary.FIELDS.difficulty.label"}}</label>
<div>{{item.system.difficulty}}</div>
</div>
</div>
<div class="tooltip-information-section spaced">
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.hitPoints"}}</label>
<div>{{item.system.resources.hitPoints.max}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.stress"}}</label>
<div>{{item.system.resources.stress.max}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.major"}}</label>
<div>{{item.system.damageThresholds.major}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.severe"}}</label>
<div>{{item.system.damageThresholds.severe}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.attack"}}</label>
<div>{{numberFormat item.system.attack.roll.bonus sign=true}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.GENERAL.damage"}}</label>
<div>{{damageFormula item.system.attack item}}</div>
</div>
</div>
<strong>{{localize "DAGGERHEART.GENERAL.Experience.plural"}}</strong>
{{#each item.system.experiences as | experience |}}
<div>{{experience.name}} {{numberFormat experience.value sign=true}}</div>
{{/each}}
<div class="tooltip-information-section">
<div class="tooltip-information full-width">
<label>{{localize "DAGGERHEART.ACTORS.Adversary.FIELDS.motivesAndTactics.label"}}</label>
<div>{{item.system.motivesAndTactics}}</div>
</div>
</div>
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.features }}
</div>

View file

@ -1,5 +1,37 @@
<div>
<div>{{name}}</div>
<img src="{{img}}" />
<div>{{{system.description}}}</div>
<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-information-section">
<div class="tooltip-information full-width">
<label>{{localize "DAGGERHEART.ITEMS.Armor.baseScore"}}</label>
<div>{{item.system.baseScore}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.major"}}</label>
<div>{{item.system.baseThresholds.major}}</div>
</div>
<div class="tooltip-information">
<label>{{localize "DAGGERHEART.ITEMS.Armor.baseThresholds.severe"}}</label>
<div>{{item.system.baseThresholds.severe}}</div>
</div>
</div>
{{#if (gt item.system.armorFeatures.length 0)}}<h4 class="tooltip-sub-title">{{localize "DAGGERHEART.GENERAL.features"}}</h4>{{/if}}
<div class="tooltip-tags">
{{#each item.system.armorFeatures}}
{{#with (lookup ../config.ITEM.armorFeatures this.value) as | feature | }}
<div class="tooltip-tag">
<div class="tooltip-tag-label-container">
<div class="tooltip-tag-label">{{localize feature.label}}</div>
</div>
<div class="tooltip-tag-description">{{{localize feature.description}}}</div>
</div>
{{/with}}
{{/each}}
</div>
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.customActions label=(localize "DAGGERHEART.GENERAL.Action.plural")}}
</div>

View file

@ -1,6 +0,0 @@
<div>
<div>{{name}}</div>
<img src="{{system.tokenImg}}" />
<div>{{{system.examples}}}</div>
<div>{{system.advantageOn}}</div>
</div>

View file

@ -0,0 +1,14 @@
<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-information-section">
<div class="tooltip-information full-width">
<label>{{localize "DAGGERHEART.GENERAL.quantity"}}</label>
<div>{{item.system.quantity}}</div>
</div>
</div>
{{> "systems/daggerheart/templates/ui/tooltip/parts/tooltipTags.hbs" features=item.system.actions label=(localize "DAGGERHEART.GENERAL.Action.plural") }}
</div>

Some files were not shown because too many files have changed in this diff Show more