
//--------------------------------------------------------------------------
// Projectiles.cs: Note that the actual projectile blocks are stored with
//                  with weapon that uses them in base/scripts/weapons/*.cs,
//                  the blocks below are only to illustrate the default values
//                  for each block type.  Also, ProjectileData cannot be used
//                  as a concrete datablock type.
//  Inheritance:
//   ProjectileData            : GameBaseData
//   LinearProjectileData      : ProjectileData
//   LinearFlareProjectileData : LinearProjectileData
//   GrenadeProjectileData     : ProjectileData
//   SeekerProjectileData      : ProjectileData
//   SniperProjectileData      : ProjectileData
// 
//--------------------------------------------------------------------------
//-------------------------------------- Default functions
//

// This code handles regular projectiles colliding with objects
function ProjectileData::onCollision(%Data, %Projectile, %TargetObject, %Modifier, %Position, %Normal)
{
   %Name = %TargetObject.getDataBlock().getName();
   if (%Name $= "")
      return;

   if (%Name.Category $= "ForceField")
   {
      %TargetObject.getDataBlock().damageObject(%TargetObject, %Projectile.SourceObject, %Position, %Data.DirectDamage * %Modifier, %Data.DirectDamageType);
      return;
   }

   // If it should have kickback strength and does not have a blast radius, we do the kickback now.
   %Impulse = %Data.kickBackStrength;
   if (%Impulse $= "")
      %Impulse = 0;

   if (%Impulse != 0 && %Data.hasDamageRadius != true)
   {
      %TargetObject.getOwnerClient().headShot = "";

      if (%TargetObject.CheckForShields() == true)
         %Impulse *= 0.75;

      %ImpulseVector = VectorNormalize(%Projectile.getVelocity());
      %ForceVector = VectorScale(%ImpulseVector, %Impulse);
      %TargetObject.applyImpulse(%Position, %ForceVector);
   }

   // If it has damage, then damage the target
   if ((%Data.DirectDamage * %Modifier) != 0)
      %TargetObject.damage(%Projectile.SourceObject, %Position, %Data.DirectDamage * %Modifier, %Data.DirectDamageType);
}

function ProjectileDetonate(%Projectile)
{
   if (isObject(%Projectile) == false)
      return;

   %Transform = %Projectile.getTransform();
   %Position = posFromTransform(%Transform);

   %Data = %Projectile.getDataBlock();
   %PrematureProjectile = %Data.PrematureProjectile;

   %Item = new Item()
   {
      datablock = %PrematureProjectile;
      position = %Position;
      sourceObject = %Projectile.sourceObject;
   };
   MissionGroup.add(%Item);

   %Projectile.delete();
   schedule(250, 0, detonateGrenade, %Item);
}

function SniperProjectileData::onCollision(%Data, %Projectile, %TargetObject, %Modifier, %Position, %Normal)
{
   %DamageAmount = %Data.directDamage;

   // Apply the Energy to Damage Factor
   if (%Projectile.damageFactor !$= "")
      %DamageAmount *= %Projectile.damageFactor;
   
   %Category = %TargetObject.getDataBlock().Category;
   if (%Category $= "ForceField")
   {
      %TargetObject.getDataBlock().damageObject(%TargetObject, %Projectile.SourceObject, %Position, %Data.DirectDamage * %Modifier, %Data.DirectDamageType);
      return;
   }

   if(%TargetObject.getDataBlock().getClassName() $= "PlayerData")
   {
      %DamageLocation = getWord(%targetObject.getDamageLocation(%position), 0);
      %DamageSubLocation = getWord(%targetObject.getDamageLocation(%position), 1);
      %Length = strlen(%DamageSubLocation);
      %Front = getSubStr(%DamageSubLocation, %Length - 4, %Length);

      if(%DamageLocation $= "head")
      {   
         %TargetObject.getOwnerClient().headShot = true;
         %TargetObject.schedule(1250, NoHeadShot);
         %DamageAmount *= 1.3;

         if (%Front $= "ront")
            %TargetObject.setWhiteOut(%Modifier * 1.2);

         if (%Front $= "ddle")
            %TargetObject.setWhiteOut(%Modifier * 0.7);

         if (%Front $= "back")
         {
            %Flag = %TargetObject.holdingFlag;
            if (%Flag > 0)
            {
               %TargetObject.throwObject(%Flag);
               %Flag.ApplyImpulse(%Position, "0 0 100");
               %Velocity = VectorScale(%Flag.getVelocity(), 0.2);
               %Flag.schedule(800, setVelocity, %Velocity);
            }
         }         
      } else {
         %TargetObject.getOwnerClient().headShot = "";

         if (%DamageLocation $= "torso" && getSubStr(%DamageSubLocation, 0, 4) $= "back")
         {
            %Pack = %TargetObject.getMountedImage($BackPackSlot).getName();
            if (%Pack $= "SatchelChargeImage")
            {
               %TargetObject.PrematureSatchel();
            } else {

               %Flag = %TargetObject.holdingFlag;
               if (%Flag > 0)
               {
                  %TargetObject.throwObject(%Flag);
                  %Flag.ApplyImpulse(%Position, "0 0 100");
                  %Velocity = VectorScale(%Flag.getVelocity(), 0.2);
                  %Flag.schedule(800, setVelocity, %Velocity);
               }
            }
         }
      }
   }

   // We do the kickback now.
   %Impulse = %Data.kickBackStrength;
   if (%Impulse $= "")
      %Impulse = 0;

   if (%Impulse != 0)
   {
      if (%TargetObject.CheckForShields() == true)
         %Impulse *= 0.75;

      if (%Data.reverseKickBack == true)
         %Impulse *= -1;

      %ImpulseVector = VectorNormalize(%Projectile.SourceObject.getMuzzleVector(0));
      %ForceVector = VectorScale(%ImpulseVector, %Impulse);
      %TargetObject.applyImpulse(%Position, %ForceVector);
   }

   %TargetObject.damage(%Projectile.SourceObject, %Position, %DamageAmount, %Data.directDamageType);
}

function Player::NoHeadShot(%Player)
{
   %Player.headShot = "";
}

function ShapeBaseImageData::onFire(%data, %Object, %slot)
{
   if (%Object.PlayerNoFire == true)
      return;

   %Data.lightStart = getSimTime();

   // If cloaked, uncloak the player for 0.5 seconds to cause the partial phasing effect
   if (%Object.station $= "" && %Object.isCloaked())
   {
      if (%Object.respawnCloakThread !$= "")
      {
         Cancel(%Object.respawnCloakThread);
         %Object.setCloaked(false);
         %Object.respawnCloakThread = "";
      } else {
         if (%Object.getEnergyLevel() > 20)
         {   
            %Object.setCloaked(false);
            %Object.reCloak = %Object.schedule(500, "setCloaked", true);
         }
      }   
   }
   
   %Vehicle = 0;

   %SourceObject = %Object;
   if (%Object.lastPilot !$= "")
      %SourceObject = %Object.lastPilot;

   // If the Weapon takes energy, consume it now.
   if (%Data.usesEnergy)
   {
      // If part of a greater object, take the power from there (such as a vehicle)
      if (%Data.useMountEnergy)
      {
         %UseEnergyObject = %Object.getObjectMount();
         if (!%UseEnergyObject)
            %UseEnergyObject = %Object;

         %Energy = %UseEnergyObject.getEnergyLevel();
         %Source = %UseEnergyObject;
      } else {
         %Energy = %Object.getEnergyLevel();
      }
      if (%Energy < %Data.minEnergy)
         return;
   }

   if (%Data.projectileSpread)
   {
      %Pi = 3.1415926;
      %Spread = 2 * %Pi * %Data.projectileSpread;

      %Vector = %Object.getMuzzleVector(%Slot);
      %VectorX = (getRandom() - 0.5) * %Spread;
      %VectorY = (getRandom() - 0.5) * %Spread;
      %VectorZ = (getRandom() - 0.5) * %Spread;
      %Matrix = MatrixCreateFromEuler(%VectorX @ " " @ %VectorY @ " " @ %VectorZ);
      %Vector = MatrixMulVector(%Matrix, %Vector);

      %Projectile = new (%Data.projectileType)() 
      {
         dataBlock        = %Data.projectile;
         initialDirection = %Vector;
         initialPosition  = %Object.getMuzzlePoint(%Slot);
         sourceObject     = %SourceObject;
         sourceSlot       = %Slot;
         vehicleObject    = %Vehicle;
      };
   } else {
      %WeaponProjectile = %Data.Projectile;
      switch$ (%Object.WCSProjectile)
      {
         case "EMP" :
            %WeaponProjectile = %Data.EMPProjectile;

         case "Incendiary" :
            if (%Object.InWater != true)
               %WeaponProjectile = %Data.IncProjectile;
            else {
               %FailSound = %Data.FailSound;
               serverPlay3D(%FailSound, %Object.getMuzzlePoint(%slot));
               messageClient(%SourceObject.client, '', "Weapons Control System prevents waste of Incendiary Weapon underwater");
               return;
            }

         case "Concussion" :
            %WeaponProjectile = %Data.ConcProjectile;
      }

      %Projectile = new (%Data.projectileType)()
      {
         dataBlock        = %WeaponProjectile;
         initialDirection = %Object.getMuzzleVector(%slot);
         initialPosition  = %Object.getMuzzlePoint(%slot);
         sourceObject     = %SourceObject;
         sourceSlot       = %Slot;
         vehicleObject    = %Vehicle;
      };
   }

   if (isObject(%Object.lastProjectile) && %Object.deleteLastProjectile)
      %Object.lastProjectile.delete();

   %Object.lastProjectile = %Projectile;
   %Object.deleteLastProjectile = %Data.deleteLastProjectile;
   MissionCleanup.add(%Projectile);

   %Projectile.InWater = %Object.InWater;
   if (%Projectile.InWater == true && %Projectile.getDataBlock().PrematureWetTime > 0)
   {
      %Time = %Projectile.getDataBlock().PrematureWetTime;
      %Time += getRandom(%Projectile.getDataBlock().PrematureWetTimeVariance);
      schedule(%Time, 0, ProjectileDetonate, %Projectile);
   }

   // AI hook
   if(%Object.client)
      %Object.client.projectile = %Projectile;

   if(%Data.usesEnergy)
   {
      if(%Data.useMountEnergy)
         %useEnergyObject.setEnergyLevel(%Energy - %Data.fireEnergy);
      else
         %Object.setEnergyLevel(%Energy - %Data.fireEnergy);
   } else {
      %Object.decInventory(%Data.ammo,1);
   }

   %Data = %Projectile.getDataBlock();

   return(%Projectile);
}

function ShapeBaseImageData::onUnmount(%data, %obj, %slot)
{
   if (%data.deleteLastProjectile && isObject(%obj.lastProjectile))
   {
      %obj.lastProjectile.delete();
      %obj.lastProjectile = "";
   }
}

function TurretImageData::deconstruct(%data, %obj, %slot)
{
   if (%data.deleteLastProjectile && isObject(%obj.lastProjectile))
   {
      %obj.lastProjectile.delete();
      %obj.lastProjectile = "";
   }
}

function ShapeBaseImageData::deconstruct(%data, %obj, %slot)
{
   if (%data.deleteLastProjectile && isObject(%obj.lastProjectile))
   {
      %obj.lastProjectile.delete();
      %obj.lastProjectile = "";
   }
}

//--------------------------------------------------------------------------

//add mortars to the "grenade set" so the AI's can avoid them better...
function MortarImage::onFire(%data, %obj, %slot)
{
   if (%Obj.PlayerNoFire == true)
      return;

   %p = Parent::onFire(%data, %obj, %slot);
   AIGrenadeThrown(%p);
}

function ElfGunImage::onFire(%data, %obj, %slot)
{
   if (%Object.PlayerNoFire == true)
      return;

   %p = Parent::onFire(%data, %obj, %slot);
   
   if(!%p.hasTarget())
      %obj.playAudio(0, ELFFireWetSound);
}

function TargetingLaserImage::onFire(%data, %obj, %slot)
{
   if (%Obj.PlayerNoFire == true)
      return;

   %p = Parent::onFire(%data, %obj, %slot);
   %p.setTarget(%obj.team);
}

$ELFZapSound = 2;
$ELFFireSound = 3;

function ELFProjectileData::zapTarget(%Data, %Projectile, %Target, %Player) 
{ 
   %TargetRate = %Target.getRechargeRate(); 
   %PlayerRate = %Player.getRechargeRate(); 

   %Target.teamDamageStateOnZap = $teamDamage; 

   if(%Target.teamDamageStateOnZap == false && %Target.team == %Player.team) 
   { 
      %Target.setRechargeRate(%TargetRate); 
      %Player.setRechargeRate(%PlayerRate); 
   } else { 
      %EnergyDrain = %Target.getDataBlock().elfDrain; 
      if (%EnergyDrain $= "") 
         %EnergyDrain = %Data.drainEnergy; 

      %Target.setRechargeRate(%TargetRate - %EnergyDrain); 
      %Player.setRechargeRate(%PlayerRate + (%EnergyDrain * 0.4)) ; 
   } 

   // If meant to pull the target towards it, then do so now. 
   if (%Data.targetPull !$= "") 
   { 
      %Position1 = posFromTransform(%Player.getTransform()); 
      %Position2 = posFromTransform(%Target.getTransform()); 

      %VectorBetween = VectorNormalize(VectorSub(%Position1, %Position2)); 
      %PullVector = VectorScale(%VectorBetween, %Data.targetPuill); 

      %Target.ApplyImpulse(%Position2, %PullVector); 
   } 

   %Projectile.checkELFStatus(%Data, %Target, %Player); 
} 

function ELFProjectileData::unzapTarget(%Data, %Projectile, %Target, %Player)
{
   cancel(%Projectile.ELFrecur);

   %Target.stopAudio($ELFZapSound);
   %Target.zapSound = false;

   %Player.stopAudio($ELFFireSound);
   %Player.zappingSound = false;
	
   %TargetRate = %Target.getRechargeRate();
   %PlayerRate = %Player.getRechargeRate();

   if (%Target.teamDamageStateOnZap == false && %Target.team == %Player.team)
   {
      %Target.setRechargeRate(%TargetRate);
      %Player.setRechargeRate(%PlayerRate);
   } else {
      %EnergyDrain = %Target.getDataBlock().elfDrain;
      if (%EnergyDrain $= "")
         %EnergyDrain = %Data.drainEnergy;

      %Target.setRechargeRate(%TargetRate + %EnergyDrain);
      %Player.setRechargeRate(%PlayerRate - (%EnergyDrain * 0.4));
   }
}

function ELFProjectileData::targetDestroyedCancel(%Data, %Projectile, %Target, %Player)
{
   %Projectile.unzapTarget(%Data, %Projectile, %Target, %Player);

   %Projectile.schedule(500, delete);
}

function ELFProjectile::checkELFStatus(%Projectile, %Data, %Target, %Player)
{
   if (isObject(%Target) == false)
      return;

   if (%Target.getDamageState() $= "Destroyed" || isObject(%Target) == false)
   {
      %Data.targetDestroyedCancel(%Projectile, %Target, %Player);
      return;
   }

   %Energy = %Target.getEnergyLevel();
   if (%Energy < 5.0)
   {
      %Position = posFromTransform(%Target.getTransform());

      %DataBlock = %Target.getDataBlock();
      %DataBlock.damageObject(%Target, %Projectile.sourceObject, %Position,
%Data.drainHealth, %Data.directDamageType);

   } else {
      %target.playShieldEffect("0.0 0.0 1.0");
   }

   %Projectile.ELFrecur = %Projectile.schedule(32, checkELFStatus, %Data,
%Target, %Player);
      
   %Player.playAudio($ELFFireSound, ELFGunFireSound);

   if (%Target.zapSound != true)
   {
      %Target.playAudio($ELFZapSound, ELFHitTargetSound);
      %Target.zapSound = true;
      %Player.zappingSound = true;

   } else if(%Player.zappingSound == true)
      {
         %Player.stopAudio($ELFFireSound);
         %Player.zappingSound = false;
   }
}

function RadiusExplosion(%explosionSource, %Position, %Radius, %Damage, %Impulse, %SourceObject, %damageType)
{
   // If underwater, make the explosions that much more powerful
   if (CheckinWater(%explosionSource) == true)
   {
      %Radius *= 1.4;
      %Damage *= 1.2;
   }

   InitContainerRadiusSearch(%position, %radius, $TypeMasks::PlayerObjectType      |
                                                 $TypeMasks::VehicleObjectType     |
                                                 $TypeMasks::StaticShapeObjectType |
                                                 $TypeMasks::TurretObjectType      |
                                                 $TypeMasks::ItemObjectType);

   %numTargets = 0;
   while ((%targetObject = containerSearchNext()) != 0)
   {
      %dist = containerSearchCurrRadDamageDist();

      if (%dist > %radius)
         continue;

      if (%targetObject.isMounted())
      {
         %mount = %targetObject.getObjectMount();
         %found = -1;
         for (%i = 0; %i < %mount.getDataBlock().numMountPoints; %i++)
         {
            if (%mount.getMountNodeObject(%i) == %targetObject)
            {
               %found = %i;
               break;
            }
         }
      
         if (%found != -1)
         {
            if (%mount.getDataBlock().isProtectedMountPoint[%found])
            {
               continue;
            }
         }
      }

      %targets[%numTargets]     = %targetObject;
      %targetDists[%numTargets] = %dist;
      %numTargets++;
   }

   %ExplosionDataBlock = %ExplosionSource.getDataBlock();
   %ApplyConcussion = %ExplosionDataBlock.applyConcussion;

   // If there are targets in the array, now apply the damage, impulses, and effects per thier distance
   for (%i = 0; %i < %numTargets; %i++)
   {
      %targetObject = %targets[%i];
      %dist = %targetDists[%i];

      %coverage = calcExplosionCoverage(%position, %targetObject,
                                        ($TypeMasks::InteriorObjectType |
                                         $TypeMasks::TerrainObjectType |
                                         $TypeMasks::ForceFieldObjectType |
                                         $TypeMasks::VehicleObjectType));
      if (%coverage == 0)
         continue;

      %distancefactor = (1.0 - (%dist / %radius));
      %amount = %distancefactor * %coverage * %damage;
      %data = %targetObject.getDataBlock();
      %className = %data.className;

      if (%data.getClassName() $= "PlayerData")
         %TargetObject.getOwnerClient().headShot = "";

      if (%impulse && %data.shouldApplyImpulse(%targetObject) && %targetObject.invincible != true)
      {
         %p = %targetObject.getWorldBoxCenter();
         %momVec = VectorSub(%p, %position);
         %momVec = VectorNormalize(%momVec);
         %impulseVec = VectorScale(%momVec, %impulse * (1.0 - (%dist / %radius)));
         %doImpulse = true;
      }
      else if( %className $= WheeledVehicleData || %className $= FlyingVehicleData || %className $= HoverVehicleData )
      {
         %p = %targetObject.getWorldBoxCenter();
         %momVec = VectorSub(%p, %position);
         %momVec = VectorNormalize(%momVec);
         
         %impulseVec = VectorScale(%momVec, %impulse * (1.0 - (%dist / %radius)));
         
         if( getWord( %momVec, 2 ) < -0.5 )
            %momVec = "0 0 1";
            
         // Add obj's velocity into the momentum vector
         %velocity = %targetObject.getVelocity();
         //%momVec = VectorNormalize( vectorAdd( %momVec, %velocity) );
         %doImpulse = true;
      }
      else
      {   
         %momVec = "0 0 1";
         %doImpulse = false;
      }

      if(%amount > 0)
         %data.damageObject(%targetObject, %sourceObject, %position, %amount, %damageType, %momVec);

      if (%ApplyConcussion == true && %data.getClassName() $= "PlayerData" && %targetObject.invincible != true)
      {
         %data.applyConcussion(%dist, %radius, %sourceObject, %targetObject);

         %random = getRandom() * 3.6 - 1.8;

         %Trans = %targetObject.getTransform();
         %pos = posFromTransform(%Trans);
         %rot = rotFromTransform(%Trans);

         %rotXYZ = getWords(%rot, 0, 2);
         %rotA = getWord(%rot, 3) + %random;
         %targetObject.setTransform(%pos @ " " @ %rotXYZ @ " " @ %rotA);
 	
         if (!$teamDamage && %sourceObject != %targetObject && %sourceObject.client.team == %targetObject.client.team)
         {
            messageClient(%targetObject.client, 'msgTeamConcussionGrenade', '\c1You were hit by %1\'s concussion grenade.', getTaggedString(%sourceObject.client.name));
         }
      }
      
   if(%doImpulse)
      %targetObject.applyImpulse(%position, %impulseVec);
   }
}

function ProjectileData::onExplode(%Data, %Projectile, %Position, %Modifier)
{
   if (%Data.hasDamageRadius == true)
   {
      %Radius = %Data.damageRadius;
      %Damage = %Data.indirectDamage;
      %KickBack = %Data.kickBackStrength;
      %SourceObject = %Projectile.sourceObject;

      %DamageType = %Data.radiusDamageType;

      RadiusExplosion(%Projectile, %Position, %Radius, %Damage, %KickBack, %SourceObject, %DamageType);
   }

   // If the Missile is currently looking for a hot source
   if (%Projectile.SeekHeat !$= "")
      cancel(%Projectile.SeekHeat);
}

function Flag::shouldApplyImpulse(%data, %Flag)
{
   if(%Flag.isHome == true)
      return(false);

   return(true);
}

function SeekerProjectileData::SeekHeat(%Data, %Projectile, %Team, %HeatNeeded, %CurrentVector, %SearchLeed, %SearchRadius)
{
   // Check to see if it is still active
   if (isObject(%Projectile) == false)
      return;

   %Found = false;

   %Transform = %Projectile.getTransform();
   %CurrentPosition = posFromTransform(%Transform);

   %SearchPosition = VectorAdd(%CurrentPosition, VectorScale(%CurrentVector, %SearchLeed));

   // Look for Flares first since they should be hotter
   %i = 0;
   %Count = FlareSet.getCount();

   while (%i < %Count && %Found == false)
   {
      %Flare = FlareSet.getObject(%i);

      if (%Flare.team == %Team)
      {
         %i++;
         continue;
      }

      %FlarePosition = posFromTransform(%Flare.getTransform());

      %Distance = VectorLen(VectorSub(%FlarePosition, %SearchPosition));

      if (%Distance <= %SearchRadius)
      {
         %Projectile.setObjectTarget(%Flare);
         %Found = true;
      }

      %i++;
   }

   // Look for everything else next
   InitContainerRadiusSearch(%SearchPosition, %SearchRadius, $TypeMasks::PlayerObjectType | 
                                                             $TypeMasks::VehicleObjectType |
                                                             $TypeMasks::SensorObjectType |
                                                             $TypeMasks::TurretObjectType |                                                                                                $TypeMasks::StaticShapeObjectType |
                                                             $TypeMasks::ItemObjectType);

   while ((%TargetObject = containerSearchNext()) != 0 && %Found == false)
   {
      // Only something on the other team and not underwater
      if (%TargetObject.team == %Team || %TargetObject.InWater == true)
         continue;

      // If hot then check further
      if (%TargetObject.getHeat() >= %HeatNeeded || %TargetObject.getDataBlock().heatSignature >= %HeatNeeded)
      {

         // If destroyed, look elsewhere.
         if (%TargetObject.getDamageState() $= "Destroyed")
            continue;

         // Check for Line of Sight
         %TargetPosition = %TargetObject.getWorldBoxCenter();
         %Result = ContainerRayCast(%CurrentPosition, %TargetPosition, $EverythingMask, %TargetObject);

         if (%Result $= "0")
         {
            %Projectile.setObjectTarget(%TargetObject);
            %Found = true;
         }
      }
   }

   // If it has not found a target, keep looking otherwise clear the schedule.
   // The longer the missile remains active the longer/wider the search (since it is accelerating
   // and getVelocity doesn't work with projectiles).
   if (%Found == false)
      %Projectile.SeekHeat = %Data.schedule(600, SeekHeat, %Projectile, %Team, %HeatNeeded * 1.02, %CurrentVector, %SearchLeed * 1.3, %SearchRadius * 1.14);
   else
      %Projectile.SeekHeat = "";
}