Newer
Older
BlackoutServerNode / room.js
const Item = require("./item");
const EventEmitter = require("events");
const Utils = require("./utils");
const CutHandler = require("./cut");
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;

		this.cutSpawnTime = 5;

		this.nextNewItemId = -1;
	}
	spawnCut()
	{
		this.cutSpawnTimeout = setTimeout(this.spawnCut.bind(this), this.cutSpawnTime * 1000);
		this.cutSpawnTime = Math.max(0.5, this.cutSpawnTime * 0.9);

		if (this.tagsByType['cut'] !== undefined)
		{
			for (var cutTag of this.tagsByType['cut'])
			{
				if (!cutTag.handler)
				{
					const item = this.spawnItem(this.getNextPlayerOwner(), 'cut', {},
						i =>
						{
							i.details.x = cutTag.x;
							i.details.y = cutTag.y;
							i.details.z = cutTag.z;
							var cutHandler = i.getHandler(CutHandler, true);
							console.log("Created cut: ");
							console.log(cutHandler);
						}
					);
					cutTag.handler = item.getHandler(CutHandler);
					console.log(cutTag.handler);
					break;
				}
			}
		}
	}
	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, setupCallback = null)
	{
		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
			const itemId = this.nextNewItemId--;

			const item = new Item(playerId, itemId, this);

			itemDetails.__change_ownership_player__ = playerId;
			itemDetails.__change_ownership_old_key__ = item.key;
			itemDetails.type = type;
			item.details = itemDetails;

			this.items[item.key] = item;

			if (setupCallback != null) setupCallback(item);

			item.detailsUpdated();

			console.log("Inserting: (" + Object.values(this.items).length + ")");
			console.log(item);

			this.players[playerId].sendMessage('update_items', {items:[item.getJSON()]});

			return item;
		}
		return null;
	}
	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 countSwapPlayers = Object.values(this.players).length;

		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 (countSwapPlayers == 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
				{
					const nextPlayerId = this.getNextPlayerOwner();
					toChangeOwner.push([item, nextPlayerId]);
				}
			}
		}
		this.updateItems(toDelete);
		this.changeItemsOwner(toChangeOwner);
		//console.log(this.items);
		
		p.removeFromRoom();

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

		return true;
	}
	getNextPlayerOwner()
	{
		var playerArray = Object.values(this.players);
		if (playerArray.length == 0) return -1;
		this.nextPlayerOwner = this.nextPlayerOwner % playerArray.length;
		const ret = playerArray[this.nextPlayerOwner].id;
		this.nextPlayerOwner++;
		return ret;
	}
	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);
						}
					}

					if (this.tagsByType['spawn'] !== undefined)
					{
						Utils.shuffle(this.tagsByType['spawn']);
					}
					if (this.tagsByType['cut'] !== undefined)
					{
						Utils.shuffle(this.tagsByType['cut']);
					}

					for (var p of Object.values(this.players))
					{
						this.spawnPlayer(p);
					}

					this.cutSpawnTimeout = setTimeout(this.spawnCut.bind(this), 400);
				}
				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 isDelete = 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);
								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.stop();
								console.log("Deleting: " + item.key);
							}

							toSend.push(item);
						}
					}
					//console.log("ITEMS: ");
					//console.log(this.items);
					this.updateItems(toSend, player);
				}
				break;
			case 'item_command':
				if (message.item_keys !== undefined && message.command)
				{
					let byPlayerId = {};
					for (var key of message.item_keys)
					{
						if (this.items[key] !== undefined)
						{
							var playerId = this.items[key].playerId;
							if (this.players[playerId] !== undefined)
							{
								if (byPlayerId[playerId] === undefined)
								{
									byPlayerId[playerId] = [];
								}
								byPlayerId[playerId].push(key);
							}
						}
					}
					for (var playerId in byPlayerId)
					{
						console.log("Player " + playerId + " gets item command " + message.command + " on:");
						console.log(byPlayerId[playerId]);
						this.players[playerId].sendMessage('item_command', {...message, item_keys:byPlayerId[playerId]});
					}
				}
				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;

		clearTimeout(this.cutSpawnTimeout);
	}
}

module.exports = Room;