diff --git a/cut.js b/cut.js new file mode 100644 index 0000000..2fdc725 --- /dev/null +++ b/cut.js @@ -0,0 +1,39 @@ +class CutHandler +{ + constructor(room, item) + { + this.room = room; + this.item = item; + this.nextSpawnTime = 4; + this.virusRemaining = 1; + } + start() + { + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + spawn() + { + const range = 1.2; + const offX = Math.random() * range - range * 0.5; + const offZ = Math.random() * range - range * 0.5; + this.room.spawnItem(this.room.getNextPlayerOwner(), 'virus', {x:this.item.details.x+offX,y:this.item.details.y,z:this.item.details.z+offZ}); + console.log("Spawning virus"); + + this.virusRemaining--; + + if (this.virusRemaining > 0) + { + this.nextSpawnTime = Math.max(1, this.nextSpawnTime - 0.1); + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + } + stop() + { + clearTimeout(this.spawnTimeout); + } + itemUpdated() + { + } +} + +module.exports = CutHandler; diff --git a/cut.js b/cut.js new file mode 100644 index 0000000..2fdc725 --- /dev/null +++ b/cut.js @@ -0,0 +1,39 @@ +class CutHandler +{ + constructor(room, item) + { + this.room = room; + this.item = item; + this.nextSpawnTime = 4; + this.virusRemaining = 1; + } + start() + { + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + spawn() + { + const range = 1.2; + const offX = Math.random() * range - range * 0.5; + const offZ = Math.random() * range - range * 0.5; + this.room.spawnItem(this.room.getNextPlayerOwner(), 'virus', {x:this.item.details.x+offX,y:this.item.details.y,z:this.item.details.z+offZ}); + console.log("Spawning virus"); + + this.virusRemaining--; + + if (this.virusRemaining > 0) + { + this.nextSpawnTime = Math.max(1, this.nextSpawnTime - 0.1); + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + } + stop() + { + clearTimeout(this.spawnTimeout); + } + itemUpdated() + { + } +} + +module.exports = CutHandler; diff --git a/item.js b/item.js index 91da176..15104d5 100644 --- a/item.js +++ b/item.js @@ -1,11 +1,12 @@ class Item { - constructor(playerId, itemId) + constructor(playerId, itemId, room) { this.playerId = playerId; this.itemId = itemId; this.updateKey(); this.details = {}; + this.room = room; this.handlers = []; } @@ -30,6 +31,22 @@ } } + getHandler(handlerType, createIfNotFound = false) + { + for (var h of this.handlers) + { + if (h instanceof handlerType) + { + return h; + } + } + if (!createIfNotFound) return null; + let handler = new handlerType(this.room, this); + if (typeof handler.start === "function") handler.start(); + this.handlers.push(handler); + return handler; + } + start() { } diff --git a/cut.js b/cut.js new file mode 100644 index 0000000..2fdc725 --- /dev/null +++ b/cut.js @@ -0,0 +1,39 @@ +class CutHandler +{ + constructor(room, item) + { + this.room = room; + this.item = item; + this.nextSpawnTime = 4; + this.virusRemaining = 1; + } + start() + { + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + spawn() + { + const range = 1.2; + const offX = Math.random() * range - range * 0.5; + const offZ = Math.random() * range - range * 0.5; + this.room.spawnItem(this.room.getNextPlayerOwner(), 'virus', {x:this.item.details.x+offX,y:this.item.details.y,z:this.item.details.z+offZ}); + console.log("Spawning virus"); + + this.virusRemaining--; + + if (this.virusRemaining > 0) + { + this.nextSpawnTime = Math.max(1, this.nextSpawnTime - 0.1); + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + } + stop() + { + clearTimeout(this.spawnTimeout); + } + itemUpdated() + { + } +} + +module.exports = CutHandler; diff --git a/item.js b/item.js index 91da176..15104d5 100644 --- a/item.js +++ b/item.js @@ -1,11 +1,12 @@ class Item { - constructor(playerId, itemId) + constructor(playerId, itemId, room) { this.playerId = playerId; this.itemId = itemId; this.updateKey(); this.details = {}; + this.room = room; this.handlers = []; } @@ -30,6 +31,22 @@ } } + getHandler(handlerType, createIfNotFound = false) + { + for (var h of this.handlers) + { + if (h instanceof handlerType) + { + return h; + } + } + if (!createIfNotFound) return null; + let handler = new handlerType(this.room, this); + if (typeof handler.start === "function") handler.start(); + this.handlers.push(handler); + return handler; + } + start() { } diff --git a/room.js b/room.js index d02b5a5..e54d9df 100644 --- a/room.js +++ b/room.js @@ -1,5 +1,7 @@ const Item = require("./item"); const EventEmitter = require("events"); +const Utils = require("./utils"); +const CutHandler = require("./cut"); var nextId = 0; class Room extends EventEmitter @@ -21,6 +23,39 @@ 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) { @@ -59,17 +94,36 @@ this.spawnItem(p.id, 'player', {x:spawn.x, y:spawn.y, z:spawn.z}); } - spawnItem(playerId, type, itemDetails) + 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 - let details = {...itemDetails, player_id:playerId, type:type, item_id:-1, key:'no_assigned_key'}; + const itemId = this.nextNewItemId--; - this.players[playerId].sendMessage('update_items', {items:[details]}); + 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*/) { @@ -91,7 +145,7 @@ delete this.players[p.id]; - var swapPlayers = Object.values(this.players); + var countSwapPlayers = Object.values(this.players).length; this.broadcastMessage("remove_player", {player_id:p.id}); @@ -105,7 +159,7 @@ var shouldSwap = false; if (item.details.swap_on_disconnect) { - if (swapPlayers.length == 0) + if (countSwapPlayers == 0) { shouldDelete = true; } @@ -126,8 +180,8 @@ } else if (shouldSwap) //swapOnDisconnect { - this.nextPlayerOwner = this.nextPlayerOwner % swapPlayers.length; - toChangeOwner.push([item, swapPlayers[this.nextPlayerOwner].id]); + const nextPlayerId = this.getNextPlayerOwner(); + toChangeOwner.push([item, nextPlayerId]); } } } @@ -137,13 +191,22 @@ p.removeFromRoom(); - if (swapPlayers.length == 0) + 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; @@ -190,10 +253,21 @@ } } + 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': @@ -225,7 +299,7 @@ } let item = this.getItem(player.id, itemId); let isNew = item == null; - let isDeleted = false; + let isDelete = false; if (!isNew && itemData.__delete__) { delete this.items[item.key]; @@ -237,7 +311,7 @@ { console.log("Uhoh, wasRenamed item not found!"); } - item = new Item(player.id, itemId); + item = new Item(player.id, itemId, this); this.items[item.key] = item; } const { item_id, player_id, key, ...cleanDetails } = itemData; @@ -252,7 +326,7 @@ } if (isDelete) { - item.remove(); + item.stop(); console.log("Deleting: " + item.key); } @@ -264,6 +338,33 @@ 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) @@ -306,6 +407,8 @@ player.removeFromRoom(); } this.players.length = 0; + + clearTimeout(this.cutSpawnTimeout); } } diff --git a/cut.js b/cut.js new file mode 100644 index 0000000..2fdc725 --- /dev/null +++ b/cut.js @@ -0,0 +1,39 @@ +class CutHandler +{ + constructor(room, item) + { + this.room = room; + this.item = item; + this.nextSpawnTime = 4; + this.virusRemaining = 1; + } + start() + { + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + spawn() + { + const range = 1.2; + const offX = Math.random() * range - range * 0.5; + const offZ = Math.random() * range - range * 0.5; + this.room.spawnItem(this.room.getNextPlayerOwner(), 'virus', {x:this.item.details.x+offX,y:this.item.details.y,z:this.item.details.z+offZ}); + console.log("Spawning virus"); + + this.virusRemaining--; + + if (this.virusRemaining > 0) + { + this.nextSpawnTime = Math.max(1, this.nextSpawnTime - 0.1); + this.spawnTimeout = setTimeout(this.spawn.bind(this), this.nextSpawnTime * 1000); + } + } + stop() + { + clearTimeout(this.spawnTimeout); + } + itemUpdated() + { + } +} + +module.exports = CutHandler; diff --git a/item.js b/item.js index 91da176..15104d5 100644 --- a/item.js +++ b/item.js @@ -1,11 +1,12 @@ class Item { - constructor(playerId, itemId) + constructor(playerId, itemId, room) { this.playerId = playerId; this.itemId = itemId; this.updateKey(); this.details = {}; + this.room = room; this.handlers = []; } @@ -30,6 +31,22 @@ } } + getHandler(handlerType, createIfNotFound = false) + { + for (var h of this.handlers) + { + if (h instanceof handlerType) + { + return h; + } + } + if (!createIfNotFound) return null; + let handler = new handlerType(this.room, this); + if (typeof handler.start === "function") handler.start(); + this.handlers.push(handler); + return handler; + } + start() { } diff --git a/room.js b/room.js index d02b5a5..e54d9df 100644 --- a/room.js +++ b/room.js @@ -1,5 +1,7 @@ const Item = require("./item"); const EventEmitter = require("events"); +const Utils = require("./utils"); +const CutHandler = require("./cut"); var nextId = 0; class Room extends EventEmitter @@ -21,6 +23,39 @@ 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) { @@ -59,17 +94,36 @@ this.spawnItem(p.id, 'player', {x:spawn.x, y:spawn.y, z:spawn.z}); } - spawnItem(playerId, type, itemDetails) + 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 - let details = {...itemDetails, player_id:playerId, type:type, item_id:-1, key:'no_assigned_key'}; + const itemId = this.nextNewItemId--; - this.players[playerId].sendMessage('update_items', {items:[details]}); + 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*/) { @@ -91,7 +145,7 @@ delete this.players[p.id]; - var swapPlayers = Object.values(this.players); + var countSwapPlayers = Object.values(this.players).length; this.broadcastMessage("remove_player", {player_id:p.id}); @@ -105,7 +159,7 @@ var shouldSwap = false; if (item.details.swap_on_disconnect) { - if (swapPlayers.length == 0) + if (countSwapPlayers == 0) { shouldDelete = true; } @@ -126,8 +180,8 @@ } else if (shouldSwap) //swapOnDisconnect { - this.nextPlayerOwner = this.nextPlayerOwner % swapPlayers.length; - toChangeOwner.push([item, swapPlayers[this.nextPlayerOwner].id]); + const nextPlayerId = this.getNextPlayerOwner(); + toChangeOwner.push([item, nextPlayerId]); } } } @@ -137,13 +191,22 @@ p.removeFromRoom(); - if (swapPlayers.length == 0) + 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; @@ -190,10 +253,21 @@ } } + 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': @@ -225,7 +299,7 @@ } let item = this.getItem(player.id, itemId); let isNew = item == null; - let isDeleted = false; + let isDelete = false; if (!isNew && itemData.__delete__) { delete this.items[item.key]; @@ -237,7 +311,7 @@ { console.log("Uhoh, wasRenamed item not found!"); } - item = new Item(player.id, itemId); + item = new Item(player.id, itemId, this); this.items[item.key] = item; } const { item_id, player_id, key, ...cleanDetails } = itemData; @@ -252,7 +326,7 @@ } if (isDelete) { - item.remove(); + item.stop(); console.log("Deleting: " + item.key); } @@ -264,6 +338,33 @@ 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) @@ -306,6 +407,8 @@ player.removeFromRoom(); } this.players.length = 0; + + clearTimeout(this.cutSpawnTimeout); } } diff --git a/utils.js b/utils.js new file mode 100644 index 0000000..2edc0c2 --- /dev/null +++ b/utils.js @@ -0,0 +1,22 @@ +var Utils = {}; + +Utils.shuffle = function shuffle(array) { + var currentIndex = array.length; + var temporaryValue, randomIndex; + + // While there remain elements to shuffle... + while (0 !== currentIndex) + { + // Pick a remaining element... + randomIndex = Math.floor(Math.random() * currentIndex); + currentIndex -= 1; + + // And swap it with the current element. + temporaryValue = array[currentIndex]; + array[currentIndex] = array[randomIndex]; + array[randomIndex] = temporaryValue; + } + return array; +}; + +module.exports = Utils;