Compare commits

..

10 commits

Author SHA1 Message Date
WBHarry
4558fbdcf6 Raised version
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-05-01 23:09:45 +02:00
Carlos Fernandez
c7159eff11
Fix retrieving parent documents when the model is null (#1853) 2026-05-01 23:00:03 +02:00
WBHarry
d0c2c783f1
Improved armor source names (#1851) 2026-05-01 16:53:20 -04:00
WBHarry
905d1f7e88 Corrected a typo in Greater Earth Elemental and Huge Green Ooze 2026-05-01 20:58:21 +02:00
Carlos Fernandez
b22ce9697d
Fix detection of negative modifiers (#1847) 2026-05-01 20:54:18 +02:00
WBHarry
404640a0a3 Fixed SRD DireBat experience value
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-05-01 17:45:50 +02:00
WBHarry
20056cd950 Corrected contributing link in readme
Some checks failed
Project CI / build (24.x) (push) Has been cancelled
2026-04-29 22:04:08 +02:00
Carlos Fernandez
118c52a996
[Fix] console noise when starting a tag team dialog or group roll (#1842)
Some checks failed
Project CI / build (24.x) (push) Has been cancelled
2026-04-28 01:47:11 -04:00
WBHarry
ca32aa5d35
Fixed so that the delete option is available in the compendium (#1843) 2026-04-28 01:46:46 -04:00
WBHarry
1cece731ee Corrected Glowing Rings damage
Some checks are pending
Project CI / build (24.x) (push) Waiting to run
2026-04-27 16:06:33 +02:00
16 changed files with 105 additions and 112 deletions

View file

@ -64,7 +64,7 @@ You can find the documentation here: https://github.com/Foundryborne/daggerheart
## Contributing ## Contributing
Looking to contribute to the project? Look no further, check out our [contributing guide](contributing.md), and keep the [Code of Conduct](coc.md) in mind when working on things. Looking to contribute to the project? Look no further, check out our [contributing guide](CONTRIBUTING.md), and keep the [Code of Conduct](coc.md) in mind when working on things.
## Disclaimer: ## Disclaimer:

View file

@ -342,7 +342,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.tagTeamStart, async data => {
const party = game.actors.get(data.partyId); const party = game.actors.get(data.partyId);
if (!party) return; if (!party) return;
const dialog = new game.system.api.applications.dialogs.TagTeamDialog(party); const TagTeamDialog = game.system.api.applications.dialogs.TagTeamDialog;
const dialog = foundry.applications.instances.get(`TagTeamDialog-${party.id}`) ?? new TagTeamDialog(party);
dialog.tabGroups.application = 'tagTeamRoll'; dialog.tabGroups.application = 'tagTeamRoll';
await dialog.render({ force: true }); await dialog.render({ force: true });
} }
@ -353,7 +354,8 @@ Hooks.on(CONFIG.DH.HOOKS.hooksConfig.groupRollStart, async data => {
const party = game.actors.get(data.partyId); const party = game.actors.get(data.partyId);
if (!party) return; if (!party) return;
const dialog = new game.system.api.applications.dialogs.GroupRollDialog(party); const GroupRollDialog = game.system.api.applications.dialogs.GroupRollDialog;
const dialog = foundry.applications.instances.get(`GroupRollDialog-${party.id}`) ?? new GroupRollDialog(party);
dialog.tabGroups.application = 'groupRoll'; dialog.tabGroups.application = 'groupRoll';
await dialog.render({ force: true }); await dialog.render({ force: true });
} }

View file

@ -22,9 +22,10 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
); );
const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled); const orderedArmorSources = getArmorSources(actor).filter(s => !s.disabled);
const armor = orderedArmorSources.reduce((acc, { document }) => { const armor = orderedArmorSources.reduce((acc, { name, document }) => {
const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData; const { current, max } = document.type === 'armor' ? document.system.armor : document.system.armorData;
acc.push({ acc.push({
name,
effect: document, effect: document,
marks: [...Array(max).keys()].reduce((acc, _, index) => { marks: [...Array(max).keys()].reduce((acc, _, index) => {
const spent = index < current; const spent = index < current;
@ -152,14 +153,8 @@ export default class DamageReductionDialog extends HandlebarsApplicationMixin(Ap
const armorSources = []; const armorSources = [];
for (const source of this.marks.armor) { for (const source of this.marks.armor) {
const parent = source.effect.origin
? await foundry.utils.fromUuid(source.effect.origin)
: source.effect.parent;
const useEffectName = parent.type === 'armor' || parent instanceof Actor;
const label = useEffectName ? source.effect.name : parent.name;
armorSources.push({ armorSources.push({
label: label, label: source.name,
uuid: source.effect.uuid, uuid: source.effect.uuid,
marks: source.marks marks: source.marks
}); });

View file

@ -6,7 +6,7 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) { export default class GroupRollDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(party) { constructor(party) {
super(); super({ id: `GroupRollDialog-${party.id}` });
this.party = party; this.party = party;
this.partyMembers = party.system.partyMembers this.partyMembers = party.system.partyMembers
@ -35,7 +35,6 @@ export default class GroupRollDialog extends HandlebarsApplicationMixin(Applicat
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
id: 'GroupRollDialog',
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'], classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'group-roll-dialog'],
position: { width: 390, height: 'auto' }, position: { width: 390, height: 'auto' },
window: { window: {

View file

@ -7,7 +7,7 @@ const { HandlebarsApplicationMixin, ApplicationV2 } = foundry.applications.api;
export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) { export default class TagTeamDialog extends HandlebarsApplicationMixin(ApplicationV2) {
constructor(party) { constructor(party) {
super(); super({ id: `TagTeamDialog-${party.id}` });
this.party = party; this.party = party;
this.partyMembers = party.system.partyMembers this.partyMembers = party.system.partyMembers
@ -36,7 +36,6 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
tag: 'form', tag: 'form',
id: 'TagTeamDialog',
classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'], classes: ['daggerheart', 'views', 'dh-style', 'dialog', 'tag-team-dialog'],
position: { width: 550, height: 'auto' }, position: { width: 550, height: 'auto' },
actions: { actions: {
@ -60,13 +59,17 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
id: 'initialization', id: 'initialization',
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs' template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/initialization.hbs'
}, },
tagTeamRoll: {
id: 'tagTeamRoll',
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs'
},
rollSelection: { rollSelection: {
id: 'rollSelection', id: 'rollSelection',
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/rollSelection.hbs' template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/rollSelection.hbs'
}, },
tagTeamRoll: { result: {
id: 'tagTeamRoll', id: 'result',
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamRoll.hbs' template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/result.hbs'
} }
}; };
@ -97,36 +100,15 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
} }
_configureRenderParts(options) { _configureRenderParts(options) {
const { initialization, rollSelection, tagTeamRoll } = super._configureRenderParts(options); const parts = super._configureRenderParts(options);
const augmentedParts = { initialization };
for (const memberKey of Object.keys(this.party.system.tagTeam.members)) { for (const memberKey of Object.keys(this.party.system.tagTeam.members)) {
augmentedParts[memberKey] = { parts[memberKey] = {
id: memberKey, id: memberKey,
template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamMember.hbs' template: 'systems/daggerheart/templates/dialogs/tagTeamDialog/tagTeamMember.hbs'
}; };
} }
augmentedParts.rollSelection = rollSelection;
augmentedParts.tagTeamRoll = tagTeamRoll;
return augmentedParts; return parts;
}
/**@inheritdoc */
async _onRender(context, options) {
await super._onRender(context, options);
// if (this.element.querySelector('.roll-selection')) {
// for (const element of this.element.querySelectorAll('.team-member-container')) {
// element.classList.add('select-padding');
// }
// }
if (this.element.querySelector('.team-container')) return;
const initializationPart = this.element.querySelector('.initialization-container');
initializationPart.insertAdjacentHTML('afterend', '<div class="team-container"></div>');
const teamContainer = this.element.querySelector('.team-container');
for (const memberContainer of this.element.querySelectorAll('.team-member-container'))
teamContainer.appendChild(memberContainer);
} }
async _prepareContext(_options) { async _prepareContext(_options) {
@ -167,6 +149,9 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
partContext.initiatorDisabled = !selectedMembers.length; partContext.initiatorDisabled = !selectedMembers.length;
partContext.openForAllPlayers = this.openForAllPlayers; partContext.openForAllPlayers = this.openForAllPlayers;
break;
case 'tagTeamRoll':
partContext.memberKeys = Object.keys(this.party.system.tagTeam.members);
break; break;
case 'rollSelection': case 'rollSelection':
partContext.members = Object.keys(this.party.system.tagTeam.members).reduce((acc, key) => { partContext.members = Object.keys(this.party.system.tagTeam.members).reduce((acc, key) => {
@ -175,7 +160,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
return acc; return acc;
}, {}); }, {});
break; break;
case 'tagTeamRoll': case 'result':
const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected); const selectedRoll = Object.values(this.party.system.tagTeam.members).find(member => member.selected);
const critSelected = !selectedRoll const critSelected = !selectedRoll
? undefined ? undefined
@ -243,7 +228,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
} }
getUpdatingParts(target) { getUpdatingParts(target) {
const { initialization, rollSelection, tagTeamRoll } = this.constructor.PARTS; const { initialization, rollSelection, result } = this.constructor.PARTS;
const isInitialization = this.tabGroups.application === initialization.id; const isInitialization = this.tabGroups.application === initialization.id;
const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey; const updatingMember = target.closest('.team-member-container')?.dataset?.memberKey;
@ -251,7 +236,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
...(isInitialization ? [initialization.id] : []), ...(isInitialization ? [initialization.id] : []),
...(updatingMember ? [updatingMember] : []), ...(updatingMember ? [updatingMember] : []),
...(!isInitialization ? [rollSelection.id] : []), ...(!isInitialization ? [rollSelection.id] : []),
...(!isInitialization ? [tagTeamRoll.id] : []) ...(!isInitialization ? [result.id] : [])
]; ];
} }

View file

@ -531,7 +531,7 @@ export default function DHApplicationMixin(Base) {
visible: element => { visible: element => {
const target = element.closest('[data-item-uuid]'); const target = element.closest('[data-item-uuid]');
const doc = getDocFromElementSync(target); const doc = getDocFromElementSync(target);
return doc?.isOwner && target.dataset.itemType !== 'beastform'; return doc?.isOwner !== false && target.dataset.itemType !== 'beastform';
}, },
callback: async (target, event) => { callback: async (target, event) => {
const doc = await getDocFromElement(target); const doc = await getDocFromElement(target);

View file

@ -171,6 +171,7 @@ export default class DhActiveEffect extends foundry.documents.ActiveEffect {
/** Recursively finds the first parent document of the given object */ /** Recursively finds the first parent document of the given object */
static #resolveParentDocument(model, documentClass) { static #resolveParentDocument(model, documentClass) {
if (!model) return null;
return model instanceof documentClass return model instanceof documentClass
? model ? model
: model.parent : model.parent

View file

@ -757,9 +757,12 @@ export function getArmorSources(actor) {
// Get the origin item. Since the actor is already loaded, it should already be cached // Get the origin item. Since the actor is already loaded, it should already be cached
// Consider the relative function versions if this causes an issue // Consider the relative function versions if this causes an issue
const origin = doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc; const origin = doc.origin ? foundry.utils.fromUuidSync(doc.origin) : doc;
const useParentName = doc.parent && !(doc.parent instanceof Actor);
const name = doc.origin || !useParentName ? doc.name : doc.parent.name;
return { return {
origin, origin,
name: origin.name, name,
document: doc, document: doc,
data: doc.system.armor ?? doc.system.armorData, data: doc.system.armor ?? doc.system.armorData,
disabled: !!doc.disabled || !!doc.isSuppressed disabled: !!doc.disabled || !!doc.isSuppressed

View file

@ -40,7 +40,8 @@
"experiences": { "experiences": {
"ti3Z1mq2M92KK4GJ": { "ti3Z1mq2M92KK4GJ": {
"name": "Bloodthirsty", "name": "Bloodthirsty",
"description": "" "description": "",
"value": 3
} }
}, },
"bonuses": { "bonuses": {
@ -242,27 +243,24 @@
"type": "withinRange", "type": "withinRange",
"target": "hostile", "target": "hostile",
"range": "melee" "range": "melee"
}
}, },
"_id": "qZfNiqw1iAIxeuYg",
"img": "icons/commodities/biological/wing-lizard-brown.webp",
"changes": [ "changes": [
{ {
"key": "system.difficulty", "key": "system.difficulty",
"mode": 2, "value": 3,
"value": "3", "priority": null,
"priority": null "type": "add"
} }
], ]
},
"_id": "qZfNiqw1iAIxeuYg",
"img": "icons/commodities/biological/wing-lizard-brown.webp",
"disabled": false, "disabled": false,
"duration": { "duration": {
"startTime": null, "value": null,
"combat": null, "units": "seconds",
"seconds": null, "expiry": null,
"rounds": null, "expired": false
"turns": null,
"startRound": null,
"startTurn": null
}, },
"description": "<p>While flying, the Bat gains a +3 bonus to their Difficulty.</p>", "description": "<p>While flying, the Bat gains a +3 bonus to their Difficulty.</p>",
"origin": null, "origin": null,
@ -274,6 +272,9 @@
"_stats": { "_stats": {
"compendiumSource": null "compendiumSource": null
}, },
"start": null,
"showIcon": 1,
"folder": null,
"_key": "!actors.items.effects!tBWHW00epmMnkawe.gx22MpD8fWoi8klZ.qZfNiqw1iAIxeuYg" "_key": "!actors.items.effects!tBWHW00epmMnkawe.gx22MpD8fWoi8klZ.qZfNiqw1iAIxeuYg"
} }
], ],

View file

@ -138,12 +138,9 @@
"src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg", "src": "systems/daggerheart/assets/icons/documents/actors/dragon-head.svg",
"anchorX": 0.5, "anchorX": 0.5,
"anchorY": 0.5, "anchorY": 0.5,
"offsetX": 0,
"offsetY": 0,
"fit": "contain", "fit": "contain",
"scaleX": 1, "scaleX": 1,
"scaleY": 1, "scaleY": 1,
"rotation": 0,
"tint": "#ffffff", "tint": "#ffffff",
"alphaThreshold": 0.75 "alphaThreshold": 0.75
}, },
@ -194,7 +191,7 @@
"saturation": 0, "saturation": 0,
"contrast": 0 "contrast": 0
}, },
"detectionModes": [], "detectionModes": {},
"occludable": { "occludable": {
"radius": 0 "radius": 0
}, },
@ -220,7 +217,8 @@
"flags": {}, "flags": {},
"randomImg": false, "randomImg": false,
"appendNumber": false, "appendNumber": false,
"prependAdjective": false "prependAdjective": false,
"depth": 1
}, },
"items": [ "items": [
{ {

View file

@ -45,7 +45,7 @@
"hitPoints": { "hitPoints": {
"value": { "value": {
"dice": "d10", "dice": "d10",
"bonus": 1, "bonus": 2,
"multiplier": "prof", "multiplier": "prof",
"flatMultiplier": 1, "flatMultiplier": 1,
"custom": { "custom": {

View file

@ -2,7 +2,7 @@
"id": "daggerheart", "id": "daggerheart",
"title": "Daggerheart", "title": "Daggerheart",
"description": "An unofficial implementation of the Daggerheart system", "description": "An unofficial implementation of the Daggerheart system",
"version": "2.2.1", "version": "2.2.2",
"compatibility": { "compatibility": {
"minimum": "14.359", "minimum": "14.359",
"verified": "14.360", "verified": "14.360",
@ -10,7 +10,7 @@
}, },
"url": "https://github.com/Foundryborne/daggerheart", "url": "https://github.com/Foundryborne/daggerheart",
"manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json", "manifest": "https://raw.githubusercontent.com/Foundryborne/daggerheart/v14/system.json",
"download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.1/system.zip", "download": "https://github.com/Foundryborne/daggerheart/releases/download/2.2.2/system.zip",
"authors": [ "authors": [
{ {
"name": "WBHarry" "name": "WBHarry"

View file

@ -0,0 +1,38 @@
<section class="tag-team-roll tab {{#if tabs.tagTeamRoll.active}} active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}">
<div class="tag-team-roll-container {{#unless isEditable}}inactive{{/unless}}">
<div class="results-container">
<span class="result-container-label">{{localize "DAGGERHEART.GENERAL.result.plural"}}</span>
<div class="results-inner-container">
{{#if hintText}}
<div class="hint">{{localize hintText}}</div>
{{else}}
{{#if joinedRoll.roll}}
<div class="result-container">
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
<div class="result-info">
<div class="damage-info">{{joinedRoll.roll.total}}</div>
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
</div>
</div>
{{/if}}
{{#if joinedRoll.rollData.options.hasDamage}}
<div class="result-container">
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
{{#each joinedRoll.rollData.options.damage as |damage key|}}
<div class="result-info">
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
<div class="damage-info">{{damage.total}}</div>
</div>
{{/each}}
</div>
{{/if}}
{{/if}}
</div>
</div>
<div class="finish-container">
<button type="button" data-action="cancelRoll">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelTagTeamRoll"}}</button>
<button type="button" data-action="finishRoll" {{#if hintText}}disabled{{/if}} class="finish-button">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.finishTagTeamRoll"}}</button>
</div>
</div>
</section>

View file

@ -1,38 +1,9 @@
<section class="tag-team-roll tab {{#if tabs.tagTeamRoll.active}} active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}"> <div class="tab {{#if tabs.tagTeamRoll.active}}active{{/if}}" data-group="{{tabs.tagTeamRoll.group}}" data-tab="{{tabs.tagTeamRoll.id}}">
<div class="tag-team-roll-container {{#unless isEditable}}inactive{{/unless}}"> <div class="team-container">
<div class="results-container"> {{#each memberKeys as |key|}}
<span class="result-container-label">{{localize "DAGGERHEART.GENERAL.result.plural"}}</span> <div data-application-part="{{key}}"></div>
<div class="results-inner-container">
{{#if hintText}}
<div class="hint">{{localize hintText}}</div>
{{else}}
{{#if joinedRoll.roll}}
<div class="result-container">
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.dualityRoll"}}</span>
<div class="result-info">
<div class="damage-info">{{joinedRoll.roll.total}}</div>
<div>{{localize "DAGGERHEART.GENERAL.withThing" thing=joinedRoll.roll.totalLabel}}</div>
</div>
</div>
{{/if}}
{{#if joinedRoll.rollData.options.hasDamage}}
<div class="result-container">
<span class="result-section-label">{{localize "DAGGERHEART.GENERAL.damage"}}</span>
{{#each joinedRoll.rollData.options.damage as |damage key|}}
<div class="result-info">
<div>{{localize (concat "DAGGERHEART.CONFIG.HealingType." key ".name")}}</div>
<div class="damage-info">{{damage.total}}</div>
</div>
{{/each}} {{/each}}
</div> </div>
{{/if}} <div data-application-part="rollSelection"></div>
{{/if}} <div data-application-part="result"></div>
</div> </div>
</div>
<div class="finish-container">
<button type="button" data-action="cancelRoll">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.cancelTagTeamRoll"}}</button>
<button type="button" data-action="finishRoll" {{#if hintText}}disabled{{/if}} class="finish-button">{{localize "DAGGERHEART.APPLICATIONS.TagTeamSelect.finishTagTeamRoll"}}</button>
</div>
</div>
</section>