Added menu with refresh tools

This commit is contained in:
WBHarry 2025-08-18 15:42:01 +02:00
parent 18b6194afe
commit 4622d7ae51
11 changed files with 269 additions and 1 deletions

View file

@ -134,6 +134,8 @@ Hooks.once('init', () => {
CONFIG.ui.combat = applications.ui.DhCombatTracker;
CONFIG.ui.chat = applications.ui.DhChatLog;
CONFIG.ui.hotbar = applications.ui.DhHotbar;
CONFIG.ui.sidebar = applications.sidebar.DhSidebar;
CONFIG.ui.daggerheartMenu = applications.sidebar.DaggerheartMenu;
CONFIG.Token.rulerClass = placeables.DhTokenRuler;
CONFIG.ui.resources = applications.ui.DhFearTracker;

View file

@ -2314,6 +2314,10 @@
"heal": "Heal",
"applyHealing": "Apply Healing"
},
"refreshMessage": {
"title": "Feature Refresh",
"header": "Refreshed"
},
"reroll": {
"confirmTitle": "Reroll Dice",
"confirmText": "Are you sure you want to reroll?"
@ -2384,7 +2388,15 @@
"insufficientResources": "You have insufficient resources",
"multiclassAlreadyPresent": "You already have a class and multiclass",
"subclassesAlreadyPresent": "You already have a class and multiclass subclass",
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice"
"noDiceSystem": "Your selected dice {system} does not have a {faces} dice",
"gmMenuRefresh": "You refreshed all actions and resources {types}"
},
"Sidebar": {
"daggerheartMenu": {
"title": "Daggerheart Menu",
"startSession": "Start Session",
"startScene": "Start Scene"
}
},
"Tooltip": {
"disableEffect": "Disable Effect",

View file

@ -5,5 +5,6 @@ export * as levelup from './levelup/_module.mjs';
export * as settings from './settings/_module.mjs';
export * as sheets from './sheets/_module.mjs';
export * as sheetConfigs from './sheets-configs/_module.mjs';
export * as sidebar from './sidebar/_module.mjs';
export * as ui from './ui/_module.mjs';
export * as ux from './ux/_module.mjs';

View file

@ -0,0 +1,2 @@
export { default as DaggerheartMenu } from './tabs/daggerheartMenu.mjs';
export { default as DhSidebar } from './sidebar.mjs';

View file

@ -0,0 +1,9 @@
export default class DhSidebar extends Sidebar {
static TABS = {
...super.TABS,
daggerheartMenu: {
tooltip: 'DAGGERHEART.UI.Sidebar.daggerheartMenu.title',
icon: 'fa-solid fa-bars'
}
};
}

View file

@ -0,0 +1,160 @@
const { HandlebarsApplicationMixin } = foundry.applications.api;
const { AbstractSidebarTab } = foundry.applications.sidebar;
/**
* The daggerheart menu tab.
* @extends {AbstractSidebarTab}
* @mixes HandlebarsApplication
*/
export default class DaggerheartMenu extends HandlebarsApplicationMixin(AbstractSidebarTab) {
constructor(options) {
super(options);
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
}
static #defaultRefreshSelections() {
return {
session: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.session') },
scene: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.scene') },
longRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.longrest') },
shortRest: { selected: false, label: game.i18n.localize('DAGGERHEART.GENERAL.RefreshType.shortrest') }
};
}
/** @override */
static DEFAULT_OPTIONS = {
classes: ['dh-style'],
window: {
title: 'SIDEBAR.TabSettings'
},
actions: {
selectRefreshable: DaggerheartMenu.#selectRefreshable,
refreshActors: DaggerheartMenu.#refreshActors
}
};
/** @override */
static tabName = 'daggerheartMenu';
/** @override */
static PARTS = {
main: { template: 'systems/daggerheart/templates/sidebar/daggerheart-menu/main.hbs' }
};
/* -------------------------------------------- */
/** @inheritDoc */
async _prepareContext(options) {
const context = await super._prepareContext(options);
context.refreshables = this.refreshSelections;
context.disableRefresh = Object.values(this.refreshSelections).every(x => !x.selected);
return context;
}
async getRefreshables(types) {
const refreshedActors = {};
for (let actor of game.actors) {
if (['character', 'adversary'].includes(actor.type) && actor.prototypeToken.actorLink) {
const updates = {};
for (let item of actor.items) {
if (item.system.metadata.hasResource && types.includes(item.system.resource?.recovery)) {
if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add(
game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[item.system.resource.recovery].label)
);
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
const increasing =
item.system.resource.progression === CONFIG.DH.ITEM.itemResourceProgression.increasing.id;
updates[item.id].system = {
...updates[item.id].system,
'resource.value': increasing
? 0
: Roll.replaceFormulaData(item.system.resource.max, actor.getRollData())
};
}
if (item.system.metadata.hasActions) {
const refreshTypes = new Set();
const actions = item.system.actions.filter(action => {
if (types.includes(action.uses.recovery)) {
refreshTypes.add(action.uses.recovery);
return true;
}
return false;
});
if (actions.length === 0) continue;
if (!refreshedActors[actor.id])
refreshedActors[actor.id] = { name: actor.name, img: actor.img, refreshed: new Set() };
refreshedActors[actor.id].refreshed.add(
...refreshTypes.map(type => game.i18n.localize(CONFIG.DH.GENERAL.refreshTypes[type].label))
);
if (!updates[item.id]?.system) updates[item.id] = { system: {} };
updates[item.id].system = {
...updates[item.id].system,
...actions.reduce(
(acc, action) => {
acc.actions[action.id] = { 'uses.value': 0 };
return acc;
},
{ actions: updates[item.id].system.actions ?? {} }
)
};
}
}
for (let key in updates) {
const update = updates[key];
await actor.items.get(key).update(update);
}
}
}
return refreshedActors;
}
/* -------------------------------------------- */
/* Application Clicks Actions */
/* -------------------------------------------- */
static async #selectRefreshable(_event, button) {
const { type } = button.dataset;
this.refreshSelections[type].selected = !this.refreshSelections[type].selected;
this.render();
}
static async #refreshActors() {
const refreshKeys = Object.keys(this.refreshSelections).filter(key => this.refreshSelections[key].selected);
await this.getRefreshables(refreshKeys);
const types = refreshKeys.map(x => this.refreshSelections[x].label).join(', ');
ui.notifications.info(
game.i18n.format('DAGGERHEART.UI.Notifications.gmMenuRefresh', {
types: `[${types}]`
})
);
this.refreshSelections = DaggerheartMenu.#defaultRefreshSelections();
const cls = getDocumentClass('ChatMessage');
const msg = {
user: game.user.id,
content: await foundry.applications.handlebars.renderTemplate(
'systems/daggerheart/templates/ui/chat/refreshMessage.hbs',
{
types: types
}
),
title: game.i18n.localize('DAGGERHEART.UI.Chat.refreshMessage.title'),
speaker: cls.getSpeaker()
};
cls.create(msg);
this.render();
}
}

View file

@ -0,0 +1,13 @@
.daggerheart.chat.refresh-message {
header {
display: flex;
flex-direction: column;
align-items: center;
gap: 2px;
.subtitle {
font-size: 18;
font-weight: bold;
}
}
}

View file

@ -2,6 +2,7 @@
@import './chat/action.less';
@import './chat/chat.less';
@import './chat/downtime.less';
@import './chat/refresh-message.less';
@import './chat/sheet.less';
@import './combat-sidebar/combat-sidebar.less';
@ -21,3 +22,5 @@
@import './settings/settings.less';
@import './settings/homebrew-settings/domains.less';
@import './sidebar/daggerheartMenu.less';

View file

@ -0,0 +1,38 @@
.tab.sidebar-tab.daggerheartMenu-sidebar {
padding: 0 4px;
.menu-refresh-container {
display: flex;
flex-direction: column;
gap: 8px;
.menu-refresh-inner-container {
display: grid;
grid-template-columns: 1fr 1fr;
gap: 8px;
.experience-chip {
display: flex;
align-items: center;
border-radius: 5px;
width: fit-content;
gap: 5px;
cursor: pointer;
padding: 5px;
background: light-dark(@dark-blue-10, @golden-10);
color: light-dark(@dark-blue, @golden);
.label {
font-style: normal;
font-weight: 400;
font-size: 14px;
line-height: 17px;
}
&.selected {
background: light-dark(@dark-blue-40, @golden-40);
}
}
}
}
}

View file

@ -0,0 +1,22 @@
<div>
<fieldset>
<legend>{{localize "Refresh Features"}}</legend>
<div class="menu-refresh-container">
<div class="menu-refresh-inner-container">
{{#each refreshables as |type key|}}
<div class="experience-chip {{#if type.selected}}selected{{/if}}" data-action="selectRefreshable" data-type="{{key}}">
{{#if type.selected}}
<span><i class="fa-solid fa-circle"></i></span>
{{else}}
<span><i class="fa-regular fa-circle"></i></span>
{{/if}}
<span>{{type.label}}</span>
</div>
{{/each}}
</div>
<button data-action="refreshActors" {{disabled disableRefresh}}>{{localize "Refresh"}}</button>
</div>
</fieldset>
</div>

View file

@ -0,0 +1,6 @@
<div class="daggerheart chat refresh-message">
<header>
<div class="subtitle">{{localize "DAGGERHEART.UI.Chat.refreshMessage.header"}}</div>
<div class="types">{{types}}</div>
</header>
</div>