'use strict';
const EventEmitter = require('events');
const Data = require('./Data');
const Player = require('./Player');
const EventManager = require('./EventManager');
/**
* Keeps track of all active players in game
* @extends EventEmitter
* @property {Map} players
* @property {EventManager} events Player events
* @property {EntityLoader} loader
* @listens PlayerManager#save
* @listens PlayerManager#updateTick
*/
class PlayerManager extends EventEmitter {
constructor() {
super();
this.players = new Map();
this.events = new EventManager();
this.loader = null;
this.on('updateTick', this.tickAll);
}
/**
* Set the entity loader from which players are loaded
* @param {EntityLoader}
*/
setLoader(loader) {
this.loader = loader;
}
/**
* @param {string} name
* @return {Player}
*/
getPlayer(name) {
return this.players.get(name.toLowerCase());
}
/**
* @param {Player} player
*/
addPlayer(player) {
this.players.set(this.keyify(player), player);
}
/**
* Remove the player from the game. WARNING: You must manually save the player first
* as this will modify serializable properties
* @param {Player} player
* @param {boolean} killSocket true to also force close the player's socket
*/
removePlayer(player, killSocket = false) {
if (killSocket) {
player.socket.end();
}
player.removeAllListeners();
player.removeFromCombat();
player.effects.clear();
if (player.room) {
player.room.removePlayer(player);
}
player.__pruned = true;
this.players.delete(this.keyify(player));
}
/**
* @return {array}
*/
getPlayersAsArray() {
return Array.from(this.players.values());
}
/**
* @param {string} behaviorName
* @param {Function} listener
*/
addListener(event, listener) {
this.events.add(event, listener);
}
/**
* @param {Function} fn Filter function
* @return {array}
*/
filter(fn) {
return this.getPlayersAsArray().filter(fn);
}
/**
* Load a player for an account
* @param {GameState} state
* @param {Account} account
* @param {string} username
* @param {boolean} force true to force reload from storage
* @return {Player}
*/
async loadPlayer(state, account, username, force) {
if (this.players.has(username) && !force) {
return this.getPlayer(username);
}
if (!this.loader) {
throw new Error('No entity loader configured for players');
}
const data = await this.loader.fetch(username);
data.name = username;
let player = new Player(data);
player.account = account;
this.events.attach(player);
this.addPlayer(player);
return player;
}
/**
* Turn player into a key used by this class's map
* @param {Player} player
* @return {string}
*/
keyify(player) {
return player.name.toLowerCase();
}
/**
* @param {string} name
* @return {boolean}
*/
exists(name) {
return Data.exists('player', name);
}
/**
* Save a player
* @fires Player#save
*/
async save(player) {
if (!this.loader) {
throw new Error('No entity loader configured for players');
}
await this.loader.update(player.name, player.serialize());
/**
* @event Player#saved
*/
player.emit('saved');
}
/**
* @fires Player#saved
*/
async saveAll() {
for (const [ name, player ] of this.players.entries()) {
await this.save(player);
}
}
/**
* @fires Player#updateTick
*/
tickAll() {
for (const [ name, player ] of this.players.entries()) {
/**
* @event Player#updateTick
*/
player.emit('updateTick');
}
}
/**
* Used by Broadcaster
* @return {Array<Character>}
*/
getBroadcastTargets() {
return this.getPlayersAsArray();
}
}
module.exports = PlayerManager;