WIP: Mostly working version that uses the eventqueue

We've mostly got this working. Events get executed in the array of
events. We don't use setTimeout anymore thankfully. I'm not in love with
this system, it still isn't straight forward about what events happen
where, but I do at least now have an ability to better control the flow.
This commit is contained in:
Tyler Marques 2025-06-13 12:12:03 -07:00
parent a929bf5ee6
commit ef3e3a79fe
No known key found for this signature in database
GPG key ID: CB99EDCF41D3016F
11 changed files with 336 additions and 393 deletions

View file

@ -49,26 +49,15 @@ export function _setPhase(phaseIndex: number) {
// Clear any existing animations to prevent overlap
// (playbackTimer is replaced by event queue system)
// Ensure any open two-power conversations are closed immediately before resetting event queue
if (gameState.isDisplayingMoment) {
closeTwoPowerConversation(true); // immediate = true
}
// Reset event queue for new phase with cleanup callback
gameState.eventQueue.reset(() => {
// Ensure proper state cleanup when events are canceled
gameState.messagesPlaying = false;
gameState.isAnimating = false;
});
if (gameState.isPlaying) {
gameState.eventQueue.start();
}
// Reset animation state (redundant but kept for clarity)
gameState.isAnimating = false;
gameState.messagesPlaying = false;
// Advance the phase index
gameState.phaseIndex++;
if (config.isDebugMode && gameState.gameData) {
@ -126,7 +115,6 @@ export function togglePlayback(explicitSet: boolean | undefined = undefined) {
if (gameState.currentPhase.messages && gameState.currentPhase.messages.length) {
// Show messages with stepwise animation
logger.log(`Playing ${gameState.currentPhase.messages.length} messages from phase ${gameState.phaseIndex + 1}/${gameState.gameData.phases.length}`);
gameState.nextPhaseScheduled = true
displayPhase()
} else {
// No messages, go straight to unit animations
@ -136,15 +124,13 @@ export function togglePlayback(explicitSet: boolean | undefined = undefined) {
if (gameState.cameraPanAnim) gameState.cameraPanAnim.getAll()[0].pause();
playBtn.textContent = "▶ Play";
// (playbackTimer is replaced by event queue system)
// Stop background audio when pausing
stopBackgroundAudio();
// Ensure any open two-power conversations are closed when pausing
if (gameState.isDisplayingMoment) {
closeTwoPowerConversation(true); // immediate = true
}
closeTwoPowerConversation(true); // immediate = true
// Stop and reset event queue when pausing with cleanup
gameState.eventQueue.stop();
gameState.eventQueue.reset(() => {
@ -152,7 +138,7 @@ export function togglePlayback(explicitSet: boolean | undefined = undefined) {
gameState.messagesPlaying = false;
gameState.isAnimating = false;
});
gameState.messagesPlaying = false;
prevBtn.disabled = false;
nextBtn.disabled = false;
@ -160,32 +146,42 @@ export function togglePlayback(explicitSet: boolean | undefined = undefined) {
}
export function scheduleNextPhase() {
gameState.eventQueue.scheduleDelay(0, nextPhase)
}
export function scheduleSummarySpeech() {
// Delay speech in streaming mode
gameState.eventQueue.scheduleDelay(config.speechDelay, () => {
// Speak the summary and advance after
speakSummary()
}, `speech-delay-${Date.now()}`);
}
/** Handels all the end-of-phase items before calling _setPhase().
*
*/
export function nextPhase() {
if (!gameState.isDisplayingMoment && gameState.gameData && gameState.momentsData) {
let moment = gameState.checkPhaseHasMoment(gameState.gameData.phases[gameState.phaseIndex].name)
if (moment !== null && moment.interest_score >= MOMENT_THRESHOLD && moment.powers_involved.length >= 2) {
moment.hasBeenDisplayed = true
let moment = gameState.checkPhaseHasMoment(gameState.gameData.phases[gameState.phaseIndex].name)
if (moment !== null && moment.interest_score >= MOMENT_THRESHOLD && moment.powers_involved.length >= 2) {
const power1 = moment.powers_involved[0];
const power2 = moment.powers_involved[1];
const power1 = moment.powers_involved[0];
const power2 = moment.powers_involved[1];
showTwoPowerConversation({
power1: power1,
power2: power2,
moment: moment
})
if (gameState.isPlaying) {
// The conversation will close itself and then advance the phase
// We don't need to schedule a timeout here anymore since the conversation manages its own timing
console.log("Two-power conversation will handle its own timing and phase advancement");
} else {
_setPhase(gameState.phaseIndex + 1)
showTwoPowerConversation({
power1: power1,
power2: power2,
moment: moment,
onClose: () => {
// Schedule the speaking of the summary after the conversation closes
scheduleSummarySpeech();
if (gameState.isPlaying) _setPhase(gameState.phaseIndex + 1)
}
} else {
_setPhase(gameState.phaseIndex + 1)
}
})
} else {
console.log("not moving")
// No conversation to show, proceed with normal flow
scheduleSummarySpeech();
_setPhase(gameState.phaseIndex + 1)
}
}
@ -220,8 +216,6 @@ export function displayPhase(skipMessages = false) {
// Only get previous phase if not the first phase
const prevIndex = isFirstPhase ? null : (index > 0 ? index - 1 : null);
const previousPhase = prevIndex !== null ? gameState.gameData.phases[prevIndex] : null;
updatePhaseDisplay()
// Update supply centers
@ -271,7 +265,6 @@ export function displayPhase(skipMessages = false) {
} else {
logger.log("No animations for this phase transition");
}
gameState.nextPhaseScheduled = false;
}
@ -293,68 +286,6 @@ export function displayPhaseWithAnimation() {
}
/**
* Advances to the next phase in the game sequence
* Handles speaking summaries and transitioning to the next phase
*/
export function advanceToNextPhase() {
// If we're not "playing" through the game, just skipping phases, move everything along
if (!gameState.isPlaying) {
nextPhase()
}
if (!gameState.gameData || !gameState.gameData.phases || gameState.phaseIndex < 0) {
logger.log("Cannot advance phase: invalid game state");
return;
}
// Get current phase
const currentPhase = gameState.gameData.phases[gameState.phaseIndex];
console.log(`Current phase: ${currentPhase.name}, Has summary: ${Boolean(currentPhase.summary)}`);
if (currentPhase.summary) {
console.log(`Summary preview: "${currentPhase.summary.substring(0, 50)}..."`);
}
if (config.isDebugMode) {
console.log(`Processing phase transition for ${currentPhase.name}`);
}
const speechDelay = config.speechDelay
// First show summary if available
if (currentPhase.summary && currentPhase.summary.trim() !== '') {
// Delay speech in streaming mode
gameState.eventQueue.scheduleDelay(speechDelay, () => {
// Speak the summary and advance after
if (!gameState.isSpeaking) {
speakSummary(currentPhase.summary)
.then(() => {
console.log("Speech completed successfully");
if (gameState.isPlaying) {
nextPhase();
}
})
.catch((error) => {
console.error("Speech failed with error:", error);
if (gameState.isPlaying) {
nextPhase();
}
}).finally(() => {
// Any cleanup code here
});
} else {
console.error("Attempted to start speaking when already speaking...");
}
}, `speech-delay-${Date.now()}`);
} else {
console.log("No summary available, skipping speech");
// No summary to speak, advance immediately
nextPhase();
}
}
function displayFinalPhase() {
if (!gameState.gameData || !gameState.gameData.phases || gameState.gameData.phases.length === 0) {
return;