Merge branch 'main' of https://github.com/Foundryborne/daggerheart into chat-levelup-summary

This commit is contained in:
Nikhil Nagarajan 2025-11-17 08:44:23 -05:00
commit 0bdcb07690
40 changed files with 387 additions and 232 deletions

View file

@ -2376,7 +2376,6 @@
"maxDomains": { "label": "Max Class Domains", "hint": "Max domains you can set on a class" }
},
"currency": {
"enabled": "Enable Overrides",
"title": "Currency Overrides",
"currencyName": "Currency Name",
"coinName": "Coin Name",
@ -2580,6 +2579,7 @@
"evasionMin": "Evasion (Min)",
"evasionMax": "Evasion (Max)",
"subtype": "Subtype",
"missing": "<i>Missing</i>",
"folders": {
"characters": "Characters",
"adversaries": "Adversaries",
@ -2633,7 +2633,7 @@
"cardTooHighLevel": "The card is too high level!",
"duplicateCard": "You cannot select the same card more than once.",
"duplicateCharacter": "This actor is already registered in the party members list.",
"onlyCharactersInPartySheet": "You can drag only characters to a party sheet.",
"onlyCharactersInPartySheet": "You can only drag characters, companions and adverasries to the party sheet.",
"notPrimary": "The weapon is not a primary weapon!",
"notSecondary": "The weapon is not a secondary weapon!",
"itemTooHighTier": "The item must be from Tier1",
@ -2670,7 +2670,8 @@
"subclassAlreadyLinked": "{name} is already a subclass in the class {class}. Remove it from there if you want it to be a subclass to this class.",
"gmRequired": "This action requires an online GM",
"gmOnly": "This can only be accessed by the GM",
"noActorOwnership": "You do not have permissions for this character"
"noActorOwnership": "You do not have permissions for this character",
"documentIsMissing": "The {documentType} is missing from the world."
},
"Sidebar": {
"daggerheartMenu": {

View file

@ -178,10 +178,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
}
static async takeDowntime() {
const moves = Object.values(this.moveData).flatMap(category => {
return Object.values(category.moves)
.filter(x => x.selected)
.flatMap(move => [...Array(move.selected).keys()].map(_ => move));
const moves = Object.keys(this.moveData).flatMap(categoryKey => {
const category = this.moveData[categoryKey];
return Object.keys(category.moves)
.filter(x => category.moves[x].selected)
.flatMap(key => {
const move = category.moves[key];
return [...Array(move.selected).keys()].map(_ => ({
...move,
movePath: `${categoryKey}.moves.${key}`
}));
});
});
const cls = getDocumentClass('ChatMessage');

View file

@ -112,7 +112,7 @@ export default class DhActiveEffectConfig extends foundry.applications.sheets.Ac
CONFIG.DH.SETTINGS.gameSettings.appearance
).showGenericStatusEffects;
if (!useGeneric) {
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions).map(status => ({
partContext.statuses = Object.values(CONFIG.DH.GENERAL.conditions()).map(status => ({
value: status.id,
label: game.i18n.localize(status.name)
}));

View file

@ -210,26 +210,33 @@ export default class CharacterSheet extends DHBaseActorSheet {
context.resources.stress.emptyPips =
context.resources.stress.max < maxResource ? maxResource - context.resources.stress.max : 0;
context.inventory = {
currency: {
title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
}
};
context.inventory = { currencies: {} };
const { title, ...currencies } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Homebrew
).currency;
for (let key in currencies) {
context.inventory.currencies[key] = {
...currencies[key],
field: context.systemFields.gold.fields[key],
value: context.source.system.gold[key]
};
}
// context.inventory = {
// currency: {
// title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
// coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
// handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
// bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
// chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
// }
// };
context.beastformActive = this.document.effects.find(x => x.type === 'beastform');
const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency;
if (homebrewCurrency.enabled) {
context.inventory.currency = homebrewCurrency;
}
if (context.inventory.length === 0) {
context.inventory = Array(1).fill(Array(5).fill([]));
}
// if (context.inventory.length === 0) {
// context.inventory = Array(1).fill(Array(5).fill([]));
// }
return context;
}

View file

@ -7,6 +7,7 @@ import { socketEvent } from '../../../systemRegistration/socket.mjs';
import GroupRollDialog from '../../dialogs/group-roll-dialog.mjs';
import DhpActor from '../../../documents/actor.mjs';
import DHItem from '../../../documents/item.mjs';
import DhParty from '../../../data/actor/party.mjs';
export default class Party extends DHBaseActorSheet {
constructor(options) {
@ -19,7 +20,8 @@ export default class Party extends DHBaseActorSheet {
static DEFAULT_OPTIONS = {
classes: ['party'],
position: {
width: 550
width: 550,
height: 900,
},
window: {
resizable: true
@ -79,6 +81,9 @@ export default class Party extends DHBaseActorSheet {
}
};
static ALLOWED_ACTOR_TYPES = ['character', 'companion', 'adversary'];
static DICE_ROLL_ACTOR_TYPES = ['character'];
async _onRender(context, options) {
await super._onRender(context, options);
this._createFilterMenus();
@ -92,23 +97,17 @@ export default class Party extends DHBaseActorSheet {
async _prepareContext(_options) {
const context = await super._prepareContext(_options);
context.inventory = {
currency: {
title: game.i18n.localize('DAGGERHEART.CONFIG.Gold.title'),
coins: game.i18n.localize('DAGGERHEART.CONFIG.Gold.coins'),
handfuls: game.i18n.localize('DAGGERHEART.CONFIG.Gold.handfuls'),
bags: game.i18n.localize('DAGGERHEART.CONFIG.Gold.bags'),
chests: game.i18n.localize('DAGGERHEART.CONFIG.Gold.chests')
}
};
const homebrewCurrency = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).currency;
if (homebrewCurrency.enabled) {
context.inventory.currency = homebrewCurrency;
}
if (context.inventory.length === 0) {
context.inventory = Array(1).fill(Array(5).fill([]));
context.inventory = { currencies: {} };
const { title, ...currencies } = game.settings.get(
CONFIG.DH.id,
CONFIG.DH.SETTINGS.gameSettings.Homebrew
).currency;
for (let key in currencies) {
context.inventory.currencies[key] = {
...currencies[key],
field: context.systemFields.gold.fields[key],
value: context.source.system.gold[key]
};
}
return context;
@ -277,13 +276,17 @@ export default class Party extends DHBaseActorSheet {
}
static async #tagTeamRoll() {
new game.system.api.applications.dialogs.TagTeamDialog(this.document.system.partyMembers).render({
new game.system.api.applications.dialogs.TagTeamDialog(
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
).render({
force: true
});
}
static async #groupRoll(params) {
new GroupRollDialog(this.document.system.partyMembers).render({ force: true });
static async #groupRoll(_params) {
new GroupRollDialog(
this.document.system.partyMembers.filter(x => Party.DICE_ROLL_ACTOR_TYPES.includes(x.type))
).render({ force: true });
}
/**
@ -453,17 +456,17 @@ export default class Party extends DHBaseActorSheet {
event.stopPropagation();
const data = foundry.applications.ux.TextEditor.implementation.getDragEventData(event);
const item = await foundry.utils.fromUuid(data.uuid);
const document = await foundry.utils.fromUuid(data.uuid);
if (item instanceof DhpActor) {
if (document instanceof DhpActor && Party.ALLOWED_ACTOR_TYPES.includes(document.type)) {
const currentMembers = this.document.system.partyMembers.map(x => x.uuid);
if (currentMembers.includes(data.uuid)) {
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.duplicateCharacter'));
}
await this.document.update({ 'system.partyMembers': [...currentMembers, item.uuid] });
} else if (item instanceof DHItem) {
this.document.createEmbeddedDocuments('Item', [item.toObject()]);
await this.document.update({ 'system.partyMembers': [...currentMembers, document.uuid] });
} else if (document instanceof DHItem) {
this.document.createEmbeddedDocuments('Item', [document.toObject()]);
} else {
ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.onlyCharactersInPartySheet'));
}

View file

@ -132,12 +132,21 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
}
async actionUseButton(event, message) {
const { moveIndex, actionIndex } = event.currentTarget.dataset;
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
const parent = await foundry.utils.fromUuid(message.system.actor);
const actionType = message.system.moves[moveIndex].actions[actionIndex];
const cls = game.system.api.models.actions.actionsTypes[actionType.type];
const action = new cls(
{ ...actionType, _id: foundry.utils.randomID(), name: game.i18n.localize(actionType.name) },
{
...actionType,
_id: foundry.utils.randomID(),
name: game.i18n.localize(actionType.name),
originItem: {
type: CONFIG.DH.ITEM.originItemType.restMove,
itemPath: movePath,
actionIndex: actionIndex
}
},
{ parent: parent.system }
);
@ -192,9 +201,18 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
async groupRollButton(event, message) {
const path = event.currentTarget.dataset.path;
const isLeader = path === 'leader';
const { actor: actorData, trait } = foundry.utils.getProperty(message.system, path);
const actor = game.actors.get(actorData._id);
if (!actor) {
return ui.notifications.error(
game.i18n.format('DAGGERHEART.UI.Notifications.documentIsMissing', {
documentType: game.i18n.localize('TYPES.Actor.character')
})
);
}
if (!actor.testUserPermission(game.user, 'OWNER')) {
return ui.notifications.warn(game.i18n.localize('DAGGERHEART.UI.Notifications.noActorOwnership'));
}
@ -214,7 +232,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
hasRoll: true,
skips: {
createMessage: true,
resources: true
resources: !isLeader
}
};
const result = await actor.diceRoll({
@ -225,6 +243,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
})
});
if (!result) return;
await game.system.api.fields.ActionFields.CostField.execute.call({ actor }, result);
const newMessageData = foundry.utils.deepClone(message.system);
foundry.utils.setProperty(newMessageData, `${path}.result`, result.roll);
const renderData = { system: new game.system.api.models.chatMessages.config.groupRoll(newMessageData) };

View file

@ -294,7 +294,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
const property = foundry.utils.getProperty(item, field.key);
if (Array.isArray(property)) property.join(', ');
if (typeof field.format !== 'function') return property ?? '-';
return field.format(property);
return game.i18n.localize(field.format(property));
}
formatChoices(data) {
@ -430,7 +430,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
if (matchesMenu) this.#filteredItems.browser.input.add(item.id);
const { search } = this.#filteredItems.browser;
li.hidden = !(search.has(item.id) && matchesMenu);
li.hidden = !((this.#search.browser.query.length === 0 || search.has(item.id)) && matchesMenu);
}
}
@ -469,6 +469,7 @@ export class ItemBrowser extends HandlebarsApplicationMixin(ApplicationV2) {
static resetFilters() {
this.render({ force: true });
this.loadItems();
}
static getFolderConfig(folder, property = 'columns') {

View file

@ -363,7 +363,7 @@ export const typeConfig = {
{
key: 'system.linkedClass',
label: 'Class',
format: linkedClass => linkedClass.name
format: linkedClass => linkedClass?.name ?? 'DAGGERHEART.UI.ItemBrowser.missing'
},
{
key: 'system.spellcastingTrait',
@ -375,10 +375,12 @@ export const typeConfig = {
key: 'system.linkedClass.uuid',
label: 'Class',
choices: items => {
const list = items.map(item => ({
value: item.system.linkedClass.uuid,
label: item.system.linkedClass.name
}));
const list = items
.filter(item => item.system.linkedClass)
.map(item => ({
value: item.system.linkedClass.uuid,
label: item.system.linkedClass.name
}));
return list.reduce((a, c) => {
if (!a.find(i => i.value === c.value)) a.push(c);
return a;

View file

@ -1547,3 +1547,8 @@ export const beastformTypes = {
label: 'DAGGERHEART.CONFIG.BeastformType.hybrid'
}
};
export const originItemType = {
itemCollection: 'itemCollection',
restMove: 'restMove'
};

View file

@ -1,6 +1,7 @@
import DhpActor from '../../documents/actor.mjs';
import D20RollDialog from '../../applications/dialogs/d20RollDialog.mjs';
import { ActionMixin } from '../fields/actionField.mjs';
import { originItemField } from '../chat-message/actorRoll.mjs';
const fields = foundry.data.fields;
@ -25,6 +26,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
description: new fields.HTMLField(),
img: new fields.FilePathField({ initial: undefined, categories: ['IMAGE'], base64: false }),
chatDisplay: new fields.BooleanField({ initial: true, label: 'DAGGERHEART.ACTIONS.Config.displayInChat' }),
originItem: originItemField(),
actionType: new fields.StringField({
choices: CONFIG.DH.ITEM.actionTypes,
initial: 'action',
@ -215,6 +217,7 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
title: `${this.item instanceof CONFIG.Actor.documentClass ? '' : `${this.item.name}: `}${game.i18n.localize(this.name)}`,
source: {
item: this.item._id,
originItem: this.originItem,
action: this._id,
actor: this.actor.uuid
},

View file

@ -6,10 +6,12 @@ export default class BeastformEffect extends BaseEffect {
const fields = foundry.data.fields;
return {
characterTokenData: new fields.SchemaField({
usesDynamicToken: new fields.BooleanField({ initial: false }),
tokenImg: new fields.FilePathField({
categories: ['IMAGE'],
base64: false,
nullable: true
nullable: true,
wildcard: true
}),
tokenRingImg: new fields.FilePathField({
initial: 'icons/svg/mystery-man.svg',
@ -38,20 +40,38 @@ export default class BeastformEffect extends BaseEffect {
async _preDelete() {
if (this.parent.parent.type === 'character') {
const update = {
const baseUpdate = {
height: this.characterTokenData.tokenSize.height,
width: this.characterTokenData.tokenSize.width,
width: this.characterTokenData.tokenSize.width
};
const update = {
...baseUpdate,
texture: {
src: this.characterTokenData.tokenImg
},
ring: {
enabled: this.characterTokenData.usesDynamicToken,
subject: {
texture: this.characterTokenData.tokenRingImg
}
}
};
await updateActorTokens(this.parent.parent, update);
const updateToken = token => ({
...baseUpdate,
'texture': {
enabled: this.characterTokenData.usesDynamicToken,
src: token.flags.daggerheart.beastformTokenImg
},
'ring': {
subject: {
texture: token.flags.daggerheart.beastformSubjectTexture
}
},
'flags.daggerheart': { '-=beastformTokenImg': null, '-=beastformSubjectTexture': null }
});
await updateActorTokens(this.parent.parent, update, updateToken);
await this.parent.parent.deleteEmbeddedDocuments('Item', this.featureIds);
await this.parent.parent.deleteEmbeddedDocuments('ActiveEffect', this.effectIds);

View file

@ -675,6 +675,8 @@ export default class DhCharacter extends BaseDataActor {
}
_getTags() {
return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter((t) => !!t);
return [this.class.value?.name, this.class.subclass?.name, this.community?.name, this.ancestry?.name].filter(
t => !!t
);
}
}

View file

@ -41,7 +41,7 @@ export default class DhParty extends BaseDataActor {
// Clear this party from all members that aren't deleted
for (const member of this.partyMembers) {
member.parties?.delete(this.parent);
member?.parties?.delete(this.parent);
}
}
}

View file

@ -17,6 +17,16 @@ const targetsField = () =>
})
);
export const originItemField = () =>
new fields.SchemaField({
type: new fields.StringField({
choices: CONFIG.DH.ITEM.originItemType,
initial: CONFIG.DH.ITEM.originItemType.itemCollection
}),
itemPath: new fields.StringField(),
actionIndex: new fields.StringField()
});
export default class DHActorRoll extends foundry.abstract.TypeDataModel {
static defineSchema() {
return {
@ -35,6 +45,7 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
source: new fields.SchemaField({
actor: new fields.StringField(),
item: new fields.StringField(),
originItem: originItemField(),
action: new fields.StringField()
}),
damage: new fields.ObjectField(),
@ -51,14 +62,23 @@ export default class DHActorRoll extends foundry.abstract.TypeDataModel {
get actionItem() {
const actionActor = this.actionActor;
if (!actionActor || !this.source.item) return null;
return actionActor.items.get(this.source.item);
switch (this.source.originItem.type) {
case CONFIG.DH.ITEM.originItemType.restMove:
const restMoves = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew).restMoves;
return Array.from(foundry.utils.getProperty(restMoves, `${this.source.originItem.itemPath}`).actions)[
this.source.originItem.actionIndex
];
default:
const item = actionActor.items.get(this.source.item);
return item ? item.system.actionsList?.find(a => a.id === this.source.action) : null;
}
}
get action() {
const actionActor = this.actionActor,
actionItem = this.actionItem;
const { actionActor, actionItem: itemAction } = this;
if (!this.source.action) return null;
if (actionItem) return actionItem.system.actionsList?.find(a => a.id === this.source.action);
if (itemAction) return itemAction;
else if (actionActor?.system.attack?._id === this.source.action) return actionActor.system.attack;
return null;
}

View file

@ -98,7 +98,9 @@ export default class DamageField extends fields.SchemaField {
});
}
const token = game.scenes.find(x => x.active).tokens.find(x => x.id === target.id);
const token = target.id
? game.scenes.find(x => x.active).tokens.find(x => x.id === target.id)
: actor.prototypeToken;
if (config.hasHealing)
damagePromises.push(
actor.takeHealing(config.damage).then(updates => targetDamage.push({ token, updates }))

View file

@ -24,7 +24,7 @@ export default class TargetField extends fields.SchemaField {
if (!this.target?.type) return (config.targets = []);
config.hasTarget = true;
let targets;
// If the Action is configured as self-targeted, set targets as the owner.
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens
if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id)
targets = [this.actor.token ?? this.actor.prototypeToken];
else {
@ -72,17 +72,17 @@ export default class TargetField extends fields.SchemaField {
/**
* Format actor to useful datas for Action roll workflow.
* @param {*} actor Actor object to format.
* @param {*} token Token object to format.
* @returns {*} Formatted Actor.
*/
static formatTarget(actor) {
static formatTarget(token) {
return {
id: actor.id,
actorId: actor.actor.uuid,
name: actor.actor.name,
img: actor.actor.img,
difficulty: actor.actor.system.difficulty,
evasion: actor.actor.system.evasion,
id: token.id,
actorId: token.actor.uuid,
name: token.actor.name,
img: token.actor.img,
difficulty: token.actor.system.difficulty,
evasion: token.actor.system.evasion,
saved: {
value: null,
success: null

View file

@ -160,7 +160,6 @@ export default class DHBeastform extends BaseDataItem {
this.parent.effects.filter(x => x.type !== 'beastform').map(x => x.toObject())
);
const tokenImages = await this.parent.parent.getTokenImages();
const beastformEffect = this.parent.effects.find(x => x.type === 'beastform');
await beastformEffect.updateSource({
changes: [
@ -175,7 +174,8 @@ export default class DHBeastform extends BaseDataItem {
],
system: {
characterTokenData: {
tokenImg: tokenImages[0],
usesDynamicToken: this.parent.parent.prototypeToken.ring.enabled,
tokenImg: this.parent.parent.prototypeToken.texture.src,
tokenRingImg: this.parent.parent.prototypeToken.ring.subject.texture,
tokenSize: {
height: this.parent.parent.prototypeToken.height,
@ -190,7 +190,7 @@ export default class DHBeastform extends BaseDataItem {
await this.parent.parent.createEmbeddedDocuments('ActiveEffect', [beastformEffect.toObject()]);
await updateActorTokens(this.parent.parent, {
const prototypeTokenUpdate = {
height: this.tokenSize.height,
width: this.tokenSize.width,
texture: {
@ -201,22 +201,20 @@ export default class DHBeastform extends BaseDataItem {
texture: this.tokenRingImg
}
}
};
const tokenUpdate = token => ({
...prototypeTokenUpdate,
flags: {
daggerheart: {
beastformTokenImg: token.texture.src,
beastformSubjectTexture: token.ring.subject.texture
}
}
});
await updateActorTokens(this.parent.parent, prototypeTokenUpdate, tokenUpdate);
return false;
}
_onCreate(_data, _options, userId) {
if (userId !== game.user.id) return;
if (!this.parent.effects.find(x => x.type === 'beastform')) {
this.parent.createEmbeddedDocuments('ActiveEffect', [
{
type: 'beastform',
name: game.i18n.localize('DAGGERHEART.ITEMS.Beastform.beastformEffect'),
img: 'icons/creatures/abilities/paw-print-pair-purple.webp'
}
]);
}
}
}

View file

@ -1,6 +1,16 @@
import { defaultRestOptions } from '../../config/generalConfig.mjs';
import { ActionsField } from '../fields/actionField.mjs';
const currencyField = (initial, label) =>
new foundry.data.fields.SchemaField({
enabled: new foundry.data.fields.BooleanField({ required: true, initial: true }),
label: new foundry.data.fields.StringField({
required: true,
initial,
label
})
});
export default class DhHomebrew extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
@ -30,36 +40,15 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
initial: () => [2, 1, 1, 0, 0, -1]
}),
currency: new fields.SchemaField({
enabled: new fields.BooleanField({
required: true,
initial: false,
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.enabled'
}),
title: new fields.StringField({
required: true,
initial: 'Gold',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.currencyName'
}),
coins: new fields.StringField({
required: true,
initial: 'Coins',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'
}),
handfuls: new fields.StringField({
required: true,
initial: 'Handfuls',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'
}),
bags: new fields.StringField({
required: true,
initial: 'Bags',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'
}),
chests: new fields.StringField({
required: true,
initial: 'Chests',
label: 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName'
})
coins: currencyField('Coins', 'DAGGERHEART.SETTINGS.Homebrew.currency.coinName'),
handfuls: currencyField('Handfuls', 'DAGGERHEART.SETTINGS.Homebrew.currency.handfulName'),
bags: currencyField('Bags', 'DAGGERHEART.SETTINGS.Homebrew.currency.bagName'),
chests: currencyField('Chests', 'DAGGERHEART.SETTINGS.Homebrew.currency.chestName')
}),
restMoves: new fields.SchemaField({
longRest: new fields.SchemaField({
@ -146,4 +135,26 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
})
};
}
/** @inheritDoc */
_initializeSource(source, options = {}) {
source = super._initializeSource(source, options);
source.currency.coins = {
enabled: source.currency.coins.enabled ?? true,
label: source.currency.coins.label || source.currency.coins
};
source.currency.handfuls = {
enabled: source.currency.handfuls.enabled ?? true,
label: source.currency.handfuls.label || source.currency.handfuls
};
source.currency.bags = {
enabled: source.currency.bags.enabled ?? true,
label: source.currency.bags.label || source.currency.bags
};
source.currency.chests = {
enabled: source.currency.chests.enabled ?? true,
label: source.currency.chests.label || source.currency.chests
};
return source;
}
}

View file

@ -145,9 +145,11 @@ export default class DhpChatMessage extends foundry.documents.ChatMessage {
});
html.querySelectorAll('.token-target-container').forEach(element => {
element.addEventListener('pointerover', this.hoverTarget);
element.addEventListener('pointerout', this.unhoverTarget);
element.addEventListener('click', this.clickTarget);
if (element.dataset.token) {
element.addEventListener('pointerover', this.hoverTarget);
element.addEventListener('pointerout', this.unhoverTarget);
element.addEventListener('click', this.clickTarget);
}
});
}

View file

@ -251,7 +251,13 @@ export const adjustRange = (rangeVal, decrease) => {
return range[rangeKeys[newIndex]];
};
export const updateActorTokens = async (actor, update) => {
/**
*
* @param {DhActor} actor - The actor for which all tokens will run a data update.
* @param {string} update - The data update to be applied to all tokens.
* @param {func} updateToken - Optional, specific data update for the non-prototype tokens as a function using the token data. Useful to handle wildcard images where each token has a different image but the prototype has a wildcard path.
*/
export const updateActorTokens = async (actor, update, updateToken) => {
await actor.prototypeToken.update({ ...update });
/* Update the tokens in all scenes belonging to Actor */
@ -259,7 +265,7 @@ export const updateActorTokens = async (actor, update) => {
const tokenActor = token.baseActor ?? token.actor;
if (token.id && tokenActor?.id === actor.id) {
await token.update({
...update,
...(updateToken ? updateToken(token) : update),
_id: token.id
});
}

View file

@ -4,6 +4,7 @@ export async function runMigrations() {
let lastMigrationVersion = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion);
if (!lastMigrationVersion) lastMigrationVersion = game.system.version;
//#region old migrations
if (foundry.utils.isNewerVersion('1.1.0', lastMigrationVersion)) {
const lockedPacks = [];
const compendiumActors = [];
@ -190,6 +191,7 @@ export async function runMigrations() {
lastMigrationVersion = '1.2.0';
}
//#endregion
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.LastMigrationVersion, lastMigrationVersion);
}

View file

@ -57,11 +57,12 @@
}
.currency-section {
display: flex;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 10px;
padding: 10px 10px 0;
input {
.input {
color: light-dark(@dark, @beige);
}
}

View file

@ -61,11 +61,12 @@
}
.currency-section {
display: flex;
display: grid;
grid-template-columns: 1fr 1fr 1fr 1fr;
gap: 10px;
padding: 10px 10px 0;
input {
.input {
color: light-dark(@dark, @beige);
}
}

View file

@ -32,7 +32,7 @@ body.game:is(.performance-low, .noblur) {
background: light-dark(@dark-blue-40, @dark-golden-40);
border-radius: 6px;
border: 1px solid light-dark(@dark-blue, @golden);
max-width: 230px;
width: 230px;
height: -webkit-fill-available;
.actor-name {

View file

@ -14,11 +14,11 @@
.application.sheet.daggerheart.actor.dh-style.party {
.tab {
height: -webkit-fill-available;
max-height: 514px;
flex: 1;
overflow-y: auto;
scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
scrollbar-gutter: stable;
&.active {
overflow: auto;

View file

@ -28,12 +28,15 @@
display: flex;
flex-direction: column;
gap: 2px;
cursor: pointer;
transition: all 0.3s ease;
border-radius: 6px;
&:hover {
background: @golden-10;
&.clickable {
cursor: pointer;
&:hover {
background: @golden-10;
}
}
header {

View file

@ -90,11 +90,14 @@
background: transparent;
transition: all 0.3s ease;
padding: 5px;
cursor: pointer;
transition: all 0.3s ease;
&:hover {
background: @golden-10;
&.clickable {
cursor: pointer;
&:hover {
background: @golden-10;
}
}
img {

View file

@ -21,11 +21,6 @@
padding: 8px;
--button-size: 0;
&.used {
opacity: 0.5;
background: transparent;
}
&.inactive {
background: var(--color-warm-2);
color: var(--color-light-1);
@ -35,6 +30,11 @@
filter: none;
}
}
&.used {
opacity: 0.5;
background: transparent;
}
}
}

View file

@ -27,6 +27,21 @@
}
}
.toggleable-row {
width: 100%;
display: flex;
align-items: center;
gap: 4px;
&.spaced {
padding-right: 28.5px;
}
.form-group {
flex: 1;
}
}
.setting-group-field {
white-space: nowrap;
display: flex;

View file

@ -2,10 +2,10 @@
"id": "daggerheart",
"title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system",
"version": "1.2.1",
"version": "1.2.2",
"compatibility": {
"minimum": "13",
"verified": "13.350",
"verified": "13.351",
"maximum": "13"
},
"authors": [

View file

@ -29,13 +29,25 @@
<legend>
{{localize "DAGGERHEART.SETTINGS.Homebrew.currency.title"}}
</legend>
{{formGroup settingFields.schema.fields.currency.fields.enabled value=settingFields._source.currency.enabled localize=true}}
{{formGroup settingFields.schema.fields.currency.fields.title value=settingFields._source.currency.title localize=true}}
{{formGroup settingFields.schema.fields.currency.fields.coins value=settingFields._source.currency.coins localize=true}}
{{formGroup settingFields.schema.fields.currency.fields.handfuls value=settingFields._source.currency.handfuls localize=true}}
{{formGroup settingFields.schema.fields.currency.fields.bags value=settingFields._source.currency.bags localize=true}}
{{formGroup settingFields.schema.fields.currency.fields.chests value=settingFields._source.currency.chests localize=true}}
<div class="toggleable-row spaced">
{{formGroup settingFields.schema.fields.currency.fields.title value=settingFields._source.currency.title localize=true}}
</div>
<div class="toggleable-row">
{{formGroup settingFields.schema.fields.currency.fields.coins.fields.label value=settingFields._source.currency.coins.label localize=true}}
<input type="checkbox" {{checked settingFields._source.currency.coins.enabled}} disabled />
</div>
<div class="toggleable-row">
{{formGroup settingFields.schema.fields.currency.fields.handfuls.fields.label value=settingFields._source.currency.handfuls.label localize=true}}
<input type="checkbox" name="currency.handfuls.enabled" {{checked settingFields._source.currency.handfuls.enabled}} />
</div>
<div class="toggleable-row">
{{formGroup settingFields.schema.fields.currency.fields.bags.fields.label value=settingFields._source.currency.bags.label localize=true}}
<input type="checkbox" name="currency.bags.enabled" {{checked settingFields._source.currency.bags.enabled}} />
</div>
<div class="toggleable-row">
{{formGroup settingFields.schema.fields.currency.fields.chests.fields.label value=settingFields._source.currency.chests.label localize=true}}
<input type="checkbox" name="currency.chests.enabled" {{checked settingFields._source.currency.chests.enabled}} />
</div>
</fieldset>
</div>
</section>

View file

@ -13,27 +13,14 @@
</div>
<div class="currency-section">
<div class="input">
<span>{{localize this.inventory.currency.coins}}</span>
{{formInput systemFields.gold.fields.coins value=source.system.gold.coins enriched=source.system.gold.coins
localize=true toggled=true}}
</div>
<div class="input">
<span>{{localize this.inventory.currency.handfuls}}</span>
{{formInput systemFields.gold.fields.handfuls value=source.system.gold.handfuls
enriched=source.system.gold.handfuls localize=true toggled=true}}
</div>
<div class="input">
<span>{{localize this.inventory.currency.bags}}</span>
{{formInput systemFields.gold.fields.bags value=source.system.gold.bags enriched=source.system.gold.bags
localize=true toggled=true}}
</div>
<div class="input">
<span>{{localize this.inventory.currency.chests}}</span>
{{formInput systemFields.gold.fields.chests value=source.system.gold.chests
enriched=source.system.gold.chests localize=true toggled=true}}
</div>
{{#each this.inventory.currencies as | currency |}}
{{#if currency.enabled}}
<div class="input">
<span>{{localize currency.label}}</span>
{{formInput currency.field value=currency.value enriched=currency.value toggled=true}}
</div>
{{/if}}
{{/each}}
</div>
<div class="items-section">

View file

@ -16,7 +16,15 @@
</div>
<div class="currency-section">
<div class="input">
{{#each this.inventory.currencies as | currency |}}
{{#if currency.enabled}}
<div class="input">
<span>{{localize currency.label}}</span>
{{formInput currency.field value=currency.value enriched=currency.value toggled=true}}
</div>
{{/if}}
{{/each}}
{{!-- <div class="input">
<span>{{localize this.inventory.currency.coins}}</span>
{{formInput systemFields.gold.fields.coins value=source.system.gold.coins enriched=source.system.gold.coins
localize=true toggled=true}}
@ -35,7 +43,7 @@
<span>{{localize this.inventory.currency.chests}}</span>
{{formInput systemFields.gold.fields.chests value=source.system.gold.chests
enriched=source.system.gold.chests localize=true toggled=true}}
</div>
</div> --}}
</div>

View file

@ -28,6 +28,7 @@
item=actor
type='character'
isActor=true
hideContextMenu=true
}}
{{/each}}
</ul>

View file

@ -22,19 +22,21 @@
<h2 class="actor-name">{{actor.name}}</h2>
<img class="actor-img" src="{{actor.img}}">
<div class="resources">
<div class="slot-section">
<div class="slot-bar">
{{#times actor.system.resources.hitPoints.max}}
<span class='slot {{#if (gte actor.system.resources.hitPoints.value (add this 1))}}filled{{/if}}'
data-action='toggleHitPoints' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
{{#unless (eq actor.type 'companion') }}
<div class="slot-section">
<div class="slot-bar">
{{#times actor.system.resources.hitPoints.max}}
<span class='slot {{#if (gte actor.system.resources.hitPoints.value (add this 1))}}filled{{/if}}'
data-action='toggleHitPoints' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
<div class="slot-label">
<span class="label">{{localize "DAGGERHEART.GENERAL.HitPoints.short"}}</span>
<span class="value">{{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}}</span>
</div>
</div>
<div class="slot-label">
<span class="label">{{localize "DAGGERHEART.GENERAL.HitPoints.short"}}</span>
<span class="value">{{actor.system.resources.hitPoints.value}} / {{actor.system.resources.hitPoints.max}}</span>
</div>
</div>
{{/unless}}
<div class="slot-section">
<div class="slot-bar">
@ -71,26 +73,30 @@
</div>
{{/if}}
{{#unless (or (eq actor.type 'companion') (eq actor.type 'adversary')) }}
<div class="hope-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
{{#times actor.system.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
{{#if (gte actor.system.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>
{{/if}}
</span>
{{/times}}
</div>
{{/unless}}
<div class="hope-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
{{#times actor.system.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-actor-id="{{actor.uuid}}" data-value="{{add this 1}}">
{{#if (gte actor.system.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>
{{/if}}
</span>
{{/times}}
</div>
<div class="threshold-section">
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}</h4>
<h4 class="threshold-value">{{actor.system.damageThresholds.major}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.major"}}</h4>
<h4 class="threshold-value">{{actor.system.damageThresholds.severe}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}</h4>
</div>
{{#unless (eq actor.type 'companion')}}
<div class="threshold-section">
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.minor"}}</h4>
<h4 class="threshold-value">{{actor.system.damageThresholds.major}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.major"}}</h4>
<h4 class="threshold-value">{{actor.system.damageThresholds.severe}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.GENERAL.DamageThresholds.severe"}}</h4>
</div>
{{/unless}}
</div>
</li>
{{/each}}

View file

@ -10,6 +10,7 @@ Parameters:
- isGlassy {boolean} : If true, applies the 'glassy' class to the fieldset.
- cardView {boolean} : If true and type is 'domainCard', renders using domain card layout.
- isActor {boolean} : Passed through to inventory-item partials.
- isItem {boolean} : Passed through to inventory-item partials
- actorType {boolean} : The actor type of the parent actor
- canCreate {boolean} : If true, show createDoc anchor on legend
- inVault {boolean} : If true, the domainCard is created with inVault=true

View file

@ -4,6 +4,7 @@
Parameters:
- type {string} : The type of items in the list
- isActor {boolean} : Passed through to inventory-item partials.
- isItem {boolean} : Passed through to inventory-item partials
- actorType {boolean} : The actor type of the parent actor
- categoryAdversary {string} : Category adversary id.
- noExtensible {boolean} : If true, the inventory-item-content would be collapsable/extendible else it always be showed
@ -18,7 +19,7 @@ Parameters:
<li class="inventory-item" data-item-id="{{item.id}}" {{#if (or (eq type 'action' ) (eq type 'attack' ))}}
data-action-id="{{item.id}}" {{/if}} data-item-uuid="{{item.uuid}}" data-type="{{type}}" data-no-compendium-edit="{{noCompendiumEdit}}" draggable="true">
<div class="inventory-item-header {{#if (eq actorType 'party')}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
<div class="inventory-item-header {{#if hideContextMenu}}padded{{/if}}" {{#unless noExtensible}}data-action="toggleExtended" {{/unless}}>
{{!-- Image --}}
<div class="img-portait" data-action='{{ifThen (or (hasProperty item "use") (eq type "attack")) "useItem" (ifThen
(hasProperty item "toChat" ) "toChat" "editDoc" ) }}' {{#unless hideTooltip}} {{#if (eq type 'attack' )}}
@ -67,7 +68,7 @@ Parameters:
{{!-- Controls --}}
{{#unless hideControls}}
<div class="controls">
{{#if isActor}}
{{#if isActor}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.openActorWorld">
<i class="fa-solid fa-globe"></i>
</a>
@ -81,7 +82,7 @@ Parameters:
<i class='fas fa-trash'></i>
</a>
{{/if}}
{{else}}
{{else}}
{{#unless (eq actorType 'party')}}
{{#if (eq type 'weapon')}}
<a class="{{#unless item.system.equipped}}unequipped{{/unless}}" data-action="toggleEquipItem"
@ -94,27 +95,30 @@ Parameters:
<i class="fa-solid fa-shield"></i>
</a>
{{/if}}
{{#if (eq type 'domainCard')}}
<a data-action="toggleVault"
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>
</a>
{{else if (eq type 'effect')}}
<a data-action="toggleEffect"
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>
</a>
{{/if}}
{{#if (hasProperty item "toChat")}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
<i class="fa-regular fa-message"></i>
</a>
{{/if}}
{{else}}
<a data-action="editDoc" data-tooltip="DAGGERHEART.UI.Tooltip.openActorWorld">
<i class="fa-solid fa-globe"></i>
</a>
<a data-action="deleteItem" data-tooltip="DAGGERHEART.UI.Tooltip.deleteItem">
<i class="fa-solid fa-trash"></i>
</a>
{{/unless}}
{{#if (eq type 'domainCard')}}
<a data-action="toggleVault"
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>
</a>
{{else if (eq type 'effect')}}
<a data-action="toggleEffect"
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>
</a>
{{/if}}
{{#if (and (hasProperty item "toChat") (not (eq actorType 'party')))}}
<a data-action="toChat" data-tooltip="DAGGERHEART.UI.Tooltip.sendToChat">
<i class="fa-regular fa-message"></i>
</a>
{{/if}}
{{#unless hideContextMenu}}
<a data-action="triggerContextMenu" data-tooltip="DAGGERHEART.UI.Tooltip.moreOptions">
<i class="fa-solid fa-ellipsis-vertical"></i>
@ -122,7 +126,7 @@ Parameters:
{{/unless}}
{{/if}}
</div>
{{/unless}}
{{/unless}}
</div>
<div class="inventory-item-content{{#unless noExtensible}} extensible{{/unless}}">
{{!-- Description --}}

View file

@ -1,6 +1,6 @@
<ul class="daggerheart chat damage-summary">
{{#each targets}}
<li class="token-target-container" data-token="{{this.token.id}}">
<li class="token-target-container {{#if this.token.id}}clickable{{/if}}" data-token="{{this.token.id}}">
<header>
<img src="{{this.token.texture.src}}" />
<h2 class="actor-name">{{this.token.name}}</h2>

View file

@ -15,7 +15,7 @@
</div>
</details>
{{#each move.actions as | action index |}}
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}">
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}" data-move-path="{{../movePath}}" >
<span>{{localize action.name}}</span>
</button>
{{/each}}

View file

@ -23,7 +23,7 @@
</div>
<div class="targets-container">
{{#each targets}}
<div class="token-target-container" data-token="{{this.id}}">
<div class="token-target-container {{#if this.id}}clickable{{/if}}" data-token="{{this.id}}">
<img src="{{this.texture.src}}" />
<h2 class="title">{{this.name}}</h2>
</div>