mirror of
https://github.com/FlightControl-Master/MOOSE.git
synced 2025-08-15 10:47:21 +00:00
2033 lines
78 KiB
Lua
2033 lines
78 KiB
Lua
--- **Functional** - Suppress fire of ground units when they get hit.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Features:
|
|
--
|
|
-- * Hold fire of attacked units when being fired upon.
|
|
-- * Retreat to a user defined zone.
|
|
-- * Fall back on hits.
|
|
-- * Take cover on hits.
|
|
-- * Gaussian distribution of suppression time.
|
|
--
|
|
-- ===
|
|
--
|
|
-- ## Missions:
|
|
--
|
|
-- ## [MOOSE - ALL Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS)
|
|
--
|
|
-- ===
|
|
--
|
|
-- When ground units get hit by (suppressive) enemy fire, they will not be able to shoot back for a certain amount of time.
|
|
--
|
|
-- The implementation is based on an idea and script by MBot. See the [DCS forum threat](https://forums.eagle.ru/showthread.php?t=107635) for details.
|
|
--
|
|
-- In addition to suppressing the fire, conditions can be specified which let the group retreat to a defined zone, move away from the attacker
|
|
-- or hide at a nearby scenery object.
|
|
--
|
|
-- ====
|
|
--
|
|
-- # YouTube Channel
|
|
--
|
|
-- ### [MOOSE YouTube Channel](https://www.youtube.com/channel/UCjrA9j5LQoWsG4SpS8i79Qg)
|
|
--
|
|
-- ===
|
|
--
|
|
-- ### Author: **[funkyfranky](https://forums.eagle.ru/member.php?u=115026)**
|
|
--
|
|
-- ### Contributions: [FlightControl](https://forums.eagle.ru/member.php?u=89536)
|
|
--
|
|
-- ===
|
|
--
|
|
-- @module Functional.Suppression
|
|
-- @image Suppression.JPG
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- SUPPRESSION class
|
|
-- @type SUPPRESSION
|
|
-- @field #string ClassName Name of the class.
|
|
-- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players.
|
|
-- @field #string lid String for DCS log file.
|
|
-- @field #boolean flare Flare units when they get hit or die.
|
|
-- @field #boolean smoke Smoke places to which the group retreats, falls back or hides.
|
|
-- @field #list DCSdesc Table containing all DCS descriptors of the group.
|
|
-- @field #string Type Type of the group.
|
|
-- @field #number SpeedMax Maximum speed of group in km/h.
|
|
-- @field #boolean IsInfantry True if group has attribute Infantry.
|
|
-- @field Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the FSM. Must be a ground group.
|
|
-- @field #number Tsuppress_ave Average time in seconds a group gets suppressed. Actual value is sampled randomly from a Gaussian distribution.
|
|
-- @field #number Tsuppress_min Minimum time in seconds the group gets suppressed.
|
|
-- @field #number Tsuppress_max Maximum time in seconds the group gets suppressed.
|
|
-- @field #number TsuppressionOver Time at which the suppression will be over.
|
|
-- @field #number IniGroupStrength Number of units in a group at start.
|
|
-- @field #number Nhit Number of times the group was hit.
|
|
-- @field #string Formation Formation which will be used when falling back, taking cover or retreating. Default "Vee".
|
|
-- @field #number Speed Speed the unit will use when falling back, taking cover or retreating. Default 999.
|
|
-- @field #boolean MenuON If true creates a entry in the F10 menu.
|
|
-- @field #boolean FallbackON If true, group can fall back, i.e. move away from the attacking unit.
|
|
-- @field #number FallbackWait Time in seconds the unit will wait at the fall back point before it resumes its mission.
|
|
-- @field #number FallbackDist Distance in meters the unit will fall back.
|
|
-- @field #number FallbackHeading Heading in degrees to which the group should fall back. Default is directly away from the attacking unit.
|
|
-- @field #boolean TakecoverON If true, group can hide at a nearby scenery object.
|
|
-- @field #number TakecoverWait Time in seconds the group will hide before it will resume its mission.
|
|
-- @field #number TakecoverRange Range in which the group will search for scenery objects to hide at.
|
|
-- @field Core.Point#COORDINATE hideout Coordinate/place where the group will try to take cover.
|
|
-- @field #number PminFlee Minimum probability in percent that a group will flee (fall back or take cover) at each hit event. Default is 10 %.
|
|
-- @field #number PmaxFlee Maximum probability in percent that a group will flee (fall back or take cover) at each hit event. Default is 90 %.
|
|
-- @field Core.Zone#ZONE RetreatZone Zone to which a group retreats.
|
|
-- @field #number RetreatDamage Damage in percent at which the group will be ordered to retreat.
|
|
-- @field #number RetreatWait Time in seconds the group will wait in the retreat zone before it resumes its mission. Default two hours.
|
|
-- @field #string CurrentAlarmState Alam state the group is currently in.
|
|
-- @field #string CurrentROE ROE the group currently has.
|
|
-- @field #string DefaultAlarmState Alarm state the group will go to when it is changed back from another state. Default is "Auto".
|
|
-- @field #string DefaultROE ROE the group will get once suppression is over. Default is "Free".
|
|
-- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true.
|
|
-- @extends Core.Fsm#FSM_CONTROLLABLE
|
|
--
|
|
|
|
--- Mimic suppressive enemy fire and let groups flee or retreat.
|
|
--
|
|
-- ## Suppression Process
|
|
--
|
|
-- 
|
|
--
|
|
-- The suppression process can be described as follows.
|
|
--
|
|
-- ### CombatReady
|
|
--
|
|
-- A group starts in the state **CombatReady**. In this state the group is ready to fight. The ROE is set to either "Weapon Free" or "Return Fire".
|
|
-- The alarm state is set to either "Auto" or "Red".
|
|
--
|
|
-- ### Event Hit
|
|
-- The most important event in this scenario is the **Hit** event. This is an event of the FSM and triggered by the DCS event hit.
|
|
--
|
|
-- ### Suppressed
|
|
-- After the **Hit** event the group changes its state to **Suppressed**. Technically, the ROE of the group is changed to "Weapon Hold".
|
|
-- The suppression of the group will last a certain amount of time. It is randomized an will vary each time the group is hit.
|
|
-- The expected suppression time is set to 15 seconds by default. But the actual value is sampled from a Gaussian distribution.
|
|
--
|
|
-- 
|
|
--
|
|
-- The graph shows the distribution of suppression times if a group would be hit 100,000 times. As can be seen, on most hits the group gets
|
|
-- suppressed for around 15 seconds. Other values are also possible but they become less likely the further away from the "expected" suppression time they are.
|
|
-- Minimal and maximal suppression times can also be specified. By default these are set to 5 and 25 seconds, respectively. This can also be seen in the graph
|
|
-- because the tails of the Gaussian distribution are cut off at these values.
|
|
--
|
|
-- ### Event Recovered
|
|
-- After the suppression time is over, the event **Recovered** is initiated and the group becomes **CombatReady** again.
|
|
-- The ROE of the group will be set to "Weapon Free".
|
|
--
|
|
-- Of course, it can also happen that a group is hit again while it is still suppressed. In that case a new random suppression time is calculated.
|
|
-- If the new suppression time is longer than the remaining suppression of the previous hit, then the group recovers when the suppression time of the last
|
|
-- hit has passed.
|
|
-- If the new suppression time is shorter than the remaining suppression, the group will recover after the longer time of the first suppression has passed.
|
|
--
|
|
-- For example:
|
|
--
|
|
-- * A group gets hit the first time and is suppressed for - let's say - 15 seconds.
|
|
-- * After 10 seconds, i.e. when 5 seconds of the old suppression are left, the group gets hit a again.
|
|
-- * A new suppression time is calculated which can be smaller or larger than the remaining 5 seconds.
|
|
-- * If the new suppression time is smaller, e.g. three seconds, than five seconds, the group will recover after the 5 remaining seconds of the first suppression have passed.
|
|
-- * If the new suppression time is longer than last suppression time, e.g. 10 seconds, then the group will recover after the 10 seconds of the new hit have passed.
|
|
--
|
|
-- Generally speaking, the suppression times are not just added on top of each other. Because this could easily lead to the situation that a group
|
|
-- never becomes CombatReady again before it gets destroyed.
|
|
--
|
|
-- The mission designer can capture the event **Recovered** by the function @{#SUPPRESSION.OnAfterRecovered}().
|
|
--
|
|
-- ## Flee Events and States
|
|
-- Apart from being suppressed the groups can also flee from the enemy under certain conditions.
|
|
--
|
|
-- ### Event Retreat
|
|
-- The first option is a retreat. This can be enabled by setting a retreat zone, i.e. a trigger zone defined in the mission editor.
|
|
--
|
|
-- If the group takes a certain amount of damage, the event **Retreat** will be called and the group will start to move to the retreat zone.
|
|
-- The group will be in the state **Retreating**, which means that its ROE is set to "Weapon Hold" and the alarm state is set to "Green".
|
|
-- Setting the alarm state to green is necessary to enable the group to move under fire.
|
|
--
|
|
-- When the group has reached the retreat zone, the event **Retreated** is triggered and the state will change to **Retreated** (note that both the event and
|
|
-- the state of the same name in this case). ROE and alarm state are
|
|
-- set to "Return Fire" and "Auto", respectively. The group will stay in the retreat zone and not actively participate in the combat any more.
|
|
--
|
|
-- If no option retreat zone has been specified, the option retreat is not available.
|
|
--
|
|
-- The mission designer can capture the events **Retreat** and **Retreated** by the functions @{#SUPPRESSION.OnAfterRetreat}() and @{#SUPPRESSION.OnAfterRetreated}().
|
|
--
|
|
-- ### Fallback
|
|
--
|
|
-- If a group is attacked by another ground group, it has the option to fall back, i.e. move away from the enemy. The probability of the event **FallBack** to
|
|
-- happen depends on the damage of the group that was hit. The more a group gets damaged, the more likely **FallBack** event becomes.
|
|
--
|
|
-- If the group enters the state **FallingBack** it will move 100 meters in the opposite direction of the attacking unit. ROE and alarmstate are set to "Weapon Hold"
|
|
-- and "Green", respectively.
|
|
--
|
|
-- At the fallback point the group will wait for 60 seconds before it resumes its normal mission.
|
|
--
|
|
-- The mission designer can capture the event **FallBack** by the function @{#SUPPRESSION.OnAfterFallBack}().
|
|
--
|
|
-- ### TakeCover
|
|
--
|
|
-- If a group is hit by either another ground or air unit, it has the option to "take cover" or "hide". This means that the group will move to a random
|
|
-- scenery object in it vicinity.
|
|
--
|
|
-- Analogously to the fall back case, the probability of a **TakeCover** event to occur, depends on the damage of the group. The more a group is damaged, the more
|
|
-- likely it becomes that a group takes cover.
|
|
--
|
|
-- When a **TakeCover** event occurs an area with a radius of 300 meters around the hit group is searched for an arbitrary scenery object.
|
|
-- If at least one scenery object is found, the group will move there. One it has reached its "hideout", it will wait there for two minutes before it resumes its
|
|
-- normal mission.
|
|
--
|
|
-- If more than one scenery object is found, the group will move to a random one.
|
|
-- If no scenery object is near the group the **TakeCover** event is rejected and the group will not move.
|
|
--
|
|
-- The mission designer can capture the event **TakeCover** by the function @{#SUPPRESSION.OnAfterTakeCover}().
|
|
--
|
|
-- ### Choice of FallBack or TakeCover if both are enabled?
|
|
--
|
|
-- If both **FallBack** and **TakeCover** events are enabled by the functions @{#SUPPRESSION.Fallback}() and @{#SUPPRESSION.Takecover}() the algorithm does the following:
|
|
--
|
|
-- * If the attacking unit is a ground unit, then the **FallBack** event is executed.
|
|
-- * Otherwise, i.e. if the attacker is *not* a ground unit, then the **TakeCover** event is triggered.
|
|
--
|
|
-- ### FightBack
|
|
--
|
|
-- When a group leaves the states **TakingCover** or **FallingBack** the event **FightBack** is triggered. This changes the ROE and the alarm state back to their default values.
|
|
--
|
|
-- The mission designer can capture the event **FightBack** by the function @{#SUPPRESSION.OnAfterFightBack}()
|
|
--
|
|
-- # Examples
|
|
--
|
|
-- ## Simple Suppression
|
|
-- This example shows the basic steps to use suppressive fire for a group.
|
|
--
|
|
-- 
|
|
--
|
|
--
|
|
-- # Customization and Fine Tuning
|
|
-- The following user functions can be used to change the default values
|
|
--
|
|
-- * @{#SUPPRESSION.SetSuppressionTime}() can be used to set the time a goup gets suppressed.
|
|
-- * @{#SUPPRESSION.SetRetreatZone}() sets the retreat zone and enables the possiblity for the group to retreat.
|
|
-- * @{#SUPPRESSION.SetFallbackDistance}() sets a value how far the unit moves away from the attacker after the fallback event.
|
|
-- * @{#SUPPRESSION.SetFallbackWait}() sets the time after which the group resumes its mission after a FallBack event.
|
|
-- * @{#SUPPRESSION.SetTakecoverWait}() sets the time after which the group resumes its mission after a TakeCover event.
|
|
-- * @{#SUPPRESSION.SetTakecoverRange}() sets the radius in which hideouts are searched.
|
|
-- * @{#SUPPRESSION.SetTakecoverPlace}() explicitly sets the place where the group will run at a TakeCover event.
|
|
-- * @{#SUPPRESSION.SetMinimumFleeProbability}() sets the minimum probability that a group flees (FallBack or TakeCover) after a hit. Note taht the probability increases with damage.
|
|
-- * @{#SUPPRESSION.SetMaximumFleeProbability}() sets the maximum probability that a group flees (FallBack or TakeCover) after a hit. Default is 90%.
|
|
-- * @{#SUPPRESSION.SetRetreatDamage}() sets the damage a group/unit can take before it is ordered to retreat.
|
|
-- * @{#SUPPRESSION.SetRetreatWait}() sets the time a group waits in the retreat zone after a retreat.
|
|
-- * @{#SUPPRESSION.SetDefaultAlarmState}() sets the alarm state a group gets after it becomes CombatReady again.
|
|
-- * @{#SUPPRESSION.SetDefaultROE}() set the rules of engagement a group gets after it becomes CombatReady again.
|
|
-- * @{#SUPPRESSION.FlareOn}() is mainly for debugging. A flare is fired when a unit is hit, gets suppressed, recovers, dies.
|
|
-- * @{#SUPPRESSION.SmokeOn}() is mainly for debugging. Puts smoke on retreat zone, hideouts etc.
|
|
-- * @{#SUPPRESSION.MenuON}() is mainly for debugging. Activates a radio menu item where certain functions like retreat etc. can be triggered manually.
|
|
--
|
|
--
|
|
-- @field #SUPPRESSION
|
|
SUPPRESSION={
|
|
ClassName = "SUPPRESSION",
|
|
Debug = false,
|
|
lid = nil,
|
|
flare = false,
|
|
smoke = false,
|
|
DCSdesc = nil,
|
|
Type = nil,
|
|
IsInfantry = nil,
|
|
SpeedMax = nil,
|
|
Tsuppress_ave = 15,
|
|
Tsuppress_min = 5,
|
|
Tsuppress_max = 25,
|
|
TsuppressOver = nil,
|
|
IniGroupStrength = nil,
|
|
Nhit = 0,
|
|
Formation = "Off road",
|
|
Speed = 4,
|
|
MenuON = false,
|
|
FallbackON = false,
|
|
FallbackWait = 60,
|
|
FallbackDist = 100,
|
|
FallbackHeading = nil,
|
|
TakecoverON = false,
|
|
TakecoverWait = 120,
|
|
TakecoverRange = 300,
|
|
hideout = nil,
|
|
PminFlee = 10,
|
|
PmaxFlee = 90,
|
|
RetreatZone = nil,
|
|
RetreatDamage = nil,
|
|
RetreatWait = 7200,
|
|
CurrentAlarmState = "unknown",
|
|
CurrentROE = "unknown",
|
|
DefaultAlarmState = "Auto",
|
|
DefaultROE = "Weapon Free",
|
|
eventmoose = true,
|
|
}
|
|
|
|
--- Enumerator of possible rules of engagement.
|
|
-- @type SUPPRESSION.ROE
|
|
-- @field #string Hold Hold fire.
|
|
-- @field #string Free Weapon fire.
|
|
-- @field #string Return Return fire.
|
|
SUPPRESSION.ROE={
|
|
Hold="Weapon Hold",
|
|
Free="Weapon Free",
|
|
Return="Return Fire",
|
|
}
|
|
|
|
--- Enumerator of possible alarm states.
|
|
-- @type SUPPRESSION.AlarmState
|
|
-- @field #string Auto Automatic.
|
|
-- @field #string Green Green.
|
|
-- @field #string Red Red.
|
|
SUPPRESSION.AlarmState={
|
|
Auto="Auto",
|
|
Green="Green",
|
|
Red="Red",
|
|
}
|
|
|
|
--- Main F10 menu for suppresion, i.e. F10/Suppression.
|
|
-- @field #string MenuF10
|
|
SUPPRESSION.MenuF10=nil
|
|
|
|
--- PSEUDOATC version.
|
|
-- @field #number version
|
|
SUPPRESSION.version="0.9.3"
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--TODO list
|
|
--DONE: Figure out who was shooting and move away from him.
|
|
--DONE: Move behind a scenery building if there is one nearby.
|
|
--DONE: Retreat to a given zone or point.
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Creates a new AI_suppression object.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Group#GROUP group The GROUP object for which suppression should be applied.
|
|
-- @return #SUPPRESSION SUPPRESSION object.
|
|
-- @return nil If group does not exist or is not a ground group.
|
|
function SUPPRESSION:New(group)
|
|
|
|
-- Inherits from FSM_CONTROLLABLE
|
|
local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #SUPPRESSION
|
|
|
|
-- Check that group is present.
|
|
if group then
|
|
self.lid=string.format("SUPPRESSION %s | ", tostring(group:GetName()))
|
|
self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s", SUPPRESSION.version, group:GetName()))
|
|
else
|
|
self:E(self.lid.."SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group.)")
|
|
return nil
|
|
end
|
|
|
|
-- Check that we actually have a GROUND group.
|
|
if group:IsGround()==false then
|
|
self:E(self.lid..string.format("SUPPRESSION fire group %s has to be a GROUND group!", group:GetName()))
|
|
return nil
|
|
end
|
|
|
|
-- Set the controllable for the FSM.
|
|
self:SetControllable(group)
|
|
|
|
-- Get DCS descriptors of group.
|
|
local DCSgroup=Group.getByName(group:GetName())
|
|
local DCSunit=DCSgroup:getUnit(1)
|
|
self.DCSdesc=DCSunit:getDesc()
|
|
|
|
-- Get max speed the group can do and convert to km/h.
|
|
self.SpeedMax=self.DCSdesc.speedMaxOffRoad*3.6
|
|
|
|
-- Set speed to maximum.
|
|
self.Speed=self.SpeedMax
|
|
|
|
-- Is this infantry or not.
|
|
self.IsInfantry=DCSunit:hasAttribute("Infantry")
|
|
|
|
-- Type of group.
|
|
self.Type=group:GetTypeName()
|
|
|
|
-- Initial group strength.
|
|
self.IniGroupStrength=#group:GetUnits()
|
|
|
|
-- Set ROE and Alarm State.
|
|
self:SetDefaultROE("Free")
|
|
self:SetDefaultAlarmState("Auto")
|
|
|
|
-- Transitions
|
|
self:AddTransition("*", "Start", "CombatReady")
|
|
self:AddTransition("*", "Status", "*")
|
|
self:AddTransition("CombatReady", "Hit", "Suppressed")
|
|
self:AddTransition("Suppressed", "Hit", "Suppressed")
|
|
self:AddTransition("Suppressed", "Recovered", "CombatReady")
|
|
self:AddTransition("Suppressed", "TakeCover", "TakingCover")
|
|
self:AddTransition("Suppressed", "FallBack", "FallingBack")
|
|
self:AddTransition("*", "Retreat", "Retreating")
|
|
self:AddTransition("TakingCover", "FightBack", "CombatReady")
|
|
self:AddTransition("FallingBack", "FightBack", "CombatReady")
|
|
self:AddTransition("Retreating", "Retreated", "Retreated")
|
|
self:AddTransition("*", "Dead", "*")
|
|
self:AddTransition("*", "Stop", "Stopped")
|
|
|
|
self:AddTransition("TakingCover", "Hit", "TakingCover")
|
|
self:AddTransition("FallingBack", "Hit", "FallingBack")
|
|
|
|
|
|
--- Trigger "Status" event.
|
|
-- @function [parent=#SUPPRESSION] Status
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- Trigger "Status" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __Status
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
|
|
--- User function for OnAfter "Status" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterStatus
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
|
|
--- Trigger "Hit" event.
|
|
-- @function [parent=#SUPPRESSION] Hit
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
|
|
|
|
--- Trigger "Hit" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __Hit
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
|
|
|
|
--- User function for OnBefore "Hit" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeHit
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "Hit" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterHit
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
|
|
|
|
|
|
--- Trigger "Recovered" event.
|
|
-- @function [parent=#SUPPRESSION] Recovered
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- Trigger "Recovered" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] Recovered
|
|
-- @param #number Delay Delay in seconds.
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- User function for OnBefore "Recovered" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeRecovered
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "Recovered" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterRecovered
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
|
|
--- Trigger "TakeCover" event.
|
|
-- @function [parent=#SUPPRESSION] TakeCover
|
|
-- @param #SUPPRESSION self
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
|
|
|
|
--- Trigger "TakeCover" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __TakeCover
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
|
|
|
|
--- User function for OnBefore "TakeCover" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeTakeCover
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "TakeCover" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterTakeCover
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
|
|
|
|
|
|
--- Trigger "FallBack" event.
|
|
-- @function [parent=#SUPPRESSION] FallBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
|
|
|
|
--- Trigger "FallBack" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __FallBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
|
|
|
|
--- User function for OnBefore "FallBack" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeFallBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "FallBack" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterFallBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
|
|
|
|
|
|
--- Trigger "Retreat" event.
|
|
-- @function [parent=#SUPPRESSION] Retreat
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- Trigger "Retreat" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __Retreat
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
|
|
--- User function for OnBefore "Retreat" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeRetreat
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "Retreat" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterRetreat
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
|
|
--- Trigger "Retreated" event.
|
|
-- @function [parent=#SUPPRESSION] Retreated
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- Trigger "Retreated" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __Retreated
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
|
|
--- User function for OnBefore "Retreated" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeRetreated
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "Retreated" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterRetreated
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
|
|
--- Trigger "FightBack" event.
|
|
-- @function [parent=#SUPPRESSION] FightBack
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- Trigger "FightBack" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __FightBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
|
|
--- User function for OnBefore "FlightBack" event.
|
|
-- @function [parent=#SUPPRESSION] OnBeforeFightBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @return #boolean
|
|
|
|
--- User function for OnAfter "FlightBack" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterFightBack
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
|
|
--- Trigger "Dead" event.
|
|
-- @function [parent=#SUPPRESSION] Dead
|
|
-- @param #SUPPRESSION self
|
|
|
|
--- Trigger "Dead" event after a delay.
|
|
-- @function [parent=#SUPPRESSION] __Dead
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Delay Delay in seconds.
|
|
|
|
--- User function for OnAfter "Dead" event.
|
|
-- @function [parent=#SUPPRESSION] OnAfterDead
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
|
|
return self
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Set average, minimum and maximum time a unit is suppressed each time it gets hit.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number Tave Average time [seconds] a group will be suppressed. Default is 15 seconds.
|
|
-- @param #number Tmin (Optional) Minimum time [seconds] a group will be suppressed. Default is 5 seconds.
|
|
-- @param #number Tmax (Optional) Maximum time a group will be suppressed. Default is 25 seconds.
|
|
function SUPPRESSION:SetSuppressionTime(Tave, Tmin, Tmax)
|
|
self:F({Tave=Tave, Tmin=Tmin, Tmax=Tmax})
|
|
|
|
-- Minimum suppression time is input or default but at least 1 second.
|
|
self.Tsuppress_min=Tmin or self.Tsuppress_min
|
|
self.Tsuppress_min=math.max(self.Tsuppress_min, 1)
|
|
|
|
-- Maximum suppression time is input or dault but at least Tmin.
|
|
self.Tsuppress_max=Tmax or self.Tsuppress_max
|
|
self.Tsuppress_max=math.max(self.Tsuppress_max, self.Tsuppress_min)
|
|
|
|
-- Expected suppression time is input or default but at leat Tmin and at most Tmax.
|
|
self.Tsuppress_ave=Tave or self.Tsuppress_ave
|
|
self.Tsuppress_ave=math.max(self.Tsuppress_min)
|
|
self.Tsuppress_ave=math.min(self.Tsuppress_max)
|
|
|
|
self:T(self.lid..string.format("Set ave suppression time to %d seconds.", self.Tsuppress_ave))
|
|
self:T(self.lid..string.format("Set min suppression time to %d seconds.", self.Tsuppress_min))
|
|
self:T(self.lid..string.format("Set max suppression time to %d seconds.", self.Tsuppress_max))
|
|
end
|
|
|
|
--- Set the zone to which a group retreats after being damaged too much.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Core.Zone#ZONE zone MOOSE zone object.
|
|
function SUPPRESSION:SetRetreatZone(zone)
|
|
self:F({zone=zone})
|
|
self.RetreatZone=zone
|
|
end
|
|
|
|
--- Turn Debug mode on. Enables messages and more output to DCS log file.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:DebugOn()
|
|
self:F()
|
|
self.Debug=true
|
|
end
|
|
|
|
--- Flare units when they are hit, die or recover from suppression.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:FlareOn()
|
|
self:F()
|
|
self.flare=true
|
|
end
|
|
|
|
--- Smoke positions where units fall back to, hide or retreat.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:SmokeOn()
|
|
self:F()
|
|
self.smoke=true
|
|
end
|
|
|
|
--- Set the formation a group uses for fall back, hide or retreat.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #string formation Formation of the group. Default "Vee".
|
|
function SUPPRESSION:SetFormation(formation)
|
|
self:F(formation)
|
|
self.Formation=formation or "Vee"
|
|
end
|
|
|
|
--- Set speed a group moves at for fall back, hide or retreat.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number speed Speed in km/h of group. Default max speed the group can do.
|
|
function SUPPRESSION:SetSpeed(speed)
|
|
self:F(speed)
|
|
self.Speed=speed or self.SpeedMax
|
|
self.Speed=math.min(self.Speed, self.SpeedMax)
|
|
end
|
|
|
|
--- Enable fall back if a group is hit.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #boolean switch Enable=true or disable=false fall back of group.
|
|
function SUPPRESSION:Fallback(switch)
|
|
self:F(switch)
|
|
if switch==nil then
|
|
switch=true
|
|
end
|
|
self.FallbackON=switch
|
|
end
|
|
|
|
--- Set distance a group will fall back when it gets hit.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number distance Distance in meters.
|
|
function SUPPRESSION:SetFallbackDistance(distance)
|
|
self:F(distance)
|
|
self.FallbackDist=distance
|
|
end
|
|
|
|
--- Set time a group waits at its fall back position before it resumes its normal mission.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number time Time in seconds.
|
|
function SUPPRESSION:SetFallbackWait(time)
|
|
self:F(time)
|
|
self.FallbackWait=time
|
|
end
|
|
|
|
--- Enable take cover option if a unit is hit.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #boolean switch Enable=true or disable=false fall back of group.
|
|
function SUPPRESSION:Takecover(switch)
|
|
self:F(switch)
|
|
if switch==nil then
|
|
switch=true
|
|
end
|
|
self.TakecoverON=switch
|
|
end
|
|
|
|
--- Set time a group waits at its hideout position before it resumes its normal mission.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number time Time in seconds.
|
|
function SUPPRESSION:SetTakecoverWait(time)
|
|
self:F(time)
|
|
self.TakecoverWait=time
|
|
end
|
|
|
|
--- Set distance a group searches for hideout places.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number range Search range in meters.
|
|
function SUPPRESSION:SetTakecoverRange(range)
|
|
self:F(range)
|
|
self.TakecoverRange=range
|
|
end
|
|
|
|
--- Set hideout place explicitly.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide after the TakeCover event.
|
|
function SUPPRESSION:SetTakecoverPlace(Hideout)
|
|
self.hideout=Hideout
|
|
end
|
|
|
|
--- Set minimum probability that a group flees (falls back or takes cover) after a hit event. Default is 10%.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number probability Probability in percent.
|
|
function SUPPRESSION:SetMinimumFleeProbability(probability)
|
|
self:F(probability)
|
|
self.PminFlee=probability or 10
|
|
end
|
|
|
|
--- Set maximum probability that a group flees (falls back or takes cover) after a hit event. Default is 90%.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number probability Probability in percent.
|
|
function SUPPRESSION:SetMaximumFleeProbability(probability)
|
|
self:F(probability)
|
|
self.PmaxFlee=probability or 90
|
|
end
|
|
|
|
--- Set damage threshold before a group is ordered to retreat if a retreat zone was defined.
|
|
-- If the group consists of only a singe unit, this referrs to the life of the unit.
|
|
-- If the group consists of more than one unit, this referrs to the group strength relative to its initial strength.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number damage Damage in percent. If group gets damaged above this value, the group will retreat. Default 50 %.
|
|
function SUPPRESSION:SetRetreatDamage(damage)
|
|
self:F(damage)
|
|
self.RetreatDamage=damage or 50
|
|
end
|
|
|
|
--- Set time a group waits in the retreat zone before it resumes its mission. Default is two hours.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number time Time in seconds. Default 7200 seconds = 2 hours.
|
|
function SUPPRESSION:SetRetreatWait(time)
|
|
self:F(time)
|
|
self.RetreatWait=time or 7200
|
|
end
|
|
|
|
--- Set alarm state a group will get after it returns from a fall back or take cover.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #string alarmstate Alarm state. Possible "Auto", "Green", "Red". Default is "Auto".
|
|
function SUPPRESSION:SetDefaultAlarmState(alarmstate)
|
|
self:F(alarmstate)
|
|
if alarmstate:lower()=="auto" then
|
|
self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto
|
|
elseif alarmstate:lower()=="green" then
|
|
self.DefaultAlarmState=SUPPRESSION.AlarmState.Green
|
|
elseif alarmstate:lower()=="red" then
|
|
self.DefaultAlarmState=SUPPRESSION.AlarmState.Red
|
|
else
|
|
self.DefaultAlarmState=SUPPRESSION.AlarmState.Auto
|
|
end
|
|
end
|
|
|
|
--- Set Rules of Engagement (ROE) a group will get when it recovers from suppression.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #string roe ROE after suppression. Possible "Free", "Hold" or "Return". Default "Free".
|
|
function SUPPRESSION:SetDefaultROE(roe)
|
|
self:F(roe)
|
|
if roe:lower()=="free" then
|
|
self.DefaultROE=SUPPRESSION.ROE.Free
|
|
elseif roe:lower()=="hold" then
|
|
self.DefaultROE=SUPPRESSION.ROE.Hold
|
|
elseif roe:lower()=="return" then
|
|
self.DefaultROE=SUPPRESSION.ROE.Return
|
|
else
|
|
self.DefaultROE=SUPPRESSION.ROE.Free
|
|
end
|
|
end
|
|
|
|
--- Create an F10 menu entry for the suppressed group. The menu is mainly for Debugging purposes.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #boolean switch Enable=true or disable=false menu group. Default is true.
|
|
function SUPPRESSION:MenuOn(switch)
|
|
self:F(switch)
|
|
if switch==nil then
|
|
switch=true
|
|
end
|
|
self.MenuON=switch
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Create F10 main menu, i.e. F10/Suppression. The menu is mainly for Debugging purposes.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:_CreateMenuGroup()
|
|
local SubMenuName=self.Controllable:GetName()
|
|
local MenuGroup=MENU_MISSION:New(SubMenuName, SUPPRESSION.MenuF10)
|
|
MENU_MISSION_COMMAND:New("Fallback!", MenuGroup, self.OrderFallBack, self)
|
|
MENU_MISSION_COMMAND:New("Take Cover!", MenuGroup, self.OrderTakeCover, self)
|
|
MENU_MISSION_COMMAND:New("Retreat!", MenuGroup, self.OrderRetreat, self)
|
|
MENU_MISSION_COMMAND:New("Report Status", MenuGroup, self.Status, self, true)
|
|
end
|
|
|
|
--- Order group to fall back between 100 and 150 meters in a random direction.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:OrderFallBack()
|
|
local group=self.Controllable --Wrapper.Controllable#CONTROLLABLE
|
|
local vicinity=group:GetCoordinate():GetRandomVec2InRadius(150, 100)
|
|
local coord=COORDINATE:NewFromVec2(vicinity)
|
|
self:FallBack(self.Controllable)
|
|
end
|
|
|
|
--- Order group to take cover at a nearby scenery object.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:OrderTakeCover()
|
|
-- Search place to hide or take specified one.
|
|
local Hideout=self.hideout
|
|
if self.hideout==nil then
|
|
Hideout=self:_SearchHideout()
|
|
end
|
|
-- Trigger TakeCover event.
|
|
self:TakeCover(Hideout)
|
|
end
|
|
|
|
--- Order group to retreat to a pre-defined zone.
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:OrderRetreat()
|
|
self:Retreat()
|
|
end
|
|
|
|
--- Status of group. Current ROE, alarm state, life.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #boolean message Send message to all players.
|
|
function SUPPRESSION:StatusReport(message)
|
|
|
|
local group=self.Controllable --Wrapper.Group#GROUP
|
|
|
|
local nunits=group:CountAliveUnits()
|
|
local roe=self.CurrentROE
|
|
local state=self.CurrentAlarmState
|
|
local life_min, life_max, life_ave, life_ave0, groupstrength=self:_GetLife()
|
|
local ammotot=group:GetAmmunition()
|
|
|
|
|
|
local text=string.format("State %s, Units=%d/%d, ROE=%s, AlarmState=%s, Hits=%d, Life(min/max/ave/ave0)=%d/%d/%d/%d, Total Ammo=%d",
|
|
self:GetState(), nunits, self.IniGroupStrength, self.CurrentROE, self.CurrentAlarmState, self.Nhit, life_min, life_max, life_ave, life_ave0, ammotot)
|
|
|
|
MESSAGE:New(text, 10):ToAllIf(message or self.Debug)
|
|
self:I(self.lid..text)
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- After "Start" event. Initialized ROE and alarm state. Starts the event handler.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterStart(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterStart", Event, From, To)
|
|
|
|
local text=string.format("Started SUPPRESSION for group %s.", Controllable:GetName())
|
|
self:I(self.lid..text)
|
|
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
|
|
|
local rzone="not defined"
|
|
if self.RetreatZone then
|
|
rzone=self.RetreatZone:GetName()
|
|
end
|
|
|
|
-- Set retreat damage value if it was not set by user input.
|
|
if self.RetreatDamage==nil then
|
|
if self.RetreatZone then
|
|
if self.IniGroupStrength==1 then
|
|
self.RetreatDamage=60.0 -- 40% of life is left.
|
|
elseif self.IniGroupStrength==2 then
|
|
self.RetreatDamage=50.0 -- 50% of group left, i.e. 1 of 2. We already order a retreat, because if for a group 2 two a zone is defined it would not be used at all.
|
|
else
|
|
self.RetreatDamage=66.5 -- 34% of the group is left, e.g. 1 of 3,4 or 5, 2 of 6,7 or 8, 3 of 9,10 or 11, 4/12, 4/13, 4/14, 5/15, ...
|
|
end
|
|
else
|
|
self.RetreatDamage=100 -- If no retreat then this should be set to 100%.
|
|
end
|
|
end
|
|
|
|
-- Create main F10 menu if it is not there yet.
|
|
if self.MenuON then
|
|
if not SUPPRESSION.MenuF10 then
|
|
SUPPRESSION.MenuF10 = MENU_MISSION:New("Suppression")
|
|
end
|
|
self:_CreateMenuGroup()
|
|
end
|
|
|
|
-- Set the current ROE and alam state.
|
|
self:_SetAlarmState(self.DefaultAlarmState)
|
|
self:_SetROE(self.DefaultROE)
|
|
|
|
local text=string.format("\n******************************************************\n")
|
|
text=text..string.format("Suppressed group = %s\n", Controllable:GetName())
|
|
text=text..string.format("Type = %s\n", self.Type)
|
|
text=text..string.format("IsInfantry = %s\n", tostring(self.IsInfantry))
|
|
text=text..string.format("Group strength = %d\n", self.IniGroupStrength)
|
|
text=text..string.format("Average time = %5.1f seconds\n", self.Tsuppress_ave)
|
|
text=text..string.format("Minimum time = %5.1f seconds\n", self.Tsuppress_min)
|
|
text=text..string.format("Maximum time = %5.1f seconds\n", self.Tsuppress_max)
|
|
text=text..string.format("Default ROE = %s\n", self.DefaultROE)
|
|
text=text..string.format("Default AlarmState = %s\n", self.DefaultAlarmState)
|
|
text=text..string.format("Fall back ON = %s\n", tostring(self.FallbackON))
|
|
text=text..string.format("Fall back distance = %5.1f m\n", self.FallbackDist)
|
|
text=text..string.format("Fall back wait = %5.1f seconds\n", self.FallbackWait)
|
|
text=text..string.format("Fall back heading = %s degrees\n", tostring(self.FallbackHeading))
|
|
text=text..string.format("Take cover ON = %s\n", tostring(self.TakecoverON))
|
|
text=text..string.format("Take cover search = %5.1f m\n", self.TakecoverRange)
|
|
text=text..string.format("Take cover wait = %5.1f seconds\n", self.TakecoverWait)
|
|
text=text..string.format("Min flee probability = %5.1f\n", self.PminFlee)
|
|
text=text..string.format("Max flee probability = %5.1f\n", self.PmaxFlee)
|
|
text=text..string.format("Retreat zone = %s\n", rzone)
|
|
text=text..string.format("Retreat damage = %5.1f %%\n", self.RetreatDamage)
|
|
text=text..string.format("Retreat wait = %5.1f seconds\n", self.RetreatWait)
|
|
text=text..string.format("Speed = %5.1f km/h\n", self.Speed)
|
|
text=text..string.format("Speed max = %5.1f km/h\n", self.SpeedMax)
|
|
text=text..string.format("Formation = %s\n", self.Formation)
|
|
text=text..string.format("******************************************************\n")
|
|
self:T(self.lid..text)
|
|
|
|
-- Add event handler.
|
|
if self.eventmoose then
|
|
self:HandleEvent(EVENTS.Hit, self._OnEventHit)
|
|
self:HandleEvent(EVENTS.Dead, self._OnEventDead)
|
|
else
|
|
world.addEventHandler(self)
|
|
end
|
|
|
|
self:__Status(-1)
|
|
end
|
|
|
|
--- After "Status" event.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterStatus(Controllable, From, Event, To)
|
|
|
|
-- Suppressed group.
|
|
local group=self.Controllable --Wrapper.Group#GROUP
|
|
|
|
-- Check if group object exists.
|
|
if group then
|
|
|
|
-- Number of alive units.
|
|
local nunits=group:CountAliveUnits()
|
|
|
|
-- Check if there are units.
|
|
if nunits>0 then
|
|
|
|
-- Retreat if completely out of ammo and retreat zone defined.
|
|
local nammo=group:GetAmmunition()
|
|
if nammo==0 and self.RetreatZone then
|
|
self:Retreat()
|
|
end
|
|
|
|
-- Status report.
|
|
self:StatusReport(false)
|
|
|
|
-- Call status again if not "Stopped".
|
|
if self:GetState()~="Stopped" then
|
|
self:__Status(-30)
|
|
end
|
|
|
|
else
|
|
self:Stop()
|
|
end
|
|
|
|
else
|
|
self:Stop()
|
|
end
|
|
|
|
end
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Before "Hit" event. (Of course, this is not really before the group got hit.)
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
|
|
-- @return boolean
|
|
function SUPPRESSION:onbeforeHit(Controllable, From, Event, To, Unit, AttackUnit)
|
|
self:_EventFromTo("onbeforeHit", Event, From, To)
|
|
|
|
--local Tnow=timer.getTime()
|
|
--env.info(self.lid..string.format("Last hit = %s %s", tostring(self.LastHit), tostring(Tnow)))
|
|
|
|
return true
|
|
end
|
|
|
|
--- After "Hit" event.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT Unit Unit that was hit.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Unit that attacked.
|
|
function SUPPRESSION:onafterHit(Controllable, From, Event, To, Unit, AttackUnit)
|
|
self:_EventFromTo("onafterHit", Event, From, To)
|
|
|
|
-- Suppress unit.
|
|
if From=="CombatReady" or From=="Suppressed" then
|
|
self:_Suppress()
|
|
end
|
|
|
|
-- Get life of group in %.
|
|
local life_min, life_max, life_ave, life_ave0, groupstrength=self:_GetLife()
|
|
|
|
-- Damage in %. If group consists only of one unit, we take its life value.
|
|
local Damage=100-life_ave0
|
|
|
|
-- Condition for retreat.
|
|
local RetreatCondition = Damage >= self.RetreatDamage-0.01 and self.RetreatZone
|
|
|
|
-- Probability that a unit flees. The probability increases linearly with the damage of the group/unit.
|
|
-- If Damage=0 ==> P=Pmin
|
|
-- if Damage=RetreatDamage ==> P=Pmax
|
|
-- If no retreat zone has been specified, RetreatDamage is 100.
|
|
local Pflee=(self.PmaxFlee-self.PminFlee)/self.RetreatDamage * math.min(Damage, self.RetreatDamage) + self.PminFlee
|
|
|
|
-- Evaluate flee condition.
|
|
local P=math.random(0,100)
|
|
local FleeCondition = P < Pflee
|
|
|
|
local text
|
|
text=string.format("\nGroup %s: Life min=%5.1f, max=%5.1f, ave=%5.1f, ave0=%5.1f group=%5.1f\n", Controllable:GetName(), life_min, life_max, life_ave, life_ave0, groupstrength)
|
|
text=string.format("Group %s: Damage = %8.4f (%8.4f retreat threshold).\n", Controllable:GetName(), Damage, self.RetreatDamage)
|
|
text=string.format("Group %s: P_Flee = %5.1f %5.1f=P_rand (P_Flee > Prand ==> Flee)\n", Controllable:GetName(), Pflee, P)
|
|
self:T(self.lid..text)
|
|
|
|
-- Group is obviously destroyed.
|
|
if Damage >= 99.9 then
|
|
return
|
|
end
|
|
|
|
if RetreatCondition then
|
|
|
|
-- Trigger Retreat event.
|
|
self:Retreat()
|
|
|
|
elseif FleeCondition then
|
|
|
|
if self.FallbackON and AttackUnit:IsGround() then
|
|
|
|
-- Trigger FallBack event.
|
|
self:FallBack(AttackUnit)
|
|
|
|
elseif self.TakecoverON then
|
|
|
|
-- Search place to hide or take specified one.
|
|
local Hideout=self.hideout
|
|
if self.hideout==nil then
|
|
Hideout=self:_SearchHideout()
|
|
end
|
|
|
|
-- Trigger TakeCover event.
|
|
self:TakeCover(Hideout)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Before "Recovered" event. Check if suppression time is over.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @return #boolean
|
|
function SUPPRESSION:onbeforeRecovered(Controllable, From, Event, To)
|
|
self:_EventFromTo("onbeforeRecovered", Event, From, To)
|
|
|
|
-- Current time.
|
|
local Tnow=timer.getTime()
|
|
|
|
-- Debug info
|
|
self:T(self.lid..string.format("onbeforeRecovered: Time now: %d - Time over: %d", Tnow, self.TsuppressionOver))
|
|
|
|
-- Recovery is only possible if enough time since the last hit has passed.
|
|
if Tnow >= self.TsuppressionOver then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
|
|
end
|
|
|
|
--- After "Recovered" event. Group has recovered and its ROE is set back to the "normal" unsuppressed state. Optionally the group is flared green.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterRecovered(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterRecovered", Event, From, To)
|
|
|
|
if Controllable and Controllable:IsAlive() then
|
|
|
|
-- Debug message.
|
|
local text=string.format("Group %s has recovered!", Controllable:GetName())
|
|
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
|
self:T(self.lid..text)
|
|
|
|
-- Set ROE back to default.
|
|
self:_SetROE()
|
|
|
|
-- Flare unit green.
|
|
if self.flare or self.Debug then
|
|
Controllable:FlareGreen()
|
|
end
|
|
|
|
end
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- After "FightBack" event. ROE and Alarm state are set back to default.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterFightBack(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterFightBack", Event, From, To)
|
|
|
|
-- Set ROE and alarm state back to default.
|
|
self:_SetROE()
|
|
self:_SetAlarmState()
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Before "FallBack" event. We check that group is not already falling back.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
|
|
-- @return #boolean
|
|
function SUPPRESSION:onbeforeFallBack(Controllable, From, Event, To, AttackUnit)
|
|
self:_EventFromTo("onbeforeFallBack", Event, From, To)
|
|
|
|
--TODO: Add retreat? Only allowd transition is Suppressed-->Fallback. So in principle no need.
|
|
if From == "FallingBack" then
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
end
|
|
|
|
--- After "FallBack" event. We get the heading away from the attacker and route the group a certain distance in that direction.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Wrapper.Unit#UNIT AttackUnit Attacking unit. We will move away from this.
|
|
function SUPPRESSION:onafterFallBack(Controllable, From, Event, To, AttackUnit)
|
|
self:_EventFromTo("onafterFallback", Event, From, To)
|
|
|
|
-- Debug info
|
|
self:T(self.lid..string.format("Group %s is falling back after %d hits.", Controllable:GetName(), self.Nhit))
|
|
|
|
-- Coordinate of the attacker and attacked unit.
|
|
local ACoord=AttackUnit:GetCoordinate()
|
|
local DCoord=Controllable:GetCoordinate()
|
|
|
|
-- Heading from attacker to attacked unit.
|
|
local heading=self:_Heading(ACoord, DCoord)
|
|
|
|
-- Overwrite heading with user specified heading.
|
|
if self.FallbackHeading then
|
|
heading=self.FallbackHeading
|
|
end
|
|
|
|
-- Create a coordinate ~ 100 m in opposite direction of the attacking unit.
|
|
local Coord=DCoord:Translate(self.FallbackDist, heading)
|
|
|
|
-- Place marker
|
|
if self.Debug then
|
|
local MarkerID=Coord:MarkToAll("Fall back position for group "..Controllable:GetName())
|
|
end
|
|
|
|
-- Smoke the coordinate.
|
|
if self.smoke or self.Debug then
|
|
Coord:SmokeBlue()
|
|
end
|
|
|
|
-- Set ROE to weapon hold.
|
|
self:_SetROE(SUPPRESSION.ROE.Hold)
|
|
|
|
-- Set alarm state to GREEN and let the unit run away.
|
|
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
|
|
|
|
-- Make the group run away.
|
|
self:_Run(Coord, self.Speed, self.Formation, self.FallbackWait)
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Before "TakeCover" event. Search an area around the group for possible scenery objects where the group can hide.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
|
|
-- @return #boolean
|
|
function SUPPRESSION:onbeforeTakeCover(Controllable, From, Event, To, Hideout)
|
|
self:_EventFromTo("onbeforeTakeCover", Event, From, To)
|
|
|
|
--TODO: Need to test this!
|
|
if From=="TakingCover" then
|
|
return false
|
|
end
|
|
|
|
-- Block transition if no hideout place is given.
|
|
if Hideout ~= nil then
|
|
return true
|
|
else
|
|
return false
|
|
end
|
|
|
|
end
|
|
|
|
--- After "TakeCover" event. Group will run to a nearby scenery object and "hide" there for a certain time.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @param Core.Point#COORDINATE Hideout Place where the group will hide.
|
|
function SUPPRESSION:onafterTakeCover(Controllable, From, Event, To, Hideout)
|
|
self:_EventFromTo("onafterTakeCover", Event, From, To)
|
|
|
|
if self.Debug then
|
|
local MarkerID=Hideout:MarkToAll(string.format("Hideout for group %s", Controllable:GetName()))
|
|
end
|
|
|
|
-- Smoke place of hideout.
|
|
if self.smoke or self.Debug then
|
|
Hideout:SmokeBlue()
|
|
end
|
|
|
|
-- Set ROE to weapon hold.
|
|
self:_SetROE(SUPPRESSION.ROE.Hold)
|
|
|
|
-- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire.
|
|
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
|
|
|
|
-- Make the group run away.
|
|
self:_Run(Hideout, self.Speed, self.Formation, self.TakecoverWait)
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Before "Retreat" event. We check that the group is not already retreating.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
-- @return #boolean True if transition is allowed, False if transition is forbidden.
|
|
function SUPPRESSION:onbeforeRetreat(Controllable, From, Event, To)
|
|
self:_EventFromTo("onbeforeRetreat", Event, From, To)
|
|
|
|
if From=="Retreating" then
|
|
local text=string.format("Group %s is already retreating.", tostring(Controllable:GetName()))
|
|
self:T2(self.lid..text)
|
|
return false
|
|
else
|
|
return true
|
|
end
|
|
|
|
end
|
|
|
|
--- After "Retreat" event. Find a random point in the retreat zone and route the group there.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterRetreat(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterRetreat", Event, From, To)
|
|
|
|
-- Route the group to a zone.
|
|
local text=string.format("Group %s is retreating! Alarm state green.", Controllable:GetName())
|
|
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
|
self:T(self.lid..text)
|
|
|
|
-- Get a random point in the retreat zone.
|
|
local ZoneCoord=self.RetreatZone:GetRandomCoordinate() -- Core.Point#COORDINATE
|
|
local ZoneVec2=ZoneCoord:GetVec2()
|
|
|
|
-- Debug smoke zone and point.
|
|
if self.smoke or self.Debug then
|
|
ZoneCoord:SmokeBlue()
|
|
end
|
|
if self.Debug then
|
|
self.RetreatZone:SmokeZone(SMOKECOLOR.Red, 12)
|
|
end
|
|
|
|
-- Set ROE to weapon hold.
|
|
self:_SetROE(SUPPRESSION.ROE.Hold)
|
|
|
|
-- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire.
|
|
self:_SetAlarmState(SUPPRESSION.AlarmState.Green)
|
|
|
|
-- Make unit run to retreat zone and wait there for ~two hours.
|
|
self:_Run(ZoneCoord, self.Speed, self.Formation, self.RetreatWait)
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Before "Retreateded" event. Check that the group is really in the retreat zone.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onbeforeRetreated(Controllable, From, Event, To)
|
|
self:_EventFromTo("onbeforeRetreated", Event, From, To)
|
|
|
|
-- Check that the group is inside the zone.
|
|
local inzone=self.RetreatZone:IsVec3InZone(Controllable:GetVec3())
|
|
|
|
return inzone
|
|
end
|
|
|
|
--- After "Retreateded" event. Group has reached the retreat zone. Set ROE to return fire and alarm state to auto.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterRetreated(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterRetreated", Event, From, To)
|
|
|
|
-- Set ROE to weapon return fire.
|
|
self:_SetROE(SUPPRESSION.ROE.Return)
|
|
|
|
-- Set the ALARM STATE to GREEN. Then the unit will move even if it is under fire.
|
|
self:_SetAlarmState(SUPPRESSION.AlarmState.Auto)
|
|
|
|
-- TODO: Add hold task? Move from _Run()
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- After "Dead" event, when a unit has died. When all units of a group are dead, FSM is stopped and eventhandler removed.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterDead(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterDead", Event, From, To)
|
|
|
|
local group=self.Controllable --Wrapper.Group#GROUP
|
|
|
|
if group then
|
|
|
|
-- Number of units left in the group.
|
|
local nunits=group:CountAliveUnits()
|
|
|
|
local text=string.format("Group %s: One of our units just died! %d units left.", self.Controllable:GetName(), nunits)
|
|
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
|
self:T(self.lid..text)
|
|
|
|
-- Go to stop state.
|
|
if nunits==0 then
|
|
self:Stop()
|
|
end
|
|
|
|
else
|
|
self:Stop()
|
|
end
|
|
|
|
end
|
|
|
|
--- After "Stop" event.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group.
|
|
-- @param #string From From state.
|
|
-- @param #string Event Event.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:onafterStop(Controllable, From, Event, To)
|
|
self:_EventFromTo("onafterStop", Event, From, To)
|
|
|
|
local text=string.format("Stopping SUPPRESSION for group %s", self.Controllable:GetName())
|
|
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
|
self:I(self.lid..text)
|
|
|
|
-- Clear all pending schedules
|
|
self.CallScheduler:Clear()
|
|
|
|
if self.mooseevents then
|
|
self:UnHandleEvent(EVENTS.Dead)
|
|
self:UnHandleEvent(EVENTS.Hit)
|
|
else
|
|
world.removeEventHandler(self)
|
|
end
|
|
|
|
end
|
|
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
--- Event Handler
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Event handler for suppressed groups.
|
|
--@param #SUPPRESSION self
|
|
function SUPPRESSION:onEvent(Event)
|
|
--self:E(event)
|
|
|
|
if Event == nil or Event.initiator == nil or Unit.getByName(Event.initiator:getName()) == nil then
|
|
return true
|
|
end
|
|
|
|
local EventData={}
|
|
if Event.initiator then
|
|
EventData.IniDCSUnit = Event.initiator
|
|
EventData.IniUnitName = Event.initiator:getName()
|
|
EventData.IniDCSGroup = Event.initiator:getGroup()
|
|
EventData.IniGroupName = Event.initiator:getGroup():getName()
|
|
EventData.IniGroup = GROUP:FindByName(EventData.IniGroupName)
|
|
EventData.IniUnit = UNIT:FindByName(EventData.IniUnitName)
|
|
end
|
|
|
|
if Event.target then
|
|
EventData.TgtDCSUnit = Event.target
|
|
EventData.TgtUnitName = Event.target:getName()
|
|
EventData.TgtDCSGroup = Event.target:getGroup()
|
|
EventData.TgtGroupName = Event.target:getGroup():getName()
|
|
EventData.TgtGroup = GROUP:FindByName(EventData.TgtGroupName)
|
|
EventData.TgtUnit = UNIT:FindByName(EventData.TgtUnitName)
|
|
end
|
|
|
|
|
|
-- Event HIT
|
|
if Event.id == world.event.S_EVENT_HIT then
|
|
self:_OnEventHit(EventData)
|
|
end
|
|
|
|
-- Event DEAD
|
|
if Event.id == world.event.S_EVENT_DEAD then
|
|
self:_OnEventDead(EventData)
|
|
end
|
|
|
|
end
|
|
|
|
--- Event handler for Dead event of suppressed groups.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
function SUPPRESSION:_OnEventHit(EventData)
|
|
self:F(EventData)
|
|
|
|
local GroupNameSelf=self.Controllable:GetName()
|
|
local GroupNameTgt=EventData.TgtGroupName
|
|
local TgtUnit=EventData.TgtUnit
|
|
local tgt=EventData.TgtDCSUnit
|
|
local IniUnit=EventData.IniUnit
|
|
|
|
-- Check that correct group was hit.
|
|
if GroupNameTgt == GroupNameSelf then
|
|
|
|
self:T(self.lid..string.format("Hit event at t = %5.1f", timer.getTime()))
|
|
|
|
-- Flare unit that was hit.
|
|
if self.flare or self.Debug then
|
|
TgtUnit:FlareRed()
|
|
end
|
|
|
|
-- Increase Hit counter.
|
|
self.Nhit=self.Nhit+1
|
|
|
|
-- Info on hit times.
|
|
self:T(self.lid..string.format("Group %s has just been hit %d times.", self.Controllable:GetName(), self.Nhit))
|
|
|
|
--self:Status()
|
|
local life=tgt:getLife()/(tgt:getLife0()+1)*100
|
|
self:T2(self.lid..string.format("Target unit life = %5.1f", life))
|
|
|
|
-- FSM Hit event.
|
|
self:__Hit(3, TgtUnit, IniUnit)
|
|
end
|
|
|
|
end
|
|
|
|
--- Event handler for Dead event of suppressed groups.
|
|
-- @param #SUPPRESSION self
|
|
-- @param Core.Event#EVENTDATA EventData
|
|
function SUPPRESSION:_OnEventDead(EventData)
|
|
|
|
local GroupNameSelf=self.Controllable:GetName()
|
|
local GroupNameIni=EventData.IniGroupName
|
|
|
|
-- Check for correct group.
|
|
if GroupNameIni==GroupNameSelf then
|
|
|
|
-- Dead Unit.
|
|
local IniUnit=EventData.IniUnit --Wrapper.Unit#UNIT
|
|
local IniUnitName=EventData.IniUnitName
|
|
|
|
if EventData.IniUnit then
|
|
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName))
|
|
else
|
|
self:T2(self.lid..string.format("Group %s: Dead MOOSE unit DOES NOT not exist! Unit name %s.", GroupNameIni, IniUnitName))
|
|
end
|
|
|
|
if EventData.IniDCSUnit then
|
|
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES exist! Unit name %s.", GroupNameIni, IniUnitName))
|
|
else
|
|
self:T2(self.lid..string.format("Group %s: Dead DCS unit DOES NOT exist! Unit name %s.", GroupNameIni, IniUnitName))
|
|
end
|
|
|
|
-- Flare unit that died.
|
|
if IniUnit and (self.flare or self.Debug) then
|
|
IniUnit:FlareWhite()
|
|
self:T(self.lid..string.format("Flare Dead MOOSE unit."))
|
|
end
|
|
|
|
-- Flare unit that died.
|
|
if EventData.IniDCSUnit and (self.flare or self.Debug) then
|
|
local p=EventData.IniDCSUnit:getPosition().p
|
|
trigger.action.signalFlare(p, trigger.flareColor.Yellow , 0)
|
|
self:T(self.lid..string.format("Flare Dead DCS unit."))
|
|
end
|
|
|
|
-- Get status.
|
|
self:Status()
|
|
|
|
-- FSM Dead event.
|
|
self:__Dead(0.1)
|
|
|
|
end
|
|
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|
|
--- Suppress fire of a unit by setting its ROE to "Weapon Hold".
|
|
-- @param #SUPPRESSION self
|
|
function SUPPRESSION:_Suppress()
|
|
|
|
-- Current time.
|
|
local Tnow=timer.getTime()
|
|
|
|
-- Controllable
|
|
local Controllable=self.Controllable --Wrapper.Controllable#CONTROLLABLE
|
|
|
|
-- Group will hold their weapons.
|
|
self:_SetROE(SUPPRESSION.ROE.Hold)
|
|
|
|
-- Get randomized time the unit is suppressed.
|
|
local sigma=(self.Tsuppress_max-self.Tsuppress_min)/4
|
|
local Tsuppress=self:_Random_Gaussian(self.Tsuppress_ave,sigma,self.Tsuppress_min, self.Tsuppress_max)
|
|
|
|
-- Time at which the suppression is over.
|
|
local renew=true
|
|
if self.TsuppressionOver ~= nil then
|
|
if Tsuppress+Tnow > self.TsuppressionOver then
|
|
self.TsuppressionOver=Tnow+Tsuppress
|
|
else
|
|
renew=false
|
|
end
|
|
else
|
|
self.TsuppressionOver=Tnow+Tsuppress
|
|
end
|
|
|
|
-- Recovery event will be called in Tsuppress seconds.
|
|
if renew then
|
|
self:__Recovered(self.TsuppressionOver-Tnow)
|
|
end
|
|
|
|
-- Debug message.
|
|
local text=string.format("Group %s is suppressed for %d seconds. Suppression ends at %d:%02d.", Controllable:GetName(), Tsuppress, self.TsuppressionOver/60, self.TsuppressionOver%60)
|
|
MESSAGE:New(text, 10):ToAllIf(self.Debug)
|
|
self:T(self.lid..text)
|
|
|
|
end
|
|
|
|
|
|
--- Make group run/drive to a certain point. We put in several intermediate waypoints because sometimes the group stops before it arrived at the desired point.
|
|
--@param #SUPPRESSION self
|
|
--@param Core.Point#COORDINATE fin Coordinate where we want to go.
|
|
--@param #number speed Speed of group. Default is 20.
|
|
--@param #string formation Formation of group. Default is "Vee".
|
|
--@param #number wait Time the group will wait/hold at final waypoint. Default is 30 seconds.
|
|
function SUPPRESSION:_Run(fin, speed, formation, wait)
|
|
|
|
speed=speed or 20
|
|
formation=formation or "Off road"
|
|
wait=wait or 30
|
|
|
|
local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE
|
|
|
|
if group and group:IsAlive() then
|
|
|
|
-- Clear all tasks.
|
|
group:ClearTasks()
|
|
|
|
-- Current coordinates of group.
|
|
local ini=group:GetCoordinate()
|
|
|
|
-- Distance between current and final point.
|
|
local dist=ini:Get2DDistance(fin)
|
|
|
|
-- Heading from ini to fin.
|
|
local heading=self:_Heading(ini, fin)
|
|
|
|
-- Number of waypoints.
|
|
local nx
|
|
if dist <= 50 then
|
|
nx=2
|
|
elseif dist <= 100 then
|
|
nx=3
|
|
elseif dist <= 500 then
|
|
nx=4
|
|
else
|
|
nx=5
|
|
end
|
|
|
|
-- Number of intermediate waypoints.
|
|
local dx=dist/(nx-1)
|
|
|
|
-- Waypoint and task arrays.
|
|
local wp={}
|
|
local tasks={}
|
|
|
|
-- First waypoint is the current position of the group.
|
|
wp[1]=ini:WaypointGround(speed, formation)
|
|
tasks[1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, 1, false)
|
|
|
|
if self.Debug then
|
|
local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)", #wp, self.Controllable:GetName()))
|
|
end
|
|
|
|
self:T2(self.lid..string.format("Number of waypoints %d", nx))
|
|
for i=1,nx-2 do
|
|
|
|
local x=dx*i
|
|
local coord=ini:Translate(x, heading)
|
|
|
|
wp[#wp+1]=coord:WaypointGround(speed, formation)
|
|
tasks[#tasks+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, false)
|
|
|
|
self:T2(self.lid..string.format("%d x = %4.1f", i, x))
|
|
if self.Debug then
|
|
local MarkerID=coord:MarkToAll(string.format("Waypoing %d of group %s", #wp, self.Controllable:GetName()))
|
|
end
|
|
|
|
end
|
|
self:T2(self.lid..string.format("Total distance: %4.1f", dist))
|
|
|
|
-- Final waypoint.
|
|
wp[#wp+1]=fin:WaypointGround(speed, formation)
|
|
if self.Debug then
|
|
local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)", #wp, self.Controllable:GetName()))
|
|
end
|
|
|
|
-- Task to hold.
|
|
local ConditionWait=group:TaskCondition(nil, nil, nil, nil, wait, nil)
|
|
local TaskHold = group:TaskHold()
|
|
|
|
-- Task combo to make group hold at final waypoint.
|
|
local TaskComboFin = {}
|
|
TaskComboFin[#TaskComboFin+1] = group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, true)
|
|
TaskComboFin[#TaskComboFin+1] = group:TaskControlled(TaskHold, ConditionWait)
|
|
|
|
-- Add final task.
|
|
tasks[#tasks+1]=group:TaskCombo(TaskComboFin)
|
|
|
|
-- Original waypoints of the group.
|
|
local Waypoints = group:GetTemplateRoutePoints()
|
|
|
|
-- New points are added to the default route.
|
|
for i,p in ipairs(wp) do
|
|
table.insert(Waypoints, i, wp[i])
|
|
end
|
|
|
|
-- Set task for all waypoints.
|
|
for i,wp in ipairs(Waypoints) do
|
|
group:SetTaskWaypoint(Waypoints[i], tasks[i])
|
|
end
|
|
|
|
-- Submit task and route group along waypoints.
|
|
group:Route(Waypoints)
|
|
|
|
else
|
|
self:E(self.lid..string.format("ERROR: Group is not alive!"))
|
|
end
|
|
|
|
end
|
|
|
|
--- Function called when group is passing a waypoint. At the last waypoint we set the group back to CombatReady.
|
|
--@param Wrapper.Group#GROUP group Group which is passing a waypoint.
|
|
--@param #SUPPRESSION Fsm The suppression object.
|
|
--@param #number i Waypoint number that has been reached.
|
|
--@param #boolean final True if it is the final waypoint. Start Fightback.
|
|
function SUPPRESSION._Passing_Waypoint(group, Fsm, i, final)
|
|
|
|
-- Debug message.
|
|
local text=string.format("Group %s passing waypoint %d (final=%s)", group:GetName(), i, tostring(final))
|
|
MESSAGE:New(text,10):ToAllIf(Fsm.Debug)
|
|
if Fsm.Debug then
|
|
env.info(self.lid..text)
|
|
end
|
|
|
|
if final then
|
|
if Fsm:is("Retreating") then
|
|
-- Retreated-->Retreated.
|
|
Fsm:Retreated()
|
|
else
|
|
-- FightBack-->Combatready: Change alarm state back to default.
|
|
Fsm:FightBack()
|
|
end
|
|
end
|
|
end
|
|
|
|
|
|
--- Search a place to hide. This is any scenery object in the vicinity.
|
|
--@param #SUPPRESSION self
|
|
--@return Core.Point#COORDINATE Coordinate of the hideout place.
|
|
--@return nil If no scenery object is within search radius.
|
|
function SUPPRESSION:_SearchHideout()
|
|
-- We search objects in a zone with radius ~300 m around the group.
|
|
local Zone = ZONE_GROUP:New("Zone_Hiding", self.Controllable, self.TakecoverRange)
|
|
local gpos = self.Controllable:GetCoordinate()
|
|
|
|
-- Scan for Scenery objects to run/drive to.
|
|
Zone:Scan(Object.Category.SCENERY)
|
|
|
|
-- Array with all possible hideouts, i.e. scenery objects in the vicinity of the group.
|
|
local hideouts={}
|
|
|
|
for SceneryTypeName, SceneryData in pairs(Zone:GetScannedScenery()) do
|
|
for SceneryName, SceneryObject in pairs(SceneryData) do
|
|
|
|
local SceneryObject = SceneryObject -- Wrapper.Scenery#SCENERY
|
|
|
|
-- Position of the scenery object.
|
|
local spos=SceneryObject:GetCoordinate()
|
|
|
|
-- Distance from group to hideout.
|
|
local distance= spos:Get2DDistance(gpos)
|
|
|
|
if self.Debug then
|
|
-- Place markers on every possible scenery object.
|
|
local MarkerID=SceneryObject:GetCoordinate():MarkToAll(string.format("%s scenery object %s", self.Controllable:GetName(),SceneryObject:GetTypeName()))
|
|
local text=string.format("%s scenery: %s, Coord %s", self.Controllable:GetName(), SceneryObject:GetTypeName(), SceneryObject:GetCoordinate():ToStringLLDMS())
|
|
self:T2(self.lid..text)
|
|
end
|
|
|
|
-- Add to table.
|
|
table.insert(hideouts, {object=SceneryObject, distance=distance})
|
|
end
|
|
end
|
|
|
|
-- Get random hideout place.
|
|
local Hideout=nil
|
|
if #hideouts>0 then
|
|
|
|
-- Debug info.
|
|
self:T(self.lid.."Number of hideouts "..#hideouts)
|
|
|
|
-- Sort results table wrt number of hits.
|
|
local _sort = function(a,b) return a.distance < b.distance end
|
|
table.sort(hideouts,_sort)
|
|
|
|
-- Pick a random location.
|
|
--Hideout=hideouts[math.random(#hideouts)].object
|
|
|
|
-- Pick closest location.
|
|
Hideout=hideouts[1].object:GetCoordinate()
|
|
|
|
else
|
|
self:E(self.lid.."No hideouts found!")
|
|
end
|
|
|
|
return Hideout
|
|
|
|
end
|
|
|
|
--- Get (relative) life in percent of a group. Function returns the value of the units with the smallest and largest life. Also the average value of all groups is returned.
|
|
-- @param #SUPPRESSION self
|
|
-- @return #number Smallest life value of all units.
|
|
-- @return #number Largest life value of all units.
|
|
-- @return #number Average life value of all alife groups
|
|
-- @return #number Average life value of all groups including already dead ones.
|
|
-- @return #number Relative group strength.
|
|
function SUPPRESSION:_GetLife()
|
|
|
|
local group=self.Controllable --Wrapper.Group#GROUP
|
|
|
|
if group and group:IsAlive() then
|
|
|
|
local units=group:GetUnits()
|
|
|
|
local life_min=nil
|
|
local life_max=nil
|
|
local life_ave=0
|
|
local life_ave0=0
|
|
local n=0
|
|
|
|
local groupstrength=#units/self.IniGroupStrength*100
|
|
|
|
self.T2(self.lid..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units))
|
|
|
|
for _,unit in pairs(units) do
|
|
|
|
local unit=unit -- Wrapper.Unit#UNIT
|
|
if unit and unit:IsAlive() then
|
|
n=n+1
|
|
local life=unit:GetLife()/(unit:GetLife0()+1)*100
|
|
if life_min==nil or life < life_min then
|
|
life_min=life
|
|
end
|
|
if life_max== nil or life > life_max then
|
|
life_max=life
|
|
end
|
|
life_ave=life_ave+life
|
|
if self.Debug then
|
|
local text=string.format("n=%02d: Life = %3.1f, Life0 = %3.1f, min=%3.1f, max=%3.1f, ave=%3.1f, group=%3.1f", n, unit:GetLife(), unit:GetLife0(), life_min, life_max, life_ave/n,groupstrength)
|
|
self:T2(self.lid..text)
|
|
end
|
|
end
|
|
|
|
end
|
|
|
|
-- If the counter did not increase (can happen!) return 0
|
|
if n==0 then
|
|
return 0,0,0,0,0
|
|
end
|
|
|
|
-- Average life relative to initial group strength including the dead ones.
|
|
life_ave0=life_ave/self.IniGroupStrength
|
|
|
|
-- Average life of all alive units.
|
|
life_ave=life_ave/n
|
|
|
|
return life_min, life_max, life_ave, life_ave0, groupstrength
|
|
else
|
|
return 0, 0, 0, 0, 0
|
|
end
|
|
end
|
|
|
|
|
|
--- Heading from point a to point b in degrees.
|
|
--@param #SUPPRESSION self
|
|
--@param Core.Point#COORDINATE a Coordinate.
|
|
--@param Core.Point#COORDINATE b Coordinate.
|
|
--@return #number angle Angle from a to b in degrees.
|
|
function SUPPRESSION:_Heading(a, b)
|
|
local dx = b.x-a.x
|
|
local dy = b.z-a.z
|
|
local angle = math.deg(math.atan2(dy,dx))
|
|
if angle < 0 then
|
|
angle = 360 + angle
|
|
end
|
|
return angle
|
|
end
|
|
|
|
--- Generate Gaussian pseudo-random numbers.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #number x0 Expectation value of distribution.
|
|
-- @param #number sigma (Optional) Standard deviation. Default 10.
|
|
-- @param #number xmin (Optional) Lower cut-off value.
|
|
-- @param #number xmax (Optional) Upper cut-off value.
|
|
-- @return #number Gaussian random number.
|
|
function SUPPRESSION:_Random_Gaussian(x0, sigma, xmin, xmax)
|
|
|
|
-- Standard deviation. Default 5 if not given.
|
|
sigma=sigma or 5
|
|
|
|
local r
|
|
local gotit=false
|
|
local i=0
|
|
while not gotit do
|
|
|
|
-- Uniform numbers in [0,1). We need two.
|
|
local x1=math.random()
|
|
local x2=math.random()
|
|
|
|
-- Transform to Gaussian exp(-(x-x0)²/(2*sigma²).
|
|
r = math.sqrt(-2*sigma*sigma * math.log(x1)) * math.cos(2*math.pi * x2) + x0
|
|
|
|
i=i+1
|
|
if (r>=xmin and r<=xmax) or i>100 then
|
|
gotit=true
|
|
end
|
|
end
|
|
|
|
return r
|
|
|
|
end
|
|
|
|
--- Sets the ROE for the group and updates the current ROE variable.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #string roe ROE the group will get. Possible "Free", "Hold", "Return". Default is self.DefaultROE.
|
|
function SUPPRESSION:_SetROE(roe)
|
|
local group=self.Controllable --Wrapper.Controllable#CONTROLLABLE
|
|
|
|
-- If no argument is given, we take the default ROE.
|
|
roe=roe or self.DefaultROE
|
|
|
|
-- Update the current ROE.
|
|
self.CurrentROE=roe
|
|
|
|
-- Set the ROE.
|
|
if roe==SUPPRESSION.ROE.Free then
|
|
group:OptionROEOpenFire()
|
|
elseif roe==SUPPRESSION.ROE.Hold then
|
|
group:OptionROEHoldFire()
|
|
elseif roe==SUPPRESSION.ROE.Return then
|
|
group:OptionROEReturnFire()
|
|
else
|
|
self:E(self.lid.."Unknown ROE requested: "..tostring(roe))
|
|
group:OptionROEOpenFire()
|
|
self.CurrentROE=SUPPRESSION.ROE.Free
|
|
end
|
|
|
|
local text=string.format("Group %s now has ROE %s.", self.Controllable:GetName(), self.CurrentROE)
|
|
self:T(self.lid..text)
|
|
end
|
|
|
|
--- Sets the alarm state of the group and updates the current alarm state variable.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #string state Alarm state the group will get. Possible "Auto", "Green", "Red". Default is self.DefaultAlarmState.
|
|
function SUPPRESSION:_SetAlarmState(state)
|
|
local group=self.Controllable --Wrapper.Controllable#CONTROLLABLE
|
|
|
|
-- Input or back to default alarm state.
|
|
state=state or self.DefaultAlarmState
|
|
|
|
-- Update the current alam state of the group.
|
|
self.CurrentAlarmState=state
|
|
|
|
-- Set the alarm state.
|
|
if state==SUPPRESSION.AlarmState.Auto then
|
|
group:OptionAlarmStateAuto()
|
|
elseif state==SUPPRESSION.AlarmState.Green then
|
|
group:OptionAlarmStateGreen()
|
|
elseif state==SUPPRESSION.AlarmState.Red then
|
|
group:OptionAlarmStateRed()
|
|
else
|
|
self:E(self.lid.."Unknown alarm state requested: "..tostring(state))
|
|
group:OptionAlarmStateAuto()
|
|
self.CurrentAlarmState=SUPPRESSION.AlarmState.Auto
|
|
end
|
|
|
|
local text=string.format("Group %s now has Alarm State %s.", self.Controllable:GetName(), self.CurrentAlarmState)
|
|
self:T(self.lid..text)
|
|
end
|
|
|
|
--- Print event-from-to string to DCS log file.
|
|
-- @param #SUPPRESSION self
|
|
-- @param #string BA Before/after info.
|
|
-- @param #string Event Event.
|
|
-- @param #string From From state.
|
|
-- @param #string To To state.
|
|
function SUPPRESSION:_EventFromTo(BA, Event, From, To)
|
|
local text=string.format("\n%s: %s EVENT %s: %s --> %s", BA, self.Controllable:GetName(), Event, From, To)
|
|
self:T2(self.lid..text)
|
|
end
|
|
|
|
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
|
|
|