Newer
Older
BlackoutServerNode / room.js
const Item = require("./item");
const EventEmitter = require("events");
var nextId = 0;

class Room extends EventEmitter
{
	constructor(map)
	{
		super();
		this.id = nextId++;
		this.players = {};	// id => Player
		this.items = {};	// key => Item
		this.map = map;

		// server tags sent by the first client to connect, so we know some stuff about the level
		this.serverTagsSent = false;
		this.tagsByType = {};	//type => Item
		this.tagsByName = {};
		this.tags = [];
		this.tagsByGroup = {};

		this.nextSpawn = 0;
		this.nextPlayerOwner = 0;
	}
	addPlayer(p)
	{
		if (this.players[p] !== undefined) return false;

		this.players[p.id] = p;

		p.room = this;
		p.sendMessage("enter_room", {room_id:this.id, map:this.map, items:Object.values(this.items).map(i => i.getJSON())});

		this.broadcastMessage("new_player", {player_id:p.id});

		p.addToRoom();

		if (this.serverTagsSent)
		{
			this.spawnPlayer(p);
		}

		return true;
	}
	spawnPlayer(p)
	{
		console.log("Spawning " + p.id);
		const spawns = this.tagsByType['spawn'];
		if (!spawns || spawns.length == 0) return;


		this.nextSpawn = this.nextSpawn % spawns.length;

		console.log("Found spawns, using " + this.nextSpawn);

		var spawn = spawns[this.nextSpawn];
		this.nextSpawn++;


		this.spawnItem(p.id, 'player', {x:spawn.x, y:spawn.y, z:spawn.z});
	}
	spawnItem(playerId, type, itemDetails)
	{
		if (this.players[playerId] !== undefined)
		{
			console.log("Spawning " + type + " for " + playerId);
			console.log(itemDetails);
			// invalid key and item_id so we know it's a new spawn
			let details = {...itemDetails, player_id:playerId, type:type, item_id:-1, key:'no_assigned_key'};

			this.players[playerId].sendMessage('update_items', {items:[details]});
		}
	}
	changeItemsOwner(items, playerId /*optional*/)
	{
		if (!Array.isArray(items))
		{
			items = [items, playerId];
		}
		this.broadcastMessage('take_ownership', {items:items.map(([item, pId]) => ({key:item.key, player_id:pId}))});
		for (let [item, pId] of items)
		{
			console.log("Swapping " + item.key + " to " + pId);

			item.details['__change_ownership_player__'] = pId;
		}
	}
	removePlayer(p)
	{
		if (this.players[p.id] === undefined) return false;

		delete this.players[p.id];

		var swapPlayers = Object.values(this.players);

		this.broadcastMessage("remove_player", {player_id:p.id});

		var toDelete = [];
		var toChangeOwner = [];
		for (var item of Object.values(this.items))
		{
			if (item.playerId == p.id)	//deleteOnDisconnect
			{
				var shouldDelete = item.details.delete_on_disconnect;
				var shouldSwap = false;
				if (item.details.swap_on_disconnect)
				{
					if (swapPlayers.length == 0)
					{
						shouldDelete = true;
					}
					else
					{
						shouldSwap = true;
					}
				}
				if (shouldDelete)
				{
					item.details.__delete__ = true;
					toDelete.push(item);
					console.log("Deleting:");
					console.log(item);

					item.stop();
					delete this.items[item.key];
				}
				else if (shouldSwap)	//swapOnDisconnect
				{
					this.nextPlayerOwner = this.nextPlayerOwner % swapPlayers.length;
					toChangeOwner.push([item, swapPlayers[this.nextPlayerOwner].id]);
				}
			}
		}
		this.updateItems(toDelete);
		this.changeItemsOwner(toChangeOwner);
		//console.log(this.items);
		
		p.removeFromRoom();

		if (swapPlayers.length == 0)
		{
			this.emit('lost_last_player');
		}

		return true;
	}
	getItem(playerId, itemId)
	{
		const key = playerId + "_" + itemId;
		if (this.items[key] === undefined) return null;

		return this.items[key];
	}
	playerMessage(player, messageStr)
	{
		const message = JSON.parse(messageStr);
		switch (message.type)
		{
			case 'server_tags':
				if (!this.serverTagsSent && message.tags !== undefined)
				{
					this.serverTagsSent = true;
					console.log(message.tags);
					for (var tag of message.tags)
					{
						const type = tag.type;
						const group = tag.group;
						const name = tag.name;

						this.tags.push(tag);

						if (tag.name)
						{
							tag.name = "" + tag.name;
							this.tagsByName[tag.name] = tag;
						}

						if (tag.group)
						{
							tag.group = "" + tag.group;
							if (this.tagsByGroup[tag.group] === undefined) this.tagsByGroup[tag.group] = [];
							this.tagsByGroup[tag.group].push(tag);
						}

						if (tag.type)
						{
							tag.type = "" + tag.type;
							if (this.tagsByType[tag.type] === undefined) this.tagsByType[tag.type] = [];
							this.tagsByType[tag.type].push(tag);
						}
					}

					for (var p of Object.values(this.players))
					{
						this.spawnPlayer(p);
					}
				}
				break;
			case 'update_items':
				if (message.items !== undefined)
				{
					let toSend = [];
					for (var itemData of message.items)
					{
						if (itemData.item_id !== undefined)
						{
							const itemId = parseInt(itemData.item_id);
							let wasRenamed = false;
							if (itemData.__change_ownership_old_key__)
							{
								const oldKey = "" + itemData.__change_ownership_old_key__;
								if (this.items[oldKey] !== undefined && this.items[oldKey].details.__change_ownership_player__ == player.id)
								{
									let renameItem = this.items[oldKey];
									delete this.items[oldKey];
									renameItem.playerId = player.id;
									renameItem.itemId = itemId;
									renameItem.updateKey();
									delete renameItem.details.__change_ownership_player__;
									this.items[renameItem.key] = renameItem;
									wasRenamed = true;

									console.log("Received re-ownership of " + oldKey + " to " + player.id + "_" + itemId);
								}
							}
							let item = this.getItem(player.id, itemId);
							let isNew = item == null;
							let isDeleted = false;
							if (!isNew && itemData.__delete__)
							{
								delete this.items[item.key];
								isDelete = true;
							}
							if (isNew)
							{
								if (wasRenamed)
								{
									console.log("Uhoh, wasRenamed item not found!");
								}
								item = new Item(player.id, itemId);
								this.items[item.key] = item;
							}
							const { item_id, player_id, key, ...cleanDetails } = itemData;
							item.details = cleanDetails;
							item.detailsUpdated();
							
							if (isNew)
							{
								item.start();
								console.log("Inserting: (" + Object.values(this.items).length + ")");
								console.log(item);
							}
							if (isDelete)
							{
								item.remove();
								console.log("Deleting: " + item.key);
							}

							toSend.push(item);
						}
					}
					//console.log("ITEMS: ");
					//console.log(this.items);
					this.updateItems(toSend, player);
				}
				break;
		}
	}
	updateItems(items, skipPlayer = null)
	{
		if (items.length > 0)
		{
			var toSend = items.map(i => i.getJSON());
			//console.log("TOSEND: ");
			//console.log(toSend);
			this.broadcastMessage('update_items', {items:toSend}, skipPlayer);
		}
	}
	broadcastString(msg, skipPlayer = null)
	{
		for (var p of Object.values(this.players))
		{
			if (p == skipPlayer) continue;
			p.socket.send(msg);
		}
	}
	broadcastMessage(type, details, skipPlayer = null)
	{
		const msg = JSON.stringify({...details, type:type});
		for (var p of Object.values(this.players))
		{
			if (p == skipPlayer) continue;
			p.socket.send(msg);
		}
	}
	remove()
	{
		for (var item of Object.values(this.items))
		{
			item.stop();
		}
		this.items = {};

		for (var player of Object.values(this.players))
		{
			player.removeFromRoom();
		}
		this.players.length = 0;
	}
}

module.exports = Room;