Video over token, free-form video windows
This commit is contained in:
@@ -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
|
||||
});
|
||||
|
||||
|
||||
Reference in New Issue
Block a user