Jump to content

Suppression Fire Script


MBot

Recommended Posts

This small script makes infantry, MANPADS, ZU-23 and Ural-375 ZU-23 suppressable by fire.

 

The approach to simulate a suppressed state is to control ROE, therefore making the suppressed group unable to return fire. Each time an infantry, MANPADS or ZU-23 unit is hit by a weapon (either by direct hit or by a close by explosion), the group of this unit is set to ROE hold fire and will therefore cease fire. After 15 second the suppressed state ends and the group is set back to ROE open fire again. Subsequent hits on an already suppressed group will prolong the suppressed period. Initial additional hits add significantly more suppression, but subsequent hits will add less additional suppression each.

 

The approach with ROE is rather crude and will sometimes cause unrealistic results. Yet I think that this is still better than having no suppression at all.

 

Usage of script: Simply run it once in the ME with "do script" or "do script file".

 

Note: Currently hits with the M134 Miniguns of the Huey are not tracked by the game. This makes Miniguns not trigger suppression effects. This is a bug with DCS that will hopefully be solved soon. Huey rockets and door guns already work fine with this script.

Edit: No longer the case, miniguns work too.

 

 

 

 

do

 

local SuppressedGroups = {} --Table to temporary store data for suppressed groups

 

--Function to end suppression and let group open fire again

local function SuppressionEnd(id)

id.ctrl:setOption(AI.Option.Ground.id.ROE , AI.Option.Ground.val.ROE.OPEN_FIRE)

SuppressedGroups[id.groupName] = nil

--trigger.action.outText(id.groupName .. " suppression end", 2) --Info for debug

end

local SuppressionEndCounter = 0 --Since SuppressionEnd() is a scheduled function it can exist in multiple instances at the same time. This counter will be used to identify each instance with the subsequent number.

 

--Function to run suppress a group

local function SuppressGroup(tgt)

local delay = math.random(15, 45) --Time in seconds the group of a hit unit will be unable to fire

 

local id = {

groupName = tgt:getGroup():getName(),

ctrl = tgt:getGroup():getController()

}

 

if SuppressedGroups[id.groupName] == nil then --If group is currently not suppressed, add to table.

SuppressionEndCounter = SuppressionEndCounter + 1 --Increase counter to indentify instance of comming SuppressionEnd() scheduled function

SuppressedGroups[id.groupName] = {

SuppressionEndTime = timer.getTime() + delay,

SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function

}

timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function

else --If group is already suppressed, update table and increase delay

local timeleft = SuppressedGroups[id.groupName].SuppressionEndTime - timer.getTime() --Get how long to the end of the suppression

local addDelay = (delay / timeleft) * delay --The longer the suppression is going to last, the smaller it is going to be increased by additional hits

if timeleft < delay then --But if the time left is shorter than a complete delay, add another full delay

addDelay = delay

end

SuppressedGroups[id.groupName].SuppressionEndTime = SuppressedGroups[id.groupName].SuppressionEndTime + addDelay

timer.setFunctionTime(SuppressedGroups[id.groupName].SuppressionEndN, SuppressedGroups[id.groupName].SuppressionEndTime) --Update the execution time of the existing instance of the SuppressionEnd() scheduled function

end

 

id.ctrl:setOption(AI.Option.Ground.id.ROE , AI.Option.Ground.val.ROE.WEAPON_HOLD) --Set ROE weapons hold to initate suppression

--trigger.action.outText(id.groupName .. " suppressed until " .. SuppressedGroups[id.groupName].SuppressionEndTime, 2) --Info for debug

end

 

--Handler to get when units are hit

SuppressionHandler = {}

function SuppressionHandler:onEvent(event)

if event.id == world.event.S_EVENT_HIT then

local tgt = event.target

local tgtType = tgt:getTypeName()

if tgt:hasAttribute("Infantry") or tgt:hasAttribute("Static AAA") or (tgtType == "Ural-375 ZU-23") then --Check if hit unit is infantry, static or mobile ZU-23

SuppressGroup(tgt) --Run suppression of hit unit (group)

end

end

end

world.addEventHandler(SuppressionHandler)

 

end

 

SuppressionFireScript.lua


Edited by MBot
Script updated to work with DCS 1.2.6
  • Like 8
Link to comment
Share on other sites

Very nice! I did something similar for Arma 1 ages ago... How about adding a chance that the group under fire will flee from its attackers? You could put a waypoint 180 degrees from the attackers bearing and move in "Green"-mode as groups in this state will move regardless if they are under attack. Will try your script as soon I have some time.

DCS AJS37 HACKERMAN

 

There will always be bugs. If everything is a priority nothing is.

Link to comment
Share on other sites

If you change line 15 to:

local delay = math.random(15,80)

 

You make the time of suppression random.

In the example above, the minimum time is 15 seconds, the max time 80 seconds.

dUJOta.jpg

 

Windows 11 | i9 12900KF | 64GB DDR4 | RTX 3090 | TM Warthog HOTAS | Saitek Combat Rudder Pedals | TM MFDs + Lilliput 8" | TIR5 Pro

Link to comment
Share on other sites

This could add a lot of immersion for a mission (definately for a Huey pilot). Thanks a lot for coming up with that .lua and the idea in the first place.

Windows 10 64bit, Intel i9-9900@5Ghz, 32 Gig RAM, MSI RTX 3080 TI, 2 TB SSD, 43" 2160p@1440p monitor.

Link to comment
Share on other sites

If you change line 15 to:

local delay = math.random(15,80)

 

You make the time of suppression random.

In the example above, the minimum time is 15 seconds, the max time 80 seconds.

 

Good idea, I updated the script. Thank you.

Link to comment
Share on other sites

Very nice! I did something similar for Arma 1 ages ago... How about adding a chance that the group under fire will flee from its attackers? You could put a waypoint 180 degrees from the attackers bearing and move in "Green"-mode as groups in this state will move regardless if they are under attack. Will try your script as soon I have some time.

So I did a little modification of your script MBot. Hope you don't mind:

 

do

   -- Finds a point betweem two points according to a given blend (0.5 = right between, 0.3 = a third from point1)
   function getpointbetween(point1, point2, blend)
       return {
           x = point1.x + blend * (point2.x - point1.x),
           y = point1.y + blend * (point2.y - point1.y),
           z = point1.z + blend * (point2.z - point1.z)
       }
   end
   
   -- Measures distance between two points
   function measuredistance(v1, v2)
       local distance = 0
       local v1x = v1.x
       local v2x = v2.x
       local v1z = v1.z
       local v2z = v2.z
       if v1x > v2x then
           distance = distance + (v1x - v2x)
       else
           distance = distance + (v2x - v1x)
       end
       if v1z > v2z then
           distance = distance + (v1z - v2z)
       else
           distance = distance + (v2z - v1z)
       end
       return distance
   end
   
   
   -- Get tablelength
   function tablelength(T)
       local count = 0
       for _ in pairs(T) do count = count + 1 end
       return count
   end
   
   -- Stolen from MiST (by Speed and Grimes)
   function getGroupPoints(groupname)   -- if groupname exists in env.mission, then returns table of the group's points in numerical order, such as: { [1] = {x = 299435.224, y = -1146632.6773}, [2] = { x = 663324.6563, y = 322424.1112}}
   for coa_name, coa_data in pairs(env.mission.coalition) do
       if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then            
           if coa_data.country then --there is a country table
               for cntry_id, cntry_data in pairs(coa_data.country) do
                   for obj_type_name, obj_type_data in pairs(cntry_data) do
                       if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" then    -- only these types have points                        
                           if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then  --there's a group!                
                               for group_num, group_data in pairs(obj_type_data.group) do        
                                   if group_data and group_data.name and group_data.name == groupname then -- this is the group we are looking for
                                       if group_data.route and group_data.route.points and #group_data.route.points > 0 then
                                           local points = {}
                                           for point_num, point in pairs(group_data.route.points) do
                                               if not point.point then
                                                   points[point_num] = { x = point.x, y = point.y }
                                               else
                                                   points[point_num] = point.point  --it's possible that the ME could move to the point = Vec2 notation.
                                               end
                                           end
                                           return points
                                       end
                                       return
                                   end  --if group_data and group_data.name and group_data.name == 'groupname'
                               end --for group_num, group_data in pairs(obj_type_data.group) do        
                           end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then    
                       end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then
                   end --for obj_type_name, obj_type_data in pairs(cntry_data) do
               end --for cntry_id, cntry_data in pairs(coa_data.country) do
           end --if coa_data.country then --there is a country table
       end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then    
   end --for coa_name, coa_data in pairs(mission.coalition) do
end
   
   -- Returns a guess of the remaining waypoints
   function getRemainingWaypoints(_group)
       local _returningwps = {} -- Table to return
       
       -- Get all waypoints as planned in the mission editor
       local _allwps = getGroupPoints(_group:getName())
       if (_allwps == nil) then return end
       
       local _curpos = _group:getUnits()[1]:getPosition().p
       
       -- Loop through all waypoints and find the most likely next wp
       local _nextwpnr = 0
       local _lastwppos
       
       for _wpnr, _point in pairs(_allwps) do
           if (_allwps[_wpnr - 1] ~= nil) then
               local _distbetweenwps = measuredistance(_allwps[_wpnr - 1], _point)
               local _disttothiswp = measuredistance(_curpos, {x = _point.x, y = _point.z})
               if (_distbetweenwps > _disttothiswp) then
                   -- Group is between wps, break loop
                   _nextwpnr = _wpnr
                   break
               end
           end
       end
       

       -- Loop through the remaining wps and fill the new table
       local _nrofwps = #_allwps
       while (_nextwpnr <= _nrofwps) do
           _returningwps[#_returningwps+1] = {_allwps[_nextwpnr]}
           _nextwpnr = _nextwpnr + 1
       end
   end

   local SuppressedGroups = {}    --Table to temporary store data for suppressed groups
   
   --Function to end suppression and let group open fire again
   local function SuppressionEnd(id)
       id.ctrl:setOption(AI.Option.Ground.id.ROE , AI.Option.Ground.val.ROE.OPEN_FIRE)
       id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)
       SuppressedGroups[id.groupName] = nil
       --trigger.action.outText(id.groupName .. " suppression end", 2)    --Info for debug
   end
   local SuppressionEndCounter = 0    --Since SuppressionEnd() is a scheduled function it can exist in multiple instances at the same time. This counter will be used to identify each instance with the subsequent number.

   --Function to run suppress a group
   local function SuppressGroup(tgt, initiator)
   
       local delay = 15 --math.random(5,60)    --Time in seconds the group of a hit unit will be unable to fire
       
       local id = {
           groupName = tgt:getGroup():getName(),
           ctrl = tgt:getGroup():getController()
       }
       
       if SuppressedGroups[id.groupName] == nil then    --If group is currently not suppressed, add to table.
           SuppressionEndCounter = SuppressionEndCounter + 1    --Increase counter to indentify instance of comming SuppressionEnd() scheduled function
           SuppressedGroups[id.groupName] = {
               SuppressionEndTime = timer.getTime() + delay,
               SuppressionEndN = SuppressionEndCounter    --Store instance of SuppressionEnd() scheduled function
           }        
           timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime)    --Schedule the SuppressionEnd() function
       else    --If group is already suppressed, update table and increase delay
           local timeleft = SuppressedGroups[id.groupName].SuppressionEndTime - timer.getTime()    --Get how long to the end of the suppression
           local addDelay = (delay / timeleft) * delay    --The longer the suppression is going to last, the smaller it is going to be increased by additional hits
           if timeleft < delay then    --But if the time left is shorter than a complete delay, add another full delay
               addDelay = delay
           end
           SuppressedGroups[id.groupName].SuppressionEndTime = SuppressedGroups[id.groupName].SuppressionEndTime + addDelay
           timer.setFunctionTime(SuppressedGroups[id.groupName].SuppressionEndN, SuppressedGroups[id.groupName].SuppressionEndTime)    --Update the execution time of the existing instance of the SuppressionEnd() scheduled function
       end
       
       -- Get distance between the two points
       local _distance = measuredistance(Group.getUnits(tgt:getGroup())[1]:getPosition().p, initiator:getPosition().p)
       local _moveblend = 0 - (1/(_distance/200))
       
       
       local _moveto = getpointbetween(Group.getUnits(tgt:getGroup())[1]:getPosition().p, initiator:getPosition().p, _moveblend)
       --trigger.action.smoke(_moveto, 0) 
       
       
       
       local _wrappedaction = { 
           id = 'WrappedAction', 
           params = { 
               action = { 
                   id = 'Script', 
                   params = { 
                       command = "Group.getByName(\"" .. id.groupName .. "\"):getController():setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO)" --"trigger.action.outText(\"Interceptor is attacking target.\", 10)"
                   } 
               }
           }
       }
       local _controlledtask = { 
           id = 'ControlledTask', 
           params = { 
               task = { 
                   id = 'Hold', 
                   params = { 
                   } 
               }, 
               stopCondition = { 
                   duration = 30, 
                   }, 
           } 
       }
       local _combotask = { 
           id = 'ComboTask', 
           params = { 
               tasks = { 
                   [1] = _wrappedaction, 
                   [2] = _controlledtask, 
                   } 
               } 
           } 
       
               local _wps = { 
                       [1] = {
                               action = AI.Task.VehicleFormation.Vee,
                               x = Group.getUnits(tgt:getGroup())[1]:getPosition().p.x, 
                               y = Group.getUnits(tgt:getGroup())[1]:getPosition().p.z, 
                               speed = 25,
                               ETA = 100,
                               ETA_locked = false,
                               name = "Starting point", 
                               task = nil 
                       },
                       [2] = {
                               action = AI.Task.VehicleFormation.Vee,
                               x = _moveto.x, 
                               y = _moveto.z, 
                               speed = 25,
                               ETA = 100,
                               ETA_locked = false,
                               name = "Flee", 
                               task = _combotask 
                       },
                       [3] = {
                               action = AI.Task.VehicleFormation.Vee,
                               x = Group.getUnits(tgt:getGroup())[1]:getPosition().p.x, 
                               y = Group.getUnits(tgt:getGroup())[1]:getPosition().p.z, 
                               speed = 25,
                               ETA = 100,
                               ETA_locked = false,
                               name = "Fight back", 
                               task = nil 
                       },
                   }
       -- Get remaining waypoints
       local _remainingwps = getRemainingWaypoints(tgt:getGroup())
       -- Loop through the remaining wps and create proper waypoints
       if (_remainingwps ~= nil) then
           local _nrofwps = #_remainingwps

           local _rwpscount = 1
           local _cwp = 3
           while (_cwp <= (_nrofwps + 3)) do
               _wps[#_wps+1] = {[_cwp] = {
                                   action = AI.Task.VehicleFormation.Vee,
                                   x = _remainingwps[_rwpscount].x, 
                                   y = _remainingwps[_rwpscount].y, 
                                   speed = 25,
                                   ETA = 100,
                                   ETA_locked = false,
                                   name = "Remaining waypoint", 
                                   task = nil 
                           }}
               _cwp = _cwp + 1
               _rwpscount = _rwpscount + 1
           end
       end
       
       local Mission = { 
           id = 'Mission', 
           params = { 
               route = { 
                   points = _wps
               }, 
           } 
       }
       
       
       
       --id.ctrl:setOption(AI.Option.Ground.id.ROE , AI.Option.Ground.val.ROE.WEAPON_HOLD)    --Set ROE weapons hold to initate suppression
       
       if (math.random(0,100) < 30) then
           id.ctrl:setTask(Mission) -- Flee!!
           id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN)
       end
       
       --trigger.action.outText(id.groupName .. " moves to X:" .. _moveto.x .. " Y:" .. _moveto.z, 60)
       --trigger.action.outText(id.groupName .. " suppressed until " .. SuppressedGroups[id.groupName].SuppressionEndTime, 2)    --Info for debug
   end
   
   --Handler to get when units are hit
   SuppressionHandler = {}
   function SuppressionHandler:onEvent(event)
       if event.id == world.event.S_EVENT_HIT then
           local tgt = event.target
           if tgt ~= nil then
               local tgtType = tgt:getTypeName()
               local initiator = event.initiator
               if tgt:hasAttribute("Infantry") or tgt:hasAttribute("Static AAA") or (tgtType == "Ural-375 ZU-23") then    --Check if hit unit is infantry, static or mobile ZU-23
                   SuppressGroup(tgt, initiator)    --Run suppression of hit unit (group)
               end
           end
       end
   end
   world.addEventHandler(SuppressionHandler)
   
end

 

I'm not 100% happy with the result of my mod. What it does is that it makes units under fire sometimes flee for a while, then wait, and then return and carry on with their mission.

DCS AJS37 HACKERMAN

 

There will always be bugs. If everything is a priority nothing is.

Link to comment
Share on other sites

  • 3 months later...

I don't know anything and I'm not trying to make work for anyone- but would there be a way of establishing the random time units are suppressed by correlating it with their skill level? Average skill would be for 2-3 minutes, good would be 1-2 minutes... etc?

 

Only reason I ask is this looks PERFECT for my applications (even in its current form) but if I were to polish the cannonball I think it would be fantastic to have another function of AI skill applied.

"ENO"

Type in anger and you will make the greatest post you will ever regret.

 

"Sweetest's" Military Aviation Art

Link to comment
Share on other sites

  • 2 weeks later...
Any idea why I get this error in 1.2.6 ?

 

Not sure why it doesn't work with 1.2.6 anymore. Has there been a change to timer.setFunctionTime()? The wiki doesn't make much sense regarding the first argument of this function, but I don't know if this indicates a recent change or if the wiki was simply faulty from the very beginning.

 

function timer.setFunctionTime(FunctionId functionId, Time time)

 

re-schedules function to call at another model time.

 

functionToCall: Lua-function to call. Must have prototype of FunctionToCall.

 

time: Model time of the function call.

 

Should re-scheduled functions no longer be addressed by their sequential number (first scheduled function is "1" etc.)?


Edited by MBot
Link to comment
Share on other sites

  • 2 months later...

The follow up on this, there seems to be a change since 1.2.6 how IDs are applied to functions. Previously this was a sequential number (second scheduled function has functionID 2 etc.). This doesn't seem to be the case anymore. Could Grimes or anyone else comment how the functionID is now determined?

 

function PrintText(text)
  trigger.action.outText(text, 1)
end

timer.scheduleFunction(PrintText, "hello 1", 5)
timer.scheduleFunction(PrintText, "hello 2", 10)


timer.setFunctionTime(1, 7)
timer.setFunctionTime(2, 12)   --doesn't work anymore. What is the functionID of the second instance of PrintText()?

Link to comment
Share on other sites

Its working but its skipping the even numbers. I've reported the bug to the relevant dev. You can get the functionId via the following:

 

local funcId = timer.scheduleFunction(PrintText, "hello 1", 5)

The right man in the wrong place makes all the difference in the world.

Current Projects:  Grayflag ServerScripting Wiki

Useful Links: Mission Scripting Tools MIST-(GitHub) MIST-(Thread)

 SLMOD, Wiki wishlist, Mission Editing Wiki!, Mission Building Forum

Link to comment
Share on other sites

Well there is actually no DCS group ID used in the script. The above code is simply a custom table called id (but could be called anything else, perhaps another name would have been better) that holds the group name and group controller. Both could easily exist as their own variables, but the advantage of combining them into a single table is that this way both can be sent over to functions as a single argument.

Link to comment
Share on other sites

Suppression will be initiated if a infantry, MANPADS or ZU-23 unit is being hit. Suppression will be applied to whole group that such a unit belongs to. So if you have a group that consists of a T-72 and a ZU-23, both will be supressed if the ZU-23 is hit and none will be supressed if the tank is hit.

Link to comment
Share on other sites

  • Recently Browsing   0 members

    • No registered users viewing this page.
×
×
  • Create New...