ShiVa Environments pt.2: Game Tab and Remote Environments – ShiVa Engine

ShiVa Environments pt.2: Game Tab and Remote Environments

This week, we are going to delve into a way to store your environment save data on a remote server using PHP and MySQL. But first, we will have a closer look at the Environment tab in the Game Editor.

Environment Tab

As you might have noticed, the Game Module features an Environment tab, which can perform 2 functions: create environments without the need for Lua code, and also monitor changes in the environment in real-time.

Create game environment

The Environment tab can create numbers, booleans and strings for your environment, the same types you have access to via Lua code.

Once entered, your variables will all appear in a big list.

Real-time monitoring

When your game is running, you can press the gear icon to monitor changes in the environment in real-time. The tab will not only show your predefined values, but also every value you added via code, like our potions code from pt.1, which makes this tab a very helpful tool.

Remote Environments

Instead of saving and loading your environments to and from STS files on a local drive, you can also send your data to a remote server using the environment API. On the ShiVa side, there is very little Lua code you have to change, however the receiving web server requires a bit of work. Due to their popularity, we will walk you through a typical PHP/MySQL setup today.

Test server

For convenience and security reasons, we will be sending all our requests to a locally hosted test server. On Windows, XAMPP/Apachefriends is a very easy to use solution, while MAMP does something very similar of macOS. On the Linux side, your distribution probably came with everything you need already.

ShiVa Lua

Distant environments must be named and also have a valid target URL. The target is a PHP script that we yet have to write, located on our test server.

  1. application.setCurrentUserEnvironmentName ( "FromShiVa" )
  2. application.setCurrentUserEnvironmentURL ( "http://localhost/shiva/receive.php" )

ShiVa saving

Saving is a blind-fire operation. If ShiVa is able to connect to the URL, you will get an OK, but you will not receive any other feedback (error messages, confirmation, etc.)

  1. if (not application.saveCurrentUserEnvironment ( )) then
  2. log.warning ( "saving error!" )
  3. end

ShiVa loading

Loading is an asynchronous operation, which means you cannot rely on your environment variables being available immediately after a call to application.loadCurrentUserEnvironment. Instead, you have to use a polling state, similar to downloading STKs, caching resources, or receiving XML data.

onEnter loads the data:

  1. --------------------------------------------------------------------------------
  2. function envtestMain.loadRemoteEnv_onEnter ( )
  3. --------------------------------------------------------------------------------
  4.  
  5. application.loadCurrentUserEnvironment ( "Prepared" )
  6.  
  7. --------------------------------------------------------------------------------
  8. end
  9. --------------------------------------------------------------------------------

onLoop checks the status of one of your variables:

  1. --------------------------------------------------------------------------------
  2. function envtestMain.loadRemoteEnv_onLoop ( )
  3. --------------------------------------------------------------------------------
  4.  
  5. local sName = "user.nVar7"
  6. log.message ( "Checking on " ..sName )
  7.  
  8. local nStatus = application.getCurrentUserEnvironmentVariableStatus ( sName )
  9.  
  10. if ( nStatus == application.kStatusReady ) then
  11. log.message ( "the variable " .. sName .. " is available !" )
  12. -- loading success! do something here...
  13. this.idle ( )
  14.  
  15. elseif ( nStatus == application.kStatusLoading ) then
  16. -- variable is currently being loaded
  17. log.message ( "currently loading " .. sName )
  18.  
  19. elseif ( nStatus == application.kStatusSaving ) then
  20. -- variable is currently being saved
  21. log.message ( "currently saving " .. sName )
  22.  
  23. elseif ( nStatus == application.kStatusNone ) then
  24. -- variable is not available or does not exist
  25. log.message ( "the variable " .. sName .. " is not available or do not exist !" )
  26. this.idle ( )
  27. end
  28.  
  29. --------------------------------------------------------------------------------
  30. end
  31. --------------------------------------------------------------------------------

Database layout

We want to store all our data inside a database. To create our table layout, we are going to use PHPMyAdmin, which is a popular graphical tool intended to handle the administration of MySQL over the Web. It is included in xampp, mamp, most popular Linux distros as well as most commercial hosting services.

Create table

First, you have to create a new database and decide on a collation. I named my database "shivaEnvironments" and went for a general utf8 collation, since I do not want to be limited to English or European character sets.

Inside that new database, you will have to create a new table for all your save data. The following is the minimum possible layout.

You will need:
- auto-increment column "id" for management and performance
- (tinyint/varchar) playerID ( = ShiVa requirement)
- (varchar) environment name ( = ShiVa requirement)
- (tinyint/varchar) type ( = variable type ID)
- (varchar) name ( = variable name)
- (varchar) value ( = variable value)

Your table should now look like this:

From here, you could expand the layout by adding IPs, passwords, session names etc.

Unique Key

In order for the data update process to work, we need to define a UNIQUE key from at least 3 the columns, or more if your layout requires you to. In our example, we need to combine name, envName and playerID into a UNIQUE index:

Your table structure should now look like this, with key symbols and a new entry in the "indexes" list:

UNIQUE keys tell the database which columns form a logical unit. There will be many playerIDs in this table, many identical variable names per player, and possibly multiple environment names, but there will always only be one variable for one player under one environment name. We will rely on this property later in our PHP code.

Variable types

ShiVa environment variables can be booleans, strings or numbers. To identify them, ShiVa uses and index system:

  1. // TYPE selection:
  2. // 1 - number
  3. // 2 - string
  4. // 3 - boolea

Prepared data

You can prepare data in your table, like so:

Thanks to the UTF8 collation, we can add the Japanese characters without any issues.

The receiving PHP file

In our example, your ShiVa messages will be received by a PHP file. This file will handle both saving and loading of data. All replies will be in XML form, so first let us make sure we conform to the XML standard:

  1. <?xml version="1.0"; encoding="utf-8"?>

Since we will be dealing with a number of undefined $_POST indices, we need to suppress the useless NOTICE messages:

  1. error_reporting(E_ALL & ~E_NOTICE);

Now we are ready to connect to the database.

Database connection

We will be using mysqli in the object-oriented style. Hopefully, your login credentials will look different and you won't log in as root and without a password! Remember this is a local test server.

  1. // MySQL connection
  2. $servername = "localhost";
  3. $username = "root";
  4. $password = "";
  5. $db = "shivaenvironments";
  6.  
  7. // Create connection
  8. $connection = new mysqli($servername, $username, $password, $db);
  9. $connection->set_charset("utf8");
  10.  
  11. // Check connection
  12. if ($connection->connect_error) {
  13. die("Connection failed: " . $connection->connect_error);
  14. }

set_charset is very important. Set it to the correct encoding, otherwise your non-English strings will be badly garbled.

Loading or saving?

The PHP file needs to decide whether you want to load or save some data. Depending on the request, $_POST will be filled with different keys. If one of the distinctive

  1. $mode = "UNDEFINED";
  2. /* possible modes:
  3. LOADALL
  4. LOADVAR
  5. SAVE
  6. */
  7.  
  8. //SAVE
  9. $savePlayer = $_POST['SAVE_PLAYER']; // ID of the current user
  10. $saveEnviro = $_POST['SAVE_ENVNAME']; // name of the current environment
  11. // LOAD
  12. $receivedPlayer = $_POST['PLAYER']; // ID of the current user
  13. $receivedEnviro = $_POST['ENVNAME']; // name of the current environment
  14. $receivedVariab = $_POST['VAR']; // var to load, accepts wildcards
  15.  
  16. if (!empty($saveEnviro)) {
  17. $mode="SAVE";
  18. } else if (!empty($receivedVariab)) {
  19. $mode="LOADVAR";
  20. } else {
  21. $mode="LOADALL";
  22. }

Note: The names of these keys are different from ShiVa 1.x releases.

SAVE mode

Once you have determined that you are in SAVE mode, you need to unroll the $_POST array and determine the type of the variable. Before storing anything into a database, make sure you always escape the strings (real_escape_string) for security reasons.

  1. if ($mode == "SAVE") {
  2. // every variable comes as a separate POST variable
  3. foreach ($_POST as $k => $v) {
  4.  
  5. // escape for "SAVE_" variables
  6. if ($k == "SAVE_PLAYER" || $k == "SAVE_ENVNAME") continue;
  7.  
  8. // TYPE selection:
  9. // 1 - number
  10. // 2 - string
  11. // 3 - boolean
  12.  
  13. $_type = 2;
  14. if (is_numeric($v)) {
  15. $_type = 1;
  16. } elseif ($v == "true" || $v == "false") {
  17. $_type = 3;
  18. } else {
  19. $v = $connection->real_escape_string($v);
  20. }
  21.  
  22. // create SQL query - hopefully you set your UNIQUE key!
  23. $query = "INSERT INTO savedata (playerID, envName, `type`, `name`, `value`) VALUES (" .$savePlayer .",'" .$saveEnviro ."'," .$_type .",'" .$k ."','" .$v ."') ON DUPLICATE KEY UPDATE `value`='" .$v ."'";
  24. $connection->query($query);
  25. }
  26. // end SAVE mode
  27. }

Because we added a proper UNIQUE key in our database, the INSERT INTO ... ON DUPLICATE KEY UPDATE query will only create new rows when a key is not already present, otherwise it will merely update the existing value.

This is also a good point to check whether your encodings match. If you remembered to set both your database and mysqli character_set to UTF8, you will have no problems with non-English characters:

Otherwise you might end up with something like this:

LOAD mode

The goal of loading is the generation of an XML that looks something like this:

  1. <?xml version="1.0" encoding="utf-8"?>
  2. <R>
  3. <VE i="0" n="envName">
  4. <V t="1" n="myNumber">12.00000</V>
  5. <V t="2" n="myString">Hello there!</V>
  6. </VE>
  7. </R>

Inside a Root R tag (only required for ShiVa 2.0 beta 9 and below), you define a Variable Environment VE tag with the playerID i and environment name n attributes. The children with the V tag are individual variables with their name n and type t attributes.

  1. if ($mode == "LOADVAR" || $mode == "LOADALL") {
  2. /* print root element */
  3. echo '<R>'; // required for < s20.b10
  4. echo '<VE i="' .$receivedPlayer .'" n="' .$receivedEnviro .'">';
  5.  
  6. // look through database for the variable, e.g. MySQL:
  7. if ($mode == "LOADVAR") {
  8. $query = "SELECT * FROM savedata WHERE playerID='" .$receivedPlayer ."' AND envName='" .$receivedEnviro ."' AND name='" .$receivedVariab ."'";
  9. } else {
  10. $query = "SELECT * FROM savedata WHERE playerID='" .$receivedPlayer ."' AND envName='" .$receivedEnviro ."'";
  11. }
  12.  
  13. // actual query here, e.g. ...
  14. $result = $connection->query($query);
  15.  
  16. // .. then assemble output:
  17. if ($result->num_rows > 0) {
  18. // output data of each row as V tag
  19. while($row = $result->fetch_assoc()) {
  20. echo "<V t=\"" .$row["type"] ."\" n=\"" .str_replace('_', ".", $row["name"]) ."\">" .$row["value"] ."</V>";
  21. }
  22. }
  23.  
  24. // close root element
  25. echo "</VE>";
  26. echo "</R>"; // required for < s20.b10
  27.  
  28. // end LOAD mode
  29. }

Since PHP $_POST transforms variable names with dots into underscores http://php.net/manual/en/language.variables.external.php, you have to convert them back through str_replace, otherwise potions.health will stay potions_health.

Changes from ShiVa 1.x

The new remote environments not fully incompatible with 2.0, these are the most important changes:

AUTH

network.authenticate, the AUTH tag and related functions have been deprecated for many years, and now officially dead with ShiVa 2.0. Use application.setCurrentUserEnvironmentURL, user.loadEnvironment, and network.setCurrentServer instead. User ID cannot be set manually anymore.

XML structure

The XML structure has changed significantly. You will find the following in 1.x documentation, all of which is no longer relevant:

  1. <AUTH>
  2. <Network>
  3. <S3DServer name="Name of the S3DServer" url="IP:Port of the S3DServer" />
  4. <EnvServer url="URL of the page to manage environment" method="management method ( Post or Xml )"/>
  5. </Network>
  6. <Users>
  7. <Local userId="Id of the player" />
  8. </Users>
  9. ...

POST keys

The names of the POST keys are different from ShiVa 1.x releases. The new keys are: $_POST['SAVE_PLAYER'], $_POST['SAVE_ENVNAME'], $_POST['PLAYER'], $_POST['ENVNAME'] and $_POST['VAR'].

The old 1.9x XML method using $_POST['stm'] is no longer the default, instead every variable comes on its own. It is still possible to reactivate the old XML behaviour by setting kOptionDistantEnvironmentSendMethod to kDistantEnvironmentSendMethodXML, however the new default is kDistantEnvironmentSendMethodPOST.