Skip to content

Commit 240f75e

Browse files
authored
Merge pull request #183 from cortex-command-community/AA-drone
Added AA drone
2 parents 83a6cd5 + 1befb0f commit 240f75e

26 files changed

+1264
-0
lines changed

Data/Base.rte/Actors.ini

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,7 @@ IncludeFile = Base.rte/Actors/Infantry/GreenDummy/GreenDummy.ini
2424

2525
IncludeFile = Base.rte/Actors/Mecha/Medic/MedicDrone.ini
2626
IncludeFile = Base.rte/Actors/Mecha/AIBox/AIBox.ini
27+
IncludeFile = Base.rte/Actors/Mecha/AADrone/Drone.ini
2728

2829

2930
// Wildlife
Lines changed: 292 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,292 @@
1+
dofile("Base.rte/Constants.lua")
2+
require("AI/NativeCrabAI")
3+
4+
function Create(self)
5+
self.AI = NativeCrabAI:Create(self)
6+
self.SearchTimer = Timer()
7+
self.radarRange = 1024
8+
self.ValidTargets = {}
9+
self.Frame = 1
10+
11+
-- The "AA-Drone Ammo Counter" object tracks ammo across battles and reduce gold cost of the carrier
12+
if self:HasObject("AA-Drone Ammo Counter") then
13+
if self.InventorySize == 1 then
14+
self.Frame = 2 -- One SAM left
15+
else
16+
self.Frame = 0 -- Out of ammo
17+
end
18+
end
19+
20+
function self.UpdateInertSAM(self)
21+
if self.SearchTimer:IsPastSimMS(300) then
22+
local ArmedSAM = CreateAEmitter("Armed SAM", "Base.rte")
23+
if ArmedSAM then
24+
self.SAM.ToDelete = true
25+
self.SAM.HitsMOs = false
26+
self.SAM.GetsHitByMOs = false
27+
28+
ArmedSAM.Pos = self.SAM.Pos
29+
ArmedSAM.Vel = self.SAM.Vel
30+
ArmedSAM.Team = self.SAM.Team
31+
ArmedSAM.IgnoresTeamHits = true
32+
ArmedSAM.RotAngle = self.SAM.RotAngle
33+
ArmedSAM.AngularVel = self.SAM.AngularVel
34+
35+
self.proximityFuze = nil -- Reset the proximity fuze
36+
37+
-- Don't target the center of a craft
38+
if self.SAM_Target.ClassName == "ACDropShip" then
39+
if self.SAM_Target.Pos.X > self.SAM.Pos.X then
40+
self.SAM_Offset = Vector(-self.SAM_Target.Radius, 0) -- Left
41+
else
42+
self.SAM_Offset = Vector(self.SAM_Target.Radius, 0) -- Right
43+
end
44+
else
45+
self.SAM_Offset = Vector(0, self.SAM_Target.Radius * 0.5) -- Below
46+
end
47+
48+
-- Initialize missile velocity history and targeting data
49+
self.SAM_AimPos = self.SAM_Target.Pos + self.SAM_Target:RotateOffset(self.SAM_Offset)
50+
self.SAM_LastVel = Vector(self.SAM.Vel.X, self.SAM.Vel.Y)
51+
52+
self.SAM = ArmedSAM
53+
MovableMan:AddMO(self.SAM)
54+
55+
self.UpdateSAM = self.UpdateArmedSAM -- Use the UpdateArmedSAM-function from now on
56+
else
57+
self.SAM = nil
58+
end
59+
else
60+
local angError = math.asin(Vector(0, -1):Cross(self.SAM:RotateOffset(Vector(1, 0)))) -- The angle between missile facing and straight up
61+
self.SAM.RotAngle = self.SAM.RotAngle + math.min(math.max(angError, -0.02), 0.02)
62+
self.SAM.AngularVel = self.SAM.AngularVel * 0.95
63+
end
64+
end
65+
66+
function self.UpdateArmedSAM(self)
67+
-- Find the velocity vector that will take the missile to the target
68+
local FutureVel = self.SAM.Vel + (self.SAM.Vel - self.SAM_LastVel) * 10
69+
local OptimalVel = SceneMan:ShortestDistance(self.SAM.Pos, self.SAM_AimPos, false)
70+
local angError = math.asin(OptimalVel.Normalized:Cross(FutureVel.Normalized)) -- The angle between FutureVel and OptimalVel
71+
72+
-- Gradually turn towards the optimal velocity vector
73+
self.SAM.RotAngle = self.SAM.RotAngle + math.min(math.max(angError, -0.04), 0.04)
74+
75+
-- Gradually return the thruster to the starting position if the missile is facing the target
76+
if math.abs(angError) < 0.15 then
77+
self.SAM.EmitAngle = self.SAM.EmitAngle * 0.8 + math.pi * 0.2
78+
else
79+
self.SAM.EmitAngle = math.max(math.min(self.SAM.EmitAngle + angError * 0.1, 4.14), 2.14) -- Vector thrust
80+
end
81+
82+
-- Detonate the missile when appropriate
83+
local range = SceneMan:ShortestDistance(
84+
self.SAM.Pos,
85+
self.SAM_Target.Pos + self.SAM_Target:RotateOffset(self.SAM_Offset),
86+
false
87+
).Magnitude
88+
if self.proximityFuze then
89+
if range < 30 then
90+
self.SAM:GibThis() -- The target is close enough; detonate
91+
elseif math.abs(angError) > 1.5 and range > self.proximityFuze then -- The missile is moving away from the target: detonate
92+
self.SAM:GibThis()
93+
else
94+
self.proximityFuze = range
95+
end
96+
elseif range < 120 then -- The target is close: arm the proximity fuze
97+
self.proximityFuze = range
98+
end
99+
100+
self.SAM.AngularVel = self.SAM.AngularVel * 0.96 + (self.SAM.EmitAngle - math.pi) * 0.05 -- The vector thrust will cause the SAM to rotate
101+
self.SAM_LastVel = self.SAM_LastVel * 0.6 + self.SAM.Vel * 0.4 -- Used to calculate the acceleration of the missile
102+
self.SAM_AimPos = self.SAM_AimPos * 0.6
103+
+ (
104+
self.SAM_Target.Pos
105+
+ self.SAM_Target:RotateOffset(self.SAM_Offset)
106+
+ self.SAM_Target.Vel * math.min(range / 50, 20)
107+
)
108+
* 0.4 -- Filter the AimPos to reduce noise
109+
end
110+
end
111+
112+
function Update(self)
113+
if self.SAM then -- Check if any old missile is alive
114+
if MovableMan:ValidMO(self.SAM) then
115+
if self.SAM_Target and MovableMan:ValidMO(self.SAM_Target) then
116+
self.UpdateSAM(self)
117+
else
118+
-- The target is not valid any more: replace the missile with an intert one
119+
if self.SAM.PresetName == "Armed SAM" then
120+
local InertSAM = CreateAEmitter("Inert SAM", "Base.rte")
121+
if InertSAM then
122+
self.SAM.ToDelete = true
123+
self.SAM.HitsMOs = false
124+
self.SAM.GetsHitByMOs = false
125+
126+
InertSAM.Pos = self.SAM.Pos
127+
InertSAM.Vel = self.SAM.Vel
128+
InertSAM.RotAngle = self.SAM.RotAngle
129+
InertSAM.AngularVel = self.SAM.AngularVel
130+
InertSAM.Team = self.Team
131+
InertSAM.IgnoresTeamHits = true
132+
MovableMan:AddMO(InertSAM)
133+
end
134+
end
135+
136+
self.SAM = nil
137+
self.SAM_Target = nil
138+
end
139+
else
140+
self.SAM = nil
141+
end
142+
elseif self.Frame > 0 and self.Vel.Largest < 12 then -- we have SAMs left
143+
if #self.ValidTargets < 1 then -- Find valid targets
144+
if self.SearchTimer:IsPastSimMS(100) then
145+
self.SearchTimer:Reset() -- Only search a few times/sec to reduce calculations per update
146+
147+
-- Only look for targets if there are no obstacles above us
148+
local Trace = self:RotateOffset(Vector(0, -200))
149+
if not SceneMan:CastStrengthRay(self.Pos, Trace, 5, Vector(), 9, -1, true) then -- Terrain str 5
150+
local obstructed = false
151+
local ID = SceneMan:CastMORay(self.AboveHUDPos, Trace, self.ID, self.IgnoresWhichTeam, 0, true, 15)
152+
if ID < rte.NoMOID then
153+
local MO = MovableMan:GetMOFromID(ID)
154+
if ID ~= MO.RootID then
155+
MO = MovableMan:GetMOFromID(MO.RootID)
156+
end
157+
158+
if MO.Team == self.Team then
159+
obstructed = true -- The MO above us is on our team: don't shoot
160+
end
161+
end
162+
163+
if not obstructed then
164+
local Dist, range, angle
165+
for Act in MovableMan.Actors do
166+
if Act.Team ~= self.Team and not Act:IsDead() and Act:HasObjectInGroup("Craft") then
167+
Dist = SceneMan:ShortestDistance(self.Pos, Act.Pos + Act.Vel * 9, false)
168+
if
169+
Act.Vel.Y > 0 or ((Dist.X > 0 and Act.Vel.X < -5) or (Dist.X < 0 and Act.Vel.X > 5))
170+
then -- Only shoot at craft moving down, or moving towards us
171+
range = Dist.Magnitude - Act.Radius
172+
if range < self.radarRange then -- Shoot at enemy craft within radarRange pixels
173+
angle = math.abs(
174+
math.asin(Dist.Normalized:Cross(self:RotateOffset(Vector(0, -1))))
175+
)
176+
if angle < 1.7 then -- Search in a ~200 degree arc above us
177+
table.insert(
178+
self.ValidTargets,
179+
{
180+
Actor = Act,
181+
priority = angle / 3
182+
+ range / 300
183+
+ (3 - Act.Health / 100)
184+
- math.abs(Act.AngularVel),
185+
}
186+
) -- prioritize close, damaged targets that are straight above us that does not spin
187+
end
188+
end
189+
end
190+
end
191+
end
192+
end
193+
end
194+
end
195+
196+
-- Sort the targets in ascending order
197+
if #self.ValidTargets > 1 then
198+
table.sort(self.ValidTargets, function(A, B)
199+
return A.priority > B.priority
200+
end)
201+
end
202+
203+
-- Store brain location
204+
local GmActiv = ActivityMan:GetActivity()
205+
for player = Activity.PLAYER_1, Activity.MAXPLAYERCOUNT - 1 do
206+
if GmActiv:PlayerActive(player) and GmActiv:GetTeamOfPlayer(player) == self.Team then
207+
local Brain = GmActiv:GetPlayerBrain(player)
208+
if Brain and MovableMan:IsActor(Brain) then
209+
self.MyBrainPos = Vector(Brain.Pos.X, Brain.Pos.Y)
210+
end
211+
212+
break
213+
end
214+
end
215+
else -- Check if the missile have a clear line of sight to any of the selected targets
216+
local NewTarget = table.remove(self.ValidTargets).Actor -- Only check one target to reduce calculations per update
217+
if NewTarget and MovableMan:ValidMO(NewTarget) and not NewTarget:IsDead() then
218+
local Trace = SceneMan:ShortestDistance(self.AboveHUDPos, NewTarget.Pos, false)
219+
-- Don't shoot at targets that are out of reach
220+
if Trace.Magnitude < self.radarRange then
221+
-- Don't shoot at targets that are very close to the brain
222+
if
223+
SceneMan:ShortestDistance(self.MyBrainPos or self.Pos, NewTarget.Pos, false).Largest
224+
- NewTarget.Radius
225+
> 120
226+
then
227+
-- First do a very inexact scan of half the distance to the target for friendly dropships and terrain
228+
if
229+
SceneMan:CastObstacleRay(
230+
self.AboveHUDPos,
231+
Trace * 0.5,
232+
Vector(),
233+
Vector(),
234+
self.ID,
235+
self.IgnoresWhichTeam,
236+
0,
237+
25
238+
) < 0
239+
then
240+
-- If nothing was found, do a more exact scan for terrain all the way to the target
241+
if not SceneMan:CastStrengthRay(self.AboveHUDPos, Trace, 5, Vector(), 9, -1, true) then -- Terrain str 5
242+
self.SearchTimer:Reset()
243+
244+
-- Spawn the SAM
245+
self.SAM = CreateAEmitter("Inert SAM", "Base.rte")
246+
if self.SAM then
247+
local SpawnOffset = Vector(0, -15)
248+
if self.Frame < 2 then
249+
self.Frame = 2 -- Remove the left SAM
250+
SpawnOffset.X = -10
251+
else
252+
self.Frame = 0 -- Remove the right SAM
253+
SpawnOffset.X = 10
254+
end
255+
256+
self.SAM.Team = self.Team
257+
self.SAM.RotAngle = self.RotAngle + 1.571
258+
self.SAM.AngularVel = self.AngularVel
259+
self.SAM.Pos = self.Pos + self:RotateOffset(SpawnOffset)
260+
self.SAM.Vel = self.Vel + self:RotateOffset(Vector(0, -17))
261+
self.SAM.IgnoresTeamHits = true
262+
self.SAM:TriggerBurst()
263+
MovableMan:AddMO(self.SAM)
264+
265+
self.armedSAM = false
266+
self.SAM_Target = NewTarget
267+
268+
-- Call this function to update the missile
269+
self.UpdateSAM = self.UpdateInertSAM
270+
end
271+
272+
-- Add an invisible object to the inventory to track ammo
273+
local AmmoCounter = CreateMOSRotating("AA-Drone Ammo Counter", "Base.rte")
274+
if AmmoCounter then
275+
self:AddInventoryItem(AmmoCounter)
276+
end
277+
end
278+
end
279+
end
280+
end
281+
end
282+
end
283+
end
284+
end
285+
286+
function UpdateAI(self)
287+
self.AI:Update(self)
288+
end
289+
290+
function Destroy(self)
291+
self.AI:Destroy(self)
292+
end
1.01 KB
Loading
1.11 KB
Loading
1.08 KB
Loading
1.01 KB
Loading

0 commit comments

Comments
 (0)