/** * MasterWeatherSystem.js * * Complete weather & environmental particle system for DolinaSmrti * Phase 1/2/Demo CORE SYSTEM - All biomes, all weather types * * Features: * - Wind (grass, trees, hair movement) * - Rain (drops, puddles, wetness) * - Snow (flakes, accumulation, footprints) * - Fire (flames, smoke, heat distortion) * - Water (ripples, displacement, caustics) * * Style 32 Dark-Chibi Noir compatible * Performance optimized for 60 FPS */ import WindFoliageSystem from './WindFoliageSystem.js'; export default class MasterWeatherSystem { constructor(scene) { this.scene = scene; // Sub-systems this.windSystem = null; this.rainSystem = null; this.snowSystem = null; this.fireSystem = null; this.waterSystem = null; // Current weather state this.currentWeather = 'clear'; // clear, rain, snow, storm this.weatherIntensity = 0; // 0.0 - 1.0 this.transitionTime = 0; // Biome-specific settings this.biomeWeatherSettings = { 'grassland': { allowedWeather: ['clear', 'rain', 'storm'], defaultWind: 1.0 }, 'desert': { allowedWeather: ['clear', 'sandstorm'], defaultWind: 1.5 }, 'snow': { allowedWeather: ['clear', 'snow', 'blizzard'], defaultWind: 1.8 }, 'swamp': { allowedWeather: ['rain', 'fog'], defaultWind: 0.3 }, 'mountains': { allowedWeather: ['clear', 'snow', 'storm'], defaultWind: 2.0 }, 'forest': { allowedWeather: ['clear', 'rain'], defaultWind: 0.8 }, 'volcanic': { allowedWeather: ['clear', 'ash_rain'], defaultWind: 1.2, constantFire: true } }; } /** * Initialize all weather systems */ init() { console.log('🌦️ MasterWeatherSystem: Initializing...'); // Initialize Wind System (already implemented!) this.windSystem = new WindFoliageSystem(this.scene); this.windSystem.init(); // Initialize Rain System this.initRainSystem(); // Initialize Snow System this.initSnowSystem(); // Initialize Fire System this.initFireSystem(); // Initialize Water Effects this.initWaterSystem(); console.log('✅ MasterWeatherSystem: All systems ready!'); } /** * RAIN SYSTEM - Realistic rain with puddles */ initRainSystem() { // Create rain drop texture (Style 32 noir) const graphics = this.scene.add.graphics(); graphics.fillStyle(0xffffff, 0.6); graphics.fillRect(0, 0, 2, 8); // Thin vertical drop graphics.generateTexture('rainDrop', 2, 8); graphics.destroy(); // Rain particle emitter this.rainEmitter = this.scene.add.particles(0, 0, 'rainDrop', { x: { min: 0, max: this.scene.cameras.main.width }, y: -20, lifespan: 2000, speedY: { min: 300, max: 500 }, speedX: { min: -20, max: 20 }, // Wind affects rain scale: { start: 1, end: 0.8 }, alpha: { start: 0.6, end: 0.3 }, quantity: 2, frequency: 20, blendMode: 'ADD' }); this.rainEmitter.stop(); // Start inactive // Puddle system (appears after rain) this.puddles = []; console.log('💧 Rain system initialized'); } /** * SNOW SYSTEM - Falling snow with accumulation */ initSnowSystem() { // Create snowflake texture (Style 32 - simple white dot with black outline) const graphics = this.scene.add.graphics(); graphics.lineStyle(1, 0x000000, 1); graphics.fillStyle(0xffffff, 0.9); graphics.fillCircle(4, 4, 3); graphics.strokeCircle(4, 4, 3); graphics.generateTexture('snowflake', 8, 8); graphics.destroy(); // Snow particle emitter this.snowEmitter = this.scene.add.particles(0, 0, 'snowflake', { x: { min: 0, max: this.scene.cameras.main.width }, y: -20, lifespan: 8000, speedY: { min: 20, max: 50 }, speedX: { min: -30, max: 30 }, // Wind drift scale: { min: 0.5, max: 1.2 }, alpha: { start: 0.9, end: 0.6 }, rotate: { min: 0, max: 360 }, frequency: 100, quantity: 1 }); this.snowEmitter.stop(); // Snow accumulation layer (white overlay on ground) this.snowAccumulation = this.scene.add.rectangle( 0, 0, this.scene.cameras.main.width, this.scene.cameras.main.height, 0xffffff, 0 ).setOrigin(0, 0).setDepth(-1); console.log('❄️ Snow system initialized'); } /** * FIRE SYSTEM - Flames, smoke, heat distortion */ initFireSystem() { // Create flame texture (orange-red gradient) const graphics = this.scene.add.graphics(); graphics.fillStyle(0xff4500, 1); graphics.fillCircle(8, 8, 6); graphics.fillStyle(0xffaa00, 0.8); graphics.fillCircle(8, 8, 4); graphics.generateTexture('flame', 16, 16); graphics.destroy(); // Create smoke texture (dark grey) const smokeGraphics = this.scene.add.graphics(); smokeGraphics.fillStyle(0x333333, 0.4); smokeGraphics.fillCircle(12, 12, 10); smokeGraphics.generateTexture('smoke', 24, 24); smokeGraphics.destroy(); // Fire sources (campfires, torches, etc.) this.fireSources = []; console.log('🔥 Fire system initialized'); } /** * Create fire source (campfire, torch, etc.) * @param {number} x - X position * @param {number} y - Y position * @param {number} size - Fire size multiplier (0.5 - 2.0) */ createFireSource(x, y, size = 1.0) { // Flame particles const flameEmitter = this.scene.add.particles(x, y, 'flame', { lifespan: 800, speed: { min: 20, max: 60 }, angle: { min: 250, max: 290 }, // Upward scale: { start: size, end: 0.1 }, alpha: { start: 1, end: 0 }, blendMode: 'ADD', frequency: 50, tint: [0xff4500, 0xff6600, 0xffaa00] }); // Smoke particles const smokeEmitter = this.scene.add.particles(x, y - 10, 'smoke', { lifespan: 2000, speed: { min: 10, max: 30 }, angle: { min: 260, max: 280 }, scale: { start: size * 0.5, end: size * 2 }, alpha: { start: 0.5, end: 0 }, frequency: 200 }); this.fireSources.push({ x, y, size, flameEmitter, smokeEmitter }); return { flameEmitter, smokeEmitter }; } /** * WATER SYSTEM - Ripples, displacement, caustics * (Integration with planned WaterPhysicsSystem) */ initWaterSystem() { // Water ripple texture (Style 32 - concentric circles) const graphics = this.scene.add.graphics(); graphics.lineStyle(2, 0x000000, 1); graphics.strokeCircle(32, 32, 28); graphics.lineStyle(1, 0x000000, 0.6); graphics.strokeCircle(32, 32, 24); graphics.generateTexture('waterRipple', 64, 64); graphics.destroy(); // Ripple particle manager this.rippleParticles = this.scene.add.particles(0, 0, 'waterRipple'); // Water zones (areas with water effects) this.waterZones = []; console.log('🌊 Water system initialized'); } /** * Create water ripple effect * @param {number} x - X position * @param {number} y - Y position * @param {number} size - Ripple size */ createRipple(x, y, size = 1.0) { const emitter = this.rippleParticles.createEmitter({ x, y, lifespan: 1500, speed: 0, scale: { start: 0.1 * size, end: 2.0 * size }, alpha: { start: 0.7, end: 0 }, frequency: -1, quantity: 1 }); emitter.explode(1); this.scene.time.delayedCall(1500, () => { emitter.stop(); this.rippleParticles.removeEmitter(emitter); }); } /** * Set weather type * @param {string} type - Weather type: 'clear', 'rain', 'snow', 'storm', 'blizzard' * @param {number} intensity - Intensity 0.0 - 1.0 * @param {number} transitionTime - Transition duration in ms */ setWeather(type, intensity = 0.5, transitionTime = 2000) { console.log(`🌦️ Weather changing to: ${type} (intensity: ${intensity})`); this.currentWeather = type; this.weatherIntensity = intensity; this.transitionTime = transitionTime; // Stop all current weather this.rainEmitter.stop(); this.snowEmitter.stop(); // Start new weather switch (type) { case 'rain': this.startRain(intensity); break; case 'snow': this.startSnow(intensity); break; case 'storm': this.startStorm(intensity); break; case 'blizzard': this.startBlizzard(intensity); break; case 'clear': this.clearWeather(); break; } } /** * Start rain weather */ startRain(intensity) { this.rainEmitter.setFrequency(100 / intensity); // More intense = more frequent this.rainEmitter.setQuantity(Math.ceil(intensity * 3)); this.rainEmitter.start(); // Adjust wind this.windSystem.setWindStrength(0.8 + intensity * 0.5); // Create puddles over time this.scene.time.addEvent({ delay: 5000, callback: () => this.createPuddle(), loop: true }); } /** * Start snow weather */ startSnow(intensity) { this.snowEmitter.setFrequency(200 / intensity); this.snowEmitter.setQuantity(Math.ceil(intensity * 2)); this.snowEmitter.start(); // Adjust wind (snow drifts more) this.windSystem.setWindStrength(intensity * 1.2); // Gradual snow accumulation this.scene.tweens.add({ targets: this.snowAccumulation, alpha: intensity * 0.3, duration: 10000, ease: 'Linear' }); } /** * Start storm (heavy rain + strong wind) */ startStorm(intensity) { this.startRain(intensity); this.windSystem.setWindStrength(intensity * 2.0); // Lightning flashes (TODO: add later) } /** * Start blizzard (heavy snow + very strong wind) */ startBlizzard(intensity) { this.startSnow(intensity); this.windSystem.setWindStrength(intensity * 2.5); // Reduce visibility this.scene.tweens.add({ targets: this.scene.cameras.main, alpha: 0.7, duration: 2000, yoyo: true, repeat: -1 }); } /** * Clear all weather */ clearWeather() { this.rainEmitter.stop(); this.snowEmitter.stop(); this.windSystem.setWindStrength(1.0); // Fade out snow accumulation this.scene.tweens.add({ targets: this.snowAccumulation, alpha: 0, duration: 5000 }); } /** * Create puddle after rain */ createPuddle() { const x = Phaser.Math.Between(0, this.scene.cameras.main.width); const y = Phaser.Math.Between(0, this.scene.cameras.main.height); const puddle = this.scene.add.ellipse(x, y, 60, 30, 0x4682B4, 0.3); puddle.setDepth(-2); this.puddles.push(puddle); // Puddles evaporate over time this.scene.tweens.add({ targets: puddle, alpha: 0, duration: 30000, onComplete: () => { puddle.destroy(); this.puddles = this.puddles.filter(p => p !== puddle); } }); } /** * Set biome-specific weather * @param {string} biomeName - Biome name */ setBiomeWeather(biomeName) { const settings = this.biomeWeatherSettings[biomeName.toLowerCase()]; if (!settings) { console.warn(`⚠️ Unknown biome: ${biomeName}`); return; } // Set default wind for biome this.windSystem.setBiomeWind(biomeName); // Volcanic biome always has fire if (settings.constantFire) { this.createFireSource(100, 200, 1.5); this.createFireSource(300, 250, 1.2); } console.log(`🌍 Biome weather set: ${biomeName}`); } /** * Update all weather systems * @param {number} delta - Time delta in ms */ update(delta) { // Update wind system if (this.windSystem) { this.windSystem.update(delta); } // Update rain/snow particle positions based on wind // ✅ NULL CHECK: Ensure emitter exists and has setSpeedX method if (this.rainEmitter && this.rainEmitter.active && typeof this.rainEmitter.setSpeedX === 'function') { const windInfluence = this.windSystem.wind.strength * 20; this.rainEmitter.setSpeedX({ min: -windInfluence, max: windInfluence }); } if (this.snowEmitter && this.snowEmitter.active && typeof this.snowEmitter.setSpeedX === 'function') { const windInfluence = this.windSystem.wind.strength * 30; this.snowEmitter.setSpeedX({ min: -windInfluence, max: windInfluence }); } } /** * Cleanup */ destroy() { if (this.windSystem) this.windSystem.destroy(); if (this.rainEmitter) this.rainEmitter.destroy(); if (this.snowEmitter) this.snowEmitter.destroy(); this.fireSources.forEach(fire => { fire.flameEmitter.destroy(); fire.smokeEmitter.destroy(); }); this.puddles.forEach(puddle => puddle.destroy()); if (this.rippleParticles) this.rippleParticles.destroy(); } }