Suppression Fire Script - ED Forums
 


Notices

Reply
 
Thread Tools Display Modes
Old 06-02-2013, 01:14 PM   #1
MBot
Veteran
 
Join Date: Nov 2004
Posts: 3,158
Default Suppression Fire Script

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.



Spoiler:

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
Attached Files
File Type: lua SuppressionFireScript.lua (2.7 KB, 317 views)

Last edited by MBot; 12-13-2013 at 08:40 AM. Reason: Script updated to work with DCS 1.2.6
MBot is offline   Reply With Quote
Old 06-02-2013, 06:47 PM   #2
gregzagk
3rd Party Campaign Creator
 
gregzagk's Avatar
 
Join Date: Aug 2011
Location: Germany
Posts: 1,234
Default

Thanks a lot!
Will try it asap
gregzagk is offline   Reply With Quote
Old 06-02-2013, 07:51 PM   #3
RagnarDa
3rd Party Developer
 
RagnarDa's Avatar
 
Join Date: Mar 2012
Posts: 1,441
Default

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

Bugs will happen
RagnarDa is offline   Reply With Quote
Old 06-02-2013, 10:21 PM   #4
Stuka
ED Testers Team
 
Stuka's Avatar
 
Join Date: Jul 2007
Location: Belgium
Posts: 878
Default

If you change line 15 to:
Code:
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.
__________________

476 virtual Fighter Group
Website
Discord

Windows 10|i7 7700K @4800|32GB DDR3|GTX 1070|TM Warthog HOTAS|Saitek Combat Rudder Pedals|TM MFDs + Lilliput 8"|TIR5 Pro

Stuka is offline   Reply With Quote
Old 06-03-2013, 12:21 PM   #5
tintifaxl
Senior Member
 
Join Date: Mar 2009
Posts: 1,478
Default

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 i5-7600@3.8Ghz, 32 Gig RAM, Nvidia GTX 1070, 1 TB SSD, 43" 2160p monitor.
tintifaxl is offline   Reply With Quote
Old 06-03-2013, 12:58 PM   #6
Joyride
Veteran
 
Joyride's Avatar
 
Join Date: Apr 2009
Location: Colorado, USA
Posts: 513
Default

Excellent! Thanks for sharing, Mbot.
Joyride is offline   Reply With Quote
Old 06-03-2013, 04:40 PM   #7
Anastasiuss
Member
 
Anastasiuss's Avatar
 
Join Date: Oct 2007
Location: Germany
Posts: 223
Default

Nice Script MBot, will try this skript.

Nice hint Stuka.
__________________

360th TFW Falconeers
last video -> LotATC 1.0.9 beta Test

ASUS P6X58D Premium, Intel Core i7 920, 6GB DDR3, SAPPHIRE TOXIC HD 5850, Win7 64 Bit. X52, Track IR 4, Momo Racing.
ArmA1+2+3, DCS: World, K-50, A-10C, CA, P-51D, UH-1H, Mi-8FC1+2+3, FalconAF, FC1+FC2, IL2'46, rFactor.
Anastasiuss is offline   Reply With Quote
Old 06-03-2013, 10:11 PM   #8
MBot
Veteran
 
Join Date: Nov 2004
Posts: 3,158
Default

Quote:
Originally Posted by Stuka View Post
If you change line 15 to:
Code:
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.
MBot is offline   Reply With Quote
Old 06-06-2013, 12:03 AM   #9
RagnarDa
3rd Party Developer
 
RagnarDa's Avatar
 
Join Date: Mar 2012
Posts: 1,441
Default

Quote:
Originally Posted by RagnarDa View Post
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:
Spoiler:
Code:
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

Bugs will happen
RagnarDa is offline   Reply With Quote
Old 09-15-2013, 10:29 PM   #10
Stuka
ED Testers Team
 
Stuka's Avatar
 
Join Date: Jul 2007
Location: Belgium
Posts: 878
Default

Any idea why I get this error in 1.2.6 ?
Attached Thumbnails
Click image for larger version

Name:	error.PNG
Views:	362
Size:	47.7 KB
ID:	87975  
__________________

476 virtual Fighter Group
Website
Discord

Windows 10|i7 7700K @4800|32GB DDR3|GTX 1070|TM Warthog HOTAS|Saitek Combat Rudder Pedals|TM MFDs + Lilliput 8"|TIR5 Pro

Stuka is offline   Reply With Quote
Reply

Thread Tools
Display Modes

Posting Rules
You may not post new threads
You may not post replies
You may not post attachments
You may not edit your posts

BB code is On
Smilies are On
[IMG] code is On
HTML code is Off

Forum Jump

All times are GMT. The time now is 04:47 AM. vBulletin Skin by ForumMonkeys. Powered by vBulletin®.
Copyright ©2000 - 2018, Jelsoft Enterprises Ltd.