Major architectural improvements: - Simplified replay system from ~1,450 lines to ~320 lines (78% reduction) - Removed scene reconstruction complexity in favor of reusing game logic - Added isReplayMode parameter to Level1 and Ship constructors - Level1.initialize() now creates scene for both game and replay modes - ReplayPlayer simplified to find existing meshes instead of loading assets Replay system changes: - ReplayManager now uses Level1.initialize() to populate scene - Deleted obsolete files: assetCache.ts, ReplayAssetRegistry.ts - Removed full scene deserialization code from LevelDeserializer - Fixed keyboard input error when initializing in replay mode - Physics bodies converted to ANIMATED after Level1 creates them UI simplification for new users: - Hidden level editor, settings, test scene, and replay buttons - Hidden "Create New Level" link - Filtered level selector to only show recruit and pilot difficulties - Clean, focused experience for first-time users Technical improvements: - PhysicsRecorder now accepts LevelConfig via constructor - Removed sessionStorage dependency for level state - Fixed Color3 alpha property error in levelSerializer - Cleaned up unused imports and dependencies 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
155 lines
4.9 KiB
TypeScript
155 lines
4.9 KiB
TypeScript
import { getSavedLevels } from "./levelEditor";
|
|
import { LevelConfig } from "./levelConfig";
|
|
import debugLog from './debug';
|
|
|
|
const SELECTED_LEVEL_KEY = 'space-game-selected-level';
|
|
|
|
/**
|
|
* Populate the level selection screen with saved levels
|
|
*/
|
|
export function populateLevelSelector(): boolean {
|
|
const container = document.getElementById('levelCardsContainer');
|
|
if (!container) {
|
|
console.warn('Level cards container not found');
|
|
return false;
|
|
}
|
|
|
|
const savedLevels = getSavedLevels();
|
|
|
|
// Filter to only show recruit and pilot difficulty levels
|
|
const filteredLevels = new Map<string, LevelConfig>();
|
|
for (const [name, config] of savedLevels.entries()) {
|
|
if (config.difficulty === 'recruit' || config.difficulty === 'pilot') {
|
|
filteredLevels.set(name, config);
|
|
}
|
|
}
|
|
|
|
if (filteredLevels.size === 0) {
|
|
container.innerHTML = `
|
|
<div style="
|
|
grid-column: 1 / -1;
|
|
text-align: center;
|
|
padding: 40px 20px;
|
|
color: #ccc;
|
|
">
|
|
<h2 style="margin-bottom: 20px;">No Levels Found</h2>
|
|
<p style="margin-bottom: 30px;">Create your first level to get started!</p>
|
|
<a href="#/editor" style="
|
|
display: inline-block;
|
|
padding: 15px 30px;
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
text-decoration: none;
|
|
border-radius: 5px;
|
|
font-weight: bold;
|
|
font-size: 1.1em;
|
|
">Go to Level Editor</a>
|
|
</div>
|
|
`;
|
|
return false;
|
|
}
|
|
|
|
// Create level cards
|
|
let html = '';
|
|
for (const [name, config] of filteredLevels.entries()) {
|
|
const timestamp = config.timestamp ? new Date(config.timestamp).toLocaleDateString() : '';
|
|
const description = config.metadata?.description || `${config.asteroids.length} asteroids • ${config.planets.length} planets`;
|
|
|
|
html += `
|
|
<div class="level-card">
|
|
<h2>${name}</h2>
|
|
<div style="font-size: 0.9em; color: #aaa; margin: 10px 0;">
|
|
Difficulty: ${config.difficulty}
|
|
</div>
|
|
<p>${description}</p>
|
|
${timestamp ? `<div style="font-size: 0.8em; color: #888; margin-bottom: 10px;">${timestamp}</div>` : ''}
|
|
<button class="level-button" data-level="${name}">Play Level</button>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
container.innerHTML = html;
|
|
|
|
// Add event listeners to level buttons
|
|
container.querySelectorAll('.level-button').forEach(button => {
|
|
button.addEventListener('click', (e) => {
|
|
const levelName = (e.target as HTMLButtonElement).dataset.level;
|
|
if (levelName) {
|
|
selectLevel(levelName);
|
|
}
|
|
});
|
|
});
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Initialize level button listeners (for any dynamically created buttons)
|
|
*/
|
|
export function initializeLevelButtons(): void {
|
|
document.querySelectorAll('.level-button').forEach(button => {
|
|
if (!button.hasAttribute('data-listener-attached')) {
|
|
button.setAttribute('data-listener-attached', 'true');
|
|
button.addEventListener('click', (e) => {
|
|
const levelName = (e.target as HTMLButtonElement).dataset.level;
|
|
if (levelName) {
|
|
selectLevel(levelName);
|
|
}
|
|
});
|
|
}
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Select a level and store it for Level1 to use
|
|
*/
|
|
export function selectLevel(levelName: string): void {
|
|
const savedLevels = getSavedLevels();
|
|
const config = savedLevels.get(levelName);
|
|
|
|
if (!config) {
|
|
console.error(`Level "${levelName}" not found`);
|
|
alert(`Level "${levelName}" not found!`);
|
|
return;
|
|
}
|
|
|
|
// Store selected level name
|
|
sessionStorage.setItem(SELECTED_LEVEL_KEY, levelName);
|
|
|
|
debugLog(`Selected level: ${levelName}`);
|
|
|
|
// Trigger level start (the existing code will pick this up)
|
|
const event = new CustomEvent('levelSelected', { detail: { levelName, config } });
|
|
window.dispatchEvent(event);
|
|
}
|
|
|
|
/**
|
|
* Get the currently selected level configuration
|
|
*/
|
|
export function getSelectedLevel(): { name: string, config: LevelConfig } | null {
|
|
const levelName = sessionStorage.getItem(SELECTED_LEVEL_KEY);
|
|
if (!levelName) return null;
|
|
|
|
const savedLevels = getSavedLevels();
|
|
const config = savedLevels.get(levelName);
|
|
|
|
if (!config) return null;
|
|
|
|
return { name: levelName, config };
|
|
}
|
|
|
|
/**
|
|
* Clear the selected level
|
|
*/
|
|
export function clearSelectedLevel(): void {
|
|
sessionStorage.removeItem(SELECTED_LEVEL_KEY);
|
|
}
|
|
|
|
/**
|
|
* Check if there are any saved levels
|
|
*/
|
|
export function hasSavedLevels(): boolean {
|
|
const savedLevels = getSavedLevels();
|
|
return savedLevels.size > 0;
|
|
}
|