I'm trying to create a basic TCG style game with Node/Vue/Socket.io and can't seem to figure out how to emit to both clients when a "ready" count = 2 but with different data, I'll explain a little below...
The sequence of events is as such:
player connects -> server sends player a "deck" -> player clicks ready to start and also sends back their first 'card'.. Then the server should send out to each player the other players first card. (Note my emit events don't have the correct titles atm - they were already written up on the front end so just kept them the same)
On connection I've pushed to an array called sockets, that I was using for testing. Then in the "ready" event I created an array called "firstCards" that I'm pushing the socket event data to then adding a .socket property to it (to signify who's who), then incrementing ready.
I've had a little bit of a play around with a few different methods but I can only seem to get the last card sent to both clients as opposed to each client getting the other clients first.. I also tried just putting the "if" statement outside of the socket event (as you will see below with the comment on the brackets/curly braces) which doesn't seem to work either.
I haven't tried this kind of asymmetric data transfer before and unsure if that is even the correct term... or whether this is even the correct way to do so, any help would be much appreciated!
This is the code I'm using so far:
socket.on('ready-up', function (card)
{
console.log(`Player ${socket.id} is ready`);
ready++;
console.log(ready);
card.socket = socket.id;
firstCards.push(card);
console.log(firstCards);
});
if (ready == 2)
{
for (let i = 0; i < sockets.length; i++)
{
io.to(sockets[i]).emit('p2hand', "Both players ready");
let opp = sockets.find(element => element != socket.id);
console.log(`Socket ID is: ${socket.id}`);
console.log(`Opp ID is: ${opp}`);
let card = firstCards.find(element => element.socket == opp)
console.log(card);
io.to(opp).emit('reveal',
{
'name': card.name,
'hp': card.hp,
'mp': card.mp,
'skills': card.skills,
'icon': card.icon
});
// io.to(opp).emit('reveal', card);
ready = 0;
}
}
// });
So I figured this one out for anyone who may end up wanting to do what I was trying to do....
I decided that upon connection, both clients join a room called "game1".
The server will then emit "firstCards" to that room.
After that it was just a case of making sure the player-client know which was the opponents card... Now I could have used the .name property for this, but I decided to add an "id" property using the socket.id instead due to the possibility of the same card being drawn for both players.
I'm thinking that all server-client interactions will now have to carry this property for any other cards in the game such as items, spells, etc
Related
I have the following code:
openTokInit() {
this.session = OT.initSession(this.tokboxApiKey, this.sessionId);
const self = this;
this.session.on('connectionCreated', function(event) {
self.connectionCount++;
});
if (this.connectionCount < 2) {
this.session.connect(this.token, err => {
if (err) {
reject(err);
} else {
resolve(this.session);
}
});
}
The problem is that when the if statement runs, the connectionCount is always 0, because the 'connectionCreated' event is fired a few seconds later. I'm not clear on how to appropriately wait for all the connectionCreated events to fire before connecting a new session.
Adam here from the OpenTok team.
You won't get the "connectionCreated" Event until after you connect. So you will need to instead disconnect if you have connected and you are the 3rd (or more) participant. I would use the connection.creationTime to see who got there first to avoid 2 people connecting at about the same time and both of them disconnecting. Something like this should do the trick:
session = OT.initSession(apiKey, sessionId);
let connectionsBeforeUs = 0;
session.on('connectionCreated', (event) => {
if (event.connection.connectionId !== session.connection.connectionId &&
event.connection.creationTime < session.connection.creationTime) {
// There is a new connection and they got here before us
connectionsBeforeUs += 1;
if (connectionsBeforeUs >= 2) {
// We should leave there are 2 or more people already here before us
alert('disconnecting this room is already full');
session.disconnect();
}
}
});
session.connect(token);
Here is a jsbin that demonstrates it working.
I'm not sure how your whole application works but another option might be to do this at the server side and only hand out 2 tokens for people to connect. So when they try to get a 3rd token you block them at that point. Rather than letting them connect to the session and then disconnect themselves. The advantage of this approach is that you can notice quicker and give the user feedback sooner. Also a malicious user can't just hack the javascript and connect anyway. You could also use the session monitoring API to track users connecting from your server.
Another option again is to use the forceDisconnect() function to kick people out of a room if there are already 2 people in there. So it's the responsibility of the people already in the room to kick out the third participant rather than the 3rd participant noticing that there are already people in there and leaving themselves. This will mean that the malicious person couldn't hack the JavaScript code in their browser and join other people's rooms.
Without knowing your whole application though it's hard to know what the best option is for you.
I hope this helps!
I am currently coding a discord bot and have just created a JSON file. This file is to record each members points in a system. I wanted to check within the client.on("ready") function whether a member's points is undefined if so, set it to 0
if (client.points[/*problem*/guild.member.id].points == undefined) {
client.points [/*problem*/guild.member.id] = {
points: 0;
}
fs.writeFile("./points.json", JSON.stringify (client.points, null, 4), err => {
if (err) throw.err;
});
I improvised with "guild.member.id", however, I don't think that is the right way to do it.
EDIT: Due to the impossibility and inefficiency of retrieving the User's ID within the ready function, I have created an if statement every time a player speaks, which then assigns them to my JSON. This has a benefit of recording whether the user is active or not.
You typically don't want to do this every time the bot comes online. Instead, before you do anything related to points, first check if they are saved, if they aren't initialized them with your default values and save them.
https://anidiots.guide/coding-guides/json-based-points-system
I'm building a 3D game in the browser using THREE.js. Lots of fun, but I came across the following situation:
An object in my 3D scene is continuously moving around, driven by user input. I need to save the object's position to my database in real-time.
Let's start at the front-end. Angular.js is watching my object's position using its built-in $watch functionality. The object's position can change multiple times per second.
On each change, I emit an event to the backend Node.js server using Socket IO, like so:
socket.emit('update', {
id: id,
position: position
});
In the back-end, the event is caught and immediatly emitted to other members in the same Socket IO Room. This way, everyone in this room will have the most real-time update possible.
Now, because the event can happen multiple times per second, I don't want to update my MongoDB collection on each change, since this would cause a lot of overhead. Instead, I'm looking for a way of incidentally saving data to the database.
I've came up with a solution by using Node.js setInterval function, which saves data every 1000ms. For each distinct id (which is unique per object) received on the backend, a new key is created on an JavaScript object, thus keeping track of changes on a per-object basis.
The (simplified) code on the backend:
let update_queue = new Object();
// ...
// Update Event
socket.on('update', (msg) => {
// Flag Changes
if (!update_queue[msg.id]) update_queue[msg.id] = { changes: true };
// Set Interval Timer
if (!update_queue[msg.id].timer) {
update_queue[msg.id].timer = setInterval(() => {
if (!update_queue[msg.id].changes) {
clearInterval(update_queue[msg.id].timer);
return;
}
// This saves data to MongoDB
Object3DCollection.update(msg.id, msg.position)
.then((res) => {
console.log('saved');
});
// Unflag Changes
update_queue[msg.id].changes = false;
}, 1000);
}
// Immediate Broadcast to Socket Room
socket.broadcast.to('some_room').emit('object_updated', msg);
});
The Question
Is this a proper way of handling very frequent socket data and still saving it to a database? Or are there any other suggestions/solutions that are more robuust or work better.
Note
I do not want to wait for my object to be saved to the database and then emit the saved data to the rest of the socket room. The delay of database write operations is not suitable for the real-time game situation I'm dealing with.
Thanks in advance! All suggestions/solutions are appreciated and will be considered.
I have publishing setup along with sessions to send out messages to the right rooms.
I'm currently having issues as to how do I go about limiting returned messages so if a room has, say, 200 messages in it and another one is posted, the oldest one is deleted.
//how messages are created
Meteor.methods({
newMessage: function (message) {
message.user = Meteor.userId();
Messages.insert(message);
}
});
//how messages are published
Meteor.publish('messages', function (channel) {
return Messages.find({channel: channel});
});
//how chatrooms are published
Meteor.publish('channels', function () {
return Channels.find();
});
Problem is, normally I would do this by putting this in the publications {sort:{limit:15}}
However, that doesn't work in this case and results in ALL of the messages being limited.
They need to be sorted by room, or, well, per session:key.
Is there a simple way of going about this? Or would I have to make a method on the serverside to run .forEach channel?
There's no decent way of publishing the top 15 posts from each room in a single cursor. If the number of rooms is small it might make sense to publish an array of cursors instead, each cursor in the array corresponding to a single room.
Meteor.publish('messages', function (channel) {
return Messages.find({channel: channel}, {limit: 15});
});
I have no idea how I missed this but apparently I can just do it this way.
Huh.
(channel:channel corresponds to currently set session's name)
I need to keep track of a counter of a collection with a huge number of documents that's constantly being updated. (Think a giant list of logs). What I don't want to do is to have the server send me a list of 250k documents. I just want to see a counter rising.
I found a very similar question here, and I've also looked into the .observeChanges() in the docs but once again, it seems that .observe() as well as .observeChanges() actually return the whole set before tracking what's been added, changed or deleted.
In the above example, the "added" function will fire once per every document returned to increment a counter.
This is unacceptable with a large set - I only want to keep track of a change in the count as I understand .count() bypasses the fetching of the entire set of documents. The former example involves counting only documents related to a room, which isn't something I want (or was able to reproduce and get working, for that matter)
I've gotta be missing something simple, I've been stumped for hours.
Would really appreciate any feedback.
You could accomplish this with the meteor-streams smart package by Arunoda. It lets you do pub/sub without needing the database, so one thing you could send over is a reactive number, for instance.
Alternatively, and this is slightly more hacky but useful if you've got a number of things you need to count or something similar, you could have a separate "Statistics" collection (name it whatever) with a document containing that count.
There is an example in the documentation about this use case. I've modified it to your particular question:
// server: publish the current size of a collection
Meteor.publish("nbLogs", function () {
var self = this;
var count = 0;
var initializing = true;
var handle = Messages.find({}).observeChanges({
added: function (id) {
count++;
if (!initializing)
self.changed("counts", roomId, {nbLogs: count});
},
removed: function (id) {
count--;
self.changed("counts", roomId, {nbLogs: count});
}
// don't care about moved or changed
});
// Observe only returns after the initial added callbacks have
// run. Now return an initial value and mark the subscription
// as ready.
initializing = false;
self.added("counts", roomId, {nbLogs: count});
self.ready();
// Stop observing the cursor when client unsubs.
// Stopping a subscription automatically takes
// care of sending the client any removed messages.
self.onStop(function () {
handle.stop();
});
});
// client: declare collection to hold count object
Counts = new Meteor.Collection("counts");
// client: subscribe to the count for the current room
Meteor.subscribe("nbLogs");
// client: use the new collection
Deps.autorun(function() {
console.log("nbLogs: " + Counts.findOne().nbLogs);
});
There might be some higher level ways to do this in the future.