GameDesign

From Arianne
Jump to navigation Jump to search



Basic idea behind GameManager

The idea behind the Game Manager is to handle all the "business logic". This Manager decides how to reply to each individual message.

GameManager

The logic is similar to this:

GameManager
  {
  NetworkManager read Message

  switch(Message type)
    {
    case ...;
    }
  }

So let's define the reply to each message. First, let's clarify that the best way of modelling this system is using finite automates, (a finite state machine) where, based on the input, we change the state we are currently in and produce an output.

Login stage

NOTE: This stage has been split in 3 to allow proper secure login. NOTE: Explain here how secure login works.

Process C2S Login ( STATE_BEGIN_LOGIN )
  Precondition: The state MUST be NULL

  Test if there is room for more players.
  if there is no more room
    {
    reply S2C Login NACK( SERVER_FULL )
    state = NULL
    }

  if check username, password in database is correct
    {
    create clientid
    add PlayerEntry
    notify database

    reply S2C Login ACK

    get characters list of the player
    reply S2C CharacterList

    state = STATE_LOGIN_COMPLETE
    }
  else
    {
    notify database

    reply S2C Login NACK( LOGIN_INCORRECT )
    state = NULL
    }

  Postcondition: The state MUST be NULL or STATE_LOGIN_COMPLETE
    and a we have created a PlayerEntry for this player with a unique clientid.

Choose character stage

Process C2S ChooseCharacter ( STATE_LOGIN_COMPLETE )
  Precondition: The state MUST be STATE_LOGIN_COMPLETE

  if character exists in database
    {
    add character to Player's PlayerEntry
    add character to game
    reply S2C Choose Character ACK

    state = STATE_GAME_BEGIN
    }
  else
    {
    reply S2C Choose Character NACK
    state = STATE_LOGIN_COMPLETE
    }

  Postcondition: The state MUST be STATE_GAME_BEGIN and the PlayerStructure
    should be completely filled or if the character choise was wrong the state is STATE_LOGIN_COMPLETE

Logout stage


Process C2S Logout ( STATE_GAME_END )
  Precondition: The state can be anything but STATE_LOGIN_BEGIN

  if( rpEngine allows player to logout )
    {
    reply S2C Logout ACK
    state = NULL

    store character in database
    remove character from game
    delete PlayerEntry
    }
  else
    {
    reply S2C Logout NACK
    }

  Postcondition: Either the same as the input state or the state currently in

Perception confirmation stage


Process C2S Perception ACK
  Precondition: The state must be STATE_LOGIN_BEGIN

  notify that the player received the perception.

  Postcondition: The state is STATE_LOGIN_BEGIN and Timestamp field in
  PlayerContainer is updated.

Transfer confirmation stage


Process C2S Transfer ACK
  Precondition: The state must be STATE_LOGIN_BEGIN

  foreach content waiting for this player
    {
    if client acked it 
      {
      send content to client
      } 
    }

  Postcondition: The state is STATE_LOGIN_BEGIN and the content waiting for player is clear.

PlayerContainer Explained

PlayerContainer is the data structure that contains all of the information about the players while the game is running.

It consists of a list of RuntimePlayerEntry objects and is heavily linked with the PlayerDatabase, so we can hide the complexity to GameManager. By making PlayerDatabase hidden by PlayerContainer we achieve the illusion that managing the runtime behavior we modify automatically the permanent one.

RuntimePlayerEntry is the structure that contains the information about the player while it is online.
RuntimePlayerEntry contains:

  • clientid

Clientid is the field that indexes players in the server. See the documentation about clientid generation to understand what they are and how they are generated.

  • source

Source is the IPv4 address of the client. This is used to determine if the message is really coming from the client or another person trying to impersonate it.

  • timestamp

Timestamp is used to determine if a client has timed out in which case it is only wasting resources on the server. As you may already know, UDP is not a delivery-guaranteed protocol, so we need to check for dead clients ourselves. Note that this only indicates that the player timed out and it doesn't apply any kind of measures on them.

  • storedTimestamp

storeTimestamp is used to determine when the player was last stored in the database. We don't store each time the player info changes as this would obviously be very CPU time consuming. Instead we cached the changes and store them only every 5 minutes.

  • username

Username is filled in at runtime with a Login event. If we store the username here we are able to use the database from PlayerContainer thus by knowing the clientid we can also now know the username without having to look to the actual database.

  • choosenCharacter

choosenCharacter is filled in at runtime with a ChooseCharacter event. If we store the information here we are able to use the database from PlayerContainer and hence by knowing the clientid we also know the choosenCharacter without having to refer to the actual database.

  • state

State is a number expressing the state in which the player is. There are four states:

  • Have to login
  • Login Complete
  • Game begin
  • Logout

When we create the entity, by default, the state is Have to login. Once you have logged in correctly, the state changes to Login Complete and once the player has chosen a Character it changes to game begin. The logout state is pretty trivial :)

The idea is that some operations are only allowed in certain states, so the state property stores which state they are in to make validating actions easier. ( To read about Perceptions, click here )

  • perception counter

The Perception counter is used to keep an incremental count of the perceptions sent so that the client can see if it gets out of sync.

  • perception Previous RPObject

Perception previous RPObject is the RPObject that was sent on the last perception. Using this we can track changes to a RPObject without disturbing the rest of the system.

  • perception Out of Sync

This flag indicates to the server if the player has become out of sync. This allows us to re-sync it as soon as possible.

Hence, all we need to operate PlayerDatabase is a username and choosenCharacter. So using PlayerEntryContainer we can fully operate it.

ClientID generation

Each client MUST have a session id to prevent another player impersonating it. sessionid must be of short or int size to make guessing the ID much harder.

To make it even more secure, clientids are generated randomly for each player with the only condition that two different players MUST have two different clientids.


Synchronization between Game and RP Managers

Why bother with this? Well, imagine that a player logs out while the perception is being built, it will no longer be accessible by the RP Manager when it expects the object to be there, or if RPManager tries to remove a player which has already been removed, these situations are very serious as they will probably make the server fail.

So we must synchronize the Game and RP Managers.

The idea behind the solution is that the each manger requests access to the PlayerEntryContainer via a central mutex (a mutex is a syncronisation element attached to a resource, which can be owned by one task at any point in time. If the mutex is owned already when a task tries to access the object protected by it then the mutex will inform the task that it doesn't have access at this point in time to the object).

There are two types of accesses, read accesses and write accesses. Note that two readers can access an object in parallel but only one write can happen at the same time. In GameManager there are only Write actions, as this manager only modifies the state of the PlayerContainer. However, in the RP manager we have both Reads, when we build the perceptions, and Writes when the manager removes idle players. Hence here we have two different locks.

Marauroa