174 lines
6.1 KiB
JavaScript
174 lines
6.1 KiB
JavaScript
"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;
|
|
})(); |