Code ready:
----------------------------------------
-- File: 'maraudertactic.ai'
-- Created by Gambit @ 02.11.2019
class 'MarauderTactic' (GuardVehicleTactic)
Marauder = {}
function MarauderTactic:__init( squad_ai ) super( squad_ai )
self:SetName("Marauder Tactic")
-- Modifiable Stats
Marauder.G_Time_Between_Successive_Attacks = 0 -- If multiple bombers, do not have them attack simultaneously. Keep lower than 6, if enabled.
Marauder.G_Proximity_Return_Distance = 38 -- The distance from base that we deem minimum to consider the flyer is "back to base".
Marauder.G_Max_Attacking_Range_From_Base = 280 -- The max range OF THE ENEMY, that the flyer will attempt a fly-over.
Marauder.G_AttackInfantry = true -- Self-explanatory.
Marauder.G_AttackVehicles = true -- Self-explanatory. If both true, target will be chosen randomly.
Marauder.G_AttackInfiltratedUnits = false -- Self-explanatory.
Marauder.G_AttackOnlyUnitsWeCanSee = false -- Self-explanatory. Experiential. Better keep it to [false].
Marauder.G_AttackOnlyUnitsWeCanSeeRange = 35 -- If previous is true, this is the detecting range (proximity) of our nearby troops.
Marauder.G_HQ_Name = "guard_hq" -- The name of the HQ of the player. It is the AE name. No need to change, for Guard.
-- Other Stats
self.initialPosition = self.squad_ai:GetPosition()
Marauder.G_Proximity_Return_Distance_Sqr = Marauder.G_Proximity_Return_Distance * Marauder.G_Proximity_Return_Distance
Marauder.G_Proximity_Return_Distance_Triangulation = Marauder.G_Proximity_Return_Distance * 0.6
Marauder.G_NextAttackTMR = g_iGMT
if Marauder.G_Update_HQsTMR == nil then
Marauder.G_Update_HQsTMR = g_iGMT
end
if Marauder.G_Player_HQsPositions == nil then
Marauder.G_Player_HQsPositions = {}
end
end
function MarauderTactic:InitAbilities()
--[[ Init ability ID's / ABILITIES NO LONGER USED!
if Marauder.smoke_id == nil then
Marauder.smoke_id = cpu_manager.stats:GetAbilityID( "guard_smoke_bombs" )
Marauder.krak_id = cpu_manager.stats:GetAbilityID( "guard_krak_bombs" )
Marauder.incendiary_id = cpu_manager.stats:GetAbilityID( "guard_incendiary_bombs" )
end]]
end
function MarauderTactic:DoAbilities()
-- First, update HQs positions every 8 secs
if g_iGMT > Marauder.G_Update_HQsTMR + 8 then
Marauder.G_Update_HQsTMR = g_iGMT
self:UpdateHQs()
end
-- Now check if we must return (after an attack), or we are at a base
self.initialPosition = self.squad_ai:GetPosition()
local must_retrun = true
for i = 1, table.getn(Marauder.G_Player_HQsPositions) do
if distance_sqr(Marauder.G_Player_HQsPositions[i],self.initialPosition) < Marauder.G_Proximity_Return_Distance_Sqr then
must_retrun = false
break
end
end
if self.squad_ai:CanJump() then
-- In case we are away from the base, return to a random valid place (HQ)
if must_retrun then
local all_bases = table.getn(Marauder.G_Player_HQsPositions)
if all_bases > 0 then
local iBasePos = Marauder.G_Player_HQsPositions[math.random(1,all_bases)]
self:ForceSquadJumpNearBack(iBasePos)
end
-- We are at base. We must try to perform an attack
else
if g_iGMT < Marauder.G_NextAttackTMR + Marauder.G_Time_Between_Successive_Attacks then
return
end
local iEnemySquadInf = nil
local iEnemySquadVeh = nil
local iEnemySquad = nil
if Marauder.G_AttackInfantry then
iEnemySquadInf = Ability.Filters.CloseInfantryEnemy(self.initialPosition, Marauder.G_Max_Attacking_Range_From_Base, 5)
--cpu_manager.cpu_player:FindFirstInfantryEnemy(self.initialPosition, Marauder.G_Max_Attacking_Range_From_Base, 5)
end
if Marauder.G_AttackVehicles then
iEnemySquadVeh = Ability.Filters.CloseVehicleEnemy(self.initialPosition, Marauder.G_Max_Attacking_Range_From_Base, 1)
--cpu_manager.cpu_player:FindFirstVehicleEnemy(self.initialPosition, Marauder.G_Max_Attacking_Range_From_Base, 1)
end
if iEnemySquadInf ~= nil and iEnemySquadVeh ~= nil then
if math.random(1,2) == 1 then
iEnemySquad = iEnemySquadInf
else
iEnemySquad = iEnemySquadVeh
end
elseif iEnemySquadInf ~= nil and iEnemySquadVeh == nil then
iEnemySquad = iEnemySquadInf
else
iEnemySquad = iEnemySquadVeh
end
if iEnemySquad ~= nil then
if Marauder.G_AttackInfiltratedUnits or (not iEnemySquad:IsInfiltrating()) then
local iEnemyPos = iEnemySquad:GetPosition()
if (not Marauder.G_AttackOnlyUnitsWeCanSee) or self:WeCanSeePos(iEnemyPos) then
-- Do NOT perform a flyover, if we have our troops nearby!
if cpu_manager.cpu_player:FindFirstHurtSquad( iEnemyPos, 6 ) == nil then
self:ForceSquadAttackJumpNear(iEnemyPos)
Marauder.G_NextAttackTMR = g_iGMT
end
end
end
end
end
end
--[[ Check if we can deploy smoke / ABILITIES NO LONGER USED!
if (self.squad_ai:CanDoAbility(Marauder.smoke_id)) then
-- Search a squad
local iRange = self.squad_ai:GetAbilityRange(Marauder.smoke_id)
local oUnit = Ability.Filters.CloseHurt(self.squad_ai:GetPosition(), iRange, 1)
if (oUnit ~= nil and oUnit:IsInCombat() and cpu_manager:GetUnitStrength(oUnit) > 150) then
self.squad_ai:DoSpecialAbilitySquad(Marauder.smoke_id, oUnit:GetSquad())
end
end
-- Check if we're in close combat - Krak
local oEnemySquad = Ability.Filters.CloseVehicleEnemy(self.squad_ai:GetPosition(), 0, 1)
if (oEnemySquad ~= nil) then
-- Check if we can drop Krak Bombs
if (self.squad_ai:CanDoAbility(Marauder.krak_id)) then
self.squad_ai:DoSpecialAbility(Marauder.krak_id)
end
end
-- Check if we're in close combat - Incendiary
oEnemySquad = Ability.Filters.CloseInfantryEnemy(self.squad_ai:GetPosition(), 0, 5)
if (oEnemySquad ~= nil and not oEnemySquad:IsBroken()) then
-- Check if we can drop Incendiary Bombs
if (self.squad_ai:CanDoAbility(Marauder.incendiary_id)) then
self.squad_ai:DoSpecialAbility(Marauder.incendiary_id)
end
end]]
--[[ Checks jump-able stuck squads, and force them to jump nearby / NO LONGER USED!
if self.squad_ai:CanJump() then
self:SolveStuckCase()
end]]
end
function MarauderTactic:UpdateHQs()
Marauder.G_Player_HQsPositions = {}
for oBuilding in military_manager:GetBases() do
if (oBuilding:IsValid() and oBuilding:GetBaseName() == Marauder.G_HQ_Name) then
table.insert(Marauder.G_Player_HQsPositions,oBuilding:GetPosition())
end
end
end
-- Unstuck Code --------------------------------------------------
function MarauderTactic:SolveStuckCase()
local iPosition = self.squad_ai:GetPosition()
if iPosition.x ~= self.initialPosition.x or iPosition.z ~= self.initialPosition.z then
-- NOT stuck, update previous position and return, we are all good
self.initialPosition = iPosition
return
end
-- If we got here, the squad is NOT moving. See if it is simply waiting, or is stuck!
local state = self.squad_ai:GetTactic():GetState()
if (self.squad_ai:IsInStateMove() or self.squad_ai:IsInStateAttackMove() or state == "Attack") and not self.squad_ai:IsInCombat()
and iPosition.x == self.initialPosition.x and iPosition.z == self.initialPosition.z then
-- STUCK!!!!! Run the unstuck code
self:ForceSquadJumpNear(iPosition)
end
-- Update previous position anyway
self.initialPosition = self.squad_ai:GetPosition()
end
function MarauderTactic:ForceSquadJumpNear(pos)
local iPos = self.squad_ai:GetPosition()
local vJumpPosition = self.squad_ai:GetPosition()
local jumpDist = self.squad_ai:GetJumpDistance()
local jumpDistSqr = jumpDist * jumpDist
local vDir = cpu_manager:GetDirectionToEnemy(pos)
-- First, try an unstuck jump TOWARDS the enemy
-- Try to jump somewhere near, perform 30 checks in total, for a viable position
for i = 1, 12 do
-- Create a jump position
vJumpPosition.x = pos.x + vDir.x * math.random(10, jumpDist)
vJumpPosition.z = pos.z + vDir.z * math.random(10, jumpDist)
-- Check if target position is in range and if unit is able to jump to target position
local iDistanceSqr = distance_sqr(vJumpPosition, iPos)
if iDistanceSqr < jumpDistSqr and self.squad_ai:CanJumpToPosition(vJumpPosition) then
-- Jump to position
self.squad_ai:DoJump(vJumpPosition)
self.last_jump = g_iGMT
self.m_iLastGatherMove = self.last_jump - 10
return
end
end
-- Then try any random nearby place, as a secondary option
for i = 1, 18 do
-- Create a jump position
vJumpPosition.x = pos.x + 0.7 * math.random(-jumpDist, jumpDist)
vJumpPosition.z = pos.z + 0.7 * math.random(-jumpDist, jumpDist)
-- Check if target position is in range and if unit is able to jump to target position
local iDistanceSqr = distance_sqr(vJumpPosition, iPos)
if iDistanceSqr < jumpDistSqr and self.squad_ai:CanJumpToPosition(vJumpPosition) then
-- Jump to position
self.squad_ai:DoJump(vJumpPosition)
self.last_jump = g_iGMT
self.m_iLastGatherMove = self.last_jump - 10
return
end
end
end
function MarauderTactic:ForceSquadJumpNearBack(pos)
local iPos = self.squad_ai:GetPosition()
local vJumpPosition = self.squad_ai:GetPosition()
local jumpDist = self.squad_ai:GetJumpDistance()
local jumpDistSqr = jumpDist * jumpDist
local vDir = cpu_manager:GetDirectionToEnemy(pos)
-- Try to jump somewhere near, perform 15 checks in total, for a viable position, AWAY from the enemy
for i = 1, 15 do
-- Create a jump position
vJumpPosition.x = pos.x - vDir.x * math.random(4, Marauder.G_Proximity_Return_Distance_Triangulation)
vJumpPosition.z = pos.z - vDir.z * math.random(4, Marauder.G_Proximity_Return_Distance_Triangulation)
-- Check if target position is in range and if unit is able to jump to target position
local iDistanceSqr = distance_sqr(vJumpPosition, iPos)
if iDistanceSqr < jumpDistSqr and self.squad_ai:CanJumpToPosition(vJumpPosition) then
-- Jump to position
self.squad_ai:DoJump(vJumpPosition)
--self.last_jump = g_iGMT
--self.m_iLastGatherMove = self.last_jump - 10
return
end
end
end
function MarauderTactic:ForceSquadAttackJumpNear(pos)
local vJumpPosition = self.squad_ai:GetPosition()
local jumpDist = self.squad_ai:GetJumpDistance()
local jumpDistSqr = jumpDist * jumpDist
local ix = 0; local iz = 0;
if sqr(pos.x-vJumpPosition.x) < 0.0001 then
iz = 1
else
local a = (pos.z-vJumpPosition.z)/(pos.x-vJumpPosition.x)
ix = math.sqrt(1/(sqr(a) + 1))
iz = math.abs(a*ix)
end
if vJumpPosition.x > pos.x then ix = -1*ix end
if vJumpPosition.z > pos.z then iz = -1*iz end
-- Try to jump somewhere near, perform 10 checks in total, for a viable position
for i = 1, 10 do
-- Create a jump position
local rndm = math.random(25, 40)
vJumpPosition.x = pos.x + ix*rndm
vJumpPosition.z = pos.z + iz*rndm
-- Check if target position is in range and if unit is able to jump to target position
local iDistanceSqr = distance_sqr(vJumpPosition, self.initialPosition)
if iDistanceSqr < jumpDistSqr and self.squad_ai:CanJumpToPosition(vJumpPosition) then
-- Jump to position
self.squad_ai:DoJump(vJumpPosition)
--self.last_jump = g_iGMT
--self.m_iLastGatherMove = self.last_jump - 10
return
end
end
end
function MarauderTactic:WeCanSeePos(iPos)
local iRangeSqr = Marauder.G_AttackOnlyUnitsWeCanSeeRange * Marauder.G_AttackOnlyUnitsWeCanSeeRange
for oUnit in military_manager:GetSquads() do
if oUnit:IsValid() then
if distance_sqr(oUnit:GetPosition(),iPos) < iRangeSqr then
return true
end
end
end
return false
end
Note that I kept my JumpStuck code (disabled), just in case.
Anyway, it was more complex, in the end. It needed some sneaky above-average maths. My favourite
It works flawlessly for me. I tested it.
The idea is: If enemy nearby, pass over him, and land behind him. Then, return at the back of one of your HQs.
Simple to say, tricky to code.
Downside: The problem is that there is NO FoW detection function. The Fog of War needs some VERY HEAVY functions to work properly.
So I implemented a much faster, but less efficient method.
You can try enabling it (it is not enabled in the code above, but see at the options at the top).
If you see VERY RARE flyovers, then do not change it.
Test, and tell me what you think man.
Let's hope all the functions I used, are also there in DC DoWAI...
-In search of Papasmurf...