This commit is contained in:
WBHarry 2026-03-08 13:59:08 +01:00
parent f1f5102af1
commit c6bf482b07
12 changed files with 280 additions and 94 deletions

View file

@ -97,6 +97,30 @@ Hooks.once('init', () => {
fields fields
}; };
CONFIG.DH.ACTOR.characterResources.corruption = {
id: 'corruption',
initial: 0,
max: 4,
reverse: false,
label: 'Corruption'
};
CONFIG.DH.ACTOR.characterResources.hunger = {
id: 'hunger',
initial: 0,
max: 6,
reverse: false,
label: 'Hunger'
};
CONFIG.DH.ACTOR.characterResources.glitched = {
id: 'glitched',
initial: 0,
max: 6,
reverse: false,
label: 'Glitched'
};
game.system.registeredTriggers = new game.system.api.data.RegisteredTriggers(); game.system.registeredTriggers = new game.system.api.data.RegisteredTriggers();
const { DocumentSheetConfig } = foundry.applications.apps; const { DocumentSheetConfig } = foundry.applications.apps;

View file

@ -33,6 +33,7 @@ export default class CharacterSheet extends DHBaseActorSheet {
handleResourceDice: CharacterSheet.#handleResourceDice, handleResourceDice: CharacterSheet.#handleResourceDice,
advanceResourceDie: CharacterSheet.#advanceResourceDie, advanceResourceDie: CharacterSheet.#advanceResourceDie,
cancelBeastform: CharacterSheet.#cancelBeastform, cancelBeastform: CharacterSheet.#cancelBeastform,
toggleResourceManagement: CharacterSheet.#toggleResourceManagement,
useDowntime: this.useDowntime, useDowntime: this.useDowntime,
viewParty: CharacterSheet.#viewParty viewParty: CharacterSheet.#viewParty
}, },
@ -942,6 +943,71 @@ export default class CharacterSheet extends DHBaseActorSheet {
}); });
} }
static async #toggleResourceManagement(_event, button) {
const existingTooltip = document.body.querySelector('.locked-tooltip .resource-management-container');
if (existingTooltip) {
game.tooltip.dismissLockedTooltips();
return;
}
const extraResources = Object.values(CONFIG.DH.ACTOR.characterResources).reduce((acc, resource) => {
if (CONFIG.DH.ACTOR.characterBaseResources[resource.id]) return acc;
const resourceData = this.document.system.resources[resource.id];
acc[resource.id] = {
id: resource.id,
label: game.i18n.localize(resource.label),
value: resourceData.value,
max: resourceData.max
};
return acc;
}, {});
const html = document.createElement('div');
html.innerHTML = await foundry.applications.handlebars.renderTemplate(
`systems/daggerheart/templates/ui/tooltip/resourceManagement.hbs`,
{
resources: extraResources
}
);
const target = button.closest('.resource-section');
game.tooltip.dismissLockedTooltips();
game.tooltip.activate(target, {
html,
locked: true,
cssClass: 'bordered-tooltip',
direction: 'DOWN'
});
for (const element of html.querySelectorAll('.resource-value'))
element.addEventListener('click', CharacterSheet.resourceUpdate.bind(this));
}
static async resourceUpdate(event) {
const target = event.target.closest('.resource-value');
const { resource, value: textValue } = target.dataset;
const inputValue = Number.parseInt(textValue);
const decreasing = inputValue <= this.document.system.resources[resource].value;
const value = decreasing ? inputValue - 1 : inputValue;
await this.document.update({ [`system.resources.${resource}.value`]: value });
/* Update resource symbols */
const section = target.closest('.resource-section');
for (const element of section.querySelectorAll('.resource-value')) {
if (Number.parseInt(element.dataset.value) <= value) {
element.querySelector('.fa-diamond').classList.remove('hidden');
element.querySelector('.fa-circle').classList.add('hidden');
} else {
element.querySelector('.fa-diamond').classList.add('hidden');
element.querySelector('.fa-circle').classList.remove('hidden');
}
}
}
/** /**
* Open the downtime application. * Open the downtime application.
* @type {ApplicationClickAction} * @type {ApplicationClickAction}

View file

@ -55,24 +55,47 @@ export const abilities = {
} }
}; };
export const scrollingTextResource = { const baseResources = {
hitPoints: { hitPoints: {
id: 'hitPoints',
initial: 0,
max: 0,
reverse: true,
label: 'DAGGERHEART.GENERAL.HitPoints.plural', label: 'DAGGERHEART.GENERAL.HitPoints.plural',
reversed: true maxLabel: 'DAGGERHEART.ACTORS.Character.maxHPBonus'
}, },
stress: { stress: {
label: 'DAGGERHEART.GENERAL.stress', id: 'stress',
reversed: true initial: 0,
max: 6,
reverse: true,
label: 'DAGGERHEART.GENERAL.stress'
}, },
hope: { hope: {
id: 'hope',
initial: 2,
min: 0,
reverse: false,
label: 'DAGGERHEART.GENERAL.hope' label: 'DAGGERHEART.GENERAL.hope'
},
armor: {
label: 'DAGGERHEART.GENERAL.armor',
reversed: true
} }
}; };
export const characterBaseResources = {
...baseResources
};
export const characterResources = {
...characterBaseResources
};
export const getScrollingTextResources = actorType => ({
armor: {
label: 'DAGGERHEART.GENERAL.armor',
reverse: true
},
...(actorType === 'character' ? characterResources : {})
});
export const featureProperties = { export const featureProperties = {
agility: { agility: {
name: 'DAGGERHEART.CONFIG.Traits.agility.name', name: 'DAGGERHEART.CONFIG.Traits.agility.name',
@ -518,7 +541,7 @@ export const adversaryScalingData = {
severeThreshold: 10, severeThreshold: 10,
hp: 1, hp: 1,
stress: 2, stress: 2,
attack: 2, attack: 2
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -526,7 +549,7 @@ export const adversaryScalingData = {
severeThreshold: 15, severeThreshold: 15,
hp: 1, hp: 1,
stress: 0, stress: 0,
attack: 2, attack: 2
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -534,7 +557,7 @@ export const adversaryScalingData = {
severeThreshold: 25, severeThreshold: 25,
hp: 1, hp: 1,
stress: 0, stress: 0,
attack: 2, attack: 2
} }
}, },
horde: { horde: {
@ -544,7 +567,7 @@ export const adversaryScalingData = {
severeThreshold: 8, severeThreshold: 8,
hp: 2, hp: 2,
stress: 0, stress: 0,
attack: 0, attack: 0
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -552,7 +575,7 @@ export const adversaryScalingData = {
severeThreshold: 12, severeThreshold: 12,
hp: 0, hp: 0,
stress: 1, stress: 1,
attack: 1, attack: 1
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -560,7 +583,7 @@ export const adversaryScalingData = {
severeThreshold: 15, severeThreshold: 15,
hp: 2, hp: 2,
stress: 0, stress: 0,
attack: 0, attack: 0
} }
}, },
leader: { leader: {
@ -570,7 +593,7 @@ export const adversaryScalingData = {
severeThreshold: 10, severeThreshold: 10,
hp: 0, hp: 0,
stress: 0, stress: 0,
attack: 1, attack: 1
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -578,7 +601,7 @@ export const adversaryScalingData = {
severeThreshold: 15, severeThreshold: 15,
hp: 1, hp: 1,
stress: 0, stress: 0,
attack: 2, attack: 2
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -586,7 +609,7 @@ export const adversaryScalingData = {
severeThreshold: 25, severeThreshold: 25,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 3, attack: 3
} }
}, },
minion: { minion: {
@ -596,7 +619,7 @@ export const adversaryScalingData = {
severeThreshold: 0, severeThreshold: 0,
hp: 0, hp: 0,
stress: 0, stress: 0,
attack: 1, attack: 1
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -604,7 +627,7 @@ export const adversaryScalingData = {
severeThreshold: 0, severeThreshold: 0,
hp: 0, hp: 0,
stress: 1, stress: 1,
attack: 1, attack: 1
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -612,7 +635,7 @@ export const adversaryScalingData = {
severeThreshold: 0, severeThreshold: 0,
hp: 0, hp: 0,
stress: 0, stress: 0,
attack: 1, attack: 1
} }
}, },
ranged: { ranged: {
@ -622,7 +645,7 @@ export const adversaryScalingData = {
severeThreshold: 6, severeThreshold: 6,
hp: 1, hp: 1,
stress: 0, stress: 0,
attack: 1, attack: 1
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -630,7 +653,7 @@ export const adversaryScalingData = {
severeThreshold: 14, severeThreshold: 14,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 2, attack: 2
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -638,7 +661,7 @@ export const adversaryScalingData = {
severeThreshold: 10, severeThreshold: 10,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
} }
}, },
skulk: { skulk: {
@ -648,7 +671,7 @@ export const adversaryScalingData = {
severeThreshold: 8, severeThreshold: 8,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -656,7 +679,7 @@ export const adversaryScalingData = {
severeThreshold: 12, severeThreshold: 12,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -664,7 +687,7 @@ export const adversaryScalingData = {
severeThreshold: 10, severeThreshold: 10,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
} }
}, },
solo: { solo: {
@ -674,7 +697,7 @@ export const adversaryScalingData = {
severeThreshold: 10, severeThreshold: 10,
hp: 0, hp: 0,
stress: 1, stress: 1,
attack: 2, attack: 2
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -682,7 +705,7 @@ export const adversaryScalingData = {
severeThreshold: 15, severeThreshold: 15,
hp: 2, hp: 2,
stress: 1, stress: 1,
attack: 2, attack: 2
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -690,7 +713,7 @@ export const adversaryScalingData = {
severeThreshold: 25, severeThreshold: 25,
hp: 0, hp: 0,
stress: 1, stress: 1,
attack: 3, attack: 3
} }
}, },
standard: { standard: {
@ -700,7 +723,7 @@ export const adversaryScalingData = {
severeThreshold: 8, severeThreshold: 8,
hp: 0, hp: 0,
stress: 0, stress: 0,
attack: 1, attack: 1
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -708,7 +731,7 @@ export const adversaryScalingData = {
severeThreshold: 15, severeThreshold: 15,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -716,7 +739,7 @@ export const adversaryScalingData = {
severeThreshold: 15, severeThreshold: 15,
hp: 0, hp: 0,
stress: 1, stress: 1,
attack: 1, attack: 1
} }
}, },
support: { support: {
@ -726,7 +749,7 @@ export const adversaryScalingData = {
severeThreshold: 8, severeThreshold: 8,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
}, },
3: { 3: {
difficulty: 2, difficulty: 2,
@ -734,7 +757,7 @@ export const adversaryScalingData = {
severeThreshold: 12, severeThreshold: 12,
hp: 0, hp: 0,
stress: 0, stress: 0,
attack: 1, attack: 1
}, },
4: { 4: {
difficulty: 2, difficulty: 2,
@ -742,7 +765,7 @@ export const adversaryScalingData = {
severeThreshold: 10, severeThreshold: 10,
hp: 1, hp: 1,
stress: 1, stress: 1,
attack: 1, attack: 1
} }
} }
}; };
@ -753,16 +776,16 @@ export const adversaryScalingData = {
* We manually set tier 4 data to hopefully lead to better results * We manually set tier 4 data to hopefully lead to better results
*/ */
export const adversaryExpectedDamage = { export const adversaryExpectedDamage = {
basic: { basic: {
1: { mean: 7.321428571428571, deviation: 1.962519002770912 }, 1: { mean: 7.321428571428571, deviation: 1.962519002770912 },
2: { mean: 12.444444444444445, deviation: 2.0631069425529676 }, 2: { mean: 12.444444444444445, deviation: 2.0631069425529676 },
3: { mean: 15.722222222222221, deviation: 2.486565208464823 }, 3: { mean: 15.722222222222221, deviation: 2.486565208464823 },
4: { mean: 26, deviation: 5.2 } 4: { mean: 26, deviation: 5.2 }
}, },
minion: { minion: {
1: { mean: 2.142857142857143, deviation: 1.0690449676496976 }, 1: { mean: 2.142857142857143, deviation: 1.0690449676496976 },
2: { mean: 5, deviation: 0.816496580927726 }, 2: { mean: 5, deviation: 0.816496580927726 },
3: { mean: 6.5, deviation: 2.1213203435596424 }, 3: { mean: 6.5, deviation: 2.1213203435596424 },
4: { mean: 11, deviation: 1 } 4: { mean: 11, deviation: 1 }
} }
}; };

View file

@ -213,7 +213,7 @@ export default class BaseDataActor extends foundry.abstract.TypeDataModel {
const textData = Object.keys(changes.system.resources).reduce((acc, key) => { const textData = Object.keys(changes.system.resources).reduce((acc, key) => {
const resource = changes.system.resources[key]; const resource = changes.system.resources[key];
if (resource.value !== undefined && resource.value !== this.resources[key].value) { if (resource.value !== undefined && resource.value !== this.resources[key].value) {
acc.push(getScrollTextData(this.resources, resource, key)); acc.push(getScrollTextData(this.resources, resource, key, this.parent.type));
} }
return acc; return acc;

View file

@ -28,26 +28,32 @@ export default class DhCharacter extends DhCreature {
return { return {
...super.defineSchema(), ...super.defineSchema(),
resources: new fields.SchemaField({ resources: new fields.SchemaField({
hitPoints: resourceField( ...Object.values(CONFIG.DH.ACTOR.characterResources).reduce((acc, resource) => {
0, if (resource.max !== undefined) {
0, acc[resource.id] = resourceField(
'DAGGERHEART.GENERAL.HitPoints.plural', resource.max,
true, resource.initial,
'DAGGERHEART.ACTORS.Character.maxHPBonus' resource.label,
), resource.reverse,
stress: resourceField(6, 0, 'DAGGERHEART.GENERAL.stress', true), resource.maxLabel
hope: new fields.SchemaField( );
{ } else {
value: new fields.NumberField({ acc[resource.id] = new fields.SchemaField(
initial: 2, {
min: 0, value: new fields.NumberField({
integer: true, initial: resource.initial,
label: 'DAGGERHEART.GENERAL.hope' min: resource.min,
}), integer: true,
isReversed: new fields.BooleanField({ initial: false }) label: resource.label
}, }),
{ label: 'DAGGERHEART.GENERAL.hope' } isReversed: new fields.BooleanField({ initial: resource.reverse })
) },
{ label: resource.label }
);
}
return acc;
}, {})
}), }),
traits: new fields.SchemaField({ traits: new fields.SchemaField({
agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'), agility: attributeField('DAGGERHEART.CONFIG.Traits.agility.name'),

View file

@ -224,7 +224,12 @@ export default class BaseDataItem extends foundry.abstract.TypeDataModel {
const armorChanged = const armorChanged =
changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value; changed.system?.marks?.value !== undefined && changed.system.marks.value !== this.marks.value;
if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') { if (armorChanged && autoSettings.resourceScrollTexts && this.parent.parent?.type === 'character') {
const armorData = getScrollTextData(this.parent.parent.system.resources, changed.system.marks, 'armor'); const armorData = getScrollTextData(
this.parent.parent.system.resources,
changed.system.marks,
'armor',
this.parent.parent.type
);
options.scrollingTextData = [armorData]; options.scrollingTextData = [armorData];
} }

View file

@ -378,17 +378,17 @@ export const arraysEqual = (a, b) =>
export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value)); export const setsEqual = (a, b) => a.size === b.size && [...a].every(value => b.has(value));
export function getScrollTextData(resources, resource, key) { export function getScrollTextData(resources, resource, key, actorType) {
const { reversed, label } = CONFIG.DH.ACTOR.scrollingTextResource[key]; const { reverse, label } = CONFIG.DH.ACTOR.getScrollingTextResources(actorType)[key];
const { BOTTOM, TOP } = CONST.TEXT_ANCHOR_POINTS; const { BOTTOM, TOP } = CONST.TEXT_ANCHOR_POINTS;
const increased = resources[key].value < resource.value; const increased = resources[key].value < resource.value;
const value = -1 * (resources[key].value - resource.value); const value = -1 * (resources[key].value - resource.value);
const text = `${game.i18n.localize(label)} ${value.signedString()}`; const text = `${game.i18n.localize(label)} ${value.signedString()}`;
const stroke = increased ? (reversed ? 0xffffff : 0x000000) : reversed ? 0x000000 : 0xffffff; const stroke = increased ? (reverse ? 0xffffff : 0x000000) : reverse ? 0x000000 : 0xffffff;
const fill = increased ? (reversed ? 0x0032b1 : 0xffe760) : reversed ? 0xffe760 : 0x0032b1; const fill = increased ? (reverse ? 0x0032b1 : 0xffe760) : reverse ? 0xffe760 : 0x0032b1;
const direction = increased ? (reversed ? BOTTOM : TOP) : reversed ? TOP : BOTTOM; const direction = increased ? (reverse ? BOTTOM : TOP) : reverse ? TOP : BOTTOM;
return { text, stroke, fill, direction }; return { text, stroke, fill, direction };
} }

View file

@ -133,8 +133,15 @@
padding: 0; padding: 0;
margin-bottom: 15px; margin-bottom: 15px;
.hope-section { .resource-section {
display: flex;
align-items: center;
gap: 4px;
margin-right: 20px; margin-right: 20px;
.resource-manager {
color: light-dark(@dark-blue, @golden);
}
} }
.downtime-section { .downtime-section {

View file

@ -4,3 +4,5 @@
@import './tooltip/domain-cards.less'; @import './tooltip/domain-cards.less';
@import './autocomplete/autocomplete.less'; @import './autocomplete/autocomplete.less';
@import './tooltip/resource-management.less';

View file

@ -0,0 +1,37 @@
.bordered-tooltip.locked-tooltip .daggerheart.resource-management-container {
display: flex;
flex-direction: column;
gap: 16px;
.resource-section {
position: relative;
display: flex;
gap: 10px;
background-color: light-dark(transparent, @dark-blue);
color: light-dark(@dark-blue, @golden);
padding: 5px 10px;
border: 1px solid light-dark(@dark-blue, @golden);
border-radius: 6px;
align-items: center;
width: fit-content;
height: 30px;
h4 {
font-family: var(--dh-font-body, 'Montserrat'), sans-serif;
font-size: var(--font-size-14);
font-weight: bold;
text-transform: uppercase;
color: light-dark(@dark-blue, @golden);
margin: 0;
}
.resource-value {
display: flex;
cursor: pointer;
.hidden {
display: none;
}
}
}
}

View file

@ -65,22 +65,25 @@
</div> </div>
<div class="character-row"> <div class="character-row">
<div class="hope-section"> <div class="resource-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4> <div class="hope-section">
{{#times document.system.resources.hope.max}} <h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
<span class='hope-value' data-action='toggleHope' data-value="{{add this 1}}"> {{#times document.system.resources.hope.max}}
{{#if (gte ../document.system.resources.hope.value (add this 1))}} <span class='hope-value' data-action='toggleHope' data-value="{{add this 1}}">
<i class='fa-solid fa-diamond'></i> {{#if (gte ../document.system.resources.hope.value (add this 1))}}
{{else}} <i class='fa-solid fa-diamond'></i>
<i class='fa-regular fa-circle'></i> {{else}}
{{/if}} <i class='fa-regular fa-circle'></i>
</span> {{/if}}
{{/times}} </span>
{{#times document.system.scars}} {{/times}}
<span class='hope-value scar'> {{#times document.system.scars}}
<i class='fa-regular fa-ban'></i> <span class='hope-value scar'>
</span> <i class='fa-regular fa-ban'></i>
{{/times}} </span>
{{/times}}
</div>
<a class="resource-manager" data-action="toggleResourceManagement"><i class="fa-solid fa-gear"></i></a>
</div> </div>
{{#if document.system.class.value}} {{#if document.system.class.value}}
<div class="domains-section"> <div class="domains-section">

View file

@ -0,0 +1,13 @@
<div class="daggerheart resource-management-container">
{{#each resources as |resource|}}
<div class="resource-section">
<h4>{{resource.label}}</h4>
{{#times resource.max}}
<span class='resource-value' data-action='toggleResource' data-value="{{add this 1}}" data-resource="{{resource.id}}">
<i class='fa-solid fa-diamond {{#unless (gte ../value (add this 1))}}hidden{{/unless}}'></i>
<i class='fa-regular fa-circle {{#if (gte ../value (add this 1))}}hidden{{/if}}'></i>
</span>
{{/times}}
</div>
{{/each}}
</div>