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.
application.setCurrentUserEnvironmentName ( "FromShiVa" )
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.)
if (not application.saveCurrentUserEnvironment ( )) then
log.warning ( "saving error!" )
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:
--------------------------------------------------------------------------------
function envtestMain.loadRemoteEnv_onEnter ( )
--------------------------------------------------------------------------------
application.loadCurrentUserEnvironment ( "Prepared" )
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------
onLoop checks the status of one of your variables:
--------------------------------------------------------------------------------
function envtestMain.loadRemoteEnv_onLoop ( )
--------------------------------------------------------------------------------
local sName = "user.nVar7"
log.message ( "Checking on " ..sName )
local nStatus = application.getCurrentUserEnvironmentVariableStatus ( sName )
if ( nStatus == application.kStatusReady ) then
log.message ( "the variable " .. sName .. " is available !" )
-- loading success! do something here...
this.idle ( )
elseif ( nStatus == application.kStatusLoading ) then
-- variable is currently being loaded
log.message ( "currently loading " .. sName )
elseif ( nStatus == application.kStatusSaving ) then
-- variable is currently being saved
log.message ( "currently saving " .. sName )
elseif ( nStatus == application.kStatusNone ) then
-- variable is not available or does not exist
log.message ( "the variable " .. sName .. " is not available or do not exist !" )
this.idle ( )
end
--------------------------------------------------------------------------------
end
--------------------------------------------------------------------------------
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:
// TYPE selection:
// 1 - number
// 2 - string
// 3 - boolean
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:
<?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:
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.
// MySQL connection
$servername = "localhost";
$username = "root";
$password = "";
$db = "shivaenvironments";
// Create connection
$connection = new mysqli($servername, $username, $password, $db);
$connection->set_charset("utf8");
// Check connection
if ($connection->connect_error) {
die("Connection failed: " . $connection->connect_error);
}
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
$mode = "UNDEFINED";
/* possible modes:
LOADALL
LOADVAR
SAVE
*/
//SAVE
$savePlayer = $_POST['SAVE_PLAYER']; // ID of the current user
$saveEnviro = $_POST['SAVE_ENVNAME']; // name of the current environment
// LOAD
$receivedPlayer = $_POST['PLAYER']; // ID of the current user
$receivedEnviro = $_POST['ENVNAME']; // name of the current environment
$receivedVariab = $_POST['VAR']; // var to load, accepts wildcards
if (!empty($saveEnviro)) {
$mode="SAVE";
} else if (!empty($receivedVariab)) {
$mode="LOADVAR";
} else {
$mode="LOADALL";
}
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.
if ($mode == "SAVE") {
// every variable comes as a separate POST variable
foreach ($_POST as $k => $v) {
// escape for "SAVE_" variables
if ($k == "SAVE_PLAYER" || $k == "SAVE_ENVNAME") continue;
// TYPE selection:
// 1 - number
// 2 - string
// 3 - boolean
$_type = 2;
if (is_numeric($v)) {
$_type = 1;
} elseif ($v == "true" || $v == "false") {
$_type = 3;
} else {
$v = $connection->real_escape_string($v);
}
// create SQL query - hopefully you set your UNIQUE key!
$query = "INSERT INTO savedata (playerID, envName, `type`, `name`, `value`) VALUES (" .$savePlayer .",'" .$saveEnviro ."'," .$_type .",'" .$k ."','" .$v ."') ON DUPLICATE KEY UPDATE `value`='" .$v ."'";
$connection->query($query);
}
// end SAVE mode
}
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:
<?xml version="1.0" encoding="utf-8"?>
<R>
<VE i="0" n="envName">
<V t="1" n="myNumber">12.00000</V>
<V t="2" n="myString">Hello there!</V>
</VE>
</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.
if ($mode == "LOADVAR" || $mode == "LOADALL") {
/* print root element */
echo ''; // required for < s20.b10
echo '';
// look through database for the variable, e.g. MySQL:
if ($mode == "LOADVAR") {
$query = "SELECT * FROM savedata WHERE playerID='" .$receivedPlayer ."' AND envName='" .$receivedEnviro ."' AND name='" .$receivedVariab ."'";
} else {
$query = "SELECT * FROM savedata WHERE playerID='" .$receivedPlayer ."' AND envName='" .$receivedEnviro ."'";
}
// actual query here, e.g. ...
$result = $connection->query($query);
// .. then assemble output:
if ($result->num_rows > 0) {
// output data of each row as V tag
while($row = $result->fetch_assoc()) {
echo "" .$row["value"] ." ";
}
}
// close root element
echo "";
echo " "; // required for < s20.b10
// end LOAD mode
}
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:
<AUTH>
<Network>
<S3DServer name="Name of the S3DServer" url="IP:Port of the S3DServer" />
<EnvServer url="URL of the page to manage environment" method="management method ( Post or Xml )"/>
</Network>
<Users>
<Local userId="Id of the player" />
</Users>
...
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.