1--[[
2   Copyright 2007-2020 The OpenRA Developers (see AUTHORS)
3   This file is part of OpenRA, which is free software. It is made
4   available to you under the terms of the GNU General Public License
5   as published by the Free Software Foundation, either version 3 of
6   the License, or (at your option) any later version. For more
7   information, see COPYING.
8]]
9
10AIPlayers = { }
11AIBarracks = { }
12AIDerricks = { }
13AIBaseLocation = { }
14IdlingAIActors = { }
15IdlingAISupportActors = { }
16AIOnDefense = { }
17
18-- It's good to start with 10 rifle man, one medic and 5 rocket soldiers
19InitialUnitsToBuild = { "e1", "e1", "e1", "e1", "e1", "medi", "e1", "e1", "e1", "e1", "e1", "e3", "e3", "e3", "e3", "e3" }
20UnitsToBuild = { "e1", "e1", "e1", "e1", "e1", "e3", "e3", "e3", "medi" }
21
22ActivateAI = function(player, id)
23	AIPlayers[id] = player
24
25	Trigger.AfterDelay(0, function()
26		local barracks = player.GetActorsByType("tent")
27		if #barracks > 0 then
28			AIBarracks[id] = barracks[1]
29			AIBaseLocation[id] = barracks[1].Location + CVec.New(2, 1)
30			IdlingAIActors[id] = { }
31			IdlingAISupportActors[id] = { }
32			InitialInfantryProduction(id, InitialUnitsToBuild)
33			DefendActor(id, barracks[1])
34			RepairBarracks(id)
35			SellWalls(id)
36
37			Trigger.AfterDelay(DateTime.Seconds(10), function() LookOutForCrates(id) end)
38		end
39
40		local derricks = player.GetActorsByType("oilb")
41		if #derricks > 0 then
42			AIDerricks[id] = derricks[1]
43			DefendActor(id, derricks[1])
44		end
45	end)
46end
47
48InitialInfantryProduction = function(id, units)
49	local productionComplete = AIPlayers[id].Build(units, function(actors)
50		InfantryProduction(id)
51	end)
52
53	Trigger.OnProduction(AIBarracks[id], function(producer, produced)
54		BuildComplete(id, produced)
55	end)
56end
57
58InfantryProduction = function(id)
59	local productionComplete = AIPlayers[id].Build({ Utils.Random(UnitsToBuild) }, function(actors)
60		Trigger.AfterDelay(0, function() InfantryProduction(id) end)
61	end)
62
63	if not productionComplete then
64		Trigger.AfterDelay(0, function() InfantryProduction(id) end)
65	end
66end
67
68BuildComplete = function(id, actor)
69	if actor.Type == "medi" then
70		local number = #IdlingAISupportActors[id] + 1
71		IdlingAISupportActors[id][number] = actor
72
73		Trigger.OnKilled(actor, function()
74			table.remove(IdlingAISupportActors[id], number)
75		end)
76	else
77		local number = #IdlingAIActors[id] + 1
78		IdlingAIActors[id][number] = actor
79
80		Trigger.OnKilled(actor, function()
81			table.remove(IdlingAIActors[id], number)
82		end)
83	end
84
85	Trigger.AfterDelay(0, function() DefendActor(id, actor) end)
86end
87
88AttackGroupSize = 5
89SetupAttackGroup = function(id)
90	local units = { }
91
92	for i = 0, AttackGroupSize, 1 do
93		if (#IdlingAIActors[id] == 0) then
94			return units
95		end
96
97		local number = Utils.RandomInteger(0, #IdlingAIActors[id]) + 1
98		units[#units + 1] = IdlingAIActors[id][number]
99		table.remove(IdlingAIActors[id], number)
100	end
101
102	return units
103end
104
105DefendActor = function(id, actorToDefend)
106	if not actorToDefend or actorToDefend.IsDead or not actorToDefend.IsInWorld then
107		return
108	end
109
110	Trigger.OnDamaged(actorToDefend, function(self, attacker)
111		if AIOnDefense[id] or not attacker or attacker.IsDead then
112			return
113		end
114
115		-- Don't try to kill something you can't kill
116		if attacker.Type == "sniper.soviets" then
117			return
118		end
119
120		AIOnDefense[id] = true
121		local attackLoc = attacker.Location
122
123		local defenders = SetupAttackGroup(id)
124		if not defenders or #defenders == 0 then
125			Trigger.AfterDelay(DateTime.Seconds(30), function() AIOnDefense[id] = false end)
126			return
127		end
128
129		Utils.Do(defenders, function(unit)
130			if unit.IsDead then
131				return
132			end
133
134			unit.AttackMove(attackLoc)
135
136			local home = AIBaseLocation[id]
137			Trigger.OnIdle(unit, function()
138				if unit.Location == home then
139					IdlingAIActors[id][#IdlingAIActors[id] + 1] = unit
140					Trigger.Clear(unit, "OnIdle")
141					AIOnDefense[id] = false
142				else
143					unit.AttackMove(home)
144				end
145			end)
146		end)
147	end)
148end
149
150RepairBarracks = function(id)
151	Trigger.OnDamaged(AIBarracks[id], function(self, attacker)
152		self.StartBuildingRepairs(AIPlayers[id])
153	end)
154end
155
156SellWalls = function(id)
157	Media.DisplayMessage("Lonestar AI " .. id .. " sold its walls for better combat experience.")
158
159	local walls = AIPlayers[id].GetActorsByType("brik")
160	Utils.Do(walls, function(wall)
161		wall.Sell()
162	end)
163end
164
165LookOutForCrates = function(id)
166	Trigger.OnEnteredProximityTrigger(AIBarracks[id].CenterPosition, WDist.New(12 * 1024), function(actor)
167		if actor.Type ~= "fortcrate" or #IdlingAIActors[id] == 0 then
168			return
169		end
170
171		local unit = Utils.Random(IdlingAIActors[id])
172		local home = AIBaseLocation[id]
173		local aim = actor.Location
174		if unit.IsDead then
175			return
176		end
177
178		unit.AttackMove(aim)
179		Trigger.OnIdle(unit, function()
180			if unit.Location == aim or not actor.IsInWorld then
181				unit.AttackMove(home)
182				Trigger.Clear(unit, "OnIdle")
183			else
184				unit.AttackMove(aim)
185			end
186		end)
187	end)
188end
189