
// deployable objects script
//
// remote pulse sensor, remote motion sensor, remote turrets (indoor
// and outdoor), remote inventory station, remote ammo station
// Note: cameras are treated as grenades, not "regular" deployables

// --------------------------------------------
// sound datablocks
// --------------------------------------------

datablock AudioProfile(TurretDeploySound)
{
   fileName = "fx/packs/turret_place.wav";
   description = AudioClose3d;
   preload = true;
};

datablock AudioProfile(SensorDeploySound)
{
   fileName = "fx/powered/sensor_activate.wav";
   description = AudioClose3d;
   preload = true;
};

datablock AudioProfile(MotionSensorDeploySound)
{
   fileName = "fx/powered/motion_sensor_activate.wav";
   description = AudioClose3d;
   preload = true;
};

datablock AudioProfile(StationDeploySound)
{
   fileName = "fx/packs/inventory_deploy.wav";
   description = AudioClose3d;
   preload = true;
};

// --------------------------------------------
// deployable debris definition

datablock DebrisData( DeployableDebris )
{
   explodeOnMaxBounce = false;

   elasticity = 0.40;
   friction = 0.5;

   lifetime = 17.0;
   lifetimeVariance = 0.0;

   minSpinSpeed = 60;
   maxSpinSpeed = 600;

   numBounces = 10;
   bounceVariance = 0;

   staticOnMaxBounce = true;

   useRadiusMass = true;
   baseRadius = 0.2;

   velocity = 5.0;
   velocityVariance = 2.5;
   
};             


// --------------------------------------------
// deployable inventory station

datablock StaticShapeData(DeployedStationInventory) : StaticShapeDamageProfile
{
   className = Station;
   shapeFile = "deploy_inventory.dts";
   maxDamage = 0.70;
   destroyedLevel = 0.70;
   disabledLevel = 0.42;
   explosion      = DeployablesExplosion;

   expDmgRadius = 8.0;
      expDamage = 0.35;
      expImpulse = 500.0;

   dynamicType = $TypeMasks::StationObjectType;
   isShielded = true;
   energyPerDamagePoint = 110;
   maxEnergy = 50;
   rechargeRate = 0.20;
   renderWhenDestroyed = false;
   doesRepair = true;

   item = InventoryDeployable;
   name = "Deployed Inventory Station";

   deployedObject = true;

   cmdCategory = "DSupport";
   cmdIcon = CMDStationIcon;
   cmdMiniIconName = "commander/MiniIcons/com_inventory_grey";
   targetNameTag = 'Deployable';
   targetTypeTag = 'Station';

   debrisShapeName = "debris_generic_small.dts";
   debris = DeployableDebris;
   heatSignature = 0;
};

datablock ShapeBaseImageData(InventoryDeployableImage)
{
   mass = 12;
   emap = true;

   shapeFile = "pack_deploy_inventory.dts";
   item = InventoryDeployable;
   mountPoint = 1;
   offset = "0 0 0";
   deployed = DeployedStationInventory;
   heatSignature = 0;

   stateName[0] = "Idle";
   stateTransitionOnTriggerDown[0] = "Activate";

   stateName[1] = "Activate";
   stateScript[1] = "onActivate";
   stateTransitionOnTriggerUp[1] = "Idle";

   isLarge = true;
   maxDepSlope = 30;
   deploySound = StationDeploySound;
};

datablock ItemData(InventoryDeployable)
{
   className = Pack;
   catagory = "Deployables";
   shapeFile = "pack_deploy_inventory.dts";
   mass = 3.0;
   elasticity = 0.2;
   friction = 0.6;
   pickupRadius = 1;
   rotate = false;
   image = "InventoryDeployableImage";
   pickUpName = "an inventory pack";
   heatSignature = 0;

   computeCRC = true;
   emap = true;

};

// --------------------------------------------
// deployable motion sensor

datablock SensorData(DeployMotionSensorObj)
{
   detects = true;
   detectsUsingLOS = true;
   detectsActiveJammed = false;
   detectsPassiveJammed = true;
   detectsCloaked = true;
   detectionPings = false;
   detectMinVelocity = 2;
   detectRadius = 60;
};

datablock StaticShapeData(DeployedMotionSensor) : StaticShapeDamageProfile
{
   className = Sensor;
   shapeFile = "deploy_sensor_motion.dts";
   maxDamage = 0.6;
   destroyedLevel = 0.6;
   disabledLevel = 0.4;
   explosion = DeployablesExplosion;
   dynamicType = $TypeMasks::SensorObjectType;

   deployedObject = true;

   cmdCategory = "DSupport";
   cmdIcon = CMDSensorIcon;
   cmdMiniIconName = "commander/MiniIcons/com_deploymotionsensor";
   targetNameTag = 'Deployable Motion';
   targetTypeTag = 'Sensor';
   sensorData = DeployMotionSensorObj;
   sensorRadius = DeployMotionSensorObj.detectRadius;
   sensorColor = "9 136 255";
   deployAmbientThread = true;

   debrisShapeName = "debris_generic_small.dts";
   debris = DeployableDebris;
   heatSignature = 0;
};

datablock ShapeBaseImageData(MotionSensorDeployableImage)
{
   shapeFile = "pack_deploy_sensor_motion.dts";
   item = MotionSensorDeployable;
   mountPoint = 1;
   offset = "0 0 0";
   deployed = DeployedMotionSensor;

   mass = 3;

   stateName[0] = "Idle";
   stateTransitionOnTriggerDown[0] = "Activate";

   stateName[1] = "Activate";
   stateScript[1] = "onActivate";
   stateTransitionOnTriggerUp[1] = "Idle";

   maxDepSlope = 360;
   deploySound = MotionSensorDeploySound;
   emap = true;
   heatSignature = 1;

   minDeployDis                       =  0.5;
   maxDeployDis                       =  5.0;
};

datablock ItemData(MotionSensorDeployable)
{
   className = Pack;
   catagory = "Deployables";
   shapeFile = "pack_deploy_sensor_motion.dts";
   mass = 2.0;
   elasticity = 0.2;
   friction = 0.6;
   pickupRadius = 1;
   rotate = false;
   image = "MotionSensorDeployableImage";
   pickUpName = "a motion sensor pack";

   computeCRC = true;
   emap = true;
   heatSignature = 0;

   maxSensors = 2;
};

// --------------------------------------------
// deployable pulse sensor

datablock SensorData(DeployPulseSensorObj)
{
   detects = true;
   detectsUsingLOS = true;
   detectsPassiveJammed = false;
   detectsCloaked = false;
   detectionPings = true;
   detectRadius = 150;
};

datablock StaticShapeData(DeployedPulseSensor) : StaticShapeDamageProfile
{
   className = Sensor;
   shapeFile = "deploy_sensor_pulse.dts";
   maxDamage = 0.6;
   destroyedLevel = 0.6;
   disabledLevel = 0.4;
   explosion = DeployablesExplosion;
   dynamicType = $TypeMasks::SensorObjectType;

   deployedObject = true;

   item = PulseSensorDeployable;
   name = "Deployed Pulse Sensor";

   cmdCategory = "DSupport";
   cmdIcon = CMDSensorIcon;
   cmdMiniIconName = "commander/MiniIcons/com_deploypulsesensor";
   targetNameTag = 'Deployable';
   targetTypeTag = 'Pulse Sensor';
   sensorData = DeployPulseSensorObj;
   sensorRadius = DeployPulseSensorObj.detectRadius;
   sensorColor = "255 194 9";
   deployAmbientThread = true;

   debrisShapeName = "debris_generic_small.dts";
   debris = DeployableDebris;
   heatSignature = 0;
};

datablock ShapeBaseImageData(PulseSensorDeployableImage)
{
   shapeFile = "pack_deploy_sensor_pulse.dts";
   item = PulseSensorDeployable;
   mountPoint = 1;

   mass = 4.5;

   offset = "0 0 0";
   deployed = DeployedPulseSensor;

   stateName[0] = "Idle";
   stateTransitionOnTriggerDown[0] = "Activate";

   stateName[1] = "Activate";
   stateScript[1] = "onActivate";
   stateTransitionOnTriggerUp[1] = "Idle";
   deploySound = SensorDeploySound;

   maxDepSlope = 360;
   emap = true;
   heatSignature = 0;
};

datablock ItemData(PulseSensorDeployable)
{
   className = Pack;
   catagory = "Deployables";
   shapeFile = "pack_deploy_sensor_pulse.dts";
   mass = 2.0;
   elasticity = 0.2;
   friction = 0.6;
   pickupRadius = 1;
   rotate = false;
   image = "PulseSensorDeployableImage";
   pickUpName = "a pulse sensor pack";

   computeCRC = true;
   emap = true;

   maxSensors = 2;
};

// --------------------------------------------
// miscellaneous yet handy functions

function posFromTransform(%Transform)
{
   // the first three words of an object's transform are the object's position
   %Position = getWords(%Transform, 0, 2);
   return(%Position);
}

function rotFromTransform(%Transform)
{
   // the last four words of an object's transform are the object's rotation
   %Rotation = getWords(%Transform, 3, 6);
   return(%Rotation);
}

function posFromRaycast(%Transform)
{
   // the 2nd, 3rd, and 4th words returned from a successful raycast call are the position of the point
   %Position = getWords(%Transform, 1, 3);
   return(%Position);
}

function normalFromRaycast(%Transform)
{
   // the 5th, 6th and 7th words returned from a successful raycast call are the normal of the surface
   %Normal = getWords(%Transform, 4, 6);
   return(%Normal);
}

function addToDeployGroup(%object)
{
   // all deployables should go into a special group for AI purposes
   %depGroup = nameToID("MissionCleanup/Deployables");
   if(%depGroup <= 0) {
      %depGroup = new SimGroup("Deployables");
      MissionCleanup.add(%depGroup);
   }
   %depGroup.add(%object);
}

function Deployables::searchView(%obj, %searchRange, %mask)
{
   // get the eye vector and eye transform of the player
   %eyeVec   = %obj.getEyeVector();
   %eyeTrans = %obj.getEyeTransform();

   // extract the position of the player's camera from the eye transform (first 3 words)
   %eyePos = posFromTransform(%eyeTrans);

   // normalize the eye vector
   %nEyeVec = VectorNormalize(%eyeVec);

   // scale (lengthen) the normalized eye vector according to the search range
   %scEyeVec = VectorScale(%nEyeVec, %searchRange);

   // add the scaled & normalized eye vector to the position of the camera
   %eyeEnd = VectorAdd(%eyePos, %scEyeVec);

   // see if anything gets hit
   %searchResult = containerRayCast(%eyePos, %eyeEnd, %mask, 0);

   return %searchResult;
}

// This checks to see if the maximum number of deployables has been reached
function ShapeBaseImageData::testMaxDeployed(%Item, %Player)
{
   // Since turrets fluctuate, we need to test them special
   if(%Item.item $= TurretOutdoorDeployable || %Item.item $= TurretIndoorDeployable)
      %ItemCount = countTurretsAllowed(%Item.item);
   else
   {
      echo("Item: " @ Item.item);
      echo("Small Turret Barrel: " @ %Item.item.SmallTurretBarrel);
      // Barrels also fluctate
      if (%Item.item.SmallTurretBarrel == true)
         %ItemCount = countBarrelsAllowed(%Item.item);
      else 
         %ItemCount = $TeamDeployableMax[%Item.item];

      if (%ItemCount $= "")
         return(false);
   }

   %OverLimit = $TeamDeployedCount[%Player.team, %Item.item] >= %ItemCount;

   return(%OverLimit);
}

//-------------------------------------------------
function ShapeBaseImageData::testSlopeTooGreat(%item)
{
   if (%item.surface)
   {          
      return getTerrainAngle(%item.surfaceNrm) > %item.maxDepSlope;
   }   
}
//-------------------------------------------------
function ShapeBaseImageData::testSelfTooClose(%item, %plyr)
{
   InitContainerRadiusSearch(%item.surfacePt, $MinDeployableDistance, $TypeMasks::PlayerObjectType);

   return containerSearchNext() == %plyr; 
}

//-------------------------------------------------
function ShapeBaseImageData::testObjectTooClose(%Item)
{
   %Result = ContainerBoxEmpty($TypeMasks::VehicleObjectType | $TypeMasks::MoveableObjectType |
                        $TypeMasks::StaticShapeObjectType | $TypeMasks::StaticTSObjectType | 
                        $TypeMasks::ForceFieldObjectType | $TypeMasks::ItemObjectType | 
                        $TypeMasks::PlayerObjectType | $TypeMasks::TurretObjectType, 
                        %Item.surfacePt, $MinDeployableDistance);

   if (%Result == true)
      return(false);
   else
      return(true);
}

//-------------------------------------------------
function TurretOutdoorDeployableImage::testNoTerrainFound(%item)
{
   return %item.surface.getClassName() !$= TerrainBlock;
}

function ShapeBaseImageData::testNoTerrainFound(%item, %surface)
{
   //don't check this for non-Landspike turret deployables
}

//-------------------------------------------------
function TurretIndoorDeployableImage::testNoInteriorFound(%item)
{
   return %item.surface.getClassName() !$= InteriorInstance;
}

function ShapeBaseImageData::testNoInteriorFound(%item, %surface)
{
   //don't check this for non-Clasping turret deployables
}

//-------------------------------------------------
function TurretIndoorDeployableImage::testHavePurchase(%item, %xform)
{
   %footprintRadius = 0.34;
   %collMask = $TypeMasks::InteriorObjectType;
   return %item.deployed.checkDeployPurchase(%xform, %footprintRadius, %collMask);
}

function ShapeBaseImageData::testHavePurchase(%item, %xform)
{
   //don't check this for non-Clasping turret deployables
   return true;
}
                                             
//-------------------------------------------------
function ShapeBaseImageData::testInventoryTooClose(%item, %plyr)
{
   return false;
}

function InventoryDeployableImage::testInventoryTooClose(%item, %plyr)
{
   InitContainerRadiusSearch(%item.surfacePt, $InventorySpaceRadius, $TypeMasks::StaticShapeObjectType);

   // old function was only checking whether the first object found was a turret -- also wasn't checking
   // which team the object was on
   %turretInRange = false;
   while((%found = containerSearchNext()) != 0)
   {
      %foundName = %found.getDataBlock().getName();
      if( (%foundName $= DeployedStationInventory) )
         if (%found.team == %plyr.team)
         {
            %turretInRange = true;
            break;
         }
   }
   return %turretInRange;
}

function TurretIndoorDeployableImage::testTurretTooClose(%item, %plyr)
{
   InitContainerRadiusSearch(%item.surfacePt, $TurretIndoorSpaceRadius, $TypeMasks::StaticShapeObjectType);

   // old function was only checking whether the first object found was a turret -- also wasn't checking
   // which team the object was on
   %turretInRange = false;
   while((%found = containerSearchNext()) != 0)
   {
      %foundName = %found.getDataBlock().getName();
      if((%foundname $= TurretDeployedFloorIndoor) || (%foundName $= TurretDeployedWallIndoor) || (%foundName $= TurretDeployedCeilingIndoor) || (%foundName $= TurretDeployedOutdoor) )
         if (%found.team == %plyr.team)
         {
            %turretInRange = true;
            break;
         }
   }
   return %turretInRange;
}

function TurretOutdoorDeployableImage::testTurretTooClose(%item, %plyr)
{
   InitContainerRadiusSearch(%item.surfacePt, $TurretOutdoorSpaceRadius, $TypeMasks::StaticShapeObjectType);

   // old function was only checking whether the first object found was a turret -- also wasn't checking
   // which team the object was on
   %turretInRange = false;
   while((%found = containerSearchNext()) != 0)
   {
      %foundName = %found.getDataBlock().getName();
      if((%foundname $= TurretDeployedFloorIndoor) || (%foundName $= TurretDeployedWallIndoor) || (%foundName $= TurretDeployedCeilingIndoor) || (%foundName $= TurretDeployedOutdoor) )
         if (%found.team == %plyr.team)
         {
            %turretInRange = true;
            break;
         }
   }
   return %turretInRange;
}

function DeployedTurretBase::testTurretTooClose(%item, %plyr)
{
   InitContainerRadiusSearch(%item.surfacePt, $TurretBaseSpaceRadius, $TypeMasks::StaticShapeObjectType);

   // old function was only checking whether the first object found was a turret -- also wasn't checking
   // which team the object was on
   %turretInRange = false;
   while((%found = containerSearchNext()) != 0)
   {
      %foundName = %found.getDataBlock().getName();
      if ((%foundname $= DeployedTurretBase) || (%foundName $= TurretBaseLarge))
         if (%found.team == %plyr.team)
         {
            %turretInRange = true;
            break;
         }
   }
   return %turretInRange;
}

function ShapeBaseImageData::testTurretTooClose(%item, %plyr)
{
   //don't check this for non-turret deployables
}

//-------------------------------------------------
function TurretIndoorDeployableImage::testTurretSaturation(%item)
{
   %highestDensity = 0;
   InitContainerRadiusSearch(%item.surfacePt, $TurretIndoorSphereRadius, $TypeMasks::StaticShapeObjectType);
   %found = containerSearchNext();
   while(%found)
   {
      %foundName = %found.getDataBlock().getName();
      if((%foundname $= TurretDeployedFloorIndoor) || (%foundName $= TurretDeployedWallIndoor) || (%foundName $= TurretDeployedCeilingIndoor) || (%foundName $= TurretDeployedOutdoor) )
      {
           //found one
           %numTurretsNearby++;

       %nearbyDensity = testNearbyDensity(%found, $TurretIndoorSphereRadius);
       if (%nearbyDensity > %highestDensity)
          %highestDensity = %nearbyDensity;     
      }
     %found = containerSearchNext();
   }

   if (%numTurretsNearby > %highestDensity)
      %highestDensity = %numTurretsNearby;
   return %highestDensity > $TurretIndoorMaxPerSphere;
}

function TurretOutdoorDeployableImage::testTurretSaturation(%item)
{
   %highestDensity = 0;
   InitContainerRadiusSearch(%item.surfacePt, $TurretOutdoorSphereRadius, $TypeMasks::StaticShapeObjectType);
   %found = containerSearchNext();
   while(%found)
   {
      %foundName = %found.getDataBlock().getName();
      if((%foundname $= TurretDeployedFloorIndoor) || (%foundName $= TurretDeployedWallIndoor) || (%foundName $= TurretDeployedCeilingIndoor) || (%foundName $= TurretDeployedOutdoor) )
      {
           //found one
           %numTurretsNearby++;

       %nearbyDensity = testNearbyDensity(%found, $TurretOutdoorSphereRadius);
       if (%nearbyDensity > %highestDensity)
          %highestDensity = %nearbyDensity;     
      }
     %found = containerSearchNext();
   }

   if (%numTurretsNearby > %highestDensity)
      %highestDensity = %numTurretsNearby;
   return %highestDensity > $TurretOutdoorMaxPerSphere;
}

function DeployedTurretBase::testTurretSaturation(%item)
{
   %highestDensity = 0;
   InitContainerRadiusSearch(%item.surfacePt, $TurretOutdoorSphereRadius, $TypeMasks::StaticShapeObjectType);
   %found = containerSearchNext();
   while(%found)
   {
      %foundName = %found.getDataBlock().getName();
      if ((%foundname $= DeployedTurretBase) || (%foundName $= TurretBaseLarge))
      {
           //found one
           %numTurretsNearby++;

       %nearbyDensity = testNearbyDensity(%found, $TurretBaseSphereRadius);
       if (%nearbyDensity > %highestDensity)
          %highestDensity = %nearbyDensity;     
      }
     %found = containerSearchNext();
   }

   if (%numTurretsNearby > %highestDensity)
      %highestDensity = %numTurretsNearby;
   return %highestDensity > $TurretBaseMaxPerSphere;
}

function ShapeBaseImageData::testTurretSaturation(%item, %surfacePt)
{
   //don't check this for non-turret deployables
}

function testNearbyDensity(%item, %radius)
{
   //this checks how many turrets are in adjacent spheres in case placing a new one overloads them.
   %surfacePt = posFromTransform(%item.getTransform());
   %turretCount = 0;

   InitContainerRadiusSearch(%surfacePt, %radius, $TypeMasks::StaticShapeObjectType);
   %found = containerSearchNext();
   while(%found)
   {
      %foundName = %found.getDataBlock().getName();
      if((%foundname $= TurretDeployedFloorIndoor) || (%foundName $= TurretDeployedWallIndoor) || (%foundName $= TurretDeployedCeilingIndoor) || (%foundName $= TurretDeployedOutdoor) )
         %turretCount++;       
     %found = containerSearchNext();      
   }
   return %turretCount;
}

//-------------------------------------------------
function ShapeBaseImageData::testInvalidDeployConditions(%Item, %Player, %Slot)
{
   if (%Player.deployCheckThread !$= "")
      cancel(%Player.deployCheckThread);

   %Disqualified = $NotDeployableReason::None;
   
   // Check the Player's field of View and return what is within range.
   %Surface = %Player.searchView($MaxDeployableDistance, $DeployMask);

   if (%Surface !$= "0")
   {  
      %SurfacePosition = posFromRaycast(%Surface);
      %SurfaceNormal   = normalFromRaycast(%Surface);

      // Check that point to see if anything is obstructing it...
      %EyePosition = posFromTransform(%Player.getEyeTransform());

      // If nothing is in the way, we found what we were originally looking at, or we are in the way ourselves
      %SearchResult = containerRayCast(%EyePosition, %SurfacePosition, $EverythingMask);
      %OriginalSearchPosition = posFromRaycast(%SearchResult);
      %SearchObject = getWord(%SearchResult, 0);
      if (%SearchResult $= "0" || %SurfacePosition $= %OriginalSearchPosition || %SearchObject == %Player)
      {
         %Item.surface = %Surface;
         %Item.surfacePt = %SurfacePosition;
         %Item.surfaceNrm = %SurfaceNormal;
      } else {
         // Don't set the item
         %Disqualified = $NotDeployableReason::ObjectTooClose;
      }
   } else {
      // This sets it up to prevent deploying by making it where the player is
      %Item.surface = %Player.getTransform();
      %Item.surfacePt = %Player.getTransform();
      %Item.surfaceNrm = "0 0 0";

      messageClient(%Player.client, '', "Can not deploy out of reach.");

      activateDeploySensorRed(%Player);

      return;
   }

   if (%Disqualified == $NotDeployableReason::None)
   {
      // Do the other checks now
      if (%Item.testMaxDeployed(%Player))
         %Disqualified = $NotDeployableReason::MaxDeployed;
      else if (%Item.testNoTerrainFound(%Surface))
         %Disqualified = $NotDeployableReason::NoTerrainFound;
      else if (%Item.testNoInteriorFound())
         %Disqualified = $NotDeployableReason::NoInteriorFound;
      else if (%Item.testSlopeTooGreat(%Surface, %SurfaceNormal))
         %Disqualified = $NotDeployableReason::SlopeTooGreat;
      else if (%Item.testSelfTooClose(%Player, %SurfacePosition))
         %Disqualified = $NotDeployableReason::SelfTooClose;
      else if (%Item.testObjectTooClose(%SurfacePosition))
         %Disqualified = $NotDeployableReason::ObjectTooClose;
      else if (%Item.testTurretTooClose(%Player))
         %Disqualified = $NotDeployableReason::TurretTooClose;
      else if (%Item.testTurretSaturation())
         %Disqualified = $NotDeployableReason::TurretSaturation;
      else {

         // Figure out how and where it will rest
         %Rotation = %Item.getInitialRotation(%Player);

         if (%Item.deployed.className $= "DeployedTurret" && %Item.deployed.KeepPlayerRotation != true)
            %xForm = %Item.deployed.getDeployTransform(%Item.surfacePt, %Item.surfaceNrm);
         else
            %xForm = %SurfacePosition @ " " @ %Rotation;

         // Check the area around where it will be placed
         if (%Item.deployed.checkDeployPos(%xForm) == 0)
         {
            %Disqualified = $NotDeployableReason::ObjectTooClose;
         } else {
            if (%Item.testHavePurchase(%xForm) == 0)
               %Disqualified = $NotDeployableReason::SurfaceTooNarrow;
         }
      }
   }
      
   if (%Player.getMountedImage($BackpackSlot) == %Item)
   {
      if (%Disqualified > 0)
         activateDeploySensorRed(%Player);
      else
         activateDeploySensorGrn(%Player);      

      if (%Player.client.deployPack == true)
         %Item.attemptDeploy(%Player, %Slot, %Disqualified);       
      else
         %Player.deployCheckThread = %Item.schedule(50, "testInvalidDeployConditions", %Player, %Slot); //update checks every 50 milliseconds
   }
   else
       deactivateDeploySensor(%Player);
}

function ShapeBaseImageData::attemptDeploy(%Item, %Player, %Slot, %Disqualified)
{
   deactivateDeploySensor(%Player);
   Deployables::displayErrorMsg(%Item, %Player, %Slot, %Disqualified);
}

function activateDeploySensorRed(%pl)
{
   if(%pl.deploySensor !$= "red")
   {
      messageClient(%pl.client, 'msgDeploySensorRed', "");
      %pl.deploySensor = "red";
   }
}

function activateDeploySensorGrn(%pl)
{
   if(%pl.deploySensor !$= "green")
   {
      messageClient(%pl.client, 'msgDeploySensorGrn', "");
      %pl.deploySensor = "green";
   }
}  

function deactivateDeploySensor(%pl)
{
   if (%pl.deploySensor !$= "")
   {
      messageClient(%pl.client, 'msgDeploySensorOff', "");
      %pl.deploySensor = "";
   }
}

function Deployables::displayErrorMsg(%Item, %Player, %Slot, %Error)
{
   deactivateDeploySensor(%Player);
   
   %ErrorSound = '~wfx/misc/misc.error.wav';
   switch (%Error)
   {
      case $NotDeployableReason::None:
         // No error, go ahead and deploy the object
         %Item.onDeploy(%Player, %Slot);
         %Item.NoRepack = true;
         schedule(2000, 0, ClearRepack, %Item);
         messageClient(%Player.client, 'MsgTeamDeploySuccess', "");
         return;

      case $NotDeployableReason::MaxDeployed:
         %Message = '\c2Your team\'s control network has reached its capacity for this item.%1';

      case $NotDeployableReason::SlopeTooGreat:
         %Message = '\c2Surface is too steep to place this item on.%1';

      case $NotDeployableReason::SelfTooClose:
         %Message = '\c2You are too close to the surface you are trying to place the item on.%1';

      case $NotDeployableReason::ObjectTooClose:
         %Message = '\c2You cannot place this item so close to another object.%1';

      case $NotDeployableReason::NoTerrainFound:
         %Message = '\c2You must place this on outdoor terrain.%1';

      case $NotDeployableReason::NoInteriorFound:
         %Message = '\c2You must place this on a solid surface.%1';

      case $NotDeployableReason::TurretTooClose:
         %Message = '\c2Interference from a nearby turret prevents placement here.%1';

      case $NotDeployableReason::TurretSaturation:
         %Message = '\c2There are too many turrets nearby.%1';

      default:
         %Message = '\c2Deploy failed.';
   }
   messageClient(%Player.client, 'MsgDeployFailed', %Message, %ErrorSound);
}  

// This function is used by all packs.  Since the player "Packs" over write this function
// we are free to do whatever we want here for the "Deployable" packs.
function ShapeBaseImageData::onActivate(%data, %Object, %Slot)
{
   %data.testInvalidDeployConditions(%Object, %Slot);

   //whether the test passed or not, reset the image trigger
   %Object.setImageTrigger(%Slot, false);
}

// This function does the actual deploying
function ShapeBaseImageData::onDeploy(%Item, %Player, %Slot)
{
   // The item that is to be deployed is part of a sensor packthen treat that special,
   // otherwise deploy as usual
   if(%Item.item.maxSensors > 0)
   {
      %Player.deploySensors--;
      %Player.client.updateSensorPackText(%Player.deploySensors);
      if(%Player.deploySensors <= 0)
      {
         // take the deployable off the player's back and out of inventory
         %Player.unmountImage(%Slot);
         %Player.decInventory(%Item.item, 1);  
      }
   } else {
      // take the deployable off the player's back and out of inventory
      %Player.unmountImage(%Slot);
      %Player.decInventory(%Item.item, 1);  
   }
   
   // Get the Deploy Rotation based on the type of the item to be deployed
   %Rotation = %Item.getInitialRotation(%Player);

   // Get the class of the item to be deployed
   switch$ (%Item.deployed.className)
   {
      case "DeployedTurret" :
         %className = "Turret";

      case "FlyingVehicle" :
         %className = "FlyingVehicle";

      default :
         %className = "StaticShape";
   }

   %Team = %Player.Team;
   %Scale = %Item.deployed.scale;
   if (%Scale $= "")
      %Scale = "1 1 1";

   %DeployedObject = new (%className)()
   {
      dataBlock = %Item.deployed;
      team = %Team;
      owner = %Player.Client;
      scale = %Scale;
   };
   addToDeployGroup(%DeployedObject);
   
   // set orientation
   if (%className $= "Turret" && %Item.deployed.KeepPlayerRotation != true)
      %DeployedObject.setDeployRotation(%Item.surfacePt, %Item.surfaceNrm);
   else
      %DeployedObject.setTransform(%Item.surfacePt @ " " @ %Rotation);

   // set the sensor group if it needs one
   %Target = %DeployedObject.getTarget();
   if(%Target != -1)
      setTargetSensorGroup(%Target, %Team);

   //let the AI know as well...
   AIDeployObject(%Player.client, %DeployedObject);

   // set the recharge rate
   %RechargeRate = %DeployedObject.getDatablock().rechargeRate;
   if(%RechargeRate > 0)
      %DeployedObject.setRechargeRate(%RechargeRate);

   // play the deploy sound
   serverPlay3D(%Item.deploySound, %DeployedObject.getTransform());

   // increment the team count for this deployed object
   $TeamDeployedCount[%Player.team, %Item.item]++;
   %DeployedObject.deploy();

   %Player.EngObject = %DeployedObject;

   return(%DeployedObject);
}

// This returns a rotation that is the same as the players
function ShapeBaseImageData::getInitialRotation(%Item, %Player)
{
   %Rotation = rotFromTransform(%Player.getTransform());

   return(%Rotation);
}

// This returns a rotation that is perpendicular to the surface
function MotionSensorDeployableImage::getInitialRotation(%Item, %Player)
{
   echo("Surface Normal: " @ %Item.surfaceNrm);

   %RotationalAxis = vectorNormalize(vectorCross(%Item.surfaceNrm, "0 0 1"));

   // If the item is on a wall, then turn it sideways
   if (getWord(%Item.surfaceNrm, 2) == 1 || getWord(%Item.surfaceNrm, 2) == -1)
      %RotationalAxis = vectorNormalize(vectorCross(%Item.surfaceNrm, "0 1 0"));

   %Rotation = %RotationalAxis @ " " @ mACos(vectorDot(%Item.surfaceNrm, "0 0 1"));

   echo("Rotation: " @ %Rotation);

   return(%Rotation);
}

// This returns a rotation that is perpendicular to the surface
function PulseSensorDeployableImage::getInitialRotation(%Item, %Player)
{
   %RotationalAxis = vectorNormalize(vectorCross(%Item.surfaceNrm, "0 0 1"));
   // If the item is on a wall, then turn it sideways
   if (getWord(%Item.surfaceNrm, 2) == 1 || getWord(%Item.surfaceNrm, 2) == -1)
      %RotationalAxis = vectorNormalize(vectorCross(%Item.surfaceNrm, "0 1 0"));

   %Rotation = %RotationalAxis @ " " @ mACos(vectorDot(%Item.surfaceNrm, "0 0 1"));

   return(%Rotation);
}

function MotionSensorDeployable::onPickup(%this, %pack, %player, %amount)
{
   // %this = Sensor pack datablock
   // %pack = Sensor pack object number
   // %player = player
   // %amount = amount picked up (1)

   if(%pack.sensors $= "")
   {
      // assume that this is a pack that has been placed in a mission
      // this case was handled in ::onInventory below (max sensors);
   }
   else
   {
      // find out how many sensor were in the pack
      %player.deploySensors = %pack.sensors;
      %player.client.updateSensorPackText(%player.deploySensors);
   }
}

function MotionSensorDeployable::onThrow(%this,%pack,%player)
{
   // %this = Sensor pack datablock
   // %pack = Sensor pack object number
   // %player = player

   %player.throwSensorPack = 1;
   %pack.sensors = %player.deploySensors;
   %player.deploySensors = 0;
   %player.client.updateSensorPackText(%player.deploySensors);
   // do the normal ItemData::onThrow stuff -- sound and schedule deletion
   serverPlay3D(ItemThrowSound, %player.getTransform());
   %pack.schedulePop();
}

function MotionSensorDeployable::onInventory(%this,%player,%value)
{
   // %this = Sensor pack datablock
   // %player = player
   // %value = 1 if gaining a pack, 0 if losing a pack

   if(%player.getClassName() $= "Player")
   {
      if(%value)
      {
         // player picked up or bought a motion sensor pack
         %player.deploySensors = %this.maxSensors;
         %player.client.updateSensorPackText(%player.deploySensors);
      }
      else
      {
         // player dropped or sold a motion sensor pack
         if(%player.throwSensorPack)
         {
            // player threw the pack
            %player.throwSensorPack = 0;
            // everything handled in ::onThrow above
         }
         else
         {
            //the pack was sold at an inventory station, or unmounted because the player
            // used all the sensors
            %player.deploySensors = 0;
            %player.client.updateSensorPackText(%player.deploySensors);
         }
      }
   }
   Pack::onInventory(%this,%player,%value);
}

function PulseSensorDeployable::onPickup(%this, %pack, %player, %amount)
{
   // %this = Sensor pack datablock
   // %pack = Sensor pack object number
   // %player = player
   // %amount = amount picked up (1)

   if(%pack.sensors $= "")
   {
      // assume that this is a pack that has been placed in a mission
      // this case was handled in ::onInventory below (max sensors);
   }
   else
   {
      // find out how many sensor were in the pack
      %player.deploySensors = %pack.sensors;
      %player.client.updateSensorPackText(%player.deploySensors);
   }
}

function PulseSensorDeployable::onThrow(%this,%pack,%player)
{
   // %this = Sensor pack datablock
   // %pack = Sensor pack object number
   // %player = player

   %player.throwSensorPack = 1;
   %pack.sensors = %player.deploySensors;
   %player.deploySensors = 0;
   %player.client.updateSensorPackText(%player.deploySensors);
   // do the normal ItemData::onThrow stuff -- sound and schedule deletion
   serverPlay3D(ItemThrowSound, %player.getTransform());
   %pack.schedulePop();
}

function PulseSensorDeployable::onInventory(%this,%player,%value)
{
   // %this = Sensor pack datablock
   // %player = player
   // %value = 1 if gaining a pack, 0 if losing a pack

   if(%player.getClassName() $= "Player")
   {
      if(%value)
      {
         // player picked up or bought a motion sensor pack
         %player.deploySensors = %this.maxSensors;
         %player.client.updateSensorPackText(%player.deploySensors);
      }
      else
      {
         // player dropped or sold a motion sensor pack
         if(%player.throwSensorPack)
         {
            // player threw the pack
            %player.throwSensorPack = 0;
            // everything handled in ::onThrow above
         }
         else
         {
            //the pack was sold at an inventory station, or unmounted because the player
            // used all the sensors
            %player.deploySensors = 0;
            %player.client.updateSensorPackText(%player.deploySensors);
         }
      }
   }
   Pack::onInventory(%this,%player,%value);
}

function TurretIndoorDeployableImage::getInitialRotation(%Item, %Player)
{
   %SurfaceAngle = getTerrainAngle(%Item.surfaceNrm);
   if (%SurfaceAngle > 155)
      %Item.deployed = TurretDeployedCeilingIndoor;
   else if(%SurfaceAngle > 45)
      %Item.deployed = TurretDeployedWallIndoor;
   else
      %Item.deployed = TurretDeployedFloorIndoor;
}  

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

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

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

// ---------------------------------------------------------------------------------------
// deployed station functions
function DeployedStationInventory::onEndSequence(%data, %Station, %thread)
{
   Parent::onEndSequence(%data, %Station, %thread);
   if(%thread == $DeployThread)
   {
      %StationTrigger = new Trigger()
      {
         dataBlock = stationTrigger;
         polyhedron = "-0.125 0.0 0.1 0.25 0.0 0.0 0.0 -0.7 0.0 0.0 0.0 1.0";
         team = %Station.team;
      };             
      MissionCleanup.add(%StationTrigger);
   
      %trans = %Station.getTransform();
      %vSPos = getWords(%trans,0,2);
      %vRot =  getWords(%trans,3,5);
      %vAngle = getWord(%trans,6);
      %matrix = VectorOrthoBasis(%vRot @ " " @ %vAngle + 0.0);
      %yRot = getWords(%matrix, 3, 5);
      %pos = vectorAdd(%vSPos, vectorScale(%yRot, -0.1));
   
      %StationTrigger.setTransform(%pos @ " " @ %vRot @ " " @ %vAngle);

      // associate the trigger with the station
      %StationTrigger.station = %Station;
      %StationTrigger.mainObj = %Station;
      %StationTrigger.disableObj = %Station;
      %Station.trigger = %StationTrigger;

      %HackStationTrigger = new Trigger()
      {
         dataBlock = hackstationTrigger;
         polyhedron = "-0.125 0.0 0.1 0.25 0.0 0.0 0.0 -0.7 0.0 0.0 0.0 1.0";
         team = %Station.team;
      };             
      MissionCleanup.add(%HackStationTrigger);

      %HackStationTrigger.setTransform(%pos @ " " @ %vRot @ " " @ %vAngle);

      %HackStationTrigger.station = %Station;
      %HackStationTrigger.mainObj = %Station;
      %HackStationTrigger.disableObj = %Station;
      %Station.hacktrigger = %HackStationTrigger;

      %Station.Beacon = MakeTeamWaypoint(%Station.getWorldBoxCenter(), %Station.team, 0, 0);
   }
}

function DeployedStationInventory::ItemSwap(%Data, %Object)
{
   %Transform = %Object.getTransform();
   %Position = posFromTransform(%Transform);

   %Item = %Object.getDataBlock().item;

   %NewItem = new Item()
   {
      dataBlock = %Item;
      position = %Position;
   };
   MissionCleanup.add(%NewItem);

   $TeamDeployedCount[%Object.team, %Item]--;

   %Object.Beacon.delete();
   %Object.Trigger.delete();
   %Object.hacktrigger.delete();
   %Object.delete();

   %NewItem.applyImpulse(%Position, "0 0 10");
   %NewItem.schedulePop();
}

function DeployedStationInventory::onDestroyed(%this, %Station, %prevState)
{
   $TeamDeployedCount[%Station.team, InventoryDeployable]--;

   %Beacon = %Station.Beacon;
   %Beacon.delete();

   %Trigger = %Station.trigger;
   %Trigger.delete();

   %HackTrigger = %Station.hacktrigger;
   %HackTrigger.delete();

   // Schedule the Deletion of the generator
   %Station.schedule(450, delete);
}

//--------------------------------------------------------------------------
//DeployedMotionSensor:
//--------------------------------------------------------------------------

function DeployedMotionSensor::onDestroyed(%this, %obj, %prevState)
{
   //%obj.hide(true);
   Parent::onDestroyed(%this, %obj, %prevState);
   $TeamDeployedCount[%obj.team, MotionSensorDeployable]--;
   %obj.schedule(500, "delete");
}

function DeployedMotionSensor::ItemSwap(%Data, %Object)
{
   %Transform = %Object.getTransform();
   %Position = posFromTransform(%Transform);
   %Rotation = rotFromTransform(%Transform);

   %Rotation4 = getWord(%Rotation, 3);

   // if it was hanging upside down.
   if (%Rotation4 > 1.57)
   {
      %PositionXY = getWords(%Position, 0, 1);
      %PositionZ = getWord(%Position, 2);
      %Position = %PositionXY @ " " @ %PositionZ - 1.0;
   }

   %Item = %Object.getDataBlock().item;

   %NewItem = new Item()
   {
      dataBlock = %Item;
      position = %Position;
   };
   MissionCleanup.add(%NewItem);

   $TeamDeployedCount[%Object.team, %Item]--;

   if (%Rotation4 > 1.57)
      %NewItem.applyImpulse(%Position, "0 0 -10");
   else
      %NewItem.applyImpulse(%Position, "0 0 10");

   %Object.delete();
   %NewItem.schedulePop();
}

//--------------------------------------------------------------------------
//DeployedPulseSensor:
//--------------------------------------------------------------------------
function PulseSensorDeployableImage::onActivate(%data, %obj, %slot)
{
   %data.testInvalidDeployConditions(%obj, %slot);
}

function DeployedPulseSensor::onDestroyed(%this, %obj, %prevState)
{
   Parent::onDestroyed(%this, %obj, %prevState);
   $TeamDeployedCount[%obj.team, PulseSensorDeployable]--;
   %obj.schedule(300, "delete");
}

function DeployedPulseSensor::ItemSwap(%Data, %Object)
{
   %Transform = %Object.getTransform();
   %Position = posFromTransform(%Transform);

   %Item = %Object.getDataBlock().item;

   %NewItem = new Item()
   {
      dataBlock = %Item;
      position = %Position;
   };
   MissionCleanup.add(%NewItem);

   $TeamDeployedCount[%Object.team, %Item]--;

   %Object.delete();

   %NewItem.applyImpulse(%Position, "0 0 10");
   %NewItem.schedulePop();
}

// ---------------------------------------------------------------------------------------
// deployed turret functions

function DeployedTurret::onAdd(%Data, %Turret)
{
   // Make the Actual Turret
   Parent::onAdd(%data, %Turret);

   // Auto-Mount the turret
   if (%Turret.Barrel $= "")
      %Turret.Barrel = %Data.DefaultBarrel;

   %Turret.mountImage(%Turret.Barrel, 0, false);
}

function DeployedTurret::onDestroyed(%this, %Turret, %prevState)
{
   Parent::onDestroyed(%this, %Turret, %prevState);
   %Type = %this.getName();

   // either it'll be an outdoor turret, or one of the three types of indoor turret
   if (%Type $= "TurretDeployedOutdoor")
      %Type = "TurretOutdoorDeployable";
   else
      %Type = "TurretIndoorDeployable";

   // decrement team count for barrel and base
   $TeamDeployedCount[%Turret.team, %Type]--;
   $TeamDeployedCount[%Turret.team, %Turret.Barrel]--;   

   %Turret.schedule(500, "delete");
}

function countTurretsAllowed(%Type)
{
   // Make an array for each team
   %j = 1;
   while (%j < Game.numTeams)
   {
      %teamPlayerCount[%j] = 0;
      %j++;
   }

   // Count the number of people on each team, not observers
   %NumberClients = ClientGroup.getCount();

   %i = 0;
   while (%i < %NumberClients)
   {
      %Client = ClientGroup.getObject(%i);
      if(%Client.team > 0)
         %teamPlayerCount[%Client.team]++;

      %i++;
   }

   // the bigger team determines the number of turrets allowed
   if (%teamPlayerCount[1] > %teamPlayerCount[2])
   {
      %MaxPlayers = %teamPlayerCount[1];
   } else {
      %MaxPlayers = %teamPlayerCount[2];
   }

   // each team can have 1 turret of each type (indoor/outdoor) for every 2 players
   // minimum and maximums are defined in deployables.cs
   %TeamTurretMax = mFloor(%MaxPlayers / 2);
   if(%TeamTurretMax < $TeamDeployableMin[%type])
      %TeamTurretMax = $TeamDeployableMin[%type];
   else if(%TeamTurretMax > $TeamDeployableMax[%type])
      %TeamTurretMax = $TeamDeployableMax[%type];
   
   return(%TeamTurretMax);
}

function countBarrelsAllowed(%Type)
{
   echo("Checking " @ %Type);

   // Make an array for each team
   %j = 1;
   while (%j < Game.numTeams)
   {
      %teamPlayerCount[%j] = 0;
      %j++;
   }

   // Count the number of people on each team, not observers
   %NumberClients = ClientGroup.getCount();

   %i = 0;
   while (%i < %NumberClients)
   {
      %Client = ClientGroup.getObject(%i);
      if(%Client.team > 0)
         %teamPlayerCount[%Client.team]++;

      %i++;
   }

   // the bigger team determines the number of turrets allowed
   if (%teamPlayerCount[1] > %teamPlayerCount[2])
   {
      %MaxPlayers = %teamPlayerCount[1];
   } else {
      %MaxPlayers = %teamPlayerCount[2];
   }

   echo("Max Players: " @ %MaxPlayers);

   // Each team can only have 40% of it's barrels being used as one
   // type, except Fusion/Ion
   %TeamBarrelMax = mFloor(%MaxPlayers / 2) * 2;
   if (%Type !$= "FusionTurretBarrelPack" && %Type !$= "IonTurretBarrelPack")
      %TeamBarrelMax *= 0.4;

   echo("Team Barrel Max: " @ %TeamBarrelMax);

   if(%TeamBarrelMax < $TeamDeployableMin[%type])
      %TeamBarrelMax = $TeamDeployableMin[%type];
   else if(%TeamBarrelMax > $TeamDeployableMax[%type])
      %TeamBarrelMax = $TeamDeployableMax[%type];
   
   return(%TeamBarrelMax);
}