Game Development Community

T3D 1.1 Beta 3 - projectile.cpp setting of mSourceObject - RESOLVED

by James Novosel · in Torque 3D Professional · 03/16/2011 (8:54 am) · 13 replies

Build 1.1 Beta 3, 1.1 Release

Platform: Windows XP 32 bit

Target: projectile.cpp's enum ProjectileConstants SourceIdTimeoutTicks (=7, 231ms?) is evaluated in:

void Projectile::processTick( const Move *move ):
if(mSourceObject && mCurrTick > SourceIdTimeoutTicks)
{
mSourceObject = 0;
mSourceObjectId = 0;
//Con::errorf(ConsoleLogEntry::General, "Projectile::processTick: mCurrTick > SourceIdTimeoutTicks");
}

Issues: function SomeObject::damage(%this, %obj, %sourceObject, %position, %damage, %damageType) in script code returns a %sourceObject = 0 making it impossible to determine who inflicted the damage and who to award points to when scoring.

Steps to Repeat: Try tracking %sourceObject when shooting at something from close (works) and far away (returns the 0 value).

Suggested fix: I commented out the mSourceObject and mSourceObjectid = 0 in projectile.cpp rather than increase the SourceIdTimeoutTicks (was not sure what a good value would be - how far does a projectile travel in 231 ms - its not that far). Not sure of the implications of not setting the mSourceObject and mSourceObjectId to zero. Not sure why we even would be doing this - if I shot the projectile I would want that fact to remain with the projectile until the projectile explodes or no longer exists.

Link to Console Log: Nothing unusual in the console log (other than my ConsoleLogEntry identifying this is why %sourceObject is returning 0 to the script damage function).

#1
03/16/2011 (10:09 am)
Greetings!

Logged as THREED-1487. Thanks!

- Dave
#2
03/19/2011 (9:32 pm)
There have been many discussion of this very thing over the years. In the past it was explained as intended behavior - despite many people feeling justified in calling it a bug. Just a fyi.
#3
03/21/2011 (8:31 pm)
After reviewing the projectile.cpp source code it is clear as to why mSourceObject exists in the first place. Looking in Projectile::simulate() describes what is going on. When a projectile is created it is usually spawned where the weapon mount point is located which means that the projectile will most likely spawn inside of an object that has collision detection enabled. So to prevent the projectile from colliding and exploding within the weapon object the original author(s) of projectile.cpp decided to put in a time limited collision disabled state for the weapon or projectile origin object which is what mSourceObject references.

I can only assume this was done for the Tribes spinfusor weapon, where the spinning disc actually spawns midway through the weapon upon the player firing, and later projectile weapons. This is based on the short assumed safe time for the projectile to be away from the weapon object which is 231 milliseconds or 7 ticks.

Once the SourceIdTimeoutTicks is reached then it invalidates the mSourceObject reference and mSourceObjectId variables which then causes some code within Projectile::simulate() to stop from disabling and re-enabling of the projectile origin object's collision detection anymore.


Now as for determining who fired the projectile look in game/scripts/server/weapons.cs and find this function:

function WeaponImage::onFire(%this, %obj, %slot)
{
...Snip...

   // Create the projectile object
   %p = new (%this.projectileType)()
   {
      dataBlock = %this.projectile;
      initialVelocity = %muzzleVelocity;
      initialPosition = %obj.getMuzzlePoint(%slot);
      sourceObject = %obj;
      sourceSlot = %slot;
      client = %obj.client;
   };
   MissionCleanup.add(%p);
   return %p;
}

The source of the function that I didn't snip in this post is how projectiles are spawned upon firing a weapon. It is setting the projectile datablock, initial position and velocity. Now sourceObject points to the local function variable %obj of which is the weapon that is mounted to the player model/object. What you're interested in is the dynamic variable of the projectile named in this case simply client. This sets who fired the projectile by assigning it the object ID of the player's game connection.

Now look in the file game/scripts/server/player.cs and look for this function:
function Armor::damage(%this, %obj, %sourceObject, %position, %damage, %damageType)

That is the receiving end of the projectile upon collision and damage of the player object.


EDIT:

Actually, look in game/scripts/server/projectile.cs and function:
function ProjectileData::onCollision(%data, %proj, %col, %fade, %pos, %normal)
{
   //echo("ProjectileData::onCollision("@%data.getName()@", "@%proj@", "@%col.getClassName()@", "@%fade@", "@%pos@", "@%normal@")");

   // Apply damage to the object all shape base objects
   if (%data.directDamage > 0)
   {
      if (%col.getType() & ($TypeMasks::ShapeBaseObjectType))
         %col.damage(%proj.sourceObject, %pos, %data.directDamage, %data.damageType);
   }
}

That's how a projectile collision turns into player or other object damage. Enjoy.


Edit: Here is the straight-forward fix to the problem.
Edit game/scripts/server/projectile.cs and change the function ProjectileData::onCollision to this:
function ProjectileData::onCollision(%data, %proj, %col, %fade, %pos, %normal)
{
   //echo("ProjectileData::onCollision("@%data.getName()@", "@%proj@", "@%col.getClassName()@", "@%fade@", "@%pos@", "@%normal@")");

   // Apply damage to the object all shape base objects
   if (%data.directDamage > 0)
   {
      if (%col.getType() & ($TypeMasks::ShapeBaseObjectType))
         %col.damage(%proj, %pos, %data.directDamage, %data.damageType);
   }
}

All that was changed was %proj.sourceObject to just %proj in the first argument of %col.damage() method call.
#4
03/22/2011 (12:58 am)
Fix for ideal behaviour: re-enable collision with the source object after SourceIDTimeoutTicks, but retain the ID for use in the callback. Just comment out this line:
mSourceObjectId = 0;
As far as I remember, it's the sourceObjectId which is sent to the callback, but the sourceObject reference itself which is used for collision.
#5
03/22/2011 (2:19 am)
Related post to this here
#6
03/22/2011 (9:57 am)
I guess I need to make it more clear. If you want more information about who or what created the projectile then simply attach the object ID or whatever as a dynamic variable to the projectile during creation as seen as an example in game/scripts/server/weapons.cs in my previous post. And then grab that same information from the projectile in the function ProjectileData::onCollision of game/scripts/server/projectile.cs.

Therefore there is not nor has there ever been a problem with sourceObject from the projectile.cpp source file as there has hardly been any changes from it in T3D compared to TGE 1.5.2 except some organization cleanup. The problem is the script files. Somebody messed up passing of information going from ProjectileData::onCollision to reaching the function Armor::damage() function.

Player scoring and etc.. is working fine in TGE 1.5.2, it is broken in T3D because somebody basically messed up or left the changes incomplete when the scripts were re-organized from what I see. The engine its self is fine as far as the projectiles are concerned.
#7
10/11/2011 (11:09 am)
Fixed in 1.2
#8
10/15/2011 (12:19 pm)
I'm posting here to have a record of my opposition to the change that was made to resolve this issue as in my opinion that it was the incorrect fix. I saw this changelog entry on page 13:
  • Prevent Projectile::mSourceObject field being cleared after N ticks, but keep the disable-collision behaviour. This fixes problems with scripts that use this field to determine the object that fired the projectile (THREED-1487).

According to the notes in projectile.cpp:
addField("sourceObject",     TypeS32,     Offset(mSourceObjectId, Projectile),
      "@brief ID number of the object that fired the projectile.nn"
      "@note If the projectile was fired by a WeaponImage, sourceObject will be "
      "the object that owns the WeaponImage. This is usually the player.");

Sounds like to me the player model will be the one to have its collision disabled during the N ticks, but since you've changed it then the player model will not have collision turned back on and is therefore not collide able. Even if this isn't the case anymore (Such as %obj in WeaponImage::onFire() is the weapon model in T3D), what if somebody were to do a Sabotage game clone, have the projectile set to ballistic, the player aimed straight up and fired, and didn't hit anything going up or down, but now the projectile won't collide with the model that fired it.

Another problem you haven't considered is the fact that you're still leaving the ProjectileData::onCollision callback handler bugged to where nobody can take the provided sourceObj and determine what kind of a projectile collided so they could do damage source statistics or even custom death messages similar to Tribes 1 and 2. This callback handler is where the bug fix should have happened as I explained above at the end of post #3. As I've I already explained in IRC several times the projectile.cpp was never at fault and worked perfectly fine through Tribes 2, TGE 1.1.0 - 1.5.2, and probably TGEA as I don't know when the scripts were changed to cause the problem we have now with it in T3D 1.1.
#9
10/15/2011 (4:38 pm)
@Nathan
Please keep in mind that what was posted was a preliminary changelog, not the complete one which will include everything that goes into this fix. The changelog is based on svn commit messages that, as part of their nature, are not overly detailed. They're meant to convey what was changed with as much brevity as possible.
#10
10/15/2011 (10:09 pm)
Collision was disabled for n ticks to prevent the weapon from damaging the player on the way out of the player's hit box. After n ticks, projectiles will collide with the sourceObject. The bugged behavior was removing the sourceObject data from the projectile after n ticks.

Perhaps n should be something we can pass in as part of the projectile data....
#11
10/16/2011 (5:59 am)
Agree with Nathan here, the whole isssue of the projectile sourceObject is entirely due to script abuse. The sourceObject was never intended to be used to identify who shot the projectile, but was only meant to prevent collision with the caster. Tracking who launched it for other purposes should be done entirely in script using a dynamic field.

Item contains a similar issue where some fool decided to use the static member variable to indicate whether or not the item should respawn, when in the engine the static is intended to indicate whether or not the item moves.
#12
10/16/2011 (6:05 am)
Wasn't the disabled collision a leftover from one of the Tribes guns that had a visible projectile which started inside the gun, and thus inside the player bounds? Sure someone mentioned that aeons ago ...

Personally I tend to start "shooting" with a short raycast, thus saving projectile spawning at very close range.

[edit]lolz. Okay Nathan you caught me tl;dr ... surrenders
#13
01/03/2012 (10:37 am)
FYI - the script fix in "scripts/server/projectile.cs" IS included in the 1.2 Release ... just to clear that up.

[edit] But for AFX users, it appears the change do not make it into the AFX for T3D 1.2 version ... its still using %proj.sourceObject