Compare commits

...

12 commits
1.4.3 ... main

Author SHA1 Message Date
WBHarry
0b343c9f52
Fixed a lot of cases where we expected a combatant to have an attached actor (#1520) 2026-01-10 00:21:44 +01:00
Carlos Fernandez
e6973fabd0
Add view party button to character sheet (#1508) 2026-01-09 17:41:35 +01:00
WBHarry
4e18ed8270
Fixed so that chatMessages always get actor data available (#1519) 2026-01-09 17:35:00 +01:00
Nikhil Nagarajan
e7cf6594b6
[PR] Rolltables Compendium Added (#1516)
* Initial Setup

* Updated Consumables RollTable

* Placed the rolltable compendium in the SRD folder

* updated Journal with fixed links

* Re-added descriptions in rolltable

---------

Co-authored-by: WBHarry <williambjrklund@gmail.com>
2026-01-09 17:34:11 +01:00
WBHarry
bbe8fb953e
Fixed so that tagify tooltip descriptions cannot end up with raw HTML that breaks it (#1504) 2026-01-09 15:57:57 +01:00
Nikhil Nagarajan
6cebccd958
Template removed from Stardrop JSON (#1513) 2026-01-09 15:56:48 +01:00
Chris Ryan
248f7b41e7
Safety check for experiences (#1515)
Co-authored-by: Chris Ryan <chrisr@blackhole>
2026-01-09 15:55:16 +01:00
WBHarry
c6bdc846ab
[Fix] SRD Effect Priorities (#1505)
* Increased priority on effects in srd domain cards that depend on character data

* Increased priority on effects in srd classes/subclasses that depend on character data

* Increased priority on effects in srd ancestries that depend on character data

* Increased priority on effects in srd beastforms that depend on character data

* Increased priority on effects in remaining SRD items that depend on character data

* Adversaries

* Raised system version
2026-01-09 15:50:56 +01:00
Carlos Fernandez
6deadea437
Fix detection of range dependencies (#1497) 2026-01-08 19:52:25 -05:00
WBHarry
9564edb244 . 2026-01-05 13:45:03 +01:00
WBHarry
bca7e0d3c9
[Fix] Beastforms Getting Stuck (#1495)
* Fixed beastforms getting stuck

* Raised version
2025-12-31 04:52:19 +01:00
Nick Salyzyn
3b7b6258a1
[PR] Adding the ability to target downtime actions. (#1475)
* Adding the ability to target downtime actions.

* No longer using an arbitrary 100 healing. Changing the action's parent
2025-12-29 21:55:13 +01:00
50 changed files with 3728 additions and 108 deletions

View file

@ -316,7 +316,7 @@ const updateActorsRangeDependentEffects = async token => {
CONFIG.DH.SETTINGS.gameSettings.variantRules CONFIG.DH.SETTINGS.gameSettings.variantRules
).rangeMeasurement; ).rangeMeasurement;
for (let effect of token.actor.allApplicableEffects()) { for (let effect of token.actor?.allApplicableEffects() ?? []) {
if (!effect.system.rangeDependence?.enabled) continue; if (!effect.system.rangeDependence?.enabled) continue;
const { target, range, type } = effect.system.rangeDependence; const { target, range, type } = effect.system.rangeDependence;
@ -330,14 +330,14 @@ const updateActorsRangeDependentEffects = async token => {
break; break;
} }
const distanceBetween = canvas.grid.measurePath([ // Get required distance and special case 5 feet to test adjacency
userTarget.document.movement.destination, const required = rangeMeasurement[range];
token.movement.destination
]).distance;
const distance = rangeMeasurement[range];
const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id; const reverse = type === CONFIG.DH.GENERAL.rangeInclusion.outsideRange.id;
if (reverse ? distanceBetween <= distance : distanceBetween > distance) { const inRange =
required === 5
? userTarget.isAdjacentWith(token.object)
: userTarget.distanceTo(token.object) <= required;
if (reverse ? inRange : !inRange) {
enabledEffect = false; enabledEffect = false;
break; break;
} }
@ -351,16 +351,15 @@ const updateAllRangeDependentEffects = async () => {
const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects; const effectsAutomation = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Automation).effects;
if (!effectsAutomation.rangeDependent) return; if (!effectsAutomation.rangeDependent) return;
// Only consider tokens on the active scene const tokens = canvas.scene.tokens;
const tokens = game.scenes.find(x => x.active).tokens;
if (game.user.character) { if (game.user.character) {
// The character updates their character's token. There can be only one token. // The character updates their character's token. There can be only one token.
const characterToken = tokens.find(x => x.actor === game.user.character); const characterToken = tokens.find(x => x.actor === game.user.character);
updateActorsRangeDependentEffects(characterToken); updateActorsRangeDependentEffects(characterToken);
} else if (game.user.isGM) { } else if (game.user.isActiveGM) {
// The GM is responsible for all other tokens. // The GM is responsible for all other tokens.
const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character); const playerCharacters = game.users.players.filter(x => x.active).map(x => x.character);
for (let token of tokens.filter(x => !playerCharacters.includes(x.actor))) { for (const token of tokens.filter(x => !playerCharacters.includes(x.actor))) {
updateActorsRangeDependentEffects(token); updateActorsRangeDependentEffects(token);
} }
} }
@ -368,12 +367,14 @@ const updateAllRangeDependentEffects = async () => {
const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50); const debouncedRangeEffectCall = foundry.utils.debounce(updateAllRangeDependentEffects, 50);
Hooks.on('targetToken', async (user, token, targeted) => { Hooks.on('targetToken', () => {
debouncedRangeEffectCall(); debouncedRangeEffectCall();
}); });
Hooks.on('moveToken', async (movedToken, data) => { Hooks.on('refreshToken', (_, options) => {
debouncedRangeEffectCall(); if (options.refreshPosition) {
debouncedRangeEffectCall();
}
}); });
Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html)); Hooks.on('renderCompendiumDirectory', (app, html) => applications.ui.ItemBrowser.injectSidebarButton(html));

View file

@ -226,6 +226,7 @@
"confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)" "confirmText": "Would you like to level up your companion {name} by {levelChange} levels at this time? (You can do it manually later)"
}, },
"viewLevelups": "View Levelups", "viewLevelups": "View Levelups",
"viewParty": "View Party",
"InvalidOldCharacterImportTitle": "Old Character Import", "InvalidOldCharacterImportTitle": "Old Character Import",
"InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?", "InvalidOldCharacterImportText": "Character data exported prior to system version 1.1 will not generate a complete character. Do you wish to continue?",
"cancelBeastform": "Cancel Beastform" "cancelBeastform": "Cancel Beastform"
@ -1801,7 +1802,9 @@
"label": "Long Rest: Bonus Long Rest Moves", "label": "Long Rest: Bonus Long Rest Moves",
"hint": "The number of extra Long Rest Moves the character can take during a Long Rest." "hint": "The number of extra Long Rest Moves the character can take during a Long Rest."
} }
} },
"target": "Target",
"targetSelf": "Self"
}, },
"maxLoadout": { "maxLoadout": {
"label": "Max Loadout Cards Bonus" "label": "Max Loadout Cards Bonus"
@ -2778,7 +2781,9 @@
"gmRequired": "This action requires an online GM", "gmRequired": "This action requires an online GM",
"gmOnly": "This can only be accessed by the 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." "documentIsMissing": "The {documentType} is missing from the world.",
"tokenActorMissing": "{name} is missing an Actor",
"tokenActorsMissing": "[{names}] missing Actors"
}, },
"Sidebar": { "Sidebar": {
"actorDirectory": { "actorDirectory": {

View file

@ -104,7 +104,7 @@ export default class D20RollDialog extends HandlebarsApplicationMixin(Applicatio
context.roll = this.roll; context.roll = this.roll;
context.rollType = this.roll?.constructor.name; context.rollType = this.roll?.constructor.name;
context.rallyDie = this.roll.rallyChoices; context.rallyDie = this.roll.rallyChoices;
const experiences = this.config.data?.system.experiences || {}; const experiences = this.config.data?.system?.experiences || {};
context.experiences = Object.keys(experiences).map(id => ({ context.experiences = Object.keys(experiences).map(id => ({
id, id,
...experiences[id] ...experiences[id]

View file

@ -181,12 +181,17 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
.filter(x => category.moves[x].selected) .filter(x => category.moves[x].selected)
.flatMap(key => { .flatMap(key => {
const move = category.moves[key]; const move = category.moves[key];
const needsTarget = move.actions.filter(x => x.target?.type && x.target.type !== 'self').length > 0;
return [...Array(move.selected).keys()].map(_ => ({ return [...Array(move.selected).keys()].map(_ => ({
...move, ...move,
movePath: `${categoryKey}.moves.${key}` movePath: `${categoryKey}.moves.${key}`,
needsTarget: needsTarget
})); }));
}); });
}); });
const characters = game.actors.filter(x => x.type === 'character')
.filter(x => x.testUserPermission(game.user, 'LIMITED'))
.filter(x => x.uuid !== this.actor.uuid);
const cls = getDocumentClass('ChatMessage'); const cls = getDocumentClass('ChatMessage');
const msg = { const msg = {
@ -206,7 +211,9 @@ export default class DhpDowntime extends HandlebarsApplicationMixin(ApplicationV
`DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title` `DAGGERHEART.APPLICATIONS.Downtime.${this.shortrest ? 'shortRest' : 'longRest'}.title`
), ),
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
moves: moves moves: moves,
characters: characters,
selfId: this.actor.uuid
} }
), ),
flags: { flags: {

View file

@ -77,7 +77,7 @@ export default class TagTeamDialog extends HandlebarsApplicationMixin(Applicatio
cost: this.data.initiator.cost cost: this.data.initiator.cost
}; };
const selectedMember = Object.values(context.members).find(x => x.selected); const selectedMember = Object.values(context.members).find(x => x.selected && x.roll);
const selectedIsCritical = selectedMember?.roll?.system?.isCritical; const selectedIsCritical = selectedMember?.roll?.system?.isCritical;
context.selectedData = { context.selectedData = {
result: selectedMember result: selectedMember

View file

@ -21,6 +21,8 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
async _prepareContext(options) { async _prepareContext(options) {
const context = await super._prepareContext(options); const context = await super._prepareContext(options);
if (!this.actor) return context;
context.partyOnCanvas = context.partyOnCanvas =
this.actor.type === 'party' && this.actor.type === 'party' &&
this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0); this.actor.system.partyMembers.some(member => member.getActiveTokens().length > 0);
@ -58,14 +60,33 @@ export default class DHTokenHUD extends foundry.applications.hud.TokenHUD {
} }
static async #onToggleCombat() { static async #onToggleCombat() {
const tokensWithoutActors = canvas.tokens.controlled.filter(t => !t.actor);
const warning =
tokensWithoutActors.length === 1
? game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorMissing', {
name: tokensWithoutActors[0].name
})
: game.i18n.format('DAGGERHEART.UI.Notifications.tokenActorsMissing', {
names: tokensWithoutActors.map(x => x.name).join(', ')
});
const tokens = canvas.tokens.controlled const tokens = canvas.tokens.controlled
.filter(t => !t.actor || !DHTokenHUD.#nonCombatTypes.includes(t.actor.type)) .filter(t => t.actor && !DHTokenHUD.#nonCombatTypes.includes(t.actor.type))
.map(t => t.document); .map(t => t.document);
if (!this.object.controlled) tokens.push(this.document); if (!this.object.controlled && this.document.actor) tokens.push(this.document);
try { try {
if (this.document.inCombat) await TokenDocument.implementation.deleteCombatants(tokens); if (this.document.inCombat) {
else await TokenDocument.implementation.createCombatants(tokens); const tokensInCombat = tokens.filter(t => t.inCombat);
await TokenDocument.implementation.deleteCombatants([...tokensInCombat, ...tokensWithoutActors]);
} else {
if (tokensWithoutActors.length) {
ui.notifications.warn(warning);
}
const tokensOutOfCombat = tokens.filter(t => !t.inCombat);
await TokenDocument.implementation.createCombatants(tokensOutOfCombat);
}
} catch (err) { } catch (err) {
ui.notifications.warn(err.message); ui.notifications.warn(err.message);
} }

View file

@ -32,7 +32,8 @@ export default class CharacterSheet extends DHBaseActorSheet {
handleResourceDice: CharacterSheet.#handleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice,
advanceResourceDie: CharacterSheet.#advanceResourceDie, advanceResourceDie: CharacterSheet.#advanceResourceDie,
cancelBeastform: CharacterSheet.#cancelBeastform, cancelBeastform: CharacterSheet.#cancelBeastform,
useDowntime: this.useDowntime useDowntime: this.useDowntime,
viewParty: CharacterSheet.#viewParty,
}, },
window: { window: {
resizable: true, resizable: true,
@ -892,6 +893,41 @@ export default class CharacterSheet extends DHBaseActorSheet {
game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item); game.system.api.fields.ActionFields.BeastformField.handleActiveTransformations.call(item);
} }
static async #viewParty(_, target) {
const parties = this.document.parties;
if (parties.size <= 1) {
parties.first()?.sheet.render({ force: true });
return;
}
const buttons = parties.map((p) => {
const button = document.createElement("button");
button.type = "button";
button.classList.add("plain");
const img = document.createElement("img");
img.src = p.img;
button.append(img);
const name = document.createElement("span");
name.textContent = p.name;
button.append(name);
button.addEventListener("click", () => {
p.sheet?.render({ force: true });
game.tooltip.dismissLockedTooltips();
});
return button;
});
const html = document.createElement("div");
html.classList.add("party-list");
html.append(...buttons);
game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, {
html,
locked: true,
})
}
/** /**
* Open the downtime application. * Open the downtime application.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}

View file

@ -134,7 +134,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
async actionUseButton(event, message) { async actionUseButton(event, message) {
const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset; const { moveIndex, actionIndex, movePath } = event.currentTarget.dataset;
const parent = await foundry.utils.fromUuid(message.system.actor); const targetUuid = event.currentTarget.closest('.action-use-button-parent').querySelector('select')?.value;
const parent = await foundry.utils.fromUuid(targetUuid || message.system.actor)
const actionType = message.system.moves[moveIndex].actions[actionIndex]; const actionType = message.system.moves[moveIndex].actions[actionIndex];
const cls = game.system.api.models.actions.actionsTypes[actionType.type]; const cls = game.system.api.models.actions.actionsTypes[actionType.type];
const action = new cls( const action = new cls(
@ -146,7 +148,8 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
type: CONFIG.DH.ITEM.originItemType.restMove, type: CONFIG.DH.ITEM.originItemType.restMove,
itemPath: movePath, itemPath: movePath,
actionIndex: actionIndex actionIndex: actionIndex
} },
targetUuid: targetUuid
}, },
{ parent: parent.system } { parent: parent.system }
); );

View file

@ -127,7 +127,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
resource, resource,
active: index === combat.turn, active: index === combat.turn,
canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'), canPing: combatant.sceneId === canvas.scene?.id && game.user.hasPermission('PING_CANVAS'),
type: combatant.actor.system.type, type: combatant.actor?.system?.type,
img: await this._getCombatantThumbnail(combatant) img: await this._getCombatantThumbnail(combatant)
}; };
@ -165,7 +165,7 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
if (this.viewed.turn !== toggleTurn) { if (this.viewed.turn !== toggleTurn) {
const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns; const { updateCountdowns } = game.system.api.applications.ui.DhCountdowns;
if (combatant.actor.type === 'character') { if (combatant.actor?.type === 'character') {
await updateCountdowns( await updateCountdowns(
CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id, CONFIG.DH.GENERAL.countdownProgressionTypes.spotlight.id,
CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id CONFIG.DH.GENERAL.countdownProgressionTypes.characterSpotlight.id

View file

@ -34,6 +34,69 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.renderFlags.set({ refreshEffects: true }); this.renderFlags.set({ refreshEffects: true });
} }
/**
* Returns the distance from this token to another token object.
* This value is corrected to handle alternate token sizes and other grid types
* according to the diagonal rules.
*/
distanceTo(target) {
if (!canvas.ready) return NaN;
if (this === target) return 0;
const originPoint = this.center;
const destinationPoint = target.center;
// Compute for gridless. This version returns circular edge to edge + grid distance,
// so that tokens that are touching return 5.
if (canvas.grid.type === CONST.GRID_TYPES.GRIDLESS) {
const boundsCorrection = canvas.grid.distance / canvas.grid.size;
const originRadius = this.bounds.width * boundsCorrection / 2;
const targetRadius = target.bounds.width * boundsCorrection / 2;
const distance = canvas.grid.measurePath([originPoint, destinationPoint]).distance;
return distance - originRadius - targetRadius + canvas.grid.distance;
}
// Compute what the closest grid space of each token is, then compute that distance
const originEdge = this.#getEdgeBoundary(this.bounds, originPoint, destinationPoint);
const targetEdge = this.#getEdgeBoundary(target.bounds, originPoint, destinationPoint);
const adjustedOriginPoint = canvas.grid.getTopLeftPoint({
x: originEdge.x + Math.sign(originPoint.x - originEdge.x),
y: originEdge.y + Math.sign(originPoint.y - originEdge.y)
});
const adjustDestinationPoint = canvas.grid.getTopLeftPoint({
x: targetEdge.x + Math.sign(destinationPoint.x - targetEdge.x),
y: targetEdge.y + Math.sign(destinationPoint.y - targetEdge.y)
});
return canvas.grid.measurePath([adjustedOriginPoint, adjustDestinationPoint]).distance;
}
/** Returns the point at which a line starting at origin and ending at destination intersects the edge of the bounds */
#getEdgeBoundary(bounds, originPoint, destinationPoint) {
const points = [
{ x: bounds.x, y: bounds.y },
{ x: bounds.x + bounds.width, y: bounds.y },
{ x: bounds.x + bounds.width, y: bounds.y + bounds.height },
{ x: bounds.x, y: bounds.y + bounds.height }
];
const pairsToTest = [
[points[0], points[1]],
[points[1], points[2]],
[points[2], points[3]],
[points[3], points[0]]
];
for (const pair of pairsToTest) {
const result = foundry.utils.lineSegmentIntersection(originPoint, destinationPoint, pair[0], pair[1]);
if (result) return result;
}
return null;
}
/** Tests if the token is at least adjacent with another, with some leeway for diagonals */
isAdjacentWith(token) {
return this.distanceTo(token) <= (canvas.grid.distance * 1.5);
}
/** @inheritDoc */ /** @inheritDoc */
_drawBar(number, bar, data) { _drawBar(number, bar, data) {
const val = Number(data.value); const val = Number(data.value);

View file

@ -9,7 +9,7 @@ export const AdversaryBPPerEncounter = (adversaries, characters) => {
); );
if (existingEntry) { if (existingEntry) {
existingEntry.nr += 1; existingEntry.nr += 1;
} else { } else if (adversary.type) {
acc.push({ adversary, nr: 1 }); acc.push({ adversary, nr: 1 });
} }
return acc; return acc;

View file

@ -232,7 +232,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [
@ -298,7 +298,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [
@ -341,7 +341,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [
@ -407,7 +407,7 @@ export const defaultRestOptions = {
actionType: 'action', actionType: 'action',
chatDisplay: false, chatDisplay: false,
target: { target: {
type: 'self' type: 'friendly'
}, },
damage: { damage: {
parts: [ parts: [

View file

@ -435,7 +435,8 @@ export const armorFeatures = {
{ {
key: 'system.resistance.magical.reduction', key: 'system.resistance.magical.reduction',
mode: 2, mode: 2,
value: '@system.armorScore' value: '@system.armorScore',
priority: 21
} }
] ]
} }
@ -709,7 +710,8 @@ export const weaponFeatures = {
{ {
key: 'system.evasion', key: 'system.evasion',
mode: 2, mode: 2,
value: '@system.armorScore' value: '@system.armorScore',
priority: 21
} }
] ]
} }
@ -1324,7 +1326,8 @@ export const weaponFeatures = {
{ {
key: 'system.bonuses.damage.primaryWeapon.bonus', key: 'system.bonuses.damage.primaryWeapon.bonus',
mode: 2, mode: 2,
value: '@system.traits.agility.value' value: '@system.traits.agility.value',
priority: 21
} }
] ]
} }
@ -1416,9 +1419,9 @@ export const orderedWeaponFeatures = () => {
}; };
export const featureForm = { export const featureForm = {
passive: "DAGGERHEART.CONFIG.FeatureForm.passive", passive: 'DAGGERHEART.CONFIG.FeatureForm.passive',
action: "DAGGERHEART.CONFIG.FeatureForm.action", action: 'DAGGERHEART.CONFIG.FeatureForm.action',
reaction: "DAGGERHEART.CONFIG.FeatureForm.reaction" reaction: 'DAGGERHEART.CONFIG.FeatureForm.reaction'
}; };
export const featureTypes = { export const featureTypes = {

View file

@ -33,7 +33,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
initial: 'action', initial: 'action',
nullable: false, nullable: false,
required: true required: true
}) }),
targetUuid: new fields.StringField({ initial: undefined })
}; };
this.extraSchemas.forEach(s => { this.extraSchemas.forEach(s => {
@ -241,7 +242,8 @@ export default class DHBaseAction extends ActionMixin(foundry.abstract.DataModel
selectedRollMode: game.settings.get('core', 'rollMode'), selectedRollMode: game.settings.get('core', 'rollMode'),
data: this.getRollData(), data: this.getRollData(),
evaluate: this.hasRoll, evaluate: this.hasRoll,
resourceUpdates: new ResourceUpdateMap(this.actor) resourceUpdates: new ResourceUpdateMap(this.actor),
targetUuid: this.targetUuid
}; };
DHBaseAction.applyKeybindings(config); DHBaseAction.applyKeybindings(config);

View file

@ -1,3 +1,17 @@
/** -- Changes Type Priorities --
* - Base Number -
* Custom: 0
* Multiply: 10
* Add: 20
* Downgrade: 30
* Upgrade: 40
* Override: 50
*
* - Changes Value Priorities -
* Standard: +0
* "Anything that uses another data model value as its value": +1 - Effects that increase traits have to be calculated first at Base priority. (EX: Raise evasion by half your agility)
*/
export default class BaseEffect extends foundry.abstract.TypeDataModel { export default class BaseEffect extends foundry.abstract.TypeDataModel {
static defineSchema() { static defineSchema() {
const fields = foundry.data.fields; const fields = foundry.data.fields;

View file

@ -66,12 +66,20 @@ export default class BeastformEffect extends BaseEffect {
}; };
const updateToken = token => { const updateToken = token => {
const { x, y } = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( let x = null,
token.object.scene.grid, y = null;
{ x: token.x, y: token.y, elevation: token.elevation }, if (token.object?.scene?.grid) {
baseUpdate.width, const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
baseUpdate.height token.object.scene.grid,
); { x: token.x, y: token.y, elevation: token.elevation },
baseUpdate.width,
baseUpdate.height
);
x = positionData.x;
y = positionData.y;
}
return { return {
...baseUpdate, ...baseUpdate,
x, x,

View file

@ -15,8 +15,9 @@ export default class DhCombat extends foundry.abstract.TypeDataModel {
get extendedBattleToggles() { get extendedBattleToggles() {
const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers; const modifiers = CONFIG.DH.ENCOUNTER.BPModifiers;
const adversaries = const adversaries =
this.parent.turns?.filter(x => x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? []; this.parent.turns?.filter(x => x.actor && x.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ??
const characters = this.parent.turns?.filter(x => !x.isNPC) ?? []; [];
const characters = this.parent.turns?.filter(x => x.actor && !x.isNPC) ?? [];
const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => { const activeAutomatic = Object.keys(modifiers).reduce((acc, categoryKey) => {
const category = modifiers[categoryKey]; const category = modifiers[categoryKey];

View file

@ -25,9 +25,12 @@ export default class TargetField extends fields.SchemaField {
config.hasTarget = true; config.hasTarget = true;
let targets; let targets;
// If the Action is configured as self-targeted, set targets as the owner. Probably better way than to fallback to getDependentTokens // 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) if (this.target?.type === CONFIG.DH.GENERAL.targetTypes.self.id) {
targets = [this.actor.token ?? this.actor.prototypeToken]; targets = [this.actor.token ?? this.actor.prototypeToken];
else { } else if (config.targetUuid) {
const actor = fromUuidSync(config.targetUuid);
targets = [actor.token ?? actor.prototypeToken];
} else {
targets = Array.from(game.user.targets); targets = Array.from(game.user.targets);
if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) { if (this.target.type !== CONFIG.DH.GENERAL.targetTypes.any.id) {
targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type)); targets = targets.filter(target => TargetField.isTargetFriendly(this.actor, target, this.target.type));

View file

@ -272,12 +272,17 @@ export function ActionMixin(Base) {
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 : '')
}; };
const speaker = cls.getSpeaker();
const msg = { const msg = {
type: 'abilityUse', type: 'abilityUse',
user: game.user.id, user: game.user.id,
actor: { name: this.actor.name, img: this.actor.img }, actor: { name: this.actor.name, img: this.actor.img },
author: this.author, author: this.author,
speaker: cls.getSpeaker(), speaker: {
speaker,
actor: speaker.actor ?? this.actor
},
title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'), title: game.i18n.localize('DAGGERHEART.UI.Chat.action.title'),
system: systemData, system: systemData,
content: await foundry.applications.handlebars.renderTemplate( content: await foundry.applications.handlebars.renderTemplate(

View file

@ -218,12 +218,20 @@ export default class DHBeastform extends BaseDataItem {
} }
}; };
const tokenUpdate = token => { const tokenUpdate = token => {
const { x, y } = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid( let x = null,
token.object.scene.grid, y = null;
{ x: token.x, y: token.y, elevation: token.elevation }, if (token.object?.scene?.grid) {
width ?? token.width, const positionData = game.system.api.documents.DhToken.getSnappedPositionInSquareGrid(
height ?? token.height token.object.scene.grid,
); { x: token.x, y: token.y, elevation: token.elevation },
width ?? token.width,
height ?? token.height
);
x = positionData.x;
y = positionData.y;
}
return { return {
...prototypeTokenUpdate, ...prototypeTokenUpdate,
x, x,

View file

@ -83,7 +83,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
if (combat?.system?.battleToggles?.length) { if (combat?.system?.battleToggles?.length) {
await combat.toggleModifierEffects( await combat.toggleModifierEffects(
true, true,
tokens.map(x => x.actor) tokens.filter(x => x.actor).map(x => x.actor)
); );
} }
super.createCombatants(tokens, combat ?? {}); super.createCombatants(tokens, combat ?? {});
@ -95,7 +95,7 @@ export default class DHToken extends CONFIG.Token.documentClass {
if (combat?.system?.battleToggles?.length) { if (combat?.system?.battleToggles?.length) {
await combat.toggleModifierEffects( await combat.toggleModifierEffects(
false, false,
tokens.map(x => x.actor) tokens.filter(x => x.actor).map(x => x.actor)
); );
} }
super.deleteCombatants(tokens, combat ?? {}); super.deleteCombatants(tokens, combat ?? {});

View file

@ -262,7 +262,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
const combat = game.combats.get(combatId); const combat = game.combats.get(combatId);
const adversaries = const adversaries =
combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? []; combat.turns?.filter(x => x.actor?.isNPC)?.map(x => ({ ...x.actor, type: x.actor.system.type })) ?? [];
const characters = combat.turns?.filter(x => !x.isNPC) ?? []; const characters = combat.turns?.filter(x => !x.isNPC && x.actor) ?? [];
const nrCharacters = characters.length; const nrCharacters = characters.length;
const currentBP = AdversaryBPPerEncounter(adversaries, characters); const currentBP = AdversaryBPPerEncounter(adversaries, characters);
@ -272,7 +272,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
); );
const categories = combat.combatants.reduce((acc, combatant) => { const categories = combat.combatants.reduce((acc, combatant) => {
if (combatant.actor.type === 'adversary') { if (combatant.actor?.type === 'adversary') {
const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => { const keyData = Object.keys(acc).reduce((identifiers, categoryKey) => {
if (identifiers) return identifiers; if (identifiers) return identifiers;
const category = acc[categoryKey]; const category = acc[categoryKey];
@ -352,7 +352,7 @@ export default class DhTooltipManager extends foundry.helpers.interaction.Toolti
await combat.toggleModifierEffects( await combat.toggleModifierEffects(
event.target.checked, event.target.checked,
combat.combatants.filter(x => x.actor.type === 'adversary').map(x => x.actor), combat.combatants.filter(x => x.actor?.type === 'adversary').map(x => x.actor),
category, category,
grouping grouping
); );

View file

@ -119,7 +119,7 @@ export const tagifyElement = (element, baseOptions, onChange, tagifyOptions = {}
spellcheck='false' spellcheck='false'
tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}" tabIndex="${this.settings.a11y.focusableTags ? 0 : -1}"
class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}" class="${this.settings.classNames.tag} ${tagData.class ? tagData.class : ''}"
data-tooltip="${tagData.description || tagData.name}" data-tooltip="${tagData.description ? htmlToText(tagData.description) : tagData.name}"
${this.getAttributes(tagData)}> ${this.getAttributes(tagData)}>
<x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x> <x class="${this.settings.classNames.tagX}" role='button' aria-label='remove tag'></x>
<div> <div>
@ -198,7 +198,7 @@ foundry.dice.terms.Die.prototype.selfCorrecting = function (modifier) {
}; };
export const getDamageKey = damage => { export const getDamageKey = damage => {
return ['none', 'minor', 'major', 'severe', 'massive','any'][damage]; return ['none', 'minor', 'major', 'severe', 'massive', 'any'][damage];
}; };
export const getDamageLabel = damage => { export const getDamageLabel = damage => {
@ -474,3 +474,10 @@ export async function getCritDamageBonus(formula) {
const critRoll = new Roll(formula); const critRoll = new Roll(formula);
return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0); return critRoll.dice.reduce((acc, dice) => acc + dice.faces * dice.number, 0);
} }
export function htmlToText(html) {
var tempDivElement = document.createElement('div');
tempDivElement.innerHTML = html;
return tempDivElement.textContent || tempDivElement.innerText || '';
}

View file

@ -284,7 +284,7 @@
"key": "system.bonuses.roll.attack.bonus", "key": "system.bonuses.roll.attack.bonus",
"mode": 2, "mode": 2,
"value": "ITEM.@system.resource.value", "value": "ITEM.@system.resource.value",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -36,13 +36,13 @@
"key": "system.damageThresholds.major", "key": "system.damageThresholds.major",
"mode": 2, "mode": 2,
"value": "@prof", "value": "@prof",
"priority": null "priority": 21
}, },
{ {
"key": "system.damageThresholds.severe", "key": "system.damageThresholds.severe",
"mode": 2, "mode": 2,
"value": "@prof", "value": "@prof",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -100,7 +100,7 @@
"key": "system.resistance.physical.reduction", "key": "system.resistance.physical.reduction",
"mode": 2, "mode": 2,
"value": "@system.armorScore", "value": "@system.armorScore",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -38,13 +38,13 @@
"key": "system.bonuses.damage.primaryWeapon.bonus", "key": "system.bonuses.damage.primaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.strength.value", "value": "@system.traits.strength.value",
"priority": null "priority": 21
}, },
{ {
"key": "system.bonuses.damage.secondaryWeapon.bonus", "key": "system.bonuses.damage.secondaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.strength.value", "value": "@system.traits.strength.value",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,
@ -57,7 +57,7 @@
"startRound": null, "startRound": null,
"startTurn": null "startTurn": null
}, },
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">On a successful attack using a weapon with a </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Melee</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> range, gain a bonus to your damage roll equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Strength</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</p>", "description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">On a successful attack using a weapon with a </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Melee</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\"> range, gain a bonus to your damage roll equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Strength</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</span></p>",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,

View file

@ -37,13 +37,13 @@
"key": "system.bonuses.damage.primaryWeapon.bonus", "key": "system.bonuses.damage.primaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.agility.value", "value": "@system.traits.agility.value",
"priority": null "priority": 21
}, },
{ {
"key": "system.bonuses.damage.secondaryWeapon.bonus", "key": "system.bonuses.damage.secondaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.agility.value", "value": "@system.traits.agility.value",
"priority": null "priority": 21
} }
], ],
"disabled": true, "disabled": true,

View file

@ -101,7 +101,7 @@
"key": "system.traits.presence.value", "key": "system.traits.presence.value",
"mode": 5, "mode": 5,
"value": "@cast", "value": "@cast",
"priority": null "priority": 51
} }
], ],
"disabled": false, "disabled": false,

View file

@ -113,13 +113,13 @@
"key": "system.bonuses.damage.magical.bonus", "key": "system.bonuses.damage.magical.bonus",
"mode": 2, "mode": 2,
"value": "2*@system.traits.strength.value", "value": "2*@system.traits.strength.value",
"priority": null "priority": 21
}, },
{ {
"key": "system.bonuses.damage.physical.bonus", "key": "system.bonuses.damage.physical.bonus",
"mode": 2, "mode": 2,
"value": "2*@system.traits.strength.value", "value": "2*@system.traits.strength.value",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,
@ -162,13 +162,13 @@
"key": "system.bonuses.damage.magical.bonus", "key": "system.bonuses.damage.magical.bonus",
"mode": 2, "mode": 2,
"value": "4*@system.traits.strength.value", "value": "4*@system.traits.strength.value",
"priority": null "priority": 21
}, },
{ {
"key": "system.bonuses.damage.physical.bonus", "key": "system.bonuses.damage.physical.bonus",
"mode": 2, "mode": 2,
"value": "4*@system.traits.strength.value", "value": "4*@system.traits.strength.value",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -106,7 +106,7 @@
"key": "system.damageThresholds.severe", "key": "system.damageThresholds.severe",
"mode": 2, "mode": 2,
"value": "@system.proficiency", "value": "@system.proficiency",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,
@ -119,7 +119,7 @@
"startRound": null, "startRound": null,
"startTurn": null "startTurn": null
}, },
"description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Gain a bonus to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Severe </span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">threshold equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Proficiency</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</p>", "description": "<p><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">Gain a bonus to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Severe </span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">threshold equal to your </span><span class=\"tooltip-convert\" style=\"box-sizing:border-box;scrollbar-width:thin;scrollbar-color:rgb(93, 20, 43) rgba(0, 0, 0, 0);color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial\">Proficiency</span><span style=\"color:rgb(239, 230, 216);font-family:Montserrat, sans-serif;font-size:14px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:start;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:normal;background-color:rgba(24, 22, 46, 0.376);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">.</span></p>",
"origin": null, "origin": null,
"tint": "#ffffff", "tint": "#ffffff",
"transfer": true, "transfer": true,

View file

@ -37,7 +37,7 @@
"key": "system.evasion", "key": "system.evasion",
"mode": 2, "mode": 2,
"value": "ceil(@system.traits.agility.value / 2)", "value": "ceil(@system.traits.agility.value / 2)",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -4,7 +4,7 @@
"type": "domainCard", "type": "domainCard",
"folder": "OwsbTSWzKq2WJmQN", "folder": "OwsbTSWzKq2WJmQN",
"system": { "system": {
"description": "<p class=\"Body-Foundation\">Make a <strong>Spellcast Roll (16)</strong>. Once per long rest on a success, choose a point within Far range and create a visible zone of protection there for all allies within Very Close range of that point. When you do, place a <strong>d6</strong> on this card with the 1 value facing up. When an ally in this zone takes damage, they reduce it by the dies value. You then increase the dies value by one. When the dies value would exceed 6, this effect ends.</p><p><span style=\"color:oklab(0.952331 0.000418991 -0.00125992);font-family:'gg mono', 'Source Code Pro', Consolas, 'Andale Mono WT', 'Andale Mono', 'Lucida Console', 'Lucida Sans Typewriter', 'DejaVu Sans Mono', 'Bitstream Vera Sans Mono', 'Liberation Mono', 'Nimbus Mono L', Monaco, 'Courier New', Courier, monospace;font-size:13.6px;font-style:normal;font-variant-ligatures:normal;font-variant-caps:normal;font-weight:400;letter-spacing:normal;orphans:2;text-align:left;text-indent:0px;text-transform:none;widows:2;word-spacing:0px;-webkit-text-stroke-width:0px;white-space:pre-wrap;background-color:oklab(0.57738 0.0140701 -0.208587 / 0.0784314);text-decoration-thickness:initial;text-decoration-style:initial;text-decoration-color:initial;display:inline !important;float:none\">@Template[type:emanation|range:vc]</p>", "description": "<p class=\"Body-Foundation\">Make a <strong>Spellcast Roll (16)</strong>. Once per long rest on a success, choose a point within Far range and create a visible zone of protection there for all allies within Very Close range of that point. When you do, place a <strong>d6</strong> on this card with the 1 value facing up. When an ally in this zone takes damage, they reduce it by the dies value. You then increase the dies value by one. When the dies value would exceed 6, this effect ends.</p><p>@Template[type:emanation|range:vc]</p>",
"domain": "splendor", "domain": "splendor",
"recallCost": 2, "recallCost": 2,
"level": 6, "level": 6,

View file

@ -39,7 +39,7 @@
"key": "system.armorScore", "key": "system.armorScore",
"mode": 2, "mode": 2,
"value": "@system.traits.strength.value", "value": "@system.traits.strength.value",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -42,7 +42,8 @@
{ {
"key": "system.resistance.magical.reduction", "key": "system.resistance.magical.reduction",
"mode": 2, "mode": 2,
"value": "@system.armorScore" "value": "@system.armorScore",
"priority": 21
} }
], ],
"_id": "xGxqTCO8MjNq5Cw6", "_id": "xGxqTCO8MjNq5Cw6",

View file

@ -4,7 +4,7 @@
"_id": "y4c1jrlHrf0wBWOq", "_id": "y4c1jrlHrf0wBWOq",
"img": "icons/magic/light/projectiles-star-purple.webp", "img": "icons/magic/light/projectiles-star-purple.webp",
"system": { "system": {
"description": "<p>You can use this stardrop to summon a hailstorm of comets that deals 8d20 physical damage to all targets within Very Far range.</p><p>@Template[type:emanation|range:vf]</p>", "description": "<p>You can use this stardrop to summon a hailstorm of comets that deals 8d20 physical damage to all targets within Very Far range.</p>",
"quantity": 1, "quantity": 1,
"actions": { "actions": {
"pt5U6hlyx4T7MUOa": { "pt5U6hlyx4T7MUOa": {

View file

@ -155,7 +155,8 @@
{ {
"key": "system.evasion", "key": "system.evasion",
"mode": 2, "mode": 2,
"value": "@system.armorScore" "value": "@system.armorScore",
"priority": 21
} }
], ],
"transfer": false, "transfer": false,

View file

@ -117,7 +117,8 @@
{ {
"key": "system.bonuses.damage.primaryWeapon.bonus", "key": "system.bonuses.damage.primaryWeapon.bonus",
"mode": 2, "mode": 2,
"value": "@system.traits.agility.value" "value": "@system.traits.agility.value",
"priority": 21
} }
], ],
"_id": "jMIrOhpPUncn7dWg", "_id": "jMIrOhpPUncn7dWg",

File diff suppressed because one or more lines are too long

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

View file

@ -0,0 +1,319 @@
{
"name": "Table of Random Objectives",
"img": "icons/sundries/documents/document-torn-diagram-tan.webp",
"description": "<p>Layering Goals Other than Attrition into Combat</p>",
"results": [
{
"type": "text",
"weight": 1,
"range": [
1,
1
],
"_id": "LDuVbmdvhJiEOe7U",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Acquire (obtain or steal) an important item or items.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.LDuVbmdvhJiEOe7U"
},
{
"type": "text",
"weight": 1,
"range": [
2,
2
],
"_id": "FxYpST4nQUTBp1mN",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Capture one or more of the opponents.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.FxYpST4nQUTBp1mN"
},
{
"type": "text",
"weight": 1,
"range": [
3,
3
],
"_id": "bTkZgxqEr4lNxzeK",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Activate a magical device.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.bTkZgxqEr4lNxzeK"
},
{
"type": "text",
"weight": 1,
"range": [
4,
4
],
"_id": "T39LgOL1cw5AIY59",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Frame a character or tarnish their reputation.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.T39LgOL1cw5AIY59"
},
{
"type": "text",
"weight": 1,
"range": [
5,
5
],
"_id": "MHgv8dlrwA3ZmBKq",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Drive the opponent into a corner or ambush point.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.MHgv8dlrwA3ZmBKq"
},
{
"type": "text",
"weight": 1,
"range": [
6,
6
],
"_id": "4USCNNavzVvBqldn",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Stop a magical ritual, legal ceremony, or time-sensitive spell.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.4USCNNavzVvBqldn"
},
{
"type": "text",
"weight": 1,
"range": [
7,
7
],
"_id": "gwZnWTauHsbb6rsr",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Hold the line—keep the enemy from reaching a specific area or group.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.gwZnWTauHsbb6rsr"
},
{
"type": "text",
"weight": 1,
"range": [
8,
8
],
"_id": "beDIxxPyCCVa8nlE",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Plant evidence or a tracking device on a target.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.beDIxxPyCCVa8nlE"
},
{
"type": "text",
"weight": 1,
"range": [
9,
9
],
"_id": "C70V6prVmZd5VRV8",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Secure a specific location ahead of another groups arrival.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.C70V6prVmZd5VRV8"
},
{
"type": "text",
"weight": 1,
"range": [
10,
10
],
"_id": "i02rh05CvhHlKJCN",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Harass the opponent to deplete their resources or keep them occupied.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.i02rh05CvhHlKJCN"
},
{
"type": "text",
"weight": 1,
"range": [
11,
11
],
"_id": "AbNgD5GCbWuui9oP",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Destroy a piece of architecture, a statue, a shrine, or a weapon.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.AbNgD5GCbWuui9oP"
},
{
"type": "text",
"weight": 1,
"range": [
12,
12
],
"_id": "TCrdyh3qhl2vtQxO",
"name": "",
"img": "icons/svg/d12-grey.svg",
"description": "<p>Investigate a situation to confirm or deny existing information.</p>",
"drawn": false,
"flags": {},
"_stats": {
"compendiumSource": null,
"duplicateSource": null,
"exportSource": null,
"coreVersion": "13.351",
"systemId": "daggerheart",
"systemVersion": "1.4.4",
"lastModifiedBy": null
},
"documentUuid": null,
"_key": "!tables.results!I5L1dlgxXTNrCCkL.TCrdyh3qhl2vtQxO"
}
],
"replacement": true,
"displayRoll": true,
"folder": null,
"ownership": {
"default": 0,
"Bgvu4A6AMkRFOTGR": 3
},
"flags": {},
"formula": "1d12",
"_id": "I5L1dlgxXTNrCCkL",
"sort": 400000,
"_key": "!tables!I5L1dlgxXTNrCCkL"
}

View file

@ -35,7 +35,7 @@
"key": "system.evasion", "key": "system.evasion",
"mode": 2, "mode": 2,
"value": "@system.proficiency", "value": "@system.proficiency",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -255,13 +255,13 @@
"key": "system.damageThresholds.major", "key": "system.damageThresholds.major",
"mode": 2, "mode": 2,
"value": "@system.proficiency", "value": "@system.proficiency",
"priority": null "priority": 21
}, },
{ {
"key": "system.damageThresholds.severe", "key": "system.damageThresholds.severe",
"mode": 2, "mode": 2,
"value": "@system.proficiency", "value": "@system.proficiency",
"priority": null "priority": 21
} }
], ],
"disabled": false, "disabled": false,

View file

@ -145,6 +145,11 @@
button { button {
flex: 1; flex: 1;
padding: 0 0.375rem;
}
button[data-action=viewParty] {
margin-right: 6px;
} }
} }

View file

@ -99,12 +99,35 @@
} }
} }
.action-use-button-parent {
width: 100%;
.action-use-target {
display:flex;
align-items: center;
justify-content: space-between;
gap: 4px;
width: 100%;
padding: 4px 8px 10px 40px;
font-size: var(--font-size-12);
label {
font-weight: bold;
}
select {
flex: 1;
}
}
}
.action-use-button { .action-use-button {
width: -webkit-fill-available; width: -webkit-fill-available;
margin: 0 8px; margin: 0 8px;
font-weight: 600; font-weight: 600;
height: 40px; height: 40px;
} }
} }
} }
} }

View file

@ -344,4 +344,20 @@ aside[role='tooltip'].locked-tooltip:has(div.daggerheart.dh-style.tooltip.card-s
margin-bottom: 4px; margin-bottom: 4px;
} }
} }
.party-list {
display: flex;
flex-direction: column;
button {
width: 100%;
align-items: center;
justify-content: start;
img {
border: none;
width: 1rem;
height: 1rem;
object-fit: contain;
}
}
}
} }

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": "1.4.3", "version": "1.4.6",
"compatibility": { "compatibility": {
"minimum": "13.346", "minimum": "13.346",
"verified": "13.351", "verified": "13.351",
@ -173,6 +173,15 @@
"private": false, "private": false,
"flags": {} "flags": {}
}, },
{
"name": "rolltables",
"label": "Rolltables",
"system": "daggerheart",
"path": "packs/rolltables.db",
"type": "RollTable",
"private": false,
"flags": {}
},
{ {
"name": "beastforms", "name": "beastforms",
"label": "Beastforms", "label": "Beastforms",
@ -188,7 +197,7 @@
"name": "Daggerheart SRD", "name": "Daggerheart SRD",
"sorting": "m", "sorting": "m",
"color": "#08718c", "color": "#08718c",
"packs": ["adversaries", "environments", "journals"], "packs": ["adversaries", "environments", "journals", "rolltables"],
"folders": [ "folders": [
{ {
"name": "Character Options", "name": "Character Options",

View file

@ -87,11 +87,16 @@
</div> </div>
{{/if}} {{/if}}
<div class="downtime-section"> <div class="downtime-section">
<button type="button" data-action="useDowntime" data-type="shortRest" data-tooltip="{{localize "DAGGERHEART.APPLICATIONS.Downtime.shortRest.title"}}"> {{#if document.parties.size}}
<i class="fa-solid fa-utensils"></i> <button type="button" data-action="viewParty" data-tooltip="DAGGERHEART.ACTORS.Character.viewParty">
<i class="fa-solid fa-fw fa-users"></i>
</button>
{{/if}}
<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>
</button> </button>
<button type="button" data-action="useDowntime" data-type="longRest" data-tooltip="{{localize "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-bed"></i> <i class="fa-solid fa-fw fa-bed"></i>
</button> </button>
</div> </div>
</div> </div>

View file

@ -15,9 +15,22 @@
</div> </div>
</details> </details>
{{#each move.actions as | action index |}} {{#each move.actions as | action index |}}
<button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}" data-move-path="{{../movePath}}" > <div class="action-use-button-parent">
<span>{{localize action.name}}</span> <button class="action-use-button" data-move-index="{{@../key}}" data-action-index="{{index}}" data-move-path="{{../movePath}}" >
</button> <span>{{localize action.name}}</span>
</button>
{{#if move.needsTarget}}
<div class="action-use-target">
<label>{{localize "DAGGERHEART.GENERAL.Bonuses.rest.target"}}:</label>
<select>
<option value="{{../../selfId}}">{{localize "DAGGERHEART.GENERAL.Bonuses.rest.targetSelf"}}</option>
{{#each ../../characters as | character |}}
<option value="{{character.uuid}}">{{character.name}}</option>
{{/each}}
</select>
</div>
{{/if}}
</div>
{{/each}} {{/each}}
{{/each}} {{/each}}
</ul> </ul>