Video over token, free-form video windows
CI / ci (push) Successful in 40s
Release Creation / build (release) Successful in 46s

This commit is contained in:
2026-06-07 22:18:08 +02:00
parent a9dbb9306a
commit 76ce992505
22 changed files with 2649 additions and 36 deletions
+98 -6
View File
@@ -34,16 +34,19 @@ import { DirectorsBoard } from './src/ui/gm/DirectorsBoard.js';
import { ConfirmationBar } from './src/ui/gm/ConfirmationBar.js';
import { PlayerPrivacyPanelMenu, initPlayerPrivacyPanelMenu } from './src/ui/player/PlayerPrivacyPanelMenu.js';
import { initGMPlayerPrivacySelector } from './src/ui/gm/GMPlayerPrivacySelector.js';
import { GMActorMappingPanel, initGMActorMappingPanel } from './src/ui/gm/GMActorMappingPanel.js';
import { ScryingPoolCameraViews, initScryingPoolCameraViews } from './src/ui/shared/ScryingPoolCameraViews.js';
import { TokenVideoOverlay } from './src/ui/shared/TokenVideoOverlay.js';
import { FreeformLayoutManager } from './src/ui/gm/FreeformLayoutManager.js';
import { ScryingPoolSettings } from './src/ui/gm/ScryingPoolSettings.js';
import { SOCKET_EVENTS } from './src/contracts/socket-message.js';
// Factory function to create ScryingPoolSettings with roleRenderer dependency
// Factory function to create ScryingPoolSettings with roleRenderer and adapter dependencies
// Returns a class constructor (not a function) that Foundry can use for registerMenu
function initScryingPoolSettings(roleRendererRef) {
function initScryingPoolSettings(roleRendererRef, adapterRef) {
return class extends ScryingPoolSettings {
constructor(options = {}) {
super(roleRendererRef, options);
super(roleRendererRef, adapterRef, options);
}
};
}
@@ -65,6 +68,8 @@ let visibilityBadge;
let notificationBus;
let directorsBoard;
let confirmationBar;
let tokenVideoOverlay;
let freeformLayoutManager;
/** @type {boolean} Flag to prevent duplicate scene control button addition */
let directorsBoardButtonAdded = false;
@@ -119,6 +124,40 @@ Hooks.once("init", () => {
hint: "When enabled, the GM's own camera feed is shown in the Scrying Pool strip.",
});
// Story 5.X: Token Video Overlay — completely optional
adapter.settings.register("showVideoOnTokens", {
scope: "world",
config: true,
type: Boolean,
default: false,
name: "Show Webcam Video on Tokens",
hint: "When enabled, each player's webcam feed replaces their character token image on the active scene, masked to a circle. The video strip is automatically hidden to free screen space.",
onChange: (val) => {
tokenVideoOverlay?.[val ? 'enable' : 'disable']();
if (val) {
roleRenderer?.closeStrip();
} else {
roleRenderer?.openStrip();
}
},
});
// Story 5.X: GM actor-to-user mapping for token video overlay
adapter.settings.register("userActorMapping", {
scope: "world",
config: false,
type: Object,
default: {},
});
// Story 5.3: Freeform layout — floating camera window positions
adapter.settings.register("freeformLayout", {
scope: "world",
config: false,
type: Object,
default: { windows: {} },
});
// Story 2.1: per-user notification verbosity preference (client-scoped)
adapter.settings.register("notificationVerbosity", {
scope: "client",
@@ -150,7 +189,16 @@ Hooks.once("init", () => {
config: false,
type: String,
default: "vertical-sm",
onChange: () => roleRenderer?.rerenderStrip(),
onChange: (val) => {
if (val === 'freeform') {
roleRenderer?.closeStrip();
freeformLayoutManager?.init();
freeformLayoutManager?.sync();
} else {
freeformLayoutManager?.destroy();
roleRenderer?.rerenderStrip();
}
},
});
// Per-user size toggle — client-scoped so each user can expand/collapse independently.
@@ -376,6 +424,13 @@ Hooks.once("ready", () => {
// Story 2.1: NotificationBus — runs for all clients (GM and players)
notificationBus = new NotificationBus(adapter);
notificationBus.init();
// Story 5.X: Token Video Overlay — optional, all clients (GM sees all players)
if (adapter.webrtc) {
tokenVideoOverlay = new TokenVideoOverlay(adapter);
tokenVideoOverlay.init();
}
// Story 3.1: Register socket listener for preset apply echo (all clients receive)
// Note: In Foundry, socket messages are automatically broadcast to all clients.
// The GM emits PRESET_APPLIED, and all clients (including GM) receive it.
@@ -407,13 +462,37 @@ Hooks.once("ready", () => {
directorsBoard = new DirectorsBoard(stateStore, scryingPoolController, adapter, scenePresetManager, playerPrivacyManager);
directorsBoard.init();
window.directorsBoard = directorsBoard;
// Story 5.3: Freeform Layout Manager
const SettingsCls = initScryingPoolSettings(roleRenderer, adapter);
let settingsPanel = null;
freeformLayoutManager = new FreeformLayoutManager(adapter, scryingPoolController, stateStore, {
onOpenSettings: () => {
if (settingsPanel && settingsPanel.rendered) {
settingsPanel.close();
settingsPanel = null;
} else {
settingsPanel = new SettingsCls();
settingsPanel.render(true);
}
},
});
// If initial layout is freeform, switch immediately
const initialLayout = adapter.settings?.get?.('dockLayout');
if (initialLayout === 'freeform') {
roleRenderer.closeStrip();
freeformLayoutManager.init();
freeformLayoutManager.sync();
}
}
// Inject Scrying Pool deps into our camera views replacement (all clients)
// Directors Board reference is GM-only — players get null so _onConfigure is a no-op
initScryingPoolCameraViews(
adapter.users.isGM() ? directorsBoard : null,
stateStore
stateStore,
adapter
);
// Pre-load participant-card as a Handlebars partial for directors-board
@@ -428,6 +507,9 @@ Hooks.once("ready", () => {
}
})();
// Story 5.X: Initialize GMActorMappingPanel with DI dependencies
initGMActorMappingPanel(adapter);
// Story 4.1: Initialize PlayerPrivacyPanelMenu with DI dependencies
// Story 4.2: Pass portraitFallbackHandler for portrait selection
initPlayerPrivacyPanelMenu(adapter, playerPrivacyManager, portraitFallbackHandler);
@@ -448,6 +530,16 @@ Hooks.once("ready", () => {
restricted: false,
});
// Story 5.X: Register Actor-Webcam Mapping settings menu (GM only)
game.settings.registerMenu('scrying-pool', 'actorMapping', {
name: 'SCRYING_POOL.ActorMapping.title',
label: 'SCRYING_POOL.ActorMapping.label',
hint: 'SCRYING_POOL.ActorMapping.hint',
icon: 'fa-solid fa-address-card',
type: GMActorMappingPanel,
restricted: true,
});
// Register ScryingPoolSettings in module settings
// Provides button to reopen the strip when user closes it
game.settings.registerMenu('scrying-pool', 'stripSettings', {
@@ -455,7 +547,7 @@ Hooks.once("ready", () => {
label: 'SCRYING_POOL.Settings.Title',
hint: 'SCRYING_POOL.Settings.Hint',
icon: 'fa-solid fa-cog',
type: initScryingPoolSettings(roleRenderer),
type: initScryingPoolSettings(roleRenderer, adapter),
restricted: true, // GM only
});