"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; } }; })();