Source: models/Player.js

/**
 * @typedef {Object} PlayerScoreData
 * @property {number} combat
 * @property {number} offense
 * @property {number} defense
 * @property {number} support
 */

/**
 * @typedef {Object} PlayerStats
 * @property {number} deaths
 * @property {number} infantryKills
 * @property {number} vehicleKills
 * @property {number} teamKills
 * @property {number} vehiclesDestroyed
 */

/**
 * @typedef {Object} PlayerPosition
 * @property {number} x
 * @property {number} y
 * @property {number} z
 */

/**
 * Represents a player.
 */
class Player {
  /** @type {RCONClient} */
  client;

  /** @type {boolean} */
  partial;

  /** @type {string} */
  id;

  /** @type {string} */
  name;

  /** @type {string} */
  clanTag;

  /** @type {string} */
  platform;

  /** @type {string} */
  eosId;

  /** @type {number} */
  level;

  /** @type {number} */
  team;

  /** @type {number} */
  role;

  /** @type {string} */
  platoon;

  /** @type {PlayerScoreData} */
  scoreData;

  /** @type {PlayerStats} */
  stats;

  /** @type {PlayerPosition} */
  worldPosition;

  /**
   * @param {RCONClient} client
   * @param {Object} data
   * @param {boolean} isPartial
   */
  constructor(client, data, isPartial) {
    // Hide the client reference from console logs
    Object.defineProperty(this, "client", { value: client, enumerable: false });

    this.partial = isPartial;
    this._patch(data);
  }

  /**
   * Patches this player with new data.
   *
   * @param {Object} data
   */
  _patch(data) {
    if ("iD" in data) this.id = data.iD;
    if ("name" in data) this.name = data.name;
    if ("clanTag" in data) this.clanTag = data.clanTag;
    if ("platform" in data) this.platform = data.platform;
    if ("eosId" in data) this.eosId = data.eosId;
    if ("level" in data) this.level = data.level;
    if ("team" in data) this.team = data.team;
    if ("role" in data) this.role = data.role;
    if ("platoon" in data) this.platoon = data.platoon;
    if ("loadout" in data) this.loadout = data.loadout;

    // Normalize the weird casing on cOMBAT while mapping the object
    if ("scoreData" in data && data.scoreData) {
      this.scoreData = {
        combat: data.scoreData.cOMBAT ?? 0,
        offense: data.scoreData.offense ?? 0,
        defense: data.scoreData.defense ?? 0,
        support: data.scoreData.support ?? 0
      };
    }

    if ("stats" in data && data.stats) {
      this.stats = {
        deaths: data.stats.deaths ?? 0,
        infantryKills: data.stats.infantryKills ?? 0,
        vehicleKills: data.stats.vehicleKills ?? 0,
        teamKills: data.stats.teamKills ?? 0,
        vehiclesDestroyed: data.stats.vehiclesDestroyed ?? 0
      };
    }

    if ("worldPosition" in data && data.worldPosition) {
      this.worldPosition = {
        x: data.worldPosition.x ?? 0,
        y: data.worldPosition.y ?? 0,
        z: data.worldPosition.z ?? 0
      };
    }

    // Automatically clear the partial flag if core state data is received
    if ("scoreData" in data || "worldPosition" in data) {
      this.partial = false;
    }
  }

  /**
   * Add this user as an admin.
   *
   * @param {string} adminGroup
   * @param {string} [comment]
   * @returns {Promise<void>}
   */
  async addAdmin(adminGroup, comment) {
    return this.client.players.addAdmin(this.id, adminGroup, comment);
  }

  /**
   * Grants this player VIP status.
   *
   * @param {string} comment
   * @returns {Promise<void>}
   */
  async addVIP(comment) {
    return this.client.players.addVIP(this.id, comment);
  }

  /**
   * Kicks this player from the server.
   *
   * @param {string} [reason]
   * @returns {Promise<void>}
   */
  async kick(reason) {
    return this.client.players.kick(this.id, reason);
  }

  /**
   * Sends this player a message.
   *
   * @param {string} message
   * @returns {Promise<void>}
   */
  async message(message) {
    return this.client.players.message(this.id, message);
  }

  /**
   * Permanently ban this player.
   *
   * @param {string} [reason]
   * @param {string} [adminName]
   * @returns {Promise<void>}
   */
  async permaBan(reason, adminName) {
    return this.client.players.permaBan(this.id, reason, adminName);
  }

  /**
   * Punishes this player.
   *
   * @param {string} [reason]
   * @returns {Promise<void>}
   */
  async punish(reason) {
    return this.client.players.punish(this.id, reason);
  }

  /**
   * Remove this user as an admin.
   * @returns {Promise<void>}
   */
  async removeAdmin() {
    return this.client.players.removeAdmin(this.id);
  }

  /**
   * Removes this player from their platoon.
   *
   * @param {string} [reason]
   * @returns {Promise<void>}
   * @throws {Error} - If the player is commander or not in a unit.
   */
  async removeFromPlatoon(reason) {
    return this.client.players.removePlayerFromPlatoon(this.id, reason);
  }

  /**
   * Remove this player's permanent ban.
   *
   * @returns {Promise<void>}
   */
  async removePermaBan() {
    return this.client.players.removePermaBan(this.id);
  }

  /**
   * Removes this player's temporary ban.
   *
   * @returns {Promise<void>}
   */
  async removeTempBan() {
    return this.client.players.removeTempBan(this.id);
  }

  /**
   * Removes this player's VIP status.
   *
   * @returns {Promise<void>}
   */
  async removeVIP() {
    return this.client.players.removeVIP(this.id);
  }

  /**
   * Temporarily bans this player.
   *
   * @param {number} duration - The number in hours for the temporary ban.
   * @param {string} [reason]
   * @param {string} [adminName]
   * @returns {Promise<void>}
   * @throws {Error} - If duration isn't a positive integer.
   */
  async tempBan(duration, reason, adminName) {
    return this.client.players.tempBan(this.id, duration, reason, adminName);
  }

  /**
   * Switch this player's team.
   *
   * @param {boolean} [switchNow=true] - Whether to switch this player's team immediately, as opposed to on death.
   * @returns {Promise<void>}
   */
  async switchTeams(switchNow) {
    return this.client.players.switchTeams(this.id, switchNow);
  }
}

module.exports = Player;