Merge branch 'main' into feature/Homebrew-Downtime-Actions

This commit is contained in:
WBHarry 2025-07-25 00:18:03 +02:00
commit 92a77fa42e
22 changed files with 250 additions and 125 deletions

View file

@ -14,7 +14,9 @@ jobs:
run: npm ci run: npm ci
- name: Build Packs - name: Build Packs
run: npm run pullYMLtoLDB run: |
npm run pullYMLtoLDB
mv --force src/packs/LICENSE packs/LICENSE
- name: Build daggerheart.js - name: Build daggerheart.js
run: npm run build run: npm run build

View file

@ -1695,7 +1695,8 @@
"makeDeathMove": "Make a Death Move", "makeDeathMove": "Make a Death Move",
"rangeAndTarget": "Range & Target", "rangeAndTarget": "Range & Target",
"dragApplyEffect": "Drag effect to apply it to an actor", "dragApplyEffect": "Drag effect to apply it to an actor",
"appliedEvenIfSuccessful": "Applied even if save succeeded" "appliedEvenIfSuccessful": "Applied even if save succeeded",
"diceIsRerolled": "The dice has been rerolled (x{times})"
} }
} }
} }

View file

@ -47,13 +47,13 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
element.addEventListener('click', this.onToggleTargets) element.addEventListener('click', this.onToggleTargets)
); );
html.querySelectorAll('.ability-use-button').forEach(element => html.querySelectorAll('.ability-use-button').forEach(element =>
element.addEventListener('click', event => this.abilityUseButton(this, event, data.message)) element.addEventListener('click', event => this.abilityUseButton(event, data.message))
); );
html.querySelectorAll('.action-use-button').forEach(element => html.querySelectorAll('.action-use-button').forEach(element =>
element.addEventListener('click', event => this.actionUseButton(this, event, data.message)) element.addEventListener('click', event => this.actionUseButton(event, data.message))
); );
html.querySelectorAll('.reroll-button').forEach(element => html.querySelectorAll('.reroll-button').forEach(element =>
element.addEventListener('click', event => this.rerollEvent(this, event, data.message)) element.addEventListener('click', event => this.rerollEvent(event, data.message))
); );
}; };
@ -271,6 +271,7 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
} }
async rerollEvent(event, message) { async rerollEvent(event, message) {
event.stopPropagation();
if (!event.shiftKey) { if (!event.shiftKey) {
const confirmed = await foundry.applications.api.DialogV2.confirm({ const confirmed = await foundry.applications.api.DialogV2.confirm({
window: { window: {
@ -287,6 +288,9 @@ export default class DhpChatLog extends foundry.applications.sidebar.tabs.ChatLo
game.system.api.dice[ game.system.api.dice[
message.type === 'dualityRoll' ? 'DualityRoll' : target.dataset.type === 'damage' ? 'DHRoll' : 'D20Roll' message.type === 'dualityRoll' ? 'DualityRoll' : target.dataset.type === 'damage' ? 'DHRoll' : 'D20Roll'
]; ];
if (!game.modules.get('dice-so-nice')?.active) foundry.audio.AudioHelper.play({ src: CONFIG.sounds.dice });
const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message); const { newRoll, parsedRoll } = await rollClass.reroll(originalRoll_parsed, target, message);
await game.messages.get(message._id).update({ await game.messages.get(message._id).update({

View file

@ -1,4 +1,5 @@
import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs'; import D20RollDialog from '../applications/dialogs/d20RollDialog.mjs';
import { getDiceSoNicePresets } from '../config/generalConfig.mjs';
import DHRoll from './dhRoll.mjs'; import DHRoll from './dhRoll.mjs';
export default class D20Roll extends DHRoll { export default class D20Roll extends DHRoll {
@ -156,6 +157,14 @@ export default class D20Roll extends DHRoll {
dice: roll.dAdvantage?.denomination, dice: roll.dAdvantage?.denomination,
value: roll.dAdvantage?.total value: roll.dAdvantage?.total
}; };
data.dice = data.dice.map(dice => ({
...dice,
results: dice.results.filter(x => !x.rerolled),
rerolled: {
any: dice.results.some(x => x.rerolled),
rerolls: dice.results.filter(x => x.rerolled)
}
}));
data.isCritical = roll.isCritical; data.isCritical = roll.isCritical;
data.extra = roll.dice data.extra = roll.dice
.filter(d => !roll.baseTerms.includes(d)) .filter(d => !roll.baseTerms.includes(d))
@ -188,6 +197,26 @@ export default class D20Roll extends DHRoll {
await game.dice3d.showForRoll(parsedRoll, game.user, true); await game.dice3d.showForRoll(parsedRoll, game.user, true);
} }
return { newRoll, parsedRoll }; const rerolled = {
any: true,
rerolls: [
...(message.system.roll.dice[0].rerolled?.rerolls?.length > 0
? [message.system.roll.dice[0].rerolled?.rerolls]
: []),
rollString.terms[0].results
]
};
return {
newRoll: {
...newRoll,
dice: [
{
...newRoll.dice[0],
rerolled: rerolled
}
]
},
parsedRoll
};
} }
} }

View file

@ -193,6 +193,11 @@ export const registerRollDiceHooks = () => {
if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 }); if (config.roll.isCritical) updates.push({ key: 'stress', value: -1 });
if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 }); if (config.roll.result.duality === -1) updates.push({ key: 'fear', value: 1 });
if (config.rerolledRoll.isCritical || config.rerolledRoll.result.duality === 1)
updates.push({ key: 'hope', value: -1 });
if (config.rerolledRoll.isCritical) updates.push({ key: 'stress', value: 1 });
if (config.rerolledRoll.result.duality === -1) updates.push({ key: 'fear', value: -1 });
if (updates.length) { if (updates.length) {
const target = actor.system.partner ?? actor; const target = actor.system.partner ?? actor;
if (!['dead', 'unconcious'].some(x => actor.statuses.has(x))) { if (!['dead', 'unconcious'].some(x => actor.statuses.has(x))) {

View file

@ -171,11 +171,19 @@ export default class DualityRoll extends D20Roll {
data.hope = { data.hope = {
dice: roll.dHope.denomination, dice: roll.dHope.denomination,
value: roll.dHope.total value: roll.dHope.total,
rerolled: {
any: roll.dHope.results.some(x => x.rerolled),
rerolls: roll.dHope.results.filter(x => x.rerolled)
}
}; };
data.fear = { data.fear = {
dice: roll.dFear.denomination, dice: roll.dFear.denomination,
value: roll.dFear.total value: roll.dFear.total,
rerolled: {
any: roll.dFear.results.some(x => x.rerolled),
rerolls: roll.dFear.results.filter(x => x.rerolled)
}
}; };
data.rally = { data.rally = {
dice: roll.dRally?.denomination, dice: roll.dRally?.denomination,
@ -232,6 +240,13 @@ export default class DualityRoll extends D20Roll {
}); });
newRoll.extra = newRoll.extra.slice(2); newRoll.extra = newRoll.extra.slice(2);
Hooks.call(`${CONFIG.DH.id}.postRollDuality`, {
source: { actor: message.system.source.actor ?? '' },
targets: message.system.targets,
roll: newRoll,
rerolledRoll:
newRoll.result.duality !== message.system.roll.result.duality ? message.system.roll : undefined
});
return { newRoll, parsedRoll }; return { newRoll, parsedRoll };
} }
} }

7
src/packs/LICENSE Normal file
View file

@ -0,0 +1,7 @@
This product includes materials from the
Daggerheart System Reference Document 1.0, ©
Critical Role, LLC. under the terms of the
Darrington Press Community Gaming (DPCGL)
License. More information can be found at
https://www.daggerheart.com/. There are no
previous modifications by others.

View file

@ -0,0 +1,19 @@
@import '../../../utils/colors.less';
@import '../../../utils/fonts.less';
.application.sheet.daggerheart.actor.dh-style.character {
.tab.effects {
.effects-sections {
display: flex;
flex-direction: column;
gap: 10px;
overflow-y: auto;
mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
padding: 20px 0;
padding-top: 10px;
scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent;
}
}
}

View file

@ -11,7 +11,6 @@
mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
padding: 20px 0; padding: 20px 0;
padding-top: 10px; padding-top: 10px;
height: 84%;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-color: light-dark(@dark-blue, @golden) transparent;

View file

@ -56,7 +56,6 @@
overflow-y: auto; overflow-y: auto;
mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%); mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
padding: 20px 0; padding: 20px 0;
height: 73%;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-color: light-dark(@dark-blue, @golden) transparent;

View file

@ -97,7 +97,6 @@
overflow-y: auto; overflow-y: auto;
mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%); mask-image: linear-gradient(0deg, transparent 0%, black 10%, black 98%, transparent 100%);
padding: 20px 0; padding: 20px 0;
height: 84%;
scrollbar-width: thin; scrollbar-width: thin;
scrollbar-color: light-dark(@dark-blue, @golden) transparent; scrollbar-color: light-dark(@dark-blue, @golden) transparent;

View file

@ -11,6 +11,7 @@
width: 100%; width: 100%;
padding-bottom: 0; padding-bottom: 0;
overflow-x: auto; overflow-x: auto;
margin-bottom: 0;
.character-sidebar-sheet { .character-sidebar-sheet {
grid-row: 1 / span 2; grid-row: 1 / span 2;
@ -25,6 +26,11 @@
.tab { .tab {
grid-row: 2; grid-row: 2;
grid-column: 2; grid-column: 2;
&.active {
display: flex;
flex-direction: column;
overflow: hidden;
}
} }
} }
} }

View file

@ -63,7 +63,7 @@
flex-direction: column; flex-direction: column;
top: -20px; top: -20px;
gap: 30px; gap: 30px;
margin-bottom: -10px; margin-bottom: -16px;
.resources-section { .resources-section {
display: flex; display: flex;
@ -357,54 +357,39 @@
} }
} }
.equipment-section { .shortcut-items-section {
.title { overflow-y: hidden;
display: flex; max-height: 56%;
gap: 15px; padding-top: 16px;
align-items: center; padding-bottom: 20px;
mask-image: linear-gradient(0deg, transparent 0%, black 5%, black 95%, transparent 100%);
h3 { &:hover {
font-size: 20px; overflow-y: auto;
} scrollbar-width: thin;
} scrollbar-color: light-dark(@dark-blue, @golden) transparent;
.items-list {
display: flex;
flex-direction: column;
gap: 10px;
align-items: center;
} }
} }
.loadout-section { .equipment-section,
.loadout-section,
.experience-section {
.title { .title {
display: flex; .section-title();
gap: 15px; }
align-items: center; }
h3 { .equipment-section {
font-size: 20px; .items-list {
} .column-list(10px);
} }
} }
.experience-section { .experience-section {
.title {
display: flex;
gap: 15px;
align-items: center;
h3 {
font-size: 20px;
}
}
.experience-list { .experience-list {
display: flex; .column-list(5px);
flex-direction: column;
gap: 5px;
width: 100%; width: 100%;
margin-top: 10px; margin-top: 10px;
align-items: center;
.experience-row { .experience-row {
display: flex; display: flex;

View file

@ -4,6 +4,7 @@
@import './actors/adversary/sidebar.less'; @import './actors/adversary/sidebar.less';
@import './actors/character/biography.less'; @import './actors/character/biography.less';
@import './actors/character/effects.less';
@import './actors/character/features.less'; @import './actors/character/features.less';
@import './actors/character/header.less'; @import './actors/character/header.less';
@import './actors/character/inventory.less'; @import './actors/character/inventory.less';

View file

@ -62,6 +62,17 @@
} }
&.rerollable { &.rerollable {
position: relative;
flex: none;
.dice-rerolled {
z-index: 2;
position: absolute;
right: 0;
font-size: 12px;
cursor: help;
}
.reroll-button { .reroll-button {
border: none; border: none;
background: initial; background: initial;
@ -85,12 +96,21 @@
display: flex; display: flex;
flex-direction: column; flex-direction: column;
gap: 2px; gap: 2px;
position: relative;
.dice-title { .dice-title {
color: var(--color-light-1); color: var(--color-light-1);
text-shadow: 0 0 1px black; text-shadow: 0 0 1px black;
} }
.dice-rerolled {
z-index: 2;
position: absolute;
right: -2px;
font-size: 12px;
cursor: help;
}
.dice-inner-container { .dice-inner-container {
display: flex; display: flex;
align-items: center; align-items: center;

View file

@ -20,3 +20,27 @@
@lightRules(); @lightRules();
} }
} }
/**
* Apply a style to sidebar titles.
*/
.section-title() {
display: flex;
gap: 15px;
align-items: center;
h3 {
font-size: 20px;
}
}
/**
* Apply default item list style.
* @param {Length} @gap - The vertical spacing between elements (e.g., 10px, 1rem)
*/
.column-list(@gap: 10px) {
display: flex;
flex-direction: column;
gap: @gap;
align-items: center;
}

View file

@ -1,7 +1,7 @@
<div class="ownership-outer-container"> <div class="ownership-outer-container">
<div class="form-group"> <div class="form-group">
<div class="form-fields"> <div class="form-fields">
<label>{{localize "DAGGERHEART.APPLICATIONS.OwnershipSelection.Default"}}</label> <label>{{localize "DAGGERHEART.APPLICATIONS.OwnershipSelection.default"}}</label>
<select name="ownership.default" data-dtype="Number"> <select name="ownership.default" data-dtype="Number">
{{selectOptions @root.ownershipOptions selected=ownership.default labelAttr="label" valueAttr="value" }} {{selectOptions @root.ownershipOptions selected=ownership.default labelAttr="label" valueAttr="value" }}
</select> </select>

View file

@ -1,21 +1,23 @@
<section class='tab {{tabs.effects.cssClass}} {{tabs.effects.id}}' data-tab='{{tabs.effects.id}}' <section class='tab {{tabs.effects.cssClass}} {{tabs.effects.id}}' data-tab='{{tabs.effects.id}}'
data-group='{{tabs.effects.group}}'> data-group='{{tabs.effects.group}}'>
{{> 'daggerheart.inventory-items' <div class="effects-sections">
title='DAGGERHEART.GENERAL.activeEffects' {{> 'daggerheart.inventory-items'
type='effect' title='DAGGERHEART.GENERAL.activeEffects'
isGlassy=true type='effect'
collection=effects.actives isGlassy=true
canCreate=true collection=effects.actives
hideResources=true canCreate=true
}} hideResources=true
}}
{{> 'daggerheart.inventory-items' {{> 'daggerheart.inventory-items'
title='DAGGERHEART.GENERAL.inactiveEffects' title='DAGGERHEART.GENERAL.inactiveEffects'
type='effect' type='effect'
isGlassy=true isGlassy=true
collection=effects.inactives collection=effects.inactives
canCreate=true canCreate=true
hideResources=true hideResources=true
}} }}
</div>
</section> </section>

View file

@ -88,71 +88,74 @@
</div> </div>
</div> </div>
</div> </div>
<div class="equipment-section">
<div class="title"> <div class="shortcut-items-section">
<side-line-div class="invert"></side-line-div> <div class="equipment-section">
<h3>{{localize "DAGGERHEART.GENERAL.equipment"}}</h3> <div class="title">
<side-line-div></side-line-div> <side-line-div class="invert"></side-line-div>
</div> <h3>{{localize "DAGGERHEART.GENERAL.equipment"}}</h3>
<ul class="items-sidebar-list"> <side-line-div></side-line-div>
{{#if document.system.usedUnarmed}} </div>
{{> 'daggerheart.inventory-item' item=document.system.usedUnarmed type='attack' isSidebar=true}} <ul class="items-sidebar-list">
{{/if}} {{#if document.system.usedUnarmed}}
{{#each document.items as |item|}} {{> 'daggerheart.inventory-item' item=document.system.usedUnarmed type='attack' isSidebar=true}}
{{#if item.system.equipped}} {{/if}}
{{> 'daggerheart.inventory-item' {{#each document.items as |item|}}
item=item {{#if item.system.equipped}}
type=item.type {{> 'daggerheart.inventory-item'
hideTags=true item=item
hideDescription=true type=item.type
hideResources=true
noExtensible=true
}}
{{/if}}
{{/each}}
</ul>
</div>
<div class="loadout-section">
<div class="title">
<side-line-div class="invert"></side-line-div>
<h3>{{localize "DAGGERHEART.GENERAL.loadout"}}</h3>
<side-line-div></side-line-div>
</div>
<ul class="items-sidebar-list">
{{#each document.system.domainCards.loadout as |card|}}
{{> 'daggerheart.inventory-item'
item=card
type='domainCard'
hideTags=true hideTags=true
hideDescription=true hideDescription=true
hideResources=true hideResources=true
noExtensible=true noExtensible=true
}} }}
{{/if}}
{{/each}} {{/each}}
</ul> </ul>
</div>
<div class="experience-section">
<div class="title">
<side-line-div class="invert"></side-line-div>
<h3>{{localize "DAGGERHEART.GENERAL.experience.single"}}</h3>
<side-line-div></side-line-div>
</div> </div>
<div class="experience-list"> <div class="loadout-section">
{{#each document.system.experiences as |experience id|}} <div class="title">
<div class="experience-row"> <side-line-div class="invert"></side-line-div>
<div class="experience-value"> <h3>{{localize "DAGGERHEART.GENERAL.loadout"}}</h3>
+{{experience.value}} <side-line-div></side-line-div>
</div> </div>
<input name="system.experiences.{{id}}.name" data-experience={{id}} <ul class="items-sidebar-list">
value="{{experience.name}}" type="text" /> {{#each document.system.domainCards.loadout as |card|}}
<div class="controls"> {{> 'daggerheart.inventory-item'
<a data-action="sendExpToChat" data-type="experience" data-id="{{id}}"> item=card
<i class="fa-regular fa-message"></i> type='domainCard'
</a> hideTags=true
</div> hideDescription=true
hideResources=true
noExtensible=true
}}
{{/each}}
</ul>
</div>
<div class="experience-section">
<div class="title">
<side-line-div class="invert"></side-line-div>
<h3>{{localize "DAGGERHEART.GENERAL.experience.single"}}</h3>
<side-line-div></side-line-div>
</div>
<div class="experience-list">
{{#each document.system.experiences as |experience id|}}
<div class="experience-row">
<div class="experience-value">
+{{experience.value}}
</div>
<input name="system.experiences.{{id}}.name" data-experience={{id}}
value="{{experience.name}}" type="text" />
<div class="controls">
<a data-action="sendExpToChat" data-type="experience" data-id="{{id}}">
<i class="fa-regular fa-message"></i>
</a>
</div>
</div>
{{/each}}
</div> </div>
{{/each}}
</div> </div>
</div> </div>
</aside> </aside>

View file

@ -221,7 +221,7 @@ Parameters:
{{else if (eq type 'effect')}} {{else if (eq type 'effect')}}
<a data-action="toggleEffect" <a data-action="toggleEffect"
data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.disabled 'enableEffect' 'disableEffect' }}"> data-tooltip="DAGGERHEART.UI.Tooltip.{{ifThen item.disabled 'enableEffect' 'disableEffect' }}">
<i class="{{ifThen item.disabled 'fa-regular fa-lightbulb' 'fa-solid fa-lightbulb'}}"></i> <i class="{{ifThen item.disabled 'fa-solid fa-toggle-off' 'fa-solid fa-toggle-on'}}"></i>
</a> </a>
{{/if}} {{/if}}
{{#if (hasProperty item "toChat")}} {{#if (hasProperty item "toChat")}}

View file

@ -13,6 +13,9 @@
</header> </header>
<div class="flexrow"> <div class="flexrow">
<ol class="dice-rolls rerollable"> <ol class="dice-rolls rerollable">
{{#if dice.rerolled.any}}
<i class="fa-solid fa-dice dice-rerolled" title="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=dice.rerolled.rerolls.length}}"></i>
{{/if}}
<button type="checkbox" class="reroll-button" data-die-index="0" data-tooltip="{{localize "DAGGERHEART.GENERAL.reroll"}}"> <button type="checkbox" class="reroll-button" data-die-index="0" data-tooltip="{{localize "DAGGERHEART.GENERAL.reroll"}}">
{{#each results as |result index|}} {{#each results as |result index|}}
<li class="roll die {{../dice}}{{#if discarded}} discarded{{/if}} min">{{result.result}}</li> <li class="roll die {{../dice}}{{#if discarded}} discarded{{/if}} min">{{result.result}}</li>

View file

@ -41,6 +41,7 @@
<li class="roll die {{roll.hope.dice}}"> <li class="roll die {{roll.hope.dice}}">
<div class="dice-container"> <div class="dice-container">
<div class="dice-title">{{localize "DAGGERHEART.GENERAL.hope"}}</div> <div class="dice-title">{{localize "DAGGERHEART.GENERAL.hope"}}</div>
{{#if roll.hope.rerolled.any}}<i class="fa-solid fa-dice dice-rerolled" title="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=roll.hope.rerolled.rerolls.length}}"></i>{{/if}}
<div class="dice-inner-container hope" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}"> <div class="dice-inner-container hope" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.hope")}}">
<button type="checkbox" class="reroll-button" data-die-index="0" data-type="hope"> <button type="checkbox" class="reroll-button" data-die-index="0" data-type="hope">
<div class="dice-wrapper"> <div class="dice-wrapper">
@ -54,6 +55,7 @@
<li class="roll die {{roll.fear.dice}}"> <li class="roll die {{roll.fear.dice}}">
<div class="dice-container"> <div class="dice-container">
<div class="dice-title">{{localize "DAGGERHEART.GENERAL.fear"}}</div> <div class="dice-title">{{localize "DAGGERHEART.GENERAL.fear"}}</div>
{{#if roll.fear.rerolled.any}}<i class="fa-solid fa-dice dice-rerolled" title="{{localize "DAGGERHEART.UI.Tooltip.diceIsRerolled" times=roll.fear.rerolled.rerolls.length}}"></i>{{/if}}
<div class="dice-inner-container fear" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}"> <div class="dice-inner-container fear" data-tooltip="{{localize "DAGGERHEART.GENERAL.rerollThing" thing=(localize "DAGGERHEART.GENERAL.fear")}}">
<button type="checkbox" class="reroll-button" data-die-index="2" data-type="fear"> <button type="checkbox" class="reroll-button" data-die-index="2" data-type="fear">
<div class="dice-wrapper"> <div class="dice-wrapper">