Story 3.2 done

This commit is contained in:
2026-05-23 18:23:48 +02:00
parent d175f92806
commit a1e8886fce
66 changed files with 18258 additions and 1650 deletions
+65 -1
View File
@@ -22,11 +22,15 @@ import { StateStore } from './src/core/StateStore.js';
import { SocketHandler } from './src/core/SocketHandler.js';
import { VisibilityManager } from './src/core/VisibilityManager.js';
import { ScryingPoolController } from './src/core/ScryingPoolController.js';
import { ScenePresetManager } from './src/core/ScenePresetManager.js';
import { AVTileAdapter } from './src/ui/shared/AVTileAdapter.js';
import { RoleRenderer } from './src/ui/RoleRenderer.js';
import { VisibilityBadge } from './src/ui/player/VisibilityBadge.js';
import { NotificationBus } from './src/notifications/NotificationBus.js';
import { DirectorsBoard } from './src/ui/gm/DirectorsBoard.js';
import { ConfirmationBar } from './src/ui/gm/ConfirmationBar.js';
import { StripOverlayLayer } from './src/ui/shared/StripOverlayLayer.js';
import { SOCKET_EVENTS } from './src/contracts/socket-message.js';
// Module-level references — constructed in init hook, used across hooks
let adapter;
@@ -34,11 +38,14 @@ let stateStore;
let socketHandler;
let visibilityManager;
let scryingPoolController;
let scenePresetManager;
let avTileAdapter;
let roleRenderer;
let visibilityBadge;
let notificationBus;
let directorsBoard;
let stripOverlayLayer;
let confirmationBar;
/** @type {boolean} Flag to prevent duplicate scene control button addition */
let directorsBoardButtonAdded = false;
@@ -89,7 +96,18 @@ Hooks.once("init", () => {
default: "all",
});
// Story 3.2: Global auto-apply enable/disable setting
adapter.settings.register("autoApplyEnabled", {
scope: "world",
config: true,
type: Boolean,
default: true,
name: "Enable Scene Preset Auto-Apply",
hint: "When enabled, scenes with configured presets will automatically apply them on activation",
});
// Construct data layer — constructors are side-effect-free
// Note: ScenePresetManager is constructed in 'ready' hook after visibilityManager is available
stateStore = new StateStore(adapter.settings);
socketHandler = new SocketHandler(adapter.socket, adapter.hooks);
@@ -164,6 +182,27 @@ Hooks.once("ready", () => {
// Wire core managers — construct both before setReady so handler can reference both
visibilityManager = new VisibilityManager(stateStore, adapter);
scryingPoolController = new ScryingPoolController(stateStore, socketHandler, adapter);
// Story 3.2: Re-construct ScenePresetManager with visibilityManager for auto-apply
scenePresetManager = new ScenePresetManager(adapter, stateStore, socketHandler, visibilityManager);
// Story 3.2: Create StripOverlayLayer (shared infrastructure for UI components)
stripOverlayLayer = new StripOverlayLayer(adapter);
stripOverlayLayer.init();
// Story 3.2: Create ConfirmationBar for preset apply feedback
confirmationBar = new ConfirmationBar(adapter, visibilityManager, socketHandler, stripOverlayLayer);
confirmationBar.init();
// Story 3.2: Register updateScene hook for auto-apply
adapter.hooks.on('updateScene', (scene) => {
if (adapter.users.isGM()) {
scenePresetManager.onSceneActivate(scene);
}
});
// Story 3.1: Initialize ScenePresetManager to load presets from current scene
scenePresetManager.init();
// Set up composite handler for SocketHandler timeout callbacks
// This allows cleanup of ScryingPoolController._pendingOps when onRevert fires
@@ -194,9 +233,34 @@ Hooks.once("ready", () => {
// Story 2.1: NotificationBus — runs for all clients (GM and players)
notificationBus = new NotificationBus(adapter);
notificationBus.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.
// We skip processing on the GM since they already applied it locally.
adapter.socket.on(SOCKET_EVENTS.PRESET_APPLIED, async (payload) => {
try {
// Validate payload
if (!payload || typeof payload !== 'object' || typeof payload.presetName !== 'string') {
console.warn('[ScryingPool] Invalid PRESET_APPLIED payload:', payload);
return;
}
// Skip on GM — they already applied the preset locally
if (adapter.users.isGM()) {
return;
}
// Load the preset on this client (emitSocket: false to prevent loop)
await scenePresetManager.load(payload.presetName, { emitSocket: false });
} catch (err) {
console.error('[ScryingPool] Failed to handle PRESET_APPLIED:', err);
}
});
// Story 2.2: DirectorsBoard (lazy, GM only)
// Story 3.1: Pass scenePresetManager for preset save/load functionality
if (adapter.users.isGM()) {
directorsBoard = new DirectorsBoard(stateStore, scryingPoolController, adapter);
directorsBoard = new DirectorsBoard(stateStore, scryingPoolController, adapter, scenePresetManager);
directorsBoard.init();
}
} catch (err) {