153 lines
6.0 KiB
JavaScript
153 lines
6.0 KiB
JavaScript
"use strict";
|
|
|
|
const SpawnStrategies = (function(){
|
|
|
|
const STRATEGIES = {
|
|
|
|
// 1. SOBREVUELO RASANTE CON EASE-OUT CÚBICO
|
|
overflight : {
|
|
init : (enemy, config, xgravity) => {
|
|
const base_size = config.size || 1.8;
|
|
const duration = (typeof config.duration === "number")
|
|
? config.duration
|
|
: xgravity.settings.get("enemy_overflight_duration", null, 1.4);
|
|
|
|
const initial_scale = (typeof config.initial_scale === "number")
|
|
? config.initial_scale
|
|
: xgravity.settings.get("enemy_overflight_initial_scale", null, 6.5);
|
|
|
|
const target_angle = Math.random() * Math.PI * 2;
|
|
const target_dist = 4.0 + (Math.random() * 7.0);
|
|
const target_x = Math.cos(target_angle) * target_dist;
|
|
const target_y = Math.sin(target_angle) * target_dist;
|
|
|
|
const spawn_angle = Math.random() * Math.PI * 2;
|
|
const giant_half_size = (base_size * initial_scale) / 2;
|
|
const spawn_distance = 22.0 + giant_half_size + 2.0;
|
|
|
|
const spawn_x = Math.cos(spawn_angle) * spawn_distance;
|
|
const spawn_y = Math.sin(spawn_angle) * spawn_distance;
|
|
|
|
enemy.spawn_data = {
|
|
spawn_x, spawn_y,
|
|
target_x, target_y,
|
|
duration : Math.max(0.1, duration),
|
|
initial_scale
|
|
};
|
|
|
|
enemy.x = spawn_x;
|
|
enemy.y = spawn_y;
|
|
enemy.scale = initial_scale;
|
|
enemy.alpha = 0.4;
|
|
enemy.ready = false;
|
|
|
|
const dx = target_x - spawn_x;
|
|
const dy = target_y - spawn_y;
|
|
enemy.angle = Math.atan2(dy, dx) + (Math.PI / 2);
|
|
},
|
|
|
|
update : (enemy, delta_time) => {
|
|
const data = enemy.spawn_data;
|
|
const t = Math.min(1.0, enemy.age / data.duration);
|
|
|
|
const inv_t = 1.0 - t;
|
|
const ease_progress = 1.0 - (inv_t * inv_t * inv_t);
|
|
|
|
enemy.x = data.spawn_x + ((data.target_x - data.spawn_x) * ease_progress);
|
|
enemy.y = data.spawn_y + ((data.target_y - data.spawn_y) * ease_progress);
|
|
|
|
const speed_factor = (3.0 * inv_t * inv_t) / data.duration;
|
|
enemy.vx = (data.target_x - data.spawn_x) * speed_factor;
|
|
enemy.vy = (data.target_y - data.spawn_y) * speed_factor;
|
|
|
|
enemy.scale = data.initial_scale - ((data.initial_scale - 1.0) * ease_progress);
|
|
enemy.alpha = 0.4 + (ease_progress * 0.6);
|
|
|
|
if(t >= 1.0){
|
|
enemy.scale = 1.0;
|
|
enemy.alpha = 1.0;
|
|
enemy.ready = true;
|
|
|
|
const spd = Math.sqrt(enemy.vx * enemy.vx + enemy.vy * enemy.vy);
|
|
if(spd > 0.001){
|
|
enemy.vx = (enemy.vx / spd) * 3.2;
|
|
enemy.vy = (enemy.vy / spd) * 3.2;
|
|
}
|
|
}
|
|
}
|
|
},
|
|
|
|
// 2. EMERGENCIA DESDE EL FONDO ESPACIAL (DEEP SPACE)
|
|
deep_space : {
|
|
init : (enemy, config) => {
|
|
const spawn_angle = Math.random() * Math.PI * 2;
|
|
const r = Math.random() * 15.0;
|
|
enemy.x = Math.cos(spawn_angle) * r;
|
|
enemy.y = Math.sin(spawn_angle) * r;
|
|
enemy.scale = 0.0;
|
|
enemy.alpha = 0.3;
|
|
enemy.ready = false;
|
|
enemy.scale_speed = config.scale_speed || 0.65;
|
|
enemy.angle = Math.random() * Math.PI * 2;
|
|
},
|
|
|
|
update : (enemy, delta_time) => {
|
|
enemy.scale += enemy.scale_speed * delta_time;
|
|
enemy.alpha = 0.3 + (enemy.scale * 0.7);
|
|
|
|
if(enemy.scale >= 1.0){
|
|
enemy.scale = 1.0;
|
|
enemy.alpha = 1.0;
|
|
enemy.ready = true;
|
|
}
|
|
}
|
|
},
|
|
|
|
// 3. ENTRADA PERIMETRAL EXTERIOR (100% FUERA DE PANTALLA)
|
|
perimeter : {
|
|
init : (enemy, config) => {
|
|
const base_size = config.size || 1.8;
|
|
const spawn_angle = Math.random() * Math.PI * 2;
|
|
// Calculado holgadamente para que el sprite nunca corte el borde al nacer
|
|
const spawn_distance = 22.0 + (base_size / 2) + 2.0;
|
|
|
|
enemy.x = Math.cos(spawn_angle) * spawn_distance;
|
|
enemy.y = Math.sin(spawn_angle) * spawn_distance;
|
|
|
|
// Apunta hacia el cuadrante interior visible con ligera dispersión
|
|
const target_angle = spawn_angle + Math.PI + ((Math.random() - 0.5) * 0.7);
|
|
const speed = config.initial_speed || 4.2;
|
|
|
|
enemy.vx = Math.cos(target_angle) * speed;
|
|
enemy.vy = Math.sin(target_angle) * speed;
|
|
enemy.scale = 1.0;
|
|
enemy.alpha = 1.0;
|
|
enemy.ready = false;
|
|
enemy.angle = Math.atan2(enemy.vy, enemy.vx) + (Math.PI / 2);
|
|
},
|
|
|
|
update : (enemy, delta_time) => {
|
|
enemy.x += enemy.vx * delta_time;
|
|
enemy.y += enemy.vy * delta_time;
|
|
|
|
// Al entrar en el radio de combate (16 celdas), se activa para combate
|
|
const dist = Math.sqrt(enemy.x * enemy.x + enemy.y * enemy.y);
|
|
if(dist <= 16.0){
|
|
enemy.ready = true;
|
|
}
|
|
}
|
|
}
|
|
};
|
|
|
|
return {
|
|
get : (mode) => {
|
|
// Si le pasamos un array de modos, elige uno aleatoriamente
|
|
if(Array.isArray(mode)){
|
|
const pick = mode[Math.floor(Math.random() * mode.length)];
|
|
return STRATEGIES[pick] || STRATEGIES.overflight;
|
|
}
|
|
return STRATEGIES[mode] || STRATEGIES.overflight;
|
|
},
|
|
register : (name, strategy) => { STRATEGIES[name] = strategy; }
|
|
};
|
|
})(); |