mirror of
https://github.com/GoodStartLabs/AI_Diplomacy.git
synced 2026-04-19 12:58:09 +00:00
FIX: Map jump bug, leaderboard misname
Reintroducing the fix for the bug in tween.js (issue 677) that causes the map to jump at the end of the tween. Leaderboard name was used twice in the code base, once for the modal at the beginning of games, and also for the rotating display in the bottom left. I've removed the modal at the beggining of the game as its data is stale and not updated yet. I've renamed the bottom left to 'rotatingDisplay' and the bottom right to 'leaderboard'.
This commit is contained in:
parent
d52753569b
commit
b8a8f5a6f2
13 changed files with 213 additions and 264 deletions
|
|
@ -15,7 +15,6 @@
|
|||
<div class="top-controls">
|
||||
<div>
|
||||
<button id="load-btn">Load Game</button>
|
||||
<button id="standings-btn">📊 Leaderboard</button>
|
||||
<button id="prev-btn" disabled>← Prev</button>
|
||||
<button id="next-btn" disabled>Next →</button>
|
||||
<button id="play-btn" disabled>▶ Play</button>
|
||||
|
|
@ -30,9 +29,8 @@
|
|||
</div>
|
||||
<div id="map-view" class="map-view"></div>
|
||||
<input type="file" id="file-input" accept=".json">
|
||||
<div id="info-panel"></div>
|
||||
<!-- New leaderboard element -->
|
||||
<div id="leaderboard"></div>
|
||||
<div id="rotating-display"></div>
|
||||
<!-- Chat windows container -->
|
||||
<div id="chat-container"></div>
|
||||
<!-- Add this after the info-panel div -->
|
||||
|
|
|
|||
|
|
@ -10,10 +10,9 @@ let isAudioInitialized = false;
|
|||
* Only loads in streaming mode to avoid unnecessary downloads
|
||||
*/
|
||||
export function initializeBackgroundAudio(): void {
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
|
||||
if (!isStreamingMode || isAudioInitialized) {
|
||||
return;
|
||||
if (isAudioInitialized) {
|
||||
throw new Error("Attempted to init audio twice.")
|
||||
}
|
||||
|
||||
isAudioInitialized = true;
|
||||
|
|
|
|||
77
ai_animation/src/components/leaderboard.ts
Normal file
77
ai_animation/src/components/leaderboard.ts
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
import { gameState } from "../gameState";
|
||||
import { getPowerDisplayName } from "../utils/powerNames";
|
||||
import { PowerENUM } from "../types/map";
|
||||
|
||||
|
||||
let containerElement = document.getElementById("leaderboard")
|
||||
|
||||
export function initLeaderBoard() {
|
||||
if (!containerElement) {
|
||||
console.error(`Container element with ID "${containerId}" not found`);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// Updated function to update leaderboard with useful information and smooth transitions
|
||||
export function updateLeaderboard() {
|
||||
const totalPhases = gameState.gameData?.phases?.length || 0;
|
||||
const currentPhaseNumber = gameState.phaseIndex + 1;
|
||||
const phaseName = gameState.gameData?.phases?.[gameState.phaseIndex]?.name || 'Unknown';
|
||||
|
||||
// Add fade-out transition
|
||||
containerElement.style.transition = 'opacity 0.3s ease-out';
|
||||
containerElement.style.opacity = '0';
|
||||
|
||||
// Update content after fade-out
|
||||
setTimeout(() => {
|
||||
// Get supply center counts for the current phase
|
||||
const scCounts = getSupplyCenterCounts();
|
||||
|
||||
containerElement.innerHTML = `
|
||||
<div><strong>Power:</strong> <span class="power-${gameState.currentPower.toLowerCase()}">${getPowerDisplayName(gameState.currentPower)}</span></div>
|
||||
<div><strong>Current Phase:</strong> ${phaseName}</div>
|
||||
<hr/>
|
||||
<h4>Supply Center Counts</h4>
|
||||
<ul style="list-style:none;padding-left:0;margin:0;">
|
||||
<li><span class="power-austria">${getPowerDisplayName(PowerENUM.AUSTRIA)}:</span> ${scCounts.AUSTRIA || 0}</li>
|
||||
<li><span class="power-england">${getPowerDisplayName(PowerENUM.ENGLAND)}:</span> ${scCounts.ENGLAND || 0}</li>
|
||||
<li><span class="power-france">${getPowerDisplayName(PowerENUM.FRANCE)}:</span> ${scCounts.FRANCE || 0}</li>
|
||||
<li><span class="power-germany">${getPowerDisplayName(PowerENUM.GERMANY)}:</span> ${scCounts.GERMANY || 0}</li>
|
||||
<li><span class="power-italy">${getPowerDisplayName(PowerENUM.ITALY)}:</span> ${scCounts.ITALY || 0}</li>
|
||||
<li><span class="power-russia">${getPowerDisplayName(PowerENUM.RUSSIA)}:</span> ${scCounts.RUSSIA || 0}</li>
|
||||
<li><span class="power-turkey">${getPowerDisplayName(PowerENUM.TURKEY)}:</span> ${scCounts.TURKEY || 0}</li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
// Fade back in
|
||||
containerElement.style.opacity = '1';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Helper function to count supply centers for each power
|
||||
function getSupplyCenterCounts() {
|
||||
const counts = {
|
||||
AUSTRIA: 0,
|
||||
ENGLAND: 0,
|
||||
FRANCE: 0,
|
||||
GERMANY: 0,
|
||||
ITALY: 0,
|
||||
RUSSIA: 0,
|
||||
TURKEY: 0
|
||||
};
|
||||
|
||||
// Get current phase's supply center data
|
||||
const centers = gameState.gameData?.phases?.[gameState.phaseIndex]?.state?.centers;
|
||||
|
||||
if (centers) {
|
||||
// Count supply centers for each power
|
||||
Object.entries(centers).forEach(([power, provinces]) => {
|
||||
if (power && Array.isArray(provinces)) {
|
||||
counts[power as keyof typeof counts] = provinces.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
|
|
@ -40,7 +40,7 @@ let isInitialized = false;
|
|||
* Initialize the rotating display
|
||||
* @param containerId The ID of the container element
|
||||
*/
|
||||
export function initRotatingDisplay(containerId: string): void {
|
||||
export function initRotatingDisplay(containerId: string = 'rotating-display'): void {
|
||||
containerElement = document.getElementById(containerId);
|
||||
|
||||
if (!containerElement) {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const config = {
|
|||
|
||||
// Streaming mode specific timing
|
||||
get streamingPlaybackSpeed(): number {
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
const isStreamingMode = import.meta.env.MODE === 'production'
|
||||
return isStreamingMode ? 1000 : this.playbackSpeed; // Slower for streaming
|
||||
},
|
||||
|
||||
|
|
|
|||
|
|
@ -71,9 +71,8 @@ if (null === mapView) throw new Error("Element with ID 'map-view' not found");
|
|||
export const leaderboard = document.getElementById('leaderboard');
|
||||
if (null === leaderboard) throw new Error("Element with ID 'leaderboard' not found");
|
||||
|
||||
export const standingsBtn = document.getElementById('standings-btn');
|
||||
if (null === standingsBtn) throw new Error("Element with ID 'standings-btn' not found");
|
||||
|
||||
export const rotatingDisplay = document.getElementById('rotating-display');
|
||||
if (null === rotatingDisplay) throw new Error("Element with ID 'rotating-display' not found");
|
||||
// Debug menu elements
|
||||
export const debugMenu = document.getElementById('debug-menu');
|
||||
if (null === debugMenu) throw new Error("Element with ID 'debug-menu' not found");
|
||||
|
|
|
|||
|
|
@ -231,10 +231,7 @@ export function updateChatWindows(phase: any, stepMessages = false) {
|
|||
index++; // Only increment after animation completes
|
||||
|
||||
// Schedule next message with proper delay
|
||||
// In streaming mode, add extra delay to prevent message overlap
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
const messageDelay = isStreamingMode ? config.effectivePlaybackSpeed : config.effectivePlaybackSpeed / 2;
|
||||
setTimeout(showNext, messageDelay);
|
||||
setTimeout(showNext, config.effectivePlaybackSpeed);
|
||||
};
|
||||
|
||||
// Add the message with word animation
|
||||
|
|
@ -398,14 +395,13 @@ function animateMessageWords(message: string, contentSpanId: string, targetPower
|
|||
// Longer words get slightly longer display time
|
||||
const wordLength = words[wordIndex - 1].length;
|
||||
// In streaming mode, use a more consistent delay to prevent overlap
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
const baseDelay = isStreamingMode ? 150 : config.effectivePlaybackSpeed / 10;
|
||||
const baseDelay = config.effectivePlaybackSpeed
|
||||
const delay = Math.max(50, Math.min(200, baseDelay * (wordLength / 4)));
|
||||
setTimeout(addNextWord, delay);
|
||||
|
||||
// Scroll to ensure newest content is visible
|
||||
// Use requestAnimationFrame to batch DOM updates in streaming mode
|
||||
const isStreamingModeForScroll = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
const isStreamingModeForScroll = import.meta.env.MODE === 'production' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
if (isStreamingModeForScroll) {
|
||||
requestAnimationFrame(() => {
|
||||
messagesContainer.scrollTop = messagesContainer.scrollHeight;
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { StandingsData, StandingsEntry, SortBy, SortDirection, SortOptions } from '../types/standings';
|
||||
import { gameState } from '../gameState';
|
||||
import { logger } from '../logger';
|
||||
import { standingsBtn } from '../domElements';
|
||||
//import { standingsBtn } from '../domElements';
|
||||
import { getPowerDisplayName } from '../utils/powerNames';
|
||||
import { PowerENUM } from '../types/map';
|
||||
|
||||
|
|
|
|||
|
|
@ -9,8 +9,8 @@ import { logger } from "./logger";
|
|||
import { OrbitControls } from "three/examples/jsm/Addons.js";
|
||||
import { displayInitialPhase, togglePlayback } from "./phase";
|
||||
import { Tween, Group as TweenGroup } from "@tweenjs/tween.js";
|
||||
import { hideStandingsBoard, } from "./domElements/standingsBoard";
|
||||
import { MomentsDataSchema, Moment, NormalizedMomentsData } from "./types/moments";
|
||||
import { updateLeaderboard } from "./components/leaderboard";
|
||||
|
||||
//FIXME: This whole file is a mess. Need to organize and format
|
||||
|
||||
|
|
@ -339,13 +339,12 @@ class GameState {
|
|||
})
|
||||
.then(() => {
|
||||
console.log(`Game file with id ${gameId} loaded and parsed successfully`);
|
||||
// Explicitly hide standings board after loading game
|
||||
hideStandingsBoard();
|
||||
// Update rotating display and relationship popup with game data
|
||||
if (this.gameData) {
|
||||
updateRotatingDisplay(this.gameData, this.phaseIndex, this.currentPower);
|
||||
this.gameId = gameId
|
||||
updateGameIdDisplay();
|
||||
updateLeaderboard();
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
|
|
@ -377,8 +376,6 @@ class GameState {
|
|||
throw Error("Cannot find mapView element, unable to continue.")
|
||||
}
|
||||
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
|
||||
this.scene.background = new THREE.Color(0x87CEEB);
|
||||
|
||||
// Camera
|
||||
|
|
@ -392,25 +389,14 @@ class GameState {
|
|||
|
||||
// Renderer with streaming optimizations
|
||||
this.renderer = new THREE.WebGLRenderer({
|
||||
antialias: !isStreamingMode, // Disable antialiasing in streaming mode
|
||||
powerPreference: "high-performance",
|
||||
preserveDrawingBuffer: isStreamingMode // Required for streaming
|
||||
});
|
||||
this.renderer.setSize(mapView.clientWidth, mapView.clientHeight);
|
||||
|
||||
// Force lower pixel ratio for streaming to reduce GPU load
|
||||
if (isStreamingMode) {
|
||||
this.renderer.setPixelRatio(1);
|
||||
} else {
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
}
|
||||
|
||||
mapView.appendChild(this.renderer.domElement);
|
||||
|
||||
// Controls with streaming optimizations
|
||||
this.camControls = new OrbitControls(this.camera, this.renderer.domElement);
|
||||
this.camControls.enableDamping = !isStreamingMode; // Disable damping for immediate response
|
||||
this.camControls.dampingFactor = 0.05;
|
||||
this.camControls.screenSpacePanning = true;
|
||||
this.camControls.minDistance = 100;
|
||||
this.camControls.maxDistance = 2000;
|
||||
|
|
|
|||
|
|
@ -20,67 +20,5 @@ class Logger {
|
|||
console.log(msg);
|
||||
}
|
||||
|
||||
// Updated function to update info panel with useful information and smooth transitions
|
||||
updateInfoPanel = () => {
|
||||
const totalPhases = gameState.gameData?.phases?.length || 0;
|
||||
const currentPhaseNumber = gameState.phaseIndex + 1;
|
||||
const phaseName = gameState.gameData?.phases?.[gameState.phaseIndex]?.name || 'Unknown';
|
||||
|
||||
// Add fade-out transition
|
||||
this.infoPanel.style.transition = 'opacity 0.3s ease-out';
|
||||
this.infoPanel.style.opacity = '0';
|
||||
|
||||
// Update content after fade-out
|
||||
setTimeout(() => {
|
||||
// Get supply center counts for the current phase
|
||||
const scCounts = this.getSupplyCenterCounts();
|
||||
|
||||
this.infoPanel.innerHTML = `
|
||||
<div><strong>Power:</strong> <span class="power-${gameState.currentPower.toLowerCase()}">${getPowerDisplayName(gameState.currentPower)}</span></div>
|
||||
<div><strong>Current Phase:</strong> ${phaseName}</div>
|
||||
<hr/>
|
||||
<h4>Supply Center Counts</h4>
|
||||
<ul style="list-style:none;padding-left:0;margin:0;">
|
||||
<li><span class="power-austria">${getPowerDisplayName(PowerENUM.AUSTRIA)}:</span> ${scCounts.AUSTRIA || 0}</li>
|
||||
<li><span class="power-england">${getPowerDisplayName(PowerENUM.ENGLAND)}:</span> ${scCounts.ENGLAND || 0}</li>
|
||||
<li><span class="power-france">${getPowerDisplayName(PowerENUM.FRANCE)}:</span> ${scCounts.FRANCE || 0}</li>
|
||||
<li><span class="power-germany">${getPowerDisplayName(PowerENUM.GERMANY)}:</span> ${scCounts.GERMANY || 0}</li>
|
||||
<li><span class="power-italy">${getPowerDisplayName(PowerENUM.ITALY)}:</span> ${scCounts.ITALY || 0}</li>
|
||||
<li><span class="power-russia">${getPowerDisplayName(PowerENUM.RUSSIA)}:</span> ${scCounts.RUSSIA || 0}</li>
|
||||
<li><span class="power-turkey">${getPowerDisplayName(PowerENUM.TURKEY)}:</span> ${scCounts.TURKEY || 0}</li>
|
||||
</ul>
|
||||
`;
|
||||
|
||||
// Fade back in
|
||||
this.infoPanel.style.opacity = '1';
|
||||
}, 300);
|
||||
}
|
||||
|
||||
// Helper function to count supply centers for each power
|
||||
getSupplyCenterCounts = () => {
|
||||
const counts = {
|
||||
AUSTRIA: 0,
|
||||
ENGLAND: 0,
|
||||
FRANCE: 0,
|
||||
GERMANY: 0,
|
||||
ITALY: 0,
|
||||
RUSSIA: 0,
|
||||
TURKEY: 0
|
||||
};
|
||||
|
||||
// Get current phase's supply center data
|
||||
const centers = gameState.gameData?.phases?.[gameState.phaseIndex]?.state?.centers;
|
||||
|
||||
if (centers) {
|
||||
// Count supply centers for each power
|
||||
Object.entries(centers).forEach(([power, provinces]) => {
|
||||
if (power && Array.isArray(provinces)) {
|
||||
counts[power as keyof typeof counts] = provinces.length;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return counts;
|
||||
}
|
||||
}
|
||||
export const logger = new Logger()
|
||||
|
|
|
|||
|
|
@ -5,7 +5,6 @@ import { gameState } from "./gameState";
|
|||
import { logger } from "./logger";
|
||||
import { loadBtn, prevBtn, nextBtn, speedSelector, fileInput, playBtn, mapView, loadGameBtnFunction } from "./domElements";
|
||||
import { updateChatWindows } from "./domElements/chatWindows";
|
||||
import { initStandingsBoard, hideStandingsBoard, showStandingsBoard } from "./domElements/standingsBoard";
|
||||
import { displayPhaseWithAnimation, advanceToNextPhase, resetToPhase, nextPhase, previousPhase, togglePlayback } from "./phase";
|
||||
import { config } from "./config";
|
||||
import { Tween, Group, Easing } from "@tweenjs/tween.js";
|
||||
|
|
@ -13,84 +12,50 @@ import { initRotatingDisplay, updateRotatingDisplay } from "./components/rotatin
|
|||
import { closeTwoPowerConversation, showTwoPowerConversation } from "./components/twoPowerConversation";
|
||||
import { PowerENUM } from "./types/map";
|
||||
import { debugMenuInstance } from "./debug/debugMenu";
|
||||
import { sineWave } from "./utils/timing";
|
||||
import { initializeBackgroundAudio, startBackgroundAudio } from "./backgroundAudio";
|
||||
import { initLeaderBoard, updateLeaderboard } from "./components/leaderboard";
|
||||
|
||||
//TODO: Create a function that finds a suitable unit location within a given polygon, for placing units better
|
||||
// Currently the location for label, unit, and SC are all the same manually picked location
|
||||
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true'
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE
|
||||
|
||||
// --- INITIALIZE SCENE ---
|
||||
function initScene() {
|
||||
gameState.createThreeScene()
|
||||
|
||||
// Initialize background audio for streaming mode
|
||||
initializeBackgroundAudio();
|
||||
|
||||
// Enable audio on first user interaction (to comply with browser autoplay policies)
|
||||
let audioEnabled = false;
|
||||
const enableAudio = () => {
|
||||
if (!audioEnabled) {
|
||||
console.log('User interaction detected, audio enabled');
|
||||
audioEnabled = true;
|
||||
// Create and play a silent audio to unlock audio context
|
||||
const silentAudio = new Audio();
|
||||
silentAudio.volume = 0;
|
||||
silentAudio.play().catch(() => {});
|
||||
|
||||
// Start background audio in streaming mode
|
||||
if (isStreamingMode) {
|
||||
startBackgroundAudio();
|
||||
}
|
||||
|
||||
// Remove the listener after first interaction
|
||||
document.removeEventListener('click', enableAudio);
|
||||
document.removeEventListener('keydown', enableAudio);
|
||||
}
|
||||
};
|
||||
|
||||
document.addEventListener('click', enableAudio);
|
||||
document.addEventListener('keydown', enableAudio);
|
||||
initializeBackgroundAudio()
|
||||
|
||||
|
||||
// Initialize standings board
|
||||
initStandingsBoard();
|
||||
// TODO: Re-add standinds board when it has an actual use, and not stale data
|
||||
//
|
||||
//initStandingsBoard();
|
||||
|
||||
|
||||
// Load coordinate data, then build the map
|
||||
gameState.loadBoardState().then(() => {
|
||||
initMap(gameState.scene).then(() => {
|
||||
// Update info panel with initial power information
|
||||
logger.updateInfoPanel();
|
||||
updateLeaderboard();
|
||||
|
||||
// Initialize rotating display
|
||||
initRotatingDisplay('leaderboard');
|
||||
|
||||
// Only show standings board at startup if no game is loaded
|
||||
if (!gameState.gameData || !gameState.gameData.phases || gameState.gameData.phases.length === 0) {
|
||||
showStandingsBoard();
|
||||
}
|
||||
initRotatingDisplay();
|
||||
|
||||
gameState.cameraPanAnim = createCameraPan()
|
||||
|
||||
// Load default game file if in debug mode
|
||||
if (config.isDebugMode || isStreamingMode) {
|
||||
gameState.loadGameFile(0);
|
||||
|
||||
// Initialize info panel
|
||||
logger.updateInfoPanel();
|
||||
}
|
||||
|
||||
// Initialize debug menu if in debug mode
|
||||
if (config.isDebugMode) {
|
||||
debugMenuInstance.show();
|
||||
}
|
||||
if (isStreamingMode) {
|
||||
startBackgroundAudio()
|
||||
setTimeout(() => {
|
||||
togglePlayback();
|
||||
// Try to start background audio when auto-starting
|
||||
startBackgroundAudio();
|
||||
}, 10000) // Increased delay to 10 seconds for Chrome to fully load in Docker
|
||||
togglePlayback()
|
||||
}, 2000)
|
||||
}
|
||||
})
|
||||
}).catch(err => {
|
||||
|
|
@ -102,8 +67,7 @@ function initScene() {
|
|||
window.addEventListener('resize', onWindowResize);
|
||||
|
||||
// Kick off animation loop
|
||||
requestAnimationFrame(animate);
|
||||
|
||||
animate();
|
||||
}
|
||||
|
||||
function createCameraPan(): Group {
|
||||
|
|
@ -145,24 +109,14 @@ function createCameraPan(): Group {
|
|||
* Main animation loop that runs continuously
|
||||
* Handles camera movement, animations, and game state transitions
|
||||
*/
|
||||
let lastTime = 0;
|
||||
function animate(currentTime: number = 0) {
|
||||
// Calculate delta time in seconds
|
||||
let deltaTime = lastTime ? (currentTime - lastTime) / 1000 : 0;
|
||||
lastTime = currentTime;
|
||||
|
||||
// Clamp delta time to prevent animation jumps when tab loses focus
|
||||
deltaTime = Math.min(deltaTime, config.animation.maxDeltaTime);
|
||||
|
||||
// Update global timing in gameState
|
||||
gameState.deltaTime = deltaTime;
|
||||
gameState.globalTime = currentTime / 1000; // Store in seconds
|
||||
function animate() {
|
||||
|
||||
requestAnimationFrame(animate);
|
||||
if (gameState.isPlaying) {
|
||||
// Update the camera angle with delta time
|
||||
// Pass currentTime to update() to fix the Tween.js bug properly
|
||||
gameState.cameraPanAnim.update(currentTime);
|
||||
// Update the camera angle
|
||||
// FIXME: This has to call the update functino twice inorder to avoid a bug in Tween.js, see here https://github.com/tweenjs/tween.js/issues/677
|
||||
gameState.cameraPanAnim.update();
|
||||
gameState.cameraPanAnim.update();
|
||||
|
||||
} else {
|
||||
// Manual camera controls when not in playback mode
|
||||
|
|
@ -180,8 +134,8 @@ function animate(currentTime: number = 0) {
|
|||
console.log("All unit animations have completed");
|
||||
}
|
||||
|
||||
// Call update on each active animation with current time
|
||||
gameState.unitAnimations.forEach((anim) => anim.update(currentTime))
|
||||
// Call update on each active animation
|
||||
gameState.unitAnimations.forEach((anim) => anim.update())
|
||||
|
||||
}
|
||||
|
||||
|
|
@ -191,29 +145,33 @@ function animate(currentTime: number = 0) {
|
|||
console.log(`Scheduling next phase in ${config.effectivePlaybackSpeed}ms`);
|
||||
gameState.nextPhaseScheduled = true;
|
||||
gameState.playbackTimer = setTimeout(() => {
|
||||
try {
|
||||
advanceToNextPhase()
|
||||
} catch {
|
||||
// FIXME: This is a dumb patch for us not being able to find the unit we expect to find.
|
||||
// We should instead bee figuring out why units aren't where we expect them to be when the engine has said that is a valid move
|
||||
nextPhase()
|
||||
gameState.nextPhaseScheduled;
|
||||
}
|
||||
}, config.effectivePlaybackSpeed);
|
||||
}
|
||||
// Update any pulsing or wave animations on supply centers or units
|
||||
// In streaming mode, reduce animation frequency
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
const frameSkip = isStreamingMode ? 2 : 1; // Skip every other frame in streaming
|
||||
|
||||
if (gameState.scene.userData.animatedObjects && (Math.floor(currentTime / 16.67) % frameSkip === 0)) {
|
||||
if (gameState.scene.userData.animatedObjects) {
|
||||
gameState.scene.userData.animatedObjects.forEach(obj => {
|
||||
if (obj.userData.pulseAnimation) {
|
||||
const anim = obj.userData.pulseAnimation;
|
||||
// Use delta time for consistent animation speed regardless of frame rate
|
||||
anim.time += anim.speed * deltaTime * frameSkip; // Compensate for skipped frames
|
||||
anim.time += anim.speed;
|
||||
if (obj.userData.glowMesh) {
|
||||
const pulseValue = sineWave(config.animation.supplyPulseFrequency, anim.time, anim.intensity, 0.5);
|
||||
const pulseValue = Math.sin(anim.time) * anim.intensity + 0.5;
|
||||
obj.userData.glowMesh.material.opacity = 0.2 + (pulseValue * 0.3);
|
||||
const scale = 1 + (pulseValue * 0.1);
|
||||
obj.userData.glowMesh.scale.set(scale, scale, scale);
|
||||
obj.userData.glowMesh.scale.set(
|
||||
1 + (pulseValue * 0.1),
|
||||
1 + (pulseValue * 0.1),
|
||||
1 + (pulseValue * 0.1)
|
||||
);
|
||||
}
|
||||
// Subtle bobbing up/down - reduce in streaming mode
|
||||
const bobAmount = isStreamingMode ? 0.25 : 0.5;
|
||||
obj.position.y = 2 + sineWave(config.animation.supplyPulseFrequency, anim.time, bobAmount);
|
||||
// Subtle bobbing up/down
|
||||
obj.position.y = 2 + Math.sin(anim.time) * 0.5;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
@ -244,7 +202,6 @@ fileInput.addEventListener('change', e => {
|
|||
if (file) {
|
||||
loadGameBtnFunction(file);
|
||||
// Explicitly hide standings board after loading game
|
||||
hideStandingsBoard();
|
||||
// Update rotating display and relationship popup with game data
|
||||
if (gameState.gameData) {
|
||||
updateRotatingDisplay(gameState.gameData, gameState.phaseIndex, gameState.currentPower);
|
||||
|
|
|
|||
|
|
@ -2,7 +2,7 @@ import { gameState } from "./gameState";
|
|||
import { logger } from "./logger";
|
||||
import { updatePhaseDisplay, playBtn, prevBtn, nextBtn } from "./domElements";
|
||||
import { initUnits } from "./units/create";
|
||||
import { updateSupplyCenterOwnership, updateLeaderboard, updateMapOwnership as _updateMapOwnership, updateMapOwnership } from "./map/state";
|
||||
import { updateSupplyCenterOwnership, updateMapOwnership as _updateMapOwnership, updateMapOwnership } from "./map/state";
|
||||
import { updateChatWindows, addToNewsBanner } from "./domElements/chatWindows";
|
||||
import { createAnimationsForNextPhase } from "./units/animate";
|
||||
import { speakSummary } from "./speech";
|
||||
|
|
@ -11,6 +11,7 @@ import { debugMenuInstance } from "./debug/debugMenu";
|
|||
import { showTwoPowerConversation, closeTwoPowerConversation } from "./components/twoPowerConversation";
|
||||
import { closeVictoryModal, showVictoryModal } from "./components/victoryModal";
|
||||
import { notifyPhaseChange } from "./webhooks/phaseNotifier";
|
||||
import { updateLeaderboard } from "./components/leaderboard";
|
||||
import { updateRotatingDisplay } from "./components/rotatingDisplay";
|
||||
|
||||
const MOMENT_THRESHOLD = 8.0
|
||||
|
|
@ -199,7 +200,7 @@ export function displayPhase(skipMessages = false) {
|
|||
|
||||
|
||||
// Update UI elements with smooth transitions
|
||||
updateLeaderboard(currentPhase);
|
||||
updateRotatingDisplay(gameState.gameData, gameState.phaseIndex, gameState.currentPower);
|
||||
_updateMapOwnership();
|
||||
|
||||
// Add phase info to news banner if not already there
|
||||
|
|
@ -210,8 +211,8 @@ export function displayPhase(skipMessages = false) {
|
|||
const phaseInfo = `Phase: ${currentPhase.name}\nSCs: ${currentPhase.state?.centers ? JSON.stringify(currentPhase.state.centers) : 'None'}\nUnits: ${currentPhase.state?.units ? JSON.stringify(currentPhase.state.units) : 'None'}`;
|
||||
console.log(phaseInfo); // Use console.log instead of logger.log
|
||||
|
||||
// Update info panel with power information
|
||||
logger.updateInfoPanel();
|
||||
// Update leaderboard with power information
|
||||
updateLeaderboard();
|
||||
|
||||
// Show messages with animation or immediately based on skipMessages flag
|
||||
if (!skipMessages) {
|
||||
|
|
@ -288,9 +289,7 @@ export function advanceToNextPhase() {
|
|||
console.log(`Processing phase transition for ${currentPhase.name}`);
|
||||
}
|
||||
|
||||
// In streaming mode, add extra delay before speech to ensure phase is fully displayed
|
||||
const isStreamingMode = import.meta.env.VITE_STREAMING_MODE === 'True' || import.meta.env.VITE_STREAMING_MODE === 'true';
|
||||
const speechDelay = isStreamingMode ? 2000 : 0; // 2 second delay in streaming mode
|
||||
const speechDelay = 2000
|
||||
|
||||
// First show summary if available
|
||||
if (currentPhase.summary && currentPhase.summary.trim() !== '') {
|
||||
|
|
|
|||
|
|
@ -99,10 +99,10 @@
|
|||
}
|
||||
|
||||
/* -----------------
|
||||
Info Panel
|
||||
Leaderboard
|
||||
(lower-right)
|
||||
----------------- */
|
||||
#info-panel {
|
||||
#leaderboard {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
right: 10px;
|
||||
|
|
@ -129,7 +129,7 @@
|
|||
Leaderboard
|
||||
(lower-left)
|
||||
----------------- */
|
||||
#leaderboard {
|
||||
#rotating-display {
|
||||
position: absolute;
|
||||
bottom: 20px;
|
||||
left: 10px;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue