initial commit
This commit is contained in:
commit
8e3b991518
5 changed files with 350 additions and 0 deletions
20
README.md
Normal file
20
README.md
Normal file
|
|
@ -0,0 +1,20 @@
|
||||||
|
# Cosmo's Daggerheart Tweaks
|
||||||
|
|
||||||
|
A Foundry VTT v14 module that adds quality-of-life tweaks for the **Daggerheart** system.
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
### 1. Token HUD Spotlight (On by default)
|
||||||
|
Adds a new Spotlight button directly to the default Token HUD menu:
|
||||||
|
* **For Players**: Allows requesting or cancelling a spotlight request for their character.
|
||||||
|
* **For GMs**: Allows granting spotlight to or removing spotlight from any token.
|
||||||
|
* **Real-time Updates**: The HUD button automatically reflects status changes in real-time if updated via the combat tracker or other canvas actions.
|
||||||
|
|
||||||
|
### 2. Minimal Countdown UI (On by default)
|
||||||
|
Reduces screen clutter by optimizing the Countdown Tracker widget:
|
||||||
|
* **For Players**: Hides the countdown widget completely if there are no active/visible countdowns.
|
||||||
|
* **For GMs**: Minimizes the countdown widget to a tiny square featuring a `+` icon when empty. Clicking the `+` icon opens the system's native Countdown Edit dialog.
|
||||||
|
|
||||||
|
## Configuration
|
||||||
|
|
||||||
|
Both features can be toggled on or off independently under **Configure Settings** > **Cosmo's Daggerheart Tweaks**.
|
||||||
12
lang/en.json
Normal file
12
lang/en.json
Normal file
|
|
@ -0,0 +1,12 @@
|
||||||
|
{
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.TokenHudSpotlight.Name": "Token HUD Spotlight Button",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.TokenHudSpotlight.Hint": "Add a Spotlight button to the Token HUD. Players can request/cancel spotlight, while GMs can grant/take it.",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.MinimalCountdownUI.Name": "Minimal Countdown UI",
|
||||||
|
"COSMOS_DH_TWEAKS.Settings.MinimalCountdownUI.Hint": "When no countdowns are active, hides the UI completely for players and minimizes it to a '+' button for GMs.",
|
||||||
|
"COSMOS_DH_TWEAKS.Spotlight.request": "Request Spotlight",
|
||||||
|
"COSMOS_DH_TWEAKS.Spotlight.cancel": "Cancel Spotlight Request",
|
||||||
|
"COSMOS_DH_TWEAKS.Spotlight.grant": "Grant Spotlight",
|
||||||
|
"COSMOS_DH_TWEAKS.Spotlight.take": "Take Spotlight",
|
||||||
|
"COSMOS_DH_TWEAKS.Spotlight.warnNoCombatant": "This token is not in combat. You can only request spotlight in combat.",
|
||||||
|
"COSMOS_DH_TWEAKS.Countdown.add": "Add Countdown"
|
||||||
|
}
|
||||||
39
module.json
Normal file
39
module.json
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
{
|
||||||
|
"id": "cosmos-dh-tweaks",
|
||||||
|
"title": "Cosmo's Daggerheart Tweaks",
|
||||||
|
"description": "A selection of quality of life tweaks for the Daggerheart system on Foundry VTT.",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"url": "https://git.geeks.gay/cosmo/cosmos-dh-tweaks",
|
||||||
|
"manifest": "https://git.geeks.gay/cosmo/cosmos-dh-tweaks/raw/branch/main/module.json",
|
||||||
|
"download": "https://git.geeks.gay/cosmo/cosmos-dh-tweaks/releases/download/1.0.0/cosmos-dh-tweaks.zip",
|
||||||
|
"compatibility": {
|
||||||
|
"minimum": "14.000",
|
||||||
|
"verified": "14.364"
|
||||||
|
},
|
||||||
|
"authors": [
|
||||||
|
{
|
||||||
|
"name": "Cosmo"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"relationships": {
|
||||||
|
"systems": [
|
||||||
|
{
|
||||||
|
"id": "daggerheart",
|
||||||
|
"type": "system"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"esmodules": [
|
||||||
|
"scripts/cosmos-dh-tweaks.mjs"
|
||||||
|
],
|
||||||
|
"styles": [
|
||||||
|
"styles/cosmos-dh-tweaks.css"
|
||||||
|
],
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"name": "English",
|
||||||
|
"path": "lang/en.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
208
scripts/cosmos-dh-tweaks.mjs
Normal file
208
scripts/cosmos-dh-tweaks.mjs
Normal file
|
|
@ -0,0 +1,208 @@
|
||||||
|
Hooks.once("init", () => {
|
||||||
|
// Register settings
|
||||||
|
game.settings.register("cosmos-dh-tweaks", "tokenHudSpotlight", {
|
||||||
|
name: "COSMOS_DH_TWEAKS.Settings.TokenHudSpotlight.Name",
|
||||||
|
hint: "COSMOS_DH_TWEAKS.Settings.TokenHudSpotlight.Hint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true
|
||||||
|
});
|
||||||
|
|
||||||
|
game.settings.register("cosmos-dh-tweaks", "minimalCountdownUI", {
|
||||||
|
name: "COSMOS_DH_TWEAKS.Settings.MinimalCountdownUI.Name",
|
||||||
|
hint: "COSMOS_DH_TWEAKS.Settings.MinimalCountdownUI.Hint",
|
||||||
|
scope: "world",
|
||||||
|
config: true,
|
||||||
|
type: Boolean,
|
||||||
|
default: true,
|
||||||
|
onChange: () => {
|
||||||
|
if (ui.countdowns) ui.countdowns.render();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Override the Countdown class in CONFIG
|
||||||
|
const OriginalCountdowns = CONFIG.ui.countdowns;
|
||||||
|
if (OriginalCountdowns) {
|
||||||
|
class CosmoDhTweaksCountdowns extends OriginalCountdowns {
|
||||||
|
async _onRender(context, options) {
|
||||||
|
await super._onRender(context, options);
|
||||||
|
|
||||||
|
if (!game.settings.get("cosmos-dh-tweaks", "minimalCountdownUI")) {
|
||||||
|
this.element.classList.remove("cosmo-minimal-countdown");
|
||||||
|
const addBtn = this.element.querySelector(".cosmo-add-countdown");
|
||||||
|
if (addBtn) addBtn.remove();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check if there are active countdowns using public API
|
||||||
|
const countdownData = this._getCountdownData();
|
||||||
|
let count = 0;
|
||||||
|
for (const key of Object.keys(countdownData)) {
|
||||||
|
count += Object.keys(countdownData[key]).length;
|
||||||
|
}
|
||||||
|
const hasCountdowns = count > 0;
|
||||||
|
|
||||||
|
if (game.user.isGM) {
|
||||||
|
if (!hasCountdowns) {
|
||||||
|
this.element.classList.add("cosmo-minimal-countdown");
|
||||||
|
let addBtn = this.element.querySelector(".cosmo-add-countdown");
|
||||||
|
if (!addBtn) {
|
||||||
|
const header = this.element.querySelector("header.countdowns-header");
|
||||||
|
if (header) {
|
||||||
|
addBtn = document.createElement("a");
|
||||||
|
addBtn.className = "cosmo-add-countdown";
|
||||||
|
addBtn.setAttribute("data-tooltip", game.i18n.localize("COSMOS_DH_TWEAKS.Countdown.add"));
|
||||||
|
addBtn.innerHTML = '<i class="fa-solid fa-plus"></i>';
|
||||||
|
addBtn.addEventListener("click", (e) => {
|
||||||
|
e.preventDefault();
|
||||||
|
new game.system.api.applications.ui.CountdownEdit().render(true);
|
||||||
|
});
|
||||||
|
header.appendChild(addBtn);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
this.element.classList.remove("cosmo-minimal-countdown");
|
||||||
|
const addBtn = this.element.querySelector(".cosmo-add-countdown");
|
||||||
|
if (addBtn) addBtn.remove();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// For players: if there are no countdowns, hide the UI completely.
|
||||||
|
if (!hasCountdowns) {
|
||||||
|
this.element.hidden = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CONFIG.ui.countdowns = CosmoDhTweaksCountdowns;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
Hooks.on("renderTokenHUD", (tokenHUD, html, tokenData) => {
|
||||||
|
if (!game.settings.get("cosmos-dh-tweaks", "tokenHudSpotlight")) return;
|
||||||
|
|
||||||
|
const token = tokenHUD.object;
|
||||||
|
if (!token) return;
|
||||||
|
|
||||||
|
const isGM = game.user.isGM;
|
||||||
|
let active = false;
|
||||||
|
|
||||||
|
if (isGM) {
|
||||||
|
// Determine if currently spotlighted
|
||||||
|
const isSpotlighted = game.combat
|
||||||
|
? (game.combat.combatant?.tokenId === token.id)
|
||||||
|
: game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)?.spotlightedTokens.has(token.document.uuid);
|
||||||
|
active = isSpotlighted;
|
||||||
|
} else {
|
||||||
|
// Players: only proceed if they own the token
|
||||||
|
if (!token.document.isOwner) return;
|
||||||
|
const combatant = token.combatant;
|
||||||
|
active = combatant?.system.spotlight?.requesting || false;
|
||||||
|
}
|
||||||
|
|
||||||
|
const title = isGM
|
||||||
|
? (active ? game.i18n.localize("COSMOS_DH_TWEAKS.Spotlight.take") : game.i18n.localize("COSMOS_DH_TWEAKS.Spotlight.grant"))
|
||||||
|
: (active ? game.i18n.localize("COSMOS_DH_TWEAKS.Spotlight.cancel") : game.i18n.localize("COSMOS_DH_TWEAKS.Spotlight.request"));
|
||||||
|
|
||||||
|
const activeClass = active ? "active" : "";
|
||||||
|
const iconClass = active ? "fa-hand-sparkles" : "fa-hand";
|
||||||
|
|
||||||
|
// Build the button element
|
||||||
|
const button = document.createElement("div");
|
||||||
|
button.className = `control-icon cosmo-spotlight-hud-btn ${activeClass}`;
|
||||||
|
button.setAttribute("title", title);
|
||||||
|
button.innerHTML = `<i class="fa-solid ${iconClass}"></i>`;
|
||||||
|
|
||||||
|
button.addEventListener("click", async (event) => {
|
||||||
|
event.preventDefault();
|
||||||
|
|
||||||
|
if (isGM) {
|
||||||
|
// Trigger GM spotlight toggle
|
||||||
|
if (game.system.api?.macros?.spotlightCombatant) {
|
||||||
|
await game.system.api.macros.spotlightCombatant(token);
|
||||||
|
} else {
|
||||||
|
// Fallback manual update if macro is missing
|
||||||
|
const combatantCombat = token.combatant
|
||||||
|
? game.combat
|
||||||
|
: game.combats.find(combat => combat.combatants.some(x => x.token && x.token.id === token.document.id));
|
||||||
|
if (combatantCombat) {
|
||||||
|
const combatant = combatantCombat.combatants.find(x => x.token.id === token.document.id);
|
||||||
|
if (!combatantCombat.active) {
|
||||||
|
await combatantCombat.activate();
|
||||||
|
if (combatantCombat.combatant?.id !== combatant.id) ui.combat.setCombatantSpotlight(combatant.id);
|
||||||
|
} else {
|
||||||
|
ui.combat.setCombatantSpotlight(combatant.id);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
if (game.combat) await ui.combat.clearTurn();
|
||||||
|
const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker);
|
||||||
|
const isSpotlighted = spotlightTracker.spotlightedTokens.has(token.document.uuid);
|
||||||
|
if (!isSpotlighted) {
|
||||||
|
// Clear previous spotlight manually
|
||||||
|
const previouslySpotlightedUuid = spotlightTracker.spotlightedTokens.size > 0 ? spotlightTracker.spotlightedTokens.first() : null;
|
||||||
|
if (previouslySpotlightedUuid) {
|
||||||
|
spotlightTracker.updateSource({ spotlightedTokens: [] });
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker);
|
||||||
|
const previousToken = await foundry.utils.fromUuid(previouslySpotlightedUuid);
|
||||||
|
if (previousToken?.object) previousToken.object.renderFlags.set({ refreshTurnMarker: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
spotlightTracker.updateSource({
|
||||||
|
spotlightedTokens: isSpotlighted ? [] : [token.document.uuid]
|
||||||
|
});
|
||||||
|
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker);
|
||||||
|
token.renderFlags.set({ refreshTurnMarker: true });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Player requesting / cancelling spotlight
|
||||||
|
const combatant = token.combatant;
|
||||||
|
if (!combatant) {
|
||||||
|
ui.notifications.warn(game.i18n.localize("COSMOS_DH_TWEAKS.Spotlight.warnNoCombatant"));
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const isRequesting = combatant.system.spotlight.requesting;
|
||||||
|
const characters = game.combat.turns?.filter(x => !x.isNPC) ?? [];
|
||||||
|
const orderValues = characters.map(character => character.system.spotlight.requestOrderIndex || 0);
|
||||||
|
const maxRequestIndex = Math.max(...orderValues, 0);
|
||||||
|
|
||||||
|
await combatant.update({
|
||||||
|
'system.spotlight': {
|
||||||
|
requesting: !isRequesting,
|
||||||
|
requestOrderIndex: !isRequesting ? maxRequestIndex + 1 : 0
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Rerender HUD to update icon state
|
||||||
|
tokenHUD.render(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
// In Foundry, HUD HTML can be jQuery. Find col.left and append.
|
||||||
|
// Supports both jQuery and HTMLElement.
|
||||||
|
const leftCol = html.find ? html.find('.col.left') : html.querySelector('.col.left');
|
||||||
|
if (leftCol && leftCol.append) {
|
||||||
|
leftCol.append(button);
|
||||||
|
} else if (leftCol) {
|
||||||
|
leftCol.appendChild(button);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-render Token HUD if open and its token is updated in combatant spotlight
|
||||||
|
Hooks.on("updateCombatant", (combatant, change, options, userId) => {
|
||||||
|
if (!game.settings.get("cosmos-dh-tweaks", "tokenHudSpotlight")) return;
|
||||||
|
if (canvas.tokens.hud.rendered && canvas.tokens.hud.object?.id === combatant.tokenId) {
|
||||||
|
canvas.tokens.hud.render(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
// Re-render Token HUD if out-of-combat spotlight settings change
|
||||||
|
Hooks.on("updateSetting", (setting, change, options, userId) => {
|
||||||
|
if (!game.settings.get("cosmos-dh-tweaks", "tokenHudSpotlight")) return;
|
||||||
|
const key = setting.key || setting.id;
|
||||||
|
if (key === "daggerheart.SpotlightTracker" || key === `${CONFIG.DH.id}.SpotlightTracker`) {
|
||||||
|
if (canvas.tokens.hud.rendered) {
|
||||||
|
canvas.tokens.hud.render(true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
71
styles/cosmos-dh-tweaks.css
Normal file
71
styles/cosmos-dh-tweaks.css
Normal file
|
|
@ -0,0 +1,71 @@
|
||||||
|
/* Minimal Countdown UI styles */
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown {
|
||||||
|
width: 36px !important;
|
||||||
|
height: 36px !important;
|
||||||
|
min-width: 36px !important;
|
||||||
|
min-height: 36px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
border-radius: 4px;
|
||||||
|
align-self: flex-end;
|
||||||
|
overflow: hidden;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown::before {
|
||||||
|
opacity: 1 !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown .countdowns-header {
|
||||||
|
display: flex !important;
|
||||||
|
width: 36px !important;
|
||||||
|
height: 36px !important;
|
||||||
|
flex: 0 0 36px !important;
|
||||||
|
padding: 0 !important;
|
||||||
|
margin: 0 !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
border: none !important;
|
||||||
|
background: transparent !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown .countdowns-header > *:not(.cosmo-add-countdown) {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown .countdowns-container {
|
||||||
|
display: none !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown .cosmo-add-countdown {
|
||||||
|
display: flex !important;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
width: 36px !important;
|
||||||
|
height: 36px !important;
|
||||||
|
font-size: 16px !important;
|
||||||
|
color: var(--golden, #c5a45e) !important;
|
||||||
|
cursor: pointer;
|
||||||
|
transition: color 0.2s ease, transform 0.2s ease, text-shadow 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.daggerheart.dh-style.countdowns.cosmo-minimal-countdown .cosmo-add-countdown:hover {
|
||||||
|
color: #ffffff !important;
|
||||||
|
transform: scale(1.15);
|
||||||
|
text-shadow: 0 0 5px var(--golden, #c5a45e) !important;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Token HUD Spotlight Button styling */
|
||||||
|
.control-icon.cosmo-spotlight-hud-btn {
|
||||||
|
transition: color 0.2s ease, text-shadow 0.2s ease, background-color 0.2s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-icon.cosmo-spotlight-hud-btn:hover {
|
||||||
|
color: #ffffff;
|
||||||
|
}
|
||||||
|
|
||||||
|
.control-icon.cosmo-spotlight-hud-btn.active {
|
||||||
|
color: var(--golden, #c5a45e) !important;
|
||||||
|
text-shadow: 0 0 8px var(--golden, #c5a45e) !important;
|
||||||
|
border: 1px solid var(--golden, #c5a45e) !important;
|
||||||
|
box-shadow: 0 0 5px rgba(197, 164, 94, 0.5) !important;
|
||||||
|
}
|
||||||
Loading…
Add table
Add a link
Reference in a new issue