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;