---------------------------------------------------------------------------------------------------------------
Impulse/BlackHole Tutorial
---------------------------------------------------------------------------------------------------------------
This tutorial will show various weapons that either force the player away from the explosion/shooter, or suck them towards it. The first is a tractor beam weapon that uses the elf gun beam to pull people or vehicles towards them. The second is how to make weapons suck people to the explosion instead of blowing them away from it. Next will be a sniper rifle that does nothing but sends the target flying away from you. The fourth is an impulse mortar that blows people far away from the explosion (force is independent upon distance from explosion and is team friendly).
Ex. #1, Tractor Beam Gun
--------------------------------------------------------------------------
This is an energy based weapon that requires all the normal additions for a
weapon. Only the actual weapon file is listed here, look at the other weapon
tuts for what else needs added to make the weapon complete.

Step #1
// ------------------------------------------
// TractorBeam.cs
// ------------------------------------------
Create a file called TractorBeam.cs and put all this into it.

//--------------------------------------------------------------------------
// Tractor Gun
//--------------------------------------------------------------------------
datablock EffectProfile(TractorGunSwitchEffect)
{
   effectname = "weapons/generic_switch";
   minDistance = 2.5;
   maxDistance = 2.5;
};

datablock EffectProfile(TractorGunFireEffect)
{
   effectname = "weapons/ELF_fire";
   minDistance = 2.5;
   maxDistance = 2.5;
};

datablock EffectProfile(TractorGunFireWetEffect)
{
   effectname = "weapons/ELF_underwater";
   minDistance = 2.5;
   maxDistance = 2.5;
};

datablock AudioProfile(TractorGunSwitchSound)
{
   filename    = "fx/weapons/generic_switch.wav";
   description = AudioClosest3d;
   preload = true;
   effect = TractorGunSwitchEffect;
};

datablock AudioProfile(TractorGunFireSound)
{
   filename    = "fx/weapons/ELF_fire.wav";
   description = CloseLooping3d;
   preload = true;
   effect = TractorGunFireEffect;
};

datablock AudioProfile(TractorFireWetSound)
{
   filename    = "fx/weapons/ELF_underwater.wav";
   description = AudioClose3d;
   preload = true;
};

datablock AudioProfile(TractorHitTargetSound)
{
   filename    = "fx/weapons/ELF_hit.wav";
   description = CloseLooping3d;
   preload = true;
   effect = TractorGunFireEffect;
};

//--------------------------------------
// Sparks
//--------------------------------------
datablock ParticleData(TractorSparks)
{
   dragCoefficient      = 1;
   gravityCoefficient   = 0.0;
   inheritedVelFactor   = 0.2;
   constantAcceleration = 0.0;
   lifetimeMS           = 200;
   lifetimeVarianceMS   = 0;
   textureName          = "special/blueSpark";
   colors[0]     = "0.8 0.8 1.0 1.0";
   colors[1]     = "0.8 0.8 1.0 1.0";
   colors[2]     = "0.8 0.8 1.0 0.0";
   sizes[0]      = 0.35;
   sizes[1]      = 0.15;
   sizes[2]      = 0.0;
   times[0]      = 0.0;
   times[1]      = 0.5;
   times[2]      = 1.0;

};

datablock ParticleEmitterData(TractorSparksEmitter)
{
   ejectionPeriodMS = 5;
   periodVarianceMS = 0;
   ejectionVelocity = 4;
   velocityVariance = 2;
   ejectionOffset   = 0.0;
   thetaMin         = 0;
   thetaMax         = 180;
   phiReferenceVel  = 0;
   phiVariance      = 360;
   overrideAdvances = false;
   orientParticles  = true;
//   lifetimeMS       = 100;
   particles = "TractorSparks";
};


//--------------------------------------
// Projectile
//--------------------------------------
datablock ElfProjectileData(TractorBeam)
{
   beamRange         = 50;
   numControlPoints  = 8;
   restorativeFactor = 3.75;
   dragFactor        = 4.5;
   endFactor         = 2.25;
   randForceFactor   = 2;
   randForceTime     = 0.125;
	drainEnergy			= 0.0;
	drainHealth			= 0.0;
   directDamageType  = $DamageType::Tractor;
	mainBeamWidth     = 0.1;           // width of blue wave beam
	mainBeamSpeed     = 9.0;            // speed that the beam travels forward
	mainBeamRepeat    = 0.25;           // number of times the texture repeats
   lightningWidth    = 0.1;
   lightningDist      = 0.15;           // distance of lightning from main beam

   fireSound    = TractorGunFireSound;
   wetFireSound = TractorFireWetSound;

	textures[0] = "special/ELFBeam";
   textures[1] = "special/ELFLightning";
   textures[2] = "special/BlueImpact";

   emitter = TractorSparksEmitter;
};

//--------------------------------------
// Rifle and item...
//--------------------------------------
datablock ItemData(TractorGun)
{
   className    = Weapon;
   catagory     = "Spawn Items";
   shapeFile    = "weapon_elf.dts";
   image        = TractorGunImage;
   mass         = 1;
   elasticity   = 0.2;
   friction     = 0.6;
   pickupRadius = 2;
	pickUpName = "a tractor beam gun";

   computeCRC = true;
   emap = true;
};

datablock ShapeBaseImageData(TractorGunImage)
{
   className = WeaponImage;

   shapeFile = "weapon_elf.dts";
   item = TractorGun;
   showname = "Tractor Beam Gun";
   offset = "0 0 0";

   projectile = TractorBeam;
   projectileType = ELFProjectile;
   deleteLastProjectile = true;
   emap = true;


	usesEnergy = true;
 	minEnergy = 3;

   stateName[0]                     = "Activate";
   stateSequence[0]                 = "Activate";
	stateSound[0]                    = ELFGunSwitchSound;
   stateTimeoutValue[0]             = 0.5;
   stateTransitionOnTimeout[0]      = "ActivateReady";

   stateName[1]                     = "ActivateReady";
   stateTransitionOnAmmo[1]         = "Ready";
   stateTransitionOnNoAmmo[1]       = "NoAmmo";

   stateName[2]                     = "Ready";
   stateTransitionOnNoAmmo[2]       = "NoAmmo";
   stateTransitionOnTriggerDown[2]  = "CheckWet";

   stateName[3]                     = "Fire";
	stateEnergyDrain[3]              = 5;
   stateFire[3]                     = true;
   stateAllowImageChange[3]         = false;
   stateScript[3]                   = "onFire";
   stateTransitionOnTriggerUp[3]    = "Deconstruction";
   stateTransitionOnNoAmmo[3]       = "Deconstruction";
   //stateSound[3]                    = TractorFireWetSound;

   stateName[4]                     = "NoAmmo";
   stateTransitionOnAmmo[4]         = "Ready";

   stateName[5]                     = "Deconstruction";
   stateScript[5]                   = "deconstruct";
   stateTransitionOnTimeout[5]      = "Ready";
   stateTransitionOnNoAmmo[6]       = "NoAmmo";

   stateName[6]                     = "DryFire";
   stateSound[6]                    = TractorFireWetSound;
   stateTimeoutValue[6]             = 0.5;
   stateTransitionOnTimeout[6]      = "Ready";

   stateName[7]                     = "CheckWet";
   stateTransitionOnWet[7]          = "DryFire";
   stateTransitionOnNotWet[7]       = "Fire";
};

function TractorBeam::zapTarget(%data, %projectile, %target, %targeter)
{
   %projectile.checkTractorStatus(%data, %target, %targeter);
}

function ELFProjectile::checkTractorStatus(%this, %data, %target, %targeter)
{
   %obj = %this.sourceObject;
   if((isObject(%target)) && (isObject(%obj)))
   {
      if(%target.getDamageState() $= "Destroyed")
      {
         cancel(%this.Tractorrecur);
         %this.delete();
         return;
      }
      %pos = posFromTransform(%obj.getTransform());
      %pos2 = posFromTransform(%target.getTransform());
      %vec = VectorAdd(%pos, VectorScale(%pos2, -1));
      %vec = VectorNormalize(%vec);
      %data = %target.getDataBlock();
      %amount = VectorScale(%vec, %data.mass);
      if ((%target.getType() & $TypeMasks::PlayerObjectType) || (%target.getType() & $TypeMasks::VehicleObjectType))
         if ((%target.team != %obj.team) || ($teamDamage))
            if(%obj.getObjectMount() != %target)
               %target.applyImpulse(%pos2, %amount);
      %this.TractorRecur = %this.schedule(70, checkTractorStatus, %data, %target, %targeter);
   }
}


Note: To make the tractorBeam repel targets instead of pulling them, simply change this line above:

      %vec = VectorAdd(%pos, VectorScale(%pos2, -1));

to this

      %vec = VectorAdd(%pos2, VectorScale(%pos, -1));



Ex. #2, Sucking people to explosions
--------------------------------------------------------------------------
This will make weapons pull people to an explosion instead of pushing them from it.

Step #1
// ------------------------------------------
// projectiles.cs
// ------------------------------------------

In function RadiusExplosion, change this line:

      if( %doImpulse )
         %targetObject.applyImpulse(%position, %impulseVec);

to this:

      if( %doImpulse )
      {
         if (%explosionSource.getDataBlock().invertKickBack)
            %impulseVec = VectorScale(%impulseVec, -1);
         %targetObject.applyImpulse(%position, %impulseVec);
      }


Step #2
// ------------------------------------------
// NameOfWeapon.cs
// ------------------------------------------
Now, add this line to any projectile that you want to pull, instead of push.

   invertKickBack = true;


Ex. #3, Pulse Rifle
--------------------------------------------------------------------------
This is the onFire code for an ImpulseRifle that sends a person flying away when shot. To make it pull people, simply invert %force.

function ImpulseRifle::onFire(%data, %obj, %slot)
{
   %range = 1000; //Rifle Range.
   %force = 1000; //Force to push them with
   %muzzlePos = %obj.getMuzzlePoint(%slot);
   %muzzleVec = %obj.getMuzzleVector(%slot);
   %endPos    = VectorAdd(%muzzlePos, VectorScale(%muzzleVec, %range));
   %damageMasks = $TypeMasks::PlayerObjectType | $TypeMasks::VehicleObjectType;
   %hit = ContainerRayCast(%muzzlePos, %endPos, %damageMasks, %obj);
   if (isObject(%hit))
   {
      %hitpos = getWords(%hit, 1, 3);
      if ((%hit.getType() & $TypeMasks::PlayerObjectType) || (%hit.getType() & $TypeMasks::VehicleObjectType))
      {
         %vec = VectorAdd(%hitpos, VectorScale(%muzzlePos, -1));
         %vec = VectorNormalize(%vec);
         %impulseVec = VectorScale(%vec, %force);
         %hit.applyImpulse(%hitpos, %impulseVec);
         %obj.decInventory(%data.ammo,1); //If it's ammo based.
      }
   }
}


Ex. #4, Pulse Explosions
--------------------------------------------------------------------------
This Turns explosion projectiles into team friendly impulse weapons. This
will make the impulse equal regardless of distance from explosion, and make
it team friendly. First, add this line to any projectile that you want to
be a pulse projectile and give it a big kickback strength.

   Impulse = true;


Step #2
// ------------------------------------------
// projectile.cs
// ------------------------------------------

In function RadiusExplosion, find both instances of this:

            %impulseVec = VectorScale(%momVec, %impulse * (1.0 - (%dist / %radius)));

and change them to this:

         if(%explosionSource.getDataBlock().Impulse)
         {
            %client1 = %sourceObject.getOwnerClient();
            %client2 = %targetObject.getOwnerClient();
            %team1 = %sourceObject.team;
            %team2 = %targetObject.team;
            if (($teamDamage) || (%team1 != %team2) || (%client1 == %client2))
               %impulseVec = VectorScale(%momVec, %impulse);
            else
               %impulseVec = VectorScale(%momVec, 1);
         }
         else
            %impulseVec = VectorScale(%momVec, %impulse * (1.0 - (%dist / %radius)));