Propegating a Stat to clients
by Dreamer · in Torque Game Engine · 04/28/2006 (6:35 am) · 5 replies
Hello everyone,
I'm making an MMORPG, and decided it might be nice to let players know if they are about to try and tackle something beyond their ability by modifying the color of the name above the head of the NPC based upon the difference in level between player and NPC.
So to start with I needed to create and set levels. Sure no problem
Open ShapeBase.h and add it
Next we need to declare our getter and setter functions...
Now after the getter and setter functions are declared we need to create the functions we just declared.
Since the get function is going to be called so much we want to make sure to inline that puppy so in ShapeBase.h we add
Then somewhere in ShapeBase.cc we add
Now we have getter and setter functions for the engine we to make sure to create some console functions
Down near the bottom of ShapeBase.cc
Notice I put 2 checks in to make sure that ONLY the server can set the level, we don't want players hacking their client and setting their own level.
Now that we've done all of that we need to get this mLevel value to propegate.
Now I originally tried using the NameMask since it's updated infrequently but that caused my client to lock up so I used DamageMask instead and to hell with the consequences (updates more often so mLevel will be pushed more frequently that otherwise needed)
So again in ShapeBase.cc this time in the PackUpdate function
Next we want to Unpack the value so it can be set on the client as well
Notice that in both cases I just added the new value to the bottom part of the area dealing with my particular mask, by doing this you can ensure that you don't get any wierdness from putting the read and write out of order.
Ok, so now we have a new value tied to ShapeBase and it's propegating, but we also need to make sure to initialize this value
This is our last change to ShapeBase.cc
I'm making an MMORPG, and decided it might be nice to let players know if they are about to try and tackle something beyond their ability by modifying the color of the name above the head of the NPC based upon the difference in level between player and NPC.
So to start with I needed to create and set levels. Sure no problem
Open ShapeBase.h and add it
/// @name Physical Properties
/// @{
U32 mLevel; ///< Level sent to client
F32 mEnergy; ///< Current enery level.Next we need to declare our getter and setter functions...
/// @name Name & Skin tags
/// @{
void setShapeName(const char*);
void setLevel(U32 Level);
U32 getLevel();Now after the getter and setter functions are declared we need to create the functions we just declared.
Since the get function is going to be called so much we want to make sure to inline that puppy so in ShapeBase.h we add
inline const char* ShapeBase::getShapeName()
{
return mShapeNameHandle.getString();
}
inline U32 ShapeBase::getLevel(){
return mLevel;
}Then somewhere in ShapeBase.cc we add
void ShapeBase::setLevel(U32 Level){
if (!isGhost()) {
mLevel = Level;
setMaskBits(DamageMask);
}
}Now we have getter and setter functions for the engine we to make sure to create some console functions
Down near the bottom of ShapeBase.cc
ConsoleMethod( ShapeBase, getLevel, F32, 2, 2, "")
{
return object->getLevel();
}
ConsoleMethod( ShapeBase, setLevel, void, 3, 3, "(int Level)"){
if (object->isServerObject()){
object->setLevel(dAtof(argv[2]));
}
}Notice I put 2 checks in to make sure that ONLY the server can set the level, we don't want players hacking their client and setting their own level.
Now that we've done all of that we need to get this mLevel value to propegate.
Now I originally tried using the NameMask since it's updated infrequently but that caused my client to lock up so I used DamageMask instead and to hell with the consequences (updates more often so mLevel will be pushed more frequently that otherwise needed)
So again in ShapeBase.cc this time in the PackUpdate function
if (stream->writeFlag(mask & DamageMask)) {
stream->writeFloat(mClampF(mDamage / mMaxDamage, 0.f, 1.f), DamageLevelBits);
stream->writeInt(mDamageState,NumDamageStateBits);
stream->writeNormalVector( damageDir, 8 );
stream->write(mLevel);
}Next we want to Unpack the value so it can be set on the client as well
if (stream->readFlag()) {
mDamage = mClampF(stream->readFloat(DamageLevelBits) * mMaxDamage, 0.f, mMaxDamage);
DamageState prevState = mDamageState;
mDamageState = DamageState(stream->readInt(NumDamageStateBits));
stream->readNormalVector( &damageDir, 8 );
if (prevState != Destroyed && mDamageState == Destroyed && isProperlyAdded())
blowUp();
updateDamageLevel();
updateDamageState();
mLevel = stream->read(&mLevel);
}Notice that in both cases I just added the new value to the bottom part of the area dealing with my particular mask, by doing this you can ensure that you don't get any wierdness from putting the read and write out of order.
Ok, so now we have a new value tied to ShapeBase and it's propegating, but we also need to make sure to initialize this value
This is our last change to ShapeBase.cc
ShapeBase::ShapeBase()
{
mTypeMask |= ShapeBaseObjectType;
mLevel = 1;
mDrag = 0;
mBuoyancy = 0;
#2
Does the client receive mLevel? Or is it never sent? You're saying:
Which sounds very confusing (at least to my non-english ear) but also like it works as long as it is on the server. To isolate the issue, you could just see if it prints on the client, and if it does, it's an error in your onRender code.
04/28/2006 (11:59 pm)
A minor brainfart: You're using integers to store mLevel, but you're converting argv[2] to a float in ConsoleMethod, getLevel(). Just curious, why?Does the client receive mLevel? Or is it never sent? You're saying:
Quote:
It doesn't make any sense to me, but the mLevel value does not appear to propegate.
In the server if I enter 16440.setLevel(10); then echo(16440.getLevel()); It returns 10 properly
Which sounds very confusing (at least to my non-english ear) but also like it works as long as it is on the server. To isolate the issue, you could just see if it prints on the client, and if it does, it's an error in your onRender code.
#3
However after a couple of days of tearing my hair out I realized I had a double assignment going on.
In the ShapeBase unpackupdate we had
While at first that would look correct in actuality what was happening was stream->read was already assigning the proper value to mLevel and then I was assigning the return of stream->read which would always be true if there had been a value there.
So the code actually NEEDED to be
That was all.
In the final analysis it took me adding checks for changes to mLevel and noticing it was happening just fine client side, but when I would go to check the value it was always 1. Thats when it hit me to remove the assignment and it worked like a charm!
Regards,
Dreamer
04/29/2006 (12:06 am)
Actually it appeared that the value was never making it to the client.However after a couple of days of tearing my hair out I realized I had a double assignment going on.
In the ShapeBase unpackupdate we had
mLevel = stream->read(&mLevel);
While at first that would look correct in actuality what was happening was stream->read was already assigning the proper value to mLevel and then I was assigning the return of stream->read which would always be true if there had been a value there.
So the code actually NEEDED to be
stream->read(&mLevel);
That was all.
In the final analysis it took me adding checks for changes to mLevel and noticing it was happening just fine client side, but when I would go to check the value it was always 1. Thats when it hit me to remove the assignment and it worked like a charm!
Regards,
Dreamer
#4
04/29/2006 (1:29 am)
This means you're adding a general write value to every damage update though, not really efficient for an MMO. Firstly you should use writeInt, as it you lets you specify a max bit size for the int, and uses a more efficient compression routine I believe. Secondly, as far as I've been able to tell, there's no harm in adding an enum for a new mask up in ShapeBase.h and calling that for your custom updates. I made one to use when one of a few booleans I have needs to be updated to my clients (I send them all together in the one update, but booleans are the smallest type of data, so it's not a big deal).
#5
04/29/2006 (1:33 am)
Actually I decided to share space with NameMask in the end, since I realized the difficulty was my placement of the write originally and had nothing to do with NameMask causing issues, as for writeInt I had considered doing that, but this is pushed so infrequently now that there is no lag, and I didn't really feel like trying to count bits.
Torque Owner Dreamer
Default Studio Name
The next thing we want to do is modify GuiShapeNameHud so that the color of the name reflects the level. We aren't going to do anything really fancy here, we just want to start simple, we will scale the name from white to black depending on the level difference.
Open GuiShapeNameHud.cc or if you are using the MMORPG Enhancement Kit open GuiRPGHud.cc
First lets add the code to get the level of the characters we need our level and we need the level of what is being displayed.
This will be in the OnRender function down near the bottom just before the call to draw the name
F32 opacity = (shapeDist < fadeDistance)? 1.0: 1.0 - (shapeDist - fadeDistance) / (mVisDistance - fadeDistance); U32 shapeLevel = shape->getLevel(); //Con::printf("Target level is %d", shapeLevel); U32 myLevel = control->getLevel(); //Con::printf("My level is %d", myLevel);Next we want to calculate the difference in levels and turn it into a percentage
F32 levelDif = shapeLevel - myLevel; //Woops need to prevent a divide by 0 error //Con::printf("Level dif is %d", levelDif); if(levelDif <= 0){ levelDif = 1; } F32 levelDifdiv = 1 / levelDif; if(levelDifdiv >1){ levelDifdiv = 1; } //Con::printf("Level dif div is %d", levelDifdiv);Finally we want to adjust the colors based on level difference
Now we just make a couple of small adjustments to the drawName function
void GuiRPGHud::drawName(Point2I offset, const char *name, F32 levelDif, F32 opacity) { // Center the name offset.x -= mProfile->mFont->getStrWidth((const UTF8 *)name) / 2; offset.y -= mProfile->mFont->getHeight(); mTextColor.set( levelDif, levelDif, levelDif, levelDif ); // Deal with opacity and draw. mTextColor.alpha = opacity; dglSetBitmapModulation(mTextColor); dglDrawText(mProfile->mFont, offset, name); dglClearBitmapModulation(); }About the only thing we really changed in that function was to set mTextColor to levelDif all the way across the board but we did pass in a new variable so lets update the function declarationAlright boys and girls thats it! If you recompile and enter the game you should be able to set the level of anything in the zone
For instance on my server Guard Kronk typically comes up as 16440 so doing 16440.setLevel(10); Should cause his name to turn colors.
Only one caveat it doesn't
Why? I don't know. If you'll notice there are alot of Con::printf to show what the values are.
No matter what I do I can't seem to get those level values to change..
It doesn't make any sense to me, but the mLevel value does not appear to propegate.
In the server if I enter 16440.setLevel(10); then echo(16440.getLevel()); It returns 10 properly
I know I'm close here, but I just can't quite seem to see the forest for the trees.
Hence the reason I'm posting here in the forums as a question rather than as a nice shiny resource.
Thoughts? Ideas? Suggestions?
Anything here would be appreciated.
Regards,
Dreamer