Add setting to hide party stats (#1861)

This commit is contained in:
Carlos Fernandez 2026-05-04 06:39:20 -04:00 committed by GitHub
parent e95ea3c281
commit 2ffe678503
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 212 additions and 142 deletions

View file

@ -2822,6 +2822,15 @@
"hideObserverPermissionInChat": {
"label": "Hide Chat Info From Players",
"hint": "Information such as hit/miss on attack rolls against adversaries will be hidden"
},
"hidePartyStats": {
"label": "Hide Party Stats",
"hint": "Resources and stats in the party sheet's member list will be hidden to the following users, even if the user is part of the same party",
"choices": {
"never": "Never, always show",
"players": "Hide From Players",
"always": "Hide from Everyone"
}
}
}
},

View file

@ -85,6 +85,14 @@ export default class Party extends DHBaseActorSheet {
/* Prepare Context */
/* -------------------------------------------- */
async _prepareContext(options) {
const context = await super._prepareContext(options);
const settings = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming);
context.showStats =
settings.hidePartyStats === 'never' || (settings.hidePartyStats === 'players' && game.user.isGM);
return context;
}
async _preparePartContext(partId, context, options) {
context = await super._preparePartContext(partId, context, options);
switch (partId) {

View file

@ -1,4 +1,5 @@
import { defaultRestOptions } from '../../config/generalConfig.mjs';
import { resetAndRerenderActors } from '../../helpers/utils.mjs';
import { ActionsField } from '../fields/actionField.mjs';
const currencyField = (initial, label, icon) =>
@ -209,7 +210,7 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
}
this.refreshConfig();
this.#resetActors();
resetAndRerenderActors();
}
/** Update config values based on homebrew data. Make sure the references don't change */
@ -230,29 +231,6 @@ export default class DhHomebrew extends foundry.abstract.DataModel {
});
}
}
/**
* Triggers a reset and non-forced re-render on all given actors (if given)
* or all world actors and actors in all scenes to show immediate results for a changed setting.
*/
#resetActors() {
const actors = new Set(
[
game.actors.contents,
game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])
].flat()
);
for (const actor of actors) {
for (const app of Object.values(actor.apps)) {
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
element.open = false; // This triggers a save
}
}
actor.reset();
actor.render();
}
}
}
export class Resource extends foundry.abstract.DataModel {

View file

@ -1,3 +1,5 @@
import { resetAndRerenderActors } from '../../helpers/utils.mjs';
export default class DhMetagaming extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
@ -6,7 +8,24 @@ export default class DhMetagaming extends foundry.abstract.DataModel {
initial: false,
label: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hideObserverPermissionInChat.label',
hint: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hideObserverPermissionInChat.hint'
}),
hidePartyStats: new fields.StringField({
initial: 'never',
label: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.label',
hint: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.hint',
required: true,
nullable: false,
choices: {
never: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.choices.never',
players: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.choices.players',
always: 'DAGGERHEART.SETTINGS.Metagaming.FIELDS.hidePartyStats.choices.always'
}
})
};
}
/** Invoked by the setting when data changes */
handleChange() {
resetAndRerenderActors();
}
}

View file

@ -793,6 +793,26 @@ export function getArmorSources(actor) {
});
}
/**
* Triggers a reset and non-forced re-render on all given actors (if given)
* or all world actors and actors in all scenes to show immediate results for a changed setting.
*/
export function resetAndRerenderActors() {
const actors = new Set(
[game.actors.contents, game.scenes.contents.flatMap(s => s.tokens.contents).flatMap(t => t.actor ?? [])].flat()
);
for (const actor of actors) {
for (const app of Object.values(actor.apps)) {
for (const element of app.element?.querySelectorAll('prose-mirror.active')) {
element.open = false; // This triggers a save
}
}
actor.reset();
actor.render();
}
}
/**
* Returns an array sorted by a function that returns a thing to compare, or an array to compare in order
* Similar to lodash's sortBy function.

View file

@ -91,7 +91,10 @@ const registerMenuSettings = () => {
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Metagaming, {
scope: 'world',
config: false,
type: DhMetagaming
type: DhMetagaming,
onChange: value => {
value.handleChange();
}
});
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.Homebrew, {

View file

@ -280,6 +280,17 @@ body.game:is(.performance-low, .noblur) {
}
}
.actors-list.limited {
.actor-resources {
display: flex;
align-items: center;
}
.actor-img-frame {
width: 3rem;
height: 3rem;
}
}
.actors-dragger {
display: flex;
align-items: center;

View file

@ -1,3 +1,4 @@
<div>
{{formGroup settingFields.schema.fields.hideObserverPermissionInChat value=settingFields._source.hideObserverPermissionInChat localize=true}}
{{formGroup settingFields.schema.fields.hidePartyStats value=settingFields._source.hidePartyStats localize=true}}
</div>

View file

@ -23,143 +23,164 @@
</button>
</div>
<ul class="actors-list">
{{#each partyMembers as |member id|}}
<li class="actor-resources">
<div class="actor-img-frame">
<img class="actor-img" src="{{member.img}}">
{{#if member.weapons}}
<div class="equipped-weapons">
{{#each member.weapons as |weapon|}}
<img src="{{weapon.img}}" data-tooltip="{{weapon.name}}"/>
{{/each}}
</div>
{{/if}}
{{#if member.evasion includeZero=true}}
<div class="evasion" data-tooltip="DAGGERHEART.GENERAL.evasion">{{member.evasion}}</div>
{{/if}}
{{#if member.difficulty includeZero=true}}
<div class="evasion" data-tooltip="DAGGERHEART.GENERAL.difficulty">{{member.difficulty}}</div>
{{/if}}
{{#unless (eq member.type 'companion')}}
<div class="threshold-section">
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.minor"}}</h4>
<h4 class="threshold-value">{{member.damageThresholds.major}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.major"}}</h4>
<h4 class="threshold-value">{{member.damageThresholds.severe}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}</h4>
</div>
{{/unless}}
</div>
<header>
<h2 class="actor-name">
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a>
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
</h2>
<div>
{{#unless (or (eq member.type 'companion') (eq member.type 'adversary')) }}
<div class="hope-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
{{#times member.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>
{{/if}}
</span>
{{/times}}
{{#if @root.showStats}}
<ul class="actors-list">
{{#each partyMembers as |member id|}}
<li class="actor-resources">
<div class="actor-img-frame">
<img class="actor-img" src="{{member.img}}">
{{#if member.weapons}}
<div class="equipped-weapons">
{{#each member.weapons as |weapon|}}
<img src="{{weapon.img}}" data-tooltip="{{weapon.name}}"/>
{{/each}}
</div>
{{/if}}
{{#if member.evasion includeZero=true}}
<div class="evasion" data-tooltip="DAGGERHEART.GENERAL.evasion">{{member.evasion}}</div>
{{/if}}
{{#if member.difficulty includeZero=true}}
<div class="evasion" data-tooltip="DAGGERHEART.GENERAL.difficulty">{{member.difficulty}}</div>
{{/if}}
{{#unless (eq member.type 'companion')}}
<div class="threshold-section">
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.minor"}}</h4>
<h4 class="threshold-value">{{member.damageThresholds.major}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.major"}}</h4>
<h4 class="threshold-value">{{member.damageThresholds.severe}}</h4>
<h4 class="threshold-label">{{localize "DAGGERHEART.ACTORS.Party.Thresholds.severe"}}</h4>
</div>
{{/unless}}
</div>
{{#if member.subtitle}}
<span class="subtitle">{{member.subtitle}}</span>
{{/if}}
</header>
<section class="body">
<section class="resources">
{{#unless (eq member.type 'companion') }}
<header>
<h2 class="actor-name">
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a>
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
</h2>
<div>
{{#unless (or (eq member.type 'companion') (eq member.type 'adversary')) }}
<div class="hope-section">
<h4>{{localize "DAGGERHEART.GENERAL.hope"}}</h4>
{{#times member.resources.hope.max}}
<span class='hope-value' data-action='toggleHope' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.resources.hope.value (add this 1))}}
<i class='fa-solid fa-diamond'></i>
{{else}}
<i class='fa-regular fa-circle'></i>
{{/if}}
</span>
{{/times}}
</div>
{{/unless}}
</div>
{{#if member.subtitle}}
<span class="subtitle">{{member.subtitle}}</span>
{{/if}}
</header>
<section class="body">
<section class="resources">
{{#unless (eq member.type 'companion') }}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.HitPoints.plural">
<span class="label">
<i class="fa-solid fa-heart" inert></i>
</span>
<span class="value">
<span class="current">{{member.resources.hitPoints.value}}</span>
/
<span class="max">{{member.resources.hitPoints.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.resources.hitPoints.max}}
<span class='slot {{#if (gte member.resources.hitPoints.value (add this 1))}}filled{{/if}}'
data-action='toggleHitPoints' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
</div>
{{/unless}}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.HitPoints.plural">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.stress">
<span class="label">
<i class="fa-solid fa-heart" inert></i>
<i class="fa-solid fa-bolt" inert></i>
</span>
<span class="value">
<span class="current">{{member.resources.hitPoints.value}}</span>
<span class="current">{{member.resources.stress.value}}</span>
/
<span class="max">{{member.resources.hitPoints.max}}</span>
<span class="max">{{member.resources.stress.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.resources.hitPoints.max}}
<span class='slot {{#if (gte member.resources.hitPoints.value (add this 1))}}filled{{/if}}'
data-action='toggleHitPoints' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#times member.resources.stress.max}}
<span class='slot {{#if (gte member.resources.stress.value (add this 1))}}filled{{/if}}'
data-action='toggleStress' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
</div>
{{/unless}}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.stress">
<span class="label">
<i class="fa-solid fa-bolt" inert></i>
</span>
<span class="value">
<span class="current">{{member.resources.stress.value}}</span>
/
<span class="max">{{member.resources.stress.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.resources.stress.max}}
<span class='slot {{#if (gte member.resources.stress.value (add this 1))}}filled{{/if}}'
data-action='toggleStress' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
</span>
{{/times}}
</div>
</div>
{{#if member.armorScore.max}}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.armorSlots">
<span class="label">
<i class="fa-solid fa-shield" inert></i>
</span>
<span class="value">
<span class="current">{{member.armorScore.value}}</span>
/
<span class="max">{{member.armorScore.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.armorScore.max}}
<a class='armor-slot' data-action='toggleArmorSlot' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i>
{{else}}
<i class="fa-solid fa-shield-halved"></i>
{{/if}}
</a>
{{/times}}
{{#if member.armorScore.max}}
<div class="slot-section">
<div class="slot-label" data-tooltip="DAGGERHEART.GENERAL.armorSlots">
<span class="label">
<i class="fa-solid fa-shield" inert></i>
</span>
<span class="value">
<span class="current">{{member.armorScore.value}}</span>
/
<span class="max">{{member.armorScore.max}}</span>
</span>
</div>
<div class="slot-bar">
{{#times member.armorScore.max}}
<a class='armor-slot' data-action='toggleArmorSlot' data-actor-id="{{member.uuid}}" data-value="{{add this 1}}">
{{#if (gte member.armorScore.value (add this 1))}}
<i class="fa-solid fa-shield"></i>
{{else}}
<i class="fa-solid fa-shield-halved"></i>
{{/if}}
</a>
{{/times}}
</div>
</div>
{{/if}}
</section>
{{#if member.traits}}
<div class="traits">
{{#each member.traits as |trait|}}
<span class="trait">
<span class="label">{{trait.label}}</span>
<span class="value">{{trait.value}}</span>
</span>
{{/each}}
</div>
{{/if}}
</section>
{{#if member.traits}}
<div class="traits">
{{#each member.traits as |trait|}}
<span class="trait">
<span class="label">{{trait.label}}</span>
<span class="value">{{trait.value}}</span>
</span>
{{/each}}
</div>
{{/if}}
</section>
</li>
{{/each}}
</ul>
</li>
{{/each}}
</ul>
{{else}}
<ul class="actors-list limited">
{{#each partyMembers as |member id|}}
<li class="actor-resources">
<div class="actor-img-frame">
<img class="actor-img" src="{{member.img}}">
</div>
<header>
<h2 class="actor-name">
<a data-action="openDocument" data-uuid="{{member.uuid}}">{{member.name}}</a>
<a class="delete-icon" data-action="deletePartyMember" data-uuid="{{member.uuid}}"><i class="fa-regular fa-times" inert></i></a>
</h2>
{{#if member.subtitle}}
<span class="subtitle">{{member.subtitle}}</span>
{{/if}}
</header>
</li>
{{/each}}
</ul>
{{/if}}
{{#unless document.system.partyMembers.length}}
<div class="actors-dragger">
<span>{{localize "DAGGERHEART.GENERAL.dropActorsHere"}}</span>