"use strict"; const AsteroidEnemyModel = (function(){ const AsteroidEnemyModel = function(xgravity, config = {}){ const self = this; const resolve_image = (img) => { if(Array.isArray(img) && img.length > 0){ return img[Math.floor(Math.random() * img.length)]; } return typeof img === "string" ? img : null; }; const image_src = resolve_image(config.image); const base_size = config.size || 2.4; const points = config.points || 80; const mass = config.mass || 7.0; let health = config.health || 50; const max_health = health; let towards_mothership = false; const rotation_speed = (Math.random() - 0.5) * 1.5; const entity = { x : 0, y : 0, vx : 0, vy : 0, angle : Math.random() * Math.PI * 2, scale : 1.0, alpha : 1.0, ready : false, alive : true, age : 0, config : config }; const spawn_strategy = SpawnStrategies.get(config.spawn_mode || "perimeter"); const behavior_strategy = BehaviorStrategies.get(config.behavior || "cross_screen"); this.is_asteroid = true; this.is_targeted_towards_mothership = () => towards_mothership; this.has_impacted_mothership = () => Boolean(entity.impacted_mothership); // Lanzamiento hacia el centro this.launch_towards_mothership = (lvx, lvy) => { entity.vx = lvx; entity.vy = lvy; towards_mothership = true; entity.alpha = 1.0; // Garantiza 100% de opacidad sin transparencia de spawn }; // Si vuela hacia la nodriza, queremos que se pinte en render_over por encima del plano de combate // Cuando towards_mothership = true, se fuerza a FALSE para que el gestor lo pinte en render_over this.is_ready = () => entity.ready && !towards_mothership; this.is_alive = () => entity.alive; this.get_points = () => points; this.get_age = () => entity.age; this.get_mass = () => mass; this.get_health = () => health; this.get_max_health = () => max_health; this.get_velocity = () => ({ vx: entity.vx, vy: entity.vy }); this.get_state = () => ({ x : entity.x, y : entity.y, angle : entity.angle, scale : entity.scale, alpha : 1.0, // Siempre nítido en el eje Z size : base_size, image_src : image_src, ready : entity.ready && !towards_mothership, // En render_over cuando vuela al cristal alive : entity.alive, health : health, max_health : max_health, radius : (base_size * entity.scale) * 0.45, fallback_color : towards_mothership ? "#ef4444" : "#94a3b8" }); this.get_bounds = () => ({ x : entity.x, y : entity.y, radius : (base_size * entity.scale) * 0.45 }); this.apply_impulse = (ivx, ivy) => { entity.vx += ivx; entity.vy += ivy; // Si el dron lo choca con escudo, desvía la trayectoria y frena el crecimiento towards_mothership = false; entity.scale = Math.min(entity.scale, 2.5); }; this.set_velocity = (nvx, nvy) => { entity.vx = nvx; entity.vy = nvy; }; this.set_position = (nx, ny) => { entity.x = nx; entity.y = ny; }; this.take_damage = (amount) => { health = Math.max(0, health - amount); if(health <= 0){ entity.alive = false; } return entity.alive; }; this.on_hit = () => { entity.alive = false; }; this.update = (delta_time, drone_info, all_enemies) => { if(!entity.alive) return; entity.age += delta_time; if(!entity.ready){ spawn_strategy.update(entity, delta_time); } else if(towards_mothership){ // 1. VIAJE HACIA EL CRISTAL DE LA CABINA (EJE Z) entity.x += entity.vx * delta_time; entity.y += entity.vy * delta_time; // Atracción suave hacia el centro absoluto (0, 0) const dist_center = Math.sqrt(entity.x * entity.x + entity.y * entity.y); if(dist_center > 0.1){ const pull_speed = 6.0; entity.vx = -(entity.x / dist_center) * pull_speed; entity.vy = -(entity.y / dist_center) * pull_speed; } // Crecimiento de escala hasta llegar a un tamaño de 40 celdas // Tamaño actual = base_size * entity.scale const target_cells = 40.0; const scale_target = target_cells / base_size; // ~16.66 const growth_speed = 10.5; // Rapidez de expansión visual hacia la cámara entity.scale += growth_speed * delta_time; // Al alcanzar el tamaño gigante, se estrella en la pantalla if((base_size * entity.scale) >= target_cells){ entity.alive = false; entity.impacted_mothership = true; self.impacted_mothership = true; // <-- Asignado también directamente a la instancia } } else { // Vuelo normal behavior_strategy(entity, delta_time, drone_info, all_enemies); // Si se sale del marco de juego (> 24.5 celdas) se elimina definitivamente const dist_center = Math.sqrt(entity.x * entity.x + entity.y * entity.y); if(dist_center > 24.5 && entity.age > 1.2){ entity.alive = false; } } // Giro sobre su eje entity.angle += rotation_speed * delta_time; }; const constructor = () => { spawn_strategy.init(entity, config, xgravity); }; constructor(); }; return AsteroidEnemyModel; })();