[V14] [Feature] Spotlight Without Combat (#1755)

This commit is contained in:
WBHarry 2026-03-28 03:01:50 +01:00 committed by GitHub
parent 2a294684d4
commit 24d22dde59
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
8 changed files with 109 additions and 19 deletions

View file

@ -2555,8 +2555,7 @@
"MACROS": { "MACROS": {
"Spotlight": { "Spotlight": {
"errors": { "errors": {
"noActiveCombat": "There is no active encounter", "noTokenSelected": "A token on the canvas must either be selected or hovered to spotlight it"
"noCombatantSelected": "A combatant token must be either selected or hovered to spotlight it"
} }
} }
}, },

View file

@ -1,5 +1,6 @@
import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs'; import { AdversaryBPPerEncounter } from '../../config/encounterConfig.mjs';
import { expireActiveEffects } from '../../helpers/utils.mjs'; import { expireActiveEffects } from '../../helpers/utils.mjs';
import { clearPreviousSpotlight } from '../../macros/spotlightCombatant.mjs';
export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker { export default class DhCombatTracker extends foundry.applications.sidebar.tabs.CombatTracker {
static DEFAULT_OPTIONS = { static DEFAULT_OPTIONS = {
@ -150,13 +151,13 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
} }
async setCombatantSpotlight(combatantId) { async setCombatantSpotlight(combatantId) {
const combatant = this.viewed.combatants.get(combatantId);
const update = { const update = {
system: { system: {
'spotlight.requesting': false, 'spotlight.requesting': false,
'spotlight.requestOrderIndex': 0 'spotlight.requestOrderIndex': 0
} }
}; };
const combatant = this.viewed.combatants.get(combatantId);
const toggleTurn = this.viewed.combatants.contents const toggleTurn = this.viewed.combatants.contents
.sort(this.viewed._sortCombatants) .sort(this.viewed._sortCombatants)
@ -187,6 +188,14 @@ export default class DhCombatTracker extends foundry.applications.sidebar.tabs.C
round: this.viewed.round + 1 round: this.viewed.round + 1
}); });
await combatant.update(update); await combatant.update(update);
if (combatant.token) clearPreviousSpotlight();
}
async clearTurn() {
await this.viewed.update({
turn: null,
round: this.viewed.round + 1
});
} }
static async requestSpotlight(_, target) { static async requestSpotlight(_, target) {

View file

@ -10,6 +10,36 @@ export default class DhTokenPlaceable extends foundry.canvas.placeables.Token {
this.previewHelp ||= this.addChild(this.#drawPreviewHelp()); this.previewHelp ||= this.addChild(this.#drawPreviewHelp());
} }
/**@inheritdoc */
_refreshTurnMarker() {
// Should a Turn Marker be active?
const { turnMarker } = this.document;
const markersEnabled =
CONFIG.Combat.settings.turnMarker.enabled && turnMarker.mode !== CONST.TOKEN_TURN_MARKER_MODES.DISABLED;
const spotlighted = game.settings
.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker)
.spotlightedTokens.has(this.document.uuid);
const turnIsSet = game.combat?.turn !== null;
const isTurn = game.combat?.combatant?.tokenId === this.id;
const markerActive = markersEnabled && turnIsSet ? isTurn : spotlighted;
// Activate a Turn Marker
if (markerActive) {
if (!this.turnMarker)
this.turnMarker = this.addChildAt(new foundry.canvas.placeables.tokens.TokenTurnMarker(this), 0);
canvas.tokens.turnMarkers.add(this);
this.turnMarker.draw();
}
// Remove a Turn Marker
else if (this.turnMarker) {
canvas.tokens.turnMarkers.delete(this);
this.turnMarker.destroy();
this.turnMarker = null;
}
}
/** @inheritDoc */ /** @inheritDoc */
async _drawEffects() { async _drawEffects() {
this.effects.renderable = false; this.effects.renderable = false;

View file

@ -39,7 +39,8 @@ export const gameSettings = {
Countdowns: 'Countdowns', Countdowns: 'Countdowns',
LastMigrationVersion: 'LastMigrationVersion', LastMigrationVersion: 'LastMigrationVersion',
SpotlightRequestQueue: 'SpotlightRequestQueue', SpotlightRequestQueue: 'SpotlightRequestQueue',
CompendiumBrowserSettings: 'CompendiumBrowserSettings' CompendiumBrowserSettings: 'CompendiumBrowserSettings',
SpotlightTracker: 'SpotlightTracker'
}; };
export const actionAutomationChoices = { export const actionAutomationChoices = {

View file

@ -4,6 +4,7 @@ export { default as DhRollTable } from './rollTable.mjs';
export { default as RegisteredTriggers } from './registeredTriggers.mjs'; export { default as RegisteredTriggers } from './registeredTriggers.mjs';
export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs'; export { default as CompendiumBrowserSettings } from './compendiumBrowserSettings.mjs';
export { default as TagTeamData } from './tagTeamData.mjs'; export { default as TagTeamData } from './tagTeamData.mjs';
export { default as SpotlightTracker } from './spotlightTracker.mjs';
export * as countdowns from './countdowns.mjs'; export * as countdowns from './countdowns.mjs';
export * as actions from './action/_module.mjs'; export * as actions from './action/_module.mjs';

View file

@ -0,0 +1,9 @@
export default class SpotlightTracker extends foundry.abstract.DataModel {
static defineSchema() {
const fields = foundry.data.fields;
return {
spotlightedTokens: new fields.SetField(new fields.DocumentUUIDField())
};
}
}

View file

@ -1,21 +1,50 @@
/** /**
* Spotlights a combatant. * Spotlight a token on the canvas. If it is a combatant, run it through combatTracker's spotlight logic.
* The combatant can be selected in a number of ways. If many are applied at the same time, the following order is used: * @param {TokenDocument} token - The token to spotlight
* 1) SelectedCombatant * @returns {void}
* 2) HoveredCombatant
*/ */
const spotlightCombatant = () => { const spotlightCombatantMacro = async token => {
if (!game.combat) if (!token)
return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noActiveCombat')); return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noTokenSelected'));
const selectedCombatant = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0].combatant : null;
const hoveredCombatant = game.canvas.tokens.hover?.combatant;
const combatant = selectedCombatant ?? hoveredCombatant;
if (!combatant)
return ui.notifications.error(game.i18n.localize('DAGGERHEART.MACROS.Spotlight.errors.noCombatantSelected'));
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); 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) await clearPreviousSpotlight();
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 });
}
}; };
export default spotlightCombatant; export const clearPreviousSpotlight = async () => {
const spotlightTracker = game.settings.get(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker);
const previouslySpotlightedUuid =
spotlightTracker.spotlightedTokens.size > 0 ? spotlightTracker.spotlightedTokens.first() : null;
if (!previouslySpotlightedUuid) return;
spotlightTracker.updateSource({ spotlightedTokens: [] });
await game.settings.set(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, spotlightTracker);
const previousToken = await foundry.utils.fromUuid(previouslySpotlightedUuid);
previousToken.object.renderFlags.set({ refreshTurnMarker: true });
};
export default spotlightCombatantMacro;

View file

@ -16,6 +16,7 @@ import {
DhVariantRuleSettings DhVariantRuleSettings
} from '../applications/settings/_module.mjs'; } from '../applications/settings/_module.mjs';
import { CompendiumBrowserSettings } from '../data/_module.mjs'; import { CompendiumBrowserSettings } from '../data/_module.mjs';
import SpotlightTracker from '../data/spotlightTracker.mjs';
export const registerDHSettings = () => { export const registerDHSettings = () => {
registerKeyBindings(); registerKeyBindings();
@ -40,7 +41,12 @@ export const registerKeyBindings = () => {
hint: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.hint'), hint: game.i18n.localize('DAGGERHEART.SETTINGS.Keybindings.spotlight.hint'),
uneditable: [], uneditable: [],
editable: [], editable: [],
onDown: game.system.api.macros.spotlightCombatant, onDown: () => {
const selectedTokens = canvas.tokens.controlled.length > 0 ? canvas.tokens.controlled[0] : null;
const hoveredTokens = game.canvas.tokens.hover ? game.canvas.tokens.hover : null;
const tokens = selectedTokens ?? hoveredTokens;
game.system.api.macros.spotlightCombatant(tokens);
},
onUp: () => {}, onUp: () => {},
restricted: true, restricted: true,
reservedModifiers: [], reservedModifiers: [],
@ -177,4 +183,10 @@ const registerNonConfigSettings = () => {
config: false, config: false,
type: CompendiumBrowserSettings type: CompendiumBrowserSettings
}); });
game.settings.register(CONFIG.DH.id, CONFIG.DH.SETTINGS.gameSettings.SpotlightTracker, {
scope: 'world',
config: false,
type: SpotlightTracker
});
}; };