Multiplayer game server with multiple node - node.js

Say you're making a Counter-Strike server. We have a single lobby and multiple rooms in which game sessions occur.
I.E I have 3 node server, nginx load balancer, redis.
SCHEMA
I'm using socket.io, with redis adapter, So I can emit messages to every clients on any server.
I can't understand one thing. For example I have something like this:
var currentGames = {};
socket.on('createGame', function(gameName){
var game = new Game();
game.addPlayer(socket.id);
currentGames.push();
// ... Send to all clients, that game created and they can join
});
socket.on('joinGame', function(gameName){
currentGames[gameName].addPlayer(socket.id);
// ... Send to all players of this game, that player joined
});
socket.on('walk', function(gameName, coordinates){
currentGames[socket.id].walk(player, coordinates);
// ... Get all players positions and send to clients
});
class Game
{
gameName;
players = {};
score;
constructor(name){
this.gameName = name;
}
addPlayer(player){
players[name] = new Player(player);
}
walk(id, coordinates){
this.players[id].updatePosition(coordinates);
}
}
class player
{
id;
life = 100;
coordinates = {0,0,0};
kills;
death;
constructor(id){
this.id = id;
}
updatePosition(coords){
this.coordinates = coords;
}
}
I've just written this code, for example.
let, user_1 is on node-1 and user-2 is on node-2.
If user_1 creates game, instance will be created and saved on node-1.
so when user_2 receives information about game, which was created and he clicks a join button,
client will send request to server and on node-2 there won't be the game instance, which user_1 created.
I hope, you will understand, what I'm talking (my English isn't good).
I guess, that currentGames object of game instances must be a global, for all nodes. But I don't know how.
Should I use redis or mongo or something else, for storing game information, instead of variable?
Am I going with wrong way?

U should sync Game State in Redis, room and game instance, too.
I have some note :
+ Entity State - manage Game State, store and set, get Game information to Redis
+ Client State - each connection client have Client State and Sync with Entity State, each tick in Game loop, u check difference beetween Entity State and Client State and send it to Client and update Game Client

Related

Pass User to Conversation

I want to have someone say to a bot in slack:
#Bot setup #usertosetup
This should then start a conversation with that user to be setup. If I'm right and I've read correctly, the one that starts the conversation with the bot will be the one it listens to?
So the flow would be to bring the new user, an admin and the bot into their own room on slack, then start the conversation. Ideally, would be if you could say in the main chat window that phrase and it starts a private message with the user to set up, but I don't think that's possible due to the channel not existing yet?
I'm using Botkit to get this done.
Solved it.
controller.hears('setup','direct_mention', function(bot,message) {
var x = message.text.indexOf("#");
var usr = message.text.substr(x - 2, message.text.length);
usr = usr.substring(3, usr.length - 1);
bot.api.im.open({user: usr}, function(err, response) {
bot.startConversation({
user:usr,
channel: response.channel.id
}, "Hello");
});
So, it finds the index of the message sent to the bot with the # symbol, pulls it out and does some substring fun to reduce it. Then, launches the conversation by the channel.

multi redux store on node socket server

I'm currently learning react and redux and I am making a practical project in order to be face of some issues.
The project is a multiplayer labyrinth so I decided that using a socket.io server and also that player will be limited in a labyrinth game context, so I am using room (io namespace).
All the game context is store in redux on the node server and is connect to the clients UI.
because of that I have multi-Rooms server I want a different store for each room/game. In absolute I can combine reducers but that mean lot of performance lost and need a dark architecture of action and store.
the problem is that I don't know how create multi-store on node.js
currently the server work in this case ->
on client connect -> if the current room is full generate a new game else assign on current room
public generate = (server:SocketIO.Server) =>{
let newIDGenetator = new Idgenerator();
let store = redux.createStore(labActionReducer);
let room = server.of('/room'+this.labs.length);
let socketHandler = new SocketHandler(room,store,newIDGenetator);
this.labs.push(socketHandler);
this is the function call for generate a new game:
-> create idgenerator which is just an incr who's return is value cast in string
-> create a new store
-> create a io namespace
-> create a new socketHandler which implements:
room.on('connect' , (client,store) =>
client.on('foo',()=>
store.dispatch({type:bar});
)
)
and listen store in order to send the new state to each clients of the handling room
the first room work well but when a second root is create the client show the data of the first room state when I attempt to get the initial state
The client is connect on the good namespace,
So I suppose that createStore do not create a new store. Could you help me to create a new store independent of the first? if need more details I can give you the GitHub link of the project.
thank you for reading
edit: socketHandler code:
export class SocketHandler{
private playersId = {};
private store:Store<Lab,LabActionTypes>;
constructor(server:Namespace,store:Store<Lab,LabActionTypes>,idgenerator:Idgenerator){
this.store = store;
store.subscribe(()=>{
console.log(this.store.getState());
for(let playerId in this.playersId){
let newState = this.store.getState() //utiliser reducer_relatif(state,playerId)
this.playersId[playerId].emit('gridChanged',newState);
}
})
server.on('connect',(client)=>{
let newID = idgenerator.generetaId()
this.playersId[newID]=client;
store.dispatch(action.NewPlayer(newID));
NewClient(client,store,this);
});
}
public getPlayerBySocket = (socket:SocketIO.Socket)=>{
for(let playerId in this.playersId){
if(this.playersId[playerId]===socket){
return playerId;
}
}
}
}
export const NewClient = (client: SocketIO.Socket,store:redux.Store<Lab,LabActionTypes>,socketHandler:SocketHandler) => {
client.on('up',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveUp(player,store));
console.log(JSON.stringify(store.getState()));
})
client.on('down',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveDown(player,store));
})
client.on('left',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveLeft(player,store));
})
client.on('right',()=>{
let player = socketHandler.getPlayerBySocket(client);
store.dispatch(action.PlayerMooveRight(player,store));
})

How to share dynamic objects across workers?

I'm trying to make a game, which works on rooms, lobby and such (imagine the chat app, except with additional checks/information storing).
Let's say, I have a module room.js
var EventEmitter = require('events');
class Room extends EventEmitter {
constructor (id, name) {
super();
this.id = id;
this.name = name;
this.users = [];
}
}
Room.prototype.addUser = function (user) {
if(this.users.indexOf(user) === -1) {
this.users.push(user);
this.emit('user_joined', user);
} else {
/* error handling */
}
};
module.exports = {
Room: Room,
byId: function (id) {
// where should I look up?
}
};
How can I get exactly this object (with events)? How can I access events emitted by this object?
In a single instance of node, I would do something like:
var rooms = [];
var room = new Room(1234, 'test room');
room.on('user_joined', console.log);
rooms.push(room);
Also, I don't quite understood how Redis is actually helping (is it replacement of EventEmitter?)
Regards.
EDIT: Would accept PM2 solutions too.
Instead of handling rooms in Node, you can replace them with channels in Redis).
When a new client wants to join in a room, the NodeJS app returns it the ID of this given room (that is to say the name of the channel), then the client suscribes to the selected room (your client is directly connected to Redis.
You can use a Redis Set to manage the list of rooms.
In this scenario, you don't need any event emitter, and your node servers are stateless.
Otherwise, it would mean Redis would be exposed on the Internet (assuming your game is public), so you must activate Redis authentication. A major problem with this solution is that you have to give the server password to all clients, so it's definitely unsecure.
Moreover, Redis' performances allow brute force attacks so exposing it on Internet is not recommended. That's why I think all communications should go through a Node instance, even if Redis is used as a backend.
To solve this, you can use socket.io to open sockets between Node and your clients, and make the Node instances (not the client) subscribe to the Redis channel. When a message is published by Redis, send it to the client through the socket. And add a layer of authentication to ensure only valid clients connect to a given channel.
Event emitter is not required. It's the Redis client which will be an event emitter (like in this example based on ioRedis)

New websocket on different instance of site

I have a remote control for 4 Devices running on a node server.
Each device has its own page (/1, /2, /3, /4), but they are all generated from the same html/js.
the only difference is the ip for each device, loaded from a json on the server depending on the url path.
This all works, but the problem is: i have 3 obviously wrong IPs entered for testing purposes, and one correct one. Now if i open the correct one, go back to the parent page and open the page of a device with a wrong IP, it is still shown as online and can be controlled.
I understand this like: the socket stays open across the pages and is actually not built new on every site.
how can i make sure that each subpage generates a new socket?
right now, i just have
socket = new io.connect();
in the browser.js,
ioServer.on('connection', function (socket) {
//etc.
}
in the app.js and it works for ONE device.
Am I right to assume that I need some kind of "destroy socket if page is changed"-function?
Thanks for the help
By the looks of it, you only want to instantiate and boot a device when the device's client connects for the first time and emits 'startup'. If this is not the case, I'd probably instantiate and boot each device when the server starts.
Going with the first case, I'd store your devices in a key-value object and reuse them when needed.
var devices = {};
ioServer.on('connection', function (socket) {
var client_ip_address = socket.request.connection.remoteAddress;
console.log("New Connection from: " + client_ip_address);
var device;
socket.on('startup', function (data) {
var deviceId = data.device;
console.log("[INFO] startup: device " + deviceId);
//If this device is already in devices, resuse it
if (deviceId in devices) {
//Get the device from your devices object
device = devices[deviceId];
//Otherwise, create a new device and store it in devices
} else {
device = new HyperDeck(deviceId);
//Store new device in devices object
devices[deviceId] = device;
device.boot();
}
console.log(device.id);
console.log(device.ip);
});
...

how can I make private chat rooms with sockjs?

I am trying to make a chat system where only two users are able to talk to each other at a time ( much like facebook's chat )
I've tried multiplexing, using mongoDB's _id as the name so every channel is unique.
The problem I'm facing is that I cannot direct a message to a single client connection.
this is the client side code that first sends the message
$scope.sendMessage = function() {
specificChannel.send(message)
$scope.messageText = '';
};
this is the server side receiving the message
specificChannel.on('connection', function (conn) {
conn.on('data', function(message){
conn.write('message')
}
}
When I send a message, to any channel, every channel still receives the message.
How can I make it so that each client only listens to the messages sent to a specific channel?
It appeared that SockJS doesn't support "private" channels. I used the following solution for a similar issue:
var channel_id = 'my-very-private-channel'
var connection = new SockJS('/pubsub', '')
connection.onopen = function(){
connection.send({'method': 'set-channel', 'data': {'channel': channel_id}})
}
Backend solution is specific for every technology stack so I can't give a universal solution here. General idea is the following:
1) Parse the message in "on_message" function to find the requested "method name"
2) If the method is "set-channel" -> set the "self.channel" to this value
3) Broadcast further messages to subscribers with the same channel (I'm using Redis for that, but it also depends on your platform)
Hope it helps!

Resources