1
votes

I have a game built on Meteor framework. One game document is something like this:

{
...
participants : [
    {
    "name":"a",
    "character":"fighter",
    "weapon" : "sword"
    },
    {
    "name":"b",
    "character":"wizard",
    "weapon" : "book"
    },
   ...
  ],
...
}

I want Fighter character not to see the character of the "b" user. (and b character not to see the a's) There are about 10 fields like character and weapon and their value can change during the game so as the restrictions.

Right now I am using Session variables not to display that information. However, it is not a very safe idea. How can I subscribe/publish documents according to the values based on characters?

There are 2 possible solutions that come to mind: 1. Publishing all combinations for different field values and subscribing according to the current state of the user. However, I am using Iron Router's waitOn feature to load subscriptions before rendering the page. So I am not very confident that I can change subscriptions during the game. Also because it is a time-sensitive game, I guess changing subscriptions would take time during the game and corrupt the game pleasure.

  1. My problem right now is the user typing

    Collection.find({})

to the console and see fields of other users. If I change my collection name into something difficult to find, can somebody discover the collection name? I could not find a command to find collections on the client side.

1
Are you okay with the character field not being sent to the client for any of the objects in the array?challett
It's not clear what your question is. For example do you merely want a player to be able to know what their own character and weapon are? If so, then yes. You can control subscriptions to other collections based on the user's name (it would be better if you tracked _ids however).Michel Floyd
A character is free to see its own fields, however they should not see the other users's fields (they can see some fields but not all). an these restrictions based on fields can change during the game. So I want to subscribe based on a field value not on user name.Giray

1 Answers

1
votes

The way this is usually solved in Meteor is by using two publications. If your game state is represented by a single document you may have problem implementing this easily, so for the sake of an example I will temporarily assume that you have a Participants collection in which you're storing the corresponding data.

So anyway, you should have one subscription with data available to all the players, e.g.

Meteor.publish('players', function (gameId) {
  return Participants.find({ gameId: gameId }, { fields: {
    // exclude the "character" field from the result
    character: 0
  }});
});

and another subscription for private player data:

Meteor.publish('myPrivateData', function (gameId) {
  // NOTE: not excluding anything, because we are only
  //       publishing a single document here, whose owner
  //       is the current user ...
  return Participants.find({
    userId: this.userId,
    gameId: gameId,
  }); 
});

Now, on the client side, the only thing you need to do is subscribe to both datasets, so:

Meteor.subscribe('players', myGameId);
Meteor.subscribe('myPrivateData', myGameId);

Meteor will be clever enough to merge the incoming data into a single Participants collection, in which other players' documents will not contain the character field.

EDIT

If your fields visibility is going to change dynamically I suggest the following approach:

  • put all the restricted properties in a separated collection that tracks exactly who can view which field
  • on client side use observe to integrate that collection into your local player representation for easier access to the data

Data model

For example, the collection may look like this:

PlayerProperties = new Mongo.Collection('playerProperties');
/* schema:

     userId    : String
     gameId    : String
     key       : String
     value     : *
     whoCanSee : [String]
*/

Publishing data

First you will need to expose own properties to each player

Meteor.publish('myProperties', function (gameId) {
  return PlayerProperties.find({
    userId: this.userId,
    gameId: gameId
  });
});

then the other players properties:

Meteor.publish('otherPlayersProperties', function (gameId) {
  if (!this.userId) return [];
  return PlayerProperties.find({
    gameId: gameId,
    whoCanSee: this.userId,
  });
});

Now the only thing you need to do during the game is to make sure you add corresponding userId to the whoCanSee array as soon as the user gets ability to see that property.

Improvements

In order to keep your data in order I suggest having a client-side-only collection, e.g. IntegratedPlayerData, which you can use to arrange the player properties into some manageable structure:

 var IntegratedPlayerData = new Mongo.Collection(null);
 var cache = {};

 PlayerProperties.find().observe({
   added: function (doc) {
     IntegratedPlayerData.upsert({ _id : doc.userId }, {
       $set: _.object([ doc.key ], [ doc.value ])  
     });
   },
   changed: function (doc) {
     IntegratedPlayerData.update({ _id : doc.userId }, {
       $set: _.object([ doc.key ], [ doc.value ])  
     });
   },
   removed: function (doc) {
     IntegratedPlayerData.update({ _id : doc.userId }, {
       $unset: _.object([ doc.key ], [ true ])
     });
   }
 });

This data "integration" is only a draft and can be refined in many different ways. It could potentially be done on server-side with a custom publish method.