MMORPG Tutorial Article 7 Pick a Char Any Char!
by Dreamer · 04/27/2005 (9:34 am) · 37 comments
Today we are going to touch on a number of subjects and AFAIK, this will probably be my longest tutorial yet. It is a little more complex than the others, and I have re-written major portions of some of the previous tutorials to make them fully compatible. But enough of my blood sweat and tears, lets begin.
The first change we need to make isn't even to code, we have to change the directory structure a little.
Go to example/starter.fps/data/shapes/player
Create 3 new directories.
Animations, Orc and Glowy
Now copy all files ending with .dsq to Animations and all the rest of the files to Orc, enter Orc and rename the player.dts file to Orc_Player.dts also rename debris_player.dts to player_debris.dts.
Now find your tutorial.base mod and copy all the files (except the .dsq's) from the data/shapes/player to example/starter.fps/data/shapes/player/glowy
Go to the glowy directory and rename the player.dts to glowy_player.dts now delete the debris_player.dts file from there (it's redundant and could be confusing)
Now look in all 3 of those directories for files called "player.cs" and delete.
Copy/Paste the following file to data/shapes/player/player.cs
Anytime you create a new playermodel and want it to have animations, just use the above template, you will notice that the Glowy and Orc share the exact same animation sequences, any player you can base off from the default skeleton of the Orc is going to be able to use these exact sequences, however if you want to use a custom skellie, you will need to create new animations and add it to the template changing the filenames approrpriately.
Next we want to add the ability to choose a race. A simple playerview gui control and a couple of buttons is all I'm using for this, so at the bottom of...
starter.fps/client/gui/AccountGui.gui
add
The above code will enable you to add as many player body types to the menu as you wish, simply by adding them into the data/shapes directory, and suffixing them with _player.dts.
Next we need to make the player's selection mean something to the client and server, so in starter.fps/client/scripts/client_commands.cs add the following.
Ok so far we have a much better account gui, and selection of a body model will actually mean something to the client, next we want to customize our character.
Originally I had planned to create some kind of system to tie body size to level, and tie the size of certain body components to particular stats, i.e. an increase in wisdom would produce an increase in head size etc. I began to code this for my advanced tutorial series, and then the custom shape mod came out, and gave me pause to consider. I have integrated the custom shape mod, but for now at least we will stick to the basics of simply getting a functional stats system into the game.
I have to be honest here, due to a typo I made pretty early on in designing the StatsGUI and it's interaction with the server, I ended up with a system that seems more than a little strange, currently the stats are set after the player enters the game rather than before, this is all due to a mistake I made in one line of code, switching a global and a local var, thereby producing some unexpected and might I say frustrating results. Anyways my work around was to push the StatsGUI after the first login, and after missionload, but before the PlayGUI was displayed. For this reason, I will leave the actual launching of StatsGUI to you, a good place would in fact when the Accept button is pressed in the ChooseRaceGUI, but then you would need to modify the SetStats function to not Push the PlayGUI, but instead run the create account function, wait a few seconds and then send the Stats, not too difficult by anymeans, but it is outside the scope of this tutorial.
Anyways here is starter.fps/client/ui/StatsGUI.gui
Now you'll notice something a little different about this GUI than any of the others, while sending the changes to the server,instead of looping through each and every stat at each update, we are infact storing everything clientside until the client commits to the change and then sending the server, a vector comprised of the stats, this makes for alot of interesting possibilities that we can explore in the future, and really reduces bandwidth and processing overhead, for now though, lets just work on making these values persistant.
Lets start with the CommandToServer
starter.fps/server/scripts/commands.cs
Now in starter.fps/server/scripts/dbcommands.cs
Remember all those table creation statements that were throwing errors on the second and successive runs through? I have moved them all into a file called InitDB.cs I took the time to cleanup the code a little and now this is the file I use immediately after deleting the DB, the only function that is 100% nessecary here is the IntializeDB function, the rest are simply to show how to populate the tables.
Now we want to create our player in
starter.fps/server/scripts/game.cs
Finally replace your PlayerBody datablock in starter.fps/server/scripts/player.cs with the following code.
Ok I'm pretty sure that should be everything, you should now be able to create a new player, select his Race/BodyType and then customize his stats. The stats are stored on the client object and can be accessed by %client.Stats[statname], when working serverside and $Stat[statname] when working clientside, since all updates to individual stats happen clientside I am not going to go into the missing ClientCmdUpdateStats, that could be used if you felt the need to adjust a players stats serverside and let the client know about it.
As you may or may not have noticed we are leaving this tutorial with having implemented a Stats system, but not having put it to any good use, my next tutorial will add a few tradeskills and we will see them put to good use in Forage,Bake and Eat, 'A Cake!
The first change we need to make isn't even to code, we have to change the directory structure a little.
Go to example/starter.fps/data/shapes/player
Create 3 new directories.
Animations, Orc and Glowy
Now copy all files ending with .dsq to Animations and all the rest of the files to Orc, enter Orc and rename the player.dts file to Orc_Player.dts also rename debris_player.dts to player_debris.dts.
Now find your tutorial.base mod and copy all the files (except the .dsq's) from the data/shapes/player to example/starter.fps/data/shapes/player/glowy
Go to the glowy directory and rename the player.dts to glowy_player.dts now delete the debris_player.dts file from there (it's redundant and could be confusing)
Now look in all 3 of those directories for files called "player.cs" and delete.
Copy/Paste the following file to data/shapes/player/player.cs
datablock TSShapeConstructor(OrcDts)
{
baseShape = "./Orc/orc_player.dts";
sequence0 = "./Animations/player_root.dsq root";
sequence1 = "./Animations/player_forward.dsq run";
sequence2 = "./Animations/player_back.dsq back";
sequence3 = "./Animations/player_side.dsq side";
sequence4 = "./Animations/player_lookde.dsq look";
sequence5 = "./Animations/player_head.dsq head";
sequence6 = "./Animations/player_fall.dsq fall";
sequence7 = "./Animations/player_land.dsq land";
sequence8 = "./Animations/player_jump.dsq jump";
sequence9 = "./Animations/player_diehead.dsq death1";
sequence10 = "./Animations/player_diechest.dsq death2";
sequence11 = "./Animations/player_dieback.dsq death3";
sequence12 = "./Animations/player_diesidelf.dsq death4";
sequence13 = "./Animations/player_diesidert.dsq death5";
sequence14 = "./Animations/player_dieleglf.dsq death6";
sequence15 = "./Animations/player_dielegrt.dsq death7";
sequence16 = "./Animations/player_dieslump.dsq death8";
sequence17 = "./Animations/player_dieknees.dsq death9";
sequence18 = "./Animations/player_dieforward.dsq death10";
sequence19 = "./Animations/player_diespin.dsq death11";
sequence20 = "./Animations/player_looksn.dsq looksn";
sequence21 = "./Animations/player_lookms.dsq lookms";
sequence22 = "./Animations/player_scoutroot.dsq scoutroot";
sequence23 = "./Animations/player_headside.dsq headside";
sequence24 = "./Animations/player_recoilde.dsq light_recoil";
sequence25 = "./Animations/player_sitting.dsq sitting";
sequence26 = "./Animations/player_celsalute.dsq celsalute";
sequence27 = "./Animations/player_celwave.dsq celwave";
sequence28 = "./Animations/player_standjump.dsq standjump";
sequence29 = "./Animations/player_looknw.dsq looknw";
// phdana hth ->
sequence30 = "./Animations/player_h1root.dsq h1root";
sequence31 = "./Animations/player_h1thrust.dsq h1thrust";
sequence32 = "./Animations/player_h1slice.dsq h1slice";
sequence33 = "./Animations/player_h1swing.dsq h1swing";
sequence34 = "./Animations/player_h1jumpattack.dsq h1jumpattack";
// phdana stun ->
sequence35 = "./Animations/player_h1stunde.dsq h1stun";
// <- phdana stun
// <- phdana hth
};
datablock TSShapeConstructor(GlowyDts)
{
baseShape = "./Glowy/glowy_player.dts";
sequence0 = "./Animations/player_root.dsq root";
sequence1 = "./Animations/player_forward.dsq run";
sequence2 = "./Animations/player_back.dsq back";
sequence3 = "./Animations/player_side.dsq side";
sequence4 = "./Animations/player_lookde.dsq look";
sequence5 = "./Animations/player_head.dsq head";
sequence6 = "./Animations/player_fall.dsq fall";
sequence7 = "./Animations/player_land.dsq land";
sequence8 = "./Animations/player_jump.dsq jump";
sequence9 = "./Animations/player_diehead.dsq death1";
sequence10 = "./Animations/player_diechest.dsq death2";
sequence11 = "./Animations/player_dieback.dsq death3";
sequence12 = "./Animations/player_diesidelf.dsq death4";
sequence13 = "./Animations/player_diesidert.dsq death5";
sequence14 = "./Animations/player_dieleglf.dsq death6";
sequence15 = "./Animations/player_dielegrt.dsq death7";
sequence16 = "./Animations/player_dieslump.dsq death8";
sequence17 = "./Animations/player_dieknees.dsq death9";
sequence18 = "./Animations/player_dieforward.dsq death10";
sequence19 = "./Animations/player_diespin.dsq death11";
sequence20 = "./Animations/player_looksn.dsq looksn";
sequence21 = "./Animations/player_lookms.dsq lookms";
sequence22 = "./Animations/player_scoutroot.dsq scoutroot";
sequence23 = "./Animations/player_headside.dsq headside";
sequence24 = "./Animations/player_recoilde.dsq light_recoil";
sequence25 = "./Animations/player_sitting.dsq sitting";
sequence26 = "./Animations/player_celsalute.dsq celsalute";
sequence27 = "./Animations/player_celwave.dsq celwave";
sequence28 = "./Animations/player_standjump.dsq standjump";
sequence29 = "./Animations/player_looknw.dsq looknw";
// phdana hth ->
sequence30 = "./Animations/player_h1root.dsq h1root";
sequence31 = "./Animations/player_h1thrust.dsq h1thrust";
sequence32 = "./Animations/player_h1slice.dsq h1slice";
sequence33 = "./Animations/player_h1swing.dsq h1swing";
sequence34 = "./Animations/player_h1jumpattack.dsq h1jumpattack";
// phdana stun ->
sequence35 = "./Animations/player_h1stunde.dsq h1stun";
// <- phdana stun
// <- phdana hth
};Anytime you create a new playermodel and want it to have animations, just use the above template, you will notice that the Glowy and Orc share the exact same animation sequences, any player you can base off from the default skeleton of the Orc is going to be able to use these exact sequences, however if you want to use a custom skellie, you will need to create new animations and add it to the template changing the filenames approrpriately.
Next we want to add the ability to choose a race. A simple playerview gui control and a couple of buttons is all I'm using for this, so at the bottom of...
starter.fps/client/gui/AccountGui.gui
add
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 GuiObjectView(modelView) {
profile = "GuiDefaultProfile";
horizSizing = "relative";
vertSizing = "relative";
position = "0 0";
extent = "400 400";
minExtent = "8 2";
visible = "1";
helpTag = "0";
cameraZRot = "0";
forceFOV = "0";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "top";
position = "240 361";
extent = "25 15";
minExtent = "8 8";
visible = "1";
command = "PreviousModel();";
helpTag = "0";
text = "<<";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "top";
position = "280 361";
extent = "25 15";
minExtent = "8 8";
visible = "1";
command = "NextModel();";
helpTag = "0";
text = ">>";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "top";
position = "310 361";
extent = "60 30";
minExtent = "8 8";
visible = "1";
command = "Customize();";
helpTag = "0";
text = "Customize";
};
};
//--- OBJECT WRITE END ---
function ChooseRaceGUI::onWake(%this){
determinePlayerModels();
NextModel();
}
function Customize(){
$Pref::Player::Body = $model_list[$currentmodel];
Canvas.PopDialog(ChooseRaceGUI);
CreateAccount();
}
function determinePlayerModels()
{
echo("Searching for Player models:");
%filespec = "*_player.dts";
$nbmodel = 0;
for(%file = findFirstFile(%filespec); %file !$= ""; %file = findNextFile(%filespec)) {
$model_list[$nbmodel] = %file;
echo( " -> " @ %file );
$nbmodel++;
}
$currentmodel = 0;
}
function PreviousModel()
{
modelView.setEmpty();
if( $nbmodel == 0 ) return;
if( $currentmodel == 0 ) $currentmodel = $nbmodel - 1;
else $currentmodel--;
modelView.setObject($currentmodel,$model_list[$currentmodel], "",0 );
}
function NextModel()
{
modelView.setEmpty();
if( $nbmodel == 0 ) return;
if( $currentmodel == $nbmodel-1 ) $currentmodel = 0;
else $currentmodel++;
modelView.setObject($currentmodel,$model_list[$currentmodel], "",0);
}The above code will enable you to add as many player body types to the menu as you wish, simply by adding them into the data/shapes directory, and suffixing them with _player.dts.
Next we need to make the player's selection mean something to the client and server, so in starter.fps/client/scripts/client_commands.cs add the following.
//This function handles client side Authentication
function LoginAccount(){
if(isObject(ServerConnection)){
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);
}else{
connect($JoinGameAddress);
LoginAccount();
}
}
//This function handles the clients part in creating accounts
function CreateAccount(){
GetBody();
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);
$NewAccount = 1;
}else{
MessageBoxOK("Error!","Your User Name and/or Password and/or Body are empty");
}
}
function GetBody(){
//Looks for a valid race type in the Player::Body and returns what it finds.
if(strpos($Pref::Player::Body,"Orc") != -1 ){
$Pref::Player::Race = "Orc";
}
if(strpos($Pref::Player::Body,"Glowy") != -1 ){
$Pref::Player::Race = "Glowy";
}
}Ok so far we have a much better account gui, and selection of a body model will actually mean something to the client, next we want to customize our character.
Originally I had planned to create some kind of system to tie body size to level, and tie the size of certain body components to particular stats, i.e. an increase in wisdom would produce an increase in head size etc. I began to code this for my advanced tutorial series, and then the custom shape mod came out, and gave me pause to consider. I have integrated the custom shape mod, but for now at least we will stick to the basics of simply getting a functional stats system into the game.
I have to be honest here, due to a typo I made pretty early on in designing the StatsGUI and it's interaction with the server, I ended up with a system that seems more than a little strange, currently the stats are set after the player enters the game rather than before, this is all due to a mistake I made in one line of code, switching a global and a local var, thereby producing some unexpected and might I say frustrating results. Anyways my work around was to push the StatsGUI after the first login, and after missionload, but before the PlayGUI was displayed. For this reason, I will leave the actual launching of StatsGUI to you, a good place would in fact when the Accept button is pressed in the ChooseRaceGUI, but then you would need to modify the SetStats function to not Push the PlayGUI, but instead run the create account function, wait a few seconds and then send the Stats, not too difficult by anymeans, but it is outside the scope of this tutorial.
Anyways here is starter.fps/client/ui/StatsGUI.gui
//--- OBJECT WRITE BEGIN ---
new GuiControl(StatsGUI) {
profile = "GuiDefaultProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "0 0";
extent = "640 480";
minExtent = "8 2";
visible = "1";
new GuiWindowCtrl(StatsWindow) {
profile = "GuiWindowProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "0 -3";
extent = "640 480";
minExtent = "8 2";
visible = "1";
text = "";
maxLength = "255";
resizeWidth = "1";
resizeHeight = "1";
canMove = "1";
canClose = "1";
canMinimize = "1";
canMaximize = "1";
minSize = "50 50";
closeCommand = "Canvas.PopDialog(StatsGUI);";
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "380 60";
extent = "20 18";
minExtent = "8 2";
visible = "1";
text = "STR";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "380 35";
extent = "19 18";
minExtent = "8 2";
visible = "1";
text = "WIS";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "380 85";
extent = "21 18";
minExtent = "8 2";
visible = "1";
text = "STA";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "380 110";
extent = "20 18";
minExtent = "8 2";
visible = "1";
text = "END";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "380 135";
extent = "20 18";
minExtent = "8 2";
visible = "1";
text = "DEX";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "380 160";
extent = "22 18";
minExtent = "8 2";
visible = "1";
text = "CHA";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "328 218";
extent = "76 18";
minExtent = "8 2";
visible = "1";
text = "Available Points";
maxLength = "255";
};
new GuiTextCtrl() {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "413 220";
extent = "76 18";
minExtent = "8 2";
visible = "1";
variable = "$Points";
maxLength = "255";
};
new GuiTextCtrl(WIS) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "435 35";
extent = "25 18";
minExtent = "8 2";
visible = "1";
text = $Stats[WIS];
maxLength = "255";
};
new GuiTextCtrl(STR) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "435 65";
extent = "8 18";
minExtent = "8 2";
visible = "1";
variable = "$Stats[STR]";
maxLength = "255";
};
new GuiTextCtrl(STA) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "435 85";
extent = "8 18";
minExtent = "8 2";
visible = "1";
variable = "$Stats[STA]";
maxLength = "255";
};
new GuiTextCtrl(END) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "435 110";
extent = "8 18";
minExtent = "8 2";
visible = "1";
variable = "$Stats[END]";
maxLength = "255";
};
new GuiTextCtrl(DEX) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "435 135";
extent = "25 18";
minExtent = "8 2";
visible = "1";
variable = "$Stats[DEX]";
maxLength = "255";
};
new GuiTextCtrl(CHA) {
profile = "GuiTextProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "435 160";
extent = "8 18";
minExtent = "8 2";
visible = "1";
variable = "$Stats[CHA]";
maxLength = "255";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "403 35";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(-1,\"WIS\");";
text = "<";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "403 60";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(-1,\"STR\");";
text = "<";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "403 85";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(-1,\"STA\");";
text = "<";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "403 110";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(-1,\"END\");";
text = "<";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "403 135";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(-1,\"DEX\");";
text = "<";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "403 160";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(-1,\"CHA\");";
text = "<";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "475 35";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(1,\"WIS\");";
text = ">";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "475 60";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(1,\"STR\");";
text = ">";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "475 85";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(1,\"STA\");";
text = ">";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "475 110";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(1,\"END\");";
text = ">";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "475 135";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(1,\"DEX\");";
text = ">";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiButtonCtrl() {
profile = "GuiButtonProfile";
horizSizing = "right";
vertSizing = "bottom";
position = "475 160";
extent = "25 25";
minExtent = "8 2";
visible = "1";
command = "incStat(1,\"CHA\");";
text = ">";
groupNum = "-1";
buttonType = "PushButton";
};
new GuiPlayerView(PlayerView) {
profile="GuiDefaultProfile";
horizSizing="relative";
vertSizing="relative";
position="25 25";
extent="280 372";
minExtent="8 2";
visible="1";
cameraZRot="0";
forceFOV="40";
};
new GuiButtonCtrl() {
profile="GuiButtonProfile";
horizSizing="right";
vertSizing="bottom";
position="234 200";
extent="64 20";
minExtent="8 2";
visible="1";
command="setStats();";
text="Accept";
groupNum="-1";
buttonType="PushButton";
helpTag="0";
};
};
};
//--- OBJECT WRITE END ---
$Points = 50;
$Stats[WIS] = 1;
$Stats[STR] = 1;
$Stats[STA] = 1;
$Stats[DEX] = 1;
$Stats[END] = 1;
$Stats[CHA] = 1;
function StatsGUI::OnWake(%this){
%wndwtxt = $Name@" Stats";
StatsWindow.setText(%wndwtxt);
%playerAnim = $userMods @"/data/shapes/player/Animations/player_root.dsq";
echo("PlayerBody = "@$Pref::Player::Body);
PlayerView.setModel($Pref::Player::Body, "", %playerAnim );
//PlayerView.setDNA($Pref::Player::DNA);
UpdateStatsGUI();
}
function incStat(%val,%Stat){
//This function handles incrementing and Decrementing the Stats
//This is accomplished strictly by Addition to and Subtraction from
//$Stats[%Stat] and $Points respectively
//To subtract from a stat pass a negative value
if($Points >= %val && $Stats[%Stat] > 0){
$Points -= %val;
$Stats[%Stat] += %val;
}else{
if($Points >= %val && %val >0){
$Points -= 1;
$Stats[%Stat] += 1;
}
}
UpdateStatsGUI();
echo("Stat "@%Stat@" is now "@$Stats[%Stat]);
}
function UpdateStatsGUI(){
//This function updates all of the GUI stat elements at the same time
WIS.setText($Stats[WIS]);
STR.setText($Stats[STR]);
STA.setText($Stats[STA]);
DEX.setText($Stats[DEX]);
END.setText($Stats[END]);
CHA.setText($Stats[CHA]);
}
function setStats(){
//This function creates a vector of all the stats and sends it to the server
Canvas.PopDialog(StatsGUI);
$StatsVec = $Stats[WIS]@" "@$Stats[STR]@" "@$Stats[STA]@" "@$Stats[DEX]@" "@$Stats[END]@" "@$Stats[CHA];
echo($StatsVec);
CommandToServer('SetStats',$StatsVec);
$NewAccount = 0;
if (Canvas.getContent() != PlayGui.getId()){
Canvas.setContent(PlayGui);
}
}Now you'll notice something a little different about this GUI than any of the others, while sending the changes to the server,instead of looping through each and every stat at each update, we are infact storing everything clientside until the client commits to the change and then sending the server, a vector comprised of the stats, this makes for alot of interesting possibilities that we can explore in the future, and really reduces bandwidth and processing overhead, for now though, lets just work on making these values persistant.
Lets start with the CommandToServer
starter.fps/server/scripts/commands.cs
function serverCmdSetStats(%client,%Stats){
//Might be a good idea to do some range checking in here somewhere
%Stats = detag(%Stats);
//%val = detag(%val);
if(%Stats !$=""){
//%MaxValue = getPlayerPoints(%client);
//I haven't come up with a nice smooth algorithm for generating playerpoints yet, so lets just give 'em a max of 50 for the time being.
%MaxValue = 50;
%totalPoints = checkSum(%Stats);
if(%totalPoints <= %MaxValue){
UpdatePlayerStats(%client,%Stats);
}else{
%Stats = getPlayerStats(%client);
CommandToClient(%client,'UpdateStats',%Stats);
}
}else{
echo("Error bad stat recieved, discarding!");
}
}
function checkSum(%vector)
{
%cs = 0;
%i = 0;
while (getWord(%vector, %i) !$= "")
{
%cs += getWord(%vector, %i);
%i ++;
}
return (%cs);
}Now in starter.fps/server/scripts/dbcommands.cs
$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);
}
function getPlayerStats(%this){
%player = %this.Player;
%name = %this.name;
%query = "SELECT * FROM PlayerStats WHERE Name = \'"@%name@"\'";
echo(%query);
%result = $SQLite.query(%query,0);
if(%result){
%this.Stat[WIS] = $SqLite.getColumn(%result,"WIS");
%this.Stat[STR] = $SqLite.getColumn(%result,"STR");
%this.Stat[STA] = $SqLite.getColumn(%result,"STA");
%this.Stat[END] = $SqLite.getColumn(%result,"END");
%this.Stat[DEX] = $SqLite.getColumn(%result,"DEX");
%this.Stat[CHA] = $SqLite.getColumn(%result,"CHA");
$SqLite.clearResult(%result);
}
return(%this);
}
function getPlayerSkills(%player){
%name = %player.getShapeName();
%query = "SELECT * FROM PlayerSkills WHERE Name = \'"@%name@"\'";
echo(%query);
%result = $SQLite.query(%query,0);
if(%result){
while(!$SqLite.endOfResult(%result)){
%SkillName = $SqLite.getColumn(%result,"Skill");
%player.Skill[%SkillName] = $SqLite.getColumn(%result,"Level");
$SqLite.nextRow(%result);
}
$SqLite.clearResult(%result);
}
}
function getPlayerInventory(%player){
%name = %player.getShapeName();
%query = "SELECT * from Inventory WHERE Name = \'"@%name@"\'";
echo(%query);
%result = $SQLite.query(%query,0);
if(%result){
while(!$SqLite.endOfResult(%result)){
%ItemName = $SqLite.getColumn(%result,"Item");
%Amount = $SqLite.getColumn(%result,"Amount");
%player.setInventory(%ItemName,%Amount);
$SqLite.nextRow(%result);
}
$SqLite.clearResult(%result);
}
}
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 whats up!");
return("Glowy"); //Well we gotta make 'em somethin now don't we
}
}
function getClientLevel(%client){
%query = "SELECT Level FROM Accounts WHERE Name = \'"@%client.name@"\'";
%result = $SqLite.query(%query,0);
echo(%result);
if(%result){
%Level = $SqLite.getColumn(%result,1);
$SqLite.clearResult(%result);
return(%Level);
}else{
return(1);
}
}
function setUpPlayerStats(%AccountName){
//This function creates the players stats at Account Creation
%query = "INSERT INTO PlayerStats VALUES (\'"@%AccountName@"\','1','1','1','1','1','1')";
%result = $SqLite.query(%query, 0);
$SqLite.clearResult(%result);
}
function UpdatePlayerStats(%client,%stats){
%AccountName = %client.name;
if(%AccountName $=""){
echo("Error client.name is empty, try one of these maybe?");
%client.dump();
}else{
//This function Updates the player stats after Account Creation
%query = "DELETE FROM PlayerStats WHERE Name = \'"@%AccountName@"\'";
//DELETE FROM and INSERT INTO were much easier to code for this example than trying to do this function with an UPDATE WHERE, that said, UPDATE is the proper syntax, and I'm just being lazy here
%result = $SqLite.query(%query, 0);
if(%result){
$SqLite.clearResult(%result);
}else{
echo("Error while updating stats for player "@%name@" could not update stat "@%stat@" with value "@%val);
}
%query = "INSERT INTO PlayerStats VALUES (\'"@%AccountName@"\',";
%i = 0;
while (getWord(%stats, %i) !$= "")
{
%Stat =%Stat@"\'"@getWord(%stats, %i)@"\',";
%i ++;
}
%query = %query @ %Stat@")";
%query = strreplace(%query,"\,\)","\)");
echo(%query);
%result = $SqLite.query(%query, 0);
if(%result){
$SqLite.clearResult(%result);
}else{
echo("Error while updating stats for player "@%name);
}
}
}
//Account TABLE functions
function serverCmdValidateAccount(%client,%AccountName,%AccountPassword){
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');
//%client.score = 0;
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){
%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');
//serverCmdValidateAccount(%client,%AccountName,%AccountPassword);
setUpPlayerStats(%AccountName);
}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');
}
}Remember all those table creation statements that were throwing errors on the second and successive runs through? I have moved them all into a file called InitDB.cs I took the time to cleanup the code a little and now this is the file I use immediately after deleting the DB, the only function that is 100% nessecary here is the IntializeDB function, the rest are simply to show how to populate the tables.
//InitDB (C) 2005 Dreamer Inc.
//This script initializes the DB and gets it ready to run
function InitializeDB(){
//This function only ever needs initialized once
//Create Tables
if($Pref::Server::RunOnce != 1){
%query[0] = "CREATE TABLE Items (Item VARCHAR(20),Skill VARCHAR(20),Worth INT(11),Category VARCHAR(20),ClassName VARCHAR(20),Image VARCHAR(40),Description VARCHAR(255))";
%query[1] = "CREATE TABLE Accounts (Name VARCHAR(20),password VARCHAR(20),Race VARCHAR(50),Level INT(11))";
%query[2] = "CREATE TABLE Skills (SkillName VARCHAR(15),MinLevel INT(5))";
%query[3] = "CREATE TABLE PlayerStats (Name VARCHAR(20), WIS INT(5),STR INT(5),STA INT(5),END INT(5),DEX INT(5),CHA INT(5))";
%query[4] = "CREATE TABLE Inventory (Name VARCHAR(20),Item VARCHAR(20),Amount INT(11))";
%query[5] = "CREATE TABLE NPC (Name VARCHAR(20),TextIndex int(11))";
%query[6] = "CREATE TABLE NPCText (TextIndex int(11),NPCWords VarChar(255))";
%query[7] = "CREATE TABLE PlayerSkills (Name VARCHAR(15),Skill VARCHAR(15),Level INT(5))";
for(%x = 0; %x <= 7; %x++){
echo(%query[%x]);
%result = $SqLite.query(%query[%x],0);
$SqLite.clearResult(%result);
}
InitItems();
InitSkills();
$Pref::Server::RunOnce = 1;
}
}
function InitItems(){
//Insert Items
%query[0] = "INSERT INTO Items VALUES ('Berries','Forage','1','Food','StackableMisc','berries','A small bunch of berries')";
%query[1] = "INSERT INTO Items VALUES ('Water','Forage','1','Food','StackableMisc','waterflask','A flask of water')";
%query[2] = "INSERT INTO Items VALUES ('Leaves And Twigs','Forage','1','Tradeskill','StackableMisc','twig','A Twig')";
%query[3] = "INSERT INTO Items VALUES ('Sand','Forage','1','Smithing','StackableMisc','DirtPile','A little dirt')";
%query[4] = "INSERT INTO Items VALUES ('Rusty Dagger','Forage','5','Weapon','NonStackable','Dagger','A Rusty Dagger')";
%query[5] = "INSERT INTO Items VALUES ('Low Quality Arrow','Forage','1','Ammo','StackableMisc','Arrow','A low quality Arrow')";
%query[6] = "INSERT INTO Items VALUES ('Fish','Forage','1','Food','StackableMisc','Fish','A Fish')";
%query[7] = "INSERT INTO Items VALUES ('Copper Coin','Forage','1','Money','StackableMisc','CopperCoin','A Copper Coin')";
%query[8] = "INSERT INTO Items VALUES ('Silver Coin','Forage','10','Money','StackableMisc','SilverCoin','A Silver Coin')";
%query[9] = "INSERT INTO Items VALUES ('Gold Coin','Forage','100','Money','StackableMisc','GoldCoin','A Gold Coin!')";
%query[10] = "INSERT INTO Items VALUES ('Quartz','Forage','5','Gem','StackableMisc','Quartz','A Piece of rough quartz!')";
%query[11] = "INSERT INTO Items VALUES ('Emerald','Forage','15','Gem','StackableMisc','Emerald','An uncut emerald')";
%query[12] = "INSERT INTO Items VALUES ('Sapphire','Forage','50','Gem','StackableMisc','Sapphire','A rough Sapphire')";
%query[13] = "INSERT INTO Items VALUES ('Diamond','Forage','1000','Gem','StackableMisc','Diamond','An uncut Diamond!')";
for(%x = 0; %x <= 13; %x++){
echo(%query[%x]);
%result = $SqLite.query(%query[%x],0);
$SqLite.clearResult(%result);
}
}
function InitSkills(){
//This function sets up the Skills table
//Tradeskills
%query[0] = "INSERT INTO Skills VALUES ('Beg','0')";
%query[1] = "INSERT INTO Skills VALUES ('Forage','0')";
%query[2] = "INSERT INTO Skills VALUES ('WoodChop','0')";
%query[3] = "INSERT INTO Skills VALUES ('Smithing','0')";
%query[4] = "INSERT INTO Skills VALUES ('Fishing','0')";
%query[5] = "INSERT INTO Skills VALUES ('JewelCraft','0')";
%query[6] = "INSERT INTO Skills VALUES ('Mining','0')";
%query[7] = "INSERT INTO Skills VALUES ('Refining','0')";
%query[8] = "INSERT INTO Skills VALUES ('Tailoring','0')";
%query[9] = "INSERT INTO Skills VALUES ('LeatherWork','0')";
//Combat Skills
%query[10] = "INSERT INTO Skills VALUES ('1hs','0')";
%query[11] = "INSERT INTO Skills VALUES ('2hs','0')";
%query[12] = "INSERT INTO Skills VALUES ('Blocking','0')";
%query[13] = "INSERT INTO Skills VALUES ('Kick','0')";
%query[14] = "INSERT INTO Skills VALUES ('Bash','0')";
%query[15] = "INSERT INTO Skills VALUES ('Throw','0')";
%query[16] = "INSERT INTO Skills VALUES ('AtkSpeed','0')";
//Magic and Misc
%query[17] = "INSERT INTO Skills VALUES ('Focus','0')";
%query[18] = "INSERT INTO Skills VALUES ('Energy','0')";
%query[19] = "INSERT INTO Skills VALUES ('RunSpeed','0')";
%query[20] = "INSERT INTO Skills VALUES ('CastSpeed','0')";
for(%x = 0; %x <= 20; %x++){
echo(%query[%x]);
%result = $SqLite.query(%query[%x],0);
$SqLite.clearResult(%result);
}
}Now we want to create our player in
starter.fps/server/scripts/game.cs
function GameConnection::createPlayer(%this, %spawnPoint)
{
if (%this.player > 0) {
// The client should not have a player currently
// assigned. Assigning a new one could result in
// a player ghost.
error( "Attempting to create an angus ghost!" );
}
// Create the player object
echo("Setting PlayerBody");
%player = new Player() {
dataBlock = getPlayerBody(%this.name);
client = %this;
};
MissionCleanup.add(%player);
// Player setup...
%player.setTransform(%spawnPoint);
%player.setShapeName(%this.name);
//Stats, Skills and Inventory
echo("Setting Stats");
getPlayerStats(%this);
echo("Setting Skills");
getPlayerSkills(%player);
echo("Setting Inventory");
getPlayerInventory(%player);
// Update the camera to start with the player
%this.camera.setTransform(%player.getEyeTransform());
// Give the client control of the player
%this.player = %player;
%this.setControlObject(%player);
}Finally replace your PlayerBody datablock in starter.fps/server/scripts/player.cs with the following code.
datablock PlayerData(Orc)
{
renderFirstPerson = false;
emap = true;
className = Armor;
shapeFile = "~/data/shapes/player/orc/Orc_Player.dts";
cameraMaxDist = 3;
computeCRC = true;
canObserve = true;
cmdCategory = "Clients";
cameraDefaultFov = 90.0;
cameraMinFov = 5.0;
cameraMaxFov = 120.0;
debrisShapeName = "~/data/shapes/player/Orc/player_debris.dts";
debris = playerDebris;
aiAvoidThis = true;
minLookAngle = -1.4;
maxLookAngle = 1.4;
maxFreelookAngle = 3.0;
mass = 90;
drag = 0.3;
maxdrag = 0.4;
density = 10;
maxDamage = 100;
maxEnergy = 100;
repairRate = 0.1;
energyPerDamagePoint = 75.0;
rechargeRate = 0.01;
runForce = 48 * 90;
runEnergyDrain = 1;
minRunEnergy = 0;
maxForwardSpeed = 14;
maxBackwardSpeed = 13;
maxSideSpeed = 13;
maxUnderwaterForwardSpeed = 8.4;
maxUnderwaterBackwardSpeed = 7.8;
maxUnderwaterSideSpeed = 7.8;
jumpForce = 8.3 * 90;
jumpEnergyDrain = 0;
minJumpEnergy = 0;
jumpDelay = 15;
recoverDelay = 9;
recoverRunForceScale = 1.2;
minImpactSpeed = 45;
speedDamageScale = 0.4;
boundingBox = "1.2 1.2 2.3";
pickupRadius = 0.75;
// Damage location details
boxNormalHeadPercentage = 0.83;
boxNormalTorsoPercentage = 0.49;
boxHeadLeftPercentage = 0;
boxHeadRightPercentage = 1;
boxHeadBackPercentage = 0;
boxHeadFrontPercentage = 1;
// Foot Prints
decalData = PlayerFootprint;
decalOffset = 0.25;
footPuffEmitter = LightPuffEmitter;
footPuffNumParts = 10;
footPuffRadius = 0.25;
dustEmitter = LiftoffDustEmitter;
splash = PlayerSplash;
splashVelocity = 4.0;
splashAngle = 67.0;
splashFreqMod = 300.0;
splashVelEpsilon = 0.60;
bubbleEmitTime = 0.4;
splashEmitter[0] = PlayerFoamDropletsEmitter;
splashEmitter[1] = PlayerFoamEmitter;
splashEmitter[2] = PlayerBubbleEmitter;
mediumSplashSoundVelocity = 10.0;
hardSplashSoundVelocity = 20.0;
exitSplashSoundVelocity = 5.0;
// Controls over slope of runnable/jumpable surfaces
runSurfaceAngle = 70;
jumpSurfaceAngle = 80;
minJumpSpeed = 20;
maxJumpSpeed = 30;
horizMaxSpeed = 68;
horizResistSpeed = 33;
horizResistFactor = 0.35;
upMaxSpeed = 80;
upResistSpeed = 25;
upResistFactor = 0.3;
footstepSplashHeight = 0.35;
//NOTE: some sounds commented out until wav's are available
// Footstep Sounds
FootSoftSound = FootLightSoftSound;
FootHardSound = FootLightHardSound;
FootMetalSound = FootLightMetalSound;
FootSnowSound = FootLightSnowSound;
FootShallowSound = FootLightShallowSplashSound;
FootWadingSound = FootLightWadingSound;
FootUnderwaterSound = FootLightUnderwaterSound;
//FootBubblesSound = FootLightBubblesSound;
//movingBubblesSound = ArmorMoveBubblesSound;
//waterBreathSound = WaterBreathMaleSound;
//impactSoftSound = ImpactLightSoftSound;
//impactHardSound = ImpactLightHardSound;
//impactMetalSound = ImpactLightMetalSound;
//impactSnowSound = ImpactLightSnowSound;
//impactWaterEasy = ImpactLightWaterEasySound;
//impactWaterMedium = ImpactLightWaterMediumSound;
//impactWaterHard = ImpactLightWaterHardSound;
groundImpactMinSpeed = 10.0;
groundImpactShakeFreq = "4.0 4.0 4.0";
groundImpactShakeAmp = "1.0 1.0 1.0";
groundImpactShakeDuration = 0.8;
groundImpactShakeFalloff = 10.0;
//exitingWater = ExitingWaterLightSound;
observeParameters = "0.5 4.5 4.5";
};
datablock PlayerData(Glowy)
{
renderFirstPerson = false;
emap = true;
className = Armor;
shapeFile = "~/data/shapes/player/glowy/glowy_Player.dts";
cameraMaxDist = 3;
computeCRC = true;
canObserve = true;
cmdCategory = "Clients";
cameraDefaultFov = 90.0;
cameraMinFov = 5.0;
cameraMaxFov = 120.0;
debrisShapeName = "~/data/shapes/player/Orc/player_debris.dts";
debris = playerDebris;
aiAvoidThis = true;
minLookAngle = -1.4;
maxLookAngle = 1.4;
maxFreelookAngle = 3.0;
mass = 90;
drag = 0.3;
maxdrag = 0.4;
density = 10;
maxDamage = 100;
maxEnergy = 100;
repairRate = 0.01;
energyPerDamagePoint = 75.0;
rechargeRate = 0.1;
runForce = 48 * 90;
runEnergyDrain = 1;
minRunEnergy = 0;
maxForwardSpeed = 14;
maxBackwardSpeed = 13;
maxSideSpeed = 13;
maxUnderwaterForwardSpeed = 8.4;
maxUnderwaterBackwardSpeed = 7.8;
maxUnderwaterSideSpeed = 7.8;
jumpForce = 8.3 * 90;
jumpEnergyDrain = 0;
minJumpEnergy = 0;
jumpDelay = 15;
recoverDelay = 9;
recoverRunForceScale = 1.2;
minImpactSpeed = 45;
speedDamageScale = 0.4;
boundingBox = "1.2 1.2 2.3";
pickupRadius = 0.75;
// Damage location details
boxNormalHeadPercentage = 0.83;
boxNormalTorsoPercentage = 0.49;
boxHeadLeftPercentage = 0;
boxHeadRightPercentage = 1;
boxHeadBackPercentage = 0;
boxHeadFrontPercentage = 1;
// Foot Prints
decalData = PlayerFootprint;
decalOffset = 0.25;
footPuffEmitter = LightPuffEmitter;
footPuffNumParts = 10;
footPuffRadius = 0.25;
dustEmitter = LiftoffDustEmitter;
splash = PlayerSplash;
splashVelocity = 4.0;
splashAngle = 67.0;
splashFreqMod = 300.0;
splashVelEpsilon = 0.60;
bubbleEmitTime = 0.4;
splashEmitter[0] = PlayerFoamDropletsEmitter;
splashEmitter[1] = PlayerFoamEmitter;
splashEmitter[2] = PlayerBubbleEmitter;
mediumSplashSoundVelocity = 10.0;
hardSplashSoundVelocity = 20.0;
exitSplashSoundVelocity = 5.0;
// Controls over slope of runnable/jumpable surfaces
runSurfaceAngle = 70;
jumpSurfaceAngle = 80;
minJumpSpeed = 20;
maxJumpSpeed = 30;
horizMaxSpeed = 68;
horizResistSpeed = 33;
horizResistFactor = 0.35;
upMaxSpeed = 80;
upResistSpeed = 25;
upResistFactor = 0.3;
footstepSplashHeight = 0.35;
//NOTE: some sounds commented out until wav's are available
// Footstep Sounds
FootSoftSound = FootLightSoftSound;
FootHardSound = FootLightHardSound;
FootMetalSound = FootLightMetalSound;
FootSnowSound = FootLightSnowSound;
FootShallowSound = FootLightShallowSplashSound;
FootWadingSound = FootLightWadingSound;
FootUnderwaterSound = FootLightUnderwaterSound;
//FootBubblesSound = FootLightBubblesSound;
//movingBubblesSound = ArmorMoveBubblesSound;
//waterBreathSound = WaterBreathMaleSound;
//impactSoftSound = ImpactLightSoftSound;
//impactHardSound = ImpactLightHardSound;
//impactMetalSound = ImpactLightMetalSound;
//impactSnowSound = ImpactLightSnowSound;
//impactWaterEasy = ImpactLightWaterEasySound;
//impactWaterMedium = ImpactLightWaterMediumSound;
//impactWaterHard = ImpactLightWaterHardSound;
groundImpactMinSpeed = 10.0;
groundImpactShakeFreq = "4.0 4.0 4.0";
groundImpactShakeAmp = "1.0 1.0 1.0";
groundImpactShakeDuration = 0.8;
groundImpactShakeFalloff = 10.0;
//exitingWater = ExitingWaterLightSound;
observeParameters = "0.5 4.5 4.5";
};Ok I'm pretty sure that should be everything, you should now be able to create a new player, select his Race/BodyType and then customize his stats. The stats are stored on the client object and can be accessed by %client.Stats[statname], when working serverside and $Stat[statname] when working clientside, since all updates to individual stats happen clientside I am not going to go into the missing ClientCmdUpdateStats, that could be used if you felt the need to adjust a players stats serverside and let the client know about it.
As you may or may not have noticed we are leaving this tutorial with having implemented a Stats system, but not having put it to any good use, my next tutorial will add a few tradeskills and we will see them put to good use in Forage,Bake and Eat, 'A Cake!
#2
04/27/2005 (3:03 pm)
good tutorial This will be of great use in my faction game!!
#3
@Master Treb, BTW what faction game? I've got a simple good vs evil faction system in here, but I'm not seeing any refferences to it in this code (hopefully I didn't let any slip in yet, thats for another tutorial)
04/27/2005 (3:17 pm)
2 posts and no one has rated yet? I'm going to give it a 5 :) *Shamless self promotion*@Master Treb, BTW what faction game? I've got a simple good vs evil faction system in here, but I'm not seeing any refferences to it in this code (hopefully I didn't let any slip in yet, thats for another tutorial)
#4
Anyway since i added the code for the player.cs and the rest i get those memory crashes agian. I have been playing around with it and looking for syntax errors and so on before i posted, that way post something and say oops fixed it lol
Thanks
04/27/2005 (3:27 pm)
I am loveing the tut so far Dreamer, thanks man. But wouldint you know it, i am the bug man or something lol.Anyway since i added the code for the player.cs and the rest i get those memory crashes agian. I have been playing around with it and looking for syntax errors and so on before i posted, that way post something and say oops fixed it lol
Thanks
#5
04/27/2005 (3:43 pm)
I wish you could be more specific about memory errors, something like "Memory Condition xzy in Thread abc", would be helpful. I highly doubt that it's my code since nothing I'm doing here really has to do with memory per'se but I'm not above it either.
#6
Click ok to close or Cancle to debug.
04/27/2005 (4:53 pm)
The instruction at "0x0045d770" referenced memory at "0x00000000". The memory could not be "read".Click ok to close or Cancle to debug.
#7
04/27/2005 (6:42 pm)
Ok when it does that again, select the Debug option and let's figure out exactly where this is breaking at. If you debug it should bring up a section of code where the instruction is breaking.
#9
recaci@yahoo.com
Thanks
04/27/2005 (7:15 pm)
LoL sorry for all the trouble, could you email me the scripts you have working? Be much easer to see if its someting im doing, if not its ok.recaci@yahoo.com
Thanks
#10
I'm not too good at debugging windows apps, and it looks like the SDK you are running was compiled in RELEASE rather than DEBUG mode, thereby removing all the relevant debug information.
Anyways I'm sorry I couldn't be more help there for ya.
04/27/2005 (7:54 pm)
Ok I would suggest cleaning up the posts about this issue you have made in the resource section and try posting the error, it's nature and the steps leadng up to it, in the SDK forums with a topic like, "Memory Error, please help". You're more likely to get help from there with this issue, and get it from windows people who may have experience with this sort of thing.I'm not too good at debugging windows apps, and it looks like the SDK you are running was compiled in RELEASE rather than DEBUG mode, thereby removing all the relevant debug information.
Anyways I'm sorry I couldn't be more help there for ya.
#11
I learn a lot from your step by step tutorials.
Thanks, Dreamer!
04/27/2005 (8:10 pm)
Great Sources!I learn a lot from your step by step tutorials.
Thanks, Dreamer!
#12
04/27/2005 (8:23 pm)
Welcome, and please feel free to rate all of my tutorials as highly as possible :) What can I say that really gives me a sense of pride.
#13
Try that all at the same time, and then if it crashes again, email me the debug output, smorrey@gmail.com.
04/27/2005 (8:28 pm)
@Wayne, why don't you do a completely clean make and rebuild process, set your make target to DEBUG, delete all of your dso's and also the database file "if you are following the tuts exactly it will be called dream".Try that all at the same time, and then if it crashes again, email me the debug output, smorrey@gmail.com.
#14
Maybe, but probably not :)
Good job dreamer on another tutorial of yours!
04/27/2005 (9:11 pm)
Waynwe, not that I know anything, but it might be that any audio you are using might be messed up, if that's what I think the 'eax' was for in that error.Maybe, but probably not :)
Good job dreamer on another tutorial of yours!
#15
04/28/2005 (4:56 am)
Ill give it a shot , but I am useing a clean build of torque 1.3. I had problems with it when i used the lighting pack and addons so i started useing a clean one so i can see how all this works but ill give it a shot and try debug. Thanks
#16
Here is what that statement is really saying
0069150A mov eax,dword ptr [eax+4]
Take whatever is at memory address 0069150A, move it into the eax register, then move the pointer up by the size of whats in the eax register + 4.
So what the problem appears to be is that memory is empty for some reason or another. The best thing to do would be to compile with all the DEBUG information so when this happens again we can find out what instruction is trying to refference that piece of memory, and make it safer. Or better yet remove it if it's unneeded.
04/28/2005 (6:36 am)
@Idiot in this case, EAX (Extended AX) is an internal CPU register, and has nothing to do with the Creative Labs EAX sound prototcol.Here is what that statement is really saying
0069150A mov eax,dword ptr [eax+4]
Take whatever is at memory address 0069150A, move it into the eax register, then move the pointer up by the size of whats in the eax register + 4.
So what the problem appears to be is that memory is empty for some reason or another. The best thing to do would be to compile with all the DEBUG information so when this happens again we can find out what instruction is trying to refference that piece of memory, and make it safer. Or better yet remove it if it's unneeded.
#17
04/29/2005 (12:27 pm)
well i found out that i get the memory crashes when i deleted the dream database after installing #7 resorce but if i leave the old 3k dream db file in and run it where i created a character before #6 it works but i do not have the other data from #7
#18
Delete the db file and ALL of your dso's especially those in the server/scripts directory and try again. Also make sure that the function that creates the database is being called each run and the Player table creation function is being called on first run only.
You could also add some safety to the Player selection function by puting an if(%result){
do what it should do if we had a result.
}else{
Create Tables
Insert Initial Data
Try this function again.
}
My db code is a little shoddy and any time you get a result you should check to make sure you actually got on before trying any of the assignment statments for instance $Sqlite.getColumn(), $Sqlite.numRows() etc and so forth.
Also make sure you have a result before you try to clearResult, that has caused many a segfault too.
I will give some updated code that fixes all of this, in the first of my advanced tutorials in a couple of weeks, in the meantime hopefully that should be enough to get you started.
04/29/2005 (2:07 pm)
You're exeriencing a segmentation fault.Delete the db file and ALL of your dso's especially those in the server/scripts directory and try again. Also make sure that the function that creates the database is being called each run and the Player table creation function is being called on first run only.
You could also add some safety to the Player selection function by puting an if(%result){
do what it should do if we had a result.
}else{
Create Tables
Insert Initial Data
Try this function again.
}
My db code is a little shoddy and any time you get a result you should check to make sure you actually got on before trying any of the assignment statments for instance $Sqlite.getColumn(), $Sqlite.numRows() etc and so forth.
Also make sure you have a result before you try to clearResult, that has caused many a segfault too.
I will give some updated code that fixes all of this, in the first of my advanced tutorials in a couple of weeks, in the meantime hopefully that should be enough to get you started.
#19
Thanks
04/30/2005 (2:13 pm)
Dreamer, any way you can post a link to the compleated code so i can download it and try and see if that is the trouble?Thanks
#20
Then as soon as it crashes send me an email with your console log smorrey@gmail.com
I want to see where this is segfaulting, and that will give me a better idea.
04/30/2005 (5:21 pm)
@Wayne, here's the deal run the game and as soon as it loads open a console and type trace(1);Then as soon as it crashes send me an email with your console log smorrey@gmail.com
I want to see where this is segfaulting, and that will give me a better idea.

Torque Owner Wayne Eversole
Default Studio Name
Ill check it out asap :)