Source: hg/StatManager.js

// Copyright 2018-2019 Campbell Crowley. All rights reserved.
// Author: Campbell Crowley (dev@campbellcrowley.com)
const StatGroup = require('./StatGroup.js');

/**
 * @description Manages stats and leaderboard information for all of HG.
 * @memberof HungryGames
 * @inner
 */
class StatManager {
  /**
   * @description Constructor.
   * @param {HungryGames~GuildGame} game Parent game.
   */
  constructor(game) {
    /**
     * @description Parent game to reference by default.
     * @public
     * @type {HungryGames~GuildGame}
     * @constant
     */
    this.game = game;
    this.parseDay = this.parseDay.bind(this);
  }
  /**
   * @description Update stats based on the current day data of the given game.
   * @public
   */
  parseDay() {
    const game = this.game;
    const current = game && game.currentGame;
    const events = current && current.day && current.day.events;
    if (!events || !Array.isArray(events)) {
      throw new Error('GuildGame does not have event data to parse.');
    } else if (events.length == 0) {
      return;
    }
    const lifetime = new StatGroup(game, 'global');
    const previous = new StatGroup(game, 'previous');
    const group = game.statGroup ? new StatGroup(game, game.statGroup) : null;

    if (current.day.num == 0) previous.reset();

    const inc = function(...args) {
      try {
        lifetime.increment(...args);
        previous.increment(...args);
        if (group) group.increment(...args);
      } catch (err) {
        console.error(err);
      }
    };

    for (const e of events) {
      if (!e || !e.icons) continue;
      for (let i = 0; i < e.icons.length; i++) {
        const id = e.icons[i].id;
        if (!id) continue;
        const outcome =
            e.icons[i].settings.victim ? e.victim.outcome : e.attacker.outcome;
        switch (outcome) {
          case 'dies':
            inc(id, 'deaths');
            break;
          case 'wounded':
            inc(id, 'wounds');
            break;
          case 'thrives':
            inc(id, 'heals');
            break;
          case 'revived':
            inc(id, 'revives');
            break;
        }
      }
    }

    const aliveTeams = game.options.teamSize ?
        current.teams.filter((t) => t.numAlive > 0) :
        [];
    const collab = game.options.teammatesCollaborate == 'always' ||
        (game.options.teammatesCollaborate == 'untilend' &&
         aliveTeams.length > 1);
    const winners = (collab && aliveTeams.length == 1) ?
        aliveTeams[0].players :
        (current.numAlive == 1 ?
             [current.includedUsers.find((el) => el.living).id] :
             []);
    const ended = winners.length > 0 || current.numAlive == 0;

    for (const p of current.includedUsers) {
      const id = p.id;
      if (!id) continue;
      if (p.living) {
        inc(id, 'daysAlive');
        if (p.state === 'wounded') {
          inc(id, 'daysWounded');
        }
      } else {
        inc(id, 'daysDead');
      }
      if (ended) {
        inc(id, 'kills', p.kills);
        if (winners.includes(id)) {
          inc(id, 'wins');
        } else {
          inc(id, 'losses');
        }
      }
    }
  }

  /**
   * @description Fetch a {@HungryGames~StatGroup} reference.
   * @public
   * @param {string} [id='global'] The ID of the group to fetch.
   * @param {Function} cb Callback with optional error argument, otherwise
   * second argument is the group reference.
   */
  fetchGroup(id = 'global', cb) {
    if (typeof cb !== 'function') {
      cb = id;
      if (typeof cb !== 'function') {
        throw new TypeError('Callback must be a function');
      }
      id = 'global';
    }
    if (typeof id !== 'string') {
      cb(new TypeError('ID must be a string'));
    } else if (StatGroup.exists(this.game, id)) {
      cb(null, new StatGroup(this.game, id));
    } else {
      cb(new Error('Group doesn\'t exist'));
    }
  }

  /**
   * @description Create a new stat group.
   * @public
   * @param {HGStatMetadata} [metadata] Metadata to store with stat group.
   * @param {Function} [cb] Callback function once completed. Only argument is
   * the created group.
   */
  createGroup(metadata, cb) {
    if (typeof metadata === 'function') {
      cb = metadata;
      metadata = null;
    }
    const created = new StatGroup(this.game, metadata);
    if (typeof cb === 'function') cb(created);
  }

  /**
   * @description Fetch IDs of all created groups.
   * @public
   * @param {Function} cb Callback with optional error argument, otherwise
   * second argument is array of IDs as strings.
   */
  fetchGroupList(cb) {
    StatGroup.fetchList(this.game, cb);
  }
}
module.exports = StatManager;