Jump to content

Projectiles, Damage and how it works in XCOM


Amineri

Recommended Posts

Just in time for the upcoming Enemy Within release I think I've finally traced out most of the chain that handles dealing damage.

 

There are a few overall steps being performed:

1) The firing action triggers the weapon to fire

2) The weapon firing causes a projectile to be spawned

3) The projectile travels along its designated path until the correct conditions are met, causing the projectile to explode (all projectiles explode)

4) The projectile explodes

5) Each unit that takes damage processes the effects

 

The call chain appears to dip into native/Unreal core code at least three times, so some of the steps in the sequence involved a little bit of guesswork and may not be 100% accurate.

 

The details of each step are pretty long and ugly, so be warned!

 

--------------------------

 

1) The firing action triggers the weapon to fire

 

It all begins in the XGAction_Firing.state'Firing'. There is a lot of code executed there, and hiding in there is a single small line:

FireWeapon();

Note that Disabling Shot does not get processed this way but instead has a separate earlier call : FireDisablingShot();

 

The XGAction_Fire.state'Firing'.FireWeapon calls the function XGAction.DoFireUponUnitEvent which triggers a sequence event, diving into the core Unreal Event code. I'm pretty sure that eventually it ends up back at XComUnitPawnNativeBase.FireWeapon. There is no upk code that invokes XComUnitPawnNativeBase.FireWeapon.

 

XComUnitPawnNativeBase.FireWeapon calls Weapon.CustomFire(); -- this uses the currently active weapon for the unit.

 

This in turn calls XComWeapon.CustomFire (The 'XCom' prefix indicates this is a weapon pawn/mesh and not the game weapon which would be XGWeapon). XComWeapon.CustomFire calls WeaponComponent.CustomFire.

 

---------------------

 

2) The weapon firing causes a projectile to be spawned

There are four different type of XComWeaponComponents : Grenade, Melee, Projectile, and Shotgun. Grenades are special in that they follow a computed path (previewed when throwing the grenade). Shotguns are special in that they don't generate the same type of projectile as most other weapons.

 

Most weapons however use the XComWeaponComponent_Projectile class. This includes most ballistic, laser and plasma weapons. Rocket Launchers also fall in this class.

 

XComWeaponComponent_Projectile.CustomFire computes the start point and direction of the projectile (for most cases) using:

StartTrace = OwnerWeapon.Instigator.GetWeaponStartTraceLocation();
AimDir = vector(OwnerWeapon.GetAdjustedAim(StartTrace));

So far I've been unable to find this GetAdjustedAim function (which must be how the deviation for targeted shots that miss is computed).

 

It then calls :

SpawnProjectile(StartTrace, AimDir, bCanDoDamage, HACK_bMindMergeDeathProjectile);

This is handled via the parent's XComWeaponComponent.SpawnProjectile which has the code line:

OwnerWeapon.LastProjectile = PerformSpawnProjectile(OwnerWeapon, kFireAction, ClassToSpawn, NewPosition, NewDirection, bAnimNotify_FireWeaponCustom_DoDamage, HACK_bMindMergeDeathProjectile);

XComWeaponComponent.PerformSpawnProjectile does a variety of things (including InitFX) but the Projectile is actually created via the lines:

SpawnedProjectile = XComTacticalGRI(class'Engine'.static.GetCurrentWorldInfo().GRI).m_kBattle.m_kLevel.m_kProjectileMgr.FindPooledProjectile(ClassToSpawn, OwnerWeapon, NewPosition);

OR

SpawnedProjectile = OwnerWeapon.Spawn(ClassToSpawn, OwnerWeapon,, NewPosition,, OwnerWeapon.ProjectileTemplate, true);

FOLLOWED BY:

InitProjectile(NewPosition, NewDirection, SpawnedProjectile, bAnimNotify_FireWeaponCustom_DoDamage);

SpawnedProjectile is a variable of type XComProjectile

 

XComWeaponComponent.InitProjectile passes control to the XComProjectile class via the call :

SpawnedProjectile.InitProjectile(Direction, OwnerWeapon.bPreviewAim, bAnimNotify_FireWeaponCustom_DoDamage);

XComProjectile.InitProjectile does a few things. One of the more critical ones is call

CalculateUnitDamage();

which sets the projectile damage and damage radius.

 

Projectiles can either be designated to strike a target pawn or a designated location. Targeted shots (as opposed to rocket launchers) that hit are designated to strike a target pawn. Scattered shots (Rocket Launchers) and missed shots are designated to strike a designated location.

 

XComProjectile extends the core Unreal Projectile class. XComProjectile calls Projectile.Init, which invokes the core Unreal projectile code to begin animating the projectile.

 

---------------------

3) The projectile travels along its designated path

The projectile travels along its path using the built-in Unreal Engine projectile handling. The Unreal Engine invokes two possible call-backs:
a) XComProjectile.Tick -- a timer to check various conditions related to time
b) XComProjectile.ProcessTouch -- invoked whenever the Projectile touches another actor (which can be an environmental object or a unit).
One of the primary functions of Tick is to destroy/shutdown the projectile if it has existed for too long. For example, if a miss is high and fails to hit any actors at all the projectile will timeout and be destroyed.
Tick also implements some of the Reflection code that enables Ethereals to reflect the projectile back at the originating unit.
The more interesting part is in ProcessTouch. This is invoked whenever the projectile "collides" with another actor.
ProcessTouch handles the following cases:
First, if the projectile was reflected and the touched actor is the firing unit, then call Explode() to explode the projectile (damaging the firing unit).
Second, if the projectile was not reflected, the shot was a hit, and the touched actor is the target unit, then call Explode() to explode the projectile (damaging the targetted unit).
The third case is a bit more complex, and is used to handle some incidental damage.
If the touched actor is an environmental object, or if the shot is aimed at a target location AND the touched actor is an XComUnit, then one of two things can happen:
i) If the firing weapon is a Rocket Launcher and the projectile has moved at least 192 units (2 tiles), and the touched actor isn't fragile and the shot is not a hit the the rocket explodes immediately. (In practice I don't think this can happen).
ii) Otherwise if the touched actor is fragile and is at least 2 tiles from the source then damage is dealt to the touched actor but the projectile does not explode, and so continues moving.
This last case does happen in game. For example, it is how ballistic fire destroys windows in between the firing unit and the target unit (regardless of whether the shot was a hit or not).
A fourth case is if the projectile is not designated with a target unit (and is aiming for a location instead). This breaks into two subcases:
i) If the projectile is not the minimum required distance away from the firing unit then it deals damage to the actor but does not explode.
ii) If the projectile is at least the minimum required distance, then it explodes.
This required minimum distance is the distance between the firing location and the target location.
All of the following options above result in the projectile invoking XComProjectile.Explode.

 

-------------------------

 

4) The projectile explodes

XComProjectile.Explode handles most of the details of the projectile explosion. It calls:
DealSplashDamage(m_kFiredFromUnit, Damage, DamageRadius, MyDamageType, MomentumTransfer, HitLocation);

DealSplashDamage fills out a native Unreal Engine data structure ProjDamage:

    ProjDamage.EventInstigator = m_kFiredFromUnit;
    ProjDamage.DamageCauser = self;
    ProjDamage.Target = ImpactedActor;
    ProjDamage.DamageAmount = int(DamageAmount);
    ProjDamage.Radius = InDamageRadius;
    ProjDamage.DamageType = DamageType;
    ProjDamage.Momentum = Velocity;
    ProjDamage.HitLocation = HurtOrigin;
    ProjDamage.bIsHit = bIsHit;
    ProjDamage.HitInfo = TracedImpactInfo;
    ProjDamage.bRadialDamage = InDamageRadius > float(0);
    ProjDamage.bCausesSurroundingAreaDamage = class'XComDamageType'.static.CausesSurroundingAreaDamage(DamageType); 

It then calls:

bCausedDamage = class'XComDamageType'.static.DealDamage(ProjDamage); 

passing control the the XComDamageType class in order to actually deal the damage.

 

After DealSplashDamage returns to Explode, Explode shuts down the projectile with the call:

ShutdownProjectile(HitLocation, HitNormal); 

This terminates any remaining visual effects and ensures that the projectile is properly cleaned up.

 

----------------

 

5) Each unit that takes damage processes the effects

XComDamageType.DealDamage is a native function. It is unclear how it functions but it likely passes information directly to the Unreal Engine to determine which actors are touched by the explosion (is often only the targetted unit, but may be more if the projectile has a damage radius).

 

Each of the three actor classes : XComUnitPawn, XComDestructibleActor and XcomFracLevelActor have a TakeSplashDamage function that is not invoked by the upk code, so this function is the most likely callback point from the Unreal Engine/native code call initiated by XComDamageType.DealDamage. This allows Explode()-ed projectiles to deal damage to the environment as well as units.

 

XComUnitPawn.TakeSplashDamage invokes XComUnitPawn.TakeDirectDamage.

 

XComUnitPawn.TakeDirectDamage invokes XGUnit.OnTakeDamage.

 

OnTakeDamage is where the damage is applied. It applies some Combat Stims effects (so that they are effective against explosive damage) as well as HEAT bonus damage and Shredded bonus damage. It processes unit deaths and critical wounding as well.

 

Link to comment
Share on other sites

Wiki page 'Projectiles and damage functionality' is now active. Found under categories 'XCOM Modding' and 'Functions XCOM Modding' as well as the core 'XCOM'. I thought about putting it also under 'Code Breakdown', but thought it might be considered misleading as it doesn't go beyond the name of various function calls. Easy enough to add if anyone disagrees.

 

-Dubious-

Link to comment
Share on other sites

Awesome work on decrypting that information. Now, i have a little insight on something:

 

i) If the firing weapon is a Rocket Launcher and the projectile has moved at least 192 units (2 tiles), and the touched actor isn't fragile and the shot is not a hit the the rocket explodes immediately. (In practice I don't think this can happen).

 

I think this is what happens if you shoot a rocket at a distance but you have a block between the rocketeer and the intended area, but that block is not solid and it is not right next to the rocketeer (as if in the middle of the trayectory, there was a breakable wall or something). If the distance is too short, your area of effect does not proyect over the intended target, but rather closes up right next to the soldier or right next to the solid block.

 

That should explain the fact that sometimes you can get an "area of effect" around the intended target, but the %-to-hit is shown as 0, modeling the fact that the missile will explode before hitting the target.

 

ALSO: Now, with this information, what would you deduce a "%-to-hit" really means? Is it such as "40% to hit any projectile on the target", or more like an average from all projectiles (deduced from a "i want to hit this alien in a fixed point, and my chances are 40%".

 

Perhaps it is better to mod "%-to-hit" towards a more "%-to-hit-X-damage", such as dealing 1 damage with a higher chance by "bullet spraying" mechanics (just hitting some projectiles in his body, you just want to hit them somewhere, a LMG approach, if you will) than dealing 10 damage from an aimed "shot-thru-the-heart" (full 100% proyectile hit, from an sniper shot)?

 

If each bullet has its trajectory, then you can opt to take a risk and shoot a wounded alien with a higher chance of dealing that pesky 1 damage and kill them, rather than shooting for full damage and missing it altogether (making LMG a higher-aim lower-dmg suppresion choice, sniper rifles a high-dmg low-aim killshot, and rifles something in between, as i would presume is meant to be).

Edited by osito2dancer
Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...