The following is a highly useful template and checklist to build your DoW AI from scratch in a well-organized fashion.
PLEASE NOTE: This template/checklist is meant for us with the Dawn of Skirmish Advanced AI v1.2 Mod as it contains some custom scripts not found in the regular commercial AI.
import( 'Strategies/<modname>BuildBaseStrategy.ai' )
import( 'Tactics/<modname>/<unitname>Tactic.ai' )
function CpuManager:Initialize()
self.player_stats:GetPlayerRaceName() == "<mod_race>" or
self.player_stats:GetPlayerRaceName() == "<mod_race>" or
function CpuManager:AddStrategy( strategy_name, priority )
elseif self.player_stats:GetPlayerRaceName() == "<mod_race>" then
strategy = <modname>BuildBaseStrategy( BuildBaseStrategyInfo )
function CpuManager:ForceAttack( player_id )
AttackStrategyInfo.<mod_race>.stop_attack_rating = -INT_MAX
AttackStrategyInfo.<mod_race>.prefer_player = player_id
function CpuManager:CreateTactic( squad_ai )
if squad_ai:IsEngineer() then
elseif stats:GetSquadName() == "<mod's builder unit>" then
return <unitname>Tactic( squad_ai )
if class == UnitStatsAI.UC_LightInfantryLow or
class == UnitStatsAI.UC_LightInfantryMed or
class == UnitStatsAI.UC_LightInfantryHigh then
elseif squad_name == "<mod's light infantry squad>" then
tactic = <unitname>Tactic( squad_ai )
elseif class == UnitStatsAI.UC_HeavyInfantryMed or
class == UnitStatsAI.UC_HeavyInfantryHigh then
elseif squad_name == "<mod's heavy infantry squad>" then
tactic = <unitname>Tactic( squad_ai )
elseif class == UnitStatsAI.UC_Commander then
elseif squad_name == "<mod's commander unit>" then
tactic = <unitname>Tactic( squad_ai )
elseif class == UnitStatsAI.UC_VehicleLow or
class == UnitStatsAI.UC_VehicleMed or
class == UnitStatsAI.UC_VehicleHigh then
elseif squad_name == "<mod's vehicle unit>" then
tactic = <unitname>Tactic( squad_ai )
elseif class == UnitStatsAI.UC_MonsterMed or
class == UnitStatsAI.UC_MonsterHigh then
elseif squad_name == "<mod's monster unit>" then
tactic = <unitname>Tactic( squad_ai )
function CpuManager:IsBiggerGenerator( build_id )
elseif build_id == cpu_manager.stats:GetBuildingID( "<your big power generator building>" ) then
return true
function CpuManager:IsGenerator( build_id )
elseif build_id == cpu_manager.stats:GetBuildingID( "<your power generator building>" ) then
return true
function CpuManager:IsTurret( build_id )
elseif build_id == cpu_manager.stats:GetBuildingID( "<your turret building>" ) then
return true
race = "<mod name>_race",
{
name = "<Unit name>",
attack_role = "<R, M, or RM>",
sbp_name = "<SBPS Unit name>",
ebp_name = "<EBPS Unit name>",
class = UnitStatsAI.UC_<UnitClass>,
rating = <insert #>,
potential =
{
{
name = "<weapon name>",
effectiveness = GenerateUnitEffectiveness(0,0,0,0,0,0,0,0,0,0,0),
range = UnitStatsAI.RT_<None, Melee, ShortRanged, or Ranged>,
},
}
},
{
name = "<Unit name>",
attack_role = "<R, M, or RM>",
sbp_name = "<SBPS Unit name>",
ebp_name = "<EBPS Unit name>",
class = UnitStatsAI.UC_<UnitClass>,
rating = <insert #>,
potential =
{
{
name = "<weapon name>",
effectiveness = GenerateUnitEffectiveness(0,0,0,0,0,0,0,0,0,0,0),
range = UnitStatsAI.RT_<None, Melee, ShortRanged, or Ranged>,
},
{
name = "<weapon name>",
effectiveness = GenerateUnitEffectiveness(0,0,0,0,0,0,0,0,0,0,0),
range = UnitStatsAI.RT_<None, Melee, ShortRanged, or Ranged>,
},
}
},
{
class '<race_name>BuildBaseStrategy' (BuildBaseStrategy) function <race_name>BuildBaseStrategy:__init( baseinfo ) super( baseinfo ) function <race_name>BuildBaseStrategy:EvaluateSquadCap() function <race_name>BuildBaseStrategy:DoBuildUnits() --optional! function <race_name>BuildBaseStrategy:DoBuildBuildings() --here only due to AI prereq issues with v1.30 game-code! function <race_name>BuildBaseStrategy:DoSecondaryBuildings() function <race_name>BuildBaseStrategy:IsInSecondTier() function <race_name>BuildBaseStrategy:IsInThirdTier() function <race_name>BuildBaseStrategy:DoUpgradesAndAddons() function <race_name>BuildBaseStrategy:AddOnNotify( addon_id, notify_code , build_channel )
<mod name>_race =
{
--my prorate against yours before I stop attacking you and retreat
stop_attack_rating = -100,
min_units = 2,
},
<mod name>_race =
{
--how much requisition I have before I try to build an uber unit
uber_unit_cost = <insert value, if applicable>,
--minimum amount of power before I build another generator
min_power = 100,
resourcers = 2,
--reserve this amount for building units/buildings (not for use in upgrading/ reinforcing)
req_reserve = 200,
ETA =
{
cost = 250,
},
--attack rating needs to be greater than this prorated to attack
attack_rating = 200,
--need at least 3 at all times
squad_cap_threshold = 3,
--need at least 4 at all times
support_cap_threshold = 4,
--tolerance attacking turrets
turret_tolerance = 150,
--used to calculate wants for engineers
Engineers =
{
--number of engineers we're aiming for
want = 3,
},
Turrets =
{
--limit of turrets
limit = math.random( 6,10 ),
req = 400,
power = 400,
},
ResearchOrder =
--X items
{
},
SquadLimits =
{
standard =
{
},
standard2 =
{
},
function BuildOrderStrategy:__init( info ) super( info )
if map_size == small or closest_enemy < 275 then
aitrace("Map is "..map_size)
aitrace("Closest enemy is "..closest_enemy.."metres away")
elseif cpu_manager.player_stats:GetPlayerRaceName() == "<mod name>_race" then
table.insert ( self.info, { "squad", "<your first-tier infantry unit>" } )Note: Use this file instead of defining individual tactic.ai files when different unit types plan to use the same ability.
-target ability table
InfantryTactic.TargetAbilities =
{ nil, "<infantry anti-infantry weapon ability>", Ability.Filters.CloseInfantryEnemy },
{ nil, "<infantry anti-vehicle weapon ability>", Ability.Filters.CloseVehicleEnemy },
function InfantryTactic:DoAbilities()
--check if I can use <this ability name>
local <ability shortname>_id = cpu_manager.stats:GetAbilityID( "<actual ability name>" )
Ability.DoAbilityArea( self.squad_ai, ability shortname>_id, Ability.Filters.<check filter section below> )
if self.squad_ai:IsAttached() then
<Commander Unit>Tactic.DoAbilities( self )
function InfantryTactic:Update()
--check if I can use <this ability name>
local <ability shortname>_id = cpu_manager.stats:GetAbilityID( "<actual ability name>" )
if self.squad_ai:CanDoAbility( <ability shortname>_id ) then
self.squad_ai:DoSpecialAbility( <ability shortname>_id )
if cpu_manager.assassinate and
( self.squad_ai:HasSquadAttached( "<your commander unit>" ) or
Note: Use this if applicable (you may not have vehicles that require abilities so this is optional)
function VehicleTactic:DoAbilities() --check if I can use <this ability name> local <ability shortname>_id = cpu_manager.stats:GetAbilityID( "<actual ability name>" ) if self.squad_ai:CanDoAbility( <ability shortname>_id ) then self.squad_ai:DoSpecialAbility( <ability shortname>_id )
The following are templates for various types of units that require specific scripts
Purpose: FOR ENGINEER/BUILDER UNITS THAT CAN REINFORCE BUT NOT ATTACK
class '<engineer-unit>Tactic' (EngineerTactic) function <engineer-unit>Tactic:__init( squad_ai ) super( squad_ai ) end function <engineer-unit>Tactic:Update() --state machine if not EngineerTactic.Update( self ) then return end end function <engineer-unit>Tactic:GetName() return "<engineer-unit> Tactic" end function <engineer-unit>Tactic:Reinforce() --don't reinforce more than 4 <specific-unit> -- they get stuck if not self.squad_ai:IsReinforcing() and self.squad_ai:GetNumTroopers() < 4 then local use_leader = false if self.squad_ai:CanReinforce( use_leader ) then self.squad_ai:DoReinforce( use_leader ) end end end function <engineer-unit>Tactic:IsAttacker() --<engineer-unit> useless! if true then return false end --check if there are at least 2 free pioneer (leave one behind to build stuff) local num_<engineer-unit> = 0 for squad_ai in military_manager:GetUnlockedSquads() do if squad_ai:IsEngineer() then num_<engineer-unit> = num_<engineer-unit> + 1 end if num_<engineer-unit> > 1 then break end end --only use this as an offensive unit if I've reinforced enough if num_<engineer-unit> > 1 and self.squad_ai:GetNumTroopers() > 10 then return true end return false end
Purpose: FOR REGULAR UNITS THAT CAN ATTACH (NO ABILITIES)
Note: This can also be applied to Vehicles as well ie. (Vehicle Tactic?)
class '<military-unit>Tactic' (InfantryTactic)
<military-unit> = {}
function <military-unit>Tactic:__init( squad_ai ) super( squad_ai )
end
function <military-unit>Tactic:GetName()
return "<military-unit> Tactic"
end
function <military-unit>Tactic:Update()
--state machine
if not InfantryTactic.Update( self ) then
return
end
--if I'm in combat
if self.squad_ai:IsInCombat() and not self.squad_ai:IsBroken() then
local attachable_filter = function( squad_ai )
return ( self.squad_ai:CanAttachTo( squad_ai ) and squad_ai:IsRanged() and not squad_ai:IsBroken() )
end
--find close by squads
local attach_to = cpu_manager:GetClosestSquad( self.squad_ai:GetPosition(), 40, attachable_filter )
if attach_to ~= nil then
self.squad_ai:DoAttachSquad( attach_to )
end
end
end
Purpose: FOR REGULAR VEHICLES THAT CAN USE ABILITIES
class '<vehicle-unit>Tactic' (VehicleTactic)
<vehicle-unit> = {}
function <vehicle-unit>Tactic:__init( squad_ai ) super( squad_ai )
end
function <vehicle-unit>Tactic:UpdateAbilities()
if <vehicle-unit>.<ability shortname>_id == nil then
<vehicle-unit>.<ability shortname>_id = cpu_manager.stats:GetAbilityID( "<actual ability name>" )
end
end
function <vehicle-unit>Tactic:GetName()
return "<vehicle-unit> Tactic"
end
function <vehicle-unit>Tactic:DoAbilities()
<vehicle-unit>Tactic.UpdateAbilities( self )
dbAssert( <vehicle-unit>.<ability shortname>_id ~= nil )
Ability.<SelectBehaviour>( self.squad_ai, <vehicle-unit>.<ability shortname>_id, Ability.Filters.<SelectFilter>, <# of enemy units nearby> )
end
function <vehicle-unit>Tactic:Update()
--state machine
if not VehicleTactic.Update( self ) then
return
end
end
Purpose: FOR IMPORTANT (SUB-COMMANDER) UNITS THAT CAN ATTACH AND USE ABILITIES
class '<sub-commander unit>Tactic' (InfantryTactic)
<sub-commander unit> = {}
function <sub-commander unit>Tactic:__init( squad_ai ) super( squad_ai )
if <sub-commander unit>.rally_id == nil then
<sub-commander unit>.<ability shortname>_id = cpu_manager.stats:GetAbilityID( "<actual ability name>" )
end
end
function <sub-commander unit>Tactic:GetName()
return "<sub-commander unit> Tactic"
end
function <sub-commander unit>Tactic:DoAbilities()
--return if I didn't initialize myself
if <sub-commander unit>.<ability shortname>_id == nil then
return
end
dbAssert( <sub-commander unit>.<ability shortname>_id ~= nil )
Ability.<SelectBehaviour>( self.squad_ai, <sub-commander unit>.<ability shortname>_id, Ability.Filters.<SelectFilter>, <# of enemy units nearby> )
end
function <sub-commander unit>Tactic:Update()
if self:IsComplete() then
return
end
if (self.squad_ai:GetHealthPercentage() <= 0.8 and cpu_manager.cpu_player:GetGameTime() > 10 * 60 * 8) or self.squad_ai:GetHealthPercentage() <= 0.5 then
local attachable_filter = function( squad_ai )
return squad_ai:GetProRatedCost() >= 200 and self.squad_ai:CanAttachTo( squad_ai ) and squad_ai:IsRanged()
end
local attach_to = cpu_manager:GetClosestSquad( self.squad_ai:GetPosition(), 60, attachable_filter );
if attach_to ~= nil then
self.squad_ai:DoAttachSquad( attach_to )
end
return
end
--state machine
if not InfantryTactic.Update( self ) then
return
end
self:TryAttachSquadMelee()
end
Purpose: FOR COMMANDER UNITS THAT CAN ATTACH, USE ABILITIES, AND BE ASSASSINATED!
class '<commander-unit>Tactic' (InfantryTactic)
<commander-unit> = {}
function <commander-unit>Tactic:__init( squad_ai ) super( squad_ai )
if <commander-unit>.<ability shortname>_id == nil then
<commander-unit>.<ability shortname>_id = cpu_manager.stats:GetAbilityID( "<actual ability name>" )
end
<commander-unit>.<ability shortname> = cpu_manager.stats:GetAbilityID( "<actual ability name>" )
end
function <commander-unit>Tactic:GetName()
return "<commander-unit> Tactic"
end
function <commander-unit>Tactic:DoAbilities()
--return if I didn't initialize myself
if <commander-unit>.<ability shortname>_id == nil then
return
end
dbAssert( <commander-unit>.<ability shortname> ~= nil )
Ability.<SelectBehaviour>( self.squad_ai, <commander-unit>.<ability shortname>_id, Ability.Filters.<SelectFilter>, <# of enemy units nearby> )
Ability.<SelectBehaviour>( self.squad_ai, <commander-unit>.<ability shortname>, Ability.Filters.<SelectFilter>, <# of enemy units nearby> )
end
function <commander-unit>Tactic:IsAttacker()
--assassinate win condition -- never attack
return not cpu_manager.assassinate
end
function <commander-unit>Tactic:Update()
if self:IsComplete() then
return
end
if cpu_manager.assassinate or (self.squad_ai:GetHealthPercentage() <= 0.8 and cpu_manager.cpu_player:GetGameTime() > 10 * 60 * 8) or self.squad_ai:GetHealthPercentage() <= 0.5 then
local attachable_filter = function( squad_ai )
return squad_ai:GetProRatedCost() >= 200 and self.squad_ai:CanAttachTo( squad_ai )
end
--assassinate win condition or hurt -- always attach to a squad
local attach_to = cpu_manager:GetClosestSquad( self.squad_ai:GetPosition(), 50, attachable_filter );
if attach_to ~= nil then
self.squad_ai:DoAttachSquad( attach_to )
if cpu_manager.assassinate then
attach_to:DoSetMeleeStance( SquadAI.MSTANCE_Ranged )
end
end
return
end
--state machine
if not InfantryTactic.Update( self ) then
return
end
self:TryAttachSquadMelee()
end