272 lines
10 KiB
JavaScript
272 lines
10 KiB
JavaScript
"use strict";
|
|
|
|
const ExplosionsModel = (function(){
|
|
|
|
const ExplosionsModel = function(xgravity){
|
|
|
|
const self = this;
|
|
|
|
let particles = [];
|
|
let shockwaves = [];
|
|
let sparks = [];
|
|
|
|
this.add_sparks = (cell_x, cell_y, normal_x, normal_y, intensity = 1.0) => {
|
|
// A mayor intensidad de impacto, más chispas y más velocidad
|
|
const base_count = Math.floor(10 + (intensity * 25));
|
|
const count = Math.min(60, base_count);
|
|
const base_angle = Math.atan2(normal_y, normal_x);
|
|
|
|
for(let i = 0; i < count; i++){
|
|
// Cono de dispersión reflejado con +- 60 grados
|
|
const spread = (Math.random() - 0.5) * (Math.PI * 0.7);
|
|
const spark_angle = base_angle + spread;
|
|
const speed = (Math.random() * 12 + 6) * Math.min(2.5, intensity);
|
|
const life = (Math.random() * 0.18) + 0.08;
|
|
|
|
sparks.push({
|
|
x : cell_x,
|
|
y : cell_y,
|
|
vx : Math.cos(spark_angle) * speed,
|
|
vy : Math.sin(spark_angle) * speed,
|
|
len : (Math.random() * 0.3) + 0.15,
|
|
life : life,
|
|
max_life : life,
|
|
color : Math.random() > 0.3 ? "#fef08a" : "#f97316" // Chispas blanco-amarillo y naranja
|
|
});
|
|
}
|
|
};
|
|
|
|
const get_cell_size = (width, height) => {
|
|
const cells = xgravity.settings.get("cells", null, 40);
|
|
return (width < height ? width : height) / cells;
|
|
};
|
|
|
|
this.add = (cell_x, cell_y, type = "shield") => {
|
|
// 1. Delegar el audio al servicio central
|
|
if(xgravity.audio){
|
|
xgravity.audio.play(type === "shield" ? "shield_explosion" : "hit_explosion");
|
|
}
|
|
|
|
const is_shield = type === "shield";
|
|
|
|
// 2. Extraer parámetros visuales de Settings
|
|
const particles_range = xgravity.settings.get("explosion_particles_range", null, [45, 75]);
|
|
const spread_reach = xgravity.settings.get("explosion_particles_spread", null, 11.0);
|
|
const base_shockwave_radius = xgravity.settings.get("explosion_shockwave_radius", null, 4.5);
|
|
|
|
const min_p = Math.min(particles_range[0], particles_range[1]);
|
|
const max_p = Math.max(particles_range[0], particles_range[1]);
|
|
const count = Math.floor(min_p + Math.random() * (max_p - min_p + 1));
|
|
|
|
const max_radius = is_shield ? base_shockwave_radius : (base_shockwave_radius * 1.15);
|
|
|
|
// 3. Onda expansiva elíptica con inclinación aleatoria
|
|
shockwaves.push({
|
|
x : cell_x,
|
|
y : cell_y,
|
|
radius : 0.4,
|
|
max_radius : max_radius,
|
|
speed : is_shield ? 9.5 : 8.0,
|
|
angle : Math.random() * Math.PI * 2,
|
|
aspect_ratio : 0.42 + (Math.random() * 0.18),
|
|
alpha : 1.0,
|
|
color_rgb : is_shield ? "56, 189, 248" : "239, 68, 68"
|
|
});
|
|
|
|
// 4. Micropartículas esféricas difuminadas
|
|
for(let i = 0; i < count; i++){
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const speed = (Math.random() * spread_reach) + 1.2;
|
|
const life = (Math.random() * 0.38) + 0.28;
|
|
|
|
let core_rgb = "255, 255, 255";
|
|
let rim_rgb = "56, 189, 248";
|
|
|
|
if(is_shield){
|
|
rim_rgb = Math.random() > 0.4 ? "56, 189, 248" : (Math.random() > 0.5 ? "186, 230, 253" : "2, 132, 199");
|
|
} else {
|
|
rim_rgb = Math.random() > 0.4 ? "239, 68, 68" : (Math.random() > 0.5 ? "249, 115, 22" : "251, 191, 36");
|
|
}
|
|
|
|
particles.push({
|
|
x : cell_x,
|
|
y : cell_y,
|
|
vx : Math.cos(angle) * speed,
|
|
vy : Math.sin(angle) * speed,
|
|
size : (Math.random() * 0.16) + 0.08,
|
|
life : life,
|
|
max_life : life,
|
|
core_rgb : core_rgb,
|
|
rim_rgb : rim_rgb
|
|
});
|
|
}
|
|
};
|
|
|
|
this.add_mothership_impact = (cx, cy) => {
|
|
// Flash de impacto en la cabina
|
|
particles.push({
|
|
type : "screen_flash",
|
|
alpha : 0.85,
|
|
color : "#ffffff",
|
|
fade_speed : 2.5
|
|
});
|
|
|
|
// 60-80 Fragmentos de roca y metralla de diversos tamaños
|
|
const count = 75;
|
|
for(let i = 0; i < count; i++){
|
|
const angle = Math.random() * Math.PI * 2;
|
|
const speed = 120 + (Math.random() * 650); // Píxeles/segundo
|
|
const size = 3 + (Math.random() * 18); // Desde gravilla hasta peñascos
|
|
const is_rock = Math.random() > 0.3;
|
|
|
|
particles.push({
|
|
type : is_rock ? "rock_shard" : "plasma_spark",
|
|
x : cx + ((Math.random() - 0.5) * 40),
|
|
y : cy + ((Math.random() - 0.5) * 40),
|
|
vx : Math.cos(angle) * speed,
|
|
vy : Math.sin(angle) * speed,
|
|
angle : Math.random() * Math.PI * 2,
|
|
angular_vel : (Math.random() - 0.5) * 12.0,
|
|
size : size,
|
|
life : 1.0,
|
|
decay : 0.45 + (Math.random() * 0.75),
|
|
color : is_rock ? (Math.random() > 0.5 ? "#78716c" : "#a8a29e") : (Math.random() > 0.5 ? "#f97316" : "#facc15"),
|
|
vertices : is_rock ? [
|
|
{ x: -size * 0.5, y: -size * 0.4 },
|
|
{ x: size * 0.6, y: -size * 0.3 },
|
|
{ x: size * 0.4, y: size * 0.5 },
|
|
{ x: -size * 0.3, y: size * 0.6 }
|
|
] : null
|
|
});
|
|
}
|
|
};
|
|
|
|
this.update = (delta_time) => {
|
|
for(let i = particles.length - 1; i >= 0; i--){
|
|
const p = particles[i];
|
|
p.life -= delta_time;
|
|
if(p.life <= 0){
|
|
particles.splice(i, 1);
|
|
continue;
|
|
}
|
|
p.x += p.vx * delta_time;
|
|
p.y += p.vy * delta_time;
|
|
p.vx *= 0.93;
|
|
p.vy *= 0.93;
|
|
}
|
|
|
|
for(let i = shockwaves.length - 1; i >= 0; i--){
|
|
const s = shockwaves[i];
|
|
s.radius += s.speed * delta_time;
|
|
s.alpha = Math.max(0, 1.0 - (s.radius / s.max_radius));
|
|
if(s.radius >= s.max_radius){
|
|
shockwaves.splice(i, 1);
|
|
}
|
|
}
|
|
|
|
for(let i = sparks.length - 1; i >= 0; i--){
|
|
const s = sparks[i];
|
|
s.life -= delta_time;
|
|
if(s.life <= 0){
|
|
sparks.splice(i, 1);
|
|
continue;
|
|
}
|
|
s.x += s.vx * delta_time;
|
|
s.y += s.vy * delta_time;
|
|
s.vx *= 0.90;
|
|
s.vy *= 0.90;
|
|
}
|
|
};
|
|
|
|
this.render = (ctx, width, height) => {
|
|
const cell_px = get_cell_size(width, height);
|
|
const cx = width / 2;
|
|
const cy = height / 2;
|
|
|
|
ctx.save();
|
|
ctx.globalCompositeOperation = "lighter";
|
|
|
|
// Ondas de choque elípticas
|
|
for(let i = 0; i < shockwaves.length; i++){
|
|
const s = shockwaves[i];
|
|
const px = cx + (s.x * cell_px);
|
|
const py = cy + (s.y * cell_px);
|
|
const r_px = s.radius * cell_px;
|
|
|
|
ctx.save();
|
|
ctx.translate(px, py);
|
|
ctx.rotate(s.angle);
|
|
ctx.scale(1.0, s.aspect_ratio);
|
|
|
|
ctx.lineWidth = cell_px * 0.14 * s.alpha;
|
|
ctx.strokeStyle = `rgba(${s.color_rgb}, ${s.alpha * 0.85})`;
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, r_px, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
ctx.lineWidth = cell_px * 0.05 * s.alpha;
|
|
ctx.strokeStyle = `rgba(255, 255, 255, ${s.alpha * 0.6})`;
|
|
ctx.beginPath();
|
|
ctx.arc(0, 0, r_px * 0.82, 0, Math.PI * 2);
|
|
ctx.stroke();
|
|
|
|
ctx.restore();
|
|
}
|
|
|
|
// Micropartículas
|
|
for(let i = 0; i < particles.length; i++){
|
|
const p = particles[i];
|
|
const px = cx + (p.x * cell_px);
|
|
const py = cy + (p.y * cell_px);
|
|
const r_px = p.size * cell_px;
|
|
const progress = Math.max(0, p.life / p.max_life);
|
|
|
|
const grad = ctx.createRadialGradient(px, py, 0, px, py, r_px);
|
|
grad.addColorStop(0.0, `rgba(${p.core_rgb}, ${progress})`);
|
|
grad.addColorStop(0.45, `rgba(${p.rim_rgb}, ${progress * 0.75})`);
|
|
grad.addColorStop(1.0, `rgba(${p.rim_rgb}, 0.0)`);
|
|
|
|
ctx.fillStyle = grad;
|
|
ctx.beginPath();
|
|
ctx.arc(px, py, r_px, 0, Math.PI * 2);
|
|
ctx.fill();
|
|
}
|
|
|
|
// Renderizado de Chispas Cinéticas (sin delta_time)
|
|
for(let i = 0; i < sparks.length; i++){
|
|
const s = sparks[i];
|
|
const px = cx + (s.x * cell_px);
|
|
const py = cy + (s.y * cell_px);
|
|
const progress = Math.max(0, s.life / s.max_life);
|
|
|
|
// Calculamos la longitud y dirección de la estela en base a su velocidad
|
|
const spd = Math.sqrt(s.vx * s.vx + s.vy * s.vy);
|
|
const dir_x = spd > 0.001 ? s.vx / spd : 0;
|
|
const dir_y = spd > 0.001 ? s.vy / spd : 0;
|
|
|
|
// Longitud visible de la estela en píxeles (usa s.len escalado por la velocidad y vida)
|
|
const tail_length_px = (s.len || 0.25) * cell_px * Math.min(2.5, spd * 0.15);
|
|
|
|
ctx.lineWidth = cell_px * 0.06;
|
|
ctx.strokeStyle = s.color;
|
|
ctx.globalAlpha = progress;
|
|
|
|
ctx.beginPath();
|
|
ctx.moveTo(px, py);
|
|
// La estela se proyecta hacia atrás respecto a la trayectoria
|
|
ctx.lineTo(px - (dir_x * tail_length_px), py - (dir_y * tail_length_px));
|
|
ctx.stroke();
|
|
}
|
|
|
|
ctx.restore();
|
|
};
|
|
|
|
this.reset = () => {
|
|
particles = [];
|
|
shockwaves = [];
|
|
};
|
|
|
|
};
|
|
|
|
return ExplosionsModel;
|
|
})(); |