---------------------------------------------------------------------------------------------------------------
RepairGun / RepairRifle Tutorial
---------------------------------------------------------------------------------------------------------------
This tutorial will make two different repair guns. The first, is just like the repair pack, except it's a weapon. It refills health twice as fast as the pack, so is recommended for only Engineers/Mediums. The second gun, is a long range repair rifle. It fills energy at half the speed of the repair pack, and requires the energy pack, but it can shoot up to a 1000 meters.
-----------------------------------------------------------------------------
Engineer Repair Gun

Step #1
// ------------------------------------------
// inventoryHud.cs
// ------------------------------------------

$InvWeapon[12] = "Repair Gun";
$NameToInv["Repair Gun"] = "Repairgun2";


Step #2
// ------------------------------------------
// weapons.cs
// ------------------------------------------

$WeaponsHudData[12, bitmapName] = "gui/hud_new_packrepair";
$WeaponsHudData[12, itemDataName] = "Repairgun2";

$WeaponsHudCount = 13;

exec("scripts/weapons/Repairgun.cs");


Step #3
// ------------------------------------------
// inventory.cs
// ------------------------------------------

3a)
function ShapeBase::hasAmmo( %this, %weapon )
{
   switch$ ( %weapon )
   {

      case RepairGun2:
         return( true );

   }
}


3b)
function ShapeBase::clearInventory(%this)
{

   %this.setInventory(Repairgun2,0);

}


3c)
function serverCmdGiveAll(%client)
{
   if($TestCheats)
   {

      %player.setInventory(Repairgun2,1);

   }
}


Step #4
// ------------------------------------------
// player.cs
// ------------------------------------------
4a) Add these to the player datablocks. Recommend only Engineers/Mediums.

max [RepairGun2] = 1;


4b)  Add these to function armor::applyConcussion

      if( %weaps[12] = %player.getInventory("Repairgun2") > 0 ) %numWeapons++;

and

      case 12:
         %player.use("Repairgun2");


Step #5
// ------------------------------------------
// hud.cs
// ------------------------------------------
Skip this if you use any server side reticle fixes.

function clientCmdSetWeaponsHudActive(%slot)
{
   weaponsHud.setActiveWeapon(%slot);
   switch$($WeaponNames[%slot])
   {

      case "RepairGun2":
         reticleHud.setBitmap("gui/ret_chaingun");
         reticleFrameHud.setVisible(true);

   }
}


Step #6
// ------------------------------------------
// OptionsDlg.cs
// ------------------------------------------
$RemapName[$RemapCount] = "Repairgun2";
$RemapCmd[$RemapCount] = "useRepairgun2";
$RemapCount++;


Step #7
// ------------------------------------------
// ControlDefaults.cs
// ------------------------------------------

function useRepairgun2( %val )
{
   if ( %val )
     use( Repairgun2 );
}


Step #8
// ------------------------------------------
// RepairGun.cs
// ------------------------------------------
Make a file called RepairGun.cs in your weapons directory and add all this:


//--------------------------------------------------------------------------
//Repair Gun
//
//
//--------------------------------------------------------------------------

datablock ItemData(RepairGun2)
{
   className    = Weapon;
   catagory     = "Spawn Items";
   shapeFile    = "weapon_repair.dts";
   image        = RepairGunImage2;
   mass         = 1;
   elasticity   = 0.2;
   friction     = 0.6;
   pickupRadius = 2;
	pickUpName = "a repair gun";

   computeCRC = true;
   emap = true;
};

//--------------------------------------------------------------------------
// Repair Gun

datablock ShapeBaseImageData(RepairGunImage2)
{
   className = WeaponImage;
   shapeFile = "weapon_repair.dts";
   offset = "0 0 0";
   item      = Repairgun2;
   showname = "Repair Gun";

   usesEnergy = true;
   minEnergy = 3;
   cutOffEnergy = 3.1;
   emap = true;

   repairFactorPlayer = 0.004; // <--- Double RepairPack
   repairFactorObject = 0.008; // <--- Double RepairPack

   stateName[0] = "Activate";
   stateTransitionOnTimeout[0] = "ActivateReady";
   stateTimeoutValue[0] = 0.25;

   stateName[1] = "ActivateReady";
   stateScript[1] = "onActivateReady";
   stateSpinThread[1] = Stop;
   stateTransitionOnAmmo[1] = "Ready";
   stateTransitionOnNoAmmo[1] = "ActivateReady";

   stateName[2] = "Ready";
   stateSpinThread[2] = Stop;
   stateTransitionOnNoAmmo[2] = "Deactivate";
   stateTransitionOnTriggerDown[2] = "Validate";

   stateName[3] = "Validate";
   stateTransitionOnTimeout[3] = "Validate";
   stateTimeoutValue[3] = 0.2;
   stateEnergyDrain[3] = 3;
   stateSpinThread[3] = SpinUp;
   stateScript[3] = "onValidate";
   stateIgnoreLoadedForReady[3] = true;
   stateTransitionOnLoaded[3] = "Repair";
   stateTransitionOnNoAmmo[3] = "Deactivate";
   stateTransitionOnTriggerUp[3] = "Deactivate";

   stateName[4] = "Repair";
   stateSound[4] = RepairPackFireSound;
   stateScript[4] = "onRepair";
   stateSpinThread[4] = FullSpeed;
   stateAllowImageChange[4] = false;
   stateSequence[4] = "activate";
   stateFire[4] = true;
   stateEnergyDrain[4] = 9;
   stateTimeoutValue[4] = 0.2;
   stateTransitionOnTimeOut[4] = "Repair";
   stateTransitionOnNoAmmo[4] = "Deactivate";
   stateTransitionOnTriggerUp[4] = "Deactivate";
   stateTransitionOnNotLoaded[4] = "Validate";

   stateName[5] = "Deactivate";
   stateScript[5] = "onDeactivate";
   stateSpinThread[5] = SpinDown;
   stateSequence[5] = "activate";
   stateDirection[5] = false;
   stateTimeoutValue[5] = 0.2;
   stateTransitionOnTimeout[5] = "ActivateReady";
};

function RepairGunImage2::onUnmount(%this,%obj,%slot)
{
   // called when player switches to another weapon

   // stop repairing whatever player was repairing
   if(%obj.repairing)
      stopRepairing(%obj);

   %obj.setImageTrigger(%slot, false);
   // "turn off" the repair pack -- player needs to hit the "pack" key to
   // activate the repair gun again
}

function RepairGunImage2::onActivateReady(%this,%obj,%slot)
{
   %obj.errMsgSent = false;
   %obj.selfRepairing = false;
   %obj.repairing = 0;
   %obj.setImageLoaded(%slot, false);
}

function RepairGunImage2::onValidate(%this,%obj,%slot)
{
   // this = repairgunimage datablock
   // obj = player wielding the repair gun
   // slot = weapon slot

   if(%obj.getEnergyLevel() <= %this.cutOffEnergy)
   {
      stopRepairing(%obj);
      return;
   }
   %repGun = %obj.getMountedImage(%slot);
   // muzVec is the vector coming from the repair gun's "muzzle"
   %muzVec = %obj.getMuzzleVector(%slot);
   // muzNVec = normalized muzVec
   %muzNVec = VectorNormalize(%muzVec);
   %repairRange = DefaultRepairBeam.beamRange;
   // scale muzNVec to the range the repair beam can reach
   %muzScaled = VectorScale(%muzNVec, %repairRange);
   // muzPoint = the actual point of the gun's "muzzle"
   %muzPoint = %obj.getMuzzlePoint(%slot);
   // rangeEnd = muzzle point + length of beam
   %rangeEnd = VectorAdd(%muzPoint, %muzScaled);
   // search for just about anything that can be damaged as well as interiors
   %searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::VehicleObjectType |
      $TypeMasks::StaticShapeObjectType | $TypeMasks::TurretObjectType | $TypeMasks::InteriorObjectType |
      $TypeMasks::ItemObjectType | $TypeMasks::ForceFieldObjectType;
   // search for objects within the beam's range that fit the masks above
   %scanTarg = ContainerRayCast(%muzPoint, %rangeEnd, %searchMasks, %obj);
   // screen out interiors

   if (%scanTarg)
   {
      if (%scanTarg.getType() & $TypeMasks::ForceFieldObjectType)
      {
         if (%scanTarg.getDataBlock().getName() $= "DeployedForceField2")
            %scanTarg = %scanTarg.parent;
         else
            %scanTarg = "0";
      }
   }
   if(%scanTarg && !(%scanTarg.getType() & $TypeMasks::InteriorObjectType))
   {
      // a target in range was found
         %repTgt = firstWord(%scanTarg);
      // is the prospective target damaged?
      if(%repTgt.getDamageLevel())
      {
         // yes, it's damaged
         if(%repTgt != %obj.repairing)
         {
            if(isObject(%obj.repairing))
               stopRepairing(%obj);
            %obj.repairing = %repTgt;
         }
         // setting imageLoaded to true sends us to repair state (function onRepair)
         %obj.setImageLoaded(%slot, true);
      }
      else
      {
         // there is a target in range, but it's not damaged
         if(!%obj.errMsgSent)
         {
            // if the target isn't damaged, send a message to that effect only once
            messageClient(%obj.client, 'MsgRepairPackNotDamaged', '\c2Target is not damaged.', %repTgt);
            %obj.errMsgSent = true;
         }
         // if player was repairing something, stop the repairs -- we're done
         if(%obj.repairing)
            stopRepairing(%obj);
      }
   }

   //AI hack - too many things influence the aiming, so I'm going to force the repair object for bots only
   else if (%obj.client.isAIControlled() && isObject(%obj.client.repairObject))
   {
      %repTgt = %obj.client.repairObject;
      %repPoint = %repTgt.getAIRepairPoint();
      if (%repPoint $= "0 0 0")
         %repPoint = %repTgt.getWorldBoxCenter();
      %repTgtVector = VectorNormalize(VectorSub(%muzPoint, %repPoint));
      %aimVector = VectorNormalize(VectorSub(%muzPoint, %rangeEnd));

      //if the dot product is very close (ie. we're aiming in the right direction)
      if (VectorDot(%repTgtVector, %aimVector) > 0.85)
      {
         //do an LOS to make sure nothing is in the way...
         %scanTarg = ContainerRayCast(%muzPoint, %repPoint, %searchMasks, %obj);
         if (firstWord(%scanTarg) == %repTgt)
         {
            // yes, it's damaged

            if(isObject(%obj.repairing))
               stopRepairing(%obj);

            %obj.repairing = %repTgt;
            // setting imageLoaded to true sends us to repair state (function onRepair)
            %obj.setImageLoaded(%slot, true);
         }
      }
   }
   else if(%obj.getDamageLevel())
   {
      // there is no target in range, but the player is damaged
      // check to see if we were repairing something before -- if so, stop repairing old target
      if(%obj.repairing != 0)
         if(%obj.repairing != %obj)
            stopRepairing(%obj);
      if(isObject(%obj.repairing))
         stopRepairing(%obj);

      %obj.repairing = %obj;
      // quick, to onRepair!
      %obj.setImageLoaded(%slot, true);
   }
   else
   {
      // there is no target in range, and the player isn't damaged
      if(!%obj.errMsgSent)
      {
         // send an error message only once
         messageClient(%obj.client, 'MsgRepairPackNoTarget', '\c2No target to repair.');
         %obj.errMsgSent = true;
      }
      stopRepairing(%obj);
   }
}

function RepairGunImage2::onRepair(%this,%obj,%slot)
{
   // this = repairgunimage datablock
   // obj = player wielding the repair gun
   // slot = weapon slot

   if(%obj.getEnergyLevel() <= %this.cutOffEnergy)
   {
      stopRepairing(%obj);
      return;
   }
   // reset the flag that indicates an error message has been sent
   %obj.errMsgSent = false;
   %target = %obj.repairing;
   if(!%target)
   {
      // no target -- whoops! never mind
      stopRepairing(%obj);
   }
   else
   {
      %target.repairedBy = %obj.client;  //keep track of who last repaired this item
      if(%obj.repairing == %obj)
      {
         // player is self-repairing
         if(%obj.getDamageLevel())
         {
            if(!%obj.selfRepairing)
            {
               // no need for a projectile, just send a message and up the repair rate
               messageClient(%obj.client, 'MsgRepairPackPlayerSelfRepair', '\c2Repairing self.');
               %obj.selfRepairing = true;
               startRepairing3(%obj, true);
            }
         }
         else
         {
            messageClient(%obj.client, 'MsgRepairPackSelfDone', '\c2Repairs completed on self.');
            stopRepairing(%obj);
            %obj.errMsgSent = true;
         }
      }
      else
      {
         // make sure we still have a target -- more vector fun!!!
         %muzVec      = %obj.getMuzzleVector(%slot);
         %muzNVec     = VectorNormalize(%muzVec);
         %repairRange = DefaultRepairBeam.beamRange;
         %muzScaled   = VectorScale(%muzNVec, %repairRange);
         %muzPoint    = %obj.getMuzzlePoint(%slot);
         %rangeEnd    = VectorAdd(%muzPoint, %muzScaled);

         %searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::VehicleObjectType |
                        $TypeMasks::StaticShapeObjectType | $TypeMasks::TurretObjectType |
                        $TypeMasks::ItemObjectType | $TypeMasks::ForceFieldObjectType;

         //AI hack to help "fudge" the repairing stuff...
         if (%obj.client.isAIControlled() && isObject(%obj.client.repairObject) && %obj.client.repairObject == %obj.repairing)
         {
            %repTgt = %obj.client.repairObject;
            %repPoint = %repTgt.getAIRepairPoint();
            if (%repPoint $= "0 0 0")
               %repPoint = %repTgt.getWorldBoxCenter();
            %repTgtVector = VectorNormalize(VectorSub(%muzPoint, %repPoint));
            %aimVector = VectorNormalize(VectorSub(%muzPoint, %rangeEnd));

            //if the dot product is very close (ie. we're aiming in the right direction)
            if (VectorDot(%repTgtVector, %aimVector) > 0.85)
               %scanTarg = ContainerRayCast(%muzPoint, %repPoint, %searchMasks, %obj);
         }
         else
            %scanTarg = ContainerRayCast(%muzPoint, %rangeEnd, %searchMasks, %obj);
         if (%scanTarg)
         {
            if (%scanTarg.getType() & $TypeMasks::ForceFieldObjectType)
            {
               if (%scanTarg.getDataBlock().getName() $= "DeployedForceField2")
               {
                  %pos = getWords(%scanTarg, 1, 3);
                  %scanTarg = %scanTarg.parent SPC %pos;
               }
               else
                  %scanTarg = "0";
            }
         }
         if (%scanTarg)
         {
            %pos = getWords(%scanTarg, 1, 3);
            %obstructMask = $TypeMasks::InteriorObjectType | $TypeMasks::TerrainObjectType;
            %obstruction = ContainerRayCast(%muzPoint, %pos, %obstructMask, %obj);
            if (%obstruction)
               %scanTarg = "0";
         }
         if (%scanTarg && %scanTarg2)
            %scanTarg = %scanTarg2;

         if(%scanTarg)
         {
            // there's still a target out there
            %repTgt = firstWord(%scanTarg);
            // is the target damaged?
            if(%repTgt.getDamageLevel())
            {
               if(%repTgt != %obj.repairing)
               {
                  // the target is not the same as the one we were just repairing
                  // stop repairing old target, start repairing new target
                  stopRepairing(%obj);
                  if(isObject(%obj.repairing))
                     stopRepairing(%obj);
                  %obj.repairing = %repTgt;
                  // extract the name of what player is repairing based on what it is
                  // if it's a player, it's the player's name (duh)
                  // if it's an object, look for a nametag
                  // if object has no nametag, just say what it is (e.g. generatorLarge)
                  if(%repTgt.getClassName() $= Player)
                     %tgtName = getTaggedString(%repTgt.client.name);
                  else if(%repTgt.getGameName() !$= "")
                     %tgtName = %repTgt.getGameName();
                  else
                     %tgtName = %repTgt.getDatablock().getName();
                  messageClient(%obj.client, 'MsgRepairPackRepairingObj', '\c2Repairing %1.', %tgtName, %repTgt);
                  startRepairing3(%obj, false);
               }
               else
               {
                  // it's the same target as last time
                  // changed to fix "2 players can't repair same object" bug
                  if(%obj.repairProjectile == 0)
                  {
                     if(%repTgt.getClassName() $= Player)
                        %tgtName = getTaggedString(%repTgt.client.name);
                     else if(%repTgt.getGameName() !$= "")
                        %tgtName = %repTgt.getGameName();
                     else
                        %tgtName = %repTgt.getDatablock().getName();
                     messageClient(%obj.client, 'MsgRepairPackRepairingObj', '\c2Repairing %1.', %tgtName, %repTgt);
                     startRepairing3(%obj, false);
                  }
               }
            }
            else
            {
               %rateOfRepair = %this.repairFactorObject;
               if(%repTgt.getClassName() $= Player)
               {
                  %tgtName = getTaggedString(%repTgt.client.name);
                  %rateOfRepair = %this.repairFactorPlayer;
               }
               else if(%repTgt.getGameName() !$= "")
                  %tgtName = %repTgt.getGameName();
               else
                  %tgtName = %repTgt.getDatablock().getName();
               if(%repTgt != %obj.repairing)
               {
                  // it isn't the same object we were repairing previously
                  messageClient(%obj.client, 'MsgRepairPackNotDamaged', '\c2%1 is not damaged.', %tgtName);
               }
               else
               {
                  // same target, but not damaged -- we must be done
                  messageClient(%obj.client, 'MsgRepairPackDone', '\c2Repairs completed.');
                  Game.objectRepaired(%repTgt, %tgtName);
               }
               %obj.errMsgSent = true;
               stopRepairing(%obj);
            }
         }
         else
         {
            // whoops, we lost our target
            messageClient(%obj.client, 'MsgRepairPackLostTarget', '\c2Repair target no longer in range.');
            stopRepairing(%obj);
         }
      }
   }
}

function startRepairing3(%player, %self)
{
   // %player = the player who was using the repair pack
   // %self = boolean -- is player repairing him/herself?

   if(%self)
   {
      // one repair, hold the projectile
      %player.setRepairRate(%player.getRepairRate() + RepairGunImage2.repairFactorPlayer);
      %player.selfRepairing = true;
      %player.repairingRate = RepairGunImage2.repairFactorPlayer;
   }
   else
   {
      //if(%player.repairing.beingRepaired $= "")
      //   %player.repairing.beingRepaired = 1;
      //else
      //   %player.repairing.beingRepaired++;

      //AI hack...
      if (%player.client.isAIControlled() && %player.client.repairObject == %player.repairing)
      {
         %initialPosition  = %player.getMuzzlePoint($WeaponSlot);
         %initialDirection = VectorSub(%initialPosition, %player.repairing.getWorldBoxCenter());
      }
      else
      {
         %initialDirection = %player.getMuzzleVector($WeaponSlot);
         %initialPosition  = %player.getMuzzlePoint($WeaponSlot);
      }
      if(%player.repairing.getClassName() $= Player)
         %repRate = RepairGunImage2.repairFactorPlayer;
      else
         %repRate = RepairGunImage2.repairFactorObject;
      %player.repairing.setRepairRate(%player.repairing.getRepairRate() + %repRate);

      %player.repairingRate = %repRate;

      if (%player.repairing.getDataBlock().getName() $= "DeployedForceField")
         %targetObject = %player.repairing.field;
      else
         %targetObject = %player.repairing;
       %player.repairProjectile = new RepairProjectile() {
         dataBlock = DefaultRepairBeam;
         initialDirection = %initialDirection;
         initialPosition  = %initialPosition;
         sourceObject     = %player;
         sourceSlot       = $WeaponSlot;
         targetObject     = %targetObject;
      };
      MissionCleanup.add(%player.repairProjectile);
   }
}

function RepairGunImage2::onDeactivate(%this,%obj,%slot)
{
   stopRepairing(%obj);
}

function RepairGunImage2::onPickup(%this, %obj, %shape, %amount)
{
   // created to prevent console errors
}


-----------------------------------------------------------------------------
Sniper Repair Rifle
Step #1
// ------------------------------------------
// inventoryHud.cs
// ------------------------------------------
1a)

$InvWeapon[13] = "Repair Rifle";
$NameToInv["Repair Rifle"] = "RepairRifle";


1b) In function InventoryScreen::updateHud, These lines:

         else if ( "SniperRifle" $= $NameToInv[%client.favorites[getField( %client.weaponIndex,%i )]] )
         {
            %noSniperRifle = false;
            %packList = "noSelect\tEnergy Pack\tEnergy Pack must be used when \tLaser Rifle is selected!";
            %client.favorites[getField(%client.packIndex,0)] = "Energy Pack";
         }

should become:

         else if ( "SniperRifle" $= $NameToInv[%client.favorites[getField( %client.weaponIndex,%i )]] )
         {
            %noSniperRifle = false;
            %packList = "noSelect\tEnergy Pack\tEnergy Pack must be used when \tLaser Rifle is selected!";
            %client.favorites[getField(%client.packIndex,0)] = "Energy Pack";
         }
         else if ( "RepairRifle" $= $NameToInv[%client.favorites[getField( %client.weaponIndex,%i )]] )
         {
            %noSniperRifle = false;
            %packList = "noSelect\tEnergy Pack\tEnergy Pack must be used when \tRepair Rifle is selected!";
            %client.favorites[getField(%client.packIndex,0)] = "Energy Pack";
         }


1c)
function checkPackValidity(%pack, %equipment, %item)
{

	%require["Repair Rifle"] = "Pack" TAB "Energy Pack";

}


Step #2
// ------------------------------------------
// weapons.cs
// ------------------------------------------

$WeaponsHudData[13, bitmapName] = "gui/hud_new_packrepair";
$WeaponsHudData[13, itemDataName] = "RepairRifle";

$WeaponsHudCount = 14;

exec("scripts/weapons/RepairRifle.cs");


Step #3
// ------------------------------------------
// inventory.cs
// ------------------------------------------

3a)
function ShapeBase::hasAmmo( %this, %weapon )
{
   switch$ ( %weapon )
   {

      case RepairRifle:
         return( %this.getInventory( EnergyPack ) );

   }
}


3b)
function ShapeBase::clearInventory(%this)
{

   %this.setInventory(RepairRifle,0);

}


3c)
function serverCmdGiveAll(%client)
{
   if($TestCheats)
   {

      %player.setInventory(RepairRifle,1);

   }
}


Step #4
// ------------------------------------------
// player.cs
// ------------------------------------------
4a) Add this to player datablocks. Recommend only Snipers/Lights.

max [RepairRifle] = 1;


4b)  Add these to function armor::applyConcussion

      if( %weaps[13] = %player.getInventory("RepairRifle") > 0 ) %numWeapons++;

and

      case 12:
         %player.use("RepairRifle");


Step #5
// ------------------------------------------
// hud.cs
// ------------------------------------------
Skip this if you use any server side reticle fixes.

function clientCmdSetWeaponsHudActive(%slot)
{
   weaponsHud.setActiveWeapon(%slot);
   switch$($WeaponNames[%slot])
   {

      case "RepairRifle":
         reticleHud.setBitmap("gui/hud_ret_targlaser");
         reticleFrameHud.setVisible(false);

   }
}


Step #6
// ------------------------------------------
// OptionsDlg.cs
// ------------------------------------------
$RemapName[$RemapCount] = "RepairRifle";
$RemapCmd[$RemapCount] = "useRepairRifle";
$RemapCount++;


Step #7
// ------------------------------------------
// ControlDefaults.cs
// ------------------------------------------

function useRepairRifle( %val )
{
   if ( %val )
     use( RepairRifle );
}


Step #8
// ------------------------------------------
// RepairRifle.cs
// ------------------------------------------
Create a file called RepairRifle.cs in your weapons directory. Add all this into it:


//--------------------------------------------------------------------------
// Repair Rifle
//
//--------------------------------------------------------------------------
datablock EffectProfile(RepairRifleSwitchEffect)
{
   effectname = "weapons/generic_switch";
   minDistance = 2.5;
   maxDistance = 2.5;
};

datablock EffectProfile(RepairRiflePaintEffect)
{
   effectname = "weapons/targetinglaser_paint";
   minDistance = 2.5;
   maxDistance = 2.5;
};

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

datablock AudioProfile(RepairRiflePaintSound)
{
   filename    = "fx/weapons/targetinglaser_paint.wav";
   description = CloseLooping3d;
   preload = true;
   effect = RepairRiflePaintEffect;
};


//--------------------------------------
// Projectile
//--------------------------------------
datablock TargetProjectileData(RepairLaser)
{
   directDamage        	= 0.0;
   hasDamageRadius     	= false;
   indirectDamage      	= 0.0;
   damageRadius        	= 0.0;
   velInheritFactor    	= 1.0;

   maxRifleRange       	= 1000;
   beamColor           	= "1.0 0.1 0.1";

   startBeamWidth			= 0.20;
   pulseBeamWidth 	   = 0.15;
   beamFlareAngle 	   = 3.0;
   minFlareSize        	= 0.0;
   maxFlareSize        	= 400.0;
   pulseSpeed          	= 6.0;
   pulseLength         	= 0.150;

   textureName[0]      	= "special/nonlingradient";
   textureName[1]   = "special/redflare";
   textureName[2]      	= "special/pulse";
   textureName[3]      	= "special/expFlare";
   textureName[4]   = "special/redbump2";
};


//--------------------------------------
// Rifle and item...
//--------------------------------------
datablock ItemData(RepairRifle)
{
   className    = Weapon;
   catagory     = "Spawn Items";
   shapeFile    = "weapon_targeting.dts";
   image        = RepairRifleImage;
   mass         = 1;
   elasticity   = 0.2;
   friction     = 0.6;
   pickupRadius = 2;
	pickUpName = "a repair rifle";

   computeCRC = true;

};

datablock ShapeBaseImageData(RepairRifleImage)
{
   className = WeaponImage;
   shapeFile = "weapon_repair.dts";
   offset = "0 0 0";
   item      = RepairRifle;
   showname = "Repair Rifle";

   usesEnergy = true;
   minEnergy = 3;
   cutOffEnergy = 3.1;
   emap = true;

   repairFactorPlayer = 0.001; // <--- attention DaveG!
   repairFactorObject = 0.002; // <--- attention DaveG!

   stateName[0] = "Activate";
   stateTransitionOnTimeout[0] = "ActivateReady";
   stateTimeoutValue[0] = 0.25;

   stateName[1] = "ActivateReady";
   stateScript[1] = "onActivateReady";
   stateSpinThread[1] = Stop;
   stateTransitionOnAmmo[1] = "Ready";
   stateTransitionOnNoAmmo[1] = "ActivateReady";

   stateName[2] = "Ready";
   stateSpinThread[2] = Stop;
   stateTransitionOnNoAmmo[2] = "Deactivate";
   stateTransitionOnTriggerDown[2] = "Validate";

   stateName[3] = "Validate";
   stateTransitionOnTimeout[3] = "Validate";
   stateTimeoutValue[3] = 0.2;
   stateEnergyDrain[3] = 3;
   stateSpinThread[3] = SpinUp;
   stateScript[3] = "onValidate";
   stateIgnoreLoadedForReady[3] = true;
   stateTransitionOnLoaded[3] = "Repair";
   stateTransitionOnNoAmmo[3] = "Deactivate";
   stateTransitionOnTriggerUp[3] = "Deactivate";

   stateName[4] = "Repair";
   stateSound[4] = RepairPackFireSound;
   stateScript[4] = "onRepair";
   stateSpinThread[4] = FullSpeed;
   stateAllowImageChange[4] = false;
   stateSequence[4] = "activate";
   stateFire[4] = true;
   stateEnergyDrain[4] = 9;
   stateTimeoutValue[4] = 0.2;
   stateTransitionOnTimeOut[4] = "Repair";
   stateTransitionOnNoAmmo[4] = "Deactivate";
   stateTransitionOnTriggerUp[4] = "Deactivate";
   stateTransitionOnNotLoaded[4] = "Validate";

   stateName[5] = "Deactivate";
   stateScript[5] = "onDeactivate";
   stateSpinThread[5] = SpinDown;
   stateSequence[5] = "activate";
   stateDirection[5] = false;
   stateTimeoutValue[5] = 0.2;
   stateTransitionOnTimeout[5] = "ActivateReady";
};

function RepairRifleImage::onUnmount(%this,%obj,%slot)
{
   // called when player switches to another weapon
   // stop repairing whatever player was repairing
   if(%obj.repairing)
      stopRepairing(%obj);

   %obj.setImageTrigger(%slot, false);
   // "turn off" the repair pack -- player needs to hit the "pack" key to
   // activate the repair gun again
}

function RepairRifleImage::onActivateReady(%this,%obj,%slot)
{
   %obj.errMsgSent = false;
   %obj.selfRepairing = false;
   %obj.repairing = 0;
   %obj.setImageLoaded(%slot, false);
}

function RepairRifleImage::onValidate(%this,%obj,%slot)
{
   // this = repairgunimage datablock
   // obj = player wielding the repair gun
   // slot = weapon slot

   if(!%obj.hasEnergyPack)
   {
      // siddown Junior, you can't use it
      stopRepairing(%obj);
      return;
   }

   if(%obj.getEnergyLevel() <= %this.cutOffEnergy)
   {
      stopRepairing(%obj);
      return;
   }
   %repGun = %obj.getMountedImage(%slot);
   // muzVec is the vector coming from the repair gun's "muzzle"
   %muzVec = %obj.getMuzzleVector(%slot);
   // muzNVec = normalized muzVec
   %muzNVec = VectorNormalize(%muzVec);
   %repairRange = RepairLaser.maxRifleRange;
   // scale muzNVec to the range the repair beam can reach
   %muzScaled = VectorScale(%muzNVec, %repairRange);
   // muzPoint = the actual point of the gun's "muzzle"
   %muzPoint = %obj.getMuzzlePoint(%slot);
   // rangeEnd = muzzle point + length of beam
   %rangeEnd = VectorAdd(%muzPoint, %muzScaled);
   // search for just about anything that can be damaged as well as interiors
   %searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::VehicleObjectType |
      $TypeMasks::StaticShapeObjectType | $TypeMasks::TurretObjectType | $TypeMasks::InteriorObjectType |
      $TypeMasks::ItemObjectType | $TypeMasks::ForceFieldObjectType;
   // search for objects within the beam's range that fit the masks above
   %scanTarg = ContainerRayCast(%muzPoint, %rangeEnd, %searchMasks, %obj);
   // screen out interiors

   if (%scanTarg)
   {
      if (%scanTarg.getType() & $TypeMasks::ForceFieldObjectType)
      {
         if (%scanTarg.getDataBlock().getName() $= "DeployedForceField2")
            %scanTarg = %scanTarg.parent;
         else
            %scanTarg = "0";
      }
   }
   if(%scanTarg && !(%scanTarg.getType() & $TypeMasks::InteriorObjectType))
   {
      // a target in range was found
         %repTgt = firstWord(%scanTarg);
      // is the prospective target damaged?
      if(%repTgt.getDamageLevel())
      {
         // yes, it's damaged
         if(%repTgt != %obj.repairing)
         {
            if(isObject(%obj.repairing))
               stopRepairing(%obj);
            %obj.repairing = %repTgt;
         }
         // setting imageLoaded to true sends us to repair state (function onRepair)
         %obj.setImageLoaded(%slot, true);
      }
      else
      {
         // there is a target in range, but it's not damaged
         if(!%obj.errMsgSent)
         {
            // if the target isn't damaged, send a message to that effect only once
            messageClient(%obj.client, 'MsgRepairPackNotDamaged', '\c2Target is not damaged.', %repTgt);
            %obj.errMsgSent = true;
         }
         // if player was repairing something, stop the repairs -- we're done
         if(%obj.repairing)
            stopRepairing(%obj);
      }
   }

   //AI hack - too many things influence the aiming, so I'm going to force the repair object for bots only
   else if (%obj.client.isAIControlled() && isObject(%obj.client.repairObject))
   {
      %repTgt = %obj.client.repairObject;
      %repPoint = %repTgt.getAIRepairPoint();
      if (%repPoint $= "0 0 0")
         %repPoint = %repTgt.getWorldBoxCenter();
      %repTgtVector = VectorNormalize(VectorSub(%muzPoint, %repPoint));
      %aimVector = VectorNormalize(VectorSub(%muzPoint, %rangeEnd));

      //if the dot product is very close (ie. we're aiming in the right direction)
      if (VectorDot(%repTgtVector, %aimVector) > 0.85)
      {
         //do an LOS to make sure nothing is in the way...
         %scanTarg = ContainerRayCast(%muzPoint, %repPoint, %searchMasks, %obj);
         if (firstWord(%scanTarg) == %repTgt)
         {
            // yes, it's damaged

            if(isObject(%obj.repairing))
               stopRepairing(%obj);

            %obj.repairing = %repTgt;
            // setting imageLoaded to true sends us to repair state (function onRepair)
            %obj.setImageLoaded(%slot, true);
         }
      }
   }
   else if(%obj.getDamageLevel())
   {
      // there is no target in range, but the player is damaged
      // check to see if we were repairing something before -- if so, stop repairing old target
      if(%obj.repairing != 0)
         if(%obj.repairing != %obj)
            stopRepairing(%obj);
      if(isObject(%obj.repairing))
         stopRepairing(%obj);

      %obj.repairing = %obj;
      // quick, to onRepair!
      %obj.setImageLoaded(%slot, true);
   }
   else
   {
      // there is no target in range, and the player isn't damaged
      if(!%obj.errMsgSent)
      {
         // send an error message only once
         messageClient(%obj.client, 'MsgRepairPackNoTarget', '\c2No target to repair.');
         %obj.errMsgSent = true;
      }
      stopRepairing(%obj);
   }
}

function RepairRifleImage::onRepair(%this,%obj,%slot)
{
   // this = repairgunimage datablock
   // obj = player wielding the repair gun
   // slot = weapon slot

   if(!%obj.hasEnergyPack)
   {
      // siddown Junior, you can't use it
      stopRepairing(%obj);
      return;
   }

   if(%obj.getEnergyLevel() <= %this.cutOffEnergy)
   {
      stopRepairing(%obj);
      return;
   }
   // reset the flag that indicates an error message has been sent
   %obj.errMsgSent = false;
   %target = %obj.repairing;
   if(!%target)
   {
      // no target -- whoops! never mind
      stopRepairing(%obj);
   }
   else
   {
      %target.repairedBy = %obj.client;  //keep track of who last repaired this item
      if(%obj.repairing == %obj)
      {
         // player is self-repairing
         if(%obj.getDamageLevel())
         {
            if(!%obj.selfRepairing)
            {
               // no need for a projectile, just send a message and up the repair rate
               messageClient(%obj.client, 'MsgRepairPackPlayerSelfRepair', '\c2Repairing self.');
               %obj.selfRepairing = true;
               startRepairing2(%obj, true);
            }
         }
         else
         {
            messageClient(%obj.client, 'MsgRepairPackSelfDone', '\c2Repairs completed on self.');
            stopRepairing(%obj);
            %obj.errMsgSent = true;
         }
      }
      else
      {
         // make sure we still have a target -- more vector fun!!!
         %muzVec      = %obj.getMuzzleVector(%slot);
         %muzNVec     = VectorNormalize(%muzVec);
         %repairRange = RepairLaser.maxRifleRange;
         %muzScaled   = VectorScale(%muzNVec, %repairRange);
         %muzPoint    = %obj.getMuzzlePoint(%slot);
         %rangeEnd    = VectorAdd(%muzPoint, %muzScaled);

         %searchMasks = $TypeMasks::PlayerObjectType | $TypeMasks::VehicleObjectType |
                        $TypeMasks::StaticShapeObjectType | $TypeMasks::TurretObjectType |
                        $TypeMasks::ItemObjectType | $TypeMasks::ForceFieldObjectType;

         //AI hack to help "fudge" the repairing stuff...
         if (%obj.client.isAIControlled() && isObject(%obj.client.repairObject) && %obj.client.repairObject == %obj.repairing)
         {
            %repTgt = %obj.client.repairObject;
            %repPoint = %repTgt.getAIRepairPoint();
            if (%repPoint $= "0 0 0")
               %repPoint = %repTgt.getWorldBoxCenter();
            %repTgtVector = VectorNormalize(VectorSub(%muzPoint, %repPoint));
            %aimVector = VectorNormalize(VectorSub(%muzPoint, %rangeEnd));

            //if the dot product is very close (ie. we're aiming in the right direction)
            if (VectorDot(%repTgtVector, %aimVector) > 0.85)
               %scanTarg = ContainerRayCast(%muzPoint, %repPoint, %searchMasks, %obj);
         }
         else
            %scanTarg = ContainerRayCast(%muzPoint, %rangeEnd, %searchMasks, %obj);
         if (%scanTarg)
         {
            if (%scanTarg.getType() & $TypeMasks::ForceFieldObjectType)
            {
               if (%scanTarg.getDataBlock().getName() $= "DeployedForceField2")
               {
                  %pos = getWords(%scanTarg, 1, 3);
                  %scanTarg = %scanTarg.parent SPC %pos;
               }
               else
                  %scanTarg = "0";
            }
         }
         if (%scanTarg)
         {
            %pos = getWords(%scanTarg, 1, 3);
            %obstructMask = $TypeMasks::InteriorObjectType | $TypeMasks::TerrainObjectType;
            %obstruction = ContainerRayCast(%muzPoint, %pos, %obstructMask, %obj);
            if (%obstruction)
               %scanTarg = "0";
         }
         if (%scanTarg && %scanTarg2)
            %scanTarg = %scanTarg2;

         if(%scanTarg)
         {
            // there's still a target out there
            %repTgt = firstWord(%scanTarg);
            // is the target damaged?
            if(%repTgt.getDamageLevel())
            {
               if(%repTgt != %obj.repairing)
               {
                  // the target is not the same as the one we were just repairing
                  // stop repairing old target, start repairing new target
                  stopRepairing(%obj);
                  if(isObject(%obj.repairing))
                     stopRepairing(%obj);
                  %obj.repairing = %repTgt;
                  // extract the name of what player is repairing based on what it is
                  // if it's a player, it's the player's name (duh)
                  // if it's an object, look for a nametag
                  // if object has no nametag, just say what it is (e.g. generatorLarge)
                  if(%repTgt.getClassName() $= Player)
                     %tgtName = getTaggedString(%repTgt.client.name);
                  else if(%repTgt.getGameName() !$= "")
                     %tgtName = %repTgt.getGameName();
                  else
                     %tgtName = %repTgt.getDatablock().getName();
                  messageClient(%obj.client, 'MsgRepairPackRepairingObj', '\c2Repairing %1.', %tgtName, %repTgt);
                  startRepairing2(%obj, false);
               }
               else
               {
                  // it's the same target as last time
                  // changed to fix "2 players can't repair same object" bug
                  if(%obj.repairProjectile == 0)
                  {
                     if(%repTgt.getClassName() $= Player)
                        %tgtName = getTaggedString(%repTgt.client.name);
                     else if(%repTgt.getGameName() !$= "")
                        %tgtName = %repTgt.getGameName();
                     else
                        %tgtName = %repTgt.getDatablock().getName();
                     messageClient(%obj.client, 'MsgRepairPackRepairingObj', '\c2Repairing %1.', %tgtName, %repTgt);
                     startRepairing2(%obj, false);
                  }
               }
            }
            else
            {
               %rateOfRepair = %this.repairFactorObject;
               if(%repTgt.getClassName() $= Player)
               {
                  %tgtName = getTaggedString(%repTgt.client.name);
                  %rateOfRepair = %this.repairFactorPlayer;
               }
               else if(%repTgt.getGameName() !$= "")
                  %tgtName = %repTgt.getGameName();
               else
                  %tgtName = %repTgt.getDatablock().getName();
               if(%repTgt != %obj.repairing)
               {
                  // it isn't the same object we were repairing previously
                  messageClient(%obj.client, 'MsgRepairPackNotDamaged', '\c2%1 is not damaged.', %tgtName);
               }
               else
               {
                  // same target, but not damaged -- we must be done
                  messageClient(%obj.client, 'MsgRepairPackDone', '\c2Repairs completed.');
                  Game.objectRepaired(%repTgt, %tgtName);
               }
               %obj.errMsgSent = true;
               stopRepairing(%obj);
            }
         }
         else
         {
            // whoops, we lost our target
            messageClient(%obj.client, 'MsgRepairPackLostTarget', '\c2Repair target no longer in range.');
            stopRepairing(%obj);
         }
      }
   }
}

function startRepairing2(%player, %self)
{
   // %player = the player who was using the repair pack
   // %self = boolean -- is player repairing him/herself?

   if(%self)
   {
      // one repair, hold the projectile
      %player.setRepairRate(%player.getRepairRate() + RepairRifleImage.repairFactorPlayer);
      %player.selfRepairing = true;
      %player.repairingRate = RepairRifleImage.repairFactorPlayer;
   }
   else
   {
      //if(%player.repairing.beingRepaired $= "")
      //   %player.repairing.beingRepaired = 1;
      //else
      //   %player.repairing.beingRepaired++;

      //AI hack...
      if (%player.client.isAIControlled() && %player.client.repairObject == %player.repairing)
      {
         %initialPosition  = %player.getMuzzlePoint($WeaponSlot);
         %initialDirection = VectorSub(%initialPosition, %player.repairing.getWorldBoxCenter());
      }
      else
      {
         %initialDirection = %player.getMuzzleVector($WeaponSlot);
         %initialPosition  = %player.getMuzzlePoint($WeaponSlot);
      }
      if(%player.repairing.getClassName() $= Player)
         %repRate = RepairRifleImage.repairFactorPlayer;
      else
         %repRate = RepairRifleImage.repairFactorObject;
      %player.repairing.setRepairRate(%player.repairing.getRepairRate() + %repRate);

      %player.repairingRate = %repRate;

      %player.repairProjectile = new TargetProjectile() {
         dataBlock = RepairLaser;
         initialDirection = %initialDirection;
         initialPosition  = %initialPosition;
         sourceObject     = %player;
         sourceSlot       = $WeaponSlot;
      };
      MissionCleanup.add(%player.repairProjectile);
   }
}

function RepairRifleImage::onDeactivate(%this,%obj,%slot)
{
   stopRepairing(%obj);
}

function RepairRifleImage::onPickup(%this, %obj, %shape, %amount)
{
   // created to prevent console errors
}