diff --git a/ai_animation/src/debug/debugMenu.ts b/ai_animation/src/debug/debugMenu.ts index 8201979..71a47f2 100644 --- a/ai_animation/src/debug/debugMenu.ts +++ b/ai_animation/src/debug/debugMenu.ts @@ -5,6 +5,7 @@ import { updateNextMomentDisplay, initNextMomentTool } from "./nextMoment"; import { initDebugProvinceHighlighting } from "./provinceHighlight"; +import { initInstantChatTool } from "./instantChat"; export class DebugMenu { private toggleBtn: HTMLButtonElement; @@ -171,6 +172,7 @@ export class DebugMenu { } private initTools(): void { + initInstantChatTool(this); initNextMomentTool(this); initDebugProvinceHighlighting() } diff --git a/ai_animation/src/debug/instantChat.ts b/ai_animation/src/debug/instantChat.ts new file mode 100644 index 0000000..d30424d --- /dev/null +++ b/ai_animation/src/debug/instantChat.ts @@ -0,0 +1,54 @@ +/** + * Debug tool for making all chat messages appear instantly + * Provides a toggle to skip word-by-word animation during message playback + */ + +import { config } from '../config'; +import { DebugMenu } from './debugMenu'; + +// Flag to control instant chat behavior +let instantChatEnabled = false; + +/** + * Gets whether instant chat is currently enabled + */ +export function isInstantChatEnabled(): boolean { + return instantChatEnabled; +} + +/** + * Initializes the instant chat debug tool + * @param debugMenu - The debug menu instance to add this tool to + */ +export function initInstantChatTool(debugMenu: DebugMenu): void { + const content = ` +
+ +
+ Skip word-by-word animation and show all chat messages instantly +
+
+ `; + + debugMenu.addDebugTool('Chat Controls', content); + + // Set up event listener for the toggle + const toggleElement = document.getElementById('instant-chat-toggle') as HTMLInputElement; + if (toggleElement) { + // Set initial state + toggleElement.checked = instantChatEnabled; + + // Handle toggle changes + toggleElement.addEventListener('change', (e) => { + const target = e.target as HTMLInputElement; + instantChatEnabled = target.checked; + + if (config.isDebugMode) { + console.log(`Instant chat ${instantChatEnabled ? 'enabled' : 'disabled'}`); + } + }); + } +} \ No newline at end of file diff --git a/ai_animation/src/domElements/chatWindows.ts b/ai_animation/src/domElements/chatWindows.ts index a28202d..8c8ce0f 100644 --- a/ai_animation/src/domElements/chatWindows.ts +++ b/ai_animation/src/domElements/chatWindows.ts @@ -4,6 +4,7 @@ import { config } from "../config"; import { advanceToNextPhase } from "../phase"; import { getPowerDisplayName, getAllPowerDisplayNames } from '../utils/powerNames'; import { PowerENUM } from '../types/map'; +import { isInstantChatEnabled } from '../debug/instantChat'; //TODO: Sometimes the LLMs use lists, and they don't work in the chats. The just appear as bullets within a single line. @@ -165,8 +166,8 @@ export function updateChatWindows(phase: any, stepMessages = false) { console.log(`Found ${relevantMessages.length} messages for player ${gameState.currentPower} in phase ${phase.name}`); } - if (!stepMessages) { - // Normal mode: show all messages at once + if (!stepMessages || isInstantChatEnabled()) { + // Normal mode or instant chat mode: show all messages at once relevantMessages.forEach(msg => { const isNew = addMessageToChat(msg, phase.name); if (isNew) { @@ -176,6 +177,65 @@ export function updateChatWindows(phase: any, stepMessages = false) { } }); gameState.messagesPlaying = false; + + // If instant chat is enabled during stepwise mode, immediately proceed to next phase logic + if (stepMessages && isInstantChatEnabled()) { + // Trigger the same logic as the end of stepwise message display + if (gameState.isPlaying && !gameState.isSpeaking) { + if (gameState.gameData && gameState.gameData.phases) { + const currentPhase = gameState.gameData.phases[gameState.phaseIndex]; + + if (config.isDebugMode) { + console.log(`Instant chat enabled - processing end of phase ${currentPhase.name}`); + } + + // Show summary first if available + if (currentPhase.summary?.trim()) { + addToNewsBanner(`(${currentPhase.name}) ${currentPhase.summary}`); + } + + // Get previous phase for animations + const prevIndex = gameState.phaseIndex > 0 ? gameState.phaseIndex - 1 : null; + const previousPhase = prevIndex !== null ? gameState.gameData.phases[prevIndex] : null; + + // Only schedule next phase if not already scheduled + if (!gameState.nextPhaseScheduled) { + gameState.nextPhaseScheduled = true; + + // Show animations for current phase's orders + if (previousPhase) { + if (config.isDebugMode) { + console.log(`Animating orders from ${previousPhase.name} to ${currentPhase.name}`); + } + + // After animations complete, advance to next phase with longer delay + gameState.playbackTimer = setTimeout(() => { + if (gameState.isPlaying) { + if (config.isDebugMode) { + console.log(`Animations complete, advancing from ${currentPhase.name}`); + } + advanceToNextPhase(); + } + }, config.playbackSpeed + config.animationDuration); // Wait for both summary and animations + } else { + // For first phase, use shorter delay since there are no animations + if (config.isDebugMode) { + console.log(`First phase ${currentPhase.name} - no animations to wait for`); + } + + gameState.playbackTimer = setTimeout(() => { + if (gameState.isPlaying) { + if (config.isDebugMode) { + console.log(`Advancing from first phase ${currentPhase.name}`); + } + advanceToNextPhase(); + } + }, config.playbackSpeed); // Only wait for summary, no animation delay + } + } + } + } + } } else { // Stepwise mode: show one message at a time, animating word-by-word gameState.messagesPlaying = true; diff --git a/ai_animation/src/gameState.ts b/ai_animation/src/gameState.ts index be280c7..658e0ba 100644 --- a/ai_animation/src/gameState.ts +++ b/ai_animation/src/gameState.ts @@ -290,7 +290,7 @@ class GameState { // If there is more than one moment per turn, only return the largest one. if (momentMatch.length > 1) { momentMatch = momentMatch.sort((a, b) => { - return a.interest_score - b.interest_score + return b.interest_score - a.interest_score }) } diff --git a/ai_animation/src/phase.ts b/ai_animation/src/phase.ts index f729458..b9fbf6f 100644 --- a/ai_animation/src/phase.ts +++ b/ai_animation/src/phase.ts @@ -1,6 +1,6 @@ import { gameState } from "./gameState"; import { logger } from "./logger"; -import { debugMenu, updatePhaseDisplay } from "./domElements"; +import { updatePhaseDisplay } from "./domElements"; import { initUnits } from "./units/create"; import { updateSupplyCenterOwnership, updateLeaderboard, updateMapOwnership as _updateMapOwnership } from "./map/state"; import { updateChatWindows, addToNewsBanner } from "./domElements/chatWindows"; @@ -8,9 +8,12 @@ import { createAnimationsForNextPhase } from "./units/animate"; import { speakSummary } from "./speech"; import { config } from "./config"; import { debugMenuInstance } from "./debug/debugMenu"; - +import { showTwoPowerConversation, closeTwoPowerConversation } from "./components/twoPowerConversation"; +import { PowerENUM } from "./types/map"; const MOMENT_THRESHOLD = 8.0 +// If we're in debug mode, show it quick, otherwise show it for 30 seconds +const MOMENT_DISPLAY_TIMEOUT_MS = config.isDebugMode ? 5000 : 30000 // FIXME: Going to previous phases is borked. Units do not animate properly, map doesn't update. function _setPhase(phaseIndex: number) { @@ -67,11 +70,15 @@ export function nextPhase() { setTimeout(() => { closeTwoPowerConversation() gameState.isDisplayingMoment = false - }, 2000) + _setPhase(gameState.phaseIndex + 1) + }, MOMENT_DISPLAY_TIMEOUT_MS) } + else { + + _setPhase(gameState.phaseIndex + 1) + } } - _setPhase(gameState.phaseIndex + 1) } export function previousPhase() {