Skip to content

Instantly share code, notes, and snippets.

@TDC-bob
Created June 11, 2013 14:44
Show Gist options
  • Save TDC-bob/5757427 to your computer and use it in GitHub Desktop.
Save TDC-bob/5757427 to your computer and use it in GitHub Desktop.
Artillery support fire
---------------------------------------
---- Artillery Enhancement Scrip ----
---------------------------------------
--
-- v1.0 - 26. May 2013
-- By Marc "MBot" Marbot
--
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Control functions:
--
-- AddFiringBattery(GroupName, mission, level, priority, displacement)
-- GroupName: string, name of group
-- mission: string, "DS", "CF" or "All"
-- level: number, position in the chain-of-command, digit-digit-digit-digit-digit
-- priority: number, minimum priority of firemission the battery will accept
-- displacement: number, 1 = no displacement, 2 = displacement under fire, 3 = displacement after every firemission
--
-- AdjustFiringBattery(GroupName, mission, level, priority, displacement)
-- GroupName: string, name of group
-- mission: string, "None", "DS", "CF" or "All"
-- level: number, position in the chain-of-command, digit-digit-digit-digit-digit
-- priority: number, minimum priority of firemission the battery will accept
-- displacement: number, 1 = no displacement, 2 = displacement under fire, 3 = displacement after every firemission
--
-- BatteryAddNuke(GroupName, quantity, priority)
-- GroupName: string, name of group
-- quantity: number, amount of nuclear rounds to add to battery
-- priority: number, minimum priority of firemission to authorize release of nuclear shell
--
-- BatterySetNuke(GroupName, quantity, priority)
-- GroupName: string, name of group
-- quantity: number, set the amount of nuclear rounds in battery
-- priority: number, minimum priority of firemission to authorize release of nuclear shell
--
-- AddSpotter(UnitName, range, level)
-- UnitName: string, name of unit
-- range: number, target detection range of spotter in meters
-- level: number, position in the chain-of-command, digit-digit-digit-digit-digit
--
-- RemoveSpotter(UnitName)
-- UnitName: string, name of unit
--
-- AddCounterfireRadar(UnitName, type, level)
-- UnitName: string, name of unit
-- type: string, "Q36", "Q36-360", "Q37" , "ARK1" or "Omni"
-- level: number, position in the chain-of-command, digit-digit-digit-digit-digit
--
-- RemoveCounterfieRadar(UnitName)
-- UnitName: string, name of unit
--
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Table of content:
--
-- General functions
-- Management of Firing Batteries
-- Displacement of Firing Batteries
-- Nuclear Shells
-- Management of Spotters
-- Management of Counterfire Radars
-- Management of Counterfire Radar Detection
-- Management of Counterfire Tracks
-- Detection of shot events, counting shots of batteries, manage counterfire radar detection, add shots to CFtracks
-- Management of Fire Missions
-- Debug
--
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
do
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--General functions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Function to return the heading in degrees of a Pos3 position
local function GetHeading(Pos3)
if (Pos3.x.x > 0) and (Pos3.x.z == 0) then
return 360
elseif (Pos3.x.x > 0) and (Pos3.x.z > 0) then
return math.deg(math.atan(Pos3.x.z / Pos3.x.x))
elseif (Pos3.x.x == 0) and (Pos3.x.z > 0) then
return 90
elseif (Pos3.x.x < 0) and (Pos3.x.z > 0) then
return 90 - math.deg(math.atan(Pos3.x.x / Pos3.x.z)) --90 minus ... because Pos3.x.x / Pos3.x.z is a negative value
elseif (Pos3.x.x < 0) and (Pos3.x.z == 0) then
return 180
elseif (Pos3.x.x < 0) and (Pos3.x.z < 0) then
return 180 + math.deg(math.atan(Pos3.x.z / Pos3.x.x))
elseif (Pos3.x.x == 0) and (Pos3.x.z < 0) then
return 270
elseif (Pos3.x.x > 0) and (Pos3.x.z < 0) then
return 270 - math.deg(math.atan(Pos3.x.x / Pos3.x.z)) --270 minus ... because Pos3.x.x / Pos3.x.z is a negative value
end
end
--Function to return the heading from Vec2a to Vec2b
local function GetHeadingBetween(Vec2a, Vec2b)
local deltax = Vec2b.x - Vec2a.x
local deltay = Vec2b.y - Vec2a.y
if (deltax > 0) and (deltay == 0) then
return 360
elseif (deltax > 0) and (deltay > 0) then
return math.deg(math.atan(deltay / deltax))
elseif (deltax == 0) and (deltay > 0) then
return 90
elseif (deltax < 0) and (deltay > 0) then
return 90 - math.deg(math.atan(deltax / deltay)) --90 minus ... becaue Pos3.x.x / Pos3.x.z is a negative value
elseif (deltax < 0) and (deltay == 0) then
return 180
elseif (deltax < 0) and (deltay < 0) then
return 180 + math.deg(math.atan(deltay / deltax))
elseif (deltax == 0) and (deltay < 0) then
return 270
elseif (deltax > 0) and (deltay < 0) then
return 270 - math.deg(math.atan(deltax / deltay)) --270 minus ... becaue Pos3.x.x / Pos3.x.z is a negative value
end
end
--Function to return the distance in meters between two Vec2 positions
local function GetDistance(Vec2a, Vec2b)
local deltax = Vec2b.x - Vec2a.x
local deltay = Vec2b.y - Vec2a.y
return math.sqrt(math.pow(deltax, 2) + math.pow(deltay, 2))
end
--Function to return the Vec2 position through heading and distance from a initial position
local function GetOffsetPosition(InitVec2, heading, distance)
return {
x = InitVec2.x + math.cos(heading) * distance,
y = InitVec2.y + math.sin(heading) * distance
}
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Management of Firing Batteries
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BlueFiringBattery = {} --Table to store all blue artillery groups
RedFiringBattery = {} --Table to store all red artillery groups
--Function to return the type of artillery group
local function getGroupType(GroupName) --Groups can consist of multiple units of different types. We will assign the type of the first artillery unit in the group as a property of the whole group.
local group = Group.getByName(GroupName) --Get the group by its name provided as function argument
local units = group:getUnits() --Get an array of all units in the group
for n = 1, #units do --Iterate through all units of the group
local type = units[n]:getTypeName() --Get the type of the n-th unit
if (type == "2B11 mortar") or (type == "SAU 2-C9") or (type == "M-109") or (type == "SAU Gvozdika") or (type == "SAU Akatsia" ) or (type == "SAU Msta") or (type == "MLRS") or (type == "Grad-URAL") or (type == "Smerch") then --Check if the unit is an artillery system
return type --If the unit is an artillery system, return its type
end
end
end
--Table that holds the properties of all artillery types
local ArtilleryProperties = {
["2B11 mortar"] = {
minrange = 500, --Minimal firing range
maxrange = 7000, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 0, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 0 --Time the battery waits between firing and moving
},
["SAU 2-C9"] = {
minrange = 500, --Minimal firing range
maxrange = 7000, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 0, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 10 --Time the battery waits between firing and moving
},
["M-109"] = {
minrange = 300, --Minimal firing range
maxrange = 22000, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 160, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 10 --Time the battery waits between firing and moving
},
["SAU Gvozdika"] = {
minrange = 300, --Minimal firing range
maxrange = 15000, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 0, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 10 --Time the battery waits between firing and moving
},
["SAU Akatsia"] = {
minrange = 300, --Minimal firing range
maxrange = 17000, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 0, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 10 --Time the battery waits between firing and moving
},
["SAU Msta"] = {
minrange = 300, --Minimal firing range
maxrange = 23500, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 300, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 10 --Time the battery waits between firing and moving
},
["MLRS"] = {
minrange = 10000, --Minimal firing range
maxrange = 32000, --Maximal firing range
FM_rounds = 12, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 12, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 10 --Time the battery waits between firing and moving
},
["Grad-URAL"] = {
minrange = 5000, --Minimal firing range
maxrange = 19000, --Maximal firing range
FM_rounds = 120, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 40, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 120 --Time the battery waits between firing and moving
},
["Smerch"] = {
minrange = 20000, --Minimal firing range
maxrange = 70000, --Maximal firing range
FM_rounds = 24, --The total amount of shots of a fire mission for a battery of this unit type
minAmmo = 12, --The amount of rounds left in a individual unit when switching from rapid fire to sustained fire
displacementTime = 120 --Time the battery waits between firing and moving
}
}
--Function to add an artillery battery to table
function AddFiringBattery(GroupName, mis, lvl, prio, disp) --Arguments: GroupName: string name of group; mis: string "DS", "CF" or "All"; lvl: digit-digit-digit-digit-digit (i.e. 11111); prio: number, minimum priority of FM for this battery; disp: number, shoot-and-scoot level 1-3
local coal = Group.getByName(GroupName):getCoalition() --Get coalition of group
local GroupType = getGroupType(GroupName) --Get the type of battery. Battery is of type as first artillery unit found in group. Function getGroupType is a custom function.
if coal == 1 then --If group is red
RedFiringBattery[#RedFiringBattery + 1] = {
name = GroupName, --Name of the group
type = GroupType, --Type of battery (same as first arty unit found in group)
minrange = ArtilleryProperties[GroupType].minrange, --Minimal firing range
maxrange = ArtilleryProperties[GroupType].maxrange, --Maximal firing range
FM_rounds = ArtilleryProperties[GroupType].FM_rounds, --The total amount of shots of a fire mission for a battery of this unit type
displacementTime = ArtilleryProperties[GroupType].displacementTime, --Time the battery needs to transition from firing to moving
mission = mis or "All", --Mission that the battery supports. String. Valid entries: "DS" = Direct Support, "CF" = Counterfire, "All" = DS + CF. Default "All" if no argument is specified.
level = lvl or 11111, --hierarchy level of battery. Format digit-digit-digit-digit-digit. Default 11111 if no argument is specified.
priority = prio or 0, --The minimum priority a fire mission has to have for this battery. Default 0 if no argument is specified.
displace = disp or 1, --The shoot-and-scoot level of the battery. 1: stay in place, 2: displace under fire, 3: displace after every firemission. Default 1 if no argument is specified.
status = "ready", --Status of the battery. String. Valid entries: "ready", "firing", "displacing". Initial state is "ready".
shotcounter = 0, --Counter to track the number of shots when executing a fire mission
displacementcounter = 0, --Counter to track the number of displacements done by the battery
nukes = 0, --Amount if nuclear rounds in battery
nukepriority = 100, --Minimum firemission priority to engage with nuclear round
}
elseif coal == 2 then --If group is blue
BlueFiringBattery[#BlueFiringBattery + 1] = {
name = GroupName, --Name of the group
type = GroupType, --Type of battery (same as first arty unit found in group)
minrange = ArtilleryProperties[GroupType].minrange, --Minimal firing range
maxrange = ArtilleryProperties[GroupType].maxrange, --Maximal firing range
FM_rounds = ArtilleryProperties[GroupType].FM_rounds, --The total amount of shots of a fire mission for a battery of this unit type
displacementTime = ArtilleryProperties[GroupType].displacementTime, --Time the battery needs to transition from firing to moving
mission = mis or "All", --Mission that the battery supports. String. Valid entries: "DS" = Direct Support, "CF" = Counterfire, "All" = DS + CF
level = lvl or 11111, --hierarchy level of battery. Format digit-digit-digit-digit-digit
priority = prio or 0, --The minimum priority a fire mission has to have for this battery
displace = disp or 1, --The shoot-and-scoot level of the battery. 1: stay in place, 2: displace under fire, 3: displace after every firemission
status = "ready", --Status of the battery. String. Valid entries: "ready", "firing", "displacing". Initial state is "ready".
shotcounter = 0, --Counter to track the number of shots when executing a fire mission
displacementcounter = 0, --Counter to track the number of displacements done by the battery
nukes = 0, --Amount if nuclear rounds in battery
nukepriority = 100, --Minimum firemission priority to engage with nuclear round
}
end
end
--Function to adjust the settings of an artillery group
function AdjustFiringBattery(GroupName, mis, lvl, prio, disp) --Arguments: GroupName: string name of group; mis: string "DS", "CF" or "All"; lvl: digit-digit-digit-digit-digit (i.e. 11111)
local coal = Group.getCoalition(GroupName) --Get coalition of group
if coal == 1 then --If group is red
for n = 1, #RedFiringBattery do --Iterate through all groups
if RedFiringBattery[n].name == GroupName then --Find n-th entry that contains group we want to remove
RedFiringBattery[n].mission = mis or "All" --Set new mission. Valid entries: "DS" = Direct Support, "CF" = Counterfire, "All" = DS + CF. To disable group, set mission to anything not valid.
RedFiringBattery[n].level = lvl or 11111 --hierarchy level of battery. Format digit-digit-digit-digit-digit
RedFiringBattery[n].priority = prio or 0 --Set minimum priority of firemission for battery to accept
RedFiringBattery[n].displace = disp or 1 --Set displacement level
break --Quit for loop
end
end
elseif coal == 2 then --If group is blue
for n = 1, #BlueFiringBattery do --Iterate through all groups
if BlueFiringBattery[n].name == GroupName then --Find n-th entry that contains group we want to remove
BlueFiringBattery[n].mission = mis or "All" --Set new mission. Valid entries: "DS" = Direct Support, "CF" = Counterfire, "All" = DS + CF. To disable group, set mission to anything not valid.
BlueFiringBattery[n].level = lvl or 11111 --hierarchy level of battery. Format digit-digit-digit-digit-digit
BlueFiringBattery[n].priority = prio or 0 --Set minimum priority of firemission for battery to accept
BlueFiringBattery[n].displace = disp or 1 --Set displacement level
break --Quit for loop
end
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Displacement of Firing Batteries
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
function BlueDisplacement(n, underFire)
if BlueFiringBattery[n].type == "2B11 mortar" then --Check if the battery is a 2B11 mortar battery. 2B11 is static and cannot displace
BlueFiringBattery[n].status = "ready" --Make the battery ready to fire again
else
if underFire == "under fire" or BlueFiringBattery[n].displace == 3 then --Check if the battery is under fire or set to displace after each fire mission
BlueFiringBattery[n].status = "displacing" --Set status of battery to displacing
local grp = Group.getByName(BlueFiringBattery[n].name) --Get group
local units = grp:getUnits() --Get uits of group
local grpP = units[1]:getPoint() --Get Vec3 position of first unit of group
if BlueFiringBattery[n].displacementcounter == 0 then --Check if this is the first displacement of the battery
BlueFiringBattery[n].initpos = grpP --If yes, store the initial position of the battery
end
local dest = {} --New destination position
repeat
dest.x = BlueFiringBattery[n].initpos.x + math.random(-400, 400) --Find a new destination x coordinate in a 800x800m square centered on the initial group position
dest.y = BlueFiringBattery[n].initpos.z + math.random(-400, 400) --Find a new destination y coordinate in a 800x800m square centered on the initial group position
until GetDistance(dest, {x = grpP.x, y = grpP.z}) > 300 and land.getSurfaceType(dest) == 1 --Try to find new destination coordinates until dest is more than 300m away from the current group position and dest is on land surface
local DisplaceTask = { --Define new mission-task
id = 'Mission',
params = {
route = {
points = {
[1] = { --Initial waypoint
action = "Custom",
x = grpP.x, --Current position of first unit of group
y = grpP.z, --Current position of first unit of group
speed = 7, --Speed in m/s
ETA = 0,
ETA_locked = false,
name = "Initial Position",
task = {
id = "ComboTask",
params = {
tasks = {}
}
}
},
[2] = { --Destination waypont
action = "Custom",
x = dest.x, --Destination position
y = dest.y, --Destination position
speed = 7,
ETA = 0,
ETA_locked = false,
name = "Displacement Position",
task = { --Perform the following script when arriving
id = "WrappedAction",
params = {
action = {
id = "Script",
params = {
command = "BlueFiringBattery[" .. n .. "].status = 'ready'", --Set status of the battery to ready when arriving at new location
}
}
}
}
}
}
}
}
}
local ctrl = grp:getController() --Get controller of group
local function DisplaceDelay() --This is a scheduled function
Controller.setTask(ctrl, DisplaceTask) --Replace current task of firing battery and assign task to displace. This will remove the group of any further control by the mission designer!
return nil --Return nil means that this function is not repeated
end
timer.scheduleFunction(DisplaceDelay, arg, timer.getTime() + BlueFiringBattery[n].displacementTime) --Do displacement task after waiting the dispacement time of the battery
else
BlueFiringBattery[n].status = "ready" --If the battery is not set to displace after a firemission, reset status to ready.
end
end
BlueFiringBattery[n].displacementcounter = BlueFiringBattery[n].displacementcounter + 1 --Increase the displacement counter by 1
end
function RedDisplacement(n, underFire)
if RedFiringBattery[n].type == "2B11 mortar" then --Check if the battery is a 2B11 mortar battery. 2B11 is static and cannot displace
RedFiringBattery[n].status = "ready" --Make the battery ready to fire again
else
if underFire == "under fire" == 2 or RedFiringBattery[n].displace == 3 then --Check if the battery is under fire or set to displace after each fire mission
RedFiringBattery[n].status = "displacing" --Set status of battery to displacing
local grp = Group.getByName(RedFiringBattery[n].name) --Get group
local units = grp:getUnits() --Get uits of group
local grpP = units[1]:getPoint() --Get Vec3 position of first unit of group
if RedFiringBattery[n].displacementcounter == 0 then --Check if this is the first displacement of the battery
RedFiringBattery[n].initpos = grpP --If yes, store the initial position of the battery
end
local dest = {} --New destination position
repeat
dest.x = RedFiringBattery[n].initpos.x + math.random(-400, 400) --Find a new destination x coordinate in a 800x800m square centered on the initial group position
dest.y = RedFiringBattery[n].initpos.z + math.random(-400, 400) --Find a new destination y coordinate in a 800x800m square centered on the initial group position
until GetDistance(dest, {x = grpP.x, y = grpP.z}) > 300 and land.getSurfaceType(dest) == 1 --Try to find new destination coordinates until dest is more than 300m away from the current group position and dest is on land surface
local DisplaceTask = { --Define new mission-task
id = 'Mission',
params = {
route = {
points = {
[1] = { --Initial waypoint
action = "Custom",
x = grpP.x, --Current position of first unit of group
y = grpP.z, --Current position of first unit of group
speed = 7, --Speed in m/s
ETA = 0,
ETA_locked = false,
name = "Initial Position",
task = {
id = "ComboTask",
params = {
tasks = {}
}
}
},
[2] = { --Destination waypont
action = "Custom",
x = dest.x, --Destination position
y = dest.y, --Destination position
speed = 7,
ETA = 0,
ETA_locked = false,
name = "Displacement Position",
task = { --Perform the following script when arriving
id = "WrappedAction",
params = {
action = {
id = "Script",
params = {
command = "RedFiringBattery[" .. n .. "].status = 'ready'", --Set status of the battery to ready when arriving at new location
}
}
}
}
}
}
}
}
}
local ctrl = grp:getController() --Get controller of group
local function DisplaceDelay() --This is a scheduled function
Controller.setTask(ctrl, DisplaceTask) --Replace current task of firing battery and assign task to displace. This will remove the group of any further control by the mission designer!
return nil --Return nil means that this function is not repeated
end
timer.scheduleFunction(DisplaceDelay, arg, timer.getTime() + RedFiringBattery[n].displacementTime) --Do displacement task after waiting the dispacement time of the battery
else
RedFiringBattery[n].status = "ready" --If the battery is not set to displace after a firemission, reset status to ready.
end
end
RedFiringBattery[n].displacementcounter = RedFiringBattery[n].displacementcounter + 1 --Increase the displacement counter by 1
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Nuclear Shells
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--function to add nuclear rounds to batteries
function BatteryAddNuke(GroupName, q, p) --Arguments: GroupName string, q: quantity of nuclear shells to add, p: minimum priority level for nuclear attack
local coal = Group.getByName(GroupName):getCoalition() --Get coalition of group
if coal == 1 then --If group is red
for n = 1, #RedFiringBattery do --Iterate through all groups
if RedFiringBattery[n].name == GroupName then --Find the battery that is being refered to
if RedFiringBattery[n].type == "M-109" or RedFiringBattery[n].type == "SAU Akatsia" or RedFiringBattery[n].type == "SAU Msta" then --Check if the group is of nuclear capable type
RedFiringBattery[n].nukes = RedFiringBattery[n].nukes + q --Add nuclear shells to battery
RedFiringBattery[n].nukepriority = p or 100 --Set mimimum priority of firemission for nuclear attack; set to 100 if not specified
break
end
end
end
elseif coal == 2 then --If group is blue
for n = 1, #BlueFiringBattery do --Iterate through all groups
if BlueFiringBattery[n].name == GroupName then --Find the battery that is being refered to
if BlueFiringBattery[n].type == "M-109" or BlueFiringBattery[n].type == "SAU Akatsia" or BlueFiringBattery[n].type == "SAU Msta" then --Check if the group is of nuclear capable type
BlueFiringBattery[n].nukes = BlueFiringBattery[n].nukes + q --Add nuclear shells to battery
BlueFiringBattery[n].nukepriority = p or 100 --Set mimimum priority of firemission for nuclear attack; set to 100 if not specified
break
end
end
end
end
end
--function to set the amount of nuclear rounds of a battery
function BatterySetNuke(GroupName, q, p)
local coal = Group.getByName(GroupName):getCoalition() --Get coalition of group
if coal == 1 then --If group is red
for n = 1, #RedFiringBattery do --Iterate through all groups
if RedFiringBattery[n].name == GroupName then --Find the battery that is being refered to
if RedFiringBattery[n].type == "M-109" or RedFiringBattery[n].type == "SAU Akatsia" or RedFiringBattery[n].type == "SAU Msta" then --Check if the group is of nuclear capable type
RedFiringBattery[n].nukes = q --Set number of nuclear shells of battery
RedFiringBattery[n].nukepriority = p or 100 --Set mimimum priority of firemission for nuclear attack; set to 100 if not specified
break
end
end
end
elseif coal == 2 then --If group is blue
for n = 1, #BlueFiringBattery do --Iterate through all groups
if BlueFiringBattery[n].name == GroupName then --Find the battery that is being refered to
if RedFiringBattery[n].type == "M-109" or RedFiringBattery[n].type == "SAU Akatsia" or RedFiringBattery[n].type == "SAU Msta" then --Check if the group is of nuclear capable type
BlueFiringBattery[n].nukes = q --Set number of nuclear shells of battery
BlueFiringBattery[n].nukepriority = p or 100 --Set mimimum priority of firemission for nuclear attack; set to 100 if not specified
break
end
end
end
end
end
--Function to detonate a nuclear shell
local function NuclearShell(GroupName)
local firstshot = true --Local variable to only allow the first shot in a firemission to be nuclear
NuclearShellHandler = {} --Event handler to capture the shot of a nuclear artillery shell
function NuclearShellHandler:onEvent(event)
if event.id == world.event.S_EVENT_SHOT then --Check if event is a shot event
local Init = event.initiator --Get initiator unit of shot event
local InitGroup = Init:getGroup() --Get group that shot
if InitGroup == Group.getByName(GroupName) then --Check if the group that shot is the group that is provided as argument
local Wep = event.weapon --Get weapon of the shot event
local InitWep = Wep:getTypeName() --Get type of the weapon
if (InitWep == "weapons.shells.M185_155") then --Check if the weapon that is shot is a W48 warhead (M109)
if firstshot == true then --Check that this is the first round in a nuclear firemission
firstshot = false
local function TrackShell() --Scheduled function
local ShellPos = Wep:getPoint() --Get position of shell
if ShellPos.y < land.getHeight({x = ShellPos.x, y = ShellPos.z}) + 15 then --If shell is less than 15m AGL
trigger.action.explosion(ShellPos, 75000) --Detonate warhead. Yield for W48 warhead 0.075kt
Wep:destroy() --Remove shell
world.removeEventHandler(NuclearShellHanlder) --Remove event handler
return
end
return timer.getTime() + 0.05 --Repeat 20 times a second
end
timer.scheduleFunction(TrackShell, nil, timer.getTime() + 10) --Start scheduled function TrackShell in 5 seconds
end
elseif (InitWep == "weapons.shells.2A33_152" ) or (InitWep == "weapons.shells.2A64_152") then --Check if the weapon that is shot is a ZBV3 warhead (2S3 or 2S19)
if firstshot == true then --Check that this is the first round in a nuclear firemission
firstshot = false
local function TrackShell() --Scheduled function
local ShellPos = Wep:getPoint() --Get position of shell
if ShellPos.y < land.getHeight({x = ShellPos.x, y = ShellPos.z}) + 15 then --If shell is less than 15m AGL
trigger.action.explosion(ShellPos, 1000000) --Detonate warhead. Yield for ZBV3 warhead 1kt
Wep:destroy() --Remove shell
world.removeEventHandler(NuclearShellHanlder) --Remove event handler
return
end
return timer.getTime() + 0.05 --Repeat 20 times a second
end
timer.scheduleFunction(TrackShell, nil, timer.getTime() + 10) --Start scheduled function TrackShell in 5 seconds
end
end
end
end
end
world.addEventHandler(NuclearShellHandler)
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Management of Spotters
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
BlueSpotter = {} --Array holding all blue spotter units
RedSpotter = {} --Array holding all red spotter units
--Function to add a spotter unit
function AddSpotter(UnitName, rng, lvl)
local coal = Unit.getByName(UnitName):getCoalition() --Get coalition of unit
if coal == 1 then --If unit is red
RedSpotter[#RedSpotter + 1] = { --Add new red spotter to array
name = UnitName, --Name of spotter unit
range = rng or 5000, --Detection range of spotter unit. Default to 5000m if no argument is provided
level = lvl or 1111, --hierarchy level of spotter unit. Format digit-digit-digit-digit-digit. Default to 11111 if no argument is provided
target = {}, --Table to store targets of this spotter
track = {} --Array to store tracks of this spotter
}
elseif coal == 2 then --If unit is blue
BlueSpotter[#BlueSpotter + 1] = { --Add new blue spotter to array
name = UnitName, --Name of spotter unit
range = rng or 5000, --Detection range of spotter unit. Default to 5000m if no argument is provided
level = lvl or 1111, --hierarchy level of spotter unit. Format digit-digit-digit-digit-digit. Default to 11111 if no argument is provided
target = {}, --Table to store targets of this spotter
track = {} --Array to store tracks of this spotter
}
end
end
--Function to remove a counterfire radar from table
function RemoveSpotter(UnitName)
local coal = Unit.getByName(UnitName):getCoalition() --Get coalition of unit
if coal == 1 then --If units is red
for n = 1, #RedSpotter do --Iterate through all units
if RedSpotter[n].name == UnitName then --Find n-th entry that contains unit we want to remove
table.remove(RedSpotter, n) --Remove unit
break --Quit for loop
end
end
elseif coal == 2 then --If unit is blue
for n = 1, #BlueSpotter do --Iterate through all unit
if BlueSpotter[n].name == UnitName then --Find n-th entry that contains unit we want to remove
table.remove(BlueSpotter, n) --Remove unit
break --Quit for loop
end
end
end
end
--Function to determine the value of a target
local function getValue(tgt)
local v = 0 --Variable to summ value of this target
if tgt:hasAttribute("Tanks") then
v = v + 10
end
if tgt:hasAttribute("IFV") then
v = v + 5
end
if tgt:hasAttribute("APC") then
v = v + 3
end
if tgt:hasAttribute("Artillery") then
v = v + 10
end
if tgt:hasAttribute("Trucks") then
v = v + 1
end
if tgt:hasAttribute("Cars") then
v = v + 1
end
if tgt:hasAttribute("Infantry") then
v = v + 1
end
if tgt:hasAttribute("ATGM") then
v = v + 10
end
if tgt:hasAttribute("Buildings") then
v = v + 3
end
if tgt:hasAttribute("Static AAA") then
v = v + 3
end
if tgt:hasAttribute("Mobile AAA") then
v = v + 3
end
if tgt:hasAttribute("SAM SR") then
v = v + 20
end
if tgt:hasAttribute("SAM TR") then
v = v + 15
end
if tgt:hasAttribute("SAM LL") then
v = v + 10
end
if tgt:hasAttribute("SR SAM") then
v = v + 20
end
if tgt:hasAttribute("MR SAM") then
v = v + 30
end
if tgt:hasAttribute("LR SAM") then
v = v + 40
end
if tgt:hasAttribute("MANPADS") then
v = v + 10
end
if tgt:hasAttribute("Helicopters") then
v = v + 40
end
return v --Return total value
end
--Detecting blue targets and creating tracks
local function BlueSpotterDetectionHandler(tgt, n) --Function to be executed for each unit found unit
if tgt:getCoalition() == 1 then --Check if found unit is red
local desc = tgt:getDesc() --Get descriptor of target
if desc.category == 2 or desc.category == 1 then --If target is a ground unit or helicopter
local tgtVec3 = tgt:getPoint() --Get Vec3 position of target
local sptVec3 = Unit.getByName(BlueSpotter[n].name):getPoint() --Get Vec3 position of spotter
if land.isVisible({x = tgtVec3.x, y = tgtVec3.y + 2, z = tgtVec3.z}, {x = sptVec3.x, y = sptVec3.y + 2, z = sptVec3.z}) then --Check if there is a line of sight between spotter and target. Add 2 meters of height to the y axis to avoid LOS problems very close to the ground.
local tgtName = tgt:getName() --Get name of target
if BlueSpotter[n].target[tgtName] then --Check if target is already in target-table of spotter
if (desc.category == 2 and tgtVec3.x <= BlueSpotter[n].target[tgtName].x + 5 and tgtVec3.x >= BlueSpotter[n].target[tgtName].x - 5 and tgtVec3.z <= BlueSpotter[n].target[tgtName].z + 5 and tgtVec3.z >= BlueSpotter[n].target[tgtName].z - 5) or (desc.category == 1 and tgtVec3.x <= BlueSpotter[n].target[tgtName].x + 20 and tgtVec3.x >= BlueSpotter[n].target[tgtName].x - 20 and tgtVec3.z <= BlueSpotter[n].target[tgtName].z + 20 and tgtVec3.z >= BlueSpotter[n].target[tgtName].z - 20 and land.getHeight({x = tgtVec3.x, y = tgtVec3.z}) + 20 > tgtVec3.y) then --Check if target position is within 5m of previously stored target position if the target is a ground unit, or if the target is within 20m of previsouly stored target position and target altitude is lower than 20m AGL if target is a helicopter. If yes, the target is considered stationary and eligible for an artillery strike.
BlueSpotter[n].target[tgtName] = tgtVec3 --Store the new target position
---------------Put target into track---------------
for i = 0, #BlueSpotter[n].track do --Iterate through all tracks
if i > 0 and BlueSpotter[n].track[i].active == true and tgtVec3.x <= BlueSpotter[n].track[i].coord.x + 200 and tgtVec3.x >= BlueSpotter[n].track[i].coord.x - 200 and tgtVec3.z <= BlueSpotter[n].track[i].coord.y + 200 and tgtVec3.z >= BlueSpotter[n].track[i].coord.y - 200 then --Check if target is within 200m of existing track. For simplicity the check is made with x/y axis and not radius.
BlueSpotter[n].track[i].coord.x = (BlueSpotter[n].track[i].coord.x + tgtVec3.x) / 2 --Average track x axis with new target position
BlueSpotter[n].track[i].coord.y = (BlueSpotter[n].track[i].coord.y + tgtVec3.z) / 2 --Average track y axis with new target position
if timer.getTime() - 2 < BlueSpotter[n].track[i].time then --Check if track was update within last 2 seconds
BlueSpotter[n].track[i].value = BlueSpotter[n].track[i].value + getValue(tgt) --If yes, add target value to total track value
else
BlueSpotter[n].track[i].value = getValue(tgt) --If no, reset track value with target value
end
BlueSpotter[n].track[i].time = timer.getTime() --Reset timer
BlueSpotter[n].track[i].active = true --The track is active
do break end --Quit track loop
elseif i == #BlueSpotter[n].track then --If target is not within 200m of an existing track...
BlueSpotter[n].track[i + 1] = { --create a new track
coord = {x = tgtVec3.x, y = tgtVec3.z}, --Set track coordinates
time = timer.getTime(), --Time the track was created
value = getValue(tgt), --Value of the track. Initial value is value of initial target
active = true, --The track is active
firemission = false --The track currently has not created a firemission
}
break --Quit track loop
end
end
---------------------------------------------------
else --The target has moved more than 5m since the last time it was detected and is therfore ineligible for an artillery strike.
BlueSpotter[n].target[tgtName] = tgtVec3 --Store the new target position
end
else --If not...
table.insert(BlueSpotter[n].target, tgtName) --Add target to target-table of spotter
BlueSpotter[n].target[tgtName] = tgtVec3 --Store target Vec3 position
end
end
end
end
return true
end
--Detecting red targets and creating tracks
local function RedSpotterDetectionHandler(tgt, n) --Function to be executed for each unit found unit
if tgt:getCoalition() == 2 then --Check if found unit is blue
local desc = tgt:getDesc() --Get descriptor of target
if desc.category == 2 or desc.category == 1 then --If target is a ground unit or helicopter
local tgtVec3 = tgt:getPoint() --Get Vec3 position of target
local sptVec3 = Unit.getByName(RedSpotter[n].name):getPoint() --Get Vec3 position of spotter
if land.isVisible({x = tgtVec3.x, y = tgtVec3.y + 2, z = tgtVec3.z}, {x = sptVec3.x, y = sptVec3.y + 2, z = sptVec3.z}) then --Check if there is a line of sight between spotter and target. Add 2 meters of height to the y axis to avoid LOS problems very close to the ground.
local tgtName = tgt:getName() --Get name of target
if RedSpotter[n].target[tgtName] then --Check if target is already in target-table of spotter
if (desc.category == 2 and tgtVec3.x <= RedSpotter[n].target[tgtName].x + 5 and tgtVec3.x >= RedSpotter[n].target[tgtName].x - 5 and tgtVec3.z <= RedSpotter[n].target[tgtName].z + 5 and tgtVec3.z >= RedSpotter[n].target[tgtName].z - 5) or (desc.category == 1 and tgtVec3.x <= RedSpotter[n].target[tgtName].x + 20 and tgtVec3.x >= RedSpotter[n].target[tgtName].x - 20 and tgtVec3.z <= RedSpotter[n].target[tgtName].z + 20 and tgtVec3.z >= RedSpotter[n].target[tgtName].z - 20 and land.getHeight({x = tgtVec3.x, y = tgtVec3.z}) + 20 > tgtVec3.y) then --Check if target position is within 5m of previously stored target position if the target is a ground unit, or if the target is within 20m of previsouly stored target position and target altitude is lower than 20m AGL if target is a helicopter. If yes, the target is considered stationary and eligible for an artillery strike.
RedSpotter[n].target[tgtName] = tgtVec3 --Store the new target position
---------------Put target into track---------------
for i = 0, #RedSpotter[n].track do --Iterate through all tracks
if i > 0 and RedSpotter[n].track[i].active == true and tgtVec3.x <= RedSpotter[n].track[i].coord.x + 200 and tgtVec3.x >= RedSpotter[n].track[i].coord.x - 200 and tgtVec3.z <= RedSpotter[n].track[i].coord.y + 200 and tgtVec3.z >= RedSpotter[n].track[i].coord.y - 200 then --Check if target is within 200m of existing track. For simplicity the check is made with x/y axis and not radius.
RedSpotter[n].track[i].coord.x = (RedSpotter[n].track[i].coord.x + tgtVec3.x) / 2 --Average track x axis with new target position
RedSpotter[n].track[i].coord.y = (RedSpotter[n].track[i].coord.y + tgtVec3.z) / 2 --Average track y axis with new target position
if timer.getTime() - 2 < RedSpotter[n].track[i].time then --Check if track was update within last 2 seconds
RedSpotter[n].track[i].value = RedSpotter[n].track[i].value + getValue(tgt) --If yes, add target value to total track value
else
RedSpotter[n].track[i].value = getValue(tgt) --If no, reset track value with target value
end
RedSpotter[n].track[i].time = timer.getTime() --Reset timer
RedSpotter[n].track[i].active = true --The track is active
do break end --Quit track loop
elseif i == #RedSpotter[n].track then --If target is not within 200m of an existing track...
RedSpotter[n].track[i + 1] = { --create a new track
coord = {x = tgtVec3.x, y = tgtVec3.z}, --Set track coordinates
time = timer.getTime(), --Time the track was created
value = getValue(tgt), --Value of the track. Initial value is value of initial target
active = true, --The track is active
firemission = false --The track currently has not created a firemission
}
break --Quit track loop
end
end
---------------------------------------------------
else --The target has moved more than 5m since the last time it was detected and is therfore ineligible for an artillery strike.
RedSpotter[n].target[tgtName] = tgtVec3 --Store the new target position
end
else --If not...
table.insert(RedSpotter[n].target, tgtName) --Add target to target-table of spotter
RedSpotter[n].target[tgtName] = tgtVec3 --Store target Vec3 position
end
end
end
end
return true
end
--Spotter detection cycle
local function SpotterDetection() --Function to execute spotter search for targers. To be repeated every 10 seconds
for n = 1, #BlueSpotter do --Iterate through all blue spotters
local Spotter = Unit.getByName(BlueSpotter[n].name) --Get spotter unit
local SpotterP = Spotter:getPoint() --Get Vec3 position of spotter unit
local SearchArea = { --Define search area for this spotter unit
id = world.VolumeType.SPHERE, --Search area is a sphere
params = {
point = SpotterP, --Centered on spotter unit Vec3 position
radius = BlueSpotter[n].range --With a radius of the search range of the spotter unit
}
}
world.searchObjects(Object.Category.UNIT, SearchArea, BlueSpotterDetectionHandler, n) --Execute a search for units in the definded search area. For each found unit, execute function BlueSpotterDetectionHandler
end
for n = 1, #RedSpotter do --Iterate through all Red spotters
local Spotter = Unit.getByName(RedSpotter[n].name) --Get spotter unit
local SpotterP = Spotter:getPoint() --Get Vec3 position of spotter unit
local SearchArea = { --Define search area for this spotter unit
id = world.VolumeType.SPHERE, --Search area is a sphere
params = {
point = SpotterP, --Centered on spotter unit Vec3 position
radius = RedSpotter[n].range --With a radius of the search range of the spotter unit
}
}
world.searchObjects(Object.Category.UNIT, SearchArea, RedSpotterDetectionHandler, n) --Execute a search for units in the definded search area. For each found unit, execute function RedSpotterDetectionHandler
end
return timer.getTime() + 10 --Repeat function every 10 seconds
end
timer.scheduleFunction(SpotterDetection, arg, 5) --Run function for the first time after 5 seconds
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Management of Counterfire Radars
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Table that holds the properties of the available counterfire radar types
local CounterfireRadarType = {
["Q36"] = {
name = "AN/TPQ-36 Firefinder",
mortar = { --Detection area of mortar shells
minrange = 750, --Minimum detection range in meters
maxrange = 18000, --Maxium detection range in meters
angle = 45, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 90, --Chance of detection in percent
poserror = 1, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 100 --In meters. Detected position is never more accurate than maxaccuracy.
},
artillery = { --Detection area of artillery shells
minrange = 3000, --Minimum detection range in meters
maxrange = 14500, --Maxium detection range in meters
angle = 45, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 70, --Chance of detection in percent
poserror = 1.5, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 150 --In meters. Detected position is never more accurate than maxaccuracy.
},
rocket = { --Detection area of rockets
minrange = 8000, --Minimum detection range in meters
maxrange = 24000, --Maxium detection range in meters
angle = 45, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 80, --Chance of detection in percent
poserror = 2.5, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 300 --In meters. Detected position is never more accurate than maxaccuracy.
}
},
["Q36-360"] = {
name = "AN/TPQ-36 Firefinder 360°", --AN/TPQ-36 set to scan around 360° with 4 times lower detection propability.
mortar = { --Detection area of mortar shells
minrange = 750, --Minimum detection range in meters
maxrange = 18000, --Maxium detection range in meters
angle = 180, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 22.5, --Chance of detection in percent
poserror = 1, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 100 --In meters. Detected position is never more accurate than maxaccuracy.
},
artillery = { --Detection area of artillery shells
minrange = 3000, --Minimum detection range in meters
maxrange = 14500, --Maxium detection range in meters
angle = 180, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 17.5, --Chance of detection in percent
poserror = 1.5, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 150 --In meters. Detected position is never more accurate than maxaccuracy.
},
rocket = { --Detection area of rockets
minrange = 8000, --Minimum detection range in meters
maxrange = 24000, --Maxium detection range in meters
angle = 180, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 20, --Chance of detection in percent
poserror = 2.5, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 300 --In meters. Detected position is never more accurate than maxaccuracy.
}
},
["Q37"] = {
name = "AN/TPQ-37 Firefinder",
mortar = { --Detection area of mortar shells
minrange = 4000, --Minimum detection range in meters
maxrange = 30000, --Maxium detection range in meters
angle = 45, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 85, --Chance of detection in percent
poserror = 0.9, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 90 --In meters. Detected position is never more accurate than maxaccuracy.
},
artillery = { --Detection area of artillery shells
minrange = 4000, --Minimum detection range in meters
maxrange = 30000, --Maxium detection range in meters
angle = 45, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 85, --Chance of detection in percent
poserror = 0.9, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 90 --In meters. Detected position is never more accurate than maxaccuracy.
},
rocket = { --Detection area of rockets
minrange = 4000, --Minimum detection range in meters
maxrange = 37000, --Maxium detection range in meters
angle = 45, --In degrees. Angle of the scan area left or right from azimuth of unit
detect = 85, --Chance of detection in percent
poserror = 1, --In percent. Detected position is somewhere within a circle with the radius x% of range.
maxaccuracy = 175 --In meters. Detected position is never more accurate than maxaccuracy.
}
},
["ARK1"] = {
name = "ARK-1M Rys",
mortar = { --Detection area of mortar shells
minrange = 750, --Value of AN/TPQ-36
maxrange = 12000, --Source: http://www.npostrela.com/en/products/museum/92/237/
angle = 45, --Value of AN/TPQ-36
detect = 90, --Value of AN/TPQ-36
poserror = 1, --Value of AN/TPQ-36
maxaccuracy = 100 --Value of AN/TPQ-36
},
artillery = { --Detection area of artillery shells
minrange = 3000, --Value of AN/TPQ-36
maxrange = 9000, --Source: http://www.npostrela.com/en/products/museum/92/237/
angle = 45, --Value of AN/TPQ-36
detect = 70, --Value of AN/TPQ-36
poserror = 1.5, --Value of AN/TPQ-36
maxaccuracy = 150 --Value of AN/TPQ-36
},
rocket = { --Detection area of rockets
minrange = 8000, --Value of AN/TPQ-36
maxrange = 16000, --Source: http://www.npostrela.com/en/products/museum/92/237/
angle = 45, --Value of AN/TPQ-36
detect = 80, --Value of AN/TPQ-36
poserror = 2.5, --Value of AN/TPQ-36
maxaccuracy = 300 --Value of AN/TPQ-36
}
},
["Omni"] = { --A fictional system that dedects everything within 50 km
name = "Omni",
mortar = { --
minrange = 0, --
maxrange = 50000, --
angle = 180, --
detect = 100, --
poserror = 0, --
maxaccuracy = 0 --
},
artillery = { --
minrange = 0, --
maxrange = 50000, --
angle = 180, --
detect = 100, --
poserror = 0, --
maxaccuracy = 0 --
},
rocket = { --
minrange = 0, --
maxrange = 50000, --
angle = 180, --
detect = 100, --
poserror = 0, --
maxaccuracy = 0 --
}
}
}
--Tables that store counterfire radars
BlueCounterfireRadar = {} --Table that stores all blue counterfire radars
RedCounterfireRadar = {} --Table that stores all red counterfire radars
--Function that adds a counterfire radar to table
function AddCounterfireRadar(UnitName, t, lvl) --Valid entries for argument t: "Q36", "Q36-360", "Q37" , "ARK1" and "Omni"
local coal = Unit.getByName(UnitName):getCoalition() --Get coalition of unit
local UnitType = t or "Omni" --Default to "Omni" if no argument is provided
if coal == 1 then --If unit is red
RedCounterfireRadar[#RedCounterfireRadar + 1] = {
name = UnitName,
type = { --Get the specification for the indicated UnitType from the CounterfireRadarType and store them for each added radar
name = CounterfireRadarType[UnitType].name,
mortar = {
minrange = CounterfireRadarType[UnitType].mortar.minrange,
maxrange = CounterfireRadarType[UnitType].mortar.maxrange,
angle = CounterfireRadarType[UnitType].mortar.angle,
detect = CounterfireRadarType[UnitType].mortar.detect,
poserror = CounterfireRadarType[UnitType].mortar.poserror,
maxaccuracy = CounterfireRadarType[UnitType].mortar.maxaccuracy
},
artillery = {
minrange = CounterfireRadarType[UnitType].artillery.minrange,
maxrange = CounterfireRadarType[UnitType].artillery.maxrange,
angle = CounterfireRadarType[UnitType].artillery.angle,
detect = CounterfireRadarType[UnitType].artillery.detect,
poserror = CounterfireRadarType[UnitType].artillery.poserror,
maxaccuracy = CounterfireRadarType[UnitType].artillery.maxaccuracy
},
rocket = {
minrange = CounterfireRadarType[UnitType].rocket.minrange,
maxrange = CounterfireRadarType[UnitType].rocket.maxrange,
angle = CounterfireRadarType[UnitType].rocket.angle,
detect = CounterfireRadarType[UnitType].rocket.detect,
poserror = CounterfireRadarType[UnitType].rocket.poserror,
maxaccuracy = CounterfireRadarType[UnitType].rocket.maxaccuracy
}
},
level = lvl or 11111, --Hierarchy level of radar. Format digit-digit-digit-digit-digit. Default to 11111 if no argument is provided
track = {} --Table to store the tracks of this radar
}
elseif coal == 2 then --If unit is blue
BlueCounterfireRadar[#BlueCounterfireRadar + 1] = {
name = UnitName,
type = { --Get the specification for the indicated UnitType from the CounterfireRadarType and store them for each added radar
name = CounterfireRadarType[UnitType].name,
mortar = {
minrange = CounterfireRadarType[UnitType].mortar.minrange,
maxrange = CounterfireRadarType[UnitType].mortar.maxrange,
angle = CounterfireRadarType[UnitType].mortar.angle,
detect = CounterfireRadarType[UnitType].mortar.detect,
poserror = CounterfireRadarType[UnitType].mortar.poserror,
maxaccuracy = CounterfireRadarType[UnitType].mortar.maxaccuracy
},
artillery = {
minrange = CounterfireRadarType[UnitType].artillery.minrange,
maxrange = CounterfireRadarType[UnitType].artillery.maxrange,
angle = CounterfireRadarType[UnitType].artillery.angle,
detect = CounterfireRadarType[UnitType].artillery.detect,
poserror = CounterfireRadarType[UnitType].artillery.poserror,
maxaccuracy = CounterfireRadarType[UnitType].artillery.maxaccuracy
},
rocket = {
minrange = CounterfireRadarType[UnitType].rocket.minrange,
maxrange = CounterfireRadarType[UnitType].rocket.maxrange,
angle = CounterfireRadarType[UnitType].rocket.angle,
detect = CounterfireRadarType[UnitType].rocket.detect,
poserror = CounterfireRadarType[UnitType].rocket.poserror,
maxaccuracy = CounterfireRadarType[UnitType].rocket.maxaccuracy
}
},
level = lvl or 11111, --Hierarchy level of radar. Format digit-digit-digit-digit-digit. Default to 11111 if no argument is provided
track = {} --Table to store the tracks of this radar
}
end
end
--Function to remove a counterfire radar from table
function RemoveCounterfireRadar(UnitName)
local coal = Unit.getByName(UnitName):getCoalition() --Get coalition of unit
if coal == 1 then --If units is red
for n = 1, #RedCounterfireRadar do --Iterate through all units
if RedCounterfireRadar[n].name == UnitName then --Find n-th entry that contains unit we want to remove
table.remove(RedCounterfireRadar, n) --Remove unit
break --Quit for loop
end
end
elseif coal == 2 then --If unit is blue
for n = 1, #BlueCounterfireRadar do --Iterate through all unit
if BlueCounterfireRadar[n].name == UnitName then --Find n-th entry that contains unit we want to remove
table.remove(BlueCounterfireRadar, n) --Remove unit
break --Quit for loop
end
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Management of Counterfire Radar Detection
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Function that checks if a shot was detected by a counterfire radar. Returns true of false
local function CounterfireDetection(Vec2, coal, wpn, n) --Arguments: Vec2: position of shot, coal: side that shot, wpn: mortar/artillery/rocket, n: number of the counterfire radar
if coal == 1 then --Shooter is red
local CFR = Unit.getByName(BlueCounterfireRadar[n].name) --Get counterfire radar
local CFRP3 = CFR:getPosition() --Get the P3 position of the counterfire radar
local CFRVec2 = {x = CFRP3.p.x, y = CFRP3.p.z} --Get the Vec2 position of the counterfire radar
local distance = GetDistance(Vec2, CFRVec2) --Get distance between counterfire radar and shot
local maxrange = BlueCounterfireRadar[n].type[wpn].maxrange --Get maxium detection range of counterfire radar for wpn type
local minrange = BlueCounterfireRadar[n].type[wpn].minrange --Get minimum detection range of counterfire radar for wpn type
if (distance <= maxrange) and (distance >= minrange) then --Check if shot is between min and max range of counterfire radar
local heading = GetHeading(CFRP3) --Get heading of counterfire radar
local bearing = GetHeadingBetween(CFRVec2, Vec2) --Get bearing of shot from counterfire radar
local zonemax = heading + BlueCounterfireRadar[n].type[wpn].angle --Get maximum detection azimuth of counterfire radar
local zonemin = heading - BlueCounterfireRadar[n].type[wpn].angle --Get minimum detection azimuth of counterfire radar
if zonemin < 1 then --Detection sector overlaps north on left side
if (bearing <= zonemax) or (bearing >= (zonemin + 360)) then --Check if shot is in detection sector
if math.random(1, 100) <= BlueCounterfireRadar[n].type[wpn].detect then --Apply chance of detection with available radar type
return true --If random check is true, detection is true
else
return false --If random check is false, detection is false
end
else
return false --If shot is outside detection sector, detection is false
end
elseif zonemax > 360 then --Detection sector overlaps north on right side
if (bearing >= zonemin) or (bearing <= (zonemax - 360)) then --Check if shot is in detection sector
if math.random(1, 100) <= BlueCounterfireRadar[n].type[wpn].detect then --Apply chance of detection with available radar type
return true --If random check is true, detection is true
else
return false --If random check is false, detection is false
end
else
return false --If shot is outside detection sector, detection is false
end
else --Detection sector does not overlap north
if (bearing >= zonemin) and (bearing <= zonemax) then --Check if shot is in detection sector
if math.random(1, 100) <= BlueCounterfireRadar[n].type[wpn].detect then --Apply chance of detection with available radar type
return true --If random check is true, detection is true
else
return false --If random check is false, detection is false
end
else
return false --If shot is outside detection sector, detection is false
end
end
else
return false --If shot is oustide detection range, detectin is false
end
elseif coal == 2 then --Shooter is blue
local CFR = Unit.getByName(RedCounterfireRadar[n].name) --Get counterfire radar
local CFRP3 = CFR:getPosition() --Get the P3 position of the counterfire radar
local CFRVec2 = {x = CFRP3.p.x, y = CFRP3.p.z} --Get the Vec2 position of the counterfire radar
local distance = GetDistance(Vec2, CFRVec2) --Get distance between counterfire radar and shot
local maxrange = RedCounterfireRadar[n].type[wpn].maxrange --Get maxium detection range of counterfire radar for wpn type
local minrange = RedCounterfireRadar[n].type[wpn].minrange --Get minimum detection range of counterfire radar for wpn type
if (distance <= maxrange) and (distance >= minrange) then --Check if shot is between min and max range of counterfire radar
local heading = GetHeading(CFRP3) --Get heading of counterfire radar
local bearing = GetHeadingBetween(CFRVec2, Vec2) --Get bearing of shot from counterfire radar
local zonemax = heading + RedCounterfireRadar[n].type[wpn].angle --Get maximum detection azimuth of counterfire radar
local zonemin = heading - RedCounterfireRadar[n].type[wpn].angle --Get minimum detection azimuth of counterfire radar
if zonemin < 1 then --Detection sector overlaps north on left side
if (bearing <= zonemax) or (bearing >= (zonemin + 360)) then --Check if shot is in detection sector
if math.random(1, 100) <= RedCounterfireRadar[n].type[wpn].detect then --Apply chance of detection with available radar type
return true --If random check is true, detection is true
else
return false --If random check is false, detection is false
end
else
return false --If shot is outside detection sector, detection is false
end
elseif zonemax > 360 then --Detection sector overlaps north on right side
if (bearing >= zonemin) or (bearing <= (zonemax - 360)) then --Check if shot is in detection sector
if math.random(1, 100) <= RedCounterfireRadar[n].type[wpn].detect then --Apply chance of detection with available radar type
return true --If random check is true, detection is true
else
return false --If random check is false, detection is false
end
else
return false --If shot is outside detection sector, detection is false
end
else --Detection sector does not overlap north
if (bearing >= zonemin) and (bearing <= zonemax) then --Check if shot is in detection sector
if math.random(1, 100) <= RedCounterfireRadar[n].type[wpn].detect then --Apply chance of detection with available radar type
return true --If random check is true, detection is true
else
return false --If random check is false, detection is false
end
else
return false --If shot is outside detection sector, detection is false
end
end
else
return false --If shot is oustide detection range, detectin is false
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Management of Counterfire Tracks
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Building a track. A track is the accumulation of multiple target positions into one average coordinate.
local function BuildBlueCFTrack(Vec2, Rn) --Function that takes target points (Vec2) and puts them into existing or new tracks.
for n = 0, #BlueCounterfireRadar[Rn].track do
if (#BlueCounterfireRadar[Rn].track > 0) and (n > 0) and (BlueCounterfireRadar[Rn].track[n].active == true) and ((Vec2.x < (BlueCounterfireRadar[Rn].track[n].coord.x + 200)) and (Vec2.x > (BlueCounterfireRadar[Rn].track[n].coord.x - 200))) and ((Vec2.y < (BlueCounterfireRadar[Rn].track[n].coord.y + 200)) and (Vec2.y > (BlueCounterfireRadar[Rn].track[n].coord.y - 200))) then --Check if track table is not empty and if the new target point Vec2 is within 200m of an existing track. If yes, average the existing track position with the new target position. If no, create a new track. The check is actually not within a 200m radius circle of the track but a 400x400m square around the track. Implemented like this for simplicity.
BlueCounterfireRadar[Rn].track[n].coord.x = (BlueCounterfireRadar[Rn].track[n].coord.x + Vec2.x) / 2 --Average the existing track x coordinate with the suppled coordinate
BlueCounterfireRadar[Rn].track[n].coord.y = (BlueCounterfireRadar[Rn].track[n].coord.y + Vec2.y) / 2 --Average the existing track y coordinate with the suppled coordinate
BlueCounterfireRadar[Rn].track[n].time[#BlueCounterfireRadar[Rn].track[n].time + 1] = timer.getTime() --Add the time of the update
BlueCounterfireRadar[Rn].track[n].active = true --This track is active again
do break end --Update of track completed, quit for loop
elseif n == #BlueCounterfireRadar[Rn].track then --If the last track n has been reached and wasn't update, create a new track
BlueCounterfireRadar[Rn].track[n + 1] = { --Create a new track that ...
coord = {x = Vec2.x, y = Vec2.y}, --holds the coordinate of the shot
time = {timer.getTime()}, --and the time of shot
active = true, --This new track is an active track
firemission = false --This new track does currently not have an assigned firemission
}
do break end --New track added, quit for loop
end
end
end
local function BuildRedCFTrack(Vec2, Rn) --Function that takes target points (Vec2) and puts them into existing or new tracks.
for n = 0, #RedCounterfireRadar[Rn].track do
if (#RedCounterfireRadar[Rn].track > 0) and (n > 0) and (RedCounterfireRadar[Rn].track[n].active == true) and ((Vec2.x < (RedCounterfireRadar[Rn].track[n].coord.x + 200)) and (Vec2.x > (RedCounterfireRadar[Rn].track[n].coord.x - 200))) and ((Vec2.y < (RedCounterfireRadar[Rn].track[n].coord.y + 200)) and (Vec2.y > (RedCounterfireRadar[Rn].track[n].coord.y - 200))) then --Check if track table is not empty and if the new target point Vec2 is within 200m of an existing track. If yes, average the existing track position with the new target position. If no, create a new track. The check is actually not within a 200m radius circle of the track but a 400x400m square around the track. Implemented like this for simplicity.
RedCounterfireRadar[Rn].track[n].coord.x = (RedCounterfireRadar[Rn].track[n].coord.x + Vec2.x) / 2 --Average the existing track x coordinate with the suppled coordinate
RedCounterfireRadar[Rn].track[n].coord.y = (RedCounterfireRadar[Rn].track[n].coord.y + Vec2.y) / 2 --Average the existing track y coordinate with the suppled coordinate
RedCounterfireRadar[Rn].track[n].time[#RedCounterfireRadar[Rn].track[n].time + 1] = timer.getTime() --Add the time of the update
RedCounterfireRadar[Rn].track[n].active = true --This track is active again
do break end --Update of track completed, quit for loop
elseif n == #RedCounterfireRadar[Rn].track then --If the last track n has been reached and wasn't update, create a new track
RedCounterfireRadar[Rn].track[n + 1] = { --Create a new track that ...
coord = {x = Vec2.x, y = Vec2.y}, --holds the coordinate of the shot
time = {timer.getTime()}, --and the time of shot
active = true, --This new track is an active track
firemission = false --This new track does currently not have an assigned firemission
}
do break end --New track added, quit for loop
end
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Detection of shot events, counting shots of batteries, manage counterfire radar detection, add shots to CFtracks
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Eventhandler to get shot events
do
EventHandler = {}
function EventHandler:onEvent(event)
if event.id == world.event.S_EVENT_SHOT then --Check if event is a shot event
local Init = event.initiator --Get initiator unit of shot event
local Wep = event.weapon --Get weapon of the shot event
local InitWep = Wep:getTypeName() --Get type of the weapon
if (InitWep == "weapons.shells.2A60_120") then --Check if shooter is a mortar unit
InitClass = "mortar" --Assign to mortar class
elseif (InitWep == "weapons.shells.M185_155") or (InitWep == "weapons.shells.2A18_122") or (InitWep == "weapons.shells.2A33_152" ) or (InitWep == "weapons.shells.2A64_152") then --Check if shooter is an artillery unit
InitClass = "artillery" --Assign to artillery class
elseif (InitWep == "weapons.nurs.M26") or (InitWep == "weapons.nurs.GRAD_9M22U") or (InitWep == "weapons.nurs.SMERCH_9M55K") then --Check if shooter is a rocket unit
InitClass = "rocket" --Assign to rocket class
end
local InitCoal = Init:getCoalition() --Get coalition of shooter
if (InitClass == "mortar") or (InitClass == "artillery") or (InitClass == "rocket") then --Check if shooter is an indirect fire unit
-----section for the battery shotcounter-----
for n = 1, #BlueFiringBattery do --This for-loop serves to count the shots from blue batteries to end fire missions after a certain number of shots.
if Init:getGroup():getName() == BlueFiringBattery[n].name then --If the firing unit is part of a group that is a blue battery...
BlueFiringBattery[n].shotcounter = BlueFiringBattery[n].shotcounter + 1 --then increase the shotcounter of this battery by one.
end
end
for n = 1, #RedFiringBattery do --This for-loop serves to count the shots from red batteries to end fire missions after a certain number of shots.
if Init:getGroup():getName() == RedFiringBattery[n].name then --If the firing unit is part of a group that is a red battery...
RedFiringBattery[n].shotcounter = RedFiringBattery[n].shotcounter + 1 --then increase the shotcounter of this battery by one.
end
end
-----continue with counterfire detection-----
local InitPosP3 = Init:getPosition() --Get P3 position of shooter
local InitPosV2 = {x = InitPosP3.p.x, y = InitPosP3.p.z} --Get Vec2 position of shooter
if InitCoal == 1 then --If shooter is red...
for n = 1, #BlueCounterfireRadar do --Iterate through all blue counterfire radars
if CounterfireDetection(InitPosV2, InitCoal, InitClass, n) then --CounterfireDetection() checks if the shot was detection by a counterfire radar. Returns true or false
--Here follows the section that applies the accuracy error on the shot detection
local CFR = Unit.getByName(BlueCounterfireRadar[n].name) --Get counterfire radar
local CFRP3 = CFR:getPosition() --Get the P3 position of the counterfire radar
local CFRVec2 = {x = CFRP3.p.x, y = CFRP3.p.z} --Get the Vec2 position of the counterfire radar
local distance = GetDistance(InitPosV2, CFRVec2) --Get the distance between the radar and the shot
local accuracy = distance / 100 * BlueCounterfireRadar[n].type[InitClass].poserror --Determine the accuracy of the detection for application of the position error.
if accuracy > BlueCounterfireRadar[n].type[InitClass].maxaccuracy then --If the accuracy is worse than maxaccuracy, use accuracy. Target coordinates should be somehwere within a circle radius 'accuracy' of the true target position. For simplicity we apply a square position error (x and y axis).
local heading = math.random(1, 360) --Get offset heading
local offset = math.random(0, accuracy) --Get offset distance
InitPosV2.x = InitPosV2.x + math.cos(heading) * offset --Apply position error to x axis
InitPosV2.y = InitPosV2.y + math.sin(heading) * offset --Apply position error to y axis
else --If the accuracy is better than maxaccuracy, use maxaccuracy. Target coordinates should be somehwere within a circle radius maxacuracy of the true target position. For simplicity we apply a square position error (x and y axis).
local heading = math.random(1, 360) --Get offset heading
local offset = math.random(0, BlueCounterfireRadar[n].type[InitClass].maxaccuracy) --Get offset distance
InitPosV2.x = InitPosV2.x + math.cos(heading) * offset --Apply position error to x axis
InitPosV2.y = InitPosV2.y + math.sin(heading) * offset --Apply position error to y axis
end
BuildBlueCFTrack(InitPosV2, n) --Execute function to build or update track
end
end
elseif InitCoal == 2 then --If shooter is blue...
for n = 1, #RedCounterfireRadar do --Iterate through all red counterfire radars
if CounterfireDetection(InitPosV2, InitCoal, InitClass, n) then --CounterfireDetection() checks if the shot was detection by a counterfire radar. Returns true or false
--Here follows the section that applies the accuracy error on the shot detection
local CFR = Unit.getByName(RedCounterfireRadar[n].name) --Get counterfire radar
local CFRP3 = CFR:getPosition() --Get the P3 position of the counterfire radar
local CFRVec2 = {x = CFRP3.p.x, y = CFRP3.p.z} --Get the Vec2 position of the counterfire radar
local distance = GetDistance(InitPosV2, CFRVec2) --Get the distance between the radar and the shot
local accuracy = distance / 100 * RedCounterfireRadar[n].type[InitClass].poserror --Determine the accuracy of the detection for application of the position error.
if accuracy > RedCounterfireRadar[n].type[InitClass].maxaccuracy then --If the accuracy is worse than maxaccuracy, use accuracy. Target coordinates should be somehwere within a circle radius 'accuracy' of the true target position. For simplicity we apply a square position error (x and y axis).
local heading = math.random(1, 360) --Get offset heading
local offset = math.random(0, accuracy) --Get offset distance
InitPosV2.x = InitPosV2.x + math.cos(heading) * offset --Apply position error to x axis
InitPosV2.y = InitPosV2.y + math.sin(heading) * offset --Apply position error to y axis
else --If the accuracy is better than maxaccuracy, use maxaccuracy. Target coordinates should be somehwere within a circle radius maxacuracy of the true target position. For simplicity we apply a square position error (x and y axis).
local heading = math.random(1, 360) --Get offset heading
local offset = math.random(0, RedCounterfireRadar[n].type[InitClass].maxaccuracy) --Get offset distance
InitPosV2.x = InitPosV2.x + math.cos(heading) * offset --Apply position error to x axis
InitPosV2.y = InitPosV2.y + math.sin(heading) * offset --Apply position error to y axis
end
BuildRedCFTrack(InitPosV2, n) --Execute function to build or update track
end
end
end
end
-----section to check if a battery is hit by artillery fire-----
elseif event.id == world.event.S_EVENT_HIT then
local Wep = event.weapon --Get weapon of the hit event
local InitWep = Wep:getTypeName() --Get type of the weapon
local Target = event.target --Get target of the hit event
local TargetCoal = Target:getCoalition() --Get coalition of target
local TargetGroupName = Target:getGroup():getName() --Get target group name
if (InitWep == "weapons.shells.2A60_120") or (InitWep == "weapons.shells.M185_155") or (InitWep == "weapons.shells.2A18_122") or (InitWep == "weapons.shells.2A33_152" ) or (InitWep == "weapons.shells.2A64_152") or (InitWep == "weapons.nurs.M26") or (InitWep == "weapons.nurs.GRAD_9M22U") or (InitWep == "weapons.nurs.SMERCH_9M55K") then --Check if weapon type is from artillery
if TargetCoal == 2 then --If target coalition is blue
for n = 1, #BlueFiringBattery do --Iterate through all blue batteries
if TargetGroupName == BlueFiringBattery[n].name then --Find battery that is hit
if BlueFiringBattery[n].displace == 2 or BlueFiringBattery[n].displace == 3 then --Check if battery should displace under fire
if BlueFiringBattery[n].status ~= "displacing" then --Check that the battery is not already displacing
BlueDisplacement(n, "under fire") --Perform displacement
end
end
end
end
elseif TargetCoal == 1 then --If target coalition is red
for n = 1, #RedFiringBattery do --Iterate through all red batteries
if TargetGroupName == RedFiringBattery[n].name then --Find battery that is hit
if RedFiringBattery[n].displace == 2 or RedFiringBattery[n].displace == 3 then --Check if battery should displace under fire
if RedFiringBattery[n].status ~= "displacing" then --Check that the battery is not already displacing
RedDisplacement(n, "under fire") --Perform displacement
end
end
end
end
end
end
end
end
world.addEventHandler(EventHandler)
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Management of Fire Missions
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Definition of the fire mission queues
local BlueFireMission = {} --Table that holds blue fire missions
local RedFireMission = {} --Table that holds red fire missions
--Function to add a red fire mission to the queue
local function AddBlueFireMission(Vec2, r, m, p, lvl, n)
BlueFireMission[#BlueFireMission + 1] = {
coord = Vec2, --Coordinates of the firemission in Vec2
radius = r, --Radius of the fire mission in m
mission = m, --Mission type, valid entries: "DS" = Direct Support, "CF" = Counterfire
priority = p, --Priority of the fire mission.
level = lvl, --Hierarchy level of the fire mission
track = n, --Track this firemission was created from, string
time = timer.getTime() --Set time of creation the firemission
}
end
--Function to add a red fire mission to the queue
local function AddRedFireMission(Vec2, r, m, p, lvl, n)
RedFireMission[#RedFireMission + 1] = {
coord = Vec2, --Coordinates of the firemission in Vec2
radius = r, --Radius of the fire mission in m
mission = m, --Mission type, valid entries: "DS" = Direct Support, "CF" = Counterfire
priority = p, --Priority of the fire mission
level = lvl, --Hierarchy level of the fire mission
track = n, --Track this firemission was created from, string
time = timer.getTime() --Set time of creation the firemission
}
end
--Creating fire missions from tracks
function CreateFireMission() --Function to create fire missions from tracks. To be repeated every 10 seconds.
local currentTime = timer.getTime()
-----blue counterfire tracks-----
for m = 1, #BlueCounterfireRadar do --Iterate through all blue counterfire radars
for n = 1, #BlueCounterfireRadar[m].track do --Iterate through all tracks of these radars
if BlueCounterfireRadar[m].track[n].active == true then --Check if the track is still active
if currentTime > (BlueCounterfireRadar[m].track[n].time[#BlueCounterfireRadar[m].track[n].time] + 30) then --Check if the track has been updated in the last 30 seconds.
BlueCounterfireRadar[m].track[n].active = false --Deactivate the track
else
if BlueCounterfireRadar[m].track[n].firemission == false then --Check if track has already generated an active firemission
local p = 0 --Counter p is the amount of updates of the track (fired enemy shots) in the last 30 seconds. The higher p, the higher the priority the fire mission receives.
for i = #BlueCounterfireRadar[m].track[n].time, 1, -1 do --Iterate through all updates of the track, from the lates to the oldest.
if BlueCounterfireRadar[m].track[n].time[i] > (currentTime - 10) then --Check if the update of the track happened within the past 10 seconds.
p = p + 1 --The update happened in the past 10 second, increase counter p by 1.
else
break --The update is older than 30 seconds, quit the iteration of the rest of the updates which are even older
end
end
local priority = 0
if p < 3 then --If less than 3 shots in last 10 seconds, assign priority 10 (equals 1 arty piece)
priority = 10
elseif p >= 3 and p < 12 then --If between 3 and 12 shots in last 10 seconds, assign priority 30 (equals 3 arty pieces)
priority = 30
elseif p >= 12 then
priority = 60 --If more than 12 shots in last 10 seconds, assign priority 60 (equals 6 arty pieces)
end
AddBlueFireMission(BlueCounterfireRadar[m].track[n].coord, 100, "CF", priority, BlueCounterfireRadar[m].level, "BlueCounterfireRadar[" .. m .. "].track[" .. n .. "]") --Add a counterfire firemission to queue.
BlueCounterfireRadar[m].track[n].firemission = true --Track has created an active firemission and will therefore not create any more firemissions
end
end
end
end
end
-----red counterfire tracks-----
for m = 1, #RedCounterfireRadar do --Iterate through all red counterfire radars
for n = 1, #RedCounterfireRadar[m].track do --Iterate through all tracks of these radars
if RedCounterfireRadar[m].track[n].active == true then --Check if the track is still active
if currentTime > (RedCounterfireRadar[m].track[n].time[#RedCounterfireRadar[m].track[n].time] + 30) then --Check if the track has been updated in the last 30 seconds.
RedCounterfireRadar[m].track[n].active = false --Deactivate the track
else
if RedCounterfireRadar[m].track[n].firemission == false then --Check if track has already generated an active firemission
local p = 0 --Counter p is the amount of updates of the track (fired enemy shots) in the last 30 seconds. The higher p, the higher the priority the fire mission receives.
for i = #RedCounterfireRadar[m].track[n].time, 1, -1 do --Iterate through all updates of the track, from the lates to the oldest.
if RedCounterfireRadar[m].track[n].time[i] > (currentTime - 10) then --Check if the update of the track happened within the past 30 seconds.
p = p + 1 --The update happened in the past 10 second, increase counter p by 1.
else
break --The update is older than 10 seconds, quit the iteration of the rest of the updates which are even older
end
end
local priority = 0
if p < 3 then --If less than 3 shots in last 10 seconds, assign priority 10 (equals 1 arty piece)
priority = 10
elseif p >= 3 and p < 12 then --If between 3 and 12 shots in last 10 seconds, assign priority 30 (equals 3 arty pieces)
priority = 30
elseif p >= 12 then
priority = 60 --If more than 12 shots in last 3 seconds, assign priority 60 (equals 6 arty pieces)
end
AddRedFireMission(RedCounterfireRadar[m].track[n].coord, 100, "CF", priority, RedCounterfireRadar[m].level, "RedCounterfireRadar[" .. m .. "].track[" .. n .. "]") --Add a counterfire firemission to queue.
RedCounterfireRadar[m].track[n].firemission = true --Track has created an active firemission and will therefore not create any more firemissions
end
end
end
end
end
-----blue spotter tracks-----
for m = 1, #BlueSpotter do --Iterate through all blue spotters
for n = 1, #BlueSpotter[m].track do --Iterate through all blue spotter tracks
if BlueSpotter[m].track[n].active == true then --Check if track is active
if currentTime > BlueSpotter[m].track[n].time + 30 then --Check if track is more than 30 seconds old
BlueSpotter[m].track[n].active = false --Deactivate track
else
if BlueSpotter[m].track[n].firemission == false then --Check if track has already generated a firemission
AddBlueFireMission(BlueSpotter[m].track[n].coord, 100, "DS", BlueSpotter[m].track[n].value, BlueSpotter[m].level, "BlueSpotter[" .. m .. "].track[" .. n .. "]") --Add firemission to queue
BlueSpotter[m].track[n].firemission = true --A firemission is now assigned for this track
end
end
end
end
end
-----red spotter tracks-----
for m = 1, #RedSpotter do --Iterate through all red spotters
for n = 1, #RedSpotter[m].track do --Iterate through all red spotter tracks
if RedSpotter[m].track[n].active == true then --Check if track is active
if currentTime > RedSpotter[m].track[n].time + 30 then --Check if track is more than 30 seconds old
RedSpotter[m].track[n].active = false --Deactivate track
else
if RedSpotter[m].track[n].firemission == false then --Check if track has already generated a firemission
AddRedFireMission(RedSpotter[m].track[n].coord, 100, "DS", RedSpotter[m].track[n].value, RedSpotter[m].level, "RedSpotter[" .. m .. "].track[" .. n .. "]") --Add firemission to queue
RedSpotter[m].track[n].firemission = true --A firemission is now assigned for this track
end
end
end
end
end
return timer.getTime() + 10 --Repeat function every 10 senconds
end
timer.scheduleFunction(CreateFireMission, arg, 10) --Execute the reccuring CreateFireMission function for the first time after 10 seconds
--Function to execute a blue fire mission
function ShootBlueFireMission(GroupName, Vec2, r, Bn, Tn) --Arguments: GroupName: Name of firing group as string; Vec2 position of target; r: radius of fire mission in meter; Bn: number n of the FiringBattery; Tn: string, name of the track this firemission was build from)
FireAtPointControlledTask = { --Defining a controlled task, which includes a stop condition for the task.
id = 'ControlledTask',
params = {
task = { --Defining the task
id = 'FireAtPoint',
params = {
point = Vec2,
radius = r
}
},
stopCondition = { --Primary stop conditio: when the battery has fired the amount of shots as defined by FM_rounds.
condition = "if BlueFiringBattery[" .. Bn .. "].shotcounter == BlueFiringBattery[" .. Bn .. "].FM_rounds then" .. [[
]] .. "BlueDisplacement(" .. Bn .. ")" .. [[
]] .. "local DelayTrackFMfalse = function() " .. Tn .. ".firemission = false end" .. [[
]] .. "timer.scheduleFunction(DelayTrackFMfalse, nil, timer.getTime() + 60)" .. [[
]] .. "return true end", --After mission is complete, return status of battery back to ready and remove the firemission flag from the respective track
duration = 360 --Alternate stop condition. Stop fire mission after 6 minutes.
}
}
}
Group.getByName(GroupName):getController():pushTask(FireAtPointControlledTask)
end
--Function to execute a red fire mission
function ShootRedFireMission(GroupName, Vec2, r, Bn, Tn) --Arguments: GroupName: Name of firing group as string; Vec2 position of target; r: radius of fire mission in meter; Bn: number n of the FiringBattery; Tn: string, name of the track this firemission was build from
FireAtPointControlledTask = { --Defining a controlled task, which includes a stop condition for the task.
id = 'ControlledTask',
params = {
task = { --Defining the task
id = 'FireAtPoint',
params = {
point = Vec2,
radius = r
}
},
stopCondition = { --Primary stop conditio: when the battery has fired the amount of shots as defined by FM_rounds.
condition = "if RedFiringBattery[" .. Bn .. "].shotcounter == RedFiringBattery[" .. Bn .. "].FM_rounds then" .. [[
]] .. "RedDisplacement(" .. Bn .. ")" .. [[
]] .. "local DelayTrackFMfalse = function() " .. Tn .. ".firemission = false end" .. [[
]] .. "timer.scheduleFunction(DelayTrackFMfalse, nil, timer.getTime() + 60)" .. [[
]] .. "return true end", --After mission is complete, return status of battery back to ready and remove the firemission flag from the respective track
duration = 360 --Alternate stop condition. Stop fire mission after 6 minutes.
}
}
}
Group.getByName(GroupName):getController():pushTask(FireAtPointControlledTask)
end
--Function to execute a blue nuclear fire mission
function ShootBlueNukeFireMission(GroupName, Vec2, r, Bn, Tn) --Arguments: GroupName: Name of firing group as string; Vec2 position of target; r: radius of fire mission in meter; Bn: number n of the FiringBattery; Tn: string, name of the track this firemission was build from)
FireAtPointControlledTask = { --Defining a controlled task, which includes a stop condition for the task.
id = 'ControlledTask',
params = {
task = { --Defining the task
id = 'FireAtPoint',
params = {
point = Vec2,
radius = r
}
},
stopCondition = { --Primary stop conditio: when the battery has fired the amount of shots as defined by FM_rounds.
condition = "if BlueFiringBattery[" .. Bn .. "].shotcounter == 1 then" .. [[
]] .. "BlueDisplacement(" .. Bn .. ")" .. [[
]] .. "BlueFiringBattery[" .. Bn .. "].nukes = BlueFiringBattery[" .. Bn .. "].nukes - 1" .. [[
]] .. "local DelayTrackFMfalse = function() " .. Tn .. ".firemission = false end" .. [[
]] .. "timer.scheduleFunction(DelayTrackFMfalse, nil, timer.getTime() + 60)" .. [[
]] .. "return true end", --After mission is complete, return status of battery back to ready and remove the firemission flag from the respective track
duration = 360 --Alternate stop condition. Stop fire mission after 6 minutes.
}
}
}
Group.getByName(GroupName):getController():pushTask(FireAtPointControlledTask)
end
--Function to execute a red nuclear fire mission
function ShootRedNukeFireMission(GroupName, Vec2, r, Bn, Tn) --Arguments: GroupName: Name of firing group as string; Vec2 position of target; r: radius of fire mission in meter; Bn: number n of the FiringBattery; Tn: string, name of the track this firemission was build from
FireAtPointControlledTask = { --Defining a controlled task, which includes a stop condition for the task.
id = 'ControlledTask',
params = {
task = { --Defining the task
id = 'FireAtPoint',
params = {
point = Vec2,
radius = r
}
},
stopCondition = { --Primary stop conditio: when the battery has fired the amount of shots as defined by FM_rounds.
condition = "if RedFiringBattery[" .. Bn .. "].shotcounter == 1 then" .. [[
]] .. "RedDisplacement(" .. Bn .. ")" .. [[
]] .. "RedFiringBattery[" .. Bn .. "].nukes = RedFiringBattery[" .. Bn .. "].nukes - 1" .. [[
]] .. "local DelayTrackFMfalse = function() " .. Tn .. ".firemission = false end" .. [[
]] .. "timer.scheduleFunction(DelayTrackFMfalse, nil, timer.getTime() + 60)" .. [[
]] .. "return true end", --After mission is complete, return status of battery back to ready and remove the firemission flag from the respective track
duration = 360 --Alternate stop condition. Stop fire mission after 6 minutes.
}
}
}
Group.getByName(GroupName):getController():pushTask(FireAtPointControlledTask)
end
--Function that checks if a battery has enough ammo to execute a fire mission
function CheckAmmo(grp) --Input group
local groupName = grp:getName() --Get name of group
local groupType = getGroupType(groupName) --Get type of group (custom function)
local units = grp:getUnits() --Get units of group
local minAmmo = ArtilleryProperties[groupType].minAmmo --minAmmo is the number of rounds left after rapid fire
local BatteryAmmo = 0
for n = 1, #units do --Iterate through all units of the battery
if units[n]:getTypeName() == groupType then --Check if a unit is of the same type as the battery
local ammo = units[n]:getAmmo() --Get ammo for this unit
if ammo[1] then --Check if ammo[1] exists. If the ammo is used up, it returns nil...
local UnitAmmo = ammo[1].count --Get the shell count for this unit
if UnitAmmo > minAmmo then --Check if there is ready ammo left
local UnitReadyAmmo = UnitAmmo - minAmmo --Calculate amount of ready ammo
BatteryAmmo = BatteryAmmo + UnitReadyAmmo --Add unit ready ammo to total of group
end
end
end
end
if BatteryAmmo >= ArtilleryProperties[groupType].FM_rounds then --Check if the amount of ready ammo of the battery is equal or bigger then the number of rounds for a fire mission
return true --Return true if yes
else
return false
end
end
--Assigning fire missions to batteries and executing the task
function AssignFireMission() --Function to assign fire missions to available batteries. To be repeated every 10 seconds.
-----------------------------This section sorts the blue firemissions by priority, from highest to lowest
if #BlueFireMission >= 2 then --Sorting is only required for 2 or more firemissions in queue
local T = {}
T[1] = BlueFireMission[1]
for n = 2, #BlueFireMission do
for i = 1, #T do
if BlueFireMission[n].priority >= T[i].priority then
table.insert(T, i, BlueFireMission[n])
do break end
elseif i == #T then
table.insert(T, i + 1, BlueFireMission[n])
break
end
end
end
BlueFireMission = T
end
----------------------------This section tries to assign a blue fire mission to a blue battery
if #BlueFireMission > 0 then --If a blue fire mission exists, try to assign it to a battery
local n = 1
repeat --Iterate through all blue fire missions. We use a repeat loop instead of for since the content of the loop adjusts both the counter and the end condition.
local loop = true --Local variable that controls which Hierarchy levels should be checked. If a FM is assigned, the variable is set false so that higher Hierarchy levels are not checked.
--------------------Try to assign FM to a battery of Hierarchy level 1
for i = 1, #BlueFiringBattery do
if BlueFireMission[n].level == BlueFiringBattery[i].level then --Check if FM matches Hierarchy level of battery
if BlueFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (BlueFiringBattery[i].mission == "All") or (BlueFiringBattery[i].mission == BlueFireMission[n].mission) then --Check if battery can accept the mission type
if BlueFireMission[n].priority >= BlueFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(BlueFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(BlueFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= BlueFiringBattery[i].minrange) and (dist <= BlueFiringBattery[i].maxrange) then --Check if battery is within range of target
if BlueFiringBattery[i].nukes > 0 and BlueFireMission[n].priority >= BlueFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueNukeFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, 0, i, BlueFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(BlueFiringBattery[i].name) --Function to track the shot of a nuclear shell
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end --Quit battery loop for this Hierarchy.
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, BlueFireMission[n].radius, i, BlueFireMission[n].track) --Execute fire mission
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 2
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #BlueFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(BlueFireMission[n].level / 10) * 10 == BlueFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if BlueFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (BlueFiringBattery[i].mission == "All") or (BlueFiringBattery[i].mission == BlueFireMission[n].mission) then --Check if battery can accept the mission type
if BlueFireMission[n].priority >= BlueFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(BlueFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(BlueFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= BlueFiringBattery[i].minrange) and (dist <= BlueFiringBattery[i].maxrange) then --Check if battery is within range of target
if BlueFiringBattery[i].nukes > 0 and BlueFireMission[n].priority >= BlueFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueNukeFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, 0, i, BlueFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(BlueFiringBattery[i].name) --Function to track the shot of a nuclear shell
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, BlueFireMission[n].radius, i, BlueFireMission[n].track) --Execute fire mission
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 3
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #BlueFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(BlueFireMission[n].level / 100) * 100 == BlueFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if BlueFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (BlueFiringBattery[i].mission == "All") or (BlueFiringBattery[i].mission == BlueFireMission[n].mission) then --Check if battery can accept the mission type
if BlueFireMission[n].priority >= BlueFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(BlueFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(BlueFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= BlueFiringBattery[i].minrange) and (dist <= BlueFiringBattery[i].maxrange) then --Check if battery is within range of target
if BlueFiringBattery[i].nukes > 0 and BlueFireMission[n].priority >= BlueFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueNukeFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, 0, i, BlueFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(BlueFiringBattery[i].name) --Function to track the shot of a nuclear shell
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, BlueFireMission[n].radius, i, BlueFireMission[n].track) --Execute fire mission
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 4
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #BlueFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(BlueFireMission[n].level / 1000) * 1000 == BlueFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if BlueFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (BlueFiringBattery[i].mission == "All") or (BlueFiringBattery[i].mission == BlueFireMission[n].mission) then --Check if battery can accept the mission type
if BlueFireMission[n].priority >= BlueFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(BlueFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(BlueFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= BlueFiringBattery[i].minrange) and (dist <= BlueFiringBattery[i].maxrange) then --Check if battery is within range of target
if BlueFiringBattery[i].nukes > 0 and BlueFireMission[n].priority >= BlueFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueNukeFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, 0, i, BlueFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(BlueFiringBattery[i].name) --Function to track the shot of a nuclear shell
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, BlueFireMission[n].radius, i, BlueFireMission[n].track) --Execute fire mission
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 5
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #BlueFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(BlueFireMission[n].level / 10000) * 10000 == BlueFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if BlueFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (BlueFiringBattery[i].mission == "All") or (BlueFiringBattery[i].mission == BlueFireMission[n].mission) then --Check if battery can accept the mission type
if BlueFireMission[n].priority >= BlueFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(BlueFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(BlueFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= BlueFiringBattery[i].minrange) and (dist <= BlueFiringBattery[i].maxrange) then --Check if battery is within range of target
if BlueFiringBattery[i].nukes > 0 and BlueFireMission[n].priority >= BlueFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueNukeFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, 0, i, BlueFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(BlueFiringBattery[i].name) --Function to track the shot of a nuclear shell
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
BlueFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootBlueFireMission(BlueFiringBattery[i].name, BlueFireMission[n].coord, BlueFireMission[n].radius, i, BlueFireMission[n].track) --Execute fire mission
BlueFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Remove outdated FM
if loop == true then
if BlueFireMission[n].time < (timer.getTime() - 600) then --Check if creation time fo firemission is older than 10 minutes
local x = BlueFireMission[n].track .. ".firemission = false" --Create a string of the location of the original track as string + ".firemission = false" which holds the booleadn that this track has no offspring firemission
loadstring(x)() --Eexecute the avove string as LUA code to let the track generate a firemission again
table.remove(BlueFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
end
end
n = n + 1
until n > #BlueFireMission
end
----------------------------This section sorts the red firemissions by priority, from highest to lowest
if #RedFireMission >= 2 then --Sorting is only required for 2 or more firemissions in queue
local T = {}
T[1] = RedFireMission[1]
for n = 2, #RedFireMission do
for i = 1, #T do
if RedFireMission[n].priority >= T[i].priority then
table.insert(T, i, RedFireMission[n])
do break end
elseif i == #T then
table.insert(T, i + 1, RedFireMission[n])
break
end
end
end
RedFireMission = T
end
---------------------------This section tries to assign a red fire mission to a red battery
if #RedFireMission > 0 then --If a red fire mission exists, try to assign it to a battery
local n = 1
repeat --Iterate through all Red fire missions. We use a repeat loop instead of for since the content of the loop adjusts both the counter and the end condition.
local loop = true --Local variable that controls which hierarchy levels should be checked. If a FM is assigned, the variable is set false so that higher hierarchy levels are not checked.
--------------------Try to assign FM to a battery of hierarchy level 1
for i = 1, #RedFiringBattery do
if RedFireMission[n].level == RedFiringBattery[i].level then --Check if FM matches hierarchy level of battery
if RedFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (RedFiringBattery[i].mission == "All") or (RedFiringBattery[i].mission == RedFireMission[n].mission) then --Check if battery can accept the mission type
if RedFireMission[n].priority >= RedFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(RedFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(RedFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= RedFiringBattery[i].minrange) and (dist <= RedFiringBattery[i].maxrange) then --Check if battery is within range of target
if RedFiringBattery[i].nukes > 0 and RedFireMission[n].priority >= RedFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedNukeFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, 0, i, RedFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(RedFiringBattery[i].name) --Function to track the shot of a nuclear shell
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, RedFireMission[n].radius, i, RedFireMission[n].track) --Execute fire mission
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 2
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #RedFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(RedFireMission[n].level / 10) * 10 == RedFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if RedFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (RedFiringBattery[i].mission == "All") or (RedFiringBattery[i].mission == RedFireMission[n].mission) then --Check if battery can accept the mission type
if RedFireMission[n].priority >= RedFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(RedFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(RedFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= RedFiringBattery[i].minrange) and (dist <= RedFiringBattery[i].maxrange) then --Check if battery is within range of target
if RedFiringBattery[i].nukes > 0 and RedFireMission[n].priority >= RedFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedNukeFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, 0, i, RedFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(RedFiringBattery[i].name) --Function to track the shot of a nuclear shell
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, RedFireMission[n].radius, i, RedFireMission[n].track) --Execute fire mission
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 3
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #RedFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(RedFireMission[n].level / 100) * 100 == RedFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if RedFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (RedFiringBattery[i].mission == "All") or (RedFiringBattery[i].mission == RedFireMission[n].mission) then --Check if battery can accept the mission type
if RedFireMission[n].priority >= RedFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(RedFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(RedFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= RedFiringBattery[i].minrange) and (dist <= RedFiringBattery[i].maxrange) then --Check if battery is within range of target
if RedFiringBattery[i].nukes > 0 and RedFireMission[n].priority >= RedFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedNukeFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, 0, i, RedFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(RedFiringBattery[i].name) --Function to track the shot of a nuclear shell
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, RedFireMission[n].radius, i, RedFireMission[n].track) --Execute fire mission
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 4
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #RedFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(RedFireMission[n].level / 1000) * 1000 == RedFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if RedFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (RedFiringBattery[i].mission == "All") or (RedFiringBattery[i].mission == RedFireMission[n].mission) then --Check if battery can accept the mission type
if RedFireMission[n].priority >= RedFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(RedFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(RedFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= RedFiringBattery[i].minrange) and (dist <= RedFiringBattery[i].maxrange) then --Check if battery is within range of target
if RedFiringBattery[i].nukes > 0 and RedFireMission[n].priority >= RedFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedNukeFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, 0, i, RedFireMission[n].track) --Execute a nuclear fire mission
NuclearShell(RedFiringBattery[i].name) --Function to track the shot of a nuclear shell
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, RedFireMission[n].radius, i, RedFireMission[n].track) --Execute fire mission
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Try to assign FM to a battery of hierarchy level 5
if loop == true then --Check if attempt should be made to assign FM on this level
for i = 1, #RedFiringBattery do --Iterate through all batteries of this hierarchy level
if math.floor(RedFireMission[n].level / 10000) * 10000 == RedFiringBattery[i].level then --Check if FM matches the hierarchy level of battery
if RedFiringBattery[i].status == "ready" then --Check if battery is ready to fire
if (RedFiringBattery[i].mission == "All") or (RedFiringBattery[i].mission == RedFireMission[n].mission) then --Check if battery can accept the mission type
if RedFireMission[n].priority >= RedFiringBattery[i].priority then --Check if priority of fire mission is equal or bigger than the minimum priority of the battery
local Battery = Group.getByName(RedFiringBattery[i].name) --Get the battery
local BatteryUnit1 = Battery:getUnit(1) --Get the first unit of the battery
local BatteryPos = BatteryUnit1:getPosition() --Get Vec3 position of battery
local dist = GetDistance(RedFireMission[n].coord, {x = BatteryPos.p.x, y = BatteryPos.p.z}) --Get distance between battery and target
if (dist >= RedFiringBattery[i].minrange) and (dist <= RedFiringBattery[i].maxrange) then --Check if battery is within range of target
if RedFiringBattery[i].nukes > 0 and RedFireMission[n].priority >= RedFiringBattery[i].nukepriority then --Check if battery is eligible for a nuclear strike. Amount of nuclear shells > 0 and priority of firemission >= minimum priority of battery for nuclear attack
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
NuclearShell(RedFiringBattery[i].name) --Function to track the shot of a nuclear shell
ShootRedNukeFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, 0, i, RedFireMission[n].track) --Execute a nuclear fire mission
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
do break end
elseif CheckAmmo(Battery) then --Check if the battery has ammo left for the fire mission
RedFiringBattery[i].shotcounter = 0 --Reset the battery shotcounter to zero
ShootRedFireMission(RedFiringBattery[i].name, RedFireMission[n].coord, RedFireMission[n].radius, i, RedFireMission[n].track) --Execute fire mission
RedFiringBattery[i].status = "firing" --Set the status of the battery to "firing"
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
loop = false --FM is assigned, no higher hirarchies have to be checked.
break --Quit battery loop for this hierarchy.
end
end
end
end
end
end
end
end
--------------------Remove outdated FM
if loop == true then
if RedFireMission[n].time < (timer.getTime() - 600) then --Check if creation time fo firemission is older than 10 minutes
local x = RedFireMission[n].track .. ".firemission = false" --Create a string of the location of the original track as string + ".firemission = false" which holds the booleadn that this track has no offspring firemission
loadstring(x)() --Eexecute the avove string as LUA code to let the track generate a firemission again
table.remove(RedFireMission, n) --Remove the executed fire mission from the queue
n = n - 1 --Decrease counter n because next firemission is now already n
end
end
n = n + 1
until n > #RedFireMission
end
----------------
return timer.getTime() + 10 --Repeat function every 10 senconds
end
timer.scheduleFunction(AssignFireMission, arg, 11) --Execute the reccuring AssignFireMission function for the first time after 11 seconds
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--Debug
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
local function RedBatteryStatus()
if #RedFiringBattery == 1 then
return "Red Batteries:\n" .. RedFiringBattery[1].name .. ": " .. RedFiringBattery[1].status .. "\n"
elseif #RedFiringBattery > 1 then
local a = "Red Batteries:\n"
for n = 1, #RedFiringBattery do
a = a .. RedFiringBattery[n].name .. ": " .. RedFiringBattery[n].status .. "\n"
end
return a
else
return "\nRed Batteries:\nnone\n"
end
end
local function BlueBatteryStatus()
if #BlueFiringBattery == 1 then
return "\nBlue Batteries:\n" .. BlueFiringBattery[1].name .. ": " .. BlueFiringBattery[1].status .. "\n"
elseif #BlueFiringBattery > 1 then
local a = "\nBlue Batteries:\n"
for n = 1, #BlueFiringBattery do
a = a .. BlueFiringBattery[n].name .. ": " .. BlueFiringBattery[n].status .. "\n"
end
return a
else
return "\nBlue Batteries:\nnone\n"
end
end
local function RedSpotterStatus()
if #RedSpotter == 1 then
return "\nRed Spotters:\n" .. RedSpotter[1].name .. ": Targets: " .. #RedSpotter[1].target .. ", Tracks: " .. #RedSpotter[1].track .. "\n"
elseif #RedSpotter > 1 then
local a = "\nRed Spotter:\n"
for n = 1, #RedSpotter do
a = a .. RedSpotter[n].name .. ": Targets: " .. #RedSpotter[n].target .. ", Tracks: " .. #RedSpotter[n].track .. "\n"
end
return a
else
return "\nRed Spotters:\nnone\n"
end
end
local function BlueSpotterStatus()
if #BlueSpotter == 1 then
return "\nBlue Spotters:\n" .. BlueSpotter[1].name .. ": Targets: " .. #BlueSpotter[1].target .. ", Tracks: " .. #BlueSpotter[1].track .. "\n"
elseif #BlueSpotter > 1 then
local a = "\nBlue Spotter:\n"
for n = 1, #BlueSpotter do
a = a .. BlueSpotter[n].name .. ": Targets: " .. #BlueSpotter[n].target .. ", Tracks: " .. #BlueSpotter[n].track .. "\n"
end
return a
else
return "\nBlue Spotters:\nnone\n"
end
end
local function RedCounterfireRadarStatus()
if #RedCounterfireRadar == 1 then
return "\nRed CounterfireRadars:\n" .. RedCounterfireRadar[1].name .. ": Tracks: " .. #RedCounterfireRadar[1].track .. "\n"
elseif #RedCounterfireRadar > 1 then
local a = "\nRed CounterfireRadar:\n"
for n = 1, #RedCounterfireRadar do
a = a .. RedCounterfireRadar[n].name .. ": Tracks: " .. #RedCounterfireRadar[n].track .. "\n"
end
return a
else
return "\nRed CounterfireRadars:\nnone\n"
end
end
local function BlueCounterfireRadarStatus()
if #BlueCounterfireRadar == 1 then
return "\nBlue CounterfireRadars:\n" .. BlueCounterfireRadar[1].name .. ": Tracks: " .. #BlueCounterfireRadar[1].track .. "\n"
elseif #BlueCounterfireRadar > 1 then
local a = "\nBlue CounterfireRadar:\n"
for n = 1, #BlueCounterfireRadar do
a = a .. BlueCounterfireRadar[n].name .. ": Tracks: " .. #BlueCounterfireRadar[n].track .. "\n"
end
return a
else
return "\nBlue CounterfireRadars:\nnone\n"
end
end
local function RedFireMissionStatus()
return "\nRed FireMissions in Queue: " .. #RedFireMission
end
local function BlueFireMissionStatus()
return "\nBlue FireMissions in Queue: " .. #BlueFireMission
end
function StatusOverview()
trigger.action.outText(RedBatteryStatus() .. BlueBatteryStatus() .. RedSpotterStatus() .. BlueSpotterStatus() .. RedCounterfireRadarStatus() .. BlueCounterfireRadarStatus() .. RedFireMissionStatus() .. BlueFireMissionStatus(), 1)
return timer.getTime() + 1
end
--timer.scheduleFunction(StatusOverview, nil, 2) --Remove the "--" at the beginning of this line to activate status overview window.
end
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment