Game Development Community

MMORPG Tutorial Article 6 Logins R US

by Dreamer · 04/14/2005 (12:13 pm) · 35 comments

First make sure you have followed ALL of the other tutorials, starting with this one.
http://www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7514

In it's default setup TGE has a pretty straightforward connection sequence.
#1 Start Server
#2 Connect To Server
#3 Send mission information.

What we need to do is to interrupt this sequence between steps 2 and 3 and insert authentication of some kind.

I am using a local SQLite database for all character information storage, a better solution would be to setup an offsite PHP/MySQL auth server for login information and then store specific character information like level, inventory etc in a local DB on the server side which is updated against another MySQL server every hour or so.

For right now though lets just dump everything into a server side SQLite DB, I am calling mine Dream, you may call it anything you like, just remember to delete it at the end of testing like any .dso

Just a heads up, the UserName and Passwords are both sent and stored as plain text, it would be VERY wise to at least apply a CRC32 to the password before storage and while doing comparisons. But I have chosen to not do this at the moment, since a good CRC tut is a little hard to find and I want to keep these articles on topic as much as possible.

Since we are adjusting alot of stuff server side, and merely ADDING things to the client side lets start with the Server code.

Open up server/scripts/commands.cs and add the following code
function serverCmdLoadZone(%client,%AccountName){
	echo("\n\nIf we are seeing this the mission should be downloading to the client\n\n");
      	sendLoadInfoToClient(%client);
   
	%client.guid = 0;
	addToServerGuidList( %client.guid );
	
	// Set admin status
	if (%client.getAddress() $= "local") {
		%client.isAdmin = true;
		%client.isSuperAdmin = true;
	}else{
		%client.isAdmin = false;
		%client.isSuperAdmin = false;
	}
	
	// Save client preferences on the connection object for later use.
	%client.setPlayerName(%name);
	%client.score = 0;
	%client.name = %AccountName;
	%client.Level = getClientLevel(%client);
	// 
	$instantGroup = ServerGroup;
	$instantGroup = MissionCleanup;
	echo("CADD: " @ %client @ " " @ %client.getAddress());
	
	// Inform the client of all the other clients
	%count = ClientGroup.getCount();
	for (%cl = 0; %cl < %count; %cl++) {
		%other = ClientGroup.getObject(%cl);
		if ((%other != %client)) {
			// These should be "silent" versions of these messages...
			messageClient(%client, 'MsgClientJoin', "", 
			%other.name,
			%other,
			%other.sendGuid,
			%other.score, 
			%other.isAIControlled(),
			%other.isAdmin, 
			%other.isSuperAdmin);
		}
	}
	
	// Inform the client we've joined up
	messageClient(%client,
	'MsgClientJoin', '\c2Welcome to Dream! %1.',
	%client.name, 
	%client,
	%client.sendGuid,
	%client.score,
	%client.isAiControlled(), 
	%client.isAdmin, 
	%client.isSuperAdmin);
	
	// Inform all the other clients of the new guy
	messageAllExcept(%client, -1, 'MsgClientJoin', '\c1%1 joined the game.', 
	%client.name, 
	%client,
	%client.sendGuid,
	%client.score,
	%client.isAiControlled(), 
	%client.isAdmin, 
	%client.isSuperAdmin);
	
	// If the mission is running, go ahead download it to the client
	if ($missionRunning){
		%client.loadMission();
		$Server::PlayerCount++;
	}
	
}

Now create a file named dbcommands.cs in server/scripts and add this code to it.
$dbname = "dream";
$SqLite = new SqLiteObject(sqlite);
if ($SqLite == 0){
	echo("ERROR: Failed to create SqLiteObject. $SqLiteObject aborted.");
	return;
}

// open database
if ($SqLite.openDatabase($dbname) == 0){
	echo("ERROR: Failed to open database: " @ $dbname);
	echo("       Ensure that the disk is not full or write protected.  $SqLiteObject aborted.");
	return;
}

//Reports errors on Query Fail
function SqLite::onQueryFailed(%this, %error)
{
   echo ("$SqLite Query Error: " @ %error);
}


//Account TABLE functions
function serverCmdValidateAccount(%client,%AccountName,%AccountPassword){
   %query = "CREATE TABLE Accounts (Name VARCHAR(20),password VARCHAR(20),Race VARCHAR(20),Level INT(11))";
   %result = $SqLite.query(%query, 0);
   if (%result == 0)
   {
      // query failed
      echo("Table already exists table creation aborted.");
      //
      //
      //return;
   }
   if(%AccountName !$="" && %AccountPassword !$=""){
	%query ="SELECT * FROM Accounts WHERE Name = \'" @ %AccountName @ "\' AND Password = \'" @ %AccountPassword @ "\'";
	echo(%query);
	%result = $SqLite.query(%query,0);
	if ($SqLite.numRows(%result) > 0){
		echo("If we are seeing this then the server has found a match for the Account name and Password");
		$SqLite.clearResult(%result);
		commandToClient(%client,'ValidateAccountResult','TRUE');
		serverCmdLoadZone(%client,%AccountName);
	}else{
		%query ="SELECT * FROM Accounts WHERE Name = \'" @ %AccountName @ "\'";
		%result = $SqLite.query(%query,0);
		echo(%query);
		if($SqLite.numRows(%result) > 0){
			echo("If we are seeing this then the server has found a match for the Account Name but not the Password");
			$SqLite.clearResult(%result);
			commandToClient(%client,'ValidateAccountResult','WRONGPASSWORD');
			return("WRONGPASSWORD");		
		}else{
			echo("If we are seeing this then the server was unable to find a match for the Account name and Password");
			$SqLite.clearResult(%result);
			commandToClient(%client,'ValidateAccountResult','NEW');
			return("NEW");
			
		}
	}
   }else{
   	commandToClient(%client,'ValidateAccountResult','NULL');
   }
}

function serverCmdCreateAccount(%client,%AccountName,%AccountPassword,%Race){
   if(%Race $=""){
    	%Race ="Glowy";
   }	
   %query ="INSERT INTO Accounts VALUES (\'" @ %AccountName @ "\',\'" @ %AccountPassword @ "\',\'"@%Race@"\',\'1\')";
   echo(%query);
   %result = $SqLite.query(%query,0);
   if(%result != 0){
   	echo("If we are seeing this then the server was able to insert the Account name, Password and Race");
   	$SqLite.clearResult(%result);
   	commandToClient(%client,'ValidateAccountResult','TRUE');
   }else{
   	echo("If we are seeing this then the server was not able to insert the Account name, Password and Race");
	$SqLite.clearResult(%result);
   	commandToClient(%client,'ValidateAccountResult','FALSE');
   }
   
}

function getPlayerBody(%name){
	%query = "SELECT * from Accounts WHERE Name = \'"@%name@"\'";
	echo(%query);
	%result = $SqLite.query(%query,0);
	echo(%result);
	if(%result){
		%Race = $SqLite.getColumn(%result, "Race");
		$SqLite.clearResult(%result);
		return(%Race);
	}else{
		echo("Uhoh! This player has no body, guess we can just choose one... Might want to figure out Why this happened?");
		return("Glowy"); //Well we gotta make 'em somethin now don't we
	}
}

Thats our authentication code serverside now we need to interrupt the login sequence
common/server/client_connection.cs (this will be above starter.fps)
Edit the on connect function
function GameConnection::onConnect( %client, %name, %password)
{
   // Send down the connection error info, the client is
   // responsible for displaying this message if a connection
   // error occured.
   messageClient(%client,'MsgConnectionError',"",$Pref::Server::ConnectionError);

  //Validate the client
   echo("Attempting to Authenticate Client " @ %client);
   serverCmdValidateAccount(%client,%name,%password);
}

That should be about it for the server side stuff, now on to the client
In client/scripts/client_commands.cs add the following
//This function handles client side Authentication
function LoginAccount(){
	echo($Pref::Player::Name @" Is attempting to login, using password " @ $Pref::Player::Password);
	echo("We are already connected, attempting to Authenticate");
	CommandToServer('ValidateAccount',$Pref::Player::Name,$Pref::Player::Password);
}
}

//This function handles the clients part in creating accounts
function CreateAccount(){
	if($Pref::Player::Name !$="" && $Pref::Player::Password !$= "" && $Pref::Player::Race !$=""){
		echo("Attempting to create " @ $Pref::Player::Name @", using password " @ $Pref::Player::Password);
		CommandToServer('CreateAccount',$Pref::Player::Name,$Pref::Player::Password,$Pref::Player::Race);
	}else{
		MessageBoxOK("Error!","Your User Name and/or Password and/or Body are empty");
	}
}

function ChooseRace(){
	echo("We are attempting to discern race for a new account");
	if(RaceBoxOrc.getValue()){
		echo("Looks like an Orc to me!");
		$Pref::Player::Race = "Orc";
	}else{
		if(RaceBoxHuman.getValue()){
			echo("Looks like a Human to me!");
			$Pref::Player::Race = "Human";
		}else{
			if(RaceBoxGlowy.getValue()){
				echo("Looks like a Glowy to me!");
				$Pref::Player::Race = "Glowy";
			}else{
				echo("Looks like nothin to me!");
				$Pref::Player::Race = "None";
			}
		}
	}
	
	if($Pref::Player::Race !$="None"){
		echo("Ok so we choose a race now lets confirm it, just to be sure");
		MessageBoxYesNO("Race","You have choosen to be  "@$Pref::Player::Race,"Canvas.PopDialog(ChooseRaceGUI);CreateAccount();","");
	}else{
		echo("They didn't wanna be nuthin?");
		MessageBoxOK("Error!","Please choose a race!");
	}
	echo("Huh? Wuh? What am I doing here? Looks like we fell through!");
}  

//This function handles validate account responses from the server
function clientCmdValidateAccountResult(%Result){
	%errmsg = "";
	$Result = detag(%Result);
	//The following could be done much more cleanly if switch and case statements functioned properly
	if(strpos(%Result,"TRUE") != -1){
		Canvas.PopDialog(AccountGUI);
		MessageBoxOK("Success!","You have now successfully logged in!","LoginAccount();");
		
	}else{
		if(strpos(%Result,"NEW") != -1 ){
			if($Pref::Player::Name !$="" && $Pref::Player::Password !$= ""){
				MessageBoxYesNo( "New Account?","Do you wish to create a new account?","Canvas.PushDialog(ChooseRaceGUI);", "");
			}else{
				MessageBoxOK("Error!","Your User Name and/or Password are empty");
			}
		}else{
			if(strpos(%Result,"WRONGPASSWORD") != -1){
				MessageBoxOK("Error!","Your Password is incorrect");
				
			}else{
				if(%Result !$= ""){
					%errmsg = "The server returned an invalid response\nThe response was " @ %Result;
				}else{
					%errmsg = "The server returned a NULL response!";
				}
				MessageBoxOK("Error!",%errmsg);
				
			}
			if(strpos(%Result,"NULL") != -1){
				MessageBoxOK("Error!","Your User Name and/or Password are empty");
				
			}	
		}

	}
}

Now we need to add 2 GUI's and edit the main menu
client/gui/mainmenugui.gui edit to the following
new GuiButtonCtrl() {
      profile = "GuiButtonProfile";
      horizSizing = "right";
      vertSizing = "top";
      position = "36 264";
      extent = "110 20";
      minExtent = "8 8";
      visible = "1";
      command = "Canvas.PushDialog(AccountGUI);";
      text = "Login";
      groupNum = "-1";
      buttonType = "PushButton";
         helpTag = "0";
   };

Now add a new file called client/ui/AccountGUI.gui
new GuiControl(AccountGUI) {
	profile = "GuiDefaultProfile";
	horizSizing = "right";
	vertSizing = "bottom";
	position = "0 0";
	extent = "320 240";
	minExtent = "8 8";
	visible = "1";
	helpTag = "0";

	new GuiWindowCtrl() {
		profile = "GuiWindowProfile";
		horizSizing = "center";
		vertSizing = "center";
		position = "131 10";
		extent = "377 303";
		minExtent = "8 8";
		visible = "1";
		helpTag = "0";
		text ="Account Info";
		maxLength = "255";
		resizeWidth = "0";
		resizeHeight = "0";
		canMove = "1";
		canClose = "1";
		canMinimize = "0";
		canMaximize = "0";
		minSize = "25 25";
		closeCommand = "Canvas.popDialog(AccountGUI);";
	
		new GuiTextCtrl() {
			profile = "GuiTextProfile";
			horizSizing = "right";
			vertSizing = "top";
			position = "36 30";
			extent = "63 18";
			minExtent = "8 8";
			visible = "1";
			text = "User Name:";
			maxLength = "255";
			helpTag = "0";
		};
		
		new GuiTextEditCtrl() {
			profile = "GuiTextEditProfile";
			horizSizing = "right";
			vertSizing = "top";
			position = "36 50";
			extent = "134 18";
			minExtent = "8 8";
			visible = "1";
			variable = "$Pref::Player::Name";
			maxLength = "255";
			historySize = "1";
			password = "0";
			tabComplete = "0";
			sinkAllKeyEvents = "0";
			helpTag = "0";
		};
		
		new GuiTextCtrl() {
			profile = "GuiTextProfile";
			horizSizing = "right";
			vertSizing = "top";
			position = "36 70";
			extent = "63 18";
			minExtent = "8 8";
			visible = "1";
			text = "Password:";
			maxLength = "255";
			helpTag = "0";
		};         
		
		new GuiTextEditCtrl() {
			profile = "GuiTextEditProfile";
			horizSizing = "right";
			vertSizing = "top";
			position = "36 90";
			extent = "134 18";
			minExtent = "8 8";
			visible = "1";
			variable = "$Pref::Player::Password";
			maxLength = "255";
			historySize = "1";
			password = "1";
			tabComplete = "0";
			sinkAllKeyEvents = "0";
			helpTag = "0";
		};
		
		new GuiButtonCtrl() {
			profile = "GuiButtonProfile";
			horizSizing = "right";
			vertSizing = "top";
			position = "36 110";
			extent = "134 18";
			minExtent = "8 8";
			visible = "1";
			command = "LoginAccount();";
			text = "Login";
			groupNum = "-1";
			buttonType = "PushButton";
			helpTag = "0";
		};
   
	};
};

//--- OBJECT WRITE BEGIN ---
new GuiWindowCtrl(ChooseRaceGUI) {
   profile = "GuiWindowProfile";
   horizSizing = "right";
   vertSizing = "bottom";
   position = "0 0";
   extent = "640 480";
   minExtent = "8 2";
   visible = "1";
   maxLength = "255";
   resizeWidth = "1";
   resizeHeight = "1";
   canMove = "1";
   canClose = "1";
   canMinimize = "1";
   canMaximize = "1";
   minSize = "50 50";

   new GuiRadioCtrl(RaceBoxOrc) {
      profile = "GuiRadioProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "208 106";
      extent = "140 30";
      minExtent = "8 2";
      visible = "1";
      text = "Orc";
      groupNum = "1";
      buttonType = "RadioButton";
   };
   new GuiRadioCtrl(RaceBoxHuman) {
      profile = "GuiRadioProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "208 136";
      extent = "140 30";
      minExtent = "8 2";
      visible = "1";
      text = "Human";
      groupNum = "1";
      buttonType = "RadioButton";
   };
   new GuiRadioCtrl(RaceBoxGlowy) {
      profile = "GuiRadioProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "208 164";
      extent = "140 30";
      minExtent = "8 2";
      visible = "1";
      text = "Glowy";
      groupNum = "1";
      buttonType = "RadioButton";
   };
   new GuiButtonCtrl() {
      profile = "GuiButtonProfile";
      horizSizing = "right";
      vertSizing = "bottom";
      position = "211 203";
      extent = "140 30";
      minExtent = "8 2";
      visible = "1";
      command = "ChooseRace();";
      text = "Race";
      groupNum = "11";
      buttonType = "PushButton";
   };
};

That should be it, to make all of this work, you need to run 2 instances of TGE one as a dedicated server, and another as a client with the following command
torqueDemo_Debug.bin -connect 127.0.0.1 (or whatever IP if you happen to be using a remote server)

Enjoy!
In our next tutorial we will make use of the Race information and make a proper character creation screen!

Next Tutorial
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7695
Page «Previous 1 2
#1
04/14/2005 (1:40 pm)
Almost forgot, please remember to exec all these new scripts.
#2
04/14/2005 (7:13 pm)
The next one (custom characters) is what im really looking forward to :-) something ive been struggling with for weeks.
#3
04/14/2005 (11:15 pm)
@Ed Hope you're not too disappointed, next tut is called Pick a Char Any Char, and will deal with character customization i.e. Pick a race, choose some stats, make 'em tall or make 'em fat etc.
#4
04/15/2005 (1:36 am)
stats/skillz are exactly what im talkin about.... like

agility, strength, speed, magic, fishing, mining, smithing, fletching

you get the idea :)
#5
04/15/2005 (10:29 am)
It's going to be a few days before I can get Pick a Char any Char up, I originally had a player selection screen with stats and etc made for it that had all but remained untouched. However I have now implemented the custom shape mod tutorial into the core of the engine, and am finishing work on making the two gui's play nice together. Now would be a great time for anyone who wants to get ahead of the game to go ahead and add
www.garagegames.com/index.php?sec=mg&mod=resource&page=view&qid=7368

It will take a couple of days of spare time, but I'm going to make this play nice with my character selection screen. Should be some very interesting results :)
#6
04/16/2005 (10:04 am)
Dreamer, Excellent tutorials!
#7
04/16/2005 (7:52 pm)
Yep, really helping the non-fps people out :-)
#8
04/17/2005 (4:29 am)
Definately a great line of tutorials. Kudos.
#9
04/18/2005 (12:56 pm)
I just fixed a huge gaping bug in the code today, that was being a pain the butt to track, seems you had to delete your prefs file before you could login after the first attempt, anyways the problem was in the LoginAccount function on the client side and it's been fixed above.

*Update* Ok so my fix isn't perfect, you actually have to create a Prefferences file first by starting the client, entering your login info and attempting to connect, then exiting the client. After that it works!

I promise I will get this bug tracked and fixed soon.

*Update Again*
Ok I have a fix that appears to work pretty good, but I'm not liking the hack I had to come up with to get it working, properly. I should have something much more elegant soon.

*Final Update*
The error is fixed in the Pick a Char any Char tutorial, which I submitted last night.
#10
04/22/2005 (1:03 pm)
im getting this error in server/scripts/command.cs
%targetObject.applyDamage(%BaseDamage);
MessageClient(%targetObject,'Attack',%client.getShapeName()@'hits you for'@%client.getMountedImage0).BaseDamage);
}else{

the MessageClient(%targetObject,'Attack',%client.getShapeName()@'hits you for'@%client.getMountedImage0).BaseDamage);
is the syntax error line 305
Thanks
#11
04/22/2005 (1:09 pm)
That would be extremely hard to get on this tut considering there isn't any melee code here. I'll see if there is a problem in my melee tutorial though.

*Update*
@Wayne I just checked the code from server side melee redux, and there is nothing wrong with it other than the usual line break and spacing issues that happen when you cut and paste.
Please go through the code and remove any extra line breaks (there's quite a few in that tutorial. Also you might want to add that opening parenthesis ( it appears you accidently deleted back in, it's quite vital to that line of code.
#12
04/22/2005 (9:07 pm)
I thout your code was all that I needed , do i also need to install the server side melee system also?
Also when loading game the AccountGUI isant called so the only thing that shows up is black background or if i add background , only picture, the accountgui does not load at startup, i have been able to access it though the f10 key.
Thanks
by the way, do you have a chat program that you use? would be easer to show the exact errors and chat that way :)
#13
04/23/2005 (6:34 am)
OK, just wonted to let you know that i have most things working and i am loveing it so far, i do have one question about the target gui, is there any way to embed the gui only into the playgui? so that when i click m and select a fish i can see it say at the top right hand conner of my screen without haveing to toggle the targetgui like an inventory?
Thanks
#14
04/23/2005 (2:29 pm)
Well the problem I have now is that i get lots of read/write memory errors and just like to know if anyone els trying this is haveing same trouble.
Thanks
#15
04/24/2005 (7:15 am)
I still get random memory errors but that could be this laptop im useing but i got just about everything working and got the targetgui to display in playgui and works great. what i had to do was start a game and use the gui editor to copy the hud from targetgui and past it into playgui and works great.
Cant waite to see the rest of the tut's :)
Great work man and thanks for all the great code and help :)
#16
04/24/2005 (6:13 pm)
I have another quick question, would it be hard when you choose the race that it will select a diffrent character in another folder?
Thanks
#17
04/25/2005 (2:34 am)
Ok Wayne sorry for the delay in getting back on this, but I was away for the weekend. Anyways, I will be updating this code when I post the next tutorial which should be either today or tommorow, we'll see if that fixes your errors.

Anyways thanx for your patience with me.
#18
04/25/2005 (7:31 am)
Great man thanks, hope you had a good weekend :)
#19
04/25/2005 (12:27 pm)
I think I have narrowed the memory errors down to aiPlayer.cs but i took out all the fish code and doesint seem to matter pluse if i take out the exec command in game.cs the map doesint load but normly it crashes when I go to load a map. I thout it could have been the laptop but it does the same on my desktop also.
#20
04/26/2005 (6:53 am)
Well Pick a Char any Char is now complete, I submitted it last night, it also contains the bug fixes for this tutorial, (these bugs fix themselves when you apply the new tutorial code). Doesn't touch AIPlayer though Wayne, and I still have no idea what could be causing your memory issues. I just can't replicate them.

I will post a link here as soon as it's up.
Page «Previous 1 2