/** * LIVING NOIR CITY - AMBIENT WORLD SYSTEM * Stray cats, barking dogs, atmospheric sounds */ class NoirCitySystem { constructor(scene) { this.scene = scene; this.animals = []; this.ambientSounds = []; this.isActive = false; } /** * INITIALIZE CITY ATMOSPHERE */ init() { this.spawnStrayAnimals(); this.setupAmbientSounds(); this.startAtmosphere(); this.isActive = true; console.log('🌆 Noir city atmosphere activated'); } /** * SPAWN STRAY CATS */ spawnStrayAnimals() { // Spawn 3-5 stray cats in random locations const catCount = Phaser.Math.Between(3, 5); for (let i = 0; i < catCount; i++) { this.spawnCat(); } // Spawn 2-3 dogs in shadows const dogCount = Phaser.Math.Between(2, 3); for (let i = 0; i < dogCount; i++) { this.spawnDog(); } } /** * SPAWN A STRAY CAT */ spawnCat() { const x = Phaser.Math.Between(100, this.scene.cameras.main.width - 100); const y = Phaser.Math.Between(100, this.scene.cameras.main.height - 100); const cat = this.scene.physics.add.sprite(x, y, 'stray_cat'); cat.setDepth(15); cat.setScale(0.8); // Cat behavior cat.animalType = 'cat'; cat.state = 'idle'; // 'idle', 'running', 'hiding' cat.runSpeed = 200; // Random idle movement this.scene.time.addEvent({ delay: Phaser.Math.Between(3000, 8000), callback: () => this.catIdleBehavior(cat), loop: true }); this.animals.push(cat); return cat; } /** * CAT IDLE BEHAVIOR */ catIdleBehavior(cat) { if (!cat || cat.state === 'running') return; const behavior = Phaser.Math.Between(1, 3); switch (behavior) { case 1: // Sit and clean cat.state = 'idle'; cat.setVelocity(0, 0); cat.play('cat_sit', true); break; case 2: // Wander cat.state = 'idle'; const wanderX = Phaser.Math.Between(-30, 30); const wanderY = Phaser.Math.Between(-30, 30); cat.setVelocity(wanderX, wanderY); cat.play('cat_walk', true); this.scene.time.delayedCall(2000, () => { if (cat) cat.setVelocity(0, 0); }); break; case 3: // Jump on trash can cat.play('cat_jump', true); break; } } /** * CAT RUNS AWAY FROM LONGBOARD */ catRunAway(cat, kai) { const distance = Phaser.Math.Distance.Between(cat.x, cat.y, kai.x, kai.y); if (distance < 80 && kai.body.speed > 50) { cat.state = 'running'; // Run opposite direction from Kai const angle = Phaser.Math.Angle.Between(kai.x, kai.y, cat.x, cat.y); const velocityX = Math.cos(angle) * cat.runSpeed; const velocityY = Math.sin(angle) * cat.runSpeed; cat.setVelocity(velocityX, velocityY); cat.play('cat_run', true); // Play meow sound if (this.scene.sound.get('cat_meow')) { this.scene.sound.play('cat_meow', { volume: 0.3 }); } // Stop running after escaping this.scene.time.delayedCall(1500, () => { if (cat) { cat.state = 'idle'; cat.setVelocity(0, 0); } }); } } /** * SPAWN A STRAY DOG (barks from shadows) */ spawnDog() { const x = Phaser.Math.Between(50, this.scene.cameras.main.width - 50); const y = Phaser.Math.Between(50, this.scene.cameras.main.height - 50); const dog = this.scene.physics.add.sprite(x, y, 'stray_dog'); dog.setDepth(14); dog.setAlpha(0.7); // Slightly transparent (in shadows) dog.animalType = 'dog'; dog.barkTimer = null; // Random barking dog.barkTimer = this.scene.time.addEvent({ delay: Phaser.Math.Between(8000, 15000), callback: () => this.dogBark(dog), loop: true }); this.animals.push(dog); return dog; } /** * DOG BARKING */ dogBark(dog) { if (!dog) return; dog.play('dog_bark', true); // Play bark sound (spatial audio) if (this.scene.sound.get('dog_bark')) { this.scene.sound.play('dog_bark', { volume: 0.4, // Spatial audio based on distance (if available) }); } // Show bark indicator const bark = this.scene.add.text( dog.x, dog.y - 30, 'WOOF!', { fontSize: '12px', fontFamily: 'Arial', color: '#ffffff', stroke: '#000000', strokeThickness: 3 } ); bark.setOrigin(0.5); bark.setDepth(50); bark.setAlpha(0.7); this.scene.tweens.add({ targets: bark, alpha: 0, y: bark.y - 20, duration: 1000, onComplete: () => bark.destroy() }); } /** * SETUP AMBIENT SOUNDS */ setupAmbientSounds() { // City ambient loop if (this.scene.sound.get('city_ambient')) { const cityAmbient = this.scene.sound.add('city_ambient', { volume: 0.2, loop: true }); cityAmbient.play(); this.ambientSounds.push(cityAmbient); } // Wind ambience if (this.scene.sound.get('wind_ambient')) { const wind = this.scene.sound.add('wind_ambient', { volume: 0.15, loop: true }); wind.play(); this.ambientSounds.push(wind); } // Random distant sounds this.scene.time.addEvent({ delay: Phaser.Math.Between(10000, 20000), callback: () => this.playRandomDistantSound(), loop: true }); } /** * PLAY RANDOM DISTANT SOUND */ playRandomDistantSound() { const sounds = [ 'distant_siren', 'metal_clang', 'glass_break', 'trash_can_fall', 'crow_caw' ]; const randomSound = Phaser.Utils.Array.GetRandom(sounds); if (this.scene.sound.get(randomSound)) { this.scene.sound.play(randomSound, { volume: 0.1, detune: Phaser.Math.Between(-200, 200) // Vary pitch }); } } /** * ATMOSPHERIC EFFECTS */ startAtmosphere() { // Dust particles floating this.addDustParticles(); // Paper/trash blowing in wind this.addBlowingTrash(); // Flickering streetlights (if night) this.addFlickeringLights(); } /** * ADD DUST PARTICLES */ addDustParticles() { if (!this.scene.add.particles) return; const particles = this.scene.add.particles('dust_particle'); const emitter = particles.createEmitter({ x: { min: 0, max: this.scene.cameras.main.width }, y: -20, speedY: { min: 20, max: 50 }, speedX: { min: -10, max: 10 }, scale: { start: 0.1, end: 0.3 }, alpha: { start: 0.3, end: 0 }, lifespan: 5000, frequency: 200, quantity: 1 }); emitter.setDepth(5); } /** * ADD BLOWING TRASH */ addBlowingTrash() { // Spawn occasional paper/trash that blows across screen this.scene.time.addEvent({ delay: Phaser.Math.Between(8000, 15000), callback: () => { const paper = this.scene.add.sprite( -50, Phaser.Math.Between(100, this.scene.cameras.main.height - 100), 'paper_trash' ); paper.setDepth(10); // Blow across screen this.scene.tweens.add({ targets: paper, x: this.scene.cameras.main.width + 50, angle: 360, duration: 8000, ease: 'Linear', onComplete: () => paper.destroy() }); }, loop: true }); } /** * ADD FLICKERING STREETLIGHTS */ addFlickeringLights() { // Find all streetlight sprites const lights = this.scene.children.list.filter(child => child.texture && child.texture.key === 'streetlight' ); lights.forEach(light => { // Random flicker this.scene.time.addEvent({ delay: Phaser.Math.Between(2000, 8000), callback: () => { // Quick flicker light.setAlpha(0.3); this.scene.time.delayedCall(100, () => { light.setAlpha(1); this.scene.time.delayedCall(50, () => { light.setAlpha(0.3); this.scene.time.delayedCall(100, () => { light.setAlpha(1); }); }); }); }, loop: true }); }); } /** * UPDATE (called every frame) */ update(kai) { if (!this.isActive) return; // Update cat behavior (run from Kai if on longboard) this.animals.forEach(animal => { if (animal.animalType === 'cat') { this.catRunAway(animal, kai); } }); } /** * DESTROY ALL ANIMALS AND SOUNDS */ destroy() { this.animals.forEach(animal => animal.destroy()); this.animals = []; this.ambientSounds.forEach(sound => sound.stop()); this.ambientSounds = []; this.isActive = false; } } export default NoirCitySystem;