Advertisement
Madwand

Advanced aerial AI, version 4.21

Sep 9th, 2015
12,411
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 30.04 KB | None | 0 0
  1. --[[
  2. Advanced aerial AI, version 4.21
  3. Created by Madwand 10/24/2015
  4. Use and modify this code however you like, however please credit me
  5. if you use this AI or a derivative of it in a tournament, or you publish a blueprint
  6. using it. Also let me know if you make any significant improvements,
  7. so I can add them back into the code.
  8.  
  9. Documentation on the options is available at http://pastebin.com/36eFGAzV
  10. --]]
  11.  
  12. -- BASIC OPTIONS
  13. AngleBeforeTurn = 10
  14. AngleBeforeRoll = 30
  15. AttackRunDistance = 600
  16. AbortRunDistance = 100
  17. ClosingDistance = 1000
  18. ForceAttackTime = 15
  19. CruiseAltitude = 100
  20. MaxElevationAngle = 30
  21. CruiseSpeed = 5
  22. AttackRunSpeed = 5
  23. EscapeSpeed = 5
  24. RollingSpeed = 5
  25. ClosingSpeed = 5
  26. OrbitSpawn = true
  27.  
  28. -- AIRSHIP/HELICOPTER OPTIONS
  29. UseAltitudeJets = false
  30. UseSpinners = false
  31. MinHelicopterBladeSpeed = 10
  32. MaxHelicopterBladeSpeed = 30
  33. UseVTOLBeneathSpeed = 0
  34. VTOLEngines = nil
  35.  
  36. -- TERRAIN AVOIDANCE OPTIONS
  37. TerrainAvoidanceStrategy = 1
  38. MinAltitude = 100
  39. MaxAltitude = 400
  40. TerrainLookahead = {0,1,2,4}
  41. MaxTerrainSpeed = 5
  42.  
  43. -- WATER START OPTIONS
  44. DeployAlt = 5
  45. ReleaseAlt = 15
  46. EnableEnginesAlt = 5
  47.  
  48. -- COLLISION AVOIDANCE OPTIONS
  49. CollisionTThreshold = 4
  50. CollisionDetectionHeight = 30
  51. CollisionAngle = 40
  52.  
  53. -- FLOCKING OPTIONS
  54. NeighborRadius = 0
  55. IgnoreBelowSpeed = 0
  56. FlockWithBlueprintNames = 'all'
  57. AlignmentWeight = 1
  58. CohesionWeight = 1
  59. SeparationWeight = 1.5
  60. InjuredWeight = 0
  61. LongRangeWeight = .5
  62. TerrainAvoidanceWeight = 0
  63. TargetWeight = 1
  64.  
  65. -- "DOGFIGHTING" OPTIONS
  66. MatchTargetAltitude = false
  67. MatchAltitudeRange = 600
  68. MatchAltitudeOffset = 0
  69. MinMatchingAltitude = 100
  70.  
  71. -- VECTOR THRUST OPTIONS
  72. VTSpinners = nil
  73. MaxVTAngle = {30,30,30,90} -- yaw, roll, pitch, VTOL
  74. VTProportional = {1,1,1}
  75. VTDelta = {.1,.1,0}
  76. VTOLSpinners = 'all'
  77.  
  78. -- MISSILE AVOIDANCE OPTIONS
  79. WarningMainframe = -1
  80. RunAwayTTT = 2
  81. RunAngle = 180
  82. DodgeTTT = 1
  83. DodgingRollTolerance = 60
  84. DangerRadius = 20
  85. NormalSpeed = 100
  86. CraftRadius = 10
  87. DodgingSpeed = 5
  88.  
  89. -- ADVANCED OPTIONS
  90. MODE = 2
  91. AltitudeClamp = .2
  92. PitchDamping = 90
  93. YawDamping = 90
  94. RollDamping = 45
  95. MainDriveControlType = 0
  96. MinimumSpeed = 0
  97. MaximumSpeed = 999
  98. MaxRollAngle = 120
  99. RollTolerance = 30
  100. DebugMode = false
  101. UsePreferredSide = false
  102. UsePredictiveGuidance = false
  103. AltitudeOffset = 0
  104. AngleOfEscape = AngleBeforeTurn
  105. ExcludeSpinners = nil
  106.  
  107. --Static variables. Do not change these.
  108. DRIVELEFT = 0
  109. DRIVERIGHT = 1
  110. ROLLLEFT = 2
  111. ROLLRIGHT = 3
  112. DRIVEUP = 4
  113. DRIVEDOWN = 5
  114. DRIVEMAIN = 8
  115. DriveTypeDesc = {'Left','Right','RLeft','RRight','PUp','PDown','','','Main'}
  116.  
  117. firstpass = true
  118. state = "cruise"
  119. onattackrun = false
  120. WaterStarted = false
  121. NextAttackTime = 0
  122. OverTerrain = false
  123. PitchCorrection = 0
  124. RollSpinners = {}
  125. PitchSpinners = {}
  126. YawSpinners = {}
  127. DownSpinners = {}
  128. AllSpinners = {}
  129. ExcludedSpinners = {}
  130. HeliSpinners = {}
  131. RollEngines = {}
  132. PitchEngines = {}
  133. EngineDF = {}
  134. SpinnerStartRotations={}
  135. DYaw = 0
  136. NumSpinners = 0
  137. NumEngines = 0
  138.  
  139. --Try to pitch (or yaw, if necessary) to a given angle of elevation
  140. function AdjustElevationToAngle(I,DesiredElevation)
  141.   local PitchSpeedCheck = math.abs(DesiredElevation-Pitch)/PitchDamping
  142.   local YawSpeedCheck = math.abs(DesiredElevation-Pitch)/YawDamping
  143.   if (Pitch > DesiredElevation) then
  144.     if (math.abs(Roll)>135) then
  145.       Control(I,DRIVEUP,PitchSpeedCheck,DPitch)
  146.     elseif (Roll<-45) then
  147.       Control(I,DRIVERIGHT,YawSpeedCheck,DYaw)
  148.     elseif (Roll>45) then
  149.       Control(I,DRIVELEFT,YawSpeedCheck,-DYaw)
  150.     elseif (math.abs(Roll)<45 and state~="rolling") then
  151.       Control(I,DRIVEDOWN,PitchSpeedCheck,-DPitch)
  152.     end
  153.   elseif (Pitch < DesiredElevation) then
  154.     if (math.abs(Roll)>135 and state~="rolling") then
  155.       Control(I,DRIVEDOWN,PitchSpeedCheck,-DPitch)
  156.     elseif (Roll<-45) then
  157.       Control(I,DRIVELEFT,YawSpeedCheck,-DYaw)
  158.     elseif (Roll>45) then
  159.       Control(I,DRIVERIGHT,YawSpeedCheck,DYaw)
  160.     elseif (math.abs(Roll)<45) then
  161.       Control(I,DRIVEUP,PitchSpeedCheck,DPitch)
  162.     end
  163.   end
  164. end
  165.  
  166. --Calculate desired altitude and go there
  167. function AdjustAltitude(I, Engaged, Pos, CollisionAlt)
  168.   local DesiredAltitude = CruiseAltitude
  169.   local MatchingAltitude = Engaged and MatchTargetAltitude and Pos.GroundDistance < MatchAltitudeRange
  170.   if MatchingAltitude then
  171.     local EnemyAlt = UsePredictiveGuidance and CollisionAlt or Pos.AltitudeAboveSeaLevel
  172.     DesiredAltitude = math.max(math.min(EnemyAlt+MatchAltitudeOffset,MaxAltitude),MinMatchingAltitude)
  173.   end
  174.  
  175.   if (TerrainAvoidanceStrategy>0) then
  176.     DesiredAltitude = AdjustAltitudeForTerrain(I, DesiredAltitude, MatchingAltitude)
  177.   end
  178.  
  179.   local AltPower = limiter((DesiredAltitude-Alt)*AltitudeClamp,1)
  180.   if ((Alt < DesiredAltitude or VTOLEngines) and I:GetVelocityMagnitude()<UseVTOLBeneathSpeed) then
  181.     VTPower[4]=math.max(.2,AltPower)
  182.     AdjustElevationToAngle(I, 0)
  183.   else
  184.     local Angle = math.min(math.abs(Alt-DesiredAltitude)*AltitudeClamp,MaxElevationAngle)
  185.     if ((Alt<MinAltitude and Pitch<0) or (Alt>MaxAltitude and Pitch>0)) then
  186.       Angle = MaxElevationAngle
  187.     end -- pull up/down with full strength if we are below/above min/max altitudes
  188.     AdjustElevationToAngle(I, (Alt < DesiredAltitude) and Angle or -Angle)
  189.   end
  190.  
  191.   AdjustAltitudeJets(I,AltPower)
  192.   AdjustHelicopterBlades(I,AltPower)
  193.  
  194.   return DesiredAltitude
  195. end
  196.  
  197. function AdjustAltitudeJets(I,AltPower)
  198.   if not UseAltitudeJets then return end
  199.   if (AltPower>0) then
  200.     I:RequestThrustControl(4,AltPower)
  201.     vertical = "up"
  202.   else
  203.     I:RequestThrustControl(5,-AltPower)
  204.     vertical = "down"
  205.   end
  206. end
  207.  
  208. function ControlVTOLPower(I)
  209.   local DriveFraction = {}
  210.   if VTPower[4]~=0 then
  211.     for k,v in pairs(PitchEngines) do DriveFraction[k] = VTPower[4]*EngineDF[k] end
  212.     ComputeEngines(DriveFraction, RollEngines, 2)
  213.     ComputeEngines(DriveFraction, PitchEngines, 3)
  214.   else
  215.     for k,v in pairs(PitchEngines) do DriveFraction[k] = EngineDF[k] end
  216.   end
  217.   for k, v in pairs(PitchEngines) do I:Component_SetFloatLogic(9,k,DriveFraction[k]) end
  218. end
  219.  
  220. function AdjustHelicopterBlades(I,AltPower)
  221.   if not UseSpinners then return end
  222.   local MidSpeed = (MaxHelicopterBladeSpeed-MinHelicopterBladeSpeed)/2
  223.   local SpinSpeed = MinHelicopterBladeSpeed+MidSpeed+AltPower*MidSpeed
  224.   if not VTSpinners then
  225.     for p = 0, NumSpinners-1 do
  226.       if not ExcludedSpinners[p] then I:SetSpinnerContinuousSpeed(p, SpinSpeed) end
  227.     end
  228.   else
  229.     for k,v in pairs(HeliSpinners) do I:SetSpinnerContinuousSpeed(v, SpinSpeed) end
  230.   end
  231. end
  232.  
  233. -- Get the current stats about angles of the vehicle
  234. function GetAngleStats(I)
  235.   Roll = I:GetConstructRoll()
  236. -- transform roll into roll we are familiar with from the game
  237.   if (Roll > 180) then Roll = Roll-360 end
  238.  
  239.   Pitch = I:GetConstructPitch()
  240. -- transform pitch into pitch we are familiar with from the game
  241.   if (Pitch > 180) then
  242.     Pitch = 360 - Pitch
  243.   elseif (Pitch>0) then
  244.     Pitch = -Pitch
  245.   end
  246.  
  247.   Yaw = I:GetConstructYaw()
  248.  
  249. -- Many vehicles need to keep their nose pitched upwards relative to velocity.
  250. -- This uses basic machine learning to calculate the upwards pitch correction.
  251.   if not VTPower or VTPower[4]==0 then -- if we haven't been in VTOL mode
  252.     VPitch = math.deg(math.asin(I:GetVelocityVectorNormalized().y)) -- observed pitch from velocity
  253.     PitchCorrection = limiter(PitchCorrection + .01*(Pitch-VPitch-PitchCorrection),MaxElevationAngle)
  254.     Pitch = Pitch - PitchCorrection
  255.   end
  256.  
  257.   local AngularVelocity = I:GetLocalAngularVelocity()
  258.   DPitch = -AngularVelocity.x
  259.   DYaw = DYaw+.1*(AngularVelocity.y-DYaw)
  260.   DRoll = AngularVelocity.z
  261.  
  262.   CoM = I:GetConstructCenterOfMass()
  263.   Alt = CoM.y
  264.   ForwardV = I:GetConstructForwardVector()
  265. end
  266.  
  267. function sign(x)
  268.   return x>0 and 1 or x<0 and -1 or 0
  269. end
  270.  
  271. function GetModifiedAzimuth(Azimuth, Offset)
  272.   local Azimuth = Azimuth-(UsePreferredSide and 1 or sign(Azimuth))*Offset
  273.   if math.abs(Azimuth)>180 then return Azimuth-sign(Azimuth)*360 end
  274.   return Azimuth
  275. end
  276.  
  277. -- returns true if we are yawing, false if we are rolling
  278. function TurnTowardsAzimuth(I, Azimuth, Offset, DesiredAltitude, RTolerance, Flock)
  279.   Azimuth = GetModifiedAzimuth(Azimuth, Offset)
  280.   if Flock>0 then Azimuth=Flocking(I,Azimuth,Flock>1) end
  281.   if (VTPower[4]==0 and math.abs(Azimuth) > AngleBeforeRoll-Offset) then -- roll to turn
  282.     RollTowardsAzimuth(I,Azimuth,DesiredAltitude,RTolerance)
  283.     YawTowardsAzimuth(I,Azimuth)
  284.     state = "rolling"
  285.     return false
  286.   end -- yaw to turn
  287.   AdjustRollToAngle(I, 0)
  288.   state = "yawing"
  289.   YawTowardsAzimuth(I,Azimuth)
  290.   return true
  291. end
  292.  
  293. -- Try to roll the craft and pull up on pitch controls to face a given azimuth (relative to the vehicles facing)
  294. -- Will try to set roll angle at one that will maintain a given altitude
  295. function RollTowardsAzimuth(I,Azimuth,DesiredAltitude,Tolerance)
  296. -- depending on our altitude, RollAngle will be set to 90 +/- 40 degrees to climb or descend as appropriate
  297.   RollAngle = sign(Azimuth)*math.min(MaxRollAngle, 90+limiter((Alt-DesiredAltitude)*.66, 30))
  298.  
  299.   AdjustRollToAngle(I,RollAngle)
  300.  
  301.   if (sign(Roll)==sign(Azimuth) and Roll >= RollAngle-Tolerance and Roll <= RollAngle+Tolerance) then -- start pitching
  302.     Control(I, DRIVEUP,1,0)
  303.   end
  304. end
  305.  
  306. -- Roll the vehicle to a specified roll angle
  307. function AdjustRollToAngle(I,Angle)
  308.   local RollSpeedCheck = math.abs(Angle-Roll)/RollDamping
  309.   if (Roll < Angle) then
  310.     Control(I, ROLLLEFT,RollSpeedCheck,DRoll)
  311.   elseif (Roll > Angle) then
  312.     Control(I, ROLLRIGHT,RollSpeedCheck,-DRoll)
  313.   end
  314.   --I:LogToHud(string.format("%.2f %.2f %.2f %.2f", Angle, Roll, RollSpeedCheck, DRoll))
  315. end
  316.  
  317. -- transform an absolute azimuth into one relative to the nose of the vehicle
  318. function GetRelativeAzimuth(AbsAzimuth)
  319.   local Azimuth = math.abs(Yaw-AbsAzimuth)
  320.   if (Azimuth>180) then Azimuth = 360-Azimuth end
  321.   if (Yaw>AbsAzimuth) then return Azimuth end
  322.   return -Azimuth
  323. end
  324.      
  325. -- Yaw the vehicle towards a given aziumth (relative to the vehicle's facing)
  326. function YawTowardsAzimuth(I,Azimuth)
  327.   if (math.abs(Roll)<30) then
  328.     local YawSpeedCheck = math.abs(Azimuth)/YawDamping
  329.     if (Azimuth > 0) then
  330.       Control(I, DRIVELEFT,YawSpeedCheck,-DYaw)
  331.     elseif (Azimuth < 0) then
  332.       Control(I, DRIVERIGHT,YawSpeedCheck,DYaw)
  333.     end
  334.     return true
  335.   end
  336.   return false
  337. end
  338.  
  339. -- No enemies, just cruise along
  340. function Cruise(I)
  341.   if OrbitSpawn and I:GetNumberOfMainframes()>0 then
  342.     SpawnInfo = I:GetTargetPositionInfoForPosition(0, SpawnPos.x, 0, SpawnPos.z)
  343.     NavigateToPoint(I, false, SpawnInfo)
  344.   else
  345.     if I:GetNumberOfMainframes()==0 then
  346.       I:LogToHud("No AI mainframe found; please add one!")
  347.     end
  348.     state = "cruise"
  349.     AdjustAltitude(I,false,nil,0)
  350.     AdjustRollToAngle(I, 0)
  351.     SetSpeed(I, CruiseSpeed)
  352.     NextAttackTime = I:GetTime()+ForceAttackTime
  353.   end
  354. end
  355.  
  356. -- Given the current velocity vector and an azimuth, get the expected terrain height in that direction
  357. function GetTerrainAltitude(I, VelocityV, Angle)
  358.   local TerrainAltitude = -999
  359.   for i,Lookahead in ipairs(TerrainLookahead) do
  360.     Position = CoM + Quaternion.Euler(0,Angle,0) * VelocityV * Lookahead
  361.     TerrainAltitude = math.max(TerrainAltitude,I:GetTerrainAltitudeForPosition(Position))
  362.   end
  363.   return TerrainAltitude
  364. end
  365.  
  366. -- Adjust a given altitude to compensate for terrain, according to "TerrainAvoidanceStrategy"
  367. -- and other terrain-avoidance-specific parameters.
  368. -- Last parameter forces the use of "TerrainAvoidanceStrategy" 2
  369. -- returns new altitude that should be used
  370. function AdjustAltitudeForTerrain(I, Altitude, StrategyOverride)
  371.   VelocityV = I:GetVelocityVector()
  372.   TerrainAltitude = math.max(GetTerrainAltitude(I, VelocityV, 0),GetTerrainAltitude(I, VelocityV, 30),GetTerrainAltitude(I, VelocityV, -30))
  373.  
  374.   OverTerrain = false
  375.   if (TerrainAltitude + MinAltitude > Altitude or TerrainAltitude>0) then -- we'll need to avoid the terrain
  376.     OverTerrain = TerrainAltitude + MinAltitude > Altitude
  377.    
  378.     if(StrategyOverride or TerrainAvoidanceStrategy == 2) then -- don't change altitude unless needed
  379.       Altitude = math.max(Altitude, TerrainAltitude + MinAltitude)
  380.     elseif(TerrainAvoidanceStrategy == 1) then -- just add Altitude to terrain height
  381.       Altitude = math.max(math.min(TerrainAltitude+Altitude, MaxAltitude), TerrainAltitude + MinAltitude)
  382.     end
  383.   end
  384.  
  385.   --I:LogToHud(string.format("TA: %.2f Here: %.2f Alt: %.2f", TerrainAltitude, I:GetTerrainAltitudeForLocalPosition(0, 0, 0), Altitude))
  386.  
  387.   return Altitude
  388. end
  389.  
  390. function GetIntercept(I, Pos)
  391.   local mySpeed = I:GetVelocityMagnitude()
  392.   local TTT = FindConvergence(I, Pos.Position, Pos.Velocity, CoM, mySpeed, mySpeed*.75)
  393.   local Prediction = Pos.Position + Pos.Velocity * TTT
  394.   return TTT, I:GetTargetPositionInfoForPosition(0, Prediction.x, Prediction.y, Prediction.z).Azimuth, Prediction.y
  395. end
  396.  
  397. -- Perform a water start check. Returns true if movement is permitted.
  398. function WaterStartCheck(I)
  399.   if (Alt < DeployAlt) then
  400.     I:Component_SetBoolLogicAll(0, true)
  401.     WaterStarted = true
  402.   elseif (WaterStarted and Alt > ReleaseAlt) then
  403.     I:Component_SetBoolLogicAll(0, false)
  404.     WaterStarted = false
  405.   end
  406.  
  407.   if (not WaterStarted or Alt>=EnableEnginesAlt) then
  408.     return true
  409.   end
  410.   return false
  411. end
  412.  
  413. function SetSpeed(I, Speed)
  414.   if (I:GetVelocityMagnitude()>MaximumSpeed) then Speed=1
  415.   elseif (I:GetVelocityMagnitude()<MinimumSpeed) then Speed=5 end
  416.   if MainDriveControlType==1 then
  417.     I:RequestThrustControl(0,Speed/5)
  418.   elseif MainDriveControlType==2 then
  419.     I:RequestControl(MODE,DRIVEMAIN,5)
  420.     for k, v in pairs(Main) do I:Component_SetFloatLogic(9,v,Speed/5) end
  421.   else
  422.     I:RequestControl(MODE,DRIVEMAIN,Speed)
  423.   end
  424. end
  425.  
  426. function limiter(p,l)
  427.   return math.min(l,math.max(-l,p))
  428. end
  429.  
  430. function Control(I, Type, SpeedCheck, Delta)
  431.   local DoFType = math.floor(Type/2)+1
  432.   if (SpeedCheck>Delta) then
  433.     I:RequestControl(MODE, Type, 1)
  434.     DriveDesc[DoFType] = DriveTypeDesc[Type+1]
  435.   end
  436.   VTPower[DoFType] = (Type%2*2-1)*limiter(SpeedCheck*VTProportional[DoFType]-Delta*VTDelta[DoFType],1)
  437. end
  438.  
  439. function ComputeSpinners(Rotation, Spinners, Axis)
  440.   for Spinner,Dir in pairs(Spinners) do
  441.     Rotation[Spinner] = Rotation[Spinner] + Dir*VTPower[Axis]*MaxVTAngle[Axis]
  442.   end
  443. end
  444.  
  445. function ComputeEngines(DriveFraction, Engines, Axis)
  446.   for Engine,Dir in pairs(Engines) do
  447.     if Dir==sign(VTPower[Axis]) then
  448.       DriveFraction[Engine] = DriveFraction[Engine]*(1-math.abs(VTPower[Axis]))
  449.     end
  450.   end
  451. end
  452.  
  453. function VectorEngines(I)
  454.   local RL,RR,RY = 0,0,0
  455.  
  456.   local Rotation = {}
  457.   for k,v in pairs(AllSpinners) do Rotation[v] = 0 end
  458.  
  459.   if (state=="rolling" and VTPower[3]>0) then
  460.     VTPower[3]=0
  461.   end
  462.   ComputeSpinners(Rotation, YawSpinners, 1)
  463.   ComputeSpinners(Rotation, RollSpinners, 2)
  464.   ComputeSpinners(Rotation, PitchSpinners, 3)
  465.   if (VTOLEngines and VTPower[4]~=0) then -- VTOL Mode
  466.     for Spinner,Dir in pairs(DownSpinners) do Rotation[Spinner] = Dir*MaxVTAngle[4] end
  467.   else
  468.     ComputeSpinners(Rotation, DownSpinners, 4)
  469.   end
  470.  
  471.   RotateSpinnersTo(I,Rotation)
  472.  
  473.   --I:LogToHud(string.format("Y: %.02f R: %.02f P: %.02f", VTPower[1], VTPower[2], VTPower[3]))
  474. end
  475.  
  476. function ClassifySpinner(I,p)
  477.   if VTSpinners=='all' and ExcludedSpinners[p] then return end
  478.   local info = I:GetSpinnerInfo(p)
  479.   local h,a,b = EulerAngles(info.LocalRotation)
  480.   local pos = info.LocalPositionRelativeToCom
  481.   SpinnerStartRotations[p]=info.LocalRotation
  482.   a=math.floor(a+.5)
  483.   b=math.floor(b+.5)
  484.   if (a==0 and b==0) then
  485.     tinsert(pos.z,YawSpinners,1,-1,p)
  486.     AddSpinner(pos.y,-1,p)
  487.   elseif (a==0 and math.abs(b)>170) then
  488.     tinsert(pos.z,YawSpinners,-1,1,p)
  489.     AddSpinner(pos.y,1,p)
  490.   else
  491.     local h,a,b = EulerAngles(info.LocalRotation*Quaternion.Euler(0, 0, 90))
  492.     h=math.floor(h+.5)
  493.     a=math.floor(a+.5)
  494.     if (h==0 and a==0) then -- pointed right
  495.       tinsert(pos.z,PitchSpinners,1,-1,p)
  496.       if VTOLSpinners=='all' and ExcludedSpinners[p] then tinsert(pos.z,DownSpinners,-1,-1,p) end
  497.       AddSpinner(pos.x,-1,p)
  498.     elseif (a==0 and math.abs(h)>170) then -- pointed left
  499.       tinsert(pos.z,PitchSpinners,-1,1,p)
  500.       if VTOLSpinners=='all' and ExcludedSpinners[p] then tinsert(pos.z,DownSpinners,1,1,p) end
  501.       AddSpinner(pos.x,1,p)
  502.     end
  503.   end
  504. end
  505.  
  506. function ClassifyVTOLSpinner(I,p)
  507.   local info = I:GetSpinnerInfo(p)
  508.   local h,a,b = EulerAngles(info.LocalRotation*Quaternion.Euler(0, 0, 90))
  509.   local pos = info.LocalPositionRelativeToCom
  510.   h=math.floor(h+.5)
  511.   a=math.floor(a+.5)
  512.   --I:Log(string.format("Spinner: %d Orientation: (%.2f, %.2f, %.2f) Position: (%.2f, %.2f, %.2f)", p, h, a, b, pos.x, pos.y, pos.z))
  513.   if (h==0 and a==0) then
  514.     tinsert(pos.z,DownSpinners,-1,-1,p)
  515.   elseif (a==0 and math.abs(h)>170) then
  516.     tinsert(pos.z,DownSpinners,1,1,p)
  517.   end
  518. end
  519.  
  520. function GetPosRelativeToCoM(pos)
  521.   local rot = Quaternion.Inverse(Quaternion.LookRotation(ForwardV))
  522.   local rpos = rot*(pos-CoM)
  523.   for i, p in ipairs(rpos) do rpos[i]=math.floor(p*10+.5)/10 end
  524.   return rpos
  525. end
  526.  
  527. function ClassifyEngine(I,p,force)
  528.   local info = I:Component_GetBlockInfo(9,p)
  529.   if (force or info.LocalForwards.y==1) then
  530.     pos = GetPosRelativeToCoM(info.Position)
  531.     EngineDF[p] = I:Component_GetFloatLogic(9,p)
  532.     tinsert(pos.z,PitchEngines,1,-1,p)
  533.     if pos.x<-1.1 then  -- on left
  534.       tinsert(pos.z,RollEngines,-1,-1,p)
  535.     elseif pos.x>1.1 then -- on right
  536.       tinsert(pos.z,RollEngines,1,1,p)
  537.     end
  538.   end
  539. end
  540.  
  541. function AddSpinner(comp,dir,p)
  542.   if comp<-1 then  -- on left
  543.     RollSpinners[p] = dir
  544.   elseif comp>1 then -- on right
  545.     RollSpinners[p] = -dir
  546.   end
  547.   table.insert(AllSpinners,p)
  548. end
  549.      
  550. function tinsert(z,s,x,y,p)
  551.   s[p] = z>0 and x or y
  552. end
  553.      
  554. function EulerAngles(q1)
  555.   local sqw = q1.w*q1.w
  556.   local sqx = q1.x*q1.x
  557.   local sqy = q1.y*q1.y
  558.   local sqz = q1.z*q1.z
  559.   local unit = sqx + sqy + sqz + sqw --if normalised is one, otherwise is correction factor
  560.   local test = q1.x*q1.y + q1.z*q1.w
  561.   local heading, attitude, bank
  562.   if (test > 0.499*unit) then --singularity at north pole
  563.     heading = 2 * math.atan2(q1.x,q1.w)
  564.     attitude = math.pi/2;
  565.     bank = 0
  566.   elseif (test < -0.499*unit) then --singularity at south pole
  567.     heading = -2 * math.atan2(q1.x,q1.w)
  568.     attitude = -math.pi/2
  569.     bank = 0
  570.   else
  571.     heading = math.atan2(2*q1.y*q1.w-2*q1.x*q1.z , sqx - sqy - sqz + sqw)
  572.     attitude = math.asin(2*test/unit)
  573.     bank = math.atan2(2*q1.x*q1.w-2*q1.y*q1.z , -sqx + sqy - sqz + sqw)
  574.   end
  575.   return math.deg(heading), math.deg(attitude), math.deg(bank)
  576. end
  577.  
  578. function RotateSpinnersTo(I, Spinners)
  579.   for Spinner, NewAngle in pairs(Spinners) do
  580.     local Angle = EulerAngles(Quaternion.Inverse(SpinnerStartRotations[Spinner]) * I:GetSpinnerInfo(Spinner).LocalRotation)
  581.     local DeflectAngle = limiter(limiter(NewAngle,90)-Angle,43)
  582.     if math.abs(DeflectAngle)<2 then DeflectAngle=0 end
  583.     I:SetSpinnerContinuousSpeed(Spinner,DeflectAngle*30/43)
  584.   end
  585. end
  586.  
  587. function FindConvergence(I, tPos, tVel, wPos, wSpeed, minConv)
  588.    local relativePosition = wPos - tPos
  589.    local distance = Vector3.Magnitude(relativePosition)
  590.    local targetAngle = I:Maths_AngleBetweenVectors(relativePosition, tVel)
  591.    local tSpeed = Vector3.Magnitude(tVel)
  592.  
  593.    local a = tSpeed^2 - wSpeed^2
  594.    local b = -2 * tSpeed * distance * math.cos(math.rad(targetAngle))
  595.    local c = distance^2
  596.    local det = math.sqrt(b^2-4*a*c)
  597.    local ttt = distance / minConv
  598.  
  599.    if det > 0 then
  600.       local root1 = math.min((-b + det)/(2*a), (-b - det)/(2*a))
  601.       local root2 = math.max((-b + det)/(2*a), (-b - det)/(2*a))
  602.       ttt = (root1 > 0 and root1) or (root2 > 0 and root2) or ttt
  603.    end
  604.    return ttt
  605. end
  606.  
  607. function CheckMissileWarnings(I)
  608.   if WarningMainframe<0 then return 0 end
  609.  
  610.   local NumWarnings = I:GetNumberOfWarnings(WarningMainframe)
  611.   local MinTTT = math.max(RunAwayTTT,DodgeTTT)
  612.   local MinWarning
  613.   local OwnVelocity = ForwardV * math.max(NormalSpeed, I:GetVelocityMagnitude())
  614.  
  615.   for w = 0, NumWarnings - 1 do
  616.     local Warning = I:GetMissileWarning(WarningMainframe, w)
  617.     if Warning.Valid and I:Maths_AngleBetweenVectors(Warning.Velocity, CoM - Warning.Position) < 90 then
  618.       local mSpeed = Vector3.Magnitude(Warning.Velocity)
  619.       local TTT = FindConvergence(I, CoM, OwnVelocity, Warning.Position, mSpeed, mSpeed*.75)
  620.       local PredictedPosition = CoM + OwnVelocity * TTT
  621.       local Orthogonal = (Warning.Position + Vector3.Normalize(Warning.Velocity)
  622.                          * Vector3.Distance(Warning.Position, PredictedPosition)
  623.                          * math.cos(math.rad(I:Maths_AngleBetweenVectors(Warning.Velocity, PredictedPosition - Warning.Position))))
  624.                          - PredictedPosition
  625.       local AdjustedPosition = PredictedPosition + Vector3.ClampMagnitude(Orthogonal, CraftRadius)
  626.       local Radius = (Vector3.Distance(Warning.Position, AdjustedPosition) / 2)
  627.                      / math.cos(math.rad(I:Maths_AngleBetweenVectors(Warning.Velocity, AdjustedPosition - Warning.Position) - 90))
  628.       if TTT < MinTTT and Radius > DangerRadius then
  629.         MinTTT = TTT
  630.         MinWarning = Warning
  631.       end
  632.     end
  633.   end
  634.  
  635.   if (MinTTT < math.max(RunAwayTTT,DodgeTTT)) then
  636.     local AdjustedPosition = CoM + OwnVelocity * math.min(MinTTT,0.75)
  637.     local ApproachAngle=I:Maths_AngleBetweenVectors(MinWarning.Velocity, AdjustedPosition - MinWarning.Position)
  638.     if (MinTTT < DodgeTTT) then
  639.       if (math.abs(MinWarning.Azimuth) < 10 or math.abs(MinWarning.Azimuth) > 170) then
  640.         return sign(Roll)*120
  641.       end
  642.      
  643.       local Orthogonal = (MinWarning.Position + Vector3.Normalize(MinWarning.Velocity)
  644.                          * Vector3.Distance(MinWarning.Position, AdjustedPosition)
  645.                          * math.cos(math.rad(ApproachAngle))) - AdjustedPosition
  646.       local Objective = I:GetConstructPosition() - Orthogonal
  647.       return I:GetTargetPositionInfoForPosition(0, Objective.x, Objective.y, Objective.z).Azimuth
  648.     end
  649.     local Angle=MinWarning.Azimuth>0 and RunAngle or -RunAngle
  650.     local Objective = I:GetConstructPosition()+Quaternion.Euler(0,Angle,0)*(MinWarning.Position-CoM)
  651.     return I:GetTargetPositionInfoForPosition(0, Objective.x, 0, Objective.z).Azimuth
  652.   end
  653.   return 0
  654. end
  655.  
  656. function ClassifyEngines(I)
  657.   if NumEngines==I:Component_GetCount(9) then return end
  658.   NumEngines=I:Component_GetCount(9)
  659.  
  660.   Main={} -- try to figure out which drives are pointed backwards
  661.   for p = 0, NumEngines - 1 do
  662.     local info=I:Component_GetBlockInfo(9,p)
  663.     if (info.LocalForwards.z==1 and GetPosRelativeToCoM(info.Position)==info.LocalPositionRelativeToCom) then
  664.       table.insert(Main,p)
  665.     end
  666.   end
  667.  
  668.   if VTOLEngines=='all' then
  669.     for p = 0, NumEngines - 1 do ClassifyEngine(I,p,false) end
  670.   end
  671. end
  672.  
  673. function ClassifySpinners(I)
  674.   if NumSpinners==I:GetSpinnerCount() then return end
  675.   NumSpinners=I:GetSpinnerCount()
  676.  
  677.   if VTSpinners=='all' then
  678.     for p = 0, NumSpinners - 1 do ClassifySpinner(I,p) end
  679.   end
  680. end
  681.  
  682. function NameMatches(Name)
  683.   if FlockWithBlueprintNames=='all' then return true end
  684.   for k,v in pairs(FlockWithBlueprintNames) do
  685.     if v==string.sub(Name,1,string.len(v)) then return true end
  686.   end
  687.   return false
  688. end
  689.  
  690. function Flocking(I, Azimuth, Escaping)
  691.   if NeighborRadius==0 then return Azimuth end
  692.  
  693.   local FCount = I:GetFriendlyCount()
  694.   local A = Vector3(0,0,0)
  695.   local C,S,J,L,E = A,A,A,A,A
  696.   local Near,Aligning,Injured,Far,Terrain=0,0,0,0,0
  697.   for f = 0, FCount-1 do
  698.     local FInfo = I:GetFriendlyInfo(f)
  699.     if FInfo.Valid then
  700.       local Dist=FInfo.CenterOfMass-CoM
  701.       if Dist.magnitude<NeighborRadius then
  702.         if FInfo.Velocity.magnitude>=IgnoreBelowSpeed and NameMatches(FInfo.BlueprintName) then
  703.           A=A+FInfo.Velocity  -- Alignment
  704.           C=C+FInfo.CenterOfMass -- Cohesion
  705.           J=J+FInfo.CenterOfMass*(1-FInfo.HealthFraction) -- Injured cohesion
  706.           Aligning=Aligning+1
  707.           Injured=Injured+(1-FInfo.HealthFraction)
  708.         end
  709.         S=S+Dist -- Separation
  710.         Near=Near+1
  711.       elseif FInfo.Velocity.magnitude>=IgnoreBelowSpeed and NameMatches(FInfo.BlueprintName) then
  712.         L=L+FInfo.CenterOfMass -- Long-range cohesion
  713.         Far=Far+1
  714.       end
  715.     end
  716.   end
  717.  
  718.   if TerrainAvoidanceWeight~=0 then
  719.     local ForwardV = Vector3.forward * NeighborRadius
  720.     local Angles = {0,45,90,135,180,225,270,315}
  721.     for k, a in pairs(Angles) do
  722.       local Position = CoM + Quaternion.Euler(0,a,0) * ForwardV
  723.       local TerrainAltitude = I:GetTerrainAltitudeForPosition(Position)
  724.       if (TerrainAltitude + MinAltitude > Alt) then
  725.         local Dist=Position-CoM
  726.         E=E+Dist*(1 + math.min(50,TerrainAltitude + MinAltitude - Alt)*.02)
  727.         Terrain=Terrain+1
  728.       end
  729.     end
  730.     if Terrain>0 then E=(-E/Terrain).normalized*TerrainAvoidanceWeight end
  731.   end
  732.  
  733.   if I:GetNumberOfMainframes() > 0 then
  734.     local ECount = I:GetNumberOfTargets(0)
  735.     for e = 1, ECount-1 do
  736.       local EInfo = I:GetTargetPositionInfo(0,e)
  737.       if EInfo.Valid then
  738.         local Dist=EInfo.Position-CoM
  739.         if Dist.magnitude<NeighborRadius and math.abs(EInfo.Position.y-Alt)<CollisionDetectionHeight then
  740.           S=S+Dist -- Separation
  741.           Near=Near+1
  742.         end
  743.       end
  744.     end
  745.   end
  746.  
  747.   if Near+Far+Terrain==0 then return Azimuth end
  748.   if Aligning>0 then
  749.     A=(A/Aligning).normalized*AlignmentWeight
  750.     C=(C/Aligning-CoM).normalized*CohesionWeight
  751.   end
  752.   if Injured>0 then J=(J/Injured-CoM).normalized*InjuredWeight end
  753.   if Far>0 then L=(L/Far-CoM).normalized*LongRangeWeight end
  754.   if Near>0 then S=(-S/Near).normalized*SeparationWeight end
  755.  
  756.   local T=Quaternion.Euler(0,Yaw-Azimuth,0)*Vector3.forward*TargetWeight*(Escaping and 0 or 1)
  757.   local Objective = I:GetConstructPosition()+A+C+S+J+L+E+T
  758.   return I:GetTargetPositionInfoForPosition(0, Objective.x, 0, Objective.z).Azimuth
  759. end
  760.  
  761. -- given information about a target, navigates to it
  762. function NavigateToPoint(I, Engaged, Pos)
  763.   Speed = EscapeSpeed
  764.   local DodgeAngle = CheckMissileWarnings(I)
  765.   local CollisionTTT, CollisionAzimuth, CollisionAlt = GetIntercept(I, Pos)
  766.   local DesiredAltitude = AdjustAltitude(I,Engaged,Pos,CollisionAlt)
  767.  
  768.   if (DodgeAngle~=0) then -- dodge missiles!
  769.     TurnTowardsAzimuth(I, DodgeAngle, 0, DesiredAltitude, DodgingRollTolerance, 0)
  770.     Speed = DodgingSpeed
  771.     state = "dodging"
  772.     onattackrun = true
  773.   elseif (Engaged and CollisionTTT<CollisionTThreshold and
  774.           (math.abs(Pos.AltitudeAboveSeaLevel-Alt)<CollisionDetectionHeight or
  775.            math.abs(CollisionAlt-Alt)<CollisionDetectionHeight) and
  776.            math.abs(CollisionAzimuth)<CollisionAngle) then
  777.     TurnTowardsAzimuth(I, CollisionAzimuth, CollisionAngle, DesiredAltitude, DodgingRollTolerance, 0)
  778.     Speed = DodgingSpeed
  779.     state = "collision"
  780.     onattackrun = true
  781.   elseif (Pos.GroundDistance > ClosingDistance) then
  782.     Speed = TurnTowardsAzimuth(I, CollisionAzimuth, 0, DesiredAltitude, RollTolerance, 1) and ClosingSpeed or RollingSpeed
  783.     state = "closing"
  784.     onattackrun = true
  785.   elseif onattackrun then
  786.     local Azimuth = UsePredictiveGuidance and CollisionAzimuth or Pos.Azimuth
  787.     Speed = TurnTowardsAzimuth(I, Azimuth, AngleBeforeTurn, DesiredAltitude, RollTolerance, 1) and AttackRunSpeed or RollingSpeed
  788.     if (Pos.GroundDistance < AbortRunDistance) then
  789.       onattackrun = false
  790.       NextAttackTime = I:GetTime()+ForceAttackTime
  791.       EscapeAngle = GetModifiedAzimuth(Yaw-Azimuth, AngleOfEscape)
  792.     end
  793.   else -- not on attack run, just go forwards until we're out of range
  794.     TurnTowardsAzimuth(I, GetRelativeAzimuth(EscapeAngle), 0, DesiredAltitude, RollTolerance, 2)
  795.     state = "escaping"
  796.     onattackrun = Pos.GroundDistance > AttackRunDistance or I:GetTime()>NextAttackTime
  797.   end
  798.  
  799.   if OverTerrain then
  800.     Speed = math.min(Speed,MaxTerrainSpeed)
  801.   end
  802.   if not Engaged then
  803.     Speed = math.min(Speed,CruiseSpeed)
  804.   end
  805.   SetSpeed(I, Speed)
  806. end
  807.  
  808. -- Calculate all movement for the vehicle
  809. function Movement(I)
  810.   if firstpass then
  811.     firstpass = false
  812.     SpawnPos = CoM
  813.     EscapeAngle = Yaw
  814.     if type(AltitudeOffset) == "table" then
  815.       math.randomseed(I:GetTime()+CoM.x+CoM.y+CoM.z)
  816.       AltitudeOffset=math.floor(math.random()*(AltitudeOffset[2]-AltitudeOffset[1])+AltitudeOffset[1])
  817.       I:Log("AltitudeOffset: "..AltitudeOffset)
  818.     end
  819.     CruiseAltitude = CruiseAltitude+AltitudeOffset
  820.     MaxAltitude = MaxAltitude+AltitudeOffset
  821.     MatchAltitudeOffset = MatchAltitudeOffset+AltitudeOffset
  822.     MinMatchingAltitude = MinMatchingAltitude+AltitudeOffset
  823.  
  824.     if type(VTOLEngines) == "table" then
  825.       for k, p in pairs(VTOLEngines) do ClassifyEngine(I,p,true) end
  826.     end
  827.  
  828.     if type(VTSpinners) == "table" then
  829.       local Used = {}
  830.       for k, p in pairs(VTSpinners) do
  831.         ClassifySpinner(I,p)
  832.         Used[p]=true
  833.       end
  834.       for p = 0, I:GetSpinnerCount()-1 do
  835.         if not Used[p] then table.insert(HeliSpinners,p) end
  836.       end
  837.     end
  838.     if type(VTOLSpinners) == "table" then
  839.       for k, p in pairs(VTOLSpinners) do ClassifyVTOLSpinner(I,p) end
  840.     end
  841.     if ExcludeSpinners then
  842.       for k, p in pairs(ExcludeSpinners) do ExcludedSpinners[p]=true end
  843.     end
  844.   end
  845.   ClassifyEngines(I)
  846.   ClassifySpinners(I)
  847.  
  848.   DriveDesc = {'','','',''}
  849.   VTPower = {0,0,0,0} -- yaw, roll, pitch, VTOL
  850.    
  851.   if (I:GetNumberOfMainframes() > 0 and I:GetNumberOfTargets(0) > 0) then
  852.     local TargetPos = I:GetTargetPositionInfo(0,0)
  853.     if TargetPos.Valid then
  854.       NavigateToPoint(I, true, TargetPos)
  855.     else
  856.       Cruise(I)
  857.     end
  858.   else
  859.     Cruise(I)
  860.   end
  861.  
  862.   if VTSpinners then VectorEngines(I) end
  863.   if VTOLEngines then ControlVTOLPower(I) end
  864.  
  865.   if DebugMode then
  866.     --I:LogToHud(string.format("%.2f %.2f", DPitch, math.abs(Roll)))
  867.     I:LogToHud(string.format("%s %s %s %s %d", state, DriveDesc[1], DriveDesc[2], DriveDesc[3], Speed))
  868.     --I:Log(string.format("Y:%.2f R:%.2f P:%.2f", Yaw, Roll, Pitch))
  869.   end
  870. end
  871.  
  872. -- Main update function. Everything starts here.
  873. function Update(I)
  874.   GetAngleStats(I)
  875.   if  not I:IsDocked() and WaterStartCheck(I) then
  876.     Movement(I)
  877.   else
  878.     for p = 0, I:GetSpinnerCount()-1 do I:SetSpinnerRotationAngle(p,0) end
  879.   end
  880. end
  881. --
Advertisement
Add Comment
Please, Sign In to add comment
Advertisement