Game Development Community

dev|Pro Game Development Curriculum

TGE Simplification - Part 2: Merging /Starter.fps

by Darrel Cusey · 04/02/2006 (10:32 am) · 11 comments

I see a lot of people asking how to streamline and simplify the TGE script codebase. This second tutorial in the series shows you how to merge the /starter.fps directory, and future tutorials will build on this to show you how to eliminate packaging and module functionality entirely from the TGE scripts to further simplify the TGE script codebase.


Starting Point:
---------------
These tutorials will all build on each other, so it's important that I be very clear about the starting point. I've created the folder c:/mygame, and inside I have these folders and files:

/client
/creator
/server
/starter.fps
DeleteDSOs.bat
DeletePrefs.bat
glu2d3d.dll
main.cs
MyGame.exe (This is my renamed Torque Demo App)
OpenAL32.dll
opengl2d3d.dll
wrap_oal.dll

The /client, /creator and /server folders and the mygame.exe are modified versions of the 1.4 HEAD, with all modifications completed as described in the first tutorial in this series.

The files opengl2d3d.dll and glu2d3d.dll are all fresh builds from the HEAD. If you are unsure how to create a new Visual C++ Project from the Torque SDK, please see this tutorial.

All other files in the project root are just copied over from the "example" folder. The main.cs at the root currently has this line of code at the top: $defaultGame = "starter.fps";

If you haven't done so already, complete all the steps in the first tutorial in this series first before starting this one, as each tutorial builds on the previous one. Run the DeleteDSOs.bat and DeletePrefs.bat once each and you should be ready.


Second Goal:
------------
Our next objective will be to merge the /starter.fps folder by moving all the subfolders in /starter.fps out to the project root (/client, /server or /creator). You should be able to go through these tutorials with any of the "mods" (like starter.racing, tutorial.base or demo), but I haven't tried any of these yet -- so, you'll probably have to make in-flight adjustments if you try anything other than starter.fps.

In the end, I'd like to see a directory structure like this:

/client
/creator
/server




Conventions:
------------

Throughout this tutorial, I'll be asking you to test your "Torque Demo" application (I use the name "mygame.exe" in the text below). It is important that you really do test your demo app at each of these points -- we'll be making a lot of changes and trying to make them all at once without testing at each CHECKPOINT would be a debugging nightmare.

I will use backslashes to show the path, from project root, to a C++ source code file. I'm showing the full path so that you can be absolutely sure that you are editing the correct file.

I will use forward slashes to show the path, from your game folder root (c:/mygame in my case), to a Torque Script file.



Phase 1: Changing Data Ownership
--------------------------------


1. Move the folder /starter.fps/data and all of its contents to /client

2. In /starter.fps/main.cs, change this:

exec("./data/init.cs");

to this:

exec("client/data/init.cs");

3. In /server/init.cs change this:

// Specify where the mission files are.
   $Server::MissionFileSpec = "*/missions/*.mis";

to this:

// Specify where the mission files are.
   $Server::MissionFileSpec = "*/data/missions/*.mis";

4. Open up /server/data/missions/stronghold.mis, do a search and replace, replacing every instance of:

"~/data/

with this:

"client/data/

Note: We use "~/data/" instead of just "~" as the search term to make sure we're only changing exactly what we want to change

5. Repeat step #4 for each of these files:

...for the client:
starter.fps/client/scripts/audioProfiles.cs
starter.fps/client/scripts/chatHud.cs
starter.fps/client/scripts/optionsDlg.cs


...for the server:
starter.fps/server/scripts/audioProfiles.cs
starter.fps/server/scripts/chimneyfire.cs
starter.fps/server/scripts/crossbow.cs
starter.fps/server/scripts/health.cs
starter.fps/server/scripts/markers.cs
starter.fps/server/scripts/player.cs
starter.fps/server/scripts/weapon.cs

(There should be 65 replacements in 10 files)

Note: If you edited the terrain texture data in the last tutorial, doing a global search and replace will probably "un-fix" it -- but fixing it again is much easier than doing manual edits on all these files. However, we won't be able to fix it yet (so, you'll have to live with the ground looking like snow for awhile). If you go into the Texture Painter, you can see why this is -- only the starter.fps and creator folders are "available" to scripts. This functionality will be back in place, though, by the end of the this tutorial.

CHECKPOINT --> Run DeleteDSOs.bat and DeletePrefs.bat at your game folder root, then run your Torque Demo App (c:/mygame/mygame.exe in my case) to make sure everything worked.




Phase 2: Merging Starter.fps Client UI
--------------------------------------

1. Move all *.png, *.jpg, and *.gui files in /starter.fps/client/ui to /client/ui

2. In /starter.fps/client/init.cs, change this:

// Load up the Game GUIs
   exec("./ui/defaultGameProfiles.cs");
   exec("./ui/PlayGui.gui");
   exec("./ui/ChatHud.gui");
   exec("./ui/playerList.gui");

   // Load up the shell GUIs
   exec("./ui/mainMenuGui.gui");
   exec("./ui/QuitGui.gui");
   exec("./ui/aboutDlg.gui");
   exec("./ui/startMissionGui.gui");
   exec("./ui/joinServerGui.gui");
   exec("./ui/loadingGui.gui");
   exec("./ui/endGameGui.gui");
   exec("./ui/optionsDlg.gui");
   exec("./ui/remapDlg.gui");
   exec("./ui/StartupGui.gui");


to this:


// Load up the Game GUIs
   exec("client/ui/defaultGameProfiles.cs");
   exec("client/ui/PlayGui.gui");
   exec("client/ui/ChatHud.gui");
   exec("client/ui/playerList.gui");

   // Load up the shell GUIs
   exec("client/ui/mainMenuGui.gui");
   // exec("client/ui/QuitGui.gui");  This is not used
   exec("client/ui/aboutDlg.gui");
   exec("client/ui/startMissionGui.gui");
   exec("client/ui/joinServerGui.gui");
   exec("client/ui/loadingGui.gui");
   exec("client/ui/endGameGui.gui");
   exec("client/ui/optionsDlg.gui");
   exec("client/ui/remapDlg.gui");
   exec("client/ui/StartupGui.gui");

3. In /starter.fps/client/ui/customProfiles.cs, search for the text "bitmap =" to find all the path specificiations for the "demo" bitmaps you just oved and update the paths, for example, change this:

bitmap = "./demoWindow";

to this:

bitmap = "client/ui/demoWindow";

4. Repeat the previous step for each "bitmap =" path specification in customProfiles.cs. Since your game won't be a "demo" when you are done, you may want to take this opportunity to change the names of these bitmaps to something more apporopriate like "gameWindow" and "gameScroll" -- just make sure the name matches the bitmap entry in customProfiles.cs

5. In /starter.fps/client/ui/defaultGameProfiles.cs, search for this:

bitmap = "./chatHudBorderArray";

and change to this:

bitmap = "client/ui/chatHudBorderArray";

6. You can delete /starter.fps/client/ui/disclaimer.dtx -- this isn't Realm Wars anymore :-)

7. Move customProfiles.cs and defaultGameProfiles.cs from /starter.fps/client/ui to /client/ui

8. In /starter.fps/client/init.cs, change this:

exec("./ui/customProfiles.cs"); // override the base profiles if necessary


to this:


exec("client/ui/customProfiles.cs"); // override the base profiles if necessary

9. Delete the folder /starter.fps/client/ui

CHECKPOINT --> Run DeleteDSOs.bat and DeletePrefs.bat at your game folder root, then run your Torque Demo App to make sure everything worked.



Phase 3: Merging Starter.fps Client Scripts
-------------------------------------------

Here we'll see the first occurence of "name collision" between the module-specific script names and default script names. Specifically there is a "missionDownload.cs" in both the /starter.fps/client/scripts folder and the /client folder. To prevent this collision, we could move all the /starter.fps/client/scripts scripts to a new /client/scripts folder -- but having a seperate "scripts" folder doesn't make sense to me since the contents of all the folders are scripts. Instead, we're going to rename the missionDownload.cs in /starter.fps/client/scripts, and then adjust the exec() command that executes it.


1. Rename /starter.fps/client/scripts/missionDownload.cs to "missionDownloadGui.cs"

2. Move the entire contents of /starter.fps/client/scripts to /client

3. Update the paths for the scripts you just moved in /starter.fps/client/init.cs. For example, change:

exec("./scripts/client.cs");


to this:


exec("client/client.cs");


4. Repeat the previous step for each "./scripts/..." path in init.cs

Note: Don't overlook the paths for audioProfiles.cs and default.bind.cs -- these 2 are in different sections than the rest

5. Delete the folder /starter.fps/client/scripts

6. Move the file /starter.fps/client/init.cs to /client

7. In /starter.fps/main.cs, change this:

exec("./client/init.cs");

to this:

exec("client/init.cs");

8. Move the file /starter.fps/client/defaults.cs to /client

9. In /starter.fps/main.cs, change this:

exec("./client/defaults.cs");

to this:

exec("client/defaults.cs");

10. Delete the folder /starter.fps/client

CHECKPOINT --> Run DeleteDSOs.bat and DeletePrefs.bat at your game folder root, then run your Torque Demo App to make sure everything worked.



Phase 4: Merging Starter.fps Server
-----------------------------------

We have 2 name collisions happening in the starter.fps -- game.cs and commands.cs. Both of these files already exist in the /server folder. We could rename these like we did for the client, but that won't be necessary in this case -- neither of these scripts has any functionality of their own and simply define functions. Because of this, we're just going to add their functions to the existing scripts in /server.

1. Copy the entire contents of /starter.fps/server/scripts/commands.cs and paste it onto the end of /server/commands.cs

2. Delete /starter.fps/server/scripts/commands.cs

3. In /starter.fps/server/init.cs, remove or comment out this line:

exec("./scripts/commands.cs");


4. Replace /starter.fps/server/game.cs with this (this version is the combination of game.cs from starter.fps and server):

//-----------------------------------------------------------------------------
// Torque Game Engine 
// Copyright (C) GarageGames.com, Inc.
//-----------------------------------------------------------------------------

// Game duration in secs, no limit if the duration is set to 0
$Game::Duration = 20 * 60;

// When a client score reaches this value, the game is ended.
$Game::EndGameScore = 30;

// Pause while looking over the end game screen (in secs)
$Game::EndGamePause = 10;

//-----------------------------------------------------------------------------
function onServerCreated()
{
   // Invoked by createServer(), when server is created and ready to go

   // Server::GameType is sent to the master server.
   // This variable should uniquely identify your game and/or mod.
   $Server::GameType = "FPS Starter Kit";

   // Server::MissionType sent to the master server.  Clients can
   // filter servers based on mission type.
   $Server::MissionType = "Deathmatch";
   
   // GameStartTime is the sim time the game started. Used to calculated
   // game elapsed time.
   $Game::StartTime = 0;

   // Load up all datablocks, objects etc.  This function is called when
   // a server is constructed.
   exec("./audioProfiles.cs");
   exec("./camera.cs");
   exec("./markers.cs"); 
   exec("./triggers.cs"); 
   exec("./inventory.cs");
   exec("./shapeBase.cs");
   exec("./item.cs");
   exec("./health.cs");
   exec("./staticShape.cs");
   exec("./weapon.cs");
   exec("./radiusDamage.cs");
   exec("./crossbow.cs");
   exec("./player.cs");
   exec("./chimneyfire.cs");
   exec("./aiPlayer.cs");

   // Keep track of when the game started
   $Game::StartTime = $Sim::Time;
   
   
   // For backwards compatibility...
   createGame();
}
//-----------------------------------------------------------------------------
function onServerDestroyed()
{
   // Invoked by destroyServer(), right before the server is destroyed
   destroyGame();
}
//-----------------------------------------------------------------------------
function onMissionLoaded()
{
   // Called by loadMission() once the mission is finished loading
   startGame();
   
}
//-----------------------------------------------------------------------------
function onMissionEnded()
{
   // Called by endMission(), right before the mission is destroyed
   cancel($Game::Schedule);
   $Game::Running = false;
   $Game::Cycling = false;
   endGame();
}
//-----------------------------------------------------------------------------
function onMissionReset()
{
   // Called by resetMission(), after all the temporary mission objects
   // have been deleted.
}


//-----------------------------------------------------------------------------
// These methods are extensions to the GameConnection class. Extending
// GameConnection make is easier to deal with some of this functionality,
// but these could also be implemented as stand-alone functions.
//-----------------------------------------------------------------------------


//-----------------------------------------------------------------------------
function GameConnection::onClientEnterGame(%this)
{
   // Called for each client after it's finished downloading the
   // mission and is ready to start playing.
   // Tg: Should think about renaming this onClientEnterMission
   commandToClient(%this, 'SyncClock', $Sim::Time - $Game::StartTime);

   // Create a new camera object.
   %this.camera = new Camera() {
      dataBlock = Observer;
   };
   MissionCleanup.add( %this.camera );
   %this.camera.scopeToClient(%this);

   // Setup game parameters, the onConnect method currently starts
   // everyone with a 0 score.
   %this.score = 0;

   // Create a player object.
   %this.spawnPlayer();
   
}
//-----------------------------------------------------------------------------
function GameConnection::onClientLeaveGame(%this)
{
   // Call for each client that drops
   // Tg: Should think about renaming this onClientLeaveMission
   if (isObject(%this.camera))
      %this.camera.delete();
   if (isObject(%this.player))
      %this.player.delete();
}
//-----------------------------------------------------------------------------
function GameConnection::onLeaveMissionArea(%this)
{
   // The control objects invoked this method when they
   // move out of the mission area.
}
//-----------------------------------------------------------------------------
function GameConnection::onEnterMissionArea(%this)
{
   // The control objects invoked this method when they
   // move back into the mission area.
}
//-----------------------------------------------------------------------------
function GameConnection::onDeath(%this, %sourceObject, %sourceClient, %damageType, %damLoc)
{
   // Clear out the name on the corpse
   %this.player.setShapeName("");

   // Switch the client over to the death cam and unhook the player object.
   if (isObject(%this.camera) && isObject(%this.player)) {
      %this.camera.setMode("Corpse",%this.player);
      %this.setControlObject(%this.camera);
   }
   %this.player = 0;

   // Doll out points and display an appropriate message
   if (%damageType $= "Suicide" || %sourceClient == %this) {
      %this.incScore(-1);
      messageAll('MsgClientKilled','%1 takes his own life!',%this.name);
   }
   else {
      %sourceClient.incScore(1);
      messageAll('MsgClientKilled','%1 gets nailed by %2!',%this.name,%sourceClient.name);
      if (%sourceClient.score >= $Game::EndGameScore)
         cycleGame();
   }
}
//-----------------------------------------------------------------------------
function GameConnection::spawnPlayer(%this)
{
   // Combination create player and drop him somewhere
   %spawnPoint = pickSpawnPoint();
   %this.createPlayer(%spawnPoint);
}   
//-----------------------------------------------------------------------------
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
   %player = new Player() {
      dataBlock = PlayerBody;
      client = %this;
   };
   MissionCleanup.add(%player);

   // Player setup...
   %player.setTransform(%spawnPoint);
   %player.setShapeName(%this.name);
   
   // Starting equipment
   %player.setInventory(Crossbow,1);
   %player.setInventory(CrossbowAmmo,10);
   %player.mountImage(CrossbowImage,0);

   // 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);
}


//-----------------------------------------------------------------------------
// Functions that implement game-play
// These are here for backwards compatibilty only, games and/or mods should
// really be overloading the server and mission functions listed ubove.
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
function startGame()
{
   // This is where the game play should start
   // The default onMissionLoaded function starts the game.
   if ($Game::Running) {
      error("startGame: End the game first!");
      return;
   }

   // Inform the client we're starting up
   for( %clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++ ) {
      %cl = ClientGroup.getObject( %clientIndex );
      commandToClient(%cl, 'GameStart');

      // Other client specific setup..
      %cl.score = 0;
   }

   // Start the game timer
   if ($Game::Duration)
      $Game::Schedule = schedule($Game::Duration * 1000, 0, "onGameDurationEnd" );
   $Game::Running = true;
   
   // Start the AIManager
   new ScriptObject(AIManager) {};
   MissionCleanup.add(AIManager);
   AIManager.think();
}
//-----------------------------------------------------------------------------
function endGame()
{
   // This is where the game play should end
   // The default onMissionEnded function shuts down the game.
   if (!$Game::Running)  {
      error("endGame: No game running!");
      return;
   }
   
   // Stop the AIManager
   AIManager.delete();

   // Stop any game timers
   cancel($Game::Schedule);

   // Inform the client the game is over
   for( %clientIndex = 0; %clientIndex < ClientGroup.getCount(); %clientIndex++ ) {
      %cl = ClientGroup.getObject( %clientIndex );
      commandToClient(%cl, 'GameEnd');
   }

   // Delete all the temporary mission objects
   resetMission();
   $Game::Running = false;
}
//-----------------------------------------------------------------------------
function onGameDurationEnd()
{
   // This "redirect" is here so that we can abort the game cycle if
   // the $Game::Duration variable has been cleared, without having
   // to have a function to cancel the schedule.
   if ($Game::Duration && !isObject(EditorGui))
      cycleGame();
}
//-----------------------------------------------------------------------------
function createGame()
{
   // This function is called by onServerCreated (ubove)
}
//-----------------------------------------------------------------------------
function destroyGame()
{
   // This function is called by onServerDestroyed (ubove)
}
//-----------------------------------------------------------------------------
function cycleGame()
{
   // This is setup as a schedule so that this function can be called
   // directly from object callbacks.  Object callbacks have to be
   // carefull about invoking server functions that could cause
   // their object to be deleted.
   if (!$Game::Cycling) {
      $Game::Cycling = true;
      $Game::Schedule = schedule(0, 0, "onCycleExec");
   }
}
//-----------------------------------------------------------------------------
function onCycleExec()
{
   // End the current game and start another one, we'll pause for a little
   // so the end game victory screen can be examined by the clients.
   endGame();
   $Game::Schedule = schedule($Game::EndGamePause * 1000, 0, "onCyclePauseEnd");
}
//-----------------------------------------------------------------------------
function onCyclePauseEnd()
{
   $Game::Cycling = false;

   // Just cycle through the missions for now.
   %search = $Server::MissionFileSpec;
   for (%file = findFirstFile(%search); %file !$= ""; %file = findNextFile(%search)) {
      if (%file $= $Server::MissionFile) {
         // Get the next one, back to the first if there
         // is no next.
         %file = findNextFile(%search);
         if (%file $= "")
           %file = findFirstFile(%search);
         break;
      }
   }
   loadMission(%file);
}


//-----------------------------------------------------------------------------
// Support functions
//-----------------------------------------------------------------------------

//-----------------------------------------------------------------------------
function pickSpawnPoint() 
{
   %groupName = "MissionGroup/PlayerDropPoints";
   %group = nameToID(%groupName);

   if (%group != -1) {
      %count = %group.getCount();
      if (%count != 0) {
         %index = getRandom(%count-1);
         %spawn = %group.getObject(%index);
         return %spawn.getTransform();
      }
      else
         error("No spawn points found in " @ %groupName);
   }
   else
      error("Missing spawn points group " @ %groupName);

   // Could be no spawn points, in which case we'll stick the
   // player at the center of the world.
   return "0 0 300 1 0 0 0";
}

5. In /starter.fps/server/init.cs, remove or comment out this line:

exec("./scripts/game.cs");

6. Also in /starter.fps/server/init.cs, change this:

exec("./scripts/centerPrint.cs");


to this:


exec("server/centerPrint.cs");

7. Move /starter.fps/server/init.cs to /server

8. In /starter.fps/main.cs, change this:

exec("./server/init.cs");


to this:


exec("server/init.cs");

9. Move /starter.fps/server/defaults.cs to /server

10. Update the paths for defaults and prefs in /starter.fps/main.cs so they look like this:

// Load up defaults console values.

// Defaults console values
exec("client/defaults.cs");
exec("server/defaults.cs");

// Preferences (overide defaults)
exec("client/prefs.cs");
exec("server/prefs.cs");

Note: You can delete the file /starter.fps/server/banlist.cs -- once we merge main.cs the banlist will be re-created under /server

11. Delete the folder /starter.fps/server

CHECKPOINT --> Run DeleteDSOs.bat and DeletePrefs.bat at your game folder root, then run your Torque Demo App to make sure everything worked.


Phase 5: Merging Starter.fps main.cs
------------------------------------

Replace your root /main.cs with this:

$displayHelp = false;
$logModeSpecified = false;

$isClient = false;
$isServer = false;
$isCreator = false;
$isDedicated = false;

$userMods = "client";
$modcount = 1;


//------------------------------------------------------------------------------
// Process command line arguments

for ($i = 1; $i < $Game::argc ; $i++)
{
   $arg = $Game::argv[$i];
   $nextArg = $Game::argv[$i+1];
   $hasNextArg = $Game::argc - $i > 1;

   switch$ ($arg)
   {
      //--------------------
      case "-dedicated":
        $isDedicated = true;
	      $argUsed[$i]++;
echo("dedicated mode detected.");
      //--------------------
      case "-mission":
            $argUsed[$i]++;
            if ($hasNextArg) {
               $missionArg = $nextArg;
               $argUsed[$i+1]++;
               $i++;
            }
            else
               error("Error: Missing Command Line argument. Usage: -mission <filename>");

      //--------------------
      case "-log":
      $argUsed[$i]++;
      if ($hasNextArg)
      {
         // Turn on console logging
         if ($nextArg != 0)
         {
            // Dump existing console to logfile first.
            $nextArg += 4;
         }
         setLogMode($nextArg);
         $logModeSpecified = true;
         $argUsed[$i+1]++;
         $i++;
      }
      else
         error("Error: Missing Command Line argument. Usage: -log <Mode: 0,1,2>");

      //--------------------
      case "-console":
         enableWinConsole(true);
         $argUsed[$i]++;

      //--------------------
      case "-jSave":
         $argUsed[$i]++;
         if ($hasNextArg)
         {
            echo("Saving event log to journal: " @ $nextArg);
            saveJournal($nextArg);
            $argUsed[$i+1]++;
            $i++;
         }
         else
            error("Error: Missing Command Line argument. Usage: -jSave <journal_name>");

      //--------------------
      case "-jPlay":
         $argUsed[$i]++;
         if ($hasNextArg)
         {
            playJournal($nextArg,false);
            $argUsed[$i+1]++;
            $i++;
         }
         else
            error("Error: Missing Command Line argument. Usage: -jPlay <journal_name>");

      //--------------------
      case "-jDebug":
         $argUsed[$i]++;
         if ($hasNextArg)
         {
            playJournal($nextArg,true);
            $argUsed[$i+1]++;
            $i++;
         }
         else
            error("Error: Missing Command Line argument. Usage: -jDebug <journal_name>");

      //-------------------
      case "-help":
         $displayHelp = true;
         $argUsed[$i]++;

      //-------------------
      default:
        $argUsed[$i]++;
        //We've removed the ability for client to run arbitrary modules
   }
}

  
//-----------------------------------------------------------------------------
function onStart()
{     
   setRandomSeed();

   echo("\n\n--------- Game Initialization ---------\n\n");
   
      // Now you can mix-and-match the functionaltiy you want just 
      // by changing which of the 3 folders (/client, /server, /creator) you
      // distribute.
      
      // There are several good articles on the forums that will show you how to 
      // prepare your game for distribution.  
      
      // Traditional (current setup):  Clients can run their own servers, connect 
      // to dedicated server (LAN or Internet), and use the creator.
      // The traditional setup matches exactly the standard TGE 1.4 functionality.
      // The traditional setup includes all 3 folder (/client, /server, /creator).
      
      // Dedicated Server Only: Distribute only the /server and /client folders
      
      // Builder and Client Only: Distribute the /client and /creator folders only
      
      // Client Only: Distribute the /client folder only
      
      if (isFile("client/init.cs")){
        // Client Startup
        exec("client/canvas.cs");
        exec("client/audio.cs");
        exec("client/defaults.cs");
        exec("client/prefs.cs");
        exec("client/init.cs");
        exec("client/data/init.cs");
        $isClient = true;
      } 
      else {
        error("\n\n ERROR: /client/init.cs not found.  The client directory is required for all distributions.");
        quit();
      }
      
      if (isFile("server/init.cs")) {
        //Server Startup
        exec("server/defaults.cs");
        exec("server/prefs.cs");
        exec("server/init.cs");
        $isServer = true;
      }
      
      if (isFile("creator/init.cs")){
        exec("creator/init.cs");
        $isCreator = true;
      }
      
      if ($isServer) {
        initServer();
        if ($isDedicated){
         initDedicated();
        }
      }
      
      if (!$isDedicated && $isClient) {
        initClient();
        if ($isCreator){
          initCreator();
        }
      }
      
   echo("\n\n--------- Engine Initialization Complete ---------\n\n");
}

function onExit()
{  
   if ($isServer){
      echo("Exporting server prefs");
      export("$Pref::Server::*", "/server/prefs.cs", false);
      BanList::Export("/server/banlist.cs");
   }
   else {
      echo("Exporting client prefs");
      export("$pref::*", "/client/prefs.cs", false);

      echo("Exporting client config");
      if (isObject(moveMap))
         moveMap.save("/client/config.cs", false); 
    
   }  
}

function parseArgs()
{  
}   

package Help {
   function onExit() {
      // Override onExit when displaying help
   }
};

function displayHelp() {
   activatePackage(Help);

      // Notes on logmode: console logging is written to console.log.
      // -log 0 disables console logging.
      // -log 1 appends to existing logfile; it also closes the file
      // (flushing the write buffer) after every write.
      // -log 2 overwrites any existing logfile; it also only closes
      // the logfile when the application shuts down.  (default)

   error(
      "MyGame command line options:\n"@
      "  -log <logmode>         Logging behavior; see main.cs comments for details\n"@
      "  -console               Open a separate console\n"@
      "  -jSave  <file_name>    Record a journal\n"@
      "  -jPlay  <file_name>    Play back a journal\n"@
      "  -jDebug <file_name>    Play back a journal and issue an int3 at the end\n"@
      "  -help                  Display this help message\n"
   );
}


//--------------------------------------------------------------------------


if (!$logModeSpecified) {
   setLogMode(6);
}

setModPaths($userMods);

NextToken($userMods, currentMod, ";");


// Parse the command line arguments
echo("--------- Parsing Arguments ---------");
parseArgs();

// Either display the help message or startup the app.
if ($displayHelp) {
   enableWinConsole(true);
   displayHelp();
   quit();
}
else {
  
  onStart();

}

// Display an error message for unused arguments
for ($i = 1; $i < $Game::argc; $i++)  {
   if (!$argUsed[$i])
      error("Error: Unknown command line argument: " @ $Game::argv[$i]);
}


CHECKPOINT --> Run DeleteDSOs.bat and DeletePrefs.bat at your game folder root, then run your Torque Demo App to make sure everything worked.

CONGRATULATIONS!!!

That's it, /common and /starter.fps have now been merged into the base script code and you can now easily mix-and-match the functionality you want in a distributions just by either including or not including the 3 folders, main.cs takes care of the rest. I should note that the 3 folders aren't 100% independent of each other -- the /client folder is always required because it houses your data.

Just to save you the trouble, I tried a configuration where /server housed all the data and no data was distributed with the /client. While this should work in theory, the time it takes a /client-only install to download all the data required for even a very simple simulation over the Internet (even with a 3Meg broadband connection) is considerable. And, while it's true that the client would only have to do this large download the first time they play your game -- that's not exactly what you want your players doing the first time they play (the 30-30-30 rule comes into effect here). You're much better off (IMHO) distributing your models, texture, sounds, etc. to the client with the /client module -- and then when the client connects for the first time, they just download updates.

Packaging functionality, module functionality, and command-line help/options are _almost_ gone at this point. With /common and /starter.fps gone, their modules, packages, and specialized command-line processing went with them. There are still various and sundry "mod" global variables floating about ($usermods for example) because different parts of the engine depend on them. For example, /client isn't really a module at this point -- it's just there so /creator knows where to look for data. However, it should be relatively easy to get rid of them, but I'll save that for the next tutorial.

Edit: Updated the links to the first tutorial in the series.

#1
04/15/2006 (1:05 pm)
Good job, again :)
#2
08/12/2006 (8:35 pm)
Where is 'InitCreator()' defined? I cant find mention of it anywhere.and w/o that I cant edit anything.
#3
08/18/2006 (7:24 am)
@ Jon:

I had the same problem. What you need to do is rename /creator/main.cs to /creator/init.cs, then edit that file. I commented out all the package stuff and renamed the onStart() function to initCreator(). Then I was able to pull up the mission editor and fix my textures.

@ Darrel:

Thanks for putting together these resources. I like the format. I did have some issues with this second part, however. I was not able to select the Stronghold mission (in fact, the mission selection dialog was empty) until I completed the entire starter.fps merge. I found a few typos as well:

In phase 1, step 3, I believe the correct file is /starter.fps/server/init.cs
In phase 1, step 4, the path should be /client/data/missions/stronghold.mis

Also, in phase 4 I think there's a missing step - moving the files from /starter.fps/server/scripts to /server.

Overall, this is an excellent resource! Thanks again!
#4
09/05/2006 (2:33 am)
why dont you load further tutorials of this series
#5
09/21/2006 (8:29 pm)
I liked it, however....

When I got to phase 4 I was showing fatal errors when I ran the application, searched a long time for the bug and then decided to just work through the whole thing to see what happened...

At the end it worked (thanks Joshua Robinson for the tips in the comments)

Anyway, I am going to do the tutorial again tommorrow night (part 2) just in case I left a few of my own hacks in there when I was trying to debug it in phase 3 (4?)

BTW I admire your dedication when I think of all the time you must have put in to document what you found out the hard way, just to help us......THANKS!
#6
10/04/2006 (7:59 pm)
how do you get the guard to spawn sleeping on his side with setTransform???????
#7
10/04/2006 (8:03 pm)
function AIManager::spawn(%this)
{
//__decl AIManager %this


//===================== SETUP THE TOWN PATROLLING BOT AND 2 SCOUTS ==========================
//SET THE PATH TO BE townPath BECAUSE WE WANT THE BOT TO FOLLOW THE TOWN PATHS
//THE NAME townPath WAS SET IN THE FILE stronghold.mis AT THE BOTTOM.
//THE BOT WILL BEGIN AT THE NODE $TorkStartNode. $TorkStartNode IS INITIALIZED WITH THE GLOBALS
%path = "MissionGroup/Paths/townPath";
%player = AIPlayer::spawnOnPath("Tork","patrol",%path,$TorkStartNode);

//MAKE THE BOT BEGIN FOLLOWING ITS RESPECTIVE PATH
%player.followPath(%path,-1);

echo("is this ggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggggg");

//SET THE CROSSBOW IMAGE IN THE BOTS HANDS AND INITIALIZE HIS CrossbowAmmo TO 1000 BOLTS.
%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//LOAD THE PATROLLER'S TABLE OF NODE DATA DIRECTLY INTO THE BOT. THIS TABLE IS INITIALIZED WITH THE GLOBALS.
//HERE WE ARE LOADING tpath BECAUSE WE WANT HIM TO FOLLOW THE TOWN TABLE
//EXAMPLE: HERE IS THE DATA FOR TOWN NODE 0 ($tpath[0]) AND TOWN NODE 1 ($tpath[1])
// $tpath[0] = "regular 66 18 83 5 99 1";
// $tpath[1] = "door 66 4 99 15";
//bottable[0] will equal $tpath[0], bottable[1] will equal $tpath[1], etc.
//bottable WILL BE USED IN moveToNextNode. IT CAN BE ACCESSED THERE AND WITHIN OTHER FUNCTIONS
//BY USING THE FOLLOWING: %this.bottable[%x] WHERE %x IS THE NODE YOU WANT TO ACCESS
for(%i=0; %i<$TownNumberNodes; %i++)
{
%player.bottable[%i] = $tpath[%i];
}
//SETUP THE TWO SCOUTS AT THEIR RESPECTIVE STARTING NODES. $TorkScout1Node AND $TorkScout2Node ARE INITIALIZED WITH THE GLOBALS
%player = AIPlayer::spawnOnPath("TorkScout1","sleep",%path,$torkScout1Node);//.setTransform("-90 -2 20 0 0 1 0");






%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

%player = AIPlayer::spawnOnPath("TorkScout2","sleep",%path,$TorkScout2Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);



//===================== SETUP THE LAKE PATROLLING BOT AND 2 SCOUTS ==========================
//SET THE PATH TO BE lakePath BECAUSE WE WANT THE BOT TO FOLLOW THE LAKE PATHS
//THE NAME lakePath WAS SET IN THE FILE stronghold.mis AT THE BOTTOM.
//THE BOT WILL BEGIN AT THE NODE $LorkStartNode. $LorkStartNode IS INITIALIZED WITH THE GLOBALS
%path = "MissionGroup/Paths/lakePath";
%player = AIPlayer::spawnOnPath("Lork","patrol",%path,$LorkStartNode);

//MAKE THE BOT BEGIN FOLLOWING ITS RESPECTIVE PATH
%player.followPath(%path,-1);

//SET THE CROSSBOW IMAGE IN THE BOTS HANDS AND INITIALIZE HIS CrossbowAmmo TO 1000 BOLTS.
%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//LOAD THE PATROLLER'S TABLE OF NODE DATA DIRECTLY INTO THE BOT. THIS TABLE IS INITIALIZED WITH THE GLOBALS.
//HERE WE ARE LOADING lpath BECAUSE WE WANT HIM TO FOLLOW THE LAKE TABLE
//EXAMPLE: HERE IS THE DATA FOR LAKE NODE 0 ($lpath[0]) AND LAKE NODE 1 ($lpath[1])
// $lpath[0] = "regular 75 1 99 11";
// $lpath[1] = "regular 75 2 99 10";
//bottable[0] will equal $lpath[0], bottable[1] will equal $lpath[1], etc.
//bottable WILL BE USED IN moveToNextNode. IT CAN BE ACCESSED THERE AND WITHIN OTHER FUNCTIONS
//BY USING THE FOLLOWING: %this.bottable[%x] WHERE %x IS THE NODE YOU WANT TO ACCESS
for(%i=0; %i<$LakeNumberNodes; %i++)
{
%player.bottable[%i] = $lpath[%i];
}

//SETUP THE TWO SCOUTS AT THEIR RESPECTIVE STARTING NODES. $LorkScout1Node AND $LorkScout2Node ARE INITIALIZED WITH THE GLOBALS
%player = AIPlayer::spawnOnPath("LorkScout1","sleep",%path,$LorkScout1Node.setTransform());

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

%player = AIPlayer::spawnOnPath("LorkScout2","sleep",%path,$LorkScout2Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//===================== SETUP THE DIRT PATROLLING BOT AND 2 SCOUTS ==========================
//SET THE PATH TO BE dirtPath BECAUSE WE WANT THE BOT TO FOLLOW THE DIRT PATHS
//THE NAME dirtPath WAS SET IN THE FILE stronghold.mis AT THE BOTTOM.
//THE BOT WILL BEGIN AT THE NODE $DorkStartNode. $DorkStartNode IS INITIALIZED WITH THE GLOBALS
%path = "MissionGroup/Paths/dirtPath";
%player = AIPlayer::spawnOnPath("Dork","patrol",%path,$DorkStartNode);

//MAKE THE BOT BEGIN FOLLOWING ITS RESPECTIVE PATH
%player.followPath(%path,-1);

//SET THE CROSSBOW IMAGE IN THE BOTS HANDS AND INITIALIZE HIS CrossbowAmmo TO 1000 BOLTS.
%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);

//LOAD THE PATROLLER'S TABLE OF NODE DATA DIRECTLY INTO THE BOT. THIS TABLE IS INITIALIZED WITH THE GLOBALS.
//HERE WE ARE LOADING dpath BECAUSE WE WANT HIM TO FOLLOW THE DIRT TABLE
//EXAMPLE: HERE IS THE DATA FOR DIRT NODE 0 ($dpath[0]) AND DIRT NODE 1 ($dpath[1])
// $dpath[0] = "door 99 1";
// $dpath[1] = "regular 99 2";
//bottable[0] will equal $dpath[0], bottable[1] will equal $dpath[1], etc.
//bottable WILL BE USED IN moveToNextNode. IT CAN BE ACCESSED THERE AND WITHIN OTHER FUNCTIONS
//BY USING THE FOLLOWING: %this.bottable[%x] WHERE %x IS THE NODE YOU WANT TO ACCESS
for(%i=0; %i<$DirtNumberNodes; %i++)
{
%player.bottable[%i] = $dpath[%i];
}

//SETUP THE TWO SCOUTS AT THEIR RESPECTIVE STARTING NODES. $DorkScout1Node AND $DorkScout2Node ARE INITIALIZED WITH THE GLOBALS
%player = AIPlayer::spawnOnPath("DorkScout1","sleep",%path,$DorkScout1Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);


%player = AIPlayer::spawnOnPath("DorkScout2","sleep",%path,$DorkScout2Node);

%player.mountImage(CrossbowImage,0);
%player.setInventory(CrossbowAmmo,1000);




return; //*TSL changed
}
#8
10/04/2006 (8:05 pm)
//-----------------------------------------------------------------------------
//
//*TSL All state machine functions follow:
//

///Summary: Bot SM (StateMachine) called for each alive Bot every 500ms
///Parameters: %this is the Bot handle
function AIPlayer::SM(%this) //*TSL wrote entire function
{
//__decl AIPlayer %this

//echo("botType ",%this.botType); //testing dynamic variable

//a dead Bot shouldn't do much of anything....
if (%this.botType $= "dead")
return;

//A patrolling Bot will continue to patrol until he
//is dead (disabled must execute) or
//is damaged (damage and ondamage must execute) or
//is alarmed by seeing a human or
//must awaken his sleeping scouts or
//hears a noise or
//smells a human and initiates an ambush or
//realizes another patrol ork has not waved in 5 minutes or
//must wait and wave to the other patrol orks or
//is asked to join in an ambush another ork initiates or
//......
if (%this.botType $= "patrol"){
// if (%this.MSdoscan())
// %this.MSattack();
// else if (%this.awakenScouts) //a boolean
// %this.MSawakenScouts;
// else if (%this.hearsANoise())
// %this.MShearsANoise;
// else if (%this.smelledHuman())
// %this.SMcreateAmbush();
// else if (%this.missingWave())
// %this.MSwaitForWave(elapsedTime);
// else if (%this.myWaveTime(elapsedTime))
// %this.MSwaitandWave(elapsedTime);
// else if ($ambush) //a boolean
// %this.SMjoinAmbush();
// else {
// %this.getDataBlock().DoScan(%this);
%this.DoScan();
// SMpatrol();
// }
%this.schedule(500,SM);
return;
}

if (%this.botType $= "scout"){
// SMscout();
%this.botType = "scoutAttack";
%this.schedule(500,SM);
return;
}

//A sleeping scout will continue to sleep until he
//dies or
//is awoken by his noisy wounded buddy or an explosion or an ork collision or a doorway trigger.
//Then the botType must go from "sleep" to "scout" and he must arm himself
if (%this.botType $= "sleep"){
// if (%this.hearsANoise()){
// %this.MShearsANoise;
// %this.botType = "scout";
// %this.MSarmThySelf();
// }
//***********************sept***19***06**************************************
//***********************sept***19***06**************************************
//if(
// %this.DoScan();
//
// %this.checkForThreat();
// %this.openFire();
// %this.onTargetExitLOS();
// %this.onTargetEnterLOS();

%this.schedule(500,SM);
return;
}

if (%this.botType $= "patrolAttack"){



// ???ut();
%this.schedule(500,SM);
return;
}

// %this.MSerrorRtn("noBotType");
return;
}


//-----------------------------------------------------------------------------

function AIManager::think(%this)
{
//__decl ScriptObject %this

// We could hook into the player's onDestroyed state instead of
// having to "think", but thinking allows us to consider other
// things...
if (!isObject(%this.player))
//%this.player = %this.spawn();
%this.spawn(); //*TSL changed
// %this.schedule(500,think); //*TSL deleted
}
#9
03/12/2007 (1:54 pm)
Hello again, Darrel! So far, so good -- all is working in TGEA to this point. Like many of the others here, I wasn't able to load the mission until the very last step, and then I had to manually relink the terrain textures as described in the comments of the TGEA starter.FPS (http://www.garagegames.com/mg/forums/result.thread.php?qt=57563). Here are the additional changes I had to make to get your resource to work with TGEA, thanks again for a great resource!

TGEA starter.fps Merge Additions

Phase 1:

- For step 3, the file is starter.fps/server/init.cs not server/init.cs

- stronghold.mis is in client/data/missions not server/data/missions

Phase 3:

- Copy entire contents of /starter.fps/client/scripts/missionDownload.cs to the beginning of client/missionDownload.cs

- Copy the entire contents of client/scripts/shaders.cs to the end of /client/shaders.cs

- Copy starter.fps/client/config.cs to client/config.cs

- Move starter.fps/scripts/ to server/

- Rename creator/main.cs to creator/init.cs and replace contents with:

function initCreator()
{
Parent::onStart();
echo( "\n--------- Initializing: Torque Creator ---------" );

// Scripts
exec("./editor/editor.cs");
exec("./editor/particleEditor.cs");
exec("./scripts/scriptDoc.cs");

// Gui's
exec("./ui/creatorProfiles.cs");
exec("./ui/InspectDlg.gui");
exec("./ui/GuiEditorGui.gui");

exec("./ui/lightEditor.gui");
exec("./ui/lightEditorNewDB.gui");
}

- Run mission, use F11 to get Mission Editor, then use "Window" -> "terrain texture painter" and change (using "change" button) all three texture paths to client/data/terrain/grasslands

- Delete old lighting file from /client/data/missions and re-run to relight mission.
#10
09/07/2008 (10:59 am)
Excellent tutorial but... I have a (little) problem with the 3rd step of Phase 1.

I've set the MissionFileSpec's value to "client/data/missions/*.mis" and now when I want to start a mission, the missions' list is empty.

I moved all the content of the "starter.fps/data" directory to "client/data", so I think the path to the "missions" directory is correct.

Thanks for your help!
#11
10/23/2012 (3:56 am)
@ Yannick (and for search ins since Yannick probably isn't working with this any longer *grin*)

I ran into the same issue. I'm hoping that once starter.fps isn't used any longer that it will resolve. I'll post back with a fix once I find it.

I was able to get in game on completing the patch. I'm not sure if it's something I did wrong, or possibly a version conflict, but I can't get into world editor mode (patch applied to 1.4.2).