Source: models/endboss.class.js

// 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;
  }
}