Compare commits

...

8 commits

Author SHA1 Message Date
Carlos Fernandez
c57266e596
Run lint fix on action areas PR (#1820) 2026-04-21 14:13:18 +10:00
Carlos Fernandez
f348b64aae Merge branch 'main' into feature/action-areas 2026-04-20 20:19:58 -04:00
Carlos Fernandez
3cbc18f42b
Add eslint and run linter in workflow (#1819)
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-04-21 10:15:39 +10:00
Carlos Fernandez
f850cbda76
[Feature] Updates to inventory icons, context menus, action use, and observer visibility (#1814)
* Update inventory controls and permissions filtering

* Also disable prosemirror editors

* Address context menu deprecation warnings

* Fix context menu detection for actions

* Refine logic for use action when hovering over item icon
2026-04-20 15:30:43 +02:00
WBHarry
f2ec5ef458 Merge branch 'main' of https://github.com/Foundryborne/daggerheart 2026-04-20 15:20:42 +02:00
WBHarry
c683bc4352 Fixed IncludeBaseDamage to be an override 2026-04-20 15:20:35 +02:00
Carlos Fernandez
fa04c9920f
Fix translation string (#1817) 2026-04-20 08:11:17 +02:00
WBHarry
03110377e1 Fixed so that resource reset on downtime can handle math expressions 2026-04-20 00:04:03 +02:00
67 changed files with 1312 additions and 354 deletions

View file

@ -1,3 +1,5 @@
[*] [*]
indent_size = 4 indent_size = 4
indent_style = spaces indent_style = spaces
[*.yml]
indent_size = 2

42
.github/workflows/ci.yml vendored Normal file
View file

@ -0,0 +1,42 @@
name: Project CI
on:
pull_request:
branches: [main]
push:
branches: [main]
workflow_dispatch:
branches: [main]
jobs:
build:
runs-on: ubuntu-latest
strategy:
matrix:
node-version: [24.x]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-node@v4
with:
node-version: ${{ matrix.node-version }}
- uses: pnpm/action-setup@v4
with:
version: 10
- name: Cache NPM Deps
id: cache-npm
uses: actions/cache@v3
with:
path: node_modules/
key: npm-${{ hashFiles('package-lock.json') }}
- name: Install NPM Deps
if: ${{ steps.cache-npm.outputs.cache-hit != 'true' }}
run: npm ci
- name: Lint
run: npm run lint

14
eslint.config.mjs Normal file
View file

@ -0,0 +1,14 @@
import globals from 'globals';
import { defineConfig } from 'eslint/config';
import prettier from 'eslint-plugin-prettier';
export default defineConfig([
{ files: ['**/*.{js,mjs,cjs}'], languageOptions: { globals: globals.browser } },
{ plugins: { prettier } },
{
files: ['**/*.{js,mjs,cjs}'],
rules: {
'prettier/prettier': 'error'
}
}
]);

View file

@ -118,7 +118,9 @@
"deleteTriggerTitle": "Delete Trigger", "deleteTriggerTitle": "Delete Trigger",
"deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?", "deleteTriggerContent": "Are you sure you want to delete the {trigger} trigger?",
"advantageState": "Advantage State", "advantageState": "Advantage State",
"damageOnSave": "Damage on Save" "damageOnSave": "Damage on Save",
"useDefaultItemValues": "Use default Item values",
"itemDamageIsUsed": "Item Damage Is Used"
}, },
"RollField": { "RollField": {
"diceRolling": { "diceRolling": {
@ -133,7 +135,7 @@
"attackModifier": "Attack Modifier", "attackModifier": "Attack Modifier",
"attackName": "Attack Name", "attackName": "Attack Name",
"criticalThreshold": "Critical Threshold", "criticalThreshold": "Critical Threshold",
"includeBase": { "label": "Include Item Damage" }, "includeBase": { "label": "Use Item Damage" },
"groupAttack": { "label": "Group Attack" }, "groupAttack": { "label": "Group Attack" },
"multiplier": "Multiplier", "multiplier": "Multiplier",
"saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.", "saveHint": "Set a default Trait to enable Reaction Roll. It can be changed later in Reaction Roll Dialog.",
@ -3239,6 +3241,7 @@
"Tooltip": { "Tooltip": {
"disableEffect": "Disable Effect", "disableEffect": "Disable Effect",
"enableEffect": "Enable Effect", "enableEffect": "Enable Effect",
"edit": "Edit",
"openItemWorld": "Open Item World", "openItemWorld": "Open Item World",
"openActorWorld": "Open Actor World", "openActorWorld": "Open Actor World",
"sendToChat": "Send to Chat", "sendToChat": "Send to Chat",

View file

@ -259,8 +259,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
const resetValue = increasing const resetValue = increasing
? 0 ? 0
: feature.system.resource.max : feature.system.resource.max
? Roll.replaceFormulaData(feature.system.resource.max, this.actor) ? new Roll(Roll.replaceFormulaData(feature.system.resource.max, this.actor)).evaluateSync().total
: 0; : 0;
await feature.update({ 'system.resource.value': resetValue }); await feature.update({ 'system.resource.value': resetValue });
} }

View file

@ -28,10 +28,8 @@ export default class DHActionConfig extends DHActionBaseConfig {
game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false }) game.system.api.data.activeEffects.BaseEffect.getDefaultObject({ transfer: false })
]); ]);
if (areaIndex !== undefined) if (areaIndex !== undefined) data.area[areaIndex].effects.push(created[0]._id);
data.area[areaIndex].effects.push(created[0]._id); else data.effects.push({ _id: created[0]._id });
else
data.effects.push({ _id: created[0]._id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
this.action.item.effects.get(created[0]._id).sheet.render(true); this.action.item.effects.get(created[0]._id).sheet.render(true);
} }

View file

@ -40,10 +40,8 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
this.sheetUpdate(data, effectData); this.sheetUpdate(data, effectData);
this.effects = [...this.effects, effectData]; this.effects = [...this.effects, effectData];
if(areaIndex !== undefined) if (areaIndex !== undefined) data.area[areaIndex].effects.push(effectData.id);
data.area[areaIndex].effects.push(effectData.id); else data.effects.push({ _id: effectData.id });
else
data.effects.push({ _id: effectData.id });
this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) }); this.constructor.updateForm.bind(this)(null, null, { object: foundry.utils.flattenObject(data) });
} }
@ -62,7 +60,6 @@ export default class DHActionSettingsConfig extends DHActionBaseConfig {
this.constructor.removeElement.call(this, event, button); this.constructor.removeElement.call(this, event, button);
} }
this.sheetUpdate( this.sheetUpdate(
this.action.toObject(), this.action.toObject(),
this.effects.find(x => x.id === effectId), this.effects.find(x => x.id === effectId),

View file

@ -12,8 +12,6 @@ export default class CharacterSheet extends DHBaseActorSheet {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
classes: ['character'], classes: ['character'],
position: { width: 850, height: 800 }, position: { width: 850, height: 800 },
/* Foundry adds disabled to all buttons and inputs if editPermission is missing. This is not desired. */
editPermission: CONST.DOCUMENT_OWNERSHIP_LEVELS.OBSERVER,
actions: { actions: {
toggleVault: CharacterSheet.#toggleVault, toggleVault: CharacterSheet.#toggleVault,
rollAttribute: CharacterSheet.#rollAttribute, rollAttribute: CharacterSheet.#rollAttribute,
@ -68,7 +66,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
}, },
{ {
handler: CharacterSheet.#getEquipamentContextOptions, handler: CharacterSheet.#getEquipmentContextOptions,
selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]', selector: '[data-item-uuid][data-type="armor"], [data-item-uuid][data-type="weapon"]',
options: { options: {
parentClassHooks: false, parentClassHooks: false,
@ -170,6 +168,16 @@ export default class CharacterSheet extends DHBaseActorSheet {
return applicationOptions; return applicationOptions;
} }
/** @inheritdoc */
_toggleDisabled(disabled) {
// Overriden to only disable text inputs by default.
// Everything else is done by checking @root.editable in the sheet
const form = this.form;
for (const input of form.querySelectorAll('input:not([type=search]), .editor.prosemirror')) {
input.disabled = disabled;
}
}
/** @inheritDoc */ /** @inheritDoc */
async _onRender(context, options) { async _onRender(context, options) {
await super._onRender(context, options); await super._onRender(context, options);
@ -315,11 +323,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ /**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const options = [ const options = [
{ {
name: 'toLoadout', label: 'toLoadout',
icon: 'fa-solid fa-arrow-up', icon: 'fa-solid fa-arrow-up',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && doc.system.inVault; return doc?.isOwner && doc.system.inVault;
}, },
callback: async target => { callback: async target => {
const doc = await getDocFromElement(target); const doc = await getDocFromElement(target);
@ -329,11 +337,11 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
}, },
{ {
name: 'recall', label: 'recall',
icon: 'fa-solid fa-bolt-lightning', icon: 'fa-solid fa-bolt-lightning',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && doc.system.inVault; return doc?.isOwner && doc.system.inVault;
}, },
callback: async (target, event) => { callback: async (target, event) => {
const doc = await getDocFromElement(target); const doc = await getDocFromElement(target);
@ -368,17 +376,17 @@ export default class CharacterSheet extends DHBaseActorSheet {
} }
}, },
{ {
name: 'toVault', label: 'toVault',
icon: 'fa-solid fa-arrow-down', icon: 'fa-solid fa-arrow-down',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && !doc.system.inVault; return doc?.isOwner && !doc.system.inVault;
}, },
callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true }) callback: async target => (await getDocFromElement(target)).update({ 'system.inVault': true })
} }
].map(option => ({ ].map(option => ({
...option, ...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`, label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>` icon: `<i class="${option.icon}"></i>`
})); }));
@ -391,29 +399,29 @@ export default class CharacterSheet extends DHBaseActorSheet {
* @this {CharacterSheet} * @this {CharacterSheet}
* @protected * @protected
*/ */
static #getEquipamentContextOptions() { static #getEquipmentContextOptions() {
const options = [ const options = [
{ {
name: 'equip', label: 'equip',
icon: 'fa-solid fa-hands', icon: 'fa-solid fa-hands',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && !doc.system.equipped; return doc.isOwner && doc && !doc.system.equipped;
}, },
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target) callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
}, },
{ {
name: 'unequip', label: 'unequip',
icon: 'fa-solid fa-hands', icon: 'fa-solid fa-hands',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && doc.system.equipped; return doc.isOwner && doc && doc.system.equipped;
}, },
callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target) callback: (target, event) => CharacterSheet.#toggleEquipItem.call(this, event, target)
} }
].map(option => ({ ].map(option => ({
...option, ...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`, label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>` icon: `<i class="${option.icon}"></i>`
})); }));

View file

@ -418,18 +418,18 @@ export default function DHApplicationMixin(Base) {
/**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */ /**@type {import('@client/applications/ux/context-menu.mjs').ContextMenuEntry[]} */
const options = [ const options = [
{ {
name: 'disableEffect', label: 'disableEffect',
icon: 'fa-solid fa-lightbulb', icon: 'fa-solid fa-lightbulb',
condition: element => { visible: element => {
const target = element.closest('[data-item-uuid]'); const target = element.closest('[data-item-uuid]');
return !target.dataset.disabled && target.dataset.itemType !== 'beastform'; return !target.dataset.disabled && target.dataset.itemType !== 'beastform';
}, },
callback: async target => (await getDocFromElement(target)).update({ disabled: true }) callback: async target => (await getDocFromElement(target)).update({ disabled: true })
}, },
{ {
name: 'enableEffect', label: 'enableEffect',
icon: 'fa-regular fa-lightbulb', icon: 'fa-regular fa-lightbulb',
condition: element => { visible: element => {
const target = element.closest('[data-item-uuid]'); const target = element.closest('[data-item-uuid]');
return target.dataset.disabled && target.dataset.itemType !== 'beastform'; return target.dataset.disabled && target.dataset.itemType !== 'beastform';
}, },
@ -437,7 +437,7 @@ export default function DHApplicationMixin(Base) {
} }
].map(option => ({ ].map(option => ({
...option, ...option,
name: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.name}`, label: `DAGGERHEART.APPLICATIONS.ContextMenu.${option.label}`,
icon: `<i class="${option.icon}"></i>` icon: `<i class="${option.icon}"></i>`
})); }));
@ -468,14 +468,14 @@ export default function DHApplicationMixin(Base) {
_getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) { _getContextMenuCommonOptions({ usable = false, toChat = false, deletable = true }) {
const options = [ const options = [
{ {
name: 'CONTROLS.CommonEdit', label: 'CONTROLS.CommonEdit',
icon: 'fa-solid fa-pen-to-square', icon: 'fa-solid fa-pen-to-square',
condition: target => { visible: target => {
const { dataset } = target.closest('[data-item-uuid]'); const { dataset } = target.closest('[data-item-uuid]');
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return ( return (
(!dataset.noCompendiumEdit && !doc) || (!dataset.noCompendiumEdit && !doc) ||
(doc && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection)) (doc?.isOwner && (!doc?.hasOwnProperty('systemPath') || doc?.inCollection))
); );
}, },
callback: async target => (await getDocFromElement(target)).sheet.render({ force: true }) callback: async target => (await getDocFromElement(target)).sheet.render({ force: true })
@ -484,14 +484,14 @@ export default function DHApplicationMixin(Base) {
if (usable) { if (usable) {
options.unshift({ options.unshift({
name: 'DAGGERHEART.GENERAL.damage', label: 'DAGGERHEART.GENERAL.damage',
icon: 'fa-solid fa-explosion', icon: 'fa-solid fa-explosion',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return ( const hasDamage =
!foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) || !foundry.utils.isEmpty(doc?.system?.attack?.damage.parts) ||
!foundry.utils.isEmpty(doc?.damage?.parts) !foundry.utils.isEmpty(doc?.damage?.parts);
); return doc?.isOwner && hasDamage;
}, },
callback: async (target, event) => { callback: async (target, event) => {
const doc = await getDocFromElement(target), const doc = await getDocFromElement(target),
@ -507,11 +507,11 @@ export default function DHApplicationMixin(Base) {
}); });
options.unshift({ options.unshift({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem', label: 'DAGGERHEART.APPLICATIONS.ContextMenu.useItem',
icon: 'fa-solid fa-burst', icon: 'fa-solid fa-burst',
condition: target => { visible: target => {
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc && !(doc.type === 'domainCard' && doc.system.inVault); return doc?.isOwner && !(doc.type === 'domainCard' && doc.system.inVault);
}, },
callback: async (target, event) => (await getDocFromElement(target)).use(event) callback: async (target, event) => (await getDocFromElement(target)).use(event)
}); });
@ -519,18 +519,19 @@ export default function DHApplicationMixin(Base) {
if (toChat) if (toChat)
options.push({ options.push({
name: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat', label: 'DAGGERHEART.APPLICATIONS.ContextMenu.sendToChat',
icon: 'fa-solid fa-message', icon: 'fa-solid fa-message',
callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid) callback: async target => (await getDocFromElement(target)).toChat(this.document.uuid)
}); });
if (deletable) if (deletable)
options.push({ options.push({
name: 'CONTROLS.CommonDelete', label: 'CONTROLS.CommonDelete',
icon: 'fa-solid fa-trash', icon: 'fa-solid fa-trash',
condition: element => { visible: element => {
const target = element.closest('[data-item-uuid]'); const target = element.closest('[data-item-uuid]');
return target.dataset.itemType !== 'beastform'; const doc = getDocFromElementSync(target);
return doc?.isOwner && target.dataset.itemType !== 'beastform';
}, },
callback: async (target, event) => { callback: async (target, event) => {
const doc = await getDocFromElement(target); const doc = await getDocFromElement(target);

View file

@ -48,9 +48,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
const options = super._getEntryContextOptions(); const options = super._getEntryContextOptions();
options.push( options.push(
{ {
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier', label: 'DAGGERHEART.UI.Sidebar.actorDirectory.duplicateToNewTier',
icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`, icon: `<i class="fa-solid fa-arrow-trend-up" inert></i>`,
condition: li => { visible: li => {
const actor = game.actors.get(li.dataset.entryId); const actor = game.actors.get(li.dataset.entryId);
return actor?.type === 'adversary' && actor.system.type !== 'social'; return actor?.type === 'adversary' && actor.system.type !== 'social';
}, },
@ -92,9 +92,9 @@ export default class DhActorDirectory extends foundry.applications.sidebar.tabs.
} }
}, },
{ {
name: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty', label: 'DAGGERHEART.UI.Sidebar.actorDirectory.activateParty',
icon: `<i class="fa-regular fa-square"></i>`, icon: `<i class="fa-regular fa-square"></i>`,
condition: li => { visible: li => {
const actor = game.actors.get(li.dataset.entryId); const actor = game.actors.get(li.dataset.entryId);
return actor && actor.type === 'party' && !actor.system.active; return actor && actor.type === 'party' && !actor.system.active;
}, },

View file

@ -103,23 +103,10 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
_getEntryContextOptions() { _getEntryContextOptions() {
return [ return [
...super._getEntryContextOptions(), ...super._getEntryContextOptions(),
// {
// name: 'Reroll',
// icon: '<i class="fa-solid fa-dice"></i>',
// condition: li => {
// const message = game.messages.get(li.dataset.messageId);
// return (game.user.isGM || message.isAuthor) && message.rolls.length > 0;
// },
// callback: li => {
// const message = game.messages.get(li.dataset.messageId);
// new game.system.api.applications.dialogs.RerollDialog(message).render({ force: true });
// }
// },
{ {
name: game.i18n.localize('DAGGERHEART.UI.ChatLog.rerollDamage'), label: 'DAGGERHEART.UI.ChatLog.rerollDamage',
icon: '<i class="fa-solid fa-dice"></i>', icon: '<i class="fa-solid fa-dice"></i>',
condition: li => { visible: li => {
const message = game.messages.get(li.dataset.messageId); const message = game.messages.get(li.dataset.messageId);
const hasRolledDamage = message.system.hasDamage const hasRolledDamage = message.system.hasDamage
? Object.keys(message.system.damage).length > 0 ? Object.keys(message.system.damage).length > 0

View file

@ -84,15 +84,15 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
_getCombatContextOptions() { _getCombatContextOptions() {
return [ return [
{ {
name: 'COMBAT.ClearMovementHistories', label: 'COMBAT.ClearMovementHistories',
icon: '<i class="fa-solid fa-shoe-prints"></i>', icon: '<i class="fa-solid fa-shoe-prints"></i>',
condition: () => game.user.isGM && this.viewed?.combatants.size > 0, visible: () => game.user.isGM && this.viewed?.combatants.size > 0,
callback: () => this.viewed.clearMovementHistories() callback: () => this.viewed.clearMovementHistories()
}, },
{ {
name: 'COMBAT.Delete', label: 'COMBAT.Delete',
icon: '<i class="fa-solid fa-trash"></i>', icon: '<i class="fa-solid fa-trash"></i>',
condition: () => game.user.isGM && !!this.viewed, visible: () => game.user.isGM && !!this.viewed,
callback: () => this.viewed.endCombat() callback: () => this.viewed.endCombat()
} }
]; ];

View file

@ -100,7 +100,11 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
const { line, rectangle, inFront, cone } = CONFIG.DH.GENERAL.templateTypes; const { line, rectangle, inFront, cone } = CONFIG.DH.GENERAL.templateTypes;
const usedAngle = const usedAngle =
type === cone.id ? (angle ?? CONFIG.MeasuredTemplate.defaults.angle) : type === inFront.id ? '180' : undefined; type === cone.id
? (angle ?? CONFIG.MeasuredTemplate.defaults.angle)
: type === inFront.id
? '180'
: undefined;
const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields; const { grid, distance } = CONFIG.Scene.documentClass.schema.fields.grid.fields;
const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial; const sceneGridSize = canvas.scene?.grid.size ?? grid.size.initial;
@ -109,7 +113,8 @@ export default class DhRegionLayer extends foundry.canvas.layers.RegionLayer {
const rangeNumber = Number(range); const rangeNumber = Number(range);
const settings = canvas.scene?.rangeSettings; const settings = canvas.scene?.rangeSettings;
const baseDistance = (!Number.isNaN(rangeNumber) ? rangeNumber : (settings ? settings[range] : 0)) * dimensionConstant; const baseDistance =
(!Number.isNaN(rangeNumber) ? rangeNumber : settings ? settings[range] : 0) * dimensionConstant;
const length = baseDistance; const length = baseDistance;
const radius = length; const radius = length;

View file

@ -75,12 +75,17 @@ export const typeConfig = {
{ {
key: 'type', key: 'type',
label: 'DAGGERHEART.GENERAL.type', label: 'DAGGERHEART.GENERAL.type',
format: type => type ? `TYPES.Item.${type}` : '-' format: type => (type ? `TYPES.Item.${type}` : '-')
}, },
{ {
key: 'system.secondary', key: 'system.secondary',
label: 'DAGGERHEART.UI.ItemBrowser.subtype', label: 'DAGGERHEART.UI.ItemBrowser.subtype',
format: isSecondary => (isSecondary ? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short' : isSecondary === false ? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short' : '-') format: isSecondary =>
isSecondary
? 'DAGGERHEART.ITEMS.Weapon.secondaryWeapon.short'
: isSecondary === false
? 'DAGGERHEART.ITEMS.Weapon.primaryWeapon.short'
: '-'
}, },
{ {
key: 'system.tier', key: 'system.tier',
@ -260,12 +265,12 @@ export const typeConfig = {
{ {
key: 'system.type', key: 'system.type',
label: 'DAGGERHEART.GENERAL.type', label: 'DAGGERHEART.GENERAL.type',
format: type => type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-' format: type => (type ? `DAGGERHEART.CONFIG.DomainCardTypes.${type}` : '-')
}, },
{ {
key: 'system.domain', key: 'system.domain',
label: 'DAGGERHEART.GENERAL.Domain.single', label: 'DAGGERHEART.GENERAL.Domain.single',
format: domain => domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-' format: domain => (domain ? CONFIG.DH.DOMAIN.allDomains()[domain].label : '-')
}, },
{ {
key: 'system.level', key: 'system.level',

View file

@ -42,7 +42,7 @@ export const gameSettings = {
SpotlightRequestQueue: 'SpotlightRequestQueue', SpotlightRequestQueue: 'SpotlightRequestQueue',
CompendiumBrowserSettings: 'CompendiumBrowserSettings', CompendiumBrowserSettings: 'CompendiumBrowserSettings',
SpotlightTracker: 'SpotlightTracker', SpotlightTracker: 'SpotlightTracker',
ActiveParty: 'ActiveParty', ActiveParty: 'ActiveParty'
}; };
export const actionAutomationChoices = { export const actionAutomationChoices = {

View file

@ -13,7 +13,7 @@ export default class DHAttackAction extends DHDamageAction {
if (!!this.item?.system?.attack) { if (!!this.item?.system?.attack) {
if (this.damage.includeBase) { if (this.damage.includeBase) {
const baseDamage = this.getParentDamage(); const baseDamage = this.getParentDamage();
this.damage.parts.unshift(new DHDamageData(baseDamage)); this.damage.parts.hitPoints = new DHDamageData(baseDamage);
} }
if (this.roll.useDefault) { if (this.roll.useDefault) {
this.roll.trait = this.item.system.attack.roll.trait; this.roll.trait = this.item.system.attack.roll.trait;

View file

@ -110,6 +110,11 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
return this._id; return this._id;
} }
/** Returns true if the current user is the owner of the containing item */
get isOwner() {
return this.item?.isOwner ?? true;
}
/** /**
* Return Item the action is attached too. * Return Item the action is attached too.
*/ */
@ -143,6 +148,12 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
: null; : null;
} }
/** Returns true if the action is usable */
get usable() {
const actor = this.actor;
return this.isOwner && actor?.type === 'character';
}
static getRollType(parent) { static getRollType(parent) {
return 'trait'; return 'trait';
} }

View file

@ -94,9 +94,12 @@ export default class BaseEffect extends foundry.data.ActiveEffectTypeDataModel {
}, },
{ nullable: true, initial: null } { nullable: true, initial: null }
), ),
targetDispositions: new fields.SetField(new fields.NumberField({ targetDispositions: new fields.SetField(
choices: CONFIG.DH.GENERAL.simpleDispositions, new fields.NumberField({
}), { label: 'Affected Dispositions' }), choices: CONFIG.DH.GENERAL.simpleDispositions
}),
{ label: 'Affected Dispositions' }
)
}; };
} }

View file

@ -20,7 +20,7 @@ export default class DhCharacter extends DhCreature {
settingSheet: DHCharacterSettings, settingSheet: DHCharacterSettings,
isNPC: false, isNPC: false,
hasInventory: true, hasInventory: true,
quantifiable: ["loot", "consumable"] quantifiable: ['loot', 'consumable']
}); });
} }
@ -302,7 +302,7 @@ export default class DhCharacter extends DhCreature {
choices: CONFIG.DH.GENERAL.dieFaces, choices: CONFIG.DH.GENERAL.dieFaces,
initial: null, initial: null,
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice' label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
}), })
}) })
}) })
}; };

View file

@ -78,7 +78,7 @@ export default class DhCompanion extends DhCreature {
choices: CONFIG.DH.GENERAL.dieFaces, choices: CONFIG.DH.GENERAL.dieFaces,
initial: null, initial: null,
label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice' label: 'DAGGERHEART.ACTORS.Character.defaultDisadvantageDice'
}), })
}) })
}), }),
attack: new ActionField({ attack: new ActionField({

View file

@ -9,7 +9,7 @@ export default class DhParty extends BaseDataActor {
static get metadata() { static get metadata() {
return foundry.utils.mergeObject(super.metadata, { return foundry.utils.mergeObject(super.metadata, {
hasInventory: true, hasInventory: true,
quantifiable: ["weapon", "armor", "loot", "consumable"] quantifiable: ['weapon', 'armor', 'loot', 'consumable']
}); });
} }

View file

@ -11,7 +11,7 @@ export default class DHAbilityUse extends foundry.abstract.TypeDataModel {
actor: new fields.StringField(), actor: new fields.StringField(),
item: new fields.StringField(), item: new fields.StringField(),
action: new fields.StringField() action: new fields.StringField()
}), })
}; };
} }

View file

@ -33,7 +33,7 @@ export default class AreaField extends fields.ArrayField {
initial: CONFIG.DH.GENERAL.range.veryClose.id, initial: CONFIG.DH.GENERAL.range.veryClose.id,
label: 'DAGGERHEART.ACTIONS.Config.area.size' label: 'DAGGERHEART.ACTIONS.Config.area.size'
}), }),
effects: new fields.ArrayField(new fields.DocumentIdField()), effects: new fields.ArrayField(new fields.DocumentIdField())
}); });
super(element, options, context); super(element, options, context);
} }

View file

@ -40,9 +40,7 @@ export default class DHSummonField extends fields.ArrayField {
const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item)); const roll = new Roll(itemAbleRollParse(summon.count, this.actor, this.item));
await roll.evaluate(); await roll.evaluate();
const count = roll.total; const count = roll.total;
if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) if (!roll.isDeterministic && game.modules.get('dice-so-nice')?.active) rolls.push(roll);
rolls.push(roll);
const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID)); const actor = await DHSummonField.getWorldActor(await foundry.utils.fromUuid(summon.actorUUID));
/* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */ /* Extending summon data in memory so it's available in actionField.toChat. Think it's harmless, but ugly. Could maybe find a better way. */

View file

@ -287,7 +287,7 @@ export function ActionMixin(Base) {
source: { source: {
actor: this.actor.uuid, actor: this.actor.uuid,
item: this.item.id, item: this.item.id,
action: this.id, action: this.id
}, },
itemOrigin: this.item, itemOrigin: this.item,
description: this.description || (this.item instanceof Item ? this.item.system.description : '') description: this.description || (this.item instanceof Item ? this.item.system.description : '')

View file

@ -108,6 +108,8 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
} }
get actionsList() { get actionsList() {
// No actions on non-characters
if (this.actor && this.actor.type !== 'character') return [];
return this.actions; return this.actions;
} }

View file

@ -99,7 +99,9 @@ export default class DHWeapon extends AttachableItem {
/* -------------------------------------------- */ /* -------------------------------------------- */
get actionsList() { get actionsList() {
return [this.attack, ...this.actions]; // No actions on non-characters
if (this.actor && this.actor.type !== 'character') return [];
return [this.attack, ...super.actionsList];
} }
get customActions() { get customActions() {

View file

@ -1,7 +1,9 @@
export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect { export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModels.applyActiveEffect {
static async #getApplicableEffects(token) { static async #getApplicableEffects(token) {
const effects = await Promise.all(this.effects.map(foundry.utils.fromUuid)); const effects = await Promise.all(this.effects.map(foundry.utils.fromUuid));
return effects.filter(effect => !effect.system.targetDispositions.size || effect.system.targetDispositions.has(token.disposition)); return effects.filter(
effect => !effect.system.targetDispositions.size || effect.system.targetDispositions.has(token.disposition)
);
} }
static async #onTokenEnter(event) { static async #onTokenEnter(event) {
@ -26,13 +28,13 @@ export default class DhApplyActiveEffect extends CONFIG.RegionBehavior.dataModel
data.origin = this.parent.uuid; data.origin = this.parent.uuid;
toCreate.push(data); toCreate.push(data);
} }
if ( toCreate.length ) await actor.createEmbeddedDocuments("ActiveEffect", toCreate); if (toCreate.length) await actor.createEmbeddedDocuments('ActiveEffect', toCreate);
await resumeMovement?.(); await resumeMovement?.();
} }
/** @override */ /** @override */
static events = { static events = {
...CONFIG.RegionBehavior.dataModels.applyActiveEffect.events, ...CONFIG.RegionBehavior.dataModels.applyActiveEffect.events,
[CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter, [CONST.REGION_EVENTS.TOKEN_ENTER]: this.#onTokenEnter
}; };
} }

View file

@ -11,7 +11,9 @@ export default class DualityRoll extends D20Roll {
this.rallyChoices = this.setRallyChoices(); this.rallyChoices = this.setRallyChoices();
this.guaranteedCritical = options.guaranteedCritical; this.guaranteedCritical = options.guaranteedCritical;
const advantageFaces = data.rules?.roll?.defaultAdvantageDice ? Number.parseInt(data.rules.roll.defaultAdvantageDice) : 6 const advantageFaces = data.rules?.roll?.defaultAdvantageDice
? Number.parseInt(data.rules.roll.defaultAdvantageDice)
: 6;
this.advantageFaces = Number.isNaN(advantageFaces) ? 6 : advantageFaces; this.advantageFaces = Number.isNaN(advantageFaces) ? 6 : advantageFaces;
} }

View file

@ -200,7 +200,6 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
static effectSafeEval(expression) { static effectSafeEval(expression) {
let result; let result;
try { try {
// eslint-disable-next-line no-new-func
const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`); const evl = new Function('sandbox', `with (sandbox) { return ${expression}}`);
result = evl(Roll.MATH_PROXY); result = evl(Roll.MATH_PROXY);
} catch (err) { } catch (err) {

View file

@ -254,7 +254,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
} }
async onCreateAreas(event) { async onCreateAreas(event) {
const createArea = async (selectedArea) => { const createArea = async selectedArea => {
const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid); const effects = selectedArea.effects.map(effect => this.system.action.item.effects.get(effect).uuid);
const { shape: type, size: range } = selectedArea; const { shape: type, size: range } = selectedArea;
const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range }); const shapeData = CONFIG.Canvas.layers.regions.layerClass.getTemplateShape({ type, range });
@ -264,13 +264,15 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
name: selectedArea.name, name: selectedArea.name,
shapes: [shapeData], shapes: [shapeData],
restriction: { enabled: false, type: 'move', priority: 0 }, restriction: { enabled: false, type: 'move', priority: 0 },
behaviors: [{ behaviors: [
{
name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'), name: game.i18n.localize('TYPES.RegionBehavior.applyActiveEffect'),
type: 'applyActiveEffect', type: 'applyActiveEffect',
system: { system: {
effects: effects effects: effects
} }
}], }
],
displayMeasurements: true, displayMeasurements: true,
locked: false, locked: false,
ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE }, ownership: { default: CONST.DOCUMENT_OWNERSHIP_LEVELS.NONE },
@ -278,10 +280,9 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
}, },
{ create: true } { create: true }
); );
} };
if (this.system.action.area.length === 1) if (this.system.action.area.length === 1) createArea(this.system.action.area[0]);
createArea(this.system.action.area[0]);
else if (this.system.action.area.length > 1) { else if (this.system.action.area.length > 1) {
/* Pop a selection. Possibly a context menu? */ /* Pop a selection. Possibly a context menu? */
new foundry.applications.ux.ContextMenu.implementation( new foundry.applications.ux.ContextMenu.implementation(
@ -289,7 +290,7 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
'.action-areas', '.action-areas',
this.system.action.area.map((area, index) => ({ this.system.action.area.map((area, index) => ({
name: area.name, name: area.name,
callback: () => createArea(this.system.action.area[index]), callback: () => createArea(this.system.action.area[index])
})), })),
{ {
jQuery: false, jQuery: false,

View file

@ -3,7 +3,7 @@ export default class DhActorCollection extends foundry.documents.collections.Act
get party() { get party() {
const id = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty); const id = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.ActiveParty);
const actor = game.actors.get(id); const actor = game.actors.get(id);
return actor?.type === "party" ? actor : null; return actor?.type === 'party' ? actor : null;
} }
/** Ensure companions are initialized after all other subtypes. */ /** Ensure companions are initialized after all other subtypes. */

View file

@ -76,6 +76,13 @@ export default class DHItem extends foundry.documents.Item {
return this.system.metadata.isInventoryItem ?? false; return this.system.metadata.isInventoryItem ?? false;
} }
/** Returns true if the item can be used */
get usable() {
const actor = this.actor;
const actionsList = this.system.actionsList;
return this.isOwner && actor?.type === 'character' && (actionsList?.size || actionsList?.length);
}
/** @inheritdoc */ /** @inheritdoc */
static async createDialog(data = {}, createOptions = {}, options = {}) { static async createDialog(data = {}, createOptions = {}, options = {}) {
const { folders, types, template, context = {}, ...dialogOptions } = options; const { folders, types, template, context = {}, ...dialogOptions } = options;

View file

@ -61,7 +61,7 @@ export const renderMeasuredTemplate = async event => {
type, type,
angle, angle,
range, range,
direction, direction
}); });
await canvas.regions.placeRegion( await canvas.regions.placeRegion(

View file

@ -16,7 +16,7 @@ export default class RegisterHandlebarsHelpers {
empty: this.empty, empty: this.empty,
pluralize: this.pluralize, pluralize: this.pluralize,
positive: this.positive, positive: this.positive,
isNullish: this.isNullish, isNullish: this.isNullish
}); });
} }
static add(a, b) { static add(a, b) {

View file

@ -56,10 +56,10 @@ export const registerKeyBindings = () => {
game.keybindings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.keybindings.partySheet, { game.keybindings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.keybindings.partySheet, {
name: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.name'), name: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.name'),
hint: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.hint'), hint: _loc('DAGGERHEART.SETTINGS.Keybindings.partySheet.hint'),
editable: [{ key: "KeyP" }], editable: [{ key: 'KeyP' }],
onDown: () => { onDown: () => {
const controlled = canvas.ready ? canvas.tokens.controlled : []; const controlled = canvas.ready ? canvas.tokens.controlled : [];
const selectedParty = controlled.find((c) => c.actor?.type === 'party')?.actor; const selectedParty = controlled.find(c => c.actor?.type === 'party')?.actor;
const party = selectedParty ?? game.actors.party; const party = selectedParty ?? game.actors.party;
if (!party) return; if (!party) return;
@ -215,6 +215,6 @@ const registerNonConfigSettings = () => {
scope: 'world', scope: 'world',
config: false, config: false,
type: String, type: String,
default: null, default: null
}); });
}; };

872
package-lock.json generated

File diff suppressed because it is too large Load diff

View file

@ -17,13 +17,18 @@
"pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs", "pullYMLtoLDB": "node ./tools/pullYMLtoLDB.mjs",
"pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build", "pullYMLtoLDBBuild": "node ./tools/pullYMLtoLDB.mjs --build",
"createSymlink": "node ./tools/create-symlink.mjs", "createSymlink": "node ./tools/create-symlink.mjs",
"setup:dev": "node ./tools/dev-setup.mjs" "setup:dev": "node ./tools/dev-setup.mjs",
"lint": "eslint",
"lint:fix": "eslint --fix"
}, },
"devDependencies": { "devDependencies": {
"@foundryvtt/foundryvtt-cli": "^1.0.2", "@foundryvtt/foundryvtt-cli": "^1.0.2",
"@rollup/plugin-commonjs": "^25.0.7", "@rollup/plugin-commonjs": "^25.0.7",
"@rollup/plugin-node-resolve": "^15.2.3", "@rollup/plugin-node-resolve": "^15.2.3",
"concurrently": "^8.2.2", "concurrently": "^8.2.2",
"eslint": "^10.2.1",
"eslint-plugin-prettier": "^5.5.5",
"globals": "^17.5.0",
"husky": "^9.1.5", "husky": "^9.1.5",
"lint-staged": "^15.2.10", "lint-staged": "^15.2.10",
"postcss": "^8.4.32", "postcss": "^8.4.32",

View file

@ -11,21 +11,6 @@
padding-bottom: 0; padding-bottom: 0;
overflow-x: auto; overflow-x: auto;
&.viewMode {
button:not(.btn-toggle-view),
input:not(.search),
.controls,
.character-sidebar-sheet,
.img-portait,
.name-row,
.hope-section,
.downtime-section,
.character-traits,
.card-list {
pointer-events: none;
}
}
.character-sidebar-sheet { .character-sidebar-sheet {
grid-row: 1 / span 2; grid-row: 1 / span 2;
grid-column: 1; grid-column: 1;

View file

@ -316,9 +316,9 @@
border-radius: 3px; border-radius: 3px;
background: light-dark(@dark-blue, @golden); background: light-dark(@dark-blue, @golden);
clip-path: none; clip-path: none;
cursor: pointer;
display: flex; display: flex;
align-items: center; align-items: center;
justify-content: center;
gap: 4px; gap: 4px;
border: 1px solid transparent; border: 1px solid transparent;
transition: all 0.3s ease; transition: all 0.3s ease;

View file

@ -21,7 +21,7 @@
</div> </div>
{{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}} {{!-- Handlebars uses Symbol.Iterator to produce index|key. This isn't compatible with our parts object, so we instead use applyTo, which is the same value --}}
{{#each source.parts as |dmg|}} {{#each source.parts as |dmg key|}}
<div class="nest-inputs"> <div class="nest-inputs">
<fieldset{{#if dmg.base}} disabled{{/if}} class="one-column{{#if ../path}} no-style{{/if}}"> <fieldset{{#if dmg.base}} disabled{{/if}} class="one-column{{#if ../path}} no-style{{/if}}">
<legend class="with-icon"> <legend class="with-icon">
@ -31,6 +31,7 @@
{{/unless}} {{/unless}}
</legend> </legend>
{{#unless (and @root.source.damage.includeBase (eq key 'hitPoints'))}}
{{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}} {{#if (and (not @root.isNPC) @root.hasRoll (not dmg.base))}}
{{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}} {{formField ../fields.resultBased value=dmg.resultBased name=(concat "damage.parts." dmg.applyTo ".resultBased") localize=true classes="checkbox"}}
{{/if}} {{/if}}
@ -65,6 +66,9 @@
</fieldset> </fieldset>
{{/if}} {{/if}}
<input type="hidden" name="{{concat ../path "damage.parts." dmg.applyTo ".base"}}" value="{{dmg.base}}"> <input type="hidden" name="{{concat ../path "damage.parts." dmg.applyTo ".base"}}" value="{{dmg.base}}">
{{else}}
<span class="hint">{{localize "DAGGERHEART.ACTIONS.Config.itemDamageIsUsed"}}</span>
{{/unless}}
</fieldset> </fieldset>
</div> </div>
{{/each}} {{/each}}

View file

@ -1,7 +1,7 @@
<fieldset class="one-column{{#if source.useDefault}} child-disabled{{/if}}"> <fieldset class="one-column{{#if source.useDefault}} child-disabled{{/if}}">
<legend> <legend>
Roll {{localize "DAGGERHEART.GENERAL.roll"}}
{{#if @root.hasBaseDamage}}{{formInput fields.useDefault name="roll.useDefault" value=source.useDefault dataset=(object tooltip="Use default Item values" tooltipDirection="UP")}}{{/if}} {{#if @root.hasBaseDamage}}{{formInput fields.useDefault name="roll.useDefault" value=source.useDefault dataset=(object tooltip=(localize "DAGGERHEART.ACTIONS.Config.useDefaultItemValues") tooltipDirection="UP")}}{{/if}}
</legend> </legend>
{{formField fields.type label="DAGGERHEART.GENERAL.type" name="roll.type" value=source.type localize=true choices=@root.getRollTypeOptions localize=true}} {{formField fields.type label="DAGGERHEART.GENERAL.type" name="roll.type" value=source.type localize=true choices=@root.getRollTypeOptions localize=true}}

View file

@ -6,7 +6,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.actives collection=effects.actives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
@ -15,7 +15,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
</div> </div>

View file

@ -6,8 +6,8 @@
type='feature' type='feature'
collection=@root.features collection=@root.features
hideContextMenu=true hideContextMenu=true
canCreate=true canCreate=@root.editable
showActions=true showActions=@root.editable
}} }}
</div> </div>
</section> </section>

View file

@ -7,7 +7,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.actives collection=effects.actives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
@ -16,7 +16,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
disabled=true disabled=true
}} }}

View file

@ -8,8 +8,8 @@
type='feature' type='feature'
actorType='character' actorType='character'
collection=category.values collection=category.values
canCreate=true canCreate=@root.editable
showActions=true showActions=@root.editable
}} }}
{{else if category.values}} {{else if category.values}}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
@ -18,7 +18,7 @@
actorType='character' actorType='character'
collection=category.values collection=category.values
canCreate=false canCreate=false
showActions=true showActions=@root.editable
}} }}
{{/if}} {{/if}}

View file

@ -4,6 +4,7 @@
<h1 class="actor-name input" contenteditable="plaintext-only" data-property="name" placeholder="{{localize "DAGGERHEART.GENERAL.actorName"}}">{{source.name}}</h1> <h1 class="actor-name input" contenteditable="plaintext-only" data-property="name" placeholder="{{localize "DAGGERHEART.GENERAL.actorName"}}">{{source.name}}</h1>
<div class='level-div'> <div class='level-div'>
<h3 class='label'> <h3 class='label'>
{{#if @root.editable}}
{{#if document.system.needsCharacterSetup}} {{#if document.system.needsCharacterSetup}}
<button <button
type="button" type="button"
@ -21,6 +22,7 @@
<i class="fa-solid fa-angles-up"></i> <i class="fa-solid fa-angles-up"></i>
</button> </button>
{{/if}} {{/if}}
{{/if}}
{{#unless document.system.needsCharacterSetup}} {{#unless document.system.needsCharacterSetup}}
{{localize 'DAGGERHEART.GENERAL.level'}} {{localize 'DAGGERHEART.GENERAL.level'}}
<input type="text" data-dtype="Number" class="level-value" value={{#if document.system.needsCharacterSetup}}0{{else}}{{document.system.levelData.level.changed}}{{/if}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} /> <input type="text" data-dtype="Number" class="level-value" value={{#if document.system.needsCharacterSetup}}0{{else}}{{document.system.levelData.level.changed}}{{/if}} {{#if document.system.needsCharacterSetup}}disabled{{/if}} />
@ -110,12 +112,14 @@
<i class="fa-solid fa-fw fa-users"></i> <i class="fa-solid fa-fw fa-users"></i>
</button> </button>
{{/if}} {{/if}}
{{#if @root.editable}}
<button type="button" data-action="useDowntime" data-type="shortRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"> <button type="button" data-action="useDowntime" data-type="shortRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.shortRest.title">
<i class="fa-solid fa-fw fa-utensils"></i> <i class="fa-solid fa-fw fa-utensils"></i>
</button> </button>
<button type="button" data-action="useDowntime" data-type="longRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.longRest.title"> <button type="button" data-action="useDowntime" data-type="longRest" data-tooltip="DAGGERHEART.APPLICATIONS.Downtime.longRest.title">
<i class="fa-solid fa-fw fa-bed"></i> <i class="fa-solid fa-fw fa-bed"></i>
</button> </button>
{{/if}}
</div> </div>
</div> </div>

View file

@ -22,7 +22,7 @@
type='weapon' type='weapon'
collection=@root.inventory.weapons collection=@root.inventory.weapons
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
@ -30,7 +30,7 @@
type='armor' type='armor'
collection=@root.inventory.armor collection=@root.inventory.armor
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
@ -38,7 +38,7 @@
type='consumable' type='consumable'
collection=@root.inventory.consumables collection=@root.inventory.consumables
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
isQuantifiable=true isQuantifiable=true
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
@ -46,8 +46,8 @@
type='loot' type='loot'
collection=@root.inventory.loot collection=@root.inventory.loot
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
showActions=true showActions=@root.editable
isQuantifiable=true isQuantifiable=true
}} }}
</div> </div>

View file

@ -27,7 +27,7 @@
isGlassy=true isGlassy=true
cardView=cardView cardView=cardView
collection=document.system.domainCards.loadout collection=document.system.domainCards.loadout
canCreate=true canCreate=@root.editable
}} }}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='DAGGERHEART.GENERAL.Tabs.vault' title='DAGGERHEART.GENERAL.Tabs.vault'
@ -35,7 +35,7 @@
isGlassy=true isGlassy=true
cardView=cardView cardView=cardView
collection=document.system.domainCards.vault collection=document.system.domainCards.vault
canCreate=true canCreate=@root.editable
inVault=true inVault=true
}} }}
</div> </div>

View file

@ -45,11 +45,11 @@
</a> </a>
{{/times}} {{/times}}
</div> </div>
<a class="slot-label" data-action="toggleArmorMangement"> <a class="slot-label" data-action="toggleArmorMangement" {{disabled (not @root.editable)}}>
<span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span> <span class="label">{{localize "DAGGERHEART.GENERAL.armorSlots"}}</span>
<div class="slot-value-container"> <div class="slot-value-container">
<span class="value">{{document.system.armorScore.value}} / {{document.system.armorScore.max}}</span> <span class="value">{{document.system.armorScore.value}} / {{document.system.armorScore.max}}</span>
<i class="fa-solid fa-gear"></i> {{#if @root.editable}}<i class="fa-solid fa-gear" inert></i>{{/if}}
</div> </div>
</a> </a>
</div> </div>
@ -64,9 +64,9 @@
value='{{document.system.armorScore.value}}' value='{{document.system.armorScore.value}}'
max='{{document.system.armorScore.max}}' max='{{document.system.armorScore.max}}'
></progress> ></progress>
<a class="status-label" data-action="toggleArmorMangement"> <a class="status-label" data-action="toggleArmorMangement" {{disabled (not @root.editable)}}>
<h4>{{localize "DAGGERHEART.GENERAL.armorSlots"}}</h4> <h4>{{localize "DAGGERHEART.GENERAL.armorSlots"}}</h4>
<i class="fa-solid fa-gear"></i> {{#if @root.editable}}<i class="fa-solid fa-gear" inert></i>{{/if}}
</a> </a>
{{/if}} {{/if}}
</div> </div>

View file

@ -6,7 +6,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.actives collection=effects.actives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
@ -15,7 +15,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
</div> </div>

View file

@ -9,8 +9,8 @@
type='feature' type='feature'
collection=@root.features collection=@root.features
hideContextMenu=true hideContextMenu=true
canCreate=true canCreate=@root.editable
showActions=true showActions=@root.editable
}} }}
</div> </div>
</section> </section>

View file

@ -26,7 +26,7 @@
actorType='party' actorType='party'
collection=@root.inventory.weapons collection=@root.inventory.weapons
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
hideContextMenu=true hideContextMenu=true
isQuantifiable=true isQuantifiable=true
@ -37,7 +37,7 @@
actorType='party' actorType='party'
collection=@root.inventory.armor collection=@root.inventory.armor
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
hideContextMenu=true hideContextMenu=true
isQuantifiable=true isQuantifiable=true
@ -48,7 +48,7 @@
actorType='party' actorType='party'
collection=@root.inventory.consumables collection=@root.inventory.consumables
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
hideContextMenu=true hideContextMenu=true
isQuantifiable=true isQuantifiable=true
}} }}
@ -58,7 +58,7 @@
actorType='party' actorType='party'
collection=@root.inventory.loot collection=@root.inventory.loot
isGlassy=true isGlassy=true
canCreate=true canCreate=@root.editable
hideContextMenu=true hideContextMenu=true
isQuantifiable=true isQuantifiable=true
}} }}

View file

@ -52,12 +52,11 @@ Parameters:
{{else}} {{else}}
<ul class="items-list"> <ul class="items-list">
{{#each collection as |item|}} {{#each collection as |item|}}
{{> 'daggerheart.inventory-item' {{> 'daggerheart.inventory-item'
item=item item=item
type=../type type=../type
disabledEffect=../disabledEffect disabledEffect=../disabledEffect
actorType=../actorType actorType=(ifThen ../actorType ../actorType @root.document.type)
hideControls=../hideControls hideControls=../hideControls
hideContextMenu=../hideContextMenu hideContextMenu=../hideContextMenu
isActor=../isActor isActor=../isActor

View file

@ -25,11 +25,11 @@ Parameters:
> >
<div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}> <div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
{{!-- Image --}} {{!-- Image --}}
<div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen <div class="img-portait" data-action='{{ifThen item.usable "useItem" (ifThen
(hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}} (hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}}
data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}} draggable="true"> data-tooltip="#attack#{{item.actor.uuid}}" {{else}} data-tooltip="#item#{{item.uuid}}" {{/if}} {{/unless}} draggable="true">
<img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" /> <img src="{{item.img}}" class="item-img {{#if isActor}}actor-img{{/if}}" />
{{#if (or item.system.actionsList.size item.system.actionsList.length item.actionType)}} {{#if item.usable}}
{{#if @root.isNPC}} {{#if @root.isNPC}}
<img class="roll-img d20" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20"> <img class="roll-img d20" src="systems/daggerheart/assets/icons/dice/default/d20.svg" alt="d20">
{{else}} {{else}}
@ -72,62 +72,59 @@ Parameters:
{{!-- Controls --}} {{!-- Controls --}}
{{#unless hideControls}} {{#unless hideControls}}
<div class="controls"> <div class="controls">
{{#if isActor}} {{!-- Toggle/Equip buttons --}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.openActorWorld"> {{#if @root.editable}}
<i class="fa-solid fa-globe"></i> {{#if (and (eq actorType 'character') (eq type 'weapon'))}}
</a>
{{#if (eq type 'adversary')}}
<a data-action='deleteAdversary' data-category="{{categoryAdversary}}" data-tooltip="CONTROLS.CommonDelete">
<i class='fas fa-trash'></i>
</a>
{{/if}}
{{#if (eq type 'character')}}
<a data-action='deletePartyMember' data-tooltip="CONTROLS.CommonDelete">
<i class='fas fa-trash'></i>
</a>
{{/if}}
{{else}}
{{#unless (eq actorType 'party')}}
{{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}"> data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
<i class="fa-solid fa-hands"></i> <i class="fa-solid fa-hands" inert></i>
</a>
{{else if (eq type 'armor')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
<i class="fa-solid fa-fw fa-shield"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (eq type 'domainCard')}} {{#if (and (eq actorType 'character') (eq type 'armor'))}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
<i class="fa-solid fa-fw fa-shield" inert></i>
</a>
{{/if}}
{{#if (and (eq type 'domainCard'))}}
<a data-action="toggleVault" <a data-action="toggleVault"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.inVault 'sendToLoadout' 'sendToVault' }}"> data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.inVault 'sendToLoadout' 'sendToVault' }}">
<i class="fa-solid {{ifThen item.system.inVault 'fa-arrow-up' 'fa-arrow-down'}}"></i> <i class="fa-solid {{ifThen item.system.inVault 'fa-arrow-up' 'fa-arrow-down'}}" inert></i>
</a> </a>
{{else if (and (eq type 'effect') (not (eq item.type 'beastform')))}} {{/if}}
{{#if (and (and (eq type 'effect') (not (eq item.type 'beastform'))))}}
<a data-action="toggleEffect" <a data-action="toggleEffect"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.disabled 'enableEffect' 'disableEffect' }}"> data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.disabled 'enableEffect' 'disableEffect' }}">
<i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}"></i> <i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}" inert></i>
</a> </a>
{{/if}} {{/if}}
{{/if}}
{{!-- Send to Chat --}}
{{#if (hasProperty item "toChat")}} {{#if (hasProperty item "toChat")}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat"> <a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
<i class="fa-regular fa-fw fa-message"></i> <i class="fa-regular fa-fw fa-message" inert></i>
</a> </a>
{{/if}} {{/if}}
{{else}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.openActorWorld"> {{!-- Document management buttons or context menu --}}
<i class="fa-solid fa-globe"></i> {{#if (and (not isActor) (not hideContextMenu))}}
</a>
<a data-action="deleteItem" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
<i class="fa-solid fa-trash"></i>
</a>
{{/unless}}
{{#unless hideContextMenu}}
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions"> <a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
<i class="fa-solid fa-fw fa-ellipsis-vertical"></i> <i class="fa-solid fa-fw fa-ellipsis-vertical" inert></i>
</a> </a>
{{/unless}} {{else if @root.editable}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.edit">
<i class="fa-solid fa-edit" inert></i>
</a>
{{#if (not isActor)}}
<a data-action="deleteItem" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
<i class="fa-solid fa-trash" inert></i>
</a>
{{else if (eq type 'adversary')}}
<a data-action='deleteAdversary' data-category="{{categoryAdversary}}" data-tooltip="CONTROLS.CommonDelete">
<i class="fas fa-trash" inert></i>
</a>
{{/if}}
{{/if}} {{/if}}
</div> </div>
{{/unless}} {{/unless}}

View file

@ -48,6 +48,7 @@
</a> </a>
{{/if}} {{/if}}
{{else}} {{else}}
{{#if @root.editable}}
{{#if (eq type 'weapon')}} {{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem" <a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}"> data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.system.equipped 'unequip' 'equip' }}">
@ -69,6 +70,7 @@
<i class="fa-solid fa-fw {{ifThen item.disabled 'fa-toggle-off' 'fa-toggle-on'}}"></i> <i class="fa-solid fa-fw {{ifThen item.disabled 'fa-toggle-off' 'fa-toggle-on'}}"></i>
</a> </a>
{{/if}} {{/if}}
{{/if}}
{{#if (hasProperty item "toChat")}} {{#if (hasProperty item "toChat")}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat"> <a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
<i class="fa-regular fa-fw fa-message"></i> <i class="fa-regular fa-fw fa-message"></i>

View file

@ -8,6 +8,6 @@
title='DAGGERHEART.GENERAL.Action.plural' title='DAGGERHEART.GENERAL.Action.plural'
collection=document.system.actions collection=document.system.actions
type='action' type='action'
canCreate=true canCreate=@root.editable
}} }}
</section> </section>

View file

@ -6,7 +6,7 @@
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.actives collection=effects.actives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
@ -16,7 +16,7 @@
disabledEffect=true disabledEffect=true
isGlassy=true isGlassy=true
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=@root.editable
hideResources=true hideResources=true
}} }}
</section> </section>

View file

@ -7,7 +7,7 @@
<legend>{{localize tabs.settings.label}}</legend> <legend>{{localize tabs.settings.label}}</legend>
<span>{{localize "DAGGERHEART.GENERAL.Tiers.singular"}}</span> <span>{{localize "DAGGERHEART.GENERAL.Tiers.singular"}}</span>
{{formInput systemFields.tier value=source.system.tier}} {{formInput systemFields.tier value=source.system.tier}}
<span>{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon"}}</span> <span>{{localize "DAGGERHEART.ITEMS.Weapon.secondaryWeapon.full"}}</span>
{{formInput systemFields.secondary value=source.system.secondary}} {{formInput systemFields.secondary value=source.system.secondary}}
<span>{{localize "DAGGERHEART.GENERAL.Trait.single"}}</span> <span>{{localize "DAGGERHEART.GENERAL.Trait.single"}}</span>
{{formInput systemFields.attack.fields.roll.fields.trait value=document.system.attack.roll.trait name="system.attack.roll.trait" label="DAGGERHEART.GENERAL.Trait.single" localize=true}} {{formInput systemFields.attack.fields.roll.fields.trait value=document.system.attack.roll.trait name="system.attack.roll.trait" label="DAGGERHEART.GENERAL.Trait.single" localize=true}}

View file

@ -1,12 +1,12 @@
import { compilePack } from '@foundryvtt/foundryvtt-cli'; import { compilePack } from '@foundryvtt/foundryvtt-cli';
import readline from 'node:readline/promises'; import readline from 'node:readline/promises';
import { promises as fs } from 'fs'; import { promises as fs } from 'fs';
import systemJSON from "../system.json" with { type: "json" }; import systemJSON from '../system.json' with { type: 'json' };
const MODULE_ID = process.cwd(); const MODULE_ID = process.cwd();
const answer = await (async () => { const answer = await (async () => {
if (process.argv.includes("--build")) return "overwrite"; if (process.argv.includes('--build')) return 'overwrite';
const rl = readline.createInterface({ const rl = readline.createInterface({
input: process.stdin, input: process.stdin,
@ -42,8 +42,8 @@ async function pullToLDB() {
function transformEntry(entry) { function transformEntry(entry) {
const stats = { const stats = {
coreVersion: systemJSON.compatibility.minimum, coreVersion: systemJSON.compatibility.minimum,
systemId: "daggerheart", systemId: 'daggerheart',
systemVersion: systemJSON.version, systemVersion: systemJSON.version
}; };
entry._stats = { ...stats }; entry._stats = { ...stats };

View file

@ -23,7 +23,7 @@ for (const pack of packs) {
await extractPack(`${MODULE_ID}/${pack}`, `${MODULE_ID}/src/${pack}`, { await extractPack(`${MODULE_ID}/${pack}`, `${MODULE_ID}/src/${pack}`, {
yaml, yaml,
transformName, transformName,
transformEntry, transformEntry
}); });
} }
/** /**
@ -45,12 +45,12 @@ function transformEntry(entry) {
delete entry._stats; delete entry._stats;
for (const effect of entry.effects ?? []) { for (const effect of entry.effects ?? []) {
effect._stats = prune(effect._stats) effect._stats = prune(effect._stats);
} }
for (const item of entry.items ?? []) { for (const item of entry.items ?? []) {
item._stats = prune(item._stats); item._stats = prune(item._stats);
for (const effect of item.effects ?? []) { for (const effect of item.effects ?? []) {
effect._stats = prune(effect._stats) effect._stats = prune(effect._stats);
} }
} }
} }