// endboss.class.js
/**
* @class Endboss
*
* Represents the final boss enemy in the game.
* Inherits movement and physics from MovableObject.
* Uses frame-based animations synchronized with display refresh rate.
*/
class Endboss extends MovableObject {
/**
* Hitbox offset values to adjust collision detection.
* @type {{ top: number, bottom: number, left: number, right: number }}
*/
offset = {
top: 108,
bottom: 68,
left: 52,
right: 52,
};
/**
* Frame delays for each animation state of the boss.
* Includes: dead, hurt, alert, prepare, attack, flying, landing, walking, intro1.
* @type {{ dead: number, hurt: number, alert: number, prepare: number, attack: number, flying: number, landing: number, walking: number, intro1: number }}
*/
frameDelay = {
dead: 14,
hurt: 6,
alert: 18,
prepare: 12,
attack: 16,
flying: 10,
landing: 10,
walking: 8,
intro1: 10,
intro2: 12,
};
/**
* Reference to the world object containing game entities.
* @type {World}
*/
world;
/**
* Vertical position of the boss.
* @type {number}
*/
y = 145;
/**
* Width of the boss sprite.
* @type {number}
*/
width = 300;
/**
* Height of the boss sprite.
* @type {number}
*/
height = 300;
/**
* Left boundary of the boss's allowed horizontal movement range.
* @type {number}
*/
minX = 2690;
/**
* Right boundary of the boss's allowed horizontal movement range.
* This value is set dynamically based on the level's end position.
* @type {number}
*/
maxX = 4036;
/**
* Movement speed of the boss.
* @type {number}
*/
speed = 7 / 2;
/**
* Timestamp when the boss entered the "prepare" state (ms since epoch).
* @type {number}
* */
prepareStart = 0;
/**
* Timestamp when the boss started attacking (ms since epoch).
* @type {number}
*/
attackStart = 0;
/**
* Timestamp when the boss started recovering (ms since epoch).
* @type {number}
*/
recoverStart = 0;
/**
* Timestamp when boss starts spawning minion chickens (ms since epoch).
* @type {number}
*/
spawningStart = 0;
/**
* Timestamp when boss starts retreating (ms since epoch).
* @type {number}
*/
retreatStart = 0;
/**
* Timestamp when the boss intro sequence started (ms since epoch).
* @type {number}
*/
introStart = 0;
/**
* Whether the boss is currently in the attack sequence (locked until it ends).
* @type {boolean}
*/
isAttacking = false;
/**
* Whether the boss is currently in the recover sequence (locked until it ends).
* @type {boolean}
*/
isRecovering = false;
/**
* Whether free walking between minX/maxX is allowed (outside attack/recover locks).
* @type {boolean}
*/
isAllowedToWalk = true;
/**
* One-shot flag to ensure jump during attack happens only once.
* @type {boolean}
*/
hasJumpedThisAttack = false;
/**
* Cooldown flag: prevents immediately starting a new attack after the last one.
* @type {boolean}
*/
hasRecentlyAttacked = false;
/**
* Cooldown flag: prevents immediately spawning new minions after the last spawn.
* @type {boolean}
*/
hasRecentlySpawned = false;
/**
* Cooldown flag: prevents immediately retreating after the last retreat.
* @type {boolean}
*/
hasRecentlyRetreated = false;
/**
* Current on/off state for the vulnerable blink overlay.
* @type {boolean}
*/
blinkOn = false;
/**
* Interval between blink toggles in milliseconds.
* @type {number}
*/
blinkIntervalMs = 150;
/**
* Timestamp of the last blink toggle (ms since epoch).
* @type {number}
*/
lastBlinkToggleAt = 0;
/**
* Vertical acceleration applied to the character (simulates gravity).
*
* @type {number}
*/
acceleration = 1.2;
/**
* Whether the boss can currently take damage.
* Used to implement invulnerability phases.
* @type {boolean}
*/
canTakeDamage = true;
/**
* Current health of the boss.
* @type {number}
*/
health = 250;
/**
* Amount of damage the boss deals.
* @type {number}
*/
damage = 50;
/**
* Image paths for the walking animation.
* @type {string[]}
*/
IMAGES_WALKING = [
"assets/img/4_enemie_boss_chicken/1_walk/G1.png",
"assets/img/4_enemie_boss_chicken/1_walk/G2.png",
"assets/img/4_enemie_boss_chicken/1_walk/G3.png",
"assets/img/4_enemie_boss_chicken/1_walk/G4.png",
];
/**
* Image paths for the alert animation sequence.
* @type {string[]}
*/
IMAGES_ALERT = [
"assets/img/4_enemie_boss_chicken/2_alert/G5.png",
"assets/img/4_enemie_boss_chicken/2_alert/G6.png",
"assets/img/4_enemie_boss_chicken/2_alert/G7.png",
"assets/img/4_enemie_boss_chicken/2_alert/G8.png",
"assets/img/4_enemie_boss_chicken/2_alert/G9.png",
"assets/img/4_enemie_boss_chicken/2_alert/G10.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G12.png",
];
/**
* Image paths for the alert animation intro sequence.
* @type {string[]}
*/
IMAGES_INTRO2 = [
"assets/img/4_enemie_boss_chicken/2_alert/G9.png",
"assets/img/4_enemie_boss_chicken/2_alert/G10.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G11.png",
"assets/img/4_enemie_boss_chicken/2_alert/G12.png",
];
/**
* Image paths for the prepare animation sequence.
* Shown before the boss initiates an attack.
* @type {string[]}
*/
IMAGES_PREPARE = [
"assets/img/4_enemie_boss_chicken/3_attack/G13.png",
"assets/img/4_enemie_boss_chicken/3_attack/G14.png",
"assets/img/4_enemie_boss_chicken/3_attack/G15.png",
"assets/img/4_enemie_boss_chicken/3_attack/G16.png",
];
/**
* Image paths for the attack animation sequence.
* Includes repeated frames for visual emphasis.
* @type {string[]}
*/
IMAGES_ATTACK = [
"assets/img/4_enemie_boss_chicken/3_attack/G17.png",
"assets/img/4_enemie_boss_chicken/3_attack/G18.png",
"assets/img/4_enemie_boss_chicken/3_attack/G18.png",
"assets/img/4_enemie_boss_chicken/3_attack/G18.png",
"assets/img/4_enemie_boss_chicken/3_attack/G18.png",
"assets/img/4_enemie_boss_chicken/3_attack/G17.png",
];
/**
* Image paths for the flying animation sequence during recovery.
* @type {string[]}
*/
IMAGES_FLYING = [
"assets/img/4_enemie_boss_chicken/3_attack/G17.png",
"assets/img/4_enemie_boss_chicken/3_attack/G18.png",
];
/**
* Image paths for the landing animation sequence after flight or attack.
* @type {string[]}
*/
IMAGES_LANDING = [
"assets/img/4_enemie_boss_chicken/3_attack/G19.png",
"assets/img/4_enemie_boss_chicken/3_attack/G20.png",
];
/**
* Image paths for the hurt animation sequence.
* @type {string[]}
*/
IMAGES_HURT = [
"assets/img/4_enemie_boss_chicken/4_hurt/G21.png",
"assets/img/4_enemie_boss_chicken/4_hurt/G22.png",
"assets/img/4_enemie_boss_chicken/4_hurt/G23.png",
];
/**
* Image paths for the death animation sequence.
* Includes frames shared with the hurt animation and dedicated death frames.
* @type {string[]}
*/
IMAGES_DEAD = [
"assets/img/4_enemie_boss_chicken/4_hurt/G21.png",
"assets/img/4_enemie_boss_chicken/4_hurt/G22.png",
"assets/img/4_enemie_boss_chicken/4_hurt/G23.png",
"assets/img/4_enemie_boss_chicken/5_dead/G24.png",
"assets/img/4_enemie_boss_chicken/5_dead/G25.png",
"assets/img/4_enemie_boss_chicken/5_dead/G26.png",
];
/**
* Initializes the Endboss by:
* - Loading all animation sequences into the image cache.
* - Setting the default displayed image to the first alert frame.
* - Applying gravity.
* - Setting the starting horizontal position to x = 3800.
*/
constructor() {
super().loadImage(this.IMAGES_ALERT[0]);
this.loadImages(this.IMAGES_WALKING);
this.loadImages(this.IMAGES_ALERT);
this.loadImages(this.IMAGES_INTRO2);
this.loadImages(this.IMAGES_PREPARE);
this.loadImages(this.IMAGES_ATTACK);
this.loadImages(this.IMAGES_FLYING);
this.loadImages(this.IMAGES_LANDING);
this.loadImages(this.IMAGES_HURT);
this.loadImages(this.IMAGES_DEAD);
/**
* Horizontal starting position of the boss in the level.
* @type {number}
*/
this.x = 3800;
}
/**
* Initializes the boss's physics and animation systems.
* Sets up gravity effects synchronized with display refresh rate.
*/
initEndbossLoops() {
this.applyGravity();
}
/**
* Assigns the current game world to the boss.
* Also recalculates the maximum allowed horizontal position (`maxX`)
* based on the level's end boundary.
*
* @param {World} world - Reference to the active game world instance.
*/
setWorld(world) {
this.world = world;
this.setMaxX();
}
/**
* Draws the boss on the provided 2D canvas context.
* Updates visual effects based on state (hurt/vulnerable) and timing.
* Animation updates are synchronized with frame timing.
*
* @param {CanvasRenderingContext2D} ctx - The 2D rendering context.
*/
draw(ctx) {
const now = Date.now();
const endscreenOn = !!(this.world && this.world.endscreenTriggered);
if (this.isHurt(2)) {
this.drawHurtIndicator(ctx);
return;
}
if (!this.canTakeDamage && !endscreenOn && this.world.introPlayed) {
this.drawVulnerableIndicator(ctx, now);
return;
}
this.blinkOn = false;
this.lastBlinkToggleAt = 0;
super.draw(ctx);
}
/**
* Renders the boss with a hue-rotated filter to indicate the hurt state.
*
* @param {CanvasRenderingContext2D} ctx - The 2D rendering context.
*/
drawHurtIndicator(ctx) {
ctx.save();
ctx.filter = "hue-rotate(-40deg)";
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
ctx.restore();
}
/**
* Renders the boss with a timed grayscale/saturation blink effect
* to indicate the vulnerable state.
*
* @param {CanvasRenderingContext2D} ctx - The 2D rendering context.
* @param {number} now - Current timestamp in milliseconds.
*/
drawVulnerableIndicator(ctx, now) {
if (this.lastBlinkToggleAt === 0) {
this.lastBlinkToggleAt = now;
}
if (now - this.lastBlinkToggleAt >= this.blinkIntervalMs) {
this.blinkOn = !this.blinkOn;
this.lastBlinkToggleAt = now;
}
ctx.save();
if (this.blinkOn) {
ctx.filter = "invert(100%)";
}
ctx.drawImage(this.img, this.x, this.y, this.width, this.height);
ctx.restore();
}
/**
* Updates `maxX` to match the level's end boundary minus a fixed offset
* to account for boss sprite width and positioning.
*/
setMaxX() {
this.maxX = this.world.level.level_end_x - 214;
}
}