An interesting problem with a PHP-based game
Posted: Fri May 04, 2012 9:54 pm
I am currently writing a game that uses a Flash->PHP->MySQL combo.
I'm having no issues with flash (or I wouldn't be posting on a PHP forum) but I am encountering an interesting "roadblock" with PHP.
Here's the basics of the setup:
Flash: this.ndata.load(_root.website+"/tactics/main/data.php?rand="+Math.random()*1000);
If you copy and paste the output of that line (http://gametack.org/tactics/main/data.php?rand=158.1439) into a browser's URL you will see something like this:
returned=true&unsan_array0=\m\&unsan_array1=\~\
The exact syntax is this: &variable_name=variable_contents
flash then translates this into actual variables. After that, I handle the rest with the application. Simple when the rest is done with PHP!
So here's my problem:
I obviously need to incorporate movement into my ORPG, so I think of it like this(i'll assume an "average" for non-constants):
The game runs at about 30 frames per second. I say "about" because it's not perfect and sometimes gets bogged down with information for about a quarter of a second.
Every frame your character moves anywhere from 5-7 pixels, depending on a bunch of things.
I need to make sure the players don't go over this limit (eliminating cheaters) and try not to impair a player's normal activities(and bog down the server)
So, for the raw math: 6 pixel movement*30 frames per second=180 pixels of movement per second
I ping the server with the player's new location (x, y, and z) every 250 milliseconds, or 1/4 of a second
I see it this way: character speed*FPS = player's max movement - player's new location(x+y+z)-player's old location(database's x+y+z) = player's movement
If the player's movement is greater than the player's max movement, return an error (the player's movement is multiplied by -1 if the movement is below 0)
The server will also check the time (via the date()+microtime() functions) and if the new time-the database's old time is greater than 250 milliseconds, it again returns an error
when an error is returned, the flash program quickly puts the player back into the database's x, y, and z coords so the player doesn't disconnect
This is my problem: Even at the loosest settings, I still have some issues with the server not accepting a legitimate time+coordinate input. This is a problem because attackers can easily trick the server into accepting a location far beyond the reach of any normal user.
to the raw (relevant) PHP:
ucheck.php
game.php
In short: If anyone would like to put in their two cents, it would be very much appreciated 
And if interested, I set up a test account. (The server is in Germany. Expect a lot of latency over an ocean)
http://gametack.org/download/tactics2.html (no worries, it's an in-browser game)
user: test
pass: test
I'm having no issues with flash (or I wouldn't be posting on a PHP forum) but I am encountering an interesting "roadblock" with PHP.
Here's the basics of the setup:
Flash: this.ndata.load(_root.website+"/tactics/main/data.php?rand="+Math.random()*1000);
If you copy and paste the output of that line (http://gametack.org/tactics/main/data.php?rand=158.1439) into a browser's URL you will see something like this:
returned=true&unsan_array0=\m\&unsan_array1=\~\
The exact syntax is this: &variable_name=variable_contents
flash then translates this into actual variables. After that, I handle the rest with the application. Simple when the rest is done with PHP!
So here's my problem:
I obviously need to incorporate movement into my ORPG, so I think of it like this(i'll assume an "average" for non-constants):
The game runs at about 30 frames per second. I say "about" because it's not perfect and sometimes gets bogged down with information for about a quarter of a second.
Every frame your character moves anywhere from 5-7 pixels, depending on a bunch of things.
I need to make sure the players don't go over this limit (eliminating cheaters) and try not to impair a player's normal activities(and bog down the server)
So, for the raw math: 6 pixel movement*30 frames per second=180 pixels of movement per second
I ping the server with the player's new location (x, y, and z) every 250 milliseconds, or 1/4 of a second
I see it this way: character speed*FPS = player's max movement - player's new location(x+y+z)-player's old location(database's x+y+z) = player's movement
If the player's movement is greater than the player's max movement, return an error (the player's movement is multiplied by -1 if the movement is below 0)
The server will also check the time (via the date()+microtime() functions) and if the new time-the database's old time is greater than 250 milliseconds, it again returns an error
when an error is returned, the flash program quickly puts the player back into the database's x, y, and z coords so the player doesn't disconnect
This is my problem: Even at the loosest settings, I still have some issues with the server not accepting a legitimate time+coordinate input. This is a problem because attackers can easily trick the server into accepting a location far beyond the reach of any normal user.
to the raw (relevant) PHP:
ucheck.php
Code: Select all
<?php
if($good_connect == true){
$acct_login = false;
if((isset($_GET["user"]) and $_GET["user"] != "") and (isset($_GET["pass"]) and $_GET["pass"] != "")){
$result = mysql_query("SELECT * FROM `accounts` WHERE `user`='".san($_GET["user"])."' AND `pass`='".md5(unsan($_GET["pass"]))."'");
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
if(mysql_num_rows($result) != 1){
$message = "Account does not exist on this universe";
echo("&error=".san($message));
exit();
}
$row = mysql_fetch_array($result);
mysql_free_result($result);
if((isset($row["user"]) and $row["user"] != "") and (isset($row["pass"]) and $row["pass"] != "") and (isset($row["email"]) and $row["email"] != "")){
$acct_login = true;
$acct_id = unsan($row["id"]);
$acct_user = unsan($row["user"]);
$acct_pass = unsan($row["pass"]);
$acct_perm = unsan($row["perm"]);
$acct_email = unsan($row["email"]);
$acct_active = $row["active"];
if($row["perm"] == "adm/0"){
echo("&perm=admin");
}elseif($row["perm"] == "mod/1"){
echo("&perm=mod");
}else{
echo("&perm=user");
}
}else{
$message = "Account is missing information";
echo("&error=".san($message));
exit();
}
if((!isset($no_login) or $no_login == false) and (isset($_GET["character"]) and $_GET["character"] != "") and $acct_login == true){
$result = mysql_query("SELECT `account_id`, `created`, `adate`, `atime`, `mic` FROM `characters` WHERE `id`=".intval(unsan($_GET["character"])));
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
if(mysql_num_rows($result) != 1){
$message = "Character does not exist";
echo("&error=".san($message));
exit();
}
$row = mysql_fetch_array($result);
mysql_free_result($result);
if(unsan($row["account_id"]) != $acct_id){
$message = "Character does not belong to account \"".san($acct_user)."\"";
echo("&error=".san($message));
exit();
}
$char_created = $row["created"];
// --------------------------------------------------------------- RELEVANT -------------------------------------------- RELEVANT -----------------------------------------------------------
if(strpos(strtolower($_SERVER["REQUEST_URI"]), "game.php") !== false){
$mic = microtime(true);
$nt = (strtotime(date("m/d/Y")." ".date("H:i:s"))-strtotime($row["adate"]." ".$row["atime"]))+((($mic-floor($mic))*1000-$row["mic"])/1000);
if($nt*1000 < 250){ //check the execution time against the database's last execution time. If it's less than 250 milliseconds, throw an error
$no_move = true;
echo("&s_error=true");
}
}
if(!isset($no_online) or $no_online == false){
$mic = microtime(true);
$result = mysql_query("UPDATE `characters` SET `online`=1, `adate`='".date("m/d/Y")."', `atime`='".date("H:i:s")."', `mic`='".(($mic-floor($mic))*1000)."' WHERE `id`=".intval(unsan($_GET["character"])));
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
}
// --------------------------------------------------------------- /RELEVANT -------------------------------------------- /RELEVANT -----------------------------------------------------
}
}
if(isset($_GET["startuser"])){
$startuser = intval(decrypt(unsan($_GET["startuser"])));
}else{
$startuser = 0;
}
$startuser2 = $startuser+100;
$result = mysql_query("SELECT `id`, `adate`, `atime` FROM `characters` WHERE `online`=1");
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
for($i=0;$i<$startuser;$i++){
$row = mysql_fetch_array($result);
}
$nextuser = 1;
while($row = mysql_fetch_array($result) and $nextuser == 1){
$start = strtotime($row["adate"]." ".$row["atime"]);
$end = strtotime(date("m/d/Y")." ".date("H:i:s"));
$diff = $end-$start;
if($diff >= 5){
$result2 = mysql_query("UPDATE `characters` SET `online`=0 WHERE `id`=".$row["id"]);
if(!$result2){
echo("&db_error=".san(mysql_error()));
exit();
}
}
if($startuser > $startuser2){
$nextuser = 0;
}
$startuser++;
}
mysql_free_result($result);
if($nextuser == 1){
$startuser = 0;
}
echo("&startuser=".san(encrypt($startuser)));
}
?>Code: Select all
<?php
require("../include/connect.php");
require("../include/sanitize.php");
require("../include/security.php");
require("../include/version.php");
require("../include/ucheck.php");
if($good_connect == true){
if($acct_login == true){
if((isset($_GET["keepalive"]) and bol($_GET["keepalive"]) == true) and (isset($_GET["character"]) and $_GET["character"] != "")){
$result = mysql_query("SELECT * FROM `characters` WHERE `id`=".intval($_GET["character"]));
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
$row = mysql_fetch_array($result);
mysql_free_result($result);
$result = mysql_query("SELECT `xp` FROM `classes` WHERE `id`=".$row["class"]);
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
$row2 = mysql_fetch_array($result);
mysql_free_result($result);
$xp_array = explode(",", preg_replace("/\s+/", "", unsan($row2["xp"])));
echo("&health=".$row["health"]);
echo("&max_health=".$row["max_health"]);
echo("&mana=".$row["mana"]);
echo("&max_mana=".$row["max_mana"]);
echo("&xp=".$row["xp"]);
echo("&max_xp=".$xp_array[$row["level"]-1]);
$result = mysql_query("SELECT `name`, `race`, `sex`, `level`, `health`, `max_health`, `mana`, `max_mana`, `speed`, `x`, `y`, `z`, `dir` FROM `characters` WHERE `map`=".$row["map"]." AND `online`=1 AND `id`!=".intval($_GET["character"]));
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
$i=0;
while($row3 = mysql_fetch_array($result)){
echo("&char".$i."_name=".$row3["name"]);
echo("&char".$i."_race=".$row3["race"]);
echo("&char".$i."_sex=".$row3["sex"]);
echo("&char".$i."_level=".$row3["level"]);
echo("&char".$i."_health=".$row3["health"]);
echo("&char".$i."_max_health=".$row3["max_health"]);
echo("&char".$i."_mana=".$row3["mana"]);
echo("&char".$i."_max_mana=".$row3["max_mana"]);
echo("&char".$i."_speed=".$row3["speed"]);
echo("&char".$i."_x=".$row3["x"]);
echo("&char".$i."_y=".$row3["y"]);
echo("&char".$i."_z=".$row3["z"]);
echo("&char".$i."_dir=".$row3["dir"]);
$i++;
}
echo("&chars=".$i);
mysql_free_result($result);
if(isset($_GET["init"]) and bol(unsan($_GET["init"])) == true){
echo("&sprite_x=".$row["x"]);
echo("&sprite_y=".$row["y"]);
echo("&sprite_z=".$row["z"]);
echo("&sprite_dir=".$row["dir"]);
echo("&init=true");
}else{
if((isset($_GET["x"]) and $_GET["x"] != "") and (isset($_GET["y"]) and $_GET["y"] != "") and (isset($_GET["z"]) and $_GET["z"] != "") and (isset($_GET["dir"]) and $_GET["dir"] != "")){
// --------------------------------------------------------------- RELEVANT -------------------------------------------- RELEVANT -----------------------------------------------------------
$movement = (intval($_GET["x"])+intval($_GET["y"])+intval($_GET["z"]))-($row["x"]+$row["y"]+$row["z"]);
if($movement < 0){
$movement *= -1;
}
if($movement > ($row["speed"]*30)*0.25){ //the math: character speed*30 FPS*1/4 second = character's max movement speed
//movement is the player's submitted x, y, and z coords
echo("&s_error=true");
$no_move = true;
}
if(intval($_GET["dir"]) == 1 or intval($_GET["dir"]) == 5){
$dir = 0;
}elseif(intval($_GET["dir"]) == 2 or intval($_GET["dir"]) == 6){
$dir = 1;
}elseif(intval($_GET["dir"]) == 3 or intval($_GET["dir"]) == 7){
$dir = 2;
}else{
$dir = 3;
}
if(isset($no_move) and $no_move == true){
echo("&sprite_x=".$row["x"]);
echo("&sprite_y=".$row["y"]);
echo("&sprite_z=".$row["z"]);
}else{
$result = mysql_query("UPDATE `characters` SET `x`=".intval($_GET["x"]).", `y`=".intval($_GET["y"]).", `z`=".intval($_GET["z"]).", `dir`='".$dir."' WHERE `id`=".intval($_GET["character"]));
if(!$result){
echo("&db_error=".san(mysql_error()));
exit();
}
echo("&sprite_x=".intval($_GET["x"]));
echo("&sprite_y=".intval($_GET["y"]));
echo("&sprite_z=".intval($_GET["z"]));
}
// --------------------------------------------------------------- /RELEVANT -------------------------------------------- /RELEVANT -------------------------------------------------------------
}else{
$message = "Required field(s) missing";
echo("&error=".san($message));
exit();
}
}
}else{
$message = "Required field(s) missing";
echo("&error=".san($message));
exit();
}
}else{
$message = "Not logged in";
echo("&error=".san($message));
exit();
}
}
?>And if interested, I set up a test account. (The server is in Germany. Expect a lot of latency over an ocean)
http://gametack.org/download/tactics2.html (no worries, it's an in-browser game)
user: test
pass: test