Game Development Community

Easy to Reproduce Lag Issue (FPS RTS hybrid)

by Vijay Myneni · in RTS Starter Kit · 11/28/2004 (9:39 pm) · 92 replies

Okay, this might not actually be an issue, but it's driving me crazy. Hopefully, someone can try this out and let me know if I'm just seeing things. It's fairly quick to set up and try. This post also doubles as a good set of instructions for the basics of getting the FPS player into your RTS game (I want to ultimately make this a resource combined with instructions for setting up clean switching between RTS and FPS control).

1. Start with a clean RTS Engine build, and a clean starter.RTS project. Also have a starter.fps directory around to grab files from.
2. Grab the server/scripts/player.cs file from starter.fps and copy it to server/scripts/avatars/fpsPlayer.cs in starter.RTS.
3. Grab the data/shapes/player directory from starter.fps and copy it to data/shapes/fpsPlayer in starter.RTS.
4. In server/scripts/avatars/fpsPlayer.cs, change the very first exec line to point to your new data directory:
exec("~/data/shapes/fpsPlayer/player.cs");
5. In server/init.cs, add a line to exec your new fpsPlayer.cs script:
exec("./scripts/avatars/fpsPlayer.cs");
.
6. Repeat step 5 in your server/scripts/core/game.cs file. My understanding is that the exec's should happen in the same order in both these files. I have mine happening right after player.cs gets exec'd. Also, I had to ultimately add a line for scripts/fx/environment.cs into my game.cs, so that it matched up with init.cs. If I don't do this, the game crashes during datablock transfer.
7. In server/scripts/core/gameConnection.cs, replace the existing onClientEnterGame with this:
function GameConnection::onClientEnterGame(%this)
{
   commandToClient(%this, 'SyncClock', $Sim::Time - $Game::StartTime);

   %clientIndex = %this.getClientIndex();
   %this.setTeam(%clientIndex);

   %offset = ((%this.getTeam()-1) * (%unitsPerDir + 5));

   // 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.createFPSPlayer((%offset*5) SPC (%offset*5) SPC "250");
}

8. Also in gameConnection.cs, add the following function:
function GameConnection::createFPSPlayer(%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);
}

Continued...
#41
05/26/2005 (5:04 pm)
Okay here's my executes. I was unsure on which symbol should come before the execute statements in the scripts. This is what i typed in (with the // next to it) I really don't know if I why it is crashing during the mission loading phase. perhaps i can send my console log to someone?

Game.cs (exec portion)

exec("~/server/scripts/audio/audioProfiles.cs");
exec("~/server/scripts/audio/player.cs");
exec("~/server/scripts/fx/player.cs");
// exec("~/scripts/fx/environment.cs");
exec("~/server/scripts/avatars/base.cs");
exec("~/server/scripts/avatars/pathManager.cs");
exec("~/server/scripts/avatars/player.cs");
exec("~/data/shapes/fpsPlayer/player.cs");
exec("~/server/scripts/avatars/bot.cs");
exec("~/server/scripts/avatars/rifleman.cs");
exec("~/server/scripts/avatars/shocker.cs");
exec("./centerPrint.cs");
exec("./commands.cs");
exec("./game.cs");
exec("./centerPrint.cs");
exec("./commands.cs");
exec("./gameConnection.cs");
exec("./item.cs");
exec("./radiusDamage.cs");
exec("./shapeBase.cs");
exec("./teams.cs");
exec("./triggers.cs");
exec("./weapon.cs");
exec("./buffs.cs");
exec("~/server/scripts/globals.cs");
exec("~/server/scripts/items/camera.cs");
exec("~/server/scripts/items/crossbow.cs");
exec("~/server/scripts/items/staticShape.cs");
exec("~/server/scripts/items/building.cs");

function GameConnection::onClientEnterGame(%this)
{
commandToClient(%this, SyncClock, $Sim::Time - $Game::StartTime);

%clientIndex = %this.getClientIndex();
%this.setTeam(%clientIndex);

%offset = ((%this.getTeam() -1) * (%unitsPerDir + 5));

// create a new camera object.
%this.camera = new Camera() {
dataBlock = Observer;
};
Missioncleanup.add( %this.camera );
#42
05/26/2005 (5:08 pm)
Okay here's my executes. I was unsure on which symbol should come before the execute statements in the scripts. This is what i typed in (with the // next to it) I really don't know if I why it is crashing during the mission loading phase. perhaps i can send my console log to someone?

Game.cs (exec portion)

exec("~/server/scripts/audio/audioProfiles.cs");
exec("~/server/scripts/audio/player.cs");
exec("~/server/scripts/fx/player.cs");
// exec("~/scripts/fx/environment.cs");
exec("~/server/scripts/avatars/base.cs");
exec("~/server/scripts/avatars/pathManager.cs");
exec("~/server/scripts/avatars/player.cs");
exec("~/data/shapes/fpsPlayer/player.cs");
exec("~/server/scripts/avatars/bot.cs");
exec("~/server/scripts/avatars/rifleman.cs");
exec("~/server/scripts/avatars/shocker.cs");
exec("./centerPrint.cs");
exec("./commands.cs");
exec("./game.cs");
exec("./centerPrint.cs");
exec("./commands.cs");
exec("./gameConnection.cs");
exec("./item.cs");
exec("./radiusDamage.cs");
exec("./shapeBase.cs");
exec("./teams.cs");
exec("./triggers.cs");
exec("./weapon.cs");
exec("./buffs.cs");
exec("~/server/scripts/globals.cs");
exec("~/server/scripts/items/camera.cs");
exec("~/server/scripts/items/crossbow.cs");
exec("~/server/scripts/items/staticShape.cs");
exec("~/server/scripts/items/building.cs");

function GameConnection::onClientEnterGame(%this)
{
commandToClient(%this, SyncClock, $Sim::Time - $Game::StartTime);

%clientIndex = %this.getClientIndex();
%this.setTeam(%clientIndex);

%offset = ((%this.getTeam() -1) * (%unitsPerDir + 5));

// create a new camera object.
%this.camera = new Camera() {
dataBlock = Observer;
};
Missioncleanup.add( %this.camera );
#43
05/31/2005 (3:10 am)
Hi Marvin,

Not sure why you're having these problems. Following the steps I wrote down, it works for me. I recommend running in debug mode or throwing in a lot of printf's and echo's to figure out what it's hanging on. Are you setting a control object and setting the canvas content to the RTSTSCtrl after loading?
#44
05/31/2005 (7:26 am)
No, forgive my ignorance but what does that do?
#45
08/31/2005 (9:27 am)
Okay I've just tried the same thing again. I checked the log file that was produced when the game ran. It looks like all of the script changes that Vijay mentioned loaded fine.

But I used the RTS world domination mod. (Which worked just fine before) But I still have the same issue. The game crashes when its loading up the objects. Well more specifically it just sits there.

I did add the exec for the ".scripts/enivornment.cs" for both the server init.cs and the game.cs

they look like this

init

exec("./scripts/fx/environment.cs");
exec("./scripts/avatars/base.cs");

game

exec("./scripts/fx/environment.cs");
exec("~/server/scripts/avatars/base.cs");

But alas i get the same problem i had a few months ago. To answer Vijay's question: No I am setting a control object in the canvas. I didn't even touch the canvas.cs file in the "client" folder.

Should I do something to the canvas
#46
10/16/2005 (11:25 am)
Aaargh!

I'm sorry to keep bugging you guys with this same problem but it still won't work.

Has anyone tried this using Stephen's resource mod?

I noticed in the fpsplayer.cs there's lots of calls to other stuff that's in the fps starter kit.

The game now crashes when I hit the ready button. Here's what the console looks like after hitting that button.

Mapping string: PlayerReady to index: 0
Loading compiled script starter.RTS/server/scripts/audio/audioProfiles.cs.
Loading compiled script starter.RTS/server/scripts/audio/player.cs.
Loading compiled script starter.RTS/server/scripts/fx/player.cs.
Missing file: starter.RTS/server/scripts/core/scripts/fx/environment.cs!
Loading compiled script starter.RTS/server/scripts/avatars/base.cs.
Loading compiled script starter.RTS/data/shapes/player/player.cs.
Validation required for shape: starter.RTS/data/shapes/player/player.dts
Loading compiled script starter.RTS/server/scripts/avatars/pathManager.cs.
Loading compiled script starter.RTS/server/scripts/avatars/player.cs.
Missing file: starter.RTS/data/players/player/player.cs!
Loading compiled script starter.RTS/server/scripts/avatars/base.cs.
Loading compiled script starter.RTS/data/shapes/player/player.cs.
Validation required for shape: starter.RTS/data/shapes/player/player.dts
Loading compiled script starter.RTS/server/scripts/avatars/bot.cs.

My question is how important are the missing file messages? Could they be stopping the game from loading?
#47
10/16/2005 (10:59 pm)
Not sure if you mean me when you say "Stephen", but there isn't an fpsplayer.cs in the resource I managed...sounds like you've combined some other resource, or written some stuff yourself.

environment.cs not being there I don't think is a big issue, but missing the players/player/player.cs is probably going to cause you some issues.
#48
10/17/2005 (7:39 am)
Yes Stephen Z i meant you... sorry i should have been a bit clearer :-)

I was reffering to this resource/thread. Originally Vijay took the "player.cs" from the Fps Starter kit, and changed it to "fpsPlayer.cs" in his set of instructions.

hmmm i think i may have figured out at least one problem. I added an exec line for enviorment.cs but I don't remember actually adding it to the "core" folder.... I'll see if that fixes the problem when I get home.

Just curious, but has anyone taken Vijay's idea for Fps/RTS gameplay and used it or modified it for thier own games?

How is everyone's FPS/RTS hybrids coming along?
#49
10/18/2005 (8:54 pm)
Yikes! I finally got a chance to test my theory.. and yes it turns out that my missing enviornment.cs file was causing the sudden closing... but now it freezes up on the loading objects screen. Im really not sure what's going on because after examing the console it looks like the engine is trying to actively load the mission but then just stops trying and echos "ending mission"

Where should I usually look for errors with an issue like this?
#50
10/27/2005 (11:08 am)
All you need to do is work with a clean copy of both RTS/FPS Starter kits.
Then start working with Starter.fps, create a new folder called RTS inside YourFPSrootdir/engine/game
move over
guiRTSTSCtrl.cc,RTSBuilding.cc,rtsPathDebug.cc,RTSProjectile.cc,RTSUnit.cc,visManager.cc
from YourRTSrootdir/engine/game into the new RTS folder and move the matching header files in there also.

Then modify gameconnection.cc, + is the lines you need to add
#include "game/auth.h"
+#include "game/RTS/RTSUnit.h"
+#include "game/RTS/visManager.h"

mConnectArgc = 0;
+mTeam = -1;

comment out the
GameConnection::onAdd() and onRemove() functions
#51
10/27/2005 (11:09 am)
After GameConnection::~GameConnection()

add this whole piece of code
class SetTeamEvent : public NetEvent
{
   S32 mTeam;
public:
   SetTeamEvent(U32 team = 0)
   {
      mTeam = team;
   }
   void pack(NetConnection *, BitStream *bstream)
   {
      bstream->writeInt(mTeam, 5);
   }
   void write(NetConnection *con, BitStream *bstream)
   {
      pack(con, bstream);
   }
   void unpack(NetConnection *, BitStream *bstream)
   {
      mTeam = bstream->readInt(5);
   }
   void process(NetConnection *con)
   {
      if(con->isServerConnection())
      {
         GameConnection *rtscon = dynamic_cast<GameConnection*>(con);
         if(rtscon)
            rtscon->setTeam(mTeam);
      }
   }
   DECLARE_CONOBJECT(SetTeamEvent);
};

class RTSUnitMoveEvent : public NetEvent
{
public:
   SimSet* selection;
   SimSet objects;
   Point2F pos;

   RTSUnitMoveEvent()
   {
      selection = NULL;
      objects.registerObject();
   }

   ~RTSUnitMoveEvent()
   {
      objects.unregisterObject();
   }

   void pack(NetConnection *con, BitStream *bstream)
   {
      bstream->write(pos.x);
      bstream->write(pos.y);

      S32 numUnits = 0;
      for (S32 k = 0; k < selection->size(); k++)
      {
         AssertFatal(dynamic_cast<NetObject*>((*selection)[k]), "Non-NetObject object in move event!");
         NetObject* obj = (NetObject*)((*selection)[k]);
         if (con->getGhostIndex(obj) != -1)
            numUnits++;
      }
      bstream->write(numUnits);

      for (S32 k = 0; k < selection->size(); k++)
      {
         NetObject* obj = (NetObject*)((*selection)[k]);
         S32 id = con->getGhostIndex(obj);
         if (id != -1)
         {
            // Sanity checking...
            numUnits--;
            bstream->writeInt(id, NetConnection::GhostIdBitSize);
         }
      }

      AssertFatal(numUnits == 0, "RTSUnitMoveEvent - Wrote a different number of selections than expected!");
   }

   void write(NetConnection *con, BitStream *bstream)
   {
      pack(con, bstream);
   }

   void unpack(NetConnection *con, BitStream *bstream)
   {
      bstream->read(&pos.x);
      bstream->read(&pos.y);

      S32 numUnits;
      bstream->read(&numUnits);

      for (S32 k = 0; k < numUnits; k++)
      {
         S32 id = bstream->readInt(NetConnection::GhostIdBitSize);
         AssertFatal(id != -1, "RTSUnitMoveEvent::unpack - Unexpected invalid ghost ID got written!");

         NetObject* obj = con->resolveGhost(id);
         if(dynamic_cast<RTSUnit*>(obj))
            objects.addObject(obj);
      }
   }
   void process(NetConnection *con)
   {
      S32 realSize = 0;
      Point3F center(0,0,0);
      for (S32 k = 0; k < objects.size(); k++)
      {
         if (RTSUnit* unit = dynamic_cast<RTSUnit*>(objects[k]))
         {
            center += unit->getPosition();
            realSize++;
         }
      }
      center *= 1.0f / realSize;

      for (S32 k = 0; k < objects.size(); k++)
      {
         RTSUnit* unit = dynamic_cast<RTSUnit*>(objects[k]);

         if(!unit)
            continue;

         Point3F upos = unit->getPosition();
         VectorF offset = upos - center;
         unit->clearAim();
         unit->setMoveDestination(Point3F(pos.x + offset.x, pos.y + offset.y, 0));
      }

      //really really lame, but this keeps us from crashing on disconnect
      while (objects.size())
         objects.removeObject(objects[0]);
   }
   DECLARE_CONOBJECT(RTSUnitMoveEvent);
};
#52
10/27/2005 (11:09 am)
class RTSUnitAttackEvent : public NetEvent
{
public:
   SimSet* selection;
   SimSet objects;
   NetObject* victim;

   RTSUnitAttackEvent()
   {
      selection = NULL;
      objects.registerObject();
   }

   ~RTSUnitAttackEvent()
   {
      objects.unregisterObject();
   }

   void pack(NetConnection *con, BitStream *bstream)
   {
      bstream->writeInt(con->getGhostIndex(victim),NetConnection::GhostIdBitSize);

      S32 numUnits = 0;
      for (S32 k = 0; k < selection->size(); k++)
      {
         AssertFatal(dynamic_cast<NetObject*>((*selection)[k]), "Non-NetObject object in move event!");
         NetObject* obj = (NetObject*)((*selection)[k]);
         if (con->getGhostIndex(obj) != -1)
            numUnits++;
      }

      bstream->write(numUnits);

      for (S32 k = 0; k < selection->size(); k++)
      {
         NetObject* obj = (NetObject*)((*selection)[k]);
         S32 id = con->getGhostIndex(obj);
         if (id != -1)
            bstream->writeInt(id, NetConnection::GhostIdBitSize);
      }
   }
   void write(NetConnection *con, BitStream *bstream)
   {
      pack(con, bstream);
   }
   void unpack(NetConnection *con, BitStream *bstream)
   {
      S32 victimIdx = bstream->readInt(NetConnection::GhostIdBitSize);

      victim = con->resolveGhost(victimIdx);

      if(!victim)
         return;

      S32 numUnits;
      bstream->read(&numUnits);

      for (S32 k = 0; k < numUnits; k++)
      {
         S32 id = bstream->readInt(NetConnection::GhostIdBitSize);

         AssertFatal(id != -1, "RTSUnitAttackEvent::unpack - unexpected ghost ID!");

         NetObject* obj = con->resolveGhost(id);
         objects.addObject(obj);
      }
   }

   void process(NetConnection *con)
   {
      GameBase *rv = dynamic_cast<GameBase*>(victim);

      for (S32 k = 0; k < objects.size(); k++)
      {
         RTSUnit *unit = dynamic_cast<RTSUnit*>(objects[k]);
         unit->setAimObject(rv);
      }

      //really really lame, but this keeps us from crashing on disconnect
      while (objects.size())
         objects.removeObject(objects[0]);
   }
   DECLARE_CONOBJECT(RTSUnitAttackEvent);
};

IMPLEMENT_CO_CLIENTEVENT_V1(SetTeamEvent);
IMPLEMENT_CO_CLIENTEVENT_V1(RTSUnitMoveEvent);
IMPLEMENT_CO_CLIENTEVENT_V1(RTSUnitAttackEvent);
#53
10/27/2005 (11:10 am)
Then after GameConnection::handleConnectionMessage() add
void GameConnection::setTeam(S32 team)
{
   if (mTeam == team)
      return;

   mTeam = team;

   if (!isServerConnection())
      postNetEvent(new SetTeamEvent(team));
}

void GameConnection::sendMoveEvent(SimSet* moveSet, Point2F pos)
{
   if (!isServerConnection())
   {
      RTSUnitMoveEvent* event = new RTSUnitMoveEvent;
      event->pos = pos;
      event->selection = moveSet;
      postNetEvent(event);
   }
}

void GameConnection::sendAttackEvent(SimSet* attackSet, GameBase* victim)
{
   if (!isServerConnection())
   {
      if (getGhostIndex(victim) == -1)
         return;

      RTSUnitAttackEvent* event = new RTSUnitAttackEvent;
      event->selection = attackSet;
      event->victim = victim;
      postNetEvent(event);
   }
}

void GameConnection::setUnitVisible(RTSUnit* unit)
{
   if(!mVisibleUnits.isNull())
      mVisibleUnits->addObject(unit);
}

void GameConnection::setUnitInvisible(RTSUnit* unit)
{
   if(!mVisibleUnits.isNull())
      mVisibleUnits->removeObject(unit);
}

bool GameConnection::isUnitVisible(RTSUnit* unit)
{
   //search!
   if(mVisibleUnits.isNull())
      return false;

   SimSet *s = mVisibleUnits;
   for (U32 k = 0; k < s->size(); k++)
   {
      if ((*s)[k] == unit)
         return true;
   }
   return false;
}
#54
10/27/2005 (11:11 am)
Add these to the end of the file
ConsoleMethod(GameConnection,setTeam,void,3,3,"conn.setTeam(team)"
              "Sets the team associated with this net connection")
{
   argc;
   argv;
   if(!object->isServerConnection())
      object->setTeam(dAtoi(argv[2]));
}

ConsoleMethod(GameConnection, getTeam, S32, 2, 2, "conn.getTeam()"
              "Gets the team associated with this net connection")
{
   argc;
   argv;
   return object->getTeam();
}

ConsoleMethod( GameConnection, sendMoveEvent, void, 4, 4, "(clientSrc, position)"
              "Sends an update to the clientDest that clientSrc has moved their units to a position")
{
   SimSet *set;

   if (!Sim::findObject(dAtoi(argv[2]), set))
   {
      Con::errorf("GameConnection::sendMoveEvent - expected SimSet as first argument.");
      return;
   }

   Point2F pos;
   dSscanf(argv[3], "%f %f", &pos.x, &pos.y);
   object->sendMoveEvent(set, pos);
}

ConsoleMethod( GameConnection, sendAttackEvent, void, 4, 4, "(clientSrc, victim)"
              "Sends an update that clientSrc has attacked the victim unit")
{
   SimSet *set;
   GameBase *victim;
   if (!Sim::findObject(dAtoi(argv[2]), set) || !Sim::findObject(dAtoi(argv[3]), victim))
   {
      Con::errorf("GameConnection::sendAttackEvent - failed to find clientSrc or victim");
      return;
   }

   object->sendAttackEvent((SimSet*)set, (GameBase*)victim);
}

ConsoleMethod( GameConnection, selection, void, 4, 4, "(clientSrc, victim)"
              "Sends an update that clientSrc has attacked the victim unit")
{
   SimSet *set;
   GameBase *victim;
   if (!Sim::findObject(dAtoi(argv[2]), set) || !Sim::findObject(dAtoi(argv[3]), victim))
   {
      Con::errorf("GameConnection::sendAttackEvent - failed to find clientSrc or victim");
      return;
   }
   object->setDataField("selections", NULL, "11");
   object->sendAttackEvent((SimSet*)set, (GameBase*)victim);
}
#55
10/27/2005 (11:15 am)
Then change gameconnection.h

class MoveManager;
+class RTSUnit;

typedef Vector MoveList;

+S32 mTeam;

+SimObjectPtr mVisibleUnits;

void handleStartupError(const char *errorString);
+void setTeam(S32 team);
+S32 getTeam() const { return mTeam; }
+void sendMoveEvent(SimSet* moveSet, Point2F pos);
+void sendAttackEvent(SimSet* attackSet, GameBase* victim);

+void setUnitVisible(RTSUnit* unit);
+void setUnitInvisible(RTSUnit* unit);

+bool isUnitVisible(RTSUnit* unit);

then after void setDataBlockSequence(U32 seq) { mDataBlockSequence = seq; }

bool onAdd()
   {
      if(!Parent::onAdd())
         return false;

      SimSet *s = new SimSet();
      s->registerObject();

      mVisibleUnits = s;

      return true;
   }

   void onRemove()
   {
      Parent::onRemove();

      if(!mVisibleUnits.isNull())
         mVisibleUnits->deleteObject();
   }

This would of made a better resource, but the RTSKit dosent have a real resource section.

If you do exactly like I said, then it should work. If you have any problems just post them here.
#56
10/27/2005 (2:30 pm)
OMG thank you. If this works I owe you a steak dinner.... no better yet a copy of the game everyone will want next Christmas (or whenver I release it) Armors:Mechanized Combat

:-)

One tiny question though.

In which root folder am I creating the new "RTS" folder in? (starter.fps or the Rts starter kit)
#57
10/27/2005 (3:40 pm)
You would copy all the files from \Your RTS root dir\engine\game\rts to \Your TGE root dir\engine\game\rts, then inside your compiler you would have to include the new files.. What compiler do you use?
#58
10/27/2005 (9:15 pm)
Thanks again Rob. You have no idea how much you probably saved my project. I thought that you could simply make some scripts in a ".cs" file to get these results. I was way off.

But I'm using the Microsoft Visual Studio 6.0 I believe
#59
11/06/2005 (5:22 pm)
Okay I've finally got a chance to test it. Of course I have errors in the first pass.

What should I do when the error message says: "member function already defined"?
#60
11/06/2005 (7:57 pm)
What member function? If its a compiling error then copy paste output here.