Game Development Community

Setting Energy to Equal Health

by Willbkool · in Torque Game Engine Advanced · 04/09/2011 (9:27 pm) · 11 replies

What I'm trying to do is set the energy level to equal the damage level, so that the energy level doesn't go back to 100% if the player is damaged. It should never go above whatever the current damage level is.

I added the below code into the onDamage function.

%obj.setEnergyLevel(%this.getDamageLevel());

It does reduce the energy if damaged, but the energy climbs back to 100 percent. I tried to do something with maxEnergy in the player datablock, but if I do something like:

maxEnergy =  %obj.getDamageLevel();

it doesn't give the player any energy from the start. I even tried using the above code in the onDamage function to redefine maxEnergy, and while it does give the player energy to start with, it doesn't work either. In fact that will cause the AI player to not use the death animation. Weird. I was hoping one of you TorqueScript gurus could help me out and point me in the right direction.

#1
04/10/2011 (4:41 am)
Doesn't energy have a "rechargeRate" in the player datablock? I guess that you could disable that and then update energy at the same time as damage.
#2
04/10/2011 (6:45 am)
I'd either do it by setting the recharge rate to 0 (as mentioned above), or with a source edit to player.cpp to restrict the energy recharge beyond the equivalent HP level. Shouldn't be too hard to do.
#3
04/10/2011 (9:38 pm)
Thanks for the replies. I really don't want to disable recharge rate because I'm using the sprinting resource, and I don't want to have the energy jump from, say 50%, which is minimum energy for sprinting to the damage level, say it is 80%. I want it to slowly recharge like normal. I just might have to dig into the source for this.

But I decided to check the conole log and it gave me this:
"scriptsAndAssets/server/scripts/player.cs (769): Unknown command getDamageLevel.".

Now I'm sure there is a getDamageLevel() function in player.cpp and player.h, so why it wouldn't find it in script is puzzling. It seems that getDamageLevel() is in a public area, but mDamage, which is returned, is in a protected area in player.h. I wonder if that matters?
#4
04/12/2011 (2:44 am)
Dynamically changing the value of a datablock is never a good idea, since it will change for everything using that datablock.

Energy will always try to reach the value of maxEnergy if you use a recharge rate. This is not something that can be changed from script without constantly forcing the energy value using a schedule, which is also something you don't want to do.

Your best option, if you can, is to modify the energy roof code in shapeBase.cpp processTick to limit the energy to the damage level.
However, you also have to remember Damage and Energy use opposite values, where 0 damage means full health and 0 energy means empty energy. You'll have to do a simple math trick to get the correct value for a straight comparison.

I shall whip up a bit of code for you and post my results.
#5
04/12/2011 (3:30 am)
Right, quick and dirty hack. This method will only work if maxDamage and maxEnergy values are equal.

The following is in shapeBase.cpp.
void ShapeBase::processTick(const Move* move)
{
   // Energy management
   if (mDamageState == Enabled && mDataBlock->inheritEnergyFromMount == false) {
      F32 store = mEnergy;
      mEnergy += mRechargeRate;
      if (mEnergy > mDataBlock->maxEnergy)
         mEnergy = mDataBlock->maxEnergy;
      // Addition
      else if (mEnergy > (mDataBlock->maxDamage - mDamage))
         mEnergy = (mDataBlock->maxDamage - mDamage);
      // end
      else
         if (mEnergy < 0)
            mEnergy = 0;

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

   // Repair management
   if (mDataBlock->isInvincible == false)
   {
      F32 store = mDamage;
      mDamage -= mRepairRate;
      mDamage = mClampF(mDamage, 0.f, mDataBlock->maxDamage);

      if (mRepairReserve > mDamage)
         mRepairReserve = mDamage;
      if (mRepairReserve > 0.0)
      {
         F32 rate = getMin(mDataBlock->repairRate, mRepairReserve);
         mDamage -= rate;
         mRepairReserve -= rate;
      }

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

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

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

With those three changes, the energy value is limited to the actual damage value. The damage check is done twice, since processTick is used for repairs and setDamageLevel is used for damage taken.
However, since all we are doing is setting a limit, the bar can suddenly jump from full to the damage value. You might want to write some code to update the energy value over time, but that could get complicated.

If you want different values for maxDamage and maxEnergy, then the code will get a little more complex with the use of percentages. The changes will still be in the same same places though. If that is what you need, I can whip that up too.

EDIT: It would also be better to use this code in player.cpp instead, since this code affects all shapebase derived objects. You will, however, have to add a function instead of modifying it. How adept at coding are you?
#6
04/12/2011 (3:56 am)
OK, same as above but using the value instead of the level.

void ShapeBase::processTick(const Move* move)
{
   // Energy management
   if (mDamageState == Enabled && mDataBlock->inheritEnergyFromMount == false) {
      F32 store = mEnergy;
      mEnergy += mRechargeRate;
      // Addition
      F32 engPct = getEnergyValue();
      F32 dmgPct = 1 - getDamageValue();
      // end
      if (mEnergy > mDataBlock->maxEnergy)
         mEnergy = mDataBlock->maxEnergy;
      // Addition
      else if (engPct > dmgPct)
         mEnergy = mDataBlock->maxEnergy * dmgPct;
      // end
      else
         if (mEnergy < 0)
            mEnergy = 0;

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

   // Repair management
   if (mDataBlock->isInvincible == false)
   {
      F32 store = mDamage;
      mDamage -= mRepairRate;
      mDamage = mClampF(mDamage, 0.f, mDataBlock->maxDamage);

      if (mRepairReserve > mDamage)
         mRepairReserve = mDamage;
      if (mRepairReserve > 0.0)
      {
         F32 rate = getMin(mDataBlock->repairRate, mRepairReserve);
         mDamage -= rate;
         mRepairReserve -= rate;
      }

      if (store != mDamage)
      {
         updateDamageLevel();
         // Addition
         F32 engPct = getEnergyValue();
         F32 dmgPct = 1 - getDamageValue();
         if (engPct > dmgPct)
         {
            mEnergy = mDataBlock->maxEnergy * dmgPct;
            setEnergyLevel(mEnergy);
         }
         // end
         if (isServerObject()) {
            char delta[100];
            dSprintf(delta,sizeof(delta),"%g",mDamage - store);
            setMaskBits(DamageMask);
            Con::executef(mDataBlock, "onDamage",scriptThis(),delta);
         }
      }
   }

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

      if (store != mDamage) {
         updateDamageLevel();
         // Addition
         F32 engPct = getEnergyValue();
         F32 dmgPct = 1 - getDamageValue();
         if (engPct > dmgPct)
         {
            mEnergy = mDataBlock->maxEnergy * dmgPct;
            setEnergyLevel(mEnergy);
         }
         // end
         if (isServerObject()) {
            setMaskBits(DamageMask);
            char delta[100];
            dSprintf(delta,sizeof(delta),"%g",mDamage - store);
            Con::executef(mDataBlock, "onDamage",scriptThis(),delta);
         }
      }
   }
}

This code does not have the same limitation as previous, so you can have different values for maxEnergy and maxDamage.
#7
04/12/2011 (3:17 pm)
Thanks a lot Paul! I was mucking around in shapebase and found those areas, but I haven't had time to implement anything yet. This looks like exactly what I need. Again, thanks!
#8
05/14/2011 (7:18 pm)
Just a little background:

maxEnergy =  %obj.getDamageLevel();

Datablocks store static information about all objects of a given type. So, for example, all Players using the PlayerBody datablock will have the same shape file, max damage/energy, max movement speeds, etc. The datablock definitions you see in scripts actually create the datablock object with the values you see, and this generally happens when a server is created (in TGE, scripts are executed in server/game.cs, in onServerCreated).

When a client joins the game, all the datablocks the server has loaded are sent to the client, so that the client can accurately predict the behaviour of game objects using those datablocks.

So by changing a datablock value, you've got two problems to worry about:

1. You're changing the behaviour of multiple game objects using that datablock.
2. Datablocks are only updated on the client when the client joins the game... so if you change mid-game, clients won't know about the changes, and there will be discrepancies between what happens on the server and what the clients predict should happen.

Number one is your main issue, but number two can be more or less obvious depending on the parameter you change.
#9
05/17/2011 (5:19 pm)
Daniel, are you saying that Paul's code above won't work in multiplayer? It seems to do the trick for me, but I haven't tested it in multiplayer yet. I gave my old P4 to a computerless friend, and am working on building a new box to run a small game server on.

#10
05/18/2011 (2:23 am)
No, I think he is just saying what happens if you try the "fiddle-with-the-datablock" method, and he is correct in the inherent flaws of the method.

The code example I gave does not change any datablock values, it just limits the shapebase energy value when required. It should be network friendly, and can be used with multiple player datablocks.
#11
05/18/2011 (6:36 pm)
OK, cool. Thanks again Paul.