GameDesign: Difference between revisions
No edit summary |
imported>Blacklads Undo revision 11673 by Ufizavipupu (Talk) |
||
| (141 intermediate revisions by 6 users not shown) | |||
| Line 1: | Line 1: | ||
{{Navigation for Marauroa Top|Internals}} |
|||
{{Navigation for Marauroa Developers}} |
|||
= Basic idea behind GameManager = |
= 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. |
The idea behind the Game Manager is to handle all the "business logic". This Manager decides how to reply to each individual message. |
||
| Line 16: | Line 20: | ||
</pre> |
</pre> |
||
So let's define the reply to each message. |
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 == |
|||
<b>NOTE</b>: This stage has been split in 3 to allow proper secure login. |
|||
<b>NOTE</b>: Explain here how secure login works. |
|||
<pre> |
<pre> |
||
| Line 51: | Line 58: | ||
Postcondition: The state MUST be NULL or STATE_LOGIN_COMPLETE |
Postcondition: The state MUST be NULL or STATE_LOGIN_COMPLETE |
||
and a we have created a PlayerEntry for this player with |
and a we have created a PlayerEntry for this player with a unique clientid. |
||
</pre> |
|||
== Choose character stage == |
|||
<pre> |
|||
Process C2S ChooseCharacter ( STATE_LOGIN_COMPLETE ) |
Process C2S ChooseCharacter ( STATE_LOGIN_COMPLETE ) |
||
Precondition: The state MUST be STATE_LOGIN_COMPLETE |
Precondition: The state MUST be STATE_LOGIN_COMPLETE |
||
| Line 74: | Line 84: | ||
should be completely filled or if the character choise was wrong the state is STATE_LOGIN_COMPLETE |
should be completely filled or if the character choise was wrong the state is STATE_LOGIN_COMPLETE |
||
</pre> |
|||
== Logout stage == |
|||
<pre> |
|||
Process C2S Logout ( STATE_GAME_END ) |
Process C2S Logout ( STATE_GAME_END ) |
||
| Line 93: | Line 107: | ||
Postcondition: Either the same as the input state or the state currently in |
Postcondition: Either the same as the input state or the state currently in |
||
</pre> |
|||
== Perception confirmation stage == |
|||
<pre> |
|||
Process C2S Perception ACK |
Process C2S Perception ACK |
||
| Line 101: | Line 120: | ||
Postcondition: The state is STATE_LOGIN_BEGIN and Timestamp field in |
Postcondition: The state is STATE_LOGIN_BEGIN and Timestamp field in |
||
PlayerContainer is updated. |
PlayerContainer is updated. |
||
</pre> |
|||
== Transfer confirmation stage == |
|||
<pre> |
|||
Process C2S Transfer ACK |
Process C2S Transfer ACK |
||
| Line 116: | Line 140: | ||
</pre> |
</pre> |
||
=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. |
|||
3.a Database Tables and Relationships |
|||
Last updated: 2003/10/07 |
|||
RuntimePlayerEntry is the structure that contains the information about the player while it is online. <br>RuntimePlayerEntry contains: |
|||
The database table relationship schema is: |
|||
* <i>clientid</i><br> |
|||
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. |
|||
Tables: |
|||
* <i>source</i><br> |
|||
--------- |
|||
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. |
|||
* <i>timestamp</i><br> |
|||
Table PLAYER |
|||
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. |
|||
{ |
|||
* <i>storedTimestamp</i><br> |
|||
PK(username) |
|||
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. |
|||
password |
|||
* <i>username</i><br> |
|||
} |
|||
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. |
|||
* <i>choosenCharacter</i><br> |
|||
Table CHARACTERS |
|||
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. |
|||
{ |
|||
* <i>state</i><br> |
|||
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) |
|||
} |
|||
So we can translate this to SQL easily and we should create the following SQL Queries: |
|||
CREATE TABLE player |
|||
( |
|||
id BIGINT PRIMARY KEY NOT NULL, |
|||
username VARCHAR(30) NOT NULL, |
|||
password VARCHAR(30) NOT NULL |
|||
); |
|||
CREATE TABLE characters |
|||
( |
|||
player_id BIGINT NOT NULL, |
|||
charname VARCHAR(30) NOT NULL, |
|||
contents VARCHAR(4096) |
|||
PRIMARY KEY(player_id,character) |
|||
); |
|||
CREATE TABLE loginEvent |
|||
( |
|||
player_id BIGINT NOT NULL, |
|||
address VARCHAR(20), |
|||
timedate TIMEDATE, |
|||
result TINYINT |
|||
); |
|||
CREATE TABLE statistics |
|||
( |
|||
timedate TIMESTAMP, |
|||
bytes_send INTEGER, |
|||
bytes_recv INTEGER, |
|||
players_login INTEGER, |
|||
players_logout INTEGER, |
|||
players_timeout INTEGER, |
|||
players_online INTEGER, |
|||
PRIMARY KEY(timedate) |
|||
); |
|||
CREATE TABLE rpobject |
|||
( |
|||
id INTEGER NOT NULL PRIMARY KEY, |
|||
slot_id INTEGER |
|||
); |
|||
CREATE TABLE rpattribute |
|||
( |
|||
object_id INTEGER NOT NULL, |
|||
name VARCHAR(64) NOT NULL, |
|||
value VARCHAR(255), |
|||
PRIMARY KEY(object_id,name) |
|||
); |
|||
CREATE TABLE rpslot |
|||
( |
|||
object_id INTEGER NOT NULL, |
|||
name VARCHAR(64) NOT NULL, |
|||
slot_id INTEGER AUTO_INCREMENT NOT NULL, |
|||
PRIMARY KEY(slot_id) |
|||
); |
|||
Home |
|||
3.b JDBC Database HOWTO |
|||
Last updated: 2003/10/23 |
|||
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 marauroad.ini file 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 is the field that says what Driver to use. Please refer to your software manual to see the multiple options. |
|||
jdbc_url points to the type and source of the information, for MySQL the string must be as follow: |
|||
jdbc:mysql://[:]/ |
|||
jdbc_user is the username for the database and jdbc_pwd is the password for that username in the database. |
|||
Then simply save the changes and ready. |
|||
Before using the application with the database, you need to create the database itself. So in case of MySQL just open MySQL and write: |
|||
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 will create the tables if they don't exits. |
|||
Home |
|||
3.c PlayerContainer Explained |
|||
Last updated: 2003/10/23 |
|||
PlayerContainer is the data structure that contains all of the needed info about the players to keep the game 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. |
|||
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: |
State is a number expressing the state in which the player is. There are four states: |
||
Have to login |
*Have to login |
||
Login Complete |
*Login Complete |
||
Game begin |
*Game begin |
||
Logout |
*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. |
|||
timestamp of the Last time it was stored |
|||
Timestamp of the Last time it was stored means the timestamp of the moment in which the object was stored at Database. |
|||
As you can see all we need to operate PlayerDatabase is a username and choosenCharacter. So using PlayerEntryContainer we can fully operate it. |
|||
Home |
|||
3.d ClientID generation |
|||
Last updated: 2003/10/23 |
|||
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 |
|||
3.e Synchronization between Game and RP Managers |
|||
Last updated: 2003/12/06 |
|||
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. |
|||
Home |
|||
3.f Persistent Objects ER |
|||
Last updated: ? |
|||
use marauroa; |
|||
drop table rpobject; |
|||
drop table rpattribute; |
|||
drop table rpslot; |
|||
drop table RPObjectInRPSlot; |
|||
CREATE TABLE rpattribute ( |
|||
object_id int(11) NOT NULL default '0', |
|||
name varchar(64) NOT NULL default '', |
|||
value varchar(255) default NULL, |
|||
PRIMARY KEY (object_id,name) |
|||
) TYPE=InnoDB; |
|||
CREATE TABLE rpslot ( |
|||
object_id int(11) NOT NULL default '0', |
|||
name varchar(64) NOT NULL default '', |
|||
slot_id int(11) NOT NULL auto_increment, |
|||
PRIMARY KEY (slot_id) |
|||
) TYPE=InnoDB; |
|||
CREATE TABLE rpobject ( |
|||
id int(11) NOT NULL default '0', |
|||
slot_id int(11) default NULL, |
|||
PRIMARY KEY (id) |
|||
) TYPE=InnoDB; |
|||
Home |
|||
4.a Actions and Objects |
|||
Last updated: 2003/10/07 |
|||
The whole Marauroa system is managed by two main entities, RPAction and RPObject |
|||
Actions |
|||
To express the willing of a client to do something it must send the server a MessageC2SAction message. |
|||
An action is composed of several attributes, an attributed is similar to a variable that has a name and contains a value. |
|||
There are optional and mandatory attributes. If a mandatory attribute is not found, the message is skipped by the RPServerManager. |
|||
Mandatory Actions Attributes are action_id and type. |
|||
The action_id is used to identify the action when a resulting response comes in a perception |
|||
Optional Actions Attributes: (Read "Actions Explained" for more details.) |
|||
Objects |
|||
The containers of information of the whole Marauroa server are RPObjects. An object is composed of several attributes, an attribute is similar to a variable that has a name and contains a value and also it is composed of Slots. A Slot is a container or array of containers that the object has to host other objects inside it. |
|||
Mandatory Object Attributes: id |
|||
Optional Object Attributes: (Read "Objects Explained" for more details.) |
|||
Home |
|||
4.b Object Explained |
|||
Last updated: 2004/04/27 |
|||
Objects are the basic Marauroa data structure, and you should already be familiarized with it because you read the Actions and Object document. |
|||
Marauroa engine only enforce that objects has two attributes: |
|||
- id |
|||
- type |
|||
id is an unique identification for the Object and type is the type of the object aka class, so that you can share attributes for all the instances of the class. |
|||
Also engine give special treatment to two types of attributes: |
|||
- Attributes that begin with ! are completely hidden for all the users but the owner of the object. |
|||
- Attributes that begin with # are completely hidden for all the users. |
|||
NOTE: This document relates to an early specification of Gladiators and is no longer relevant as it refers to specification, but can help you to know how to make the basic steps to define a game. |
|||
So let's point the multiple objects that we are going to have in Marauroa and their attributes and slots. Most object share a few attributes. They are: |
|||
- object_id |
|||
- object_type |
|||
- object_look |
|||
- object_position |
|||
The object_id attribute define the unique identifier of the object in the world. The object_type define the type of object it is, for example: human. The object_look would point to the model used to represent this object. The object_position determine where the object is in the world using a X, Y basis Again, we could add the Z value, but it is totally fake, we calculate Z for adjusting character to terrain. |
|||
Object Avatar The avatar is the entity in Marauroa that represent our character and it is the way of handling everything in that world. This object must have also the next set of attributes: |
|||
- object_name |
|||
Also add too the RP attributes that are still to be defined. The object_name is just the name of the character this object is representing. The object has the next slots: |
|||
- left_hand |
|||
- right_hand |
|||
- backpack |
|||
Object Stone A stone is just a little rock used for testing get, release and exchange actions. |
|||
Object Torch A torch is a small piece of wood in form of stick that is used to light on the near area. This object must have the next set of attributes: |
|||
- object_status |
|||
The object_status define if the torch is ON or OFF. |
|||
Object Chest A wooden box with a lock that can be open or closed and can contain items inside. This object must have the next set of attributes: |
|||
- object_status |
|||
- object_forkey |
|||
The object_status define if the chest is OPEN or CLOSED. The object_forkey define the identifier of the key that opens this lock. The object has the next slots: |
|||
- container |
|||
Object Key A small piece of metal that is placed inside the lock to unlock it. This object must have the next set of attributes: |
|||
- object_key |
|||
The object_key define the identifier of the key. |
|||
Home |
|||
4.c Object and Slots |
|||
Last updated: 2003/11/28 |
|||
As you know Objects can contain inside another object much like you have the keys inside your pocket. The goal of Slots is to provide a richer game play while reducing the number of object in the zone. |
|||
To have these objects inside, we need our hoster object to have slots to place them. One slot can only handle one single object. |
|||
For example a avatar can have: |
|||
- left hand |
|||
- right hand |
|||
- backpack |
|||
- left pocket |
|||
- right pocket |
|||
and we can store objects on these slots. |
|||
Home |
|||
4.d Actions Explained |
|||
Last updated: 2003/10/23 |
|||
NOTE: This section relates to an early specification of Gladiators and is no longer relevant as it refers to specification, but can help you to know how to make the basic steps to define a game. |
|||
Arianne Game is based on several actions that are a MUST for the correct development of the game. Mainly these actions are: |
|||
- Talk |
|||
- Move |
|||
- Object handling |
|||
- Get |
|||
- Release |
|||
- Use |
|||
- Use with |
|||
- Exchange |
|||
These actions have their own attributes so that they can be useful for the game itself. Let's see these attributes and what each action is intended to be for. As common attributes, every action has the type attribute that express the type of Action that it is, and the sourceid express the object that is casting the action, but this attribute is filled on server rather than in client. |
|||
Action Talk This action is intended to express what the character wants to say. As that it is sent from client to server. Action has this next set of attributes: |
|||
- type |
|||
- sourceid |
|||
- talk_text |
|||
The talk_text attribute is for containing the text that send the client. |
|||
Action Move This action is intended to indicate the willing to move from one point to another point. Action has this set of attributes: |
|||
- type |
|||
- sourceid |
|||
- move_to |
|||
- move_speed |
|||
The move_to is a Position , in the system the Z is a fake, as there is no technology ready for using a huge world with multiple levels. The move_speed is also a meaning the increase of positions per turns. This action should take care of: |
|||
- Collision detection |
|||
- Path finding |
|||
Action Get This action is intended to indicate the action of taking one object from floor. Action has this set of attributes: |
|||
- type |
|||
- sourceid |
|||
- get_objectid |
|||
- get_toSlot |
|||
The get_objectid is the RPObject.ID of the object we want to get and the get_toSlot attribute is the Slot of the character that send the action where the object is placed. This action has several limitations: |
|||
- You can't get players |
|||
- The destination slot must be free |
|||
Action Release This action is the contrary of Get, and it is used to drop an object from a Slot. Action has this set of attributes: |
|||
- type |
|||
- sourceid |
|||
- release_fromSlot |
|||
- release_objectid |
|||
The release_fromSlot is the slot that contains the object pointed by release_objectid. The only limitation of this action is that of course the object MUST be there. |
|||
Action Use This action makes the object to perform the expected action from them. This action is a real good candidate for scripting. Action has this set of attributes: |
|||
- type |
|||
- sourceid |
|||
- use_objectid |
|||
The use_objectid attribute point the object to use. By now we will only have one default Use, later we can add extra attributes to the action. |
|||
Action UseWith This action is closely related with Use action, the difference is that we use ObjectA with ObjectB so that the state of ObjectA is modified, ObjectB may be modified as well. Action has this set of attributes: |
|||
- type |
|||
- sourceid |
|||
- usewith_objectidA |
|||
- usewith_objectidB |
|||
The usewith_objectidA refers to the Object that is the center of the action and usewith_objectidB is the catalyzer of the action. For example a door and a key respectively. |
|||
Action Exchange This action is use to exchange an object between two characters. Action has this set of attributes: |
|||
- type |
|||
- sourceid |
|||
- exchange_sourceSlot |
|||
- exchange_destObjectid |
|||
- exchange_destSlot |
|||
The exchange_sourceSlot is the slot of the source character that owns one of the objects and the exchange_destObjecid is the other object and exchange_destSlot is the slot where the other object is. The action MUST be executed by both peers on the same terms so they agree and the action is executed. |
|||
Action replies are placed inside Objects and sent to clients as perceptions, so please refer to the Action reply in "Objects Explained" document. |
|||
Home |
|||
4.e How Perceptions and Actions work |
|||
Last updated: 2004/04/27 |
|||
Actions are sent from client to server in order to make their character to do an action. In order for the client to know the result of the action Server need to send it to client. How? |
|||
On a first try, we used to send client back an action that was the result, but make code really hard because we had to update to different things, perceptions and actions, so the idea appeared intuitively: Why not join action reply and perceptions. |
|||
So the action reply is stored inside each object (that executed the action ) with a set of attributes that determine the action return status and the attributes. This way of working make a bit harder to RPManager but it simplify a lot the creation of new clients. |
|||
See Actions reply in Objects document to know exactly what is returned, but keep in mind that it depends of each particular game. |
|||
Home |
|||
4.f RPManager |
|||
Last updated: 2003/11/23 |
|||
The goal of RP Manager is to handle the whole RP game. This means mainly: |
|||
- run RPActions from clients |
|||
- manage RPWorld |
|||
- control triggers for events |
|||
- control AI |
|||
As you see this is a HUGE class that is complex. So the idea is to split this behavior into smaller subclasses. |
|||
RPManager provides a simple interface to the GameManager for using it: |
|||
- addRPAction |
|||
- addRPObject |
|||
- removeRPObject |
|||
- hasRPObject |
|||
addRPAction simply queues an action for that player to be executed on the next turn. |
|||
addRPObject, removeRPObject and hasRPObject is a interface to manage RPWorld. |
|||
The main outline of RPManager could be: |
|||
forever |
|||
{ |
|||
Procced through every action in this turn |
|||
Build Perception |
|||
Remove timed out players |
|||
Wait for Turn completion. |
|||
Go to Next Turn |
|||
} |
|||
As this part of Marauroa is subject still to development, there are still not many details. |
|||
RPScheduler is the class that handles actions to be queued for each player. All the complexity of Action management should be handled here. |
|||
RuleProcessor is a wrapper class for hide actions code. All the actions code MUST be here, this class also acts as a Action code loader, as some actions are not part of Marauroa, but scripts. |
|||
Home |
|||
4.g Delta perception Algorithm |
|||
Last updated: 2004/04/27 |
|||
The main idea behind the DPA is not to send ALL the objects to client, but only those that has been modified. |
|||
Imagine that we have 1000 objects, and only O1 and O505 are active objects that are modified each turn. Ok? |
|||
Traditional method: |
|||
- Get objects that our player should see ( 1000 objects ) |
|||
- Send them to player ( 1000 objects ) |
|||
- Next turn |
|||
- Get objects that our player should see ( 1000 objects ) |
|||
- Send them to player |
|||
- Next turn |
|||
... |
|||
I hope you see the problem..., we are sending again objects that never changed. |
|||
The delta perception algorithm: |
|||
- Get objects that our player should see ( 1000 objects ) |
|||
- Reduce the list to the modified ones ( 1000 objects ) |
|||
- Store also the objects that are not longer visible ( 0 objects ) |
|||
- Send them to player ( 1000 objects ) |
|||
- Next turn |
|||
- Get objects that our player should see ( 1000 objects ) |
|||
- Reduce the list to the modified ones ( 2 objects ) |
|||
- Store also the objects that are not longer visible ( 0 objects ) |
|||
- Send them to player ( 2 objects ) |
|||
- Next turn |
|||
... |
|||
The next step on delta perception algorithm is pretty clear: delta^2 The idea is to send only what changes of the objects that changed. That why you save even more bandwidth, making perceptions around 20% of the delta perception size. |
|||
The delta^2 algorithm is based on four containers: |
|||
- List of added objects |
|||
- List of modified added attributes of objects |
|||
- List of modified deleted attributes of objects |
|||
- List of deleted objects |
|||
An area really related to DPA is RPZone |
|||
In order to get the Delta perception algorithm to work correctly you have to play with several parameters: |
|||
- RP Turn Duration |
|||
- Relation between TOTAL perception and DELTA perception |
|||
- Know your game :) |
|||
Well, as you should know, MPEG adds a full frame each X number of frames, so it can be used as synchronization in case the file get corrupted. The idea is that if you fail to continue decompressing data, you can always omit things until the next full frame and then when you synced. The idea here is the same, we send a perception on each turn and every X turns we send a full perception so that clients can synchronize, as UDP is not a secure transport and you can also have new clients joining. |
|||
So the point is to adjust turn duration and relation so that the maximum time a client is out of sync is around 20-30 seconds. Of course, depending of the type of game you may lower or increase this value. |
|||
To make perception works it is important to call the modify method on RPZone so this way objects modified are stored in the modified list. If you skip this part the perception system will only work in Total mode. |
|||
Home |
|||
4.h Zones and Worlds |
|||
Last updated: 2003/11/28 |
|||
Objects must be stored somewhere, and we use Zones now to store them. A zone is just a container of Objects. |
|||
In order to improve the modifiability of the Marauroa platform we have made RPZone to be an interface so that if you want you can implement it as you think it is more efficient. |
|||
The actual Marauroa RP Zone consists of several data structures: |
|||
- a HashMap of |
|||
- a List of RPObject |
|||
- a Perception |
|||
The idea is to have already computed in the Zone the perception so saving LOTS of time that would be needed to generate it. All the data structures contain the same objects, but the hashmap is used to fast search of objects using its RPObject.ID, this is the most usual way for locating the object. List is used to improve the time required to build a total perception. And well, we used perception to pre-calculate the delta perception. |
|||
Actually the perception is the same for all the players on the Zone, on the future we could split the zone into several smaller areas each of them would have its own perception |
|||
In order to make perceptions work, you have to manually call modify method so that you notify the zone about changes in a character. |
|||
Home |
|||
4.i Action Reply in Objects |
|||
Last updated: 2003/11/28 |
|||
NOTE: This section relates to an early specification of Gladiators and is no longer relevant as it refers to specification, but can help you to know how to make the basic steps to define a game. |
|||
As you have read in How Perceptions and Actions works, each Object includes the attributes that are result of the action execution. So for each action we will have: |
|||
Action Talk The attributes added to the object are: |
|||
- action_talk_text |
|||
The action_talk_text contains the text the character has been asked to say. This action is always one turn only, and can never fail. |
|||
Action Move The attributes added to the object are: |
|||
- action_move_destination |
|||
- action_move_speed |
|||
- action_move_status |
|||
The action_move_destination is the place where the object wants to go when the action ends, and it is as before a Position denoted by , action_move_speed is the assigned speed for the character by the RP. The action_move_status denotes if the action is complete, incomplete or failed. This action is completed when position=destination and fail if destination is unreachable. |
|||
Action Get The attributes added to the object are: |
|||
- action_get_status |
|||
The action_get_status denotes if the action is complete or failed This action is always one turn long. |
|||
Action Release The attributes added to the object are: |
|||
- action_release_status |
|||
The action_release_status denotes if the action is complete or failed This action is always one turn long. |
|||
Action Use The attributes added to the object are: |
|||
- action_use_status |
|||
- (Specific attributes depending on the object) |
|||
The action_use_status denotes if the action is complete, incomplete or failed. Usually this action modify attributes of the RPObject instead of adding new attributes. |
|||
Action UseWith The attributes added to the object are: |
|||
- action_usewith_status |
|||
- (Specific attributes depending on the object) |
|||
The action_usewith_status denotes if the action is complete, incomplete or failed. Usually this action modify attributes of the RPObject instead of adding new attributes. |
|||
Action Exchange The attributes added to the object are: |
|||
- action_exchange_status |
|||
- action_exchange_timeout |
|||
The action_exchange_status denotes if the action is complete, incomplete or failed. This action last several turns until both peers accept or the timeout elapses. |
|||
On every object that executed an action you will have a status_= that explains what happened with the action. |
|||
Home |
|||
4.j Attributes |
|||
Last updated: 2003/12/22 |
|||
Attributes are the containers of information in Marauroa. They are designed to contains strings, because they are the most flexible. |
|||
We have also added support for List of Strings. They are encoded as: name=[value|...|value] |
|||
Try to keep the names as short as possible. |
|||
Home |
|||
4.k Classes of Objects Explained |
|||
Last updated: 2004/07/13 |
|||
Classes of Objects are the basic way of structuring Marauroa data structures. |
|||
A class defines types of the attributes and its visibility and gives it an internal code that is used to speed up searchs and save bandwidth. You can base a class on another, this feature is known as inheritance. |
|||
The data types available are: |
|||
- Strings |
|||
- Short strings ( up to 255 bytes ) |
|||
- Integers ( 4 bytes ) |
|||
- Shorts ( 2 bytes ) |
|||
- Byte ( 1 byte ) |
|||
- Flag ( it is a binary attribute ) |
|||
Attributes can also be visible if clients see them when they change, or invisible if clients can't see them. |
|||
2.d GameManager |
|||
Last updated: 2003/09/26 |
|||
The main idea behind GameManager is to handle all the "business logic", so this Manager decides about every reply to each individual message. |
|||
The logic is something like this: |
|||
GameManager |
|||
{ |
|||
NetworkManager read Message |
|||
switch(Message type) |
|||
{ |
|||
case ...; |
|||
} |
|||
} |
|||
So let's define the reply to each message. Before explaining each message processing, let's clarify that the best way of modelling this system is using finite automates, where based on the input we change state 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 are not 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 PlayerEntry for this player with an unique correct clientid. |
|||
Process C2S ChooseCharacter ( STATE_LOGIN_COMPLETE ) |
|||
Precondition: The state MUST be STATE_LOGIN_COMPLETE |
|||
if exist in database character |
|||
{ |
|||
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 |
|||
being completely filled now or state is STATE_LOGIN_COMPLETE |
|||
Process C2S Logout ( STATE_GAME_END ) |
|||
Precondition: The state can be whatever 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 is the same that the input state or the state is |
|||
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. |
|||
!!! Database Tables and Relationships |
|||
Last updated: 2003/10/07 |
|||
The database table relationship schema is: |
|||
<verbatim> |
|||
Tables: |
|||
--------- |
|||
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) |
|||
} |
|||
</verbatim> |
|||
So we can translate this to SQL easily and we should create the following SQL Queries: |
|||
<verbatim> |
|||
CREATE TABLE player |
|||
( |
|||
id BIGINT PRIMARY KEY NOT NULL, |
|||
username VARCHAR(30) NOT NULL, |
|||
password VARCHAR(30) NOT NULL |
|||
); |
|||
CREATE TABLE characters |
|||
( |
|||
player_id BIGINT NOT NULL, |
|||
charname VARCHAR(30) NOT NULL, |
|||
contents VARCHAR(4096) |
|||
PRIMARY KEY(player_id,character) |
|||
); |
|||
CREATE TABLE loginEvent |
|||
( |
|||
player_id BIGINT NOT NULL, |
|||
address VARCHAR(20), |
|||
timedate TIMEDATE, |
|||
result TINYINT |
|||
); |
|||
CREATE TABLE statistics |
|||
( |
|||
timedate TIMESTAMP, |
|||
bytes_send INTEGER, |
|||
bytes_recv INTEGER, |
|||
players_login INTEGER, |
|||
players_logout INTEGER, |
|||
players_timeout INTEGER, |
|||
players_online INTEGER, |
|||
PRIMARY KEY(timedate) |
|||
); |
|||
CREATE TABLE rpobject |
|||
( |
|||
id INTEGER NOT NULL PRIMARY KEY, |
|||
slot_id INTEGER |
|||
); |
|||
CREATE TABLE rpattribute |
|||
( |
|||
object_id INTEGER NOT NULL, |
|||
name VARCHAR(64) NOT NULL, |
|||
value VARCHAR(255), |
|||
PRIMARY KEY(object_id,name) |
|||
); |
|||
CREATE TABLE rpslot |
|||
( |
|||
object_id INTEGER NOT NULL, |
|||
name VARCHAR(64) NOT NULL, |
|||
slot_id INTEGER AUTO_INCREMENT NOT NULL, |
|||
PRIMARY KEY(slot_id) |
|||
); |
|||
</verbatim> |
|||
!!! JDBC Database HOWTO |
|||
Last updated: 2003/10/23 |
|||
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 marauroad.ini file and edit the next fields |
|||
<verbatim> |
|||
marauroa_DATABASE=JDBCPlayerDatabase |
|||
jdbc_class=com.mysql.jdbc.Driver |
|||
jdbc_url=jdbc:mysql://localhost/marauroa |
|||
jdbc_user=marauroa_dbuser |
|||
jdbc_pwd=marauroa_dbpwd |
|||
</verbatim> |
|||
jdbc_class is the field that says what Driver to use. Please refer to your software manual to see the multiple options. |
|||
jdbc_url points to the type and source of the information, for MySQL the string must be as follow: |
|||
jdbc:mysql://[:]/ |
|||
jdbc_user is the username for the database and jdbc_pwd is the password for that username in the database. |
|||
Then simply save the changes and ready. |
|||
Before using the application with the database, you need to create the database itself. So in case of MySQL just open MySQL and write: |
|||
<verbatim> |
|||
create database marauroa; |
|||
grant all on marauroa.* to marauroa_dbuser@localhost identified by 'marauroa_dbpwd'; |
|||
</verbatim> |
|||
The rest of code is handled by the server itself, and will create the tables if they don't exits. |
|||
!!! Player Container Explained |
|||
Last updated: 2003/10/23 |
|||
~PlayerContainer is the data structure that contains all of the needed info about the players to keep the game 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. |
|||
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. |
|||
timestamp of the Last time it was stored |
|||
Timestamp of the Last time it was stored means the timestamp of the moment in which the object was stored at Database. |
|||
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 |
|||
Last updated: 2003/10/23 |
|||
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 |
|||
Last updated: 2003/12/06 |
|||
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. |
|||
When we create the entity, by default, the state is <b>Have to login</b>. Once you have logged in correctly, the state changes to <b>Login Complete</b> and once the player has chosen a Character it changes to <b>game begin</b>. The logout state is pretty trivial :) |
|||
So we need to synchronize game and RP manager. |
|||
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, [[RolePlayingDesign#Perceptions | click here]] ) |
|||
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. |
|||
*perception counter<br> |
|||
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. |
|||
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<br> |
|||
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<br> |
|||
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. |
|||
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. |
|||
Home |
|||
==ClientID generation== |
|||
!!! Persistent Objects ER |
|||
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. |
|||
Last updated: ? |
|||
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. |
|||
use marauroa; |
|||
<verbatim> |
|||
drop table rpobject; |
|||
drop table rpattribute; |
|||
drop table rpslot; |
|||
drop table RPObjectInRPSlot; |
|||
==Synchronization between Game and RP Managers== |
|||
CREATE TABLE rpattribute ( |
|||
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. |
|||
object_id int(11) NOT NULL default '0', |
|||
name varchar(64) NOT NULL default '', |
|||
value varchar(255) default NULL, |
|||
PRIMARY KEY (object_id,name) |
|||
) TYPE=InnoDB; |
|||
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). |
|||
CREATE TABLE rpslot ( |
|||
object_id int(11) NOT NULL default '0', |
|||
name varchar(64) NOT NULL default '', |
|||
slot_id int(11) NOT NULL auto_increment, |
|||
PRIMARY KEY (slot_id) |
|||
) TYPE=InnoDB; |
|||
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]] |
|||
CREATE TABLE rpobject ( |
|||
id int(11) NOT NULL default '0', |
|||
slot_id int(11) default NULL, |
|||
PRIMARY KEY (id) |
|||
) TYPE=InnoDB; |
|||
Latest revision as of 02:49, 24 November 2010
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.