← tower-of-time-game
/**
* ConfigSystem - Centralized configuration management for Tower of Time
*
* This system holds all game constants and configuration values in one place,
* making it easy to tweak game balance and enable configuration from menus.
*
* Features:
* - Centralized game constants
* - Enemy type configurations
* - Wave configurations
* - Player settings
* - Energy system settings
* - Goal and building configurations
* - Energy crystal properties
*/
import { Wave } from "./WaveSystem";
// Enemy type configuration interface
export interface EnemyTypeConfig {
name: string;
speed: number;
maxHp: number;
tintColor: number;
boss?: boolean;
}
// Building configuration interface
export interface BuildingConfig {
name: string;
energyCost: number;
texture: string;
description: string;
}
// Tower-specific configuration interface
export interface TowerConfig extends BuildingConfig {
range: number;
damage: number;
shootingInterval: number; // milliseconds between shots
bulletSpeed?: number; // pixels per second (for bullet-based towers)
effectInterval?: number; // milliseconds between effects (for effect-based towers)
effectRadius?: number; // radius for area effects
slowAmount?: number; // slow multiplier (0.5 = 50% speed)
slowDuration?: number; // milliseconds slow effect lasts
}
// Energy crystal configuration
export interface EnergyCrystalConfig {
energyValue: number;
lifetimeDuration: number; // milliseconds
warningDuration: number; // milliseconds before expiring
}
// Player configuration
export interface PlayerConfig {
speed: number; // pixels per second
}
// Energy system configuration
export interface EnergyConfig {
maxEnergy: number;
regenerationRate: number; // frames between energy regeneration
initialEnergy: number;
rewindCostPerTick: number;
}
// Goal configuration
export interface GoalConfig {
maxHP: number;
initialHP: number;
attackDamage: number; // damage enemies deal per attack
attackInterval: number; // milliseconds between attacks
collisionRadius: number; // distance at which enemies can attack
}
// Wave system configuration
export interface WaveSystemConfig {
waveStartDelay: number; // milliseconds between waves
overlayDisplayDuration: number; // milliseconds to show wave start overlay
}
// Main configuration interface
export interface GameConfig {
version: number; // Configuration version for migration
skipTutorial: boolean; // Skip tutorial and start waves immediately
rewindRecordingInterval: number; // How often to record rewind states
player: PlayerConfig;
energy: EnergyConfig;
goal: GoalConfig;
waveSystem: WaveSystemConfig;
energyCrystal: EnergyCrystalConfig;
enemyTypes: Record<string, EnemyTypeConfig>;
towers: Record<string, TowerConfig>;
waves: Wave[];
}
/**
* ConfigSystem class - manages all game configuration
*/
export default class ConfigSystem {
private config: GameConfig;
constructor(customConfig?: Partial<GameConfig>) {
// Initialize with default configuration
this.config = this.getDefaultConfig();
// Try to load saved configuration from localStorage first
const savedConfig = this.loadSavedConfig();
if (savedConfig) {
// Check if migration is needed
const migratedConfig = this.migrateConfig(savedConfig);
this.config = this.mergeConfig(this.config, migratedConfig);
// If migration occurred, save the updated config
if (migratedConfig !== savedConfig) {
console.log(
"ConfigSystem migrated configuration to version",
this.config.version
);
this.saveToLocalStorage();
} else {
console.log(
"ConfigSystem loaded saved configuration from localStorage"
);
}
}
// Apply any custom configuration overrides
if (customConfig) {
this.config = this.mergeConfig(this.config, customConfig);
}
console.log("ConfigSystem initialized with configuration:", this.config);
}
/**
* Get the complete configuration object
*/
public getConfig(): GameConfig {
return { ...this.config };
}
/**
* Get player configuration
*/
public getPlayerConfig(): PlayerConfig {
return { ...this.config.player };
}
/**
* Get energy system configuration
*/
public getEnergyConfig(): EnergyConfig {
return { ...this.config.energy };
}
/**
* Get goal configuration
*/
public getGoalConfig(): GoalConfig {
return { ...this.config.goal };
}
/**
* Get wave system configuration
*/
public getWaveSystemConfig(): WaveSystemConfig {
return { ...this.config.waveSystem };
}
/**
* Get energy crystal configuration
*/
public getEnergyCrystalConfig(): EnergyCrystalConfig {
return { ...this.config.energyCrystal };
}
/**
* Get enemy types configuration
*/
public getEnemyTypesConfig(): Record<string, EnemyTypeConfig> {
return { ...this.config.enemyTypes };
}
/**
* Get towers configuration
*/
public getTowersConfig(): Record<string, TowerConfig> {
return { ...this.config.towers };
}
/**
* Get specific tower configuration
*/
public getTowerConfig(towerType: string): TowerConfig | null {
return this.config.towers[towerType]
? { ...this.config.towers[towerType] }
: null;
}
/**
* Get wave configurations
*/
public getWavesConfig(): Wave[] {
return [...this.config.waves];
}
/**
* Get skip tutorial setting
*/
public getSkipTutorial(): boolean {
return this.config.skipTutorial;
}
/**
* Update player configuration
*/
public updatePlayerConfig(updates: Partial<PlayerConfig>): void {
this.config.player = { ...this.config.player, ...updates };
console.log("Player config updated:", this.config.player);
}
/**
* Update energy configuration
*/
public updateEnergyConfig(updates: Partial<EnergyConfig>): void {
this.config.energy = { ...this.config.energy, ...updates };
console.log("Energy config updated:", this.config.energy);
}
/**
* Update goal configuration
*/
public updateGoalConfig(updates: Partial<GoalConfig>): void {
this.config.goal = { ...this.config.goal, ...updates };
console.log("Goal config updated:", this.config.goal);
}
/**
* Update wave system configuration
*/
public updateWaveSystemConfig(updates: Partial<WaveSystemConfig>): void {
this.config.waveSystem = { ...this.config.waveSystem, ...updates };
console.log("Wave system config updated:", this.config.waveSystem);
}
/**
* Update energy crystal configuration
*/
public updateEnergyCrystalConfig(
updates: Partial<EnergyCrystalConfig>
): void {
this.config.energyCrystal = { ...this.config.energyCrystal, ...updates };
console.log("Energy crystal config updated:", this.config.energyCrystal);
}
/**
* Update skip tutorial setting
*/
public updateSkipTutorial(skipTutorial: boolean): void {
this.config.skipTutorial = skipTutorial;
console.log("Skip tutorial setting updated:", skipTutorial);
}
/**
* Update enemy type configuration
*/
public updateEnemyTypeConfig(
enemyType: string,
updates: Partial<EnemyTypeConfig>
): void {
if (this.config.enemyTypes[enemyType]) {
this.config.enemyTypes[enemyType] = {
...this.config.enemyTypes[enemyType],
...updates,
};
console.log(
`Enemy type ${enemyType} config updated:`,
this.config.enemyTypes[enemyType]
);
} else {
console.warn(`Enemy type ${enemyType} not found in configuration`);
}
}
/**
* Get default configuration with values extracted from current codebase
*/
private getDefaultConfig(): GameConfig {
return {
version: 1,
skipTutorial: false,
rewindRecordingInterval: 4,
player: {
speed: 200,
},
energy: {
maxEnergy: 500,
regenerationRate: 50,
initialEnergy: 300,
rewindCostPerTick: 0.2,
},
goal: {
maxHP: 100,
initialHP: 100,
attackDamage: 5,
attackInterval: 1000,
collisionRadius: 30,
},
waveSystem: {
waveStartDelay: 2000,
overlayDisplayDuration: 2000,
},
energyCrystal: {
energyValue: 10,
lifetimeDuration: 8000,
warningDuration: 4000,
},
enemyTypes: {
BASIC: {
name: "Basic",
speed: 60,
maxHp: 4,
tintColor: 16777215,
},
FAST: {
name: "Fast",
speed: 100,
maxHp: 2,
tintColor: 6750054,
},
TANK: {
name: "Tank",
speed: 40,
maxHp: 10,
tintColor: 16737894,
},
BOSS: {
name: "Boss",
speed: 20,
maxHp: 250,
boss: true,
tintColor: 11534591,
},
},
towers: {
basic_tower: {
name: "Basic Turret",
energyCost: 200,
texture: "tower1",
description: "A basic defensive turret",
range: 100,
damage: 1,
shootingInterval: 1000,
bulletSpeed: 500,
},
sniper_tower: {
name: "Sniper Turret",
energyCost: 300,
texture: "tower2",
description:
"Long range turret with high bullet speed but slow firing rate",
range: 200,
damage: 2,
shootingInterval: 2000,
bulletSpeed: 1000,
},
slowdown_tower: {
name: "Slowdown Turret",
energyCost: 300,
texture: "tower3",
description: "Applies slow effect to enemies in range",
range: 80,
damage: 0,
shootingInterval: 0,
effectInterval: 2500,
effectRadius: 80,
slowAmount: 0.5,
slowDuration: 2500,
},
splash_tower: {
name: "Splash Turret",
energyCost: 350,
texture: "tower4",
description:
"Fires area-of-effect blasts that damage all enemies in radius",
range: 60,
damage: 1,
shootingInterval: 3500,
effectRadius: 60,
},
},
waves: [
{
name: "First Contact",
enemies: [
{
type: "BASIC",
amount: 3,
interval: 2500,
delay: 1000,
},
],
},
{
name: "Second Contact",
enemies: [
{
type: "BASIC",
amount: 5,
interval: 1500,
delay: 3000,
},
{
type: "BASIC",
amount: 5,
interval: 1200,
delay: 20000,
},
],
},
{
name: "Growing Threat",
enemies: [
{
type: "BASIC",
amount: 5,
interval: 800,
delay: 2000,
},
{
type: "FAST",
amount: 4,
interval: 2000,
delay: 10000,
},
{
type: "BASIC",
amount: 6,
interval: 2000,
delay: 20000,
},
{
type: "FAST",
amount: 5,
interval: 2000,
delay: 28000,
},
],
},
{
name: "Full Assault",
enemies: [
{
type: "BASIC",
amount: 10,
interval: 600,
delay: 2000,
},
{
type: "FAST",
amount: 8,
interval: 1500,
delay: 8000,
},
{
type: "TANK",
amount: 3,
interval: 3500,
delay: 16000,
},
],
},
{
name: "Armored Division",
enemies: [
{
type: "BASIC",
amount: 10,
interval: 2000,
delay: 2000,
},
{
type: "TANK",
amount: 3,
interval: 2500,
delay: 3000,
},
],
},
{
name: "Lightning Strike",
enemies: [
{
type: "FAST",
amount: 10,
interval: 800,
delay: 2000,
},
{
type: "FAST",
amount: 10,
interval: 1200,
delay: 6000,
},
],
},
{
name: "Hard 1",
enemies: [
{
type: "TANK",
amount: 7,
interval: 2000,
delay: 2000,
},
{
type: "FAST",
amount: 15,
interval: 1400,
delay: 4000,
energyDropRate: 0.5,
},
{
type: "BASIC",
amount: 15,
interval: 500,
delay: 12000,
energyDropRate: 0.5,
},
],
},
{
name: "Hard 2",
enemies: [
{
type: "FAST",
amount: 20,
interval: 200,
delay: 2000,
energyDropRate: 0.5,
},
{
type: "FAST",
amount: 20,
interval: 400,
delay: 4000,
energyDropRate: 0.5,
},
],
},
{
name: "Hard 3",
enemies: [
{
type: "TANK",
amount: 20,
interval: 2000,
delay: 2000,
energyDropRate: 0.5,
},
{
type: "BASIC",
amount: 22,
interval: 1200,
delay: 6000,
energyDropRate: 0.5,
},
],
},
{
name: "Final",
enemies: [
{
type: "BASIC",
amount: 30,
interval: 50,
delay: 2000,
energyDropRate: 0.3,
},
{
type: "TANK",
amount: 25,
interval: 1500,
delay: 3000,
energyDropRate: 0.3,
},
{
type: "BASIC",
amount: 30,
interval: 200,
delay: 6000,
energyDropRate: 0.3,
},
{
type: "FAST",
amount: 40,
interval: 400,
delay: 16000,
energyDropRate: 0.3,
},
{
type: "FAST",
amount: 40,
interval: 450,
delay: 16000,
energyDropRate: 0.3,
},
],
},
{
name: "Final Boss",
enemies: [
{
type: "BOSS",
amount: 1,
interval: 50,
delay: 4000,
energyDropRate: 0.1,
},
],
},
],
};
}
/**
* Migrate configuration from older versions
*/
private migrateConfig(config: Partial<GameConfig>): Partial<GameConfig> {
const currentVersion = this.getDefaultConfig().version;
const configVersion = config.version || 0;
if (configVersion >= currentVersion) {
return config; // No migration needed
}
console.log(
`Migrating config from version ${configVersion} to ${currentVersion}`
);
// Create a copy to avoid modifying the original
const migratedConfig = { ...config };
// Migration from version 0 to 1: Add towers configuration
if (configVersion < 1) {
migratedConfig.version = 1;
// Add towers config if missing - will be merged with defaults
if (!migratedConfig.towers) {
migratedConfig.towers = {};
}
}
return migratedConfig;
}
/**
* Deep merge configuration objects
*/
private mergeConfig(
base: GameConfig,
override: Partial<GameConfig>
): GameConfig {
const result = { ...base };
for (const key in override) {
const value = override[key as keyof GameConfig];
if (value !== undefined) {
if (
typeof value === "object" &&
!Array.isArray(value) &&
value !== null
) {
// Handle object merging with proper typing
const baseValue = result[key as keyof GameConfig];
if (
typeof baseValue === "object" &&
!Array.isArray(baseValue) &&
baseValue !== null
) {
(result as any)[key] = {
...baseValue,
...value,
};
} else {
(result as any)[key] = value;
}
} else {
(result as any)[key] = value;
}
}
}
return result;
}
/**
* Reset configuration to defaults
*/
public resetToDefaults(): void {
this.config = this.getDefaultConfig();
console.log("Configuration reset to defaults");
}
/**
* Export configuration as JSON string
*/
public exportConfig(): string {
return JSON.stringify(this.config, null, 2);
}
/**
* Import configuration from JSON string
*/
public importConfig(configJson: string): boolean {
try {
const importedConfig = JSON.parse(configJson);
this.config = this.mergeConfig(this.getDefaultConfig(), importedConfig);
console.log("Configuration imported successfully");
return true;
} catch (error) {
console.error("Failed to import configuration:", error);
return false;
}
}
/**
* Load saved configuration from localStorage
*/
private loadSavedConfig(): Partial<GameConfig> | null {
try {
const saved = localStorage.getItem("towerOfTimeConfig");
if (saved) {
return JSON.parse(saved);
}
} catch (error) {
console.error("Failed to load saved configuration:", error);
}
return null;
}
/**
* Save current configuration to localStorage
*/
public saveToLocalStorage(): boolean {
try {
const configJson = this.exportConfig();
localStorage.setItem("towerOfTimeConfig", configJson);
console.log("Configuration saved to localStorage");
return true;
} catch (error) {
console.error("Failed to save configuration to localStorage:", error);
return false;
}
}
/**
* Clear saved configuration from localStorage
*/
public clearSavedConfig(): void {
localStorage.removeItem("towerOfTimeConfig");
console.log("Saved configuration cleared from localStorage");
}
/**
* Check if there is a saved configuration
*/
public hasSavedConfig(): boolean {
return localStorage.getItem("towerOfTimeConfig") !== null;
}
}