GameDesign: Difference between revisions

From Arianne
Jump to navigation Jump to search
imported>StephenIerodiaconou
No edit summary
imported>StephenIerodiaconou
No edit summary
(No difference)

Revision as of 09:49, 5 February 2005

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.

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 an unique correct clientid.

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


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


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.


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.

Basic idea behind Database storage

Database Tables and Relationships

The database table relationship schema is:

Table PLAYER
  {
  PK(username)
  password
  }

Table CHARACTERS
  {
  PK(character)
  content
  }

Table LOGIN_EVENT
  {
  PK(id)
  address
  timedate
  result
  }

Table STATISTICS
  (
  PK(timedate)

  bytes_send
  bytes_recv

  players_login
  players_logout
  players_timeout
  players_online
  )

Table RPOBJECT
  (
  PK(id)
  slot_id
  )

Table RPATTRIBUTE
  (
  PK(object_id)
  PK(name)
  value
  )

Table RPSLOT
  (
  object_id
  name
  PK(slot_id)
  )

Relationships:

Relationship PLAYER_CHARACTERS
  {
  PK(player_username)
  PK(characters_character)
  }

Relationship PLAYER_LOGIN_EVENT
  {
  PK(player_username)
  PK(login_event_id)
  }

Translate this to SQL easily and you have the SQL schema of Marauroa


JDBC Database HOWTO

JDBC technology is an API that lets you access virtually any tabular data source from the Java programming language. It provides cross-DBMS connectivity to a wide range of SQL databases, and now, with the new JDBC API, it also provides access to other tabular data sources, such as spreadsheets or flat files.

JDBCPlayerDatabase is anyway not database independent; on the Player table we are using AUTOINCREMENT that is a unique keyword of MySQL that is not part of the SQL standard.

You need to download MySQL Connector/J in order to get it to run.
http://www.mysql.com/downloads/api-jdbc-stable.html

To configure Marauroa to work with a JDBC source we need to modify the configuration of the JDBC Connection.

So open the configuration file marauroad.ini (or whatever you choose to name it) and edit the next fields

marauroa_DATABASE=JDBCPlayerDatabase

jdbc_class=com.mysql.jdbc.Driver
jdbc_url=jdbc:mysql://localhost/marauroa
jdbc_user=marauroa_dbuser
jdbc_pwd=marauroa_dbpwd
  • jdbc_class: This field tells the engine what Driver to use for database access. Please refer to your software manual to see the multiple options.
  • jdbc_url: This points to the type and source of the information, for MySQL the string must be as follows:
    • jdbc:mysql://ip:database_name/
  • jdbc_user: This is the username for the database
  • jdbc_pwd: This is the password for that username in the database.

Now, simply save the changes and your configuration file is ready.

Before using the application with the database, you need to create the database itself. So, with MySQL just run MySQL and enter:

create database marauroa;
grant all on marauroa.* to marauroa_dbuser@localhost identified by 'marauroa_dbpwd';

The rest of code is handled by the server itself, and it will create the tables if they don't exist.

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 of the player while it is online.
RuntimePlayerEntry contains:

  • clientid

Clientid is the field in charge of indexing players in the server. See the document about clientid generation to understand what they are and how they are generated.

  • source

Source is the IPv4 address of the client, so that we can determine if the message is really coming from client or another person trying to impersonate it.

  • timestamp

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

  • storedTimestamp

storeTimestamp is used to determine when it was the last time that this player was stored on database, as storing for each change will be very CPU consuming so we cached it and store each 5 minutes.

  • username

Username is filled in at runtime with a Login event so that we are able to use the database from PlayerContainer, This way by knowing the clientid we can also know the username.

  • choosenCharacter

choosenCharacter is filled in at runtime with a ChooseCharacter event so that we are able to use the database from PlayerContainer, This way by knowing the clientid we can also know the choosenCharacter.

  • 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 entry it is by default Have to login. Once you have logged in correctly, we change state to Login Complete, and once you have chosen a Character we change it to game begin. The logout state is trivial :)

The main idea is that some operations are only allowed in one state, so we can more easily control it with the state property.

  • perception counter

Perception counter is used for having a incremental counter of the perceptions send, so that 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, so we can track * changes on our RPObject without disturbing the rest of the system.

  • perception Out of Sync

This flag indicates if the player notified us that it was out of sync, so we can re sync it as soon as possible.


As you can see 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 avoid another player to impersonate it. sessionid must be a short or int to make harder for an attacker to guess it.

To make it really fun, clientids are generated randomly for each player with the unique condition that two different players MUST have two different clientids. Home

Synchronization between Game and RP Managers

Why bother with it? Well, imagine that a player logs out when the perception is being built, it will no longer be accessible for the RP Manager, when it really expects the object to be there. Or a removed player that is removed too by RP Manager. That is a really serious problem, as it will make the server fail.

So we need to synchronize game and RP manager.

The idea is that they request to a central mutex access to the PlayerEntryContainer, and that mutex is the one that decide how the access is done.

We need to differentiate between the two types of accesses, read access and write access. We can have without problems two readers accessing in parallel, but we can only have one write at the same time modifying the stuff.

Whatever action we choose in GameManager they are Write actions, as the modify the state of the PlayerContainer, but in RP we have two parts, one that build the perceptions that is read only and one that removes idle players that is write, so we must apply two different locks there.