<commit_before>'use strict';

const util = require('util');
const Data = require('./Data');
const Room = require('./Room');
const Character = require('./Character');
const Config = require('./Config');
const QuestTracker = require('./QuestTracker');

class Player extends Character {
  constructor(data) {
    super(data);

    this.account = data.account || null;
    this.experience = data.experience || 0;
    this.password  = <PASSWORD>;
    this.prompt = ({healthStr, energyStr, }) => `[ ${healthStr} <bold>hp</bold> -- ${energyStr} <bold>energy</bold> ]`;
    this.socket = data.socket || null;
    this.preferences = data.preferences || new Map();
    const questData = Object.assign({
      completed: [],
      active: []
    }, data.quests);

    this.questTracker = new QuestTracker(this, questData.active, questData.completed);
  }

  /**
   * Proxy all events on the player to the quest tracker
   * @param {string} event
   * @param {...*}   args
   */
  emit(event, ...args) {
    super.emit(event, ...args);

    this.questTracker.emit(event, ...args);
  }

  /**
   * Convert prompt tokens into actual data
   * @param {function} promptBuilder (data) => prompt template
   * @param {object} extraData (key is used by promptBuilder)
   */
  interpolatePrompt(promptBuilder, extraData = {}) {
    const buildAttributeStr = attr => `${this.getAttribute(attr)}/${this.getMaxAttribute(attr)}`;
    const healthStr = buildAttributeStr('health');
    const energyStr = buildAttributeStr('energy');

    //TODO: The attr strings could likely be built in a more programmatic fashion.
    // How could this be redone to allow for customization of the prompt without major
    // edits to the Player class?
    const promptData = Object.assign({}, extraData, {
      healthStr, energyStr
    });

    return promptBuilder(promptData);
  }

  canGo(exit) {
    if (!this.room) {
      return false;
    }

    const exits = Array.from(this.room.exits).filter(e => e.direction.indexOf(exit) === 0);

    if (!exits.length) {
      return false;
    }

    if (exits.length > 1) {
      return false;
    }

    if (this.isInCombat()) {
      return false;
    }

    return true;
  }

  save(callback) {
    Data.save('player', this.name, this.serialize(), callback);
  }

  hydrate(state) {
    if (typeof this.room === 'string') {
      let room = state.RoomManager.getRoom(this.room);
      if (!room) {
        util.log(`WARNING: Player ${this.name} was saved to invalid room ${this.room}.`);
        room = state.RoomManager.getStartingRoom();
      }

      this.room = room;
      room.addPlayer(this);
    }

    if (typeof this.account === 'string') {
      this.account = state.AccountManager.getAccount(this.account);
    }

    // Hydrate inventory
    this.inventory.hydrate(state);

    // Hydrate equipment
    // maybe refactor Equipment to be an object like Inventory?
    if (this.equipment && !(this.equipment instanceof Map)) {
      const eqDefs = this.equipment;
      this.equipment = new Map();
      for (const slot in eqDefs) {
        const itemDef = eqDefs[slot];
        let newItem = state.ItemFactory.create(state.AreaManager.getArea(itemDef.area), itemDef);
        newItem.hydrate(state);
        state.ItemManager.add(newItem);
        newItem.isEquipped = true;
        this.equip(newItem);
      }<commit_msg>Fix bug with equipment hydration<commit_after>'use strict';

const util = require('util');
const Data = require('./Data');
const Room = require('./Room');
const Character = require('./Character');
const Config = require('./Config');
const QuestTracker = require('./QuestTracker');

class Player extends Character {
  constructor(data) {
    super(data);

    this.account = data.account || null;
    this.experience = data.experience || 0;
    this.password  = <PASSWORD>;
    this.prompt = ({healthStr, energyStr, }) => `[ ${healthStr} <bold>hp</bold> -- ${energyStr} <bold>energy</bold> ]`;
    this.socket = data.socket || null;
    this.preferences = data.preferences || new Map();
    const questData = Object.assign({
      completed: [],
      active: []
    }, data.quests);

    this.questTracker = new QuestTracker(this, questData.active, questData.completed);
  }

  /**
   * Proxy all events on the player to the quest tracker
   * @param {string} event
   * @param {...*}   args
   */
  emit(event, ...args) {
    super.emit(event, ...args);

    this.questTracker.emit(event, ...args);
  }

  /**
   * Convert prompt tokens into actual data
   * @param {function} promptBuilder (data) => prompt template
   * @param {object} extraData (key is used by promptBuilder)
   */
  interpolatePrompt(promptBuilder, extraData = {}) {
    const buildAttributeStr = attr => `${this.getAttribute(attr)}/${this.getMaxAttribute(attr)}`;
    const healthStr = buildAttributeStr('health');
    const energyStr = buildAttributeStr('energy');

    //TODO: The attr strings could likely be built in a more programmatic fashion.
    // How could this be redone to allow for customization of the prompt without major
    // edits to the Player class?
    const promptData = Object.assign({}, extraData, {
      healthStr, energyStr
    });

    return promptBuilder(promptData);
  }

  canGo(exit) {
    if (!this.room) {
      return false;
    }

    const exits = Array.from(this.room.exits).filter(e => e.direction.indexOf(exit) === 0);

    if (!exits.length) {
      return false;
    }

    if (exits.length > 1) {
      return false;
    }

    if (this.isInCombat()) {
      return false;
    }

    return true;
  }

  save(callback) {
    Data.save('player', this.name, this.serialize(), callback);
  }

  hydrate(state) {
    if (typeof this.room === 'string') {
      let room = state.RoomManager.getRoom(this.room);
      if (!room) {
        util.log(`WARNING: Player ${this.name} was saved to invalid room ${this.room}.`);
        room = state.RoomManager.getStartingRoom();
      }

      this.room = room;
      room.addPlayer(this);
    }

    if (typeof this.account === 'string') {
      this.account = state.AccountManager.getAccount(this.account);
    }

    // Hydrate inventory
    this.inventory.hydrate(state);

    // Hydrate equipment
    // maybe refactor Equipment to be an object like Inventory?
    if (this.equipment && !(this.equipment instanceof Map)) {
      const eqDefs = this.equipment;
      this.equipment = new Map();
      for (const slot in eqDefs) {
        const itemDef = eqDefs[slot];
        let newItem = state.ItemFactory.create(state.AreaManager.getArea(itemDef.area), itemDef.entityReference);
        newItem.inventory = itemDef.inventory;
        newItem.hydrate(state);
        state.ItemManager.add(newItem);
        newItem.isEquipped = true;
        this.equip(newItem);
      }
{'fraction_non_alphanumeric': 0.14321533923303834, 'fraction_numerical': 0.0008849557522123894, 'mean_word_length': 2.8770726129216695, 'pattern_counts': {'":': 0, '<': 13, '<?xml version=': 0, '>': 23, 'https://': 0, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 0, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '2586308', 'n_tokens_mistral': 2080, 'n_tokens_neox': 2010, 'n_words': 514}