683 lines
27 KiB
JavaScript
683 lines
27 KiB
JavaScript
"use strict";
|
|
|
|
const GameView = (function(){
|
|
|
|
const GameView = function(xgravity){
|
|
|
|
const self = this;
|
|
let view_element = null,
|
|
canvas_element = null,
|
|
ctx = null,
|
|
animation_frame_id = null,
|
|
last_timestamp = 0,
|
|
is_paused = false,
|
|
pause_modal = null,
|
|
bg_manager = null,
|
|
current_bg_mode = null;
|
|
|
|
let cockpit_model = null;
|
|
let drone_model = null;
|
|
let hud_model = null;
|
|
let enemies_manager = null;
|
|
// let explosions_model = null;
|
|
|
|
// Variables de balanceo e inercia de cámara
|
|
let camera_drift_x = 0;
|
|
let camera_drift_y = 0;
|
|
let camera_sway_clock = 0;
|
|
let bg_parallax_x = 0;
|
|
let bg_parallax_y = 0;
|
|
|
|
// Variables de Screen Shake
|
|
let shake_trauma = 0.0;
|
|
let shake_duration = 0.0;
|
|
let shake_max_duration = 0.1;
|
|
let shake_intensity = 0.0;
|
|
let shake_offset_x = 0;
|
|
let shake_offset_y = 0;
|
|
let shake_angle = 0;
|
|
|
|
// Estado del juego
|
|
let current_score = 0;
|
|
let screen_shake_intensity = 0;
|
|
let screen_shake_decay = 4.0;
|
|
|
|
let drone_lives = 3;
|
|
let max_drone_lives = 3;
|
|
let is_respawning_drone = false;
|
|
let is_game_over = false;
|
|
|
|
let session_stats = {
|
|
total_spawned : 0,
|
|
total_destroyed : 0,
|
|
shield_kills : 0
|
|
};
|
|
|
|
// const trigger_mega_shake = () => {
|
|
// screen_shake_intensity = 28.0; // Temblores violentos en píxeles
|
|
// };
|
|
|
|
// Métodos de control de estado expuestos
|
|
this.is_active = () => animation_frame_id !== null;
|
|
this.is_paused = () => is_paused;
|
|
|
|
this.pause = () => {
|
|
is_paused = true;
|
|
if(xgravity.views.fps){
|
|
xgravity.views.fps.close();
|
|
}
|
|
};
|
|
|
|
this.resume = () => {
|
|
is_paused = false;
|
|
last_timestamp = 0; // Evita saltos de delta_time al volver
|
|
};
|
|
|
|
const on_pointer_move = (e) => {
|
|
if(!canvas_element || !drone_model) return;
|
|
const rect = canvas_element.getBoundingClientRect();
|
|
const client_x = e.touches ? e.touches[0].clientX : e.clientX;
|
|
const client_y = e.touches ? e.touches[0].clientY : e.clientY;
|
|
|
|
const scale_x = canvas_element.width / rect.width;
|
|
const scale_y = canvas_element.height / rect.height;
|
|
|
|
const canvas_pixel_x = (client_x - rect.left) * scale_x;
|
|
const canvas_pixel_y = (client_y - rect.top) * scale_y;
|
|
|
|
drone_model.set_target_pixels(canvas_pixel_x, canvas_pixel_y, canvas_element.width, canvas_element.height);
|
|
};
|
|
|
|
// Controladores de activación de escudo
|
|
const on_mouse_down = (e) => {
|
|
if(e.button === 0 && drone_model){
|
|
drone_model.set_shield_active(true);
|
|
}
|
|
};
|
|
|
|
const on_mouse_up = (e) => {
|
|
if(e.button === 0 && drone_model){
|
|
drone_model.set_shield_active(false);
|
|
}
|
|
};
|
|
|
|
const on_key_down = (e) => {
|
|
// if((e.code === "Space" || e.key === " ") && !e.repeat && drone_model){
|
|
// drone_model.set_shield_active(true);
|
|
// }
|
|
// Tecla ESC: abre/cierra menú o pausa la partida
|
|
if(e.code === "Escape" || e.key === "Escape"){
|
|
e.preventDefault();
|
|
self.toggle_pause();
|
|
return;
|
|
}
|
|
|
|
const action_key = xgravity.settings.get("action_key", null, "Space");
|
|
const matches_action = (action_key === "Space" && (e.code === "Space" || e.key === " ")) ||
|
|
(e.code === action_key || e.key.toUpperCase() === action_key.toUpperCase());
|
|
|
|
if(matches_action && !e.repeat && drone_model){
|
|
drone_model.set_shield_active(true);
|
|
}
|
|
};
|
|
|
|
const on_key_up = (e) => {
|
|
// if((e.code === "Space" || e.key === " ") && drone_model){
|
|
// drone_model.set_shield_active(false);
|
|
// }
|
|
const action_key = xgravity.settings.get("action_key", null, "Space");
|
|
const matches_action = (action_key === "Space" && (e.code === "Space" || e.key === " ")) ||
|
|
(e.code === action_key || e.key.toUpperCase() === action_key.toUpperCase());
|
|
|
|
if(matches_action && drone_model){
|
|
drone_model.set_shield_active(false);
|
|
}
|
|
};
|
|
|
|
const on_touch_start = (e) => {
|
|
if(drone_model){
|
|
drone_model.set_shield_active(true);
|
|
}
|
|
};
|
|
|
|
const on_touch_end = (e) => {
|
|
if(drone_model){
|
|
drone_model.set_shield_active(false);
|
|
}
|
|
};
|
|
|
|
// Callback cuando el escudo destruye a un enemigo
|
|
const on_enemy_destroyed = (enemy, impact_damage = 0) => {
|
|
const bounds = enemy.get_bounds();
|
|
|
|
// 1. Efecto visual de destrucción
|
|
if(xgravity.explosions){
|
|
xgravity.explosions.add(bounds.x, bounds.y, "shield");
|
|
}
|
|
trigger_screen_shake("shield");
|
|
|
|
// 2. Puntos base configurados en el enemigo
|
|
const base_pts = (typeof enemy.get_points === "function") ? enemy.get_points() : 100;
|
|
|
|
// 3. MULTIPLICADOR CINÉTICO:
|
|
// Si el daño fue 20 -> x1.20 | Si embistes con inercia y fue 65 -> x1.65 | Si fue 120 -> x2.20
|
|
const kinetic_mult = impact_damage > 0 ? (1.0 + (impact_damage * 0.01)) : 1.0;
|
|
const final_pts = Math.round(base_pts * kinetic_mult);
|
|
|
|
// 4. Sumar puntos y actualizar el HUD inmediatamente
|
|
current_score += final_pts;
|
|
if(hud_model){
|
|
hud_model.set_score(current_score);
|
|
}
|
|
|
|
// 5. Estadísticas de la partida
|
|
session_stats.total_destroyed++;
|
|
session_stats.shield_kills++;
|
|
|
|
// 6. Recarga del escudo según dificultad
|
|
if(drone_model){
|
|
const base_reward = xgravity.settings.get("base_drone_shield_kill_reward", null, 10.0);
|
|
const preset = xgravity.settings.get_difficulty_preset();
|
|
const reward = base_reward * (preset ? preset.shield_reward_mult : 1.0);
|
|
drone_model.restore_shield(reward);
|
|
}
|
|
};
|
|
|
|
const on_mothership_impact = (base_damage = 25) => {
|
|
console.log("¡BOOM! Impacto en cabina recibido con daño base:", base_damage);
|
|
|
|
const preset = xgravity.settings.get_difficulty_preset();
|
|
const dmg_mult = preset ? preset.mothership_damage_mult : 1.0;
|
|
const final_damage = base_damage * dmg_mult;
|
|
|
|
if(xgravity.audio){
|
|
xgravity.audio.play("mothership_impact");
|
|
}
|
|
|
|
if(cockpit_model && typeof cockpit_model.take_damage === "function"){
|
|
const is_destroyed = cockpit_model.take_damage(final_damage);
|
|
if(is_destroyed){
|
|
on_game_over("mothership_destroyed");
|
|
}
|
|
}
|
|
|
|
// Disparo del temblor brutal de la nodriza
|
|
trigger_screen_shake("mothership");
|
|
|
|
if(xgravity.explosions && typeof xgravity.explosions.add_mothership_impact === "function"){
|
|
xgravity.explosions.add_mothership_impact();
|
|
}
|
|
};
|
|
|
|
// Callback cuando impacta al dron sin escudo
|
|
const on_drone_hit = (enemy, damage_to_drone = 35) => {
|
|
const bounds = enemy.get_bounds();
|
|
if(xgravity.explosions){
|
|
xgravity.explosions.add(bounds.x, bounds.y, "hit");
|
|
}
|
|
trigger_screen_shake("hit");
|
|
if(cockpit_model) cockpit_model.set_alert(true);
|
|
|
|
if(drone_model && damage_to_drone > 0){
|
|
drone_model.damage_shield(damage_to_drone);
|
|
// Si el escudo/vida cae a 0, estalla y entra el siguiente
|
|
if(drone_model.get_position().shield <= 0){
|
|
destroy_current_drone();
|
|
}
|
|
}
|
|
};
|
|
|
|
// Callback cuando el enemigo sobrevive al choque (chispas y deflexión)
|
|
const on_enemy_deflected = (x, y, nx, ny, intensity, damage, shield_active) => {
|
|
if(xgravity.explosions){
|
|
xgravity.explosions.add_sparks(x, y, nx, ny, intensity);
|
|
}
|
|
|
|
if(!shield_active && drone_model){
|
|
drone_model.damage_shield(damage * 0.5);
|
|
trigger_screen_shake("shield");
|
|
} else if(shield_active && drone_model){
|
|
drone_model.damage_shield(damage * 0.2);
|
|
}
|
|
};
|
|
|
|
const on_game_over = (reason = "mothership_destroyed") => {
|
|
if(is_game_over) return;
|
|
is_game_over = true;
|
|
console.log("=== GAME OVER ===", reason);
|
|
|
|
// Ocultamos el botón de pausa para que no estorbe en el Game Over
|
|
if(view_element){
|
|
const pause_btn = view_element.querySelector(".xg-hud-pause-btn");
|
|
if(pause_btn) pause_btn.style.display = "none";
|
|
}
|
|
|
|
trigger_mega_shake();
|
|
|
|
// Si EnemiesManager lleva la cuenta de spawns, sincronizamos:
|
|
if(enemies_manager && typeof enemies_manager.get_total_spawned === "function"){
|
|
session_stats.total_spawned = enemies_manager.get_total_spawned();
|
|
}
|
|
|
|
const preset = (xgravity.settings && typeof xgravity.settings.get_difficulty_preset === "function")
|
|
? xgravity.settings.get_difficulty_preset()
|
|
: null;
|
|
|
|
const final_data = {
|
|
score : current_score,
|
|
reason : reason,
|
|
difficulty : preset ? preset.id : "average",
|
|
total_spawned : Math.max(session_stats.total_spawned, session_stats.total_destroyed),
|
|
total_destroyed : session_stats.total_destroyed,
|
|
shield_kills : session_stats.shield_kills
|
|
};
|
|
|
|
// Pausar el juego y abrir la pantalla tras un breve retardo dramático
|
|
setTimeout(() => {
|
|
self.pause();
|
|
if(xgravity.views.game_over){
|
|
xgravity.views.game_over.go(final_data);
|
|
}
|
|
}, 600);
|
|
};
|
|
|
|
const destroy_current_drone = () => {
|
|
if(!drone_model || drone_model.is_deploying() || is_respawning_drone || is_game_over) return;
|
|
is_respawning_drone = true;
|
|
|
|
const pos = drone_model.get_position();
|
|
|
|
// 1. Explosión de destrucción en el plano de batalla espacial
|
|
if(xgravity.explosions){
|
|
for(let k = 0; k < 3; k++){
|
|
xgravity.explosions.add(pos.cell_x + ((Math.random() - 0.5) * 1.5), pos.cell_y + ((Math.random() - 0.5) * 1.5), "hit");
|
|
}
|
|
xgravity.explosions.add_sparks(pos.cell_x, pos.cell_y, 0, -1, 3.0);
|
|
}
|
|
trigger_screen_shake("mothership");
|
|
|
|
// 2. Restar vida
|
|
drone_lives = Math.max(0, drone_lives - 1);
|
|
if(hud_model){
|
|
hud_model.set_lives(drone_lives, max_drone_lives);
|
|
}
|
|
|
|
// 3. Evaluar derrota o siguiente dron
|
|
if(drone_lives <= 0){
|
|
on_game_over("all_drones_lost");
|
|
} else {
|
|
// Pequeña pausa dramática para que se aprecie la explosión (~0.4s) antes de que la nodriza lance el siguiente
|
|
setTimeout(() => {
|
|
if(drone_model && !is_game_over){
|
|
drone_model.restore_shield(drone_model.get_position().max_shield || 100);
|
|
drone_model.start_deployment(); // Emerge a tamaño x10 desde abajo
|
|
}
|
|
is_respawning_drone = false;
|
|
}, 400);
|
|
}
|
|
};
|
|
|
|
// Disparador de sacudida con perfiles de intensidad
|
|
const trigger_screen_shake = (type = "shield") => {
|
|
shake_trauma = 1.0;
|
|
|
|
if(type === "shield"){
|
|
// Golpe amortiguado en el escudo del dron
|
|
shake_intensity = xgravity.settings.get("screen_shake_shield_intensity", null, 0.25);
|
|
shake_max_duration = xgravity.settings.get("screen_shake_shield_duration", null, 0.15);
|
|
} else if(type === "mothership" || type === "critical"){
|
|
// IMPACTO CRÍTICO EN CABINA: Sacudida masiva y violenta
|
|
shake_intensity = xgravity.settings.get("screen_shake_mothership_intensity", null, 4.2); // ~4.2 celdas de amplitud
|
|
shake_max_duration = xgravity.settings.get("screen_shake_mothership_duration", null, 1.15); // >1 segundo de temblor
|
|
} else {
|
|
// Golpe en el dron sin escudo
|
|
shake_intensity = xgravity.settings.get("screen_shake_hit_intensity", null, 0.85);
|
|
shake_max_duration = xgravity.settings.get("screen_shake_hit_duration", null, 0.42);
|
|
}
|
|
shake_duration = shake_max_duration;
|
|
};
|
|
|
|
const trigger_mega_shake = () => {
|
|
trigger_screen_shake("mothership");
|
|
};
|
|
|
|
const sync_background = () => {
|
|
const mode = xgravity.settings.get("background_mode", null, "stars");
|
|
if(current_bg_mode !== mode || !bg_manager){
|
|
current_bg_mode = mode;
|
|
if(mode === "image")
|
|
bg_manager = new ImageBackground(xgravity);
|
|
else if(mode === "procedural")
|
|
bg_manager = new ProceduralBackground(xgravity);
|
|
else
|
|
bg_manager = new StarsBackground(xgravity);
|
|
|
|
if(canvas_element)
|
|
bg_manager.resize(canvas_element.width, canvas_element.height);
|
|
}
|
|
};
|
|
|
|
const resize_canvas = () => {
|
|
if(!canvas_element) return;
|
|
const quality_pct = xgravity.settings.get("canvas_quality", null, 100) / 100;
|
|
const rect = canvas_element.getBoundingClientRect();
|
|
|
|
const internal_w = Math.max(1, Math.floor(rect.width * quality_pct));
|
|
const internal_h = Math.max(1, Math.floor(rect.height * quality_pct));
|
|
|
|
if(canvas_element.width !== internal_w || canvas_element.height !== internal_h){
|
|
canvas_element.width = internal_w;
|
|
canvas_element.height = internal_h;
|
|
if(bg_manager)
|
|
bg_manager.resize(internal_w, internal_h);
|
|
}
|
|
};
|
|
|
|
const update_camera = (delta_time, cell_px) => {
|
|
camera_sway_clock += delta_time;
|
|
|
|
const sway_speed = xgravity.settings.get("cockpit_sway_speed", null, 0.8);
|
|
|
|
if(drone_model){
|
|
const drone_pos = drone_model.get_position();
|
|
|
|
// Inercia de cabina
|
|
const drift_factor = xgravity.settings.get("cockpit_drift_amount", null, 0.6);
|
|
const target_drift_x = (-drone_pos.cell_x / 20.0) * drift_factor;
|
|
const target_drift_y = (-drone_pos.cell_y / 20.0) * drift_factor;
|
|
|
|
camera_drift_x += (target_drift_x - camera_drift_x) * 4.0 * delta_time;
|
|
camera_drift_y += (target_drift_y - camera_drift_y) * 4.0 * delta_time;
|
|
|
|
// Parallax del fondo estelar
|
|
const bg_factor = xgravity.settings.get("background_parallax_amount", null, 0.25);
|
|
const target_bg_x = (drone_pos.cell_x / 20.0) * bg_factor * cell_px;
|
|
const target_bg_y = (drone_pos.cell_y / 20.0) * bg_factor * cell_px;
|
|
|
|
bg_parallax_x += (target_bg_x - bg_parallax_x) * 3.0 * delta_time;
|
|
bg_parallax_y += (target_bg_y - bg_parallax_y) * 3.0 * delta_time;
|
|
}
|
|
|
|
// Screen Shake
|
|
if(shake_duration > 0){
|
|
shake_duration -= delta_time;
|
|
shake_trauma = shake_max_duration > 0 ? Math.max(0, shake_duration / shake_max_duration) : 0;
|
|
|
|
// Decaimiento no lineal (trauma al cuadrado): arranca fuertísimo y decae suave
|
|
const trauma_factor = shake_trauma * shake_trauma;
|
|
const shake_offset_cells = trauma_factor * shake_intensity;
|
|
|
|
shake_offset_x = (Math.random() * 2 - 1) * shake_offset_cells * cell_px;
|
|
shake_offset_y = (Math.random() * 2 - 1) * shake_offset_cells * cell_px;
|
|
|
|
// Si es un impacto de nodriza (shake_intensity > 2.0), el latigazo angular se triplica (~0.08 rad ≈ 4.6°)
|
|
const max_roll = (shake_intensity > 2.0) ? 0.08 : 0.025;
|
|
shake_angle = (Math.random() * 2 - 1) * (trauma_factor * max_roll);
|
|
} else {
|
|
shake_offset_x = 0;
|
|
shake_offset_y = 0;
|
|
shake_angle = 0;
|
|
}
|
|
};
|
|
|
|
const game_loop = (timestamp) => {
|
|
if(!last_timestamp) last_timestamp = timestamp;
|
|
const delta_time = Math.min((timestamp - last_timestamp) / 1000, 0.1);
|
|
last_timestamp = timestamp;
|
|
|
|
const cell_size = (canvas_element.width < canvas_element.height ? canvas_element.width : canvas_element.height) / xgravity.settings.get("cells", null, 40);
|
|
|
|
if(!is_paused){
|
|
sync_background();
|
|
resize_canvas();
|
|
|
|
if(xgravity.views.fps){
|
|
xgravity.views.fps.tick(delta_time);
|
|
}
|
|
|
|
// 1. FÍSICAS Y ACTUALIZACIONES (Aquí solo van updates)
|
|
if(enemies_manager){
|
|
enemies_manager.update(delta_time, drone_model, on_enemy_destroyed, on_drone_hit, on_enemy_deflected, on_mothership_impact);
|
|
}
|
|
if(drone_model){
|
|
drone_model.update(delta_time);
|
|
}
|
|
if(cockpit_model){
|
|
cockpit_model.update(delta_time);
|
|
}
|
|
if(xgravity.explosions){
|
|
xgravity.explosions.update(delta_time); // <-- ACTIVADO
|
|
}
|
|
if(hud_model){
|
|
hud_model.render(ctx, canvas_element.width, canvas_element.height, delta_time, drone_model, cockpit_model, current_score, drone_lives, max_drone_lives);
|
|
}
|
|
|
|
// 2. CÁMARA Y TRAUMA
|
|
update_camera(delta_time, cell_size);
|
|
|
|
// 3. LIMPIEZA
|
|
ctx.clearRect(0, 0, canvas_element.width, canvas_element.height);
|
|
|
|
// 4. FONDO ESTELAR CON PARALLAX
|
|
if(bg_manager){
|
|
ctx.save();
|
|
ctx.translate(bg_parallax_x + (shake_offset_x * 0.4), bg_parallax_y + (shake_offset_y * 0.4));
|
|
bg_manager.render(ctx, canvas_element.width, canvas_element.height, delta_time);
|
|
ctx.restore();
|
|
}
|
|
|
|
// 5. SCREEN SHAKE ACTIVO PARA ENTIDADES Y CABINA
|
|
ctx.save();
|
|
const cx = canvas_element.width / 2;
|
|
const cy = canvas_element.height / 2;
|
|
|
|
ctx.translate(cx + shake_offset_x, cy + shake_offset_y);
|
|
if(shake_angle !== 0) ctx.rotate(shake_angle);
|
|
ctx.translate(-cx, -cy);
|
|
|
|
// A) Enemigos en el plano de batalla (debajo del dron)
|
|
if(enemies_manager){
|
|
enemies_manager.render_under(ctx, canvas_element.width, canvas_element.height);
|
|
}
|
|
|
|
// B) Dron y explosiones de combate espacial
|
|
if(drone_model){
|
|
drone_model.render(ctx, canvas_element.width, canvas_element.height);
|
|
}
|
|
if(xgravity.explosions){
|
|
xgravity.explosions.render(ctx, canvas_element.width, canvas_element.height);
|
|
}
|
|
|
|
// C) Enemigos en sobrevuelo + Retículas y Daño flotante
|
|
if(enemies_manager){
|
|
enemies_manager.render_over(ctx, canvas_element.width, canvas_element.height);
|
|
}
|
|
|
|
// D) Cúpula / Cabina con balanceo inercial
|
|
ctx.save();
|
|
const drift_px_x = (camera_drift_x + Math.sin(camera_sway_clock * 0.8) * 0.15) * cell_size;
|
|
const drift_px_y = (camera_drift_y + Math.cos(camera_sway_clock * 0.56) * 0.15) * cell_size;
|
|
ctx.translate(drift_px_x, drift_px_y);
|
|
|
|
if(cockpit_model){
|
|
cockpit_model.render(ctx, canvas_element.width, canvas_element.height, delta_time);
|
|
}
|
|
ctx.restore();
|
|
|
|
// E) EFECTOS DE CABINA (Flash de pantalla y fragmentos de roca sobre el cristal)
|
|
if(xgravity.explosions && typeof xgravity.explosions.render_cockpit === "function"){
|
|
xgravity.explosions.render_cockpit(ctx, canvas_element.width, canvas_element.height);
|
|
}
|
|
|
|
ctx.restore(); // Cierre de Screen Shake
|
|
|
|
// 6. HUD HOLOGRÁFICO (fijo en cabina)
|
|
if(hud_model){
|
|
hud_model.render(ctx, canvas_element.width, canvas_element.height, delta_time, drone_model, cockpit_model, current_score);
|
|
}
|
|
}
|
|
|
|
animation_frame_id = requestAnimationFrame(game_loop);
|
|
};
|
|
|
|
this.update_hud = () => {};
|
|
|
|
this.toggle_pause = () => {
|
|
if(!is_paused){
|
|
self.pause();
|
|
if(xgravity.views.main_menu){
|
|
xgravity.views.main_menu.go();
|
|
}
|
|
} else {
|
|
// Si ya estaba pausado y pulsa ESC de nuevo, reanudamos
|
|
if(xgravity.views.main_menu){
|
|
xgravity.views.main_menu.close();
|
|
}
|
|
self.resume();
|
|
}
|
|
};
|
|
|
|
this.stop = () => {
|
|
if(animation_frame_id){
|
|
cancelAnimationFrame(animation_frame_id);
|
|
animation_frame_id = null;
|
|
}
|
|
if(view_element && view_element.parentNode){
|
|
view_element.parentNode.removeChild(view_element);
|
|
view_element = null;
|
|
}
|
|
|
|
if(xgravity.views.fps){
|
|
xgravity.views.fps.close();
|
|
}
|
|
|
|
window.removeEventListener("mousedown", on_mouse_down);
|
|
window.removeEventListener("mouseup", on_mouse_up);
|
|
window.removeEventListener("keydown", on_key_down);
|
|
window.removeEventListener("keyup", on_key_up);
|
|
window.removeEventListener("touchstart", on_touch_start);
|
|
window.removeEventListener("touchend", on_touch_end);
|
|
|
|
window.removeEventListener("mousemove", on_pointer_move);
|
|
window.removeEventListener("touchmove", on_pointer_move);
|
|
|
|
cockpit_model = null;
|
|
drone_model = null;
|
|
hud_model = null;
|
|
enemies_manager = null;
|
|
// explosions_model = null;
|
|
|
|
bg_manager = null;
|
|
current_bg_mode = null;
|
|
};
|
|
|
|
this.go = () => {
|
|
self.stop();
|
|
|
|
is_paused = false;
|
|
current_score = 0;
|
|
last_timestamp = 0;
|
|
|
|
cockpit_model = new CockpitModel(xgravity);
|
|
drone_model = new DroneModel(xgravity);
|
|
hud_model = new TelemetryHudModel(xgravity);
|
|
enemies_manager = new EnemiesManager(xgravity);
|
|
// explosions_model = new ExplosionsModel(xgravity);
|
|
|
|
window.addEventListener("mousedown", on_mouse_down);
|
|
window.addEventListener("mouseup", on_mouse_up);
|
|
window.addEventListener("keydown", on_key_down);
|
|
window.addEventListener("keyup", on_key_up);
|
|
window.addEventListener("touchstart", on_touch_start, { passive : true });
|
|
window.addEventListener("touchend", on_touch_end, { passive : true });
|
|
|
|
window.addEventListener("mousemove", on_pointer_move);
|
|
window.addEventListener("touchmove", on_pointer_move, { passive : true });
|
|
|
|
session_stats = {
|
|
total_spawned : 0,
|
|
total_destroyed : 0,
|
|
shield_kills : 0
|
|
};
|
|
|
|
if(enemies_manager){
|
|
enemies_manager.set_on_mothership_impact((base_damage, impact_x, impact_y) => {
|
|
// 1. Calcular daño mitigado o potenciado por la dificultad
|
|
const preset = xgravity.settings.get_difficulty_preset();
|
|
const dmg_mult = preset ? preset.mothership_damage_mult : 1.0;
|
|
const final_damage = base_damage * dmg_mult;
|
|
|
|
// 2. Aplicar daño a la nave nodriza
|
|
let is_destroyed = false;
|
|
if(cockpit_model && typeof cockpit_model.take_damage === "function"){
|
|
is_destroyed = cockpit_model.take_damage(final_damage);
|
|
}
|
|
|
|
// 3. Efectos visuales brutales
|
|
trigger_mega_shake();
|
|
|
|
const cx = canvas.width / 2;
|
|
const cy = canvas.height / 2;
|
|
if(xgravity.explosions && typeof xgravity.explosions.add_mothership_impact === "function"){
|
|
xgravity.explosions.add_mothership_impact(cx, cy);
|
|
}
|
|
|
|
// 4. Si el casco llega a cero: Game Over
|
|
if(is_destroyed){
|
|
// Trigger de fin de partida
|
|
console.log("¡NAVE NODRIZA DESTRUIDA!");
|
|
}
|
|
});
|
|
}
|
|
|
|
const nodes = xgravity.html(xgravity.item_self, [
|
|
"div", { class : "xg-game-container" }, [
|
|
["canvas", { class : "xg-canvas" }],
|
|
["div", { class : "xg-hud" }, [
|
|
// Arriba (FPS y telemetría)
|
|
["div", { class : "xg-hud-top" }],
|
|
|
|
// Abajo a la derecha: Botón de Pausa
|
|
["div", { class : "xg-hud-bottom" }, [
|
|
["button", {
|
|
class : "xg-btn xg-hud-pause-btn",
|
|
"data-i18n" : "hud_pause_title",
|
|
onclick : () => self.toggle_pause()
|
|
}]
|
|
]]
|
|
]]
|
|
]
|
|
]);
|
|
|
|
const preset = (xgravity.settings && typeof xgravity.settings.get_difficulty_preset === "function")
|
|
? xgravity.settings.get_difficulty_preset()
|
|
: null;
|
|
|
|
max_drone_lives = (preset && preset.drone_lives !== undefined)
|
|
? preset.drone_lives
|
|
: xgravity.settings.get("drone_lives", null, 3);
|
|
|
|
drone_lives = max_drone_lives;
|
|
is_game_over = false;
|
|
is_respawning_drone = false;
|
|
|
|
if(hud_model){
|
|
hud_model.set_lives(drone_lives, max_drone_lives);
|
|
}
|
|
|
|
view_element = nodes[0];
|
|
canvas_element = view_element.querySelector("canvas");
|
|
ctx = canvas_element.getContext("2d");
|
|
|
|
sync_background();
|
|
resize_canvas();
|
|
self.update_hud();
|
|
xgravity.i18n.update();
|
|
|
|
animation_frame_id = requestAnimationFrame(game_loop);
|
|
};
|
|
|
|
};
|
|
|
|
return GameView;
|
|
})(); |