Update: fixed! SelectionDisplay incorrect assumption...
by Stephen Zepp · in RTS Starter Kit · 11/18/2004 (11:05 am) · 10 replies
Priority: Medium
Category: Improper functionality in some configurations
Reproducable: Yes (only in dedicated server-client configurations, and probably remote client connecting to multi-player hosts)
Summary: the SelectionDisplay implementation (client side script) in playGui.cs makes an incorrect assumption that datablocks can be referenced via the console methods $ObjectID.getDataBlock->getName(); technique. While this is always true server side, datablock names are not transmitted during normal loading to the client, and therefore relying on getName() for a datablock client side is not correct.
Discussion: I learned more about how objects and datablocks work while researching this bug than in any other issue I've worked...it's complex and there isn't a lot of information out there. Some of the information in this discussion may in fact be in error, or glossing over the real details, but it's the best I have been able to figure out so far.
When a datablock is created (server side), it is of course assigned a name, and when an object is created (server side), the datablock is attached with the name field intact. However, when the datablock is transmitted to a client, the name field is removed, most likely for data security/authoritative control.
The reason this is not always obvious (I know people are thinking "wait, I've used $ObjectID.getDataBlock().getName(); plenty of times and it works!") is that when you are hosting a game, or playing single player, the console has access to not only the transmitted datablocks (which will be in ServerConnection group), but also has access to the populated datablocks in DataBlocks Group. So when you do a getName() on a datablock in this play configuration, you can access the (server created, which happens to be in your same instantiation process) datablocks that DO have the names.
(functionality fix in next message)
Category: Improper functionality in some configurations
Reproducable: Yes (only in dedicated server-client configurations, and probably remote client connecting to multi-player hosts)
Summary: the SelectionDisplay implementation (client side script) in playGui.cs makes an incorrect assumption that datablocks can be referenced via the console methods $ObjectID.getDataBlock->getName(); technique. While this is always true server side, datablock names are not transmitted during normal loading to the client, and therefore relying on getName() for a datablock client side is not correct.
Discussion: I learned more about how objects and datablocks work while researching this bug than in any other issue I've worked...it's complex and there isn't a lot of information out there. Some of the information in this discussion may in fact be in error, or glossing over the real details, but it's the best I have been able to figure out so far.
When a datablock is created (server side), it is of course assigned a name, and when an object is created (server side), the datablock is attached with the name field intact. However, when the datablock is transmitted to a client, the name field is removed, most likely for data security/authoritative control.
The reason this is not always obvious (I know people are thinking "wait, I've used $ObjectID.getDataBlock().getName(); plenty of times and it works!") is that when you are hosting a game, or playing single player, the console has access to not only the transmitted datablocks (which will be in ServerConnection group), but also has access to the populated datablocks in DataBlocks Group. So when you do a getName() on a datablock in this play configuration, you can access the (server created, which happens to be in your same instantiation process) datablocks that DO have the names.
(functionality fix in next message)
#2
In bot.cs, shocker.cs, and rifleman.cs (/server/scripts/units/), you need to define the name reference you want to use for each of the blocks. They can be anything, as long as your script in playGui.cs reflects the value. I'll demo the first one and let you get the rest:
In bot.cs, I added below the shapeFile = "~/data/units/bot/bot.dts";
add
Again, make sure you do this in each of your RTSUnitData datablocks (including the building one in /items/building.cs).
Finally, in playGui.cs, replace the function GuiRTSTSCtrl::getTypeID with:
do your compilation, and boot it up, will work in all configurations (single player, hosting multiplay, connection to remote host multiplay, and dedicated server-client multiplay).
Please feel free to comment about better implementations--and if you spot any errors, please also help to fix them! This is a relatively new area to me, and there is almost certainly a better way to do this.
11/18/2004 (11:23 am)
Now, on to the script changes:In bot.cs, shocker.cs, and rifleman.cs (/server/scripts/units/), you need to define the name reference you want to use for each of the blocks. They can be anything, as long as your script in playGui.cs reflects the value. I'll demo the first one and let you get the rest:
In bot.cs, I added below the shapeFile = "~/data/units/bot/bot.dts";
add
RTSUnitTypeName = "bonecracker";
Again, make sure you do this in each of your RTSUnitData datablocks (including the building one in /items/building.cs).
Finally, in playGui.cs, replace the function GuiRTSTSCtrl::getTypeID with:
function GuiRTSTSCtrl::getTypeID(%this, %objID)
{
switch$(%this.getSelectedObject(%objID).getRTSUnitTypeName())
{
case "bonecracker":
return 0;
case "shocker":
return 1;
case "rifleman":
return 2;
}
}do your compilation, and boot it up, will work in all configurations (single player, hosting multiplay, connection to remote host multiplay, and dedicated server-client multiplay).
Please feel free to comment about better implementations--and if you spot any errors, please also help to fix them! This is a relatively new area to me, and there is almost certainly a better way to do this.
#3
11/21/2004 (11:52 am)
Huh. I distinctly remember implementing similar functionality like a month ago. Maybe it got eaten... Anyway, this issue is on my todo list. Nice work, Stephen!
#4
02/26/2005 (12:52 am)
Nicely done. Just added, works nicely. Solves problem of getting the unit type in more diverse situations as well, which is something I will need to implement a couple ideas I've been floating... Keep up the good work.
#6
03/09/2005 (6:53 pm)
Based on the forum-mod changed title to this original post, I can assume that very similar functionality was implemented in the latest release, so if you are looking to add this functionality, you may want to search for the new implementation instead. I'm looking now, and if I can find it I'll post the new function name here!
#7
06/09/2005 (8:25 pm)
Curious, did anyone find that this was already created somewhere or do I need to add this?
#8
function getBuildingTypeID(%objID)
{
return (getBuildingTypeIDFromDataBlock(objID.getDataBlock().getName()))
}
ever since i implemented the fixes listed above, the console log says that it cannot find function getDataBlock, or getName in this function. im not quite sure why it worked before but not now.
any way i tried replacing the return with
return (getBuildingTypeIDFromUnitName(objID.getRTSUnitTypeName()))
but it doesn't like that either... any help would be great.
thanks!
06/04/2008 (6:45 pm)
I have followed all of the instructions above and i have run into a little error. the problem is in the client side buildings.cs on or about line 174 is function getBuildingTypeID(%objID)
{
return (getBuildingTypeIDFromDataBlock(objID.getDataBlock().getName()))
}
ever since i implemented the fixes listed above, the console log says that it cannot find function getDataBlock, or getName in this function. im not quite sure why it worked before but not now.
any way i tried replacing the return with
return (getBuildingTypeIDFromUnitName(objID.getRTSUnitTypeName()))
but it doesn't like that either... any help would be great.
thanks!
#9
01/19/2010 (1:51 am)
I'm curious does this apply to the latest (TGE 15.2) build or only the one from 2004 ?
#10
01/24/2010 (12:01 am)
If anyone is curious, this IS included in the TGE 1.5.2 build of the RTS kit.
Torque 3D Owner Stephen Zepp
In RTSUnit.h, approximately line 84, just above "S32 mBaseDamage", add:
and approx line 204, above "void clearControllingConnection();" add:
const char *getUnitTypeName() const {return dynamic_cast<RTSUnitData*>(mDataBlock)->mRTSUnitTypeName; }In RTSUnit.cc we need to make the following changes:
around line 86, look for:
RTSUnitData::RTSUnitData() { mBaseDamage = 10;above the mBaseDamage, add:mRTSUnitTypeName = StringTable->insert("");a few lines later in RTSUnitData::packData() addabove " stream->write(mBaseDamage);"
in RTSUnitData::unpackData() read in the string (above the mBaseDamage again):
in RTSUnitData::initPersistFields(), add
addField("RTSUnitTypeName", TypeString, Offset(mRTSUnitTypeName, RTSUnitData));as the first field.
Make our new function available to the console (put this new ConsoleMethod down with the rest of the other ones, somewhere near line 1680):
ConsoleMethod( RTSUnit, getRTSUnitTypeName, const char *, 2, 2, "()" "Returns a text string of the RTSUnit's type name (defined in RTSUnitData).") { char *returnBuffer = Con::getReturnBuffer( 256 ); dSprintf( returnBuffer, 256, "%s", object->getUnitTypeName() ); return returnBuffer; }