Source: Player.js

/**
 * @license GPL-3.0-only
 *
 * @author Mark Mayes / mm-dev
 *
 *
 * @module Player
 *
 * @description
 * ## Manage and draw the player
 */

import { ASPECT_RATIO } from "./PD/ENUM.js";
import * as GAME from "./PD/GAME.js";

import { Layout } from "./Layout.js";

import { Controller } from "./Controller.js";
import { Display } from "./Display.js";
import { Game } from "./Game.js";
import { Shape } from "./Shape.js";

import { hexOpacityToRGBA, vectorToDegrees } from "./utils.js";

class Player {
  static get drawnRadius() {
    return Player.radius * Layout.proportionalMultiplier;
  }

  static get speed() {
    return Controller.speedOffset;
  }

  static get velocityVector() {
    return Controller.scalarVectorOfTravel;
  }

  static get unitVector() {
    return Controller.normalisedVectorOfTravel;
  }
}

/**
 * @function init
 * @static
 *
 * @description
 * ##### Set a neutral start position
 */
Player.init = function () {
  Player.pos = { x: 0, y: 0 };
};

/**
 * @function setupForLevel
 * @static
 *
 * @description
 * ##### Set colour, health, size etc according to the current level configuration
 */
Player.setupForLevel = function () {
  Player.health = Game.curLevelData.startHealth;
  Player.radius = Game.curLevelData.player.radius;
  Player.originLongitudinal =
    GAME.PLAYER_ORIGIN_LONGITUDINAL * Layout.proportionalMultiplier;
  Player.growthDivisor = Game.curLevelData.player.growthDivisor;
  Player.color = Game.curLevelData.player.color;
  Player.addedLayer_ar = Game.curLevelData.player.addedLayer_ar;
  Player.damagedColor = Game.curLevelData.bgColor;
  Player.damagedFramesCounter = 0;
};

/**
 * @function updateSizes
 * @static
 *
 * @description
 * ##### Update some variables to fit the current layout
 */
Player.updateSizes = function () {
  if (Layout.sessionAspectRatio === ASPECT_RATIO.LANDSCAPE) {
    Player.pos.x = Math.round(Player.originLongitudinal);
    Player.pos.y = Math.round(Layout.gameplayHeight / 2);
  } else {
    Player.pos.x = Math.round(Layout.gameplayWidth / 2);
    Player.pos.y = Math.round(Player.originLongitudinal);
  }

  //Player.speedCentre =
  //  Player.originLongitudinal * Layout.proportionalMultiplier;

  Player.outlineThickness = Math.round(
    GAME.PLAYER_OUTLINE_THICKNESS * Layout.proportionalMultiplier
  );

  Player.dampSpeedMultiplier =
    GAME.PLAYER_DAMPSPEED_MULTIPLIER * Layout.proportionalMultiplier;
};

/**
 * @function update
 * @static
 *
 * @description
 * ##### Update some properties of the player
 * - The position to be drawn at
 * - If currently damaged, or eating, decrement the counters for those states
 *
 */
Player.update = function () {
  var speedOffset = Controller.speedOffset * Player.dampSpeedMultiplier;
  if (Layout.sessionAspectRatio === ASPECT_RATIO.LANDSCAPE) {
    Player.pos.x = Player.originLongitudinal + speedOffset;
  } else {
    Player.pos.y = Player.originLongitudinal + speedOffset;
  }
  if (Player.damagedFramesCounter > 0) {
    Player.damagedFramesCounter--;
    if (Player.damagedFramesCounter === 0) {
      Controller.damageAddedSlipperiness = 0;
    }
  }
  if (Player.playerEatsFramesCounter > 0) {
    Player.playerEatsFramesCounter--;
  }
};

/**
 * @function draw
 * @static
 *
 * @description
 * ##### Draw the character
 * - If currently being damaged, flash a different colour based on the level background
 * - If currently eating, flash a different colour based on the eaten obstacle
 * - Draw the tail/friction effect
 * - At faster speeds, distort the circular shape into an ellipse to suggest stretching in the direction of travel
 * - Add any extra layers such as hats etc
 */
Player.draw = function () {
  var i,
    color,
    tailColor,
    segmentColor,
    playerDrawnPosX,
    playerDrawnPosY,
    segmentX,
    segmentY,
    ellipseStretch,
    drawnRadius = Player.drawnRadius,
    xOffset =
      Controller.scalarVectorOfTravel.x * GAME.PLAYER_TAILLENGTH_MULTIPLIER,
    yOffset =
      Controller.scalarVectorOfTravel.y * GAME.PLAYER_TAILLENGTH_MULTIPLIER,
    radiusExtra = 1,
    numSegments = Math.max(
      1,
      Math.floor(
        Controller.speedOffset * GAME.PLAYER_SPEED_TO_TAILSEGMENTS_MULTIPLIER
      )
    );

  // Color
  if (
    Player.damagedFramesCounter > 0 &&
    Display.flashIsOff(GAME.DAMAGE_FLASH_ON_SECS, GAME.DAMAGE_FLASH_OFF_SECS)
  ) {
    color = Player.damagedColor;
  } else if (
    Player.playerEatsFramesCounter > 0 &&
    Display.flashIsOff(
      GAME.PLAYEREATS_FLASH_ON_SECS,
      GAME.PLAYEREATS_FLASH_OFF_SECS
    )
  ) {
    color = Player.eatenColor;
    radiusExtra += GAME.PLAYEREATS_RADIUS_GROWTH;
  } else {
    color = Player.color;
  }

  // Player position
  if (Display.isLandscapeAspect) {
    // TODO confusing variable name
    playerDrawnPosX = Player.pos.x;
    playerDrawnPosY = Player.pos.y + Layout.gameAreaOffsetLateral;
  } else {
    playerDrawnPosX = Player.pos.x + Layout.gameAreaOffsetLateral;
    playerDrawnPosY = Player.pos.y;
  }

  // Ellipse stretch
  ellipseStretch = 1;
  if (Controller.speedOffset > GAME.PLAYER_TAIL_MINSPEED) {
    ellipseStretch +=
      Controller.speedOffset * GAME.PLAYER_ELLIPSE_STRETCH_MULTIPLIER;
  } else {
    numSegments = 0;
  }

  if (Display.levelIsDark) {
    tailColor = GAME.PLAYER_TAILCOLOR_DARKLEVEL;
  } else {
    tailColor = GAME.PLAYER_TAILCOLOR_LIGHTLEVEL;
  }

  // Draw segments
  for (i = 0; i < numSegments; i++) {
    segmentColor = hexOpacityToRGBA(
      tailColor,
      Math.max(0, GAME.PLAYER_TAIL_ALPHA - GAME.PLAYER_TAIL_ALPHA_DROP * i)
    );

    // Segment position
    segmentX = Math.round(playerDrawnPosX - xOffset * i);
    segmentY = Math.round(playerDrawnPosY - yOffset * i);

    Shape.drawEllipse(
      segmentX,
      segmentY,
      drawnRadius * radiusExtra,
      segmentColor,
      ellipseStretch,
      Controller.angleOfTravel
    );

    radiusExtra *= GAME.PLAYER_TAIL_RADIUS_GROWTH;
  }

  // Main player piece
  Shape.drawEllipse(
    playerDrawnPosX,
    playerDrawnPosY,
    drawnRadius,
    hexOpacityToRGBA(color, GAME.PLAYER_HEAD_ALPHA),
    ellipseStretch,
    Controller.angleOfTravel
  );
  Display.ctx.strokeStyle = hexOpacityToRGBA(
    Display.bgColor,
    GAME.PLAYER_OUTLINE_ALPHA
  );
  Display.ctx.lineWidth = Player.outlineThickness;
  Display.ctx.stroke();

  if (Player.addedLayer_ar) {
    Display.drawAddedLayers(playerDrawnPosX, playerDrawnPosY, {
      addedLayer_ar: Player.addedLayer_ar,
      radius: drawnRadius,
      rotation: vectorToDegrees(Controller.scalarVectorOfTravel),
      stretch: ellipseStretch * GAME.PLAYER_GRIN_INCREASERATE,
    });
  }
};

export { Player };