hoel/js/entities.js
2026-01-22 19:21:59 +01:00

470 lines
16 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Entity classes for game objects
function Star(canvas) {
this.canvas = canvas
this.reset();
this.baseBrightness = 0.6 + Math.random() * 0.4;
this.brightness = this.baseBrightness;
this.twinkleSpeed = Math.random() * 0.003 + 0.001;
this.twinkleAmount = 0.1 + Math.random() * 0.1;
var colorRand = Math.random();
for (var i = 0; i < CONFIG.STAR_COLORS.length; i++) {
if (colorRand < CONFIG.STAR_COLORS[i].threshold) {
this.color = CONFIG.STAR_COLORS[i].rgb;
break;
}
}
}
Star.prototype.reset = function() {
this.x = Math.random() * this.canvas.width;
this.y = Math.random() * this.canvas.height;
this.size = Math.random() * 1;
};
Star.prototype.update = function() {
this.brightness += this.twinkleSpeed;
if (this.brightness > this.baseBrightness + this.twinkleAmount ||
this.brightness < this.baseBrightness - this.twinkleAmount) {
this.twinkleSpeed = -this.twinkleSpeed;
}
};
Star.prototype.draw = function(ctx) {
ctx.fillStyle = 'rgba(' + this.color + ', ' + this.brightness + ')';
ctx.beginPath();
ctx.arc(this.x, this.y, this.size, 0, Math.PI * 2);
ctx.fill();
};
function Asteroid(type, blackHole, canvas) {
this.canvas = canvas
if (!type) type = 'small';
this.type = type;
var angle = Math.random() * Math.PI * 2;
var distance = Math.max(canvas.width, canvas.height) * 0.7;
this.x = this.canvas.width / 2 + Math.cos(angle) * distance;
this.y = this.canvas.height / 2 + Math.sin(angle) * distance;
this.angle = Math.atan2(this.y - blackHole.y, this.x - blackHole.x);
this.distance = Math.sqrt(
Math.pow(this.x - blackHole.x, 2) +
Math.pow(this.y - blackHole.y, 2)
);
this.orbitSpeed = 0.005 + Math.random() * 0.003;
this.decayRate = 0.08 + Math.random() * 0.05;
this.blackHole = blackHole;
this.initializeTypeSpecificProperties();
this.rotation = Math.random() * Math.PI * 2;
this.rotationSpeed = (Math.random() - 0.5) * 0.1;
}
Asteroid.prototype.initializeTypeSpecificProperties = function() {
// Visual size (unchanged)
if (this.type === 'comet') {
this.size = 5 + Math.random() * 3;
} else if (this.type === 'giant') {
this.size = 15 + Math.random() * 10;
this.orbitSpeed *= 0.3;
this.decayRate *= 0.4;
this.planetColors = CONFIG.GIANT_COLORS[Math.floor(Math.random() * CONFIG.GIANT_COLORS.length)];
// Initialize rings
this.rings = [];
var ringCount = Math.floor(Math.random() * 4); // 0-3 rings
for (var i = 0; i < ringCount; i++) {
this.rings.push({
color: CONFIG.GIANT_RING_COLORS[Math.floor(Math.random() * CONFIG.GIANT_RING_COLORS.length)],
thickness: 1 + Math.random() * 9, // random thickness
radiusOffset: 10 + i * 5 + Math.random() * 5, // spread rings outward
alpha: 0.3 + Math.random() * 0.4 // stored permanently
});
}
} else if (this.type === 'planet') {
this.size = 10 + Math.random() * 5;
this.orbitSpeed *= 0.5;
this.decayRate *= 0.6;
this.planetColors = CONFIG.PLANET_COLORS[Math.floor(Math.random() * CONFIG.PLANET_COLORS.length)];
} else if (this.type === 'large') {
this.size = 5 + Math.random() * 2;
} else if (this.type === 'medium') {
this.size = 3 + Math.random() * 2;
} else {
this.size = 1 + Math.random() * 2;
}
// Physical mass (kg)
var range = CONFIG.ASTEROID_MASS_RANGES[this.type] || CONFIG.ASTEROID_MASS_RANGES.small;
this.massKg = range[0] + Math.random() * (range[1] - range[0]);
};
Asteroid.prototype.update = function() {
this.angle += this.orbitSpeed;
this.distance -= this.decayRate;
this.x = this.blackHole.x + Math.cos(this.angle) * this.distance;
this.y = this.blackHole.y + Math.sin(this.angle) * this.distance;
this.rotation += this.rotationSpeed;
};
Asteroid.prototype.draw = function(ctx) {
if (this.type === 'comet') {
this.drawComet(ctx);
} else if (this.type === 'giant') {
// Initialize per-giant tilt if not already done
if (this._ringTiltBase === undefined) {
this._ringTiltBase = (Math.random() - 0.5) * 0.3; // ±0.15 rad
this._tiltOscillationSpeed = 0.001 + Math.random() * 0.002;
this._tiltScale = 0.1 + Math.random() * 0.69; // vertical squash
}
const tilt = this._ringTiltBase + Math.sin(Date.now() * this._tiltOscillationSpeed) * 0.05;
if (this.rings && this.rings.length > 0) {
// Draw back halves of rings
this.rings.forEach((ring) => {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(tilt);
ctx.scale(1, this._tiltScale);
ctx.strokeStyle = ring.color.replace(/;?\s*$/, '') // just in case
ctx.strokeStyle = ring.color.startsWith('rgba')
? ring.color.replace(/rgba\(([^)]+),[^)]+\)/, `rgba($1,${ring.alpha})`)
: `rgba(${parseInt(ring.color.slice(1,3),16)},${parseInt(ring.color.slice(3,5),16)},${parseInt(ring.color.slice(5,7),16)},${ring.alpha})`;
ctx.lineWidth = ring.thickness;
ctx.beginPath();
ctx.arc(0, 0, this.size + ring.radiusOffset, Math.PI, 2 * Math.PI); // back half
ctx.stroke();
ctx.restore();
});
}
// Draw the giant planet
this.drawGiant(ctx);
if (this.rings && this.rings.length > 0) {
// Draw front halves of rings
this.rings.forEach((ring) => {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(tilt);
ctx.scale(1, this._tiltScale);
ctx.strokeStyle = ring.color.startsWith('rgba')
? ring.color.replace(/rgba\(([^)]+),[^)]+\)/, `rgba($1,${rings.alpha})`)
: `rgba(${parseInt(ring.color.slice(1,3),16)},${parseInt(ring.color.slice(3,5),16)},${parseInt(ring.color.slice(5,7),16)},${ring.alpha})`;
ctx.lineWidth = ring.thickness;
ctx.beginPath();
ctx.arc(0, 0, this.size + ring.radiusOffset, 0, Math.PI); // front half
ctx.stroke();
ctx.restore();
});
}
} else if (this.type === 'planet') {
this.drawPlanet(ctx);
} else {
this.drawAsteroid(ctx);
}
};
//Asteroid.prototype.draw = function(ctx) {
// if (this.type === 'comet') {
// this.drawComet(ctx);
// } else if (this.type === 'giant') {
// // Draw the giant at its absolute position
// this.drawGiant(ctx);
//
// // Draw rings if present
// if (this.rings && this.rings.length > 0) {
// this.rings.forEach((ring, index) => {
// ctx.save();
//
// // Move origin to giant's center
// ctx.translate(this.x, this.y);
//
// // Independent tilt + oscillation per ring
// if (ring._tiltBase === undefined) {
// ring._tiltBase = (Math.random() - 0.5) * 0.4; // ±0.2 rad
// ring._oscillationSpeed = 0.001 + Math.random() * 0.002;
// }
// const tilt = ring._tiltBase + Math.sin(Date.now() * ring._oscillationSpeed) * 0.05;
//
// ctx.rotate(tilt);
//
// ctx.strokeStyle = ring.color;
// ctx.lineWidth = ring.thickness;
// ctx.beginPath();
// ctx.arc(0, 0, this.size + ring.radiusOffset, 0, Math.PI * 2);
// ctx.stroke();
//
// ctx.restore();
// });
// }
// } else if (this.type === 'planet') {
// this.drawPlanet(ctx);
// } else {
// this.drawAsteroid(ctx);
// }
//};
//Asteroid.prototype.draw = function(ctx) {
// if (this.type === 'comet') {
// this.drawComet(ctx);
// } else if (this.type === 'giant') {
// this.drawGiant(ctx);
// if (this.rings && this.rings.length > 0) {
// this.rings.forEach(function(ring) {
// ctx.strokeStyle = ring.color;
// ctx.lineWidth = ring.thickness;
// ctx.beginPath();
// ctx.arc(0, 0, this.size + ring.radiusOffset, 0, Math.PI * 2);
// ctx.stroke();
// }, this);
// }
// } else if (this.type === 'planet') {
// this.drawPlanet(ctx);
// } else {
// this.drawAsteroid(ctx);
// }
//};
Asteroid.prototype.drawComet = function(ctx) {
var orbitTangentAngle = this.angle + Math.PI / 2;
var tailLength = this.size * 5;
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(orbitTangentAngle + Math.PI);
var gradient = ctx.createLinearGradient(0, 0, tailLength, 0);
gradient.addColorStop(0, 'rgba(80, 140, 200, 0.5)');
gradient.addColorStop(0.5, 'rgba(60, 100, 150, 0.25)');
gradient.addColorStop(1, 'rgba(40, 80, 120, 0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, -this.size * 0.4, tailLength, this.size * 0.8);
ctx.restore();
var nucleusSize = this.size * 0.5;
ctx.save();
ctx.translate(this.x, this.y);
ctx.fillStyle = '#5a7a8a';
ctx.strokeStyle = 'rgba(255, 255, 255, 0.3)';
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.arc(0, 0, nucleusSize, 0, Math.PI * 2);
ctx.fill();
ctx.stroke();
ctx.fillStyle = 'rgba(80, 120, 140, 0.2)';
ctx.beginPath();
ctx.arc(0, 0, nucleusSize * 1.3, 0, Math.PI * 2);
ctx.fill();
ctx.restore();
};
Asteroid.prototype.drawGiant = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
var gradient = ctx.createRadialGradient(-this.size * 0.25, -this.size * 0.25, 0, 0, 0, this.size);
gradient.addColorStop(0, this.planetColors.light);
gradient.addColorStop(0.5, this.planetColors.mid);
gradient.addColorStop(1, this.planetColors.dark);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'rgba(0, 0, 0, 0.1)';
ctx.lineWidth = 1;
for (var band = -this.size * 0.6; band < this.size * 0.6; band += this.size * 0.15) {
ctx.beginPath();
var bandWidth = Math.sqrt(this.size * this.size - band * band);
ctx.moveTo(-bandWidth, band);
ctx.lineTo(bandWidth, band);
ctx.stroke();
}
ctx.strokeStyle = 'rgba(255, 255, 255, 0.15)';
ctx.lineWidth = 0.5;
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.stroke();
ctx.restore();
};
Asteroid.prototype.drawPlanet = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
var gradient = ctx.createRadialGradient(-this.size * 0.2, -this.size * 0.2, 0, 0, 0, this.size);
gradient.addColorStop(0, this.planetColors.light);
gradient.addColorStop(0.5, this.planetColors.mid);
gradient.addColorStop(1, this.planetColors.dark);
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(0, 0, this.size, 0, Math.PI * 2);
ctx.fill();
ctx.strokeStyle = 'rgba(255, 255, 255, 0.1)';
ctx.lineWidth = 0.5;
ctx.stroke();
ctx.restore();
};
Asteroid.prototype.drawAsteroid = function(ctx) {
ctx.save();
ctx.translate(this.x, this.y);
ctx.rotate(this.rotation);
var asteroidColor = CONFIG.ASTEROID_COLORS[this.type] || CONFIG.ASTEROID_COLORS.small;
ctx.fillStyle = asteroidColor;
ctx.beginPath();
ctx.moveTo(0, -this.size);
ctx.lineTo(this.size * 0.8, this.size * 0.6);
ctx.lineTo(-this.size * 0.8, this.size * 0.6);
ctx.closePath();
ctx.fill();
ctx.restore();
};
Asteroid.prototype.isDestroyed = function() {
return this.distance < this.blackHole.radius;
};
function BlackHole(x, y, radius) {
this.x = x;
this.y = y;
this.radius = radius;
this.pulse = 0;
this.pulseColor = '#000000';
}
BlackHole.prototype.draw = function(ctx) {
if (this.pulse > 0) {
this.pulse -= 0.02;
if (this.pulse < 0) this.pulse = 0;
}
if (this.pulse === 0) {
this.pulseColor = '#000000';
}
ctx.fillStyle = '#000000';
ctx.beginPath();
ctx.arc(this.x, this.y, this.radius, 0, Math.PI * 2);
ctx.fill();
var visualPulse = Math.min(this.pulse, 1);
var glowIntensity = 0.2 + (visualPulse * 0.3);
var glowRadius = this.radius * (1.01 + visualPulse * 0.1);
var gradient = ctx.createRadialGradient(
this.x, this.y, this.radius * 0.97,
this.x, this.y, glowRadius
);
var r, g, b;
if (this.pulseColor.startsWith('#')) {
var hex = this.pulseColor;
r = parseInt(hex.substr(1, 2), 16);
g = parseInt(hex.substr(3, 2), 16);
b = parseInt(hex.substr(5, 2), 16);
} else {
r = 60; g = 60; b = 80;
}
// Increase saturation by 3x when pulsing
var saturated = saturateColor(r, g, b, 3);
r = saturated.r;
g = saturated.g;
b = saturated.b;
gradient.addColorStop(0, 'rgba(' + r + ', ' + g + ', ' + b + ', 0)');
gradient.addColorStop(1, 'rgba(' + r + ', ' + g + ', ' + b + ', ' + glowIntensity + ')');
ctx.fillStyle = gradient;
ctx.beginPath();
ctx.arc(this.x, this.y, glowRadius, 0, Math.PI * 2);
ctx.fill();
};
BlackHole.prototype.consumeAsteroid = function(asteroid) {
// Calculate pulse contribution (same as before)
var normalized = (Math.log10(asteroid.massKg) - 3) / 6;
var pulseContribution = Math.max(0.1, normalized);
this.pulse = Math.min(this.pulse + pulseContribution, 9);
// Determine new pulse color based on asteroid type/size
var newColor;
if (asteroid.planetColors) {
newColor = asteroid.planetColors.mid;
} else if (asteroid.type === 'comet') {
newColor = '#5a7a8a';
} else if (asteroid.size === 'large') {
newColor = '#f8f8f8';
} else if (asteroid.size === 'medium') {
newColor = '#888888';
} else {
newColor = '#444444';
}
// Blend with existing pulseColor instead of replacing
if (!this.pulseColor) this.pulseColor = newColor;
else this.pulseColor = this.blendColors(this.pulseColor, newColor, 0.3);
};
// Helper function to blend two hex colors by weight (01)
BlackHole.prototype.blendColors = function(c1, c2, weight) {
var d2h = d => d.toString(16).padStart(2, '0');
var h2d = h => parseInt(h, 16);
// Remove #
c1 = c1.replace('#','');
c2 = c2.replace('#','');
var r = Math.round(h2d(c1.substring(0,2)) * (1-weight) + h2d(c2.substring(0,2)) * weight);
var g = Math.round(h2d(c1.substring(2,4)) * (1-weight) + h2d(c2.substring(2,4)) * weight);
var b = Math.round(h2d(c1.substring(4,6)) * (1-weight) + h2d(c2.substring(4,6)) * weight);
return '#' + d2h(r) + d2h(g) + d2h(b);
};
//BlackHole.prototype.consumeAsteroid = function(asteroid) {
// // Calculate pulse contribution based on mass
// // Using log scale: small asteroids (1e3 kg) give ~0.1, giants (5e14 kg) give ~2.0
// var normalized = (Math.log10(asteroid.massKg) - 3) / 6; // Range: 0 to ~2
// var pulseContribution = Math.max(0.1, normalized); // Minimum 0.05 for visibility
//
// // Add to existing pulse (cumulative effect)
// this.pulse = Math.min(this.pulse + pulseContribution, 9); // Cap at 9 for very large accumulations
//
// // Update color - blend with existing color if already glowing
// if (asteroid.planetColors) {
// this.pulseColor = asteroid.planetColors.mid;
// } else if (asteroid.type === 'comet') {
// this.pulseColor = '#5a7a8a';
// } else if (asteroid.size === 'large') {
// this.pulseColor = '#ffffff';
// } else if (asteroid.size === 'medium') {
// this.pulseColor = '#888888';
// } else {
// this.pulseColor = '#444444';
// }
//};