Madwand

Untitled

Oct 9th, 2015
171
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
Lua 15.19 KB | None | 0 0
  1. --[[
  2. Weapon guidance AI, version 1.0.1.0
  3. https://github.com/Blothorn/FTD for further documentation and license.
  4. --]]
  5.  
  6. -- Globals
  7. TargetBufferSize = 120
  8. ConvergenceIterationThreshold = 0.005
  9. ConvergenceMaxIterations = 25
  10.  
  11. TargetLists = {
  12.   AA = {
  13.     MainframeIndex = 0,
  14.     MinimumSpeed = 40,
  15.     MaximumSpeed = 400,
  16.     MinimumAltitude = -10,
  17.     MaximumAltitude = 99999,
  18.     MaximumRange = 800,
  19.     TTT = 5,
  20.     Depth = 1
  21.   }
  22. ,
  23.   GND = {
  24.     MainframeIndex = 0,
  25.     MinimumSpeed = 0,
  26.     MaximumSpeed = 40,
  27.     MinimumAltitude = -60,
  28.     MaximumAltitude = 99999,
  29.     MaximumRange = 600,
  30.     TTT = 5,
  31.     Depth = 1
  32.   }
  33. }
  34. WeaponSystems = {}
  35.  
  36. WeaponSystems[2] = {
  37.   Type = 2,
  38.   TargetList = 'AA',
  39.   MaximumAltitude = 99999,
  40.   MinimumAltitude = -10,
  41.   MaximumRange = 600,
  42.   MinimumRange = 200,
  43.   FiringAngle = 30,
  44.   Speed = 250,
  45.   LaunchDelay = 0.3,
  46.   MinimumConvergenceSpeed = 200,
  47.   AimPointProportion = 0.5,
  48.   Endurance = 5,
  49.   MinimumCruiseAltitude = 5,
  50.   DetonateOnCull = true,
  51. }
  52.  
  53. WeaponSystems[1] = {
  54.   Type = 2,
  55.   TargetList = 'GND',
  56.   MaximumAltitude = 99999,
  57.   MinimumAltitude = -60,
  58.   MaximumRange = 400,
  59.   MinimumRange = 200,
  60.   FiringAngle = 60,
  61.   Speed = 40,
  62.   LaunchDelay = 0.3,
  63.   MinimumConvergenceSpeed = 20,
  64.   AimPointProportion = 0.5,
  65.   Endurance = 60,
  66.   MinimumCruiseAltitude = 3,
  67.   AttackPatterns = {Vector3(-15,0,0), Vector3(15,0,0)},
  68.   PatternConvergeTime = 1,
  69.   PatternTimeCap = 3,
  70.   DetonateOnCull = true,
  71. }
  72.  
  73. Flag = 0
  74. Normalized = false
  75.  
  76. DefaultSecantInterval = function(ttt) return math.max(math.min(math.ceil(40*ttt/2), 100), 1) end
  77.  
  78. -- Target buffers
  79. Targets = {}
  80. Missiles = {}
  81.  
  82. for i = 0, 5 do
  83.   if WeaponSystems[i] and WeaponSystems[i].Type == 2 then
  84.     DefaultMissileGroup = i
  85.     break
  86.   end
  87. end
  88.  
  89. -- Normalize weapon configuration
  90. function Normalize(I)
  91.   for i = 0, 5 do
  92.     if WeaponSystems[i] then
  93.       local ws = WeaponSystems[i]
  94.       ws.NextFire = -9999
  95.       if ws.AimPointProportion and ws.AimPointProportion > 0 then
  96.         ws.AimPointCounter = 1
  97.       end
  98.       if ws.AttackPatterns then ws.AttackPatternIndex = 1 end
  99.       if not ws.Stagger then ws.Stagger = 0 end
  100.       ws.Fired = 0
  101.       if not ws.VolleySize then ws.VolleySize = 1 end
  102.     end
  103.   end
  104.   for k, tl in pairs(TargetLists) do
  105.     if not tl.Depth then tl.Depth = 1 end
  106.   end
  107. end
  108.  
  109. function NewTarget(I)
  110.   return {
  111.     AimPoints = {},
  112.     Index = 0,
  113.     Wrapped = 0,
  114.     AimPointIndex = 0,
  115.     NumMissiles = 0,
  116.     NumFired = 0,
  117.   }
  118. end
  119.  
  120. function UpdateTargets(I, gameTime)
  121.   -- Find all target locations
  122.   local nmf = I:GetNumberOfMainframes()
  123.   local TargetLocations = {}
  124.  
  125.   -- Aimpoint locations
  126.   for ti = 0, I:GetNumberOfTargets(0) - 1 do
  127.     local t = I:GetTargetInfo(0,ti)
  128.     TargetLocations[t.Id] = {t.AimPointPosition}
  129.   end
  130.  
  131.   -- Non-aimpoint locations
  132.   for mfi = 1, nmf - 1 do
  133.     for ti = 0, I:GetNumberOfTargets(mfi) - 1 do
  134.       local t = I:GetTargetInfo(mfi,ti)
  135.       if TargetLocations[t.Id] and t.AimPointPosition ~= TargetLocations[t.Id][1] then
  136.         table.insert(TargetLocations[t.Id], t.AimPointPosition)
  137.       end
  138.     end
  139.   end
  140.  
  141.   -- Find priority targets
  142.   for tli, tl in pairs(TargetLists) do
  143.     local m = (tl.MainframeIndex < nmf and tl.MainframeIndex) or 0
  144.  
  145.     -- Find qualifying target
  146.     tl.PresentTarget = {}
  147.     local num = 1
  148.     for tInd = 0, I:GetNumberOfTargets(m) - 1 do
  149.        local t = I:GetTargetInfo(m,tInd)
  150.        local speed = Vector3.Magnitude(t.Velocity)
  151.        local interceptPoint = t.Position + t.Velocity * tl.TTT
  152.        if t.Protected and TargetLocations[t.Id]
  153.          and (speed >= tl.MinimumSpeed) and (speed < tl.MaximumSpeed)
  154.          and (Vector3.Distance(I:GetConstructPosition(), interceptPoint) < tl.MaximumRange) then
  155.         local found = false
  156.         for k, p in ipairs(TargetLocations[t.Id]) do
  157.           if (p.y > tl.MinimumAltitude) and (p.y < tl.MaximumAltitude) then
  158.             found = true
  159.             break
  160.           end
  161.         end
  162.         if found then
  163.           tl.PresentTarget[num] = t.Id
  164.           if not Targets[t.Id] then
  165.             Targets[t.Id] = NewTarget(I)
  166.           end
  167.           Targets[t.Id].Flag = Flag
  168.           num = num + 1
  169.           if num > tl.Depth then break end
  170.         end
  171.       end
  172.     end
  173.   end
  174.  
  175.   -- Cull unused targets
  176.   for i, t in pairs(Targets) do
  177.     if t.Flag ~= Flag then
  178.       Targets[i] = nil
  179.     else
  180.       t.NumFired = 0
  181.     end
  182.   end
  183.   for i, m in pairs(Missiles) do
  184.     if m.Flag ~= Flag then
  185.       if Targets[m.Target] then
  186.         Targets[m.Target].NumMissiles = Targets[m.Target].NumMissiles - 1
  187.       end
  188.       Missiles[i] = nil
  189.     end
  190.   end
  191.   Flag = Flag + 1
  192.  
  193.   -- Update target info
  194.   for tInd = 0, I:GetNumberOfTargets(0) - 1 do
  195.     local t = I:GetTargetInfo(0, tInd)
  196.     if Targets[t.Id] then
  197.       if not t.Protected then
  198.         Targets[t.Id] = nil
  199.       else
  200.         local tar = Targets[t.Id]
  201.  
  202.         if tar.Index <= TargetBufferSize then
  203.           tar.Index = tar.Index + 1
  204.         else
  205.           tar.Index = 1
  206.           tar.Wrapped = 1
  207.         end
  208.  
  209.         tar.Velocity = t.Velocity
  210.         tar[tar.Index] = t.Position
  211.         tar.AimPoints = TargetLocations[t.Id]
  212.       end
  213.     end
  214.   end
  215. end
  216.  
  217. -- I -> Position -> Time -> Velocity
  218. function PredictVelocity(I, target, interval)
  219.   -- Calculate the interval to use
  220.   interval = math.min(interval, (target.Wrapped == 0 and (target.Index - 1))
  221.                                 or TargetBufferSize - 1)
  222.  
  223.   local velocity = target.Velocity
  224.   if interval > 0 then
  225.     -- Use secant approximation to smooth
  226.       local oldPos = target[((target.Index - interval) % TargetBufferSize) + 1]
  227.       velocity = (target[target.Index] - oldPos) * (40 / interval)
  228.    end
  229.    return velocity
  230. end
  231.  
  232. function PredictTarget(I, guess, tPos, target, wPos, wSpeed, delay, Interval, minConv)
  233.   local relativePosition = tPos - wPos
  234.   local distance = Vector3.Magnitude(relativePosition)
  235.   local x0
  236.   local fx0
  237.  
  238.   for i = 1, ConvergenceMaxIterations do
  239.     local tVel = PredictVelocity(I, target, Interval(guess))
  240.     predictedDistance = math.sqrt((relativePosition.x + tVel.x*guess)^2
  241.                                   + (relativePosition.y + tVel.y*guess)^2
  242.                                   + (relativePosition.z + tVel.z*guess)^2)
  243.     local new = (predictedDistance / wSpeed) + delay
  244.     if math.abs(new - guess) < ConvergenceIterationThreshold then
  245.       guess = math.min(guess, distance / minConv)
  246.       return tPos + tVel * guess, guess
  247.     end
  248.     local x1 = guess
  249.     local fx1 = guess - new
  250.     if i == 1 then
  251.       local a = math.min(1, wSpeed / Vector3.Magnitude(tVel))
  252.       guess = (a * new + (1-a) * guess);
  253.     else
  254.       guess = x1 - fx1 * (x1 - x0) / (fx1 - fx0)
  255.     end
  256.     x0 = x1
  257.     fx0 = fx1
  258.   end
  259.   -- Fallthrough
  260.   I:Log('Falling through')
  261.   local guess = distance / minConv
  262.   return tPos + PredictVelocity(I, target, Interval(guess)) * guess, guess
  263. end
  264.  
  265. function PredictTarget(I, guess, tPos, target, wPos, wSpeed, delay, Interval, minConv)
  266.   local relativePosition = tPos - wPos
  267.   local distance = Vector3.Magnitude(relativePosition)
  268.  
  269.   for i = 0, ConvergenceMaxIterations do
  270.     local tVel = PredictVelocity(I, target, Interval(guess))
  271.     predictedDistance = math.sqrt((relativePosition.x + tVel.x*guess)^2
  272.                                   + (relativePosition.y + tVel.y*guess)^2
  273.                                   + (relativePosition.z + tVel.z*guess)^2)
  274.     local new = (predictedDistance / wSpeed) + delay
  275.     if math.abs(new - guess) < ConvergenceIterationThreshold then
  276.       return tPos + tVel * guess, guess
  277.     else
  278.       local a = math.min(1, wSpeed / Vector3.Magnitude(tVel))
  279.       guess = (a * new + (1-a) * guess)
  280.     end
  281.   end
  282.   -- Fallthrough
  283.   local guess = distance / minConv
  284.   return tPos + PredictVelocity(I, target, Interval(guess)) * guess, guess
  285. end
  286.  
  287. function FindAimpoint(aps, target, m, ws)
  288.   local aps = Targets[m.Target].AimPoints
  289.   if #aps == 1 then m.AimPointIndex = 1; return end
  290.   local api
  291.   if target.AimPoints[m.AimPointIndex] then
  292.     api = m.AimPointIndex
  293.   elseif ws.AimPointProportion then
  294.     if ws.AimPointCounter >= 1 then
  295.       api = 1
  296.       ws.AimPointCounter = ws.AimPointCounter - 1 + ws.AimPointProportion
  297.     else
  298.       api = target.AimPointIndex + 2
  299.       target.AimPointIndex = (target.AimPointIndex + 1) % (#aps - 1)
  300.       ws.AimPointCounter = ws.AimPointCounter + ws.AimPointProportion
  301.     end
  302.   else
  303.     api = target.AimPointIndex + 1
  304.     target.AimPointIndex = (target.AimPointIndex + 1) % #aps
  305.   end
  306.   local bestErr = 99999
  307.  
  308.   for i = 0, #aps - 1 do
  309.     local api2 = ((api - 1 + i) % (#aps)) + 1
  310.     local candidate = aps[api2]
  311.     if candidate.y < ws.MaximumAltitude then
  312.       if candidate.y > ws.MinimumAltitude then
  313.         m.AimPointIndex = api2
  314.         break
  315.       elseif ws.MinimumAltitude - candidate.y < bestErr then
  316.         m.AimPointIndex = api2
  317.         bestErr = ws.MinimumAltitude - candidate.y
  318.       end
  319.     elseif candidate.y - ws.MinimumAltitude < bestErr then
  320.       m.AimPointIndex = api2
  321.       bestErr = candidate.y - ws.MinimumAltitude
  322.     end
  323.   end
  324. end
  325.  
  326. function AimFireWeapon(I, wi, ti, gameTime, groupFired)
  327.   local w = (ti and I:GetWeaponInfoOnTurretOrSpinner(ti, wi)) or I:GetWeaponInfo(wi)
  328.   if WeaponSystems[w.WeaponSlot] then
  329.     local ws = WeaponSystems[w.WeaponSlot]
  330.     if (groupFired and (ws.Type == 2 and w.WeaponSlot ~= groupFired))
  331.        or (w.WeaponType ~= 4 and ws.Stagger and gameTime < ws.NextFire) then
  332.       return
  333.     end
  334.     local tIndex = nil
  335.     for k, t in ipairs(TargetLists[ws.TargetList].PresentTarget) do
  336.       if not (ws.LimitFire and ws.MissilesPerTarget)
  337.          or Targets[t].NumMissiles + Targets[t].NumFired < ws.MissilesPerTarget then
  338.         tIndex = t
  339.         break
  340.       end
  341.     end
  342.  
  343.     if tIndex and Targets[tIndex] then
  344.       local selfPos = w.GlobalPosition
  345.       if ws.InheritedMovement then
  346.         selfPos = selfPos + I:GetVelocityVector() * ws.InheritedMovement
  347.       end
  348.       local tPos = PredictTarget(I, 0, Targets[tIndex].AimPoints[1], Targets[tIndex], selfPos, ws.Speed,
  349.                                  ws.LaunchDelay, ws.SecantInterval or DefaultSecantInterval,
  350.                                  ws.MinimumConvergenceSpeed)
  351.       local v = Vector3.Normalize(tPos - w.GlobalPosition)
  352.  
  353.       if ti then
  354.         I:AimWeaponInDirectionOnTurretOrSpinner(ti, wi, v.x, v.y, v.z, w.WeaponSlot)
  355.       else
  356.         I:AimWeaponInDirection(wi, v.x, v.y, v.z, w.WeaponSlot)
  357.       end
  358.  
  359.       if w.WeaponType ~= 4 then
  360.         local delayed = ws.Stagger and gameTime < ws.NextFire
  361.         if not delayed and Vector3.Distance(w.GlobalPosition, tPos) < ws.MaximumRange
  362.            and I:Maths_AngleBetweenVectors(w.CurrentDirection, v) < ws.FiringAngle then
  363.           local fired = (ti and I:FireWeaponOnTurretOrSpinner(ti, wi, w.WeaponSlot))
  364.                         or I:FireWeapon(wi, w.WeaponSlot)
  365.           if fired then
  366.             if ws.Stagger then
  367.               ws.Fired = ws.Fired + 1
  368.               if ws.Fired >= ws.VolleySize then
  369.                 ws.NextFire = gameTime + ws.Stagger
  370.                 ws.Fired = 0
  371.               end
  372.             end
  373.             Targets[tIndex].NumFired = Targets[tIndex].NumFired + 1
  374.             groupFired = w.WeaponSlot
  375.           end
  376.         end
  377.       end
  378.     end
  379.   end
  380.   return groupFired
  381. end
  382.  
  383. function GuideMissile(I, ti, mi, gameTime, groupFired)
  384.   local mInfo = I:GetLuaControlledMissileInfo(ti, mi)
  385.   if I:IsLuaControlledMissileAnInterceptor(ti,mi) then
  386.     return
  387.   end
  388.   if not Missiles[mInfo.Id] then
  389.     Missiles[mInfo.Id] = { Flag = Flag, Group = (groupFired or DefaultMissileGroup) }
  390.     local ws = WeaponSystems[Missiles[mInfo.Id].Group]
  391.     if ws.AttackPatterns then
  392.       Missiles[mInfo.Id].AttackPattern = ws.AttackPatterns[ws.AttackPatternIndex]
  393.       ws.AttackPatternIndex = (ws.AttackPatternIndex % #ws.AttackPatterns) + 1
  394.     end
  395.   else
  396.     Missiles[mInfo.Id].Flag = Flag
  397.   end
  398.   local m = Missiles[mInfo.Id]
  399.   local ws = WeaponSystems[m.Group]
  400.   if mInfo.TimeSinceLaunch > ws.Endurance then
  401.     if ws.DetonateOnCull then I:DetonateLuaControlledMissile(ti,mi) end
  402.   else
  403.     if m.Target == nil or Targets[m.Target] == nil then
  404.       m.TTT = nil
  405.       local best = 99999
  406.       local bestIndex = 1
  407.       for k, t in ipairs(TargetLists[ws.TargetList].PresentTarget) do
  408.         if not ws.MissilesPerTarget or Targets[t].NumMissiles < ws.MissilesPerTarget then
  409.           bestIndex = t
  410.           break
  411.         else
  412.           if Targets[t].NumMissiles < best then
  413.             best = Targets[t].NumMissiles
  414.             bestIndex = t
  415.           end
  416.         end
  417.       end
  418.  
  419.       m.Target = bestIndex
  420.       if Targets[m.Target] then
  421.         Targets[m.Target].NumMissiles = Targets[m.Target].NumMissiles + 1
  422.       end
  423.     end
  424.  
  425.     local target = Targets[m.Target]
  426.     if target then
  427.       target.Flag = Flag
  428.       if m.ResetTime and gameTime > m.ResetTime
  429.          or not target.AimPoints[m.AimPointIndex] then
  430.         FindAimpoint(Targets[m.Target].AimPoints, target, m, ws)
  431.         m.ResetTime = gameTime + 0.25
  432.       end
  433.  
  434.       local aimPoint = (m.AimPointIndex and target.AimPoints[m.AimPointIndex]) or target.AimPoints[1]
  435.  
  436.       if ws.ProxRadius and Vector3.Distance(aimPoint, mInfo.Position) < ws.ProxRadius then
  437.         I:DetonateLuaControlledMissile(ti,mi)
  438.         return
  439.       end
  440.  
  441.       local mSpeed = math.max(Vector3.Magnitude(mInfo.Velocity), ws.Speed)
  442.       local tPos, ttt = PredictTarget(I, m.TTT or 0, aimPoint, target, mInfo.Position, mSpeed, 0,
  443.                                       ws.SecantInterval or DefaultSecantInterval,
  444.                                       ws.MinimumConvergenceSpeed)
  445.       m.TTT = ttt - 1/40
  446.       if ttt < 1 then m.ResetTime = gameTime end
  447.       local floor = false
  448.       if Vector3.Distance(mInfo.Position, tPos) > 1.2 * (mInfo.Position.y - tPos.y) + 50  then
  449.         tPos.y = math.max(tPos.y, ws.MinimumCruiseAltitude)
  450.         floor = true
  451.       end
  452.       if m.AttackPattern then
  453.         local tttAdj = m.TTTadj or ttt
  454.         local q = Quaternion.LookRotation(tPos - mInfo.Position, Vector3(0,1,0))
  455.         local v = m.AttackPattern * math.min(math.max(0, tttAdj - ws.PatternConvergeTime), ws.PatternTimeCap)
  456.         tPos = tPos + q*v
  457.         if floor then tPos.y = math.max(tPos.y, ws.MinimumCruiseAltitude) end
  458.         m.TTTadj = Vector3.Distance(tPos, mInfo.Position) / mSpeed
  459.       end
  460.       I:SetLuaControlledMissileAimPoint(ti, mi, tPos.x, tPos.y,tPos.z)
  461.     end
  462.   end
  463. end
  464.  
  465. function Update(I)
  466.   I:ClearLogs()
  467.   if not Normalized then
  468.     Normalize(I)
  469.     Normalized = true
  470.   end
  471.   local gameTime = I:GetTime()
  472.  
  473.   UpdateTargets(I, gameTime)
  474.  
  475.   local groupFired
  476.   -- Aim and fire
  477.   for wi = 0, I:GetWeaponCount() - 1 do
  478.     groupFired = AimFireWeapon(I, wi, nil, gameTime, groupFired)
  479.   end
  480.   for ti = 0, I:GetTurretSpinnerCount() - 1 do
  481.     for wi = 0, I:GetWeaponCountOnTurretOrSpinner(ti) - 1 do
  482.       groupFired = AimFireWeapon(I, wi, ti, gameTime, groupFired)
  483.     end
  484.   end
  485.  
  486.   -- Guide missiles
  487.   for ti = 0, I:GetLuaTransceiverCount() - 1 do
  488.     for mi = 0, I:GetLuaControlledMissileCount(ti) - 1 do
  489.       GuideMissile(I, ti, mi, gameTime)
  490.     end
  491.   end
  492. end
Advertisement
Add Comment
Please, Sign In to add comment