All checks were successful
Build / build (push) Successful in 1m16s
- Create debugLog wrapper function in src/debug.ts - Add debug checkbox to settings screen UI - Replace all console.log statements with debugLog calls (153 replacements) - Add debug flag to GameConfig with localStorage persistence - Fix GameConfig to properly load and reset debug setting - Preserve console.error and console.warn calls unchanged - Add Developer section to settings screen with debug toggle - Enable/disable all debug logging via settings UI checkbox Debug logging can now be controlled from Settings > Developer section, reducing console noise in production while maintaining full debugging capability during development. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
645 lines
25 KiB
TypeScript
645 lines
25 KiB
TypeScript
import { LevelGenerator } from "./levelGenerator";
|
|
import { LevelConfig, DifficultyConfig, Vector3Array, validateLevelConfig } from "./levelConfig";
|
|
import debugLog from './debug';
|
|
|
|
const STORAGE_KEY = 'space-game-levels';
|
|
|
|
/**
|
|
* Level Editor UI Controller
|
|
* Handles the level editor interface and configuration generation
|
|
*/
|
|
class LevelEditor {
|
|
private currentConfig: LevelConfig | null = null;
|
|
private savedLevels: Map<string, LevelConfig> = new Map();
|
|
|
|
constructor() {
|
|
this.loadSavedLevels();
|
|
this.setupEventListeners();
|
|
this.loadPreset('captain'); // Default to captain difficulty
|
|
this.renderSavedLevelsList();
|
|
}
|
|
|
|
private setupEventListeners() {
|
|
// Preset buttons
|
|
const presetButtons = document.querySelectorAll('.preset-btn');
|
|
presetButtons.forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const difficulty = (e.target as HTMLButtonElement).dataset.difficulty;
|
|
this.loadPreset(difficulty);
|
|
|
|
// Update active state
|
|
presetButtons.forEach(b => b.classList.remove('active'));
|
|
(e.target as HTMLElement).classList.add('active');
|
|
});
|
|
});
|
|
|
|
// Difficulty dropdown
|
|
const difficultySelect = document.getElementById('difficulty') as HTMLSelectElement;
|
|
difficultySelect.addEventListener('change', (e) => {
|
|
this.loadPreset((e.target as HTMLSelectElement).value);
|
|
});
|
|
|
|
// Generate button - now saves to localStorage
|
|
document.getElementById('generateBtn')?.addEventListener('click', () => {
|
|
this.generateLevel();
|
|
this.saveToLocalStorage();
|
|
});
|
|
|
|
// Download button
|
|
document.getElementById('downloadBtn')?.addEventListener('click', () => {
|
|
this.downloadJSON();
|
|
});
|
|
|
|
// Copy button
|
|
document.getElementById('copyBtn')?.addEventListener('click', () => {
|
|
this.copyToClipboard();
|
|
});
|
|
|
|
// Save edited JSON button
|
|
document.getElementById('saveEditedJsonBtn')?.addEventListener('click', () => {
|
|
this.saveEditedJSON();
|
|
});
|
|
|
|
// Validate JSON button
|
|
document.getElementById('validateJsonBtn')?.addEventListener('click', () => {
|
|
this.validateJSON();
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load saved levels from localStorage
|
|
*/
|
|
private loadSavedLevels(): void {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored) {
|
|
const levelsArray: [string, LevelConfig][] = JSON.parse(stored);
|
|
this.savedLevels = new Map(levelsArray);
|
|
debugLog(`Loaded ${this.savedLevels.size} saved levels from localStorage`);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load saved levels:', error);
|
|
this.savedLevels = new Map();
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save current level to localStorage
|
|
*/
|
|
private saveToLocalStorage(): void {
|
|
if (!this.currentConfig) {
|
|
alert('Please generate a level configuration first!');
|
|
return;
|
|
}
|
|
|
|
const levelName = (document.getElementById('levelName') as HTMLInputElement).value ||
|
|
`${this.currentConfig.difficulty}-${Date.now()}`;
|
|
|
|
// Save to map
|
|
this.savedLevels.set(levelName, this.currentConfig);
|
|
|
|
// Convert Map to array for storage
|
|
const levelsArray = Array.from(this.savedLevels.entries());
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(levelsArray));
|
|
|
|
debugLog(`Saved level: ${levelName}`);
|
|
this.renderSavedLevelsList();
|
|
|
|
// Show feedback
|
|
const feedback = document.createElement('div');
|
|
feedback.textContent = `✓ Saved "${levelName}" to local storage`;
|
|
feedback.style.cssText = `
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
background: #4CAF50;
|
|
color: white;
|
|
padding: 15px 25px;
|
|
border-radius: 5px;
|
|
box-shadow: 0 4px 6px rgba(0,0,0,0.3);
|
|
z-index: 10000;
|
|
animation: slideIn 0.3s ease-out;
|
|
`;
|
|
document.body.appendChild(feedback);
|
|
setTimeout(() => {
|
|
feedback.remove();
|
|
}, 3000);
|
|
}
|
|
|
|
/**
|
|
* Delete a saved level
|
|
*/
|
|
private deleteSavedLevel(levelName: string): void {
|
|
if (confirm(`Delete "${levelName}"?`)) {
|
|
this.savedLevels.delete(levelName);
|
|
const levelsArray = Array.from(this.savedLevels.entries());
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(levelsArray));
|
|
this.renderSavedLevelsList();
|
|
debugLog(`Deleted level: ${levelName}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Load a saved level into the editor
|
|
*/
|
|
private loadSavedLevel(levelName: string): void {
|
|
const config = this.savedLevels.get(levelName);
|
|
if (!config) {
|
|
alert('Level not found!');
|
|
return;
|
|
}
|
|
|
|
this.currentConfig = config;
|
|
|
|
// Populate form with saved values
|
|
(document.getElementById('levelName') as HTMLInputElement).value = levelName;
|
|
(document.getElementById('difficulty') as HTMLSelectElement).value = config.difficulty;
|
|
|
|
if (config.metadata?.author) {
|
|
(document.getElementById('author') as HTMLInputElement).value = config.metadata.author;
|
|
}
|
|
if (config.metadata?.description) {
|
|
(document.getElementById('description') as HTMLInputElement).value = config.metadata.description;
|
|
}
|
|
|
|
// Ship
|
|
(document.getElementById('shipX') as HTMLInputElement).value = config.ship.position[0].toString();
|
|
(document.getElementById('shipY') as HTMLInputElement).value = config.ship.position[1].toString();
|
|
(document.getElementById('shipZ') as HTMLInputElement).value = config.ship.position[2].toString();
|
|
|
|
// Start base
|
|
(document.getElementById('baseX') as HTMLInputElement).value = config.startBase.position[0].toString();
|
|
(document.getElementById('baseY') as HTMLInputElement).value = config.startBase.position[1].toString();
|
|
(document.getElementById('baseZ') as HTMLInputElement).value = config.startBase.position[2].toString();
|
|
(document.getElementById('baseDiameter') as HTMLInputElement).value = config.startBase.diameter.toString();
|
|
(document.getElementById('baseHeight') as HTMLInputElement).value = config.startBase.height.toString();
|
|
|
|
// Sun
|
|
(document.getElementById('sunX') as HTMLInputElement).value = config.sun.position[0].toString();
|
|
(document.getElementById('sunY') as HTMLInputElement).value = config.sun.position[1].toString();
|
|
(document.getElementById('sunZ') as HTMLInputElement).value = config.sun.position[2].toString();
|
|
(document.getElementById('sunDiameter') as HTMLInputElement).value = config.sun.diameter.toString();
|
|
|
|
// Planets
|
|
(document.getElementById('planetCount') as HTMLInputElement).value = config.planets.length.toString();
|
|
|
|
// Asteroids (use difficulty config if available)
|
|
if (config.difficultyConfig) {
|
|
(document.getElementById('asteroidCount') as HTMLInputElement).value = config.difficultyConfig.rockCount.toString();
|
|
(document.getElementById('forceMultiplier') as HTMLInputElement).value = config.difficultyConfig.forceMultiplier.toString();
|
|
(document.getElementById('asteroidMinSize') as HTMLInputElement).value = config.difficultyConfig.rockSizeMin.toString();
|
|
(document.getElementById('asteroidMaxSize') as HTMLInputElement).value = config.difficultyConfig.rockSizeMax.toString();
|
|
(document.getElementById('asteroidMinDist') as HTMLInputElement).value = config.difficultyConfig.distanceMin.toString();
|
|
(document.getElementById('asteroidMaxDist') as HTMLInputElement).value = config.difficultyConfig.distanceMax.toString();
|
|
}
|
|
|
|
// Display the JSON
|
|
this.displayJSON();
|
|
|
|
debugLog(`Loaded level: ${levelName}`);
|
|
}
|
|
|
|
/**
|
|
* Render the list of saved levels
|
|
*/
|
|
private renderSavedLevelsList(): void {
|
|
const container = document.getElementById('savedLevelsList');
|
|
if (!container) return;
|
|
|
|
if (this.savedLevels.size === 0) {
|
|
container.innerHTML = '<p style="color: #888; font-style: italic;">No saved levels yet. Generate a level to save it.</p>';
|
|
return;
|
|
}
|
|
|
|
let html = '<div style="display: grid; gap: 10px;">';
|
|
|
|
for (const [name, config] of this.savedLevels.entries()) {
|
|
const timestamp = config.timestamp ? new Date(config.timestamp).toLocaleString() : 'Unknown';
|
|
html += `
|
|
<div style="
|
|
background: rgba(255, 255, 255, 0.08);
|
|
border: 1px solid rgba(255, 255, 255, 0.2);
|
|
border-radius: 5px;
|
|
padding: 12px;
|
|
display: flex;
|
|
justify-content: space-between;
|
|
align-items: center;
|
|
">
|
|
<div style="flex: 1;">
|
|
<div style="font-weight: bold; color: #fff; margin-bottom: 4px;">${name}</div>
|
|
<div style="font-size: 0.85em; color: #aaa;">
|
|
${config.difficulty} • ${config.asteroids.length} asteroids • ${timestamp}
|
|
</div>
|
|
</div>
|
|
<div style="display: flex; gap: 8px;">
|
|
<button class="load-level-btn" data-level="${name}" style="
|
|
padding: 6px 12px;
|
|
background: #4CAF50;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
">Load</button>
|
|
<button class="delete-level-btn" data-level="${name}" style="
|
|
padding: 6px 12px;
|
|
background: #f44336;
|
|
color: white;
|
|
border: none;
|
|
border-radius: 4px;
|
|
cursor: pointer;
|
|
font-size: 0.9em;
|
|
">Delete</button>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += '</div>';
|
|
container.innerHTML = html;
|
|
|
|
// Add event listeners to load/delete buttons
|
|
container.querySelectorAll('.load-level-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const levelName = (e.target as HTMLButtonElement).dataset.level;
|
|
if (levelName) this.loadSavedLevel(levelName);
|
|
});
|
|
});
|
|
|
|
container.querySelectorAll('.delete-level-btn').forEach(btn => {
|
|
btn.addEventListener('click', (e) => {
|
|
const levelName = (e.target as HTMLButtonElement).dataset.level;
|
|
if (levelName) this.deleteSavedLevel(levelName);
|
|
});
|
|
});
|
|
}
|
|
|
|
/**
|
|
* Load a difficulty preset into the form
|
|
*/
|
|
private loadPreset(difficulty: string) {
|
|
const difficultyConfig = this.getDifficultyConfig(difficulty);
|
|
|
|
// Update difficulty dropdown
|
|
(document.getElementById('difficulty') as HTMLSelectElement).value = difficulty;
|
|
|
|
// Update asteroid settings based on difficulty
|
|
(document.getElementById('asteroidCount') as HTMLInputElement).value = difficultyConfig.rockCount.toString();
|
|
(document.getElementById('forceMultiplier') as HTMLInputElement).value = difficultyConfig.forceMultiplier.toString();
|
|
(document.getElementById('asteroidMinSize') as HTMLInputElement).value = difficultyConfig.rockSizeMin.toString();
|
|
(document.getElementById('asteroidMaxSize') as HTMLInputElement).value = difficultyConfig.rockSizeMax.toString();
|
|
(document.getElementById('asteroidMinDist') as HTMLInputElement).value = difficultyConfig.distanceMin.toString();
|
|
(document.getElementById('asteroidMaxDist') as HTMLInputElement).value = difficultyConfig.distanceMax.toString();
|
|
}
|
|
|
|
/**
|
|
* Get difficulty configuration
|
|
*/
|
|
private getDifficultyConfig(difficulty: string): DifficultyConfig {
|
|
switch (difficulty) {
|
|
case 'recruit':
|
|
return {
|
|
rockCount: 5,
|
|
forceMultiplier: .5,
|
|
rockSizeMin: 10,
|
|
rockSizeMax: 15,
|
|
distanceMin: 80,
|
|
distanceMax: 100
|
|
};
|
|
case 'pilot':
|
|
return {
|
|
rockCount: 10,
|
|
forceMultiplier: 1,
|
|
rockSizeMin: 8,
|
|
rockSizeMax: 12,
|
|
distanceMin: 80,
|
|
distanceMax: 150
|
|
};
|
|
case 'captain':
|
|
return {
|
|
rockCount: 20,
|
|
forceMultiplier: 1.2,
|
|
rockSizeMin: 2,
|
|
rockSizeMax: 7,
|
|
distanceMin: 100,
|
|
distanceMax: 250
|
|
};
|
|
case 'commander':
|
|
return {
|
|
rockCount: 50,
|
|
forceMultiplier: 1.3,
|
|
rockSizeMin: 2,
|
|
rockSizeMax: 8,
|
|
distanceMin: 90,
|
|
distanceMax: 280
|
|
};
|
|
case 'test':
|
|
return {
|
|
rockCount: 100,
|
|
forceMultiplier: 0.3,
|
|
rockSizeMin: 8,
|
|
rockSizeMax: 15,
|
|
distanceMin: 150,
|
|
distanceMax: 200
|
|
};
|
|
default:
|
|
return {
|
|
rockCount: 5,
|
|
forceMultiplier: 1.0,
|
|
rockSizeMin: 4,
|
|
rockSizeMax: 8,
|
|
distanceMin: 170,
|
|
distanceMax: 220
|
|
};
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read form values and generate level configuration
|
|
*/
|
|
private generateLevel() {
|
|
const difficulty = (document.getElementById('difficulty') as HTMLSelectElement).value;
|
|
const levelName = (document.getElementById('levelName') as HTMLInputElement).value || difficulty;
|
|
const author = (document.getElementById('author') as HTMLInputElement).value;
|
|
const description = (document.getElementById('description') as HTMLInputElement).value;
|
|
|
|
// Create a custom generator with modified parameters
|
|
const generator = new CustomLevelGenerator(difficulty);
|
|
|
|
// Override ship position
|
|
generator.shipPosition = [
|
|
parseFloat((document.getElementById('shipX') as HTMLInputElement).value),
|
|
parseFloat((document.getElementById('shipY') as HTMLInputElement).value),
|
|
parseFloat((document.getElementById('shipZ') as HTMLInputElement).value)
|
|
];
|
|
|
|
// Override start base
|
|
generator.startBasePosition = [
|
|
parseFloat((document.getElementById('baseX') as HTMLInputElement).value),
|
|
parseFloat((document.getElementById('baseY') as HTMLInputElement).value),
|
|
parseFloat((document.getElementById('baseZ') as HTMLInputElement).value)
|
|
];
|
|
generator.startBaseDiameter = parseFloat((document.getElementById('baseDiameter') as HTMLInputElement).value);
|
|
generator.startBaseHeight = parseFloat((document.getElementById('baseHeight') as HTMLInputElement).value);
|
|
|
|
// Override sun
|
|
generator.sunPosition = [
|
|
parseFloat((document.getElementById('sunX') as HTMLInputElement).value),
|
|
parseFloat((document.getElementById('sunY') as HTMLInputElement).value),
|
|
parseFloat((document.getElementById('sunZ') as HTMLInputElement).value)
|
|
];
|
|
generator.sunDiameter = parseFloat((document.getElementById('sunDiameter') as HTMLInputElement).value);
|
|
|
|
// Override planet generation params
|
|
generator.planetCount = parseInt((document.getElementById('planetCount') as HTMLInputElement).value);
|
|
generator.planetMinDiameter = parseFloat((document.getElementById('planetMinDiam') as HTMLInputElement).value);
|
|
generator.planetMaxDiameter = parseFloat((document.getElementById('planetMaxDiam') as HTMLInputElement).value);
|
|
generator.planetMinDistance = parseFloat((document.getElementById('planetMinDist') as HTMLInputElement).value);
|
|
generator.planetMaxDistance = parseFloat((document.getElementById('planetMaxDist') as HTMLInputElement).value);
|
|
|
|
// Override asteroid generation params
|
|
const customDifficulty: DifficultyConfig = {
|
|
rockCount: parseInt((document.getElementById('asteroidCount') as HTMLInputElement).value),
|
|
forceMultiplier: parseFloat((document.getElementById('forceMultiplier') as HTMLInputElement).value),
|
|
rockSizeMin: parseFloat((document.getElementById('asteroidMinSize') as HTMLInputElement).value),
|
|
rockSizeMax: parseFloat((document.getElementById('asteroidMaxSize') as HTMLInputElement).value),
|
|
distanceMin: parseFloat((document.getElementById('asteroidMinDist') as HTMLInputElement).value),
|
|
distanceMax: parseFloat((document.getElementById('asteroidMaxDist') as HTMLInputElement).value)
|
|
};
|
|
generator.setDifficultyConfig(customDifficulty);
|
|
|
|
// Generate the config
|
|
this.currentConfig = generator.generate();
|
|
|
|
// Add metadata
|
|
if (author) {
|
|
this.currentConfig.metadata = this.currentConfig.metadata || {};
|
|
this.currentConfig.metadata.author = author;
|
|
}
|
|
if (description) {
|
|
this.currentConfig.metadata = this.currentConfig.metadata || {};
|
|
this.currentConfig.metadata.description = description;
|
|
}
|
|
|
|
// Display the JSON
|
|
this.displayJSON();
|
|
}
|
|
|
|
/**
|
|
* Display generated JSON in the output section
|
|
*/
|
|
private displayJSON() {
|
|
if (!this.currentConfig) return;
|
|
|
|
const outputSection = document.getElementById('outputSection');
|
|
const jsonEditor = document.getElementById('jsonEditor') as HTMLTextAreaElement;
|
|
|
|
if (outputSection && jsonEditor) {
|
|
const jsonString = JSON.stringify(this.currentConfig, null, 2);
|
|
jsonEditor.value = jsonString;
|
|
outputSection.style.display = 'block';
|
|
|
|
// Scroll to output
|
|
outputSection.scrollIntoView({ behavior: 'smooth' });
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Validate the JSON in the editor
|
|
*/
|
|
private validateJSON(): boolean {
|
|
const jsonEditor = document.getElementById('jsonEditor') as HTMLTextAreaElement;
|
|
const messageDiv = document.getElementById('jsonValidationMessage');
|
|
|
|
if (!jsonEditor || !messageDiv) return false;
|
|
|
|
try {
|
|
const json = jsonEditor.value;
|
|
const parsed = JSON.parse(json);
|
|
|
|
// Validate against schema
|
|
const validation = validateLevelConfig(parsed);
|
|
|
|
if (validation.valid) {
|
|
messageDiv.innerHTML = '<div style="color: #4CAF50; padding: 10px; background: rgba(76, 175, 80, 0.1); border-radius: 5px;">✓ JSON is valid!</div>';
|
|
return true;
|
|
} else {
|
|
messageDiv.innerHTML = `<div style="color: #f44336; padding: 10px; background: rgba(244, 67, 54, 0.1); border-radius: 5px;">
|
|
<strong>Validation Errors:</strong><br>
|
|
${validation.errors.map(e => `• ${e}`).join('<br>')}
|
|
</div>`;
|
|
return false;
|
|
}
|
|
} catch (error) {
|
|
messageDiv.innerHTML = `<div style="color: #f44336; padding: 10px; background: rgba(244, 67, 54, 0.1); border-radius: 5px;">
|
|
<strong>JSON Parse Error:</strong><br>
|
|
${error.message}
|
|
</div>`;
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Save edited JSON from the editor
|
|
*/
|
|
private saveEditedJSON() {
|
|
const jsonEditor = document.getElementById('jsonEditor') as HTMLTextAreaElement;
|
|
const messageDiv = document.getElementById('jsonValidationMessage');
|
|
|
|
if (!jsonEditor) {
|
|
alert('JSON editor not found!');
|
|
return;
|
|
}
|
|
|
|
// First validate
|
|
if (!this.validateJSON()) {
|
|
messageDiv.innerHTML += '<div style="color: #ff9800; margin-top: 10px;">Please fix validation errors before saving.</div>';
|
|
return;
|
|
}
|
|
|
|
try {
|
|
const json = jsonEditor.value;
|
|
const config = JSON.parse(json) as LevelConfig;
|
|
|
|
// Update current config
|
|
this.currentConfig = config;
|
|
|
|
// Save to localStorage
|
|
this.saveToLocalStorage();
|
|
|
|
// Update message
|
|
messageDiv.innerHTML = '<div style="color: #4CAF50; padding: 10px; background: rgba(76, 175, 80, 0.1); border-radius: 5px;">✓ Edited JSON saved successfully!</div>';
|
|
|
|
debugLog('Saved edited JSON');
|
|
} catch (error) {
|
|
alert(`Failed to save: ${error.message}`);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Download the current configuration as JSON file
|
|
*/
|
|
private downloadJSON() {
|
|
if (!this.currentConfig) {
|
|
alert('Please generate a level configuration first!');
|
|
return;
|
|
}
|
|
|
|
const levelName = (document.getElementById('levelName') as HTMLInputElement).value ||
|
|
this.currentConfig.difficulty;
|
|
const filename = `level-${levelName}-${Date.now()}.json`;
|
|
|
|
const json = JSON.stringify(this.currentConfig, null, 2);
|
|
const blob = new Blob([json], { type: 'application/json' });
|
|
const url = URL.createObjectURL(blob);
|
|
|
|
const a = document.createElement('a');
|
|
a.href = url;
|
|
a.download = filename;
|
|
document.body.appendChild(a);
|
|
a.click();
|
|
document.body.removeChild(a);
|
|
URL.revokeObjectURL(url);
|
|
|
|
debugLog(`Downloaded: ${filename}`);
|
|
}
|
|
|
|
/**
|
|
* Copy current configuration JSON to clipboard
|
|
*/
|
|
private async copyToClipboard() {
|
|
if (!this.currentConfig) {
|
|
alert('Please generate a level configuration first!');
|
|
return;
|
|
}
|
|
|
|
const json = JSON.stringify(this.currentConfig, null, 2);
|
|
|
|
try {
|
|
await navigator.clipboard.writeText(json);
|
|
alert('JSON copied to clipboard!');
|
|
} catch (err) {
|
|
console.error('Failed to copy:', err);
|
|
alert('Failed to copy to clipboard. Please copy manually from the output.');
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Custom level generator that allows overriding default values
|
|
* Simply extends LevelGenerator - all properties are now public on the base class
|
|
*/
|
|
class CustomLevelGenerator extends LevelGenerator {
|
|
// No need to duplicate anything - just use the public properties from base class
|
|
// Properties like shipPosition, startBasePosition, etc. are already defined and public in LevelGenerator
|
|
}
|
|
|
|
// Initialize the editor when this module is loaded
|
|
if (!(window as any).__levelEditorInstance) {
|
|
(window as any).__levelEditorInstance = new LevelEditor();
|
|
}
|
|
|
|
/**
|
|
* Helper to get all saved levels from localStorage
|
|
*/
|
|
export function getSavedLevels(): Map<string, LevelConfig> {
|
|
try {
|
|
const stored = localStorage.getItem(STORAGE_KEY);
|
|
if (stored) {
|
|
const levelsArray: [string, LevelConfig][] = JSON.parse(stored);
|
|
return new Map(levelsArray);
|
|
}
|
|
} catch (error) {
|
|
console.error('Failed to load saved levels:', error);
|
|
}
|
|
return new Map();
|
|
}
|
|
|
|
/**
|
|
* Helper to get a specific saved level by name
|
|
*/
|
|
export function getSavedLevel(name: string): LevelConfig | null {
|
|
const levels = getSavedLevels();
|
|
return levels.get(name) || null;
|
|
}
|
|
|
|
/**
|
|
* Generate default levels if localStorage is empty
|
|
* Creates 4 levels: recruit, pilot, captain, commander
|
|
*/
|
|
export function generateDefaultLevels(): void {
|
|
const existing = getSavedLevels();
|
|
if (existing.size > 0) {
|
|
debugLog('Levels already exist in localStorage, skipping default generation');
|
|
return;
|
|
}
|
|
|
|
debugLog('No saved levels found, generating 4 default levels...');
|
|
|
|
const difficulties = ['recruit', 'pilot', 'captain', 'commander'];
|
|
const levelsMap = new Map<string, LevelConfig>();
|
|
|
|
for (const difficulty of difficulties) {
|
|
const generator = new LevelGenerator(difficulty);
|
|
const config = generator.generate();
|
|
|
|
// Add metadata
|
|
config.metadata = {
|
|
author: 'System',
|
|
description: `Default ${difficulty} level`
|
|
};
|
|
|
|
levelsMap.set(difficulty, config);
|
|
debugLog(`Generated default level: ${difficulty}`);
|
|
}
|
|
|
|
// Save all levels to localStorage
|
|
const levelsArray = Array.from(levelsMap.entries());
|
|
localStorage.setItem(STORAGE_KEY, JSON.stringify(levelsArray));
|
|
|
|
debugLog('Default levels saved to localStorage');
|
|
}
|
|
|
|
// Export for manual initialization if needed
|
|
export { LevelEditor, CustomLevelGenerator };
|