Game Development Community

dev|Pro Game Development Curriculum

Add some Magick to T3D

by Richard Marrevee · 04/19/2010 (1:44 am) · 3 comments

This engine modification allows you to give a player or an AI magick-points, which then can be used for casting spells and other "magick"al things. This resource contains 2 parts: the engine modification and a scripted example on how to use it.

Added functionality:

object.getMagickLevel();
object.setMagickLevel(float value);
object.getMagickRate();
object.setMagickRate(float value);
object.getMagickPercent();
object.isMagick();

guiHealthBarHud: displayMagick(bool);

Note:
The line numbers I mention are approximate. Make sure that you find the corresponding code to be certain you have the right part.

Remarks:
The code works for a single-player game. I don't know if it works in a networked version. I haven't tested it because it is beyond my needs.

Thanks to:
For the magickal push I have used the following resource from Shane 'Tibius' Parker. Thanks Shane.
http://www.torquepowered.com/community/resources/view/17751

Now let's get started.

First open the source code of your project.

Now find the folder called project DLL, where project is the name of your project.
In this folder go to the "source files/engine/T3D" folder.

Open shapeBase.h

Now find this:
struct ShapeBaseData : public GameBaseData {
Should be somewhere around line 386 and goto :
F32 maxEnergy;
   F32 maxDamage;
   F32 repairRate;                  ///< Rate per tick.
somewhere around line 433

Hereafter add this:
F32 maxMagick;               ///< Maximum magick level.
   F32 magickRate;              ///< Rate at which used Magick-points are restored per tick.
   bool hasMagick;		///< has or has no Magick abilities.

Now find
F32 mEnergy;                     ///< Current enery level.
   F32 mRechargeRate;               ///< Energy recharge rate (in units/tick).

around line 743 and add hereafter

F32 mMagick;                     ///< Current Magick level.
   F32 mMagickRate;               ///< Magick recharge rate (in units/tick).

These are the variables the engine will use.

Now declare the various functions.

First find
/// Sets the rate at which the energy replentishes itself
   /// @param   rate   Rate at which energy restores
   void setRechargeRate(F32 rate) { mRechargeRate = rate; }

   /// Returns the amount of energy in the object
   F32  getEnergyLevel();

   /// Returns the percentage of energy, 0.0 - 1.0
   F32  getEnergyValue();

   /// Returns the recharge rate
   F32  getRechargeRate() { return mRechargeRate; }
around line 1120
and add after that the following new functiondeclarations.
virtual void setMagickLevel(F32 magick);
   void setMagickRate (F32 rate) { mMagickRate = rate; }

   /// Returns the amount of Magick in the object
   F32  getMagickLevel();

   /// Returns the percentage of Magick, 0.0 - 1.0
   F32  getMagickValue();

   F32 getMagickRate() { return mMagickRate; }

   bool isMagick();

That is it for shapeBase.h. Save the file (it is useful to make a backup of the old files before you do so).

Now open shapeBase.cpp

Find the following piece of code:
ShapeBaseData::ShapeBaseData()
 : shadowEnable( false ),
   shadowSize( 128 ),
   shadowMaxVisibleDistance( 80.0f ),
   shadowProjectionDistance( 10.0f ),
   shadowSphereAdjust( 1.0f ),
   shapeName( StringTable->insert("") ),
   cloakTexName( StringTable->insert("") ),
   mass( 1.0f ),
   drag( 0.0f ),
   density( 1.0f ),
   maxEnergy( 0.0f ),
   maxDamage( 1.0f ),
   disabledLevel( 1.0f ),
   destroyedLevel( 1.0f ),
   repairRate( 0.0033f ),

and add after this:

maxMagick( 0.0f ),
   magickRate( 0.0 ),
   hasMagick( false ),

Now find:
struct ShapeBaseDataProto
{
   F32 mass;
   F32 drag;
   F32 density;
   F32 maxEnergy;

around line 133

and add here:
F32 maxMagick;

Below this you will find:
ShapeBaseDataProto()
   {
      mass = 1;
      drag = 0;
      density = 1;
      maxEnergy = 0;
      maxMagick = 0;               ///< add this line for magick
      cameraMaxDist = 0;
      cameraMinDist = 0.2f;
      cameraDefaultFov = 75.f;
      cameraMinFov = 5.0f;
      cameraMaxFov = 120.f;
   }
};
and add the mentioned line of code.

Now it is getting a bit difficult.

Find the following function
void ShapeBaseData::initPersistFields()
around line 376.

and after
endGroup( "Damage/Energy" );

add:

addGroup( "Magick" );

      addField( "maxMagick",      TypeF32,        Offset(maxMagick,      ShapeBaseData) );
      addField( "magickRate",     TypeF32,        Offset(magickRate,     ShapeBaseData) );
      addField( "hasMagick",	  TypeBool,		  Offset(hasMagick,		 ShapeBaseData) );

   endGroup( "Magick" );

This will display the 3 fields in the datablock(editor), so you can change this.

Now find the function:
void ShapeBaseData::packData(BitStream* stream)
Somewhere around line 561

In this function you find:
if(stream->writeFlag(maxEnergy != gShapeBaseDataProto.maxEnergy))
      stream->write(maxEnergy);

around line 583 and add this:

if(stream->writeFlag(maxMagick != gShapeBaseDataProto.maxMagick))
      stream->write(maxMagick);

   stream->writeFlag(hasMagick);

Find this function:
void ShapeBaseData::unpackData(BitStream* stream)
at line 635
In here find:
if(stream->readFlag())
      stream->read(&maxEnergy);
   else
      maxEnergy = gShapeBaseDataProto.maxEnergy;

and add:

if(stream->readFlag())
      stream->read(&maxMagick);
   else
      maxMagick = gShapeBaseDataProto.maxMagick;

   hasMagick = stream->readFlag();

Now goto:
ShapeBase::ShapeBase()
 : mDrag( 0.0f ),
   mBuoyancy( 0.0f ),
   mWaterCoverage( 0.0f ),
   mLiquidHeight( 0.0f ),
   mControllingObject( NULL ),
   mGravityMod( 1.0f ),
   mAppliedForce( Point3F::Zero ),
   mTimeoutList( NULL ),
   mDataBlock( NULL ),
   mShapeInstance( NULL ),
   mEnergy( 0.0f ),
   mRechargeRate( 0.0f ),
   mDamage( 0.0f ),
   mRepairRate( 0.0f ),
   mRepairReserve( 0.0f ),
   mDamageState( Enabled ),
   mDamageThread( NULL ),

at line 749 and add:

mMagick( 0.0f ),
   mMagickRate( 0.0f ),

Still here? We are halfway the engine mod.

Find function:
bool ShapeBase::onNewDataBlock( GameBaseData *dptr )

around line 902

In this function find the next lines of code:

if( isGhost() )
      reSkin();


   //
   mEnergy = 0;
   mDamage = 0;

And add:

mMagick = 0;
   mMagickRate = 0;
   if (mDataBlock->hasMagick) {
	   mMagick = mDataBlock->maxMagick;
	   mMagickRate = mDataBlock->magickRate;
	   if (mMagickRate == 0) mMagickRate = 0.001f;
   }

These are just some defaults when a new datablock is created.

Now let us have the engine restoring used magick-points.

Find the function:

void ShapeBase::processTick(const Move* move)
at line 1053

In here find this piece of code:

// Virtual setEnergyLevel is used here by some derived classes to
      // decide whether they really want to set the energy mask bit.
      if (mEnergy != store)
         setEnergyLevel(mEnergy);
   }

and add:

// Magick management
   if (mDataBlock->hasMagick) {
      mMagick += mMagickRate;
      if (mMagick > mDataBlock->maxMagick)
         mMagick = mDataBlock->maxMagick;
      else
         if (mMagick < 0)
            mMagick = 0;
   }

Now we are ready to add some new functions.
First you have to find this:

void ShapeBase::setDamageLevel(F32 damage)
{
   if (!mDataBlock->isInvincible) {
      F32 store = mDamage;
      mDamage = mClampF(damage, 0.f, mDataBlock->maxDamage);

      if (store != mDamage) {
         updateDamageLevel();
         if (isServerObject()) {
            setMaskBits(DamageMask);
            char delta[100];
            dSprintf(delta,sizeof(delta),"%g",mDamage - store);
            Con::executef(mDataBlock, "onDamage",scriptThis(),delta);
         }
      }
   }
}

After this code block you add the new functions:

F32 ShapeBase::getMagickLevel()
{
	if (mDataBlock->hasMagick)
		return mMagick; 
	return 0.0f;
}

F32 ShapeBase::getMagickValue()
{
	if (mDataBlock->hasMagick)
	{
		F32 maxMagick = mDataBlock->maxMagick;
		if ( maxMagick > 0.f )
			return (mMagick / mDataBlock->maxMagick);   
   }
   return 0.0f;
}

void ShapeBase::setMagickLevel(F32 magick)
{
	if (mDataBlock->hasMagick) {
		mMagick = (magick > mDataBlock->maxMagick)?
			mDataBlock->maxMagick: (magick < 0)? 0: magick;
      }
}

bool ShapeBase::isMagick()
{
	return mDataBlock->hasMagick;
}

Now find this function and change them as mentioned:
void ShapeBase::writePacketData(GameConnection *connection, BitStream *stream)
{
   Parent::writePacketData(connection, stream);

   stream->write(getEnergyLevel());
   stream->write(mRechargeRate);
   stream->write(getMagickLevel());     ///< Add this line
   stream->write(mMagickRate);          ///< And this line
}
Somewhere around line 2832

Let's do the same for the reading part:

void ShapeBase::readPacketData(GameConnection *connection, BitStream *stream)
{
   Parent::readPacketData(connection, stream);
   F32 energy;
   F32 magick;                        ///< Add this line
   stream->read(&energy);
   setEnergyLevel(energy);
   stream->read(&mRechargeRate);
   stream->read(&magick);             ///< And add this line
   setMagickLevel(magick);            ///< this line
   stream->read(&mMagickRate);        ///< and this line
}

Now make sure we can use the new functionality from the console so add some consolemethods.

Let us find the function:

ConsoleMethod( ShapeBase, getEnergyPercent, F32, 2, 2, "")
{
   return object->getEnergyValue();
}
around line 3899

Hereafter add the following new consolemethods:

ConsoleMethod( ShapeBase, setMagickLevel, void, 3, 3, "Set the Magick (float level)")
{
   object->setMagickLevel(dAtof(argv[2]));
}

ConsoleMethod( ShapeBase, getMagickLevel, F32, 2, 2, "Get Magick Level")
{
   return object->getMagickLevel();
}

ConsoleMethod( ShapeBase, getMagickPercent, F32, 2, 2, "Get Percentage Magick")
{
   return object->getMagickValue();
}

ConsoleMethod( ShapeBase, setMagickRate, void, 3, 3, "Set rate magick restores (float rate)")
{
   object->setMagickRate(dAtof(argv[2]));
}

ConsoleMethod( ShapeBase, getMagickRate, F32, 2, 2, "Get the Magick Recharge Rate")
{
   return object->getMagickRate();
}

ConsoleMethod( ShapeBase, isMagick, bool, 2, 2, "Has this object Magick abilities?")
{
	return object->isMagick();
}

Now we are done with shapeBase.cpp

Now open player.cpp

In PlayerData::PlayerData() line 148 find
mass = 9.0f;         // from ShapeBase
   maxEnergy = 60.0f;   // from ShapeBase
and add
maxMagick = 0.0f;

Now goto
void PlayerData::packData(BitStream* stream)
at line 657 and find
stream->write(mass);
   stream->write(maxEnergy);
Add:
stream->write(maxMagick);

Now the last part.
Goto
void PlayerData::unpackData(BitStream* stream)
at line 804 and find:
stream->read(&mass);
   stream->read(&maxEnergy);
Add:
stream->read(&maxMagick);

That's it for player.cpp

Now you are done with the engine modification. The following additions are not really necessary, but add some icing to the cake.

We are going to modificate the guiHealthBarHud so it is able to display the magick-points.

Open guiHealthBarHud.cpp in the fps folder.

Find the following code and make the changes as mentioned.

class GuiHealthBarHud : public GuiControl
{
   typedef GuiControl Parent;

   bool     mShowFrame;
   bool     mShowFill;
   bool     mDisplayEnergy;
   bool	    mDisplayMagick; ///< Add this line

   ColorF   mFillColor;
   ColorF   mFrameColor;
   ColorF   mDamageFillColor;

   S32      mPulseRate;
   F32      mPulseThreshold;
   F32      mValue;

Now we have to add the fields so it can be used in the guiEditor.

find the next piece of code and add the line:

addGroup("Misc");		
   addField( "showFill", TypeBool, Offset( mShowFill, GuiHealthBarHud ) );
   addField( "showFrame", TypeBool, Offset( mShowFrame, GuiHealthBarHud ) );
   addField( "displayEnergy", TypeBool, Offset( mDisplayEnergy, GuiHealthBarHud ) );
   addField( "displayMagick", TypeBool, Offset( mDisplayMagick, GuiHealthBarHud ) ); ///< Add this new line
   endGroup("Misc");

find function
void GuiHealthBarHud::onRender(Point2I offset, const RectI &updateRect)

And change it like this

ShapeBase* control = dynamic_cast<ShapeBase*>(conn->getControlObject());
   if (!control || !(control->getType() & PlayerObjectType))
      return;

//start changes

   if(mDisplayEnergy)
   {
      mValue = control->getEnergyValue();
   }
   else if (mDisplayMagick)
   {
	   mValue = control->getMagickValue();
   }
   else
   {
      // We'll just grab the damage right off the control object.
      // Damage value 0 = no damage.
      mValue = 1 - control->getDamageValue();
   }

//end changes

   // Background first

These are all the engine changes, so now you can rebuild the engine.

Make sure that when you add this control to your HUD, you don't check both displayEnergy and displayMagick.

Now we are ready to do some scripting to show the new functionality.

In scripts/server create a file magickcommands.cs
and add:

// Magickal Weapons

function serverCmdForcePush(%client,%distance,%force)   
{   
   messageClient(%client,'MsgSpellCast', 'c0You are casting the Telekinetic Push');   
   %player = %client.player;   
   if(%player.isMagick()){
      %handle = Eye_Distance_HandleRay(%player, %distance, $TypeMasks::PlayerObjectType | $TypeMasks::StaticTSObjectType );
      if(%handle)
         PushBack(%handle, %player, %force);
      else
         echo("Nothing to push");
   }
   else
      echo ("No Magick");
}

Now create a file also in scripts/server call it push.cs
and add

function Eye_Distance_HandleRay(%player, %distance, %typeMasks)   
{  
   echo("**************Commencing Eye_Distance_HandleRay**************");
   echo("--------------------Checking For Objects--------------------");
   %magick=%player.getMagickLevel();    ///< The casting of this spell uses 20 magick-points and 10 energy
   %energy=%player.getEnergyLevel();    ///< change it to your needs
   if(%magick<=20) return false;
   if(%energy<=10) return false;
   %player.setMagickLevel(%magick-20);
   %player.setEnergyLevel(%energy-10);
   %eye = %player.getEyeVector();   
   %vec = vectorScale(%eye, %distance);   
   %start = %player.getEyeTransform();   
   %end = VectorAdd(%start, %vec);   
   echo("Eye Vec: "@ %eye); // Remove this Line When Script is Complete   
	echo("Vec Scl: "@ %vec); // Remove this Line When Script is Complete   
	echo("Vec Str: "@ %start); // Remove this Line When Script is Complete   
	echo("Vec End: "@ %end); // Remove this Line When Script is Complete   
   %found = ContainerRayCast (%start, %end, %typeMasks, %player);   
   if(%found)     
   {
      echo("======Raycast has found an Object within the Specified TypeMask.======");//Parse out the Found Object's ID Handle   
      %handle = getWord(%found, 0);   
      echo("Object has been found. ID: "@ %handle); // Remove this Line When Script is Complete   
      return %handle;
   }   
   else  
   {
      echo("======Raycast did not encounter any Object within the Specified TypeMask.======");   
   }          
	echo("--------------------Raycast Complete--------------------");   
}


// This Function applies the physical force to the object.   
// %this  = Object ID   
// %vec   = Vector Force is to Follow   
// %force = Ammount of Force Applied   
function PushBack(%this, %player, %force)   
{
   %damage=getRandom(0,100);               ///< The push applies a random damage to the target from 0-100
   %vec=%player.getEyeVector();            ///< Change it to your specific needs.
   if (%this.getType()&$TypeMasks::StaticTSObjectType)
   {
      echo("Static Object");
   }
   if (%this.getType()&$TypeMasks::PlayerObjectType)
   {
      %objectMass = %this.getDatablock().mass;   
      echo("Object's Mass:"@ %objectMass);   
      echo("Force Vector:"@ %vec);   
      echo("Force Applied:"@ %force);   
      echo("Damage:"@ %damage);
      %impulseVector = vectorScale(%vec, %objectMass * %force);   
      %this.applyImpulse( %this.getWorldBoxCenter(), %impulseVector);
      %this.damage(%player,"Body",%damage,"MagickDamage");
   }
}
This is the modified resource from Shane 'Tibius' Parker.

Now make sure these scripts are executed so open sripts/server/scriptExec.cs
and add the scripts.
Find and add:
// Load our gametypes
exec("./gameCore.cs"); // This is the 'core' of the gametype functionality.
exec("./gameDM.cs"); // Overrides GameCore with DeathMatch functionality.

exec("./push.cs");             ///< Add this line for magick.
exec("./magickcommands.cs");   ///< And this line.

Now let's assign a key to the push. I used the m key but feel free to use another. Open scripts/clients/default.bind.cs and add:

moveMap.bindCmd(keyboard, "m", "commandToServer('ForcePush',10,10);", "");

The first 10 is the distance from the player, the second is the force applied. Change this to your needs.

Make sure you delete config.cs from the same folder or these changes are lost (if there is a config.cs).

Pfweh.... we are finished.

I hope you like it.

About the author

Started programming in 1984 on an Oric, when time progressed switched to MSX, Amiga and finally the Windows PC with T3D. Now developing an epic fantasy game: The Master's Eye. Creator of the DoorClass pack and VolumetricFog pack @ richardsgamestudio.com


#1
04/20/2010 (8:19 pm)
Thanks for sharing. I'm still new to T3D, but am curious... other than performance, is there an advantage to modifying the c++ engine source to implement magick instead of making all the changes in TorqueScript? (I think you could implement this in TorqueScript in the playerdata datablock, right?)
#2
04/22/2010 (2:35 am)
@Marc:

Thanks. For certain is it possible to add these things to the player datablock in scripting. The idea behind my approach is:

1 - my game depends heavy on scenes with many high-detail objects, so I need all the performance gains I can get.

2 - I have added this functionality to the shapebase datablock (which I couldn't find in script, I even don't know if it is available in script), so it is also available on al the derived classes like vehicle, staticshape and items and not only to the player, which is a need for my game.

Maybe this explains why I modded the source.

But you're right. you can add it to the player datablock in script.



#3
09/20/2010 (5:45 am)
Heyo I tried compiling this and it compiled and stuff but when I hit the play button it crashes is there a particular reason for this or what?

btw I am using T3D 1.1b1