From e1d12cbd8e23d74aa5646cba17c22dc4b7a10213 Mon Sep 17 00:00:00 2001 From: funkyfranky Date: Mon, 16 Oct 2017 00:14:41 +0200 Subject: [PATCH] Suppression + RAT --- Moose Development/Moose/Core/Event.lua | 2 +- Moose Development/Moose/Functional/RAT.lua | 296 +++++++++-- .../Moose/Functional/SuppressionFire.lua | 460 ++++++++++++++---- 3 files changed, 605 insertions(+), 153 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 01235b650..2608c4ffe 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1019,7 +1019,7 @@ function EVENT:onEvent( Event ) end end else - self:E( { EventMeta.Text, Event } ) + self:T( { EventMeta.Text, Event } ) end Event = nil diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 03055ea3f..07ce6dc0f 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -64,6 +64,7 @@ -- @type RAT -- @field #string ClassName Name of the Class. -- @field #boolean debug Turn debug messages on or off. +-- @field Core.Group#GROUP templategroup Group serving as template for the RAT aircraft. -- @field #string alias Alias for spawned group. -- @field #number spawndelay Delay time in seconds before first spawning happens. -- @field #number spawninterval Interval between spawning units/groups. Note that we add a randomization of 50%. @@ -89,6 +90,8 @@ -- @field #table departure_ports Array containing the names of the destination airports. -- @field #table destination_ports Array containing the names of the destination airports. -- @field #table excluded_ports Array containing the names of explicitly excluded airports. +-- @field #table destination_zones Array containing the names of the destination zones. +-- @field #boolean destinationzone Destination is a zone and not an airport. -- @field Core.Zone#ZONE departure_Azone Zone containing the departure airports. -- @field Core.Zone#ZONE destination_Azone Zone containing the destination airports. -- @field #table ratcraft Array with the spawned RAT aircraft. @@ -107,6 +110,8 @@ -- @field #table Menu F10 menu items for this RAT object. -- @field #string SubMenuName Submenu name for RAT object. -- @field #boolean respawn_at_landing Respawn aircraft the moment they land rather than at engine shutdown. +-- @field #boolean norespawn Aircraft will not be respawned after they have finished their route. +-- @field #boolean respawn_after_takeoff Aircraft will be respawned directly after take-off. -- @field #number respawn_delay Delay in seconds until repawn happens after landing. -- @field #table markerids Array with marker IDs. -- @field #string livery Livery of the aircraft set by user. @@ -262,6 +267,7 @@ RAT={ ClassName = "RAT", -- Name of class: RAT = Random Air Traffic. debug=false, -- Turn debug messages on or off. + templategroup=nil, -- Template group for the RAT aircraft. alias=nil, -- Alias for spawned group. spawndelay=5, -- Delay time in seconds before first spawning happens. spawninterval=5, -- Interval between spawning units/groups. Note that we add a randomization of 50%. @@ -286,6 +292,8 @@ RAT={ departure_zones={}, -- Array containing the names of the departure zones. departure_ports={}, -- Array containing the names of the departure airports. destination_ports={}, -- Array containing the names of the destination airports. + destination_zones={}, -- Array containing the names of destination zones. + destinationzone=false, -- Destination is a zone and not an airport. excluded_ports={}, -- Array containing the names of explicitly excluded airports. departure_Azone=nil, -- Zone containing the departure airports. destination_Azone=nil, -- Zone containing the destination airports. @@ -305,11 +313,16 @@ RAT={ Menu={}, -- F10 menu items for this RAT object. SubMenuName=nil, -- Submenu name for RAT object. respawn_at_landing=false, -- Respawn aircraft the moment they land rather than at engine shutdown. + norespawn=false, -- Aircraft will not get respawned. + respawn_after_takeoff=false, -- Aircraft will be respawned directly after takeoff. respawn_delay=nil, -- Delay in seconds until repawn happens after landing. markerids={}, -- Array with marker IDs. livery=nil, -- Livery of the aircraft. skill="High", -- Skill of AI. ATCswitch=true, -- Enable ATC. + parking_id=nil, + argkey=nil, + arg={}, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -336,6 +349,7 @@ RAT.wp={ descent=7, holding=8, landing=9, + finalwp=10, } --- RAT friendly coalitions. @@ -458,6 +472,9 @@ function RAT:New(groupname, alias) env.error("Group with name "..groupname.." does not exist in the mission editor!") return nil end + + -- Store template group. + self.templategroup=GROUP:FindByName(groupname) -- Set own coalition. self.coalition=DCSgroup:getCoalition() @@ -529,6 +546,8 @@ function RAT:Spawn(naircraft) text=text..string.format("Spawn delay: %4.1f\n", self.spawndelay) text=text..string.format("Spawn interval: %4.1f\n", self.spawninterval) text=text..string.format("Respawn after landing: %s\n", tostring(self.respawn_at_landing)) + text=text..string.format("Respawning off: %s\n", tostring(self.norespawn)) + text=text..string.format("Respawn after take-off: %s\n", tostring(self.respawn_after_takeoff)) text=text..string.format("Respawn delay: %s\n", tostring(self.respawn_delay)) text=text..string.format("ROE: %s\n", tostring(self.roe)) text=text..string.format("ROT: %s\n", tostring(self.rot)) @@ -577,7 +596,7 @@ function RAT:Spawn(naircraft) self:HandleEvent(EVENTS.Land, self._OnLand) self:HandleEvent(EVENTS.EngineShutdown, self._OnEngineShutdown) self:HandleEvent(EVENTS.Dead, self._OnDead) - self:HandleEvent(EVENTS.Crash, self._OnCrash) + --self:HandleEvent(EVENTS.Crash, self._OnCrash) -- TODO: add hit event? end @@ -734,6 +753,36 @@ function RAT:SetDestination(names) end +--- Set name of destination zones for the AI aircraft. If multiple names are given as a table, one zone is picked randomly as destination. +-- @param #RAT self +-- @param #string names Name or table of names of zones defined in the mission editor. +function RAT:SetDestinationZone(names) + + -- Random destination is deactivated now that user specified destination zone(s). + self.random_destination=false + -- Destination is a zone. Needs special care. + self.destinationzone=true + -- No ATC required. + self.ATCswitch=false + + if type(names)=="table" then + + for _,name in pairs(names) do + table.insert(self.destination_zones, ZONE:New(name)) + end + + elseif type(names)=="string" then + + table.insert(self.destination_zones, ZONE:New(names)) + + else + -- Error message. + env.error("Input parameter must be a string or a table!") + end + +end + + --- Include all airports which lie in a zone as possible destinations. -- @param #RAT self -- @param Core.Zone#ZONE zone Zone in which the airports lie. @@ -818,6 +867,26 @@ function RAT:RespawnAfterLanding(delay) self.respawn_delay=delay end +--- Aircraft will not get respawned when they finished their route. +-- @param #RAT self +function RAT:NoRespawn() + self.norespawn=true +end + +--- Aircraft will be respawned directly after take-off. +-- @param #RAT self +function RAT:RespawnAfterTakeoff() + self.respawn_after_takeoff=true +end + +--- Set parking id of aircraft. +-- @param #RAT self +-- @param #string id Parking ID of the aircraft. +function RAT:SetParkingID(id) + self.parking_id=id + env.info(RAT.id.."Setting parking ID to "..self.parking_id) +end + --- Set the time after which inactive groups will be destroyed. Default is 300 seconds. -- @param #RAT self -- @param #number time Time in seconds. @@ -1098,6 +1167,13 @@ function RAT:_SpawnWithRoute(_departure, _destination) RAT:_ATCAddFlight(group:GetName(), destination:GetName()) end + if self.destinationzone then +-- env.info(RAT.id.." setstate") +-- self:E(self.argkey) +-- self:E(self.arg) +-- group:SetState(group, self.argkey, self.arg ) + end + -- Set ROE, default is "weapon hold". self:_SetROE(group, self.roe) @@ -1148,11 +1224,14 @@ function RAT:_SpawnWithRoute(_departure, _destination) MENU_MISSION_COMMAND:New("Evade on fire", self.Menu[self.SubMenuName].groups[self.SpawnIndex]["rot"], self._SetROT, self, group, RAT.ROT.evade) -- F10/RAT//Group X/ MENU_MISSION_COMMAND:New("Despawn group", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._Despawn, self, group) - MENU_MISSION_COMMAND:New("Clear for landing", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.ClearForLanding, self, group:GetName()) + if self.ATCswitch then + MENU_MISSION_COMMAND:New("Clear for landing", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.ClearForLanding, self, group:GetName()) + end MENU_MISSION_COMMAND:New("Place markers", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self._PlaceMarkers, self, waypoints) MENU_MISSION_COMMAND:New("Status report", self.Menu[self.SubMenuName].groups[self.SpawnIndex], self.Status, self, true, self.SpawnIndex) end + env.info("RAT debug before end of _SpawnWithRoute") return self.SpawnIndex end @@ -1502,26 +1581,46 @@ function RAT:_SetRoute(takeoff, _departure, _destination) d_cruise=100 end - -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). - local c0=Pdeparture - local c1=c0:Translate(d_climb/2, heading) - local c2=c1:Translate(d_climb/2, heading) - local c3=c2:Translate(d_cruise, heading) - local c4=c3:Translate(d_descent/2, heading) - local c5=Pholding - local c6=Pdestination + local waypoints + if not self.destinationzone then - --Convert coordinates into route waypoints. - local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) - local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) - local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) - local wp3=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) - local wp4=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) - local wp5=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) - local wp6=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) + -- Coordinates of route from departure (0) to cruise (1) to descent (2) to holing (3) to destination (4). + local c0=Pdeparture + local c1=c0:Translate(d_climb/2, heading) + local c2=c1:Translate(d_climb/2, heading) + local c3=c2:Translate(d_cruise, heading) + local c4=c3:Translate(d_descent/2, heading) + local c5=Pholding + local c6=Pdestination + + --Convert coordinates into route waypoints. + local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + local wp3=self:_Waypoint(RAT.wp.cruise, c3, VxCruise, FLcruise) + local wp4=self:_Waypoint(RAT.wp.descent, c4, VxDescent, FLcruise-(FLcruise-(h_holding+H_holding))/2) + local wp5=self:_Waypoint(RAT.wp.holding, c5, VxHolding, H_holding+h_holding) + local wp6=self:_Waypoint(RAT.wp.landing, c6, VxFinal, H_destination, destination) + + -- set waypoints + waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6} + + else - -- set waypoints - local waypoints = {wp0, wp1, wp2, wp3, wp4, wp5, wp6} + local c0=Pdeparture + local c1=c0:Translate(d_climb/2, heading) + local c2=c1:Translate(d_climb/2, heading) + local c3=Pdestination + + local wp0=self:_Waypoint(takeoff, c0, VxClimb, H_departure, departure) + local wp1=self:_Waypoint(RAT.wp.climb, c1, VxClimb, H_departure+(FLcruise-H_departure)/2) + local wp2=self:_Waypoint(RAT.wp.cruise, c2, VxCruise, FLcruise) + local wp3=self:_Waypoint(RAT.wp.finalwp, c3, VxCruise, FLcruise) + + -- set waypoints + waypoints = {wp0, wp1, wp2, wp3} + + end -- Place markers of waypoints on F10 map. if self.placemarkers then @@ -1637,7 +1736,12 @@ function RAT:_PickDestination(destinations, _random) destination=destinations[math.random(#destinations)] -- Wrapper.Airbase#AIRBASE -- Debug message. - local text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" + local text + if self.destinationzone then + text="Chosen destination zone: "..destination:GetName() + else + text="Chosen destination airport: "..destination:GetName().." (ID "..destination:GetID()..")" + end env.info(RAT.id..text) if self.debug then MESSAGE:New(text, 30):ToAll() @@ -1686,13 +1790,23 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) else - -- Airports specified by user. - for _,name in pairs(self.destination_ports) do - --if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then - if name~=departure:GetName() then - local airport=AIRBASE:FindByName(name) - --TODO: Maybe here I should check min/max distance as well? But the user explicitly specified the airports... - table.insert(possible_destinations, airport) + if self.destinationzone then + + -- Zones specified by user. + for _,zone in pairs(self.destination_zones) do + table.insert(possible_destinations, zone) + end + + else + + -- Airports specified by user. + for _,name in pairs(self.destination_ports) do + --if self:_IsFriendly(name) and not self:_Excluded(name) and name~=departure:GetName() then + if name~=departure:GetName() then + local airport=AIRBASE:FindByName(name) + --TODO: Maybe here I should check min/max distance as well? But the user explicitly specified the airports... + table.insert(possible_destinations, airport) + end end end @@ -1713,7 +1827,7 @@ function RAT:_GetDestinations(departure, q, minrange, maxrange) end table.sort(possible_destinations, compare) else - env.error(RAT.id.."No possible destination airports found!") + env.error(RAT.id.."No possible destinations found!") possible_destinations=nil end @@ -1933,7 +2047,11 @@ function RAT:Status(message, forID) local Ddestination=Pn:Get2DDistance(self.ratcraft[i].destination:GetCoordinate()) -- Distance remaining to holding point, which is waypoint 6 - local Hp=COORDINATE:New(self.ratcraft[i].waypoints[6].x, self.ratcraft[i].waypoints[6].alt, self.ratcraft[i].waypoints[6].y) + local idx=6 + if self.destinationzone then + idx=4 + end + local Hp=COORDINATE:New(self.ratcraft[i].waypoints[idx].x, self.ratcraft[i].waypoints[idx].alt, self.ratcraft[i].waypoints[idx].y) local Dholding=Pn:Get2DDistance(Hp) -- Status shortcut. @@ -2143,6 +2261,14 @@ function RAT:_OnTakeoff(EventData) -- Set status. self:_SetStatus(SpawnGroup, "On journey (after takeoff)") + if self.respawn_after_takeoff then + text="Event: Group "..SpawnGroup:GetName().." will be respawned." + env.info(RAT.id..text) + + -- Respawn group. + self:_Respawn(SpawnGroup) + end + end end @@ -2180,7 +2306,7 @@ function RAT:_OnLand(EventData) RAT:_ATCFlightLanded(SpawnGroup:GetName()) end - if self.respawn_at_landing then + if self.respawn_at_landing and not self.norespawn then text="Event: Group "..SpawnGroup:GetName().." will be respawned." env.info(RAT.id..text) @@ -2220,7 +2346,7 @@ function RAT:_OnEngineShutdown(EventData) -- Set status. self:_SetStatus(SpawnGroup, "Parking (shutting down engines)") - if not self.respawn_at_landing then + if not self.respawn_at_landing and not self.norespawn then text="Event: Group "..SpawnGroup:GetName().." will be respawned." env.info(RAT.id..text) @@ -2281,9 +2407,9 @@ function RAT:_OnCrash(EventData) local SpawnGroup = EventData.IniGroup --Wrapper.Group#GROUP - env.info(string.format("%sGroup %s crashed!", RAT.id, SpawnGroup:GetName())) - if SpawnGroup then + + env.info(string.format("%sGroup %s crashed!", RAT.id, SpawnGroup:GetName())) -- Get the template name of the group. This can be nil if this was not a spawned group. local EventPrefix = self:_GetPrefixFromGroup(SpawnGroup) @@ -2319,10 +2445,13 @@ end -- @param Wrapper.Group#GROUP group Group to be despawned. function RAT:_Despawn(group) + env.info("RAT debug _despawn 0") + local index=self:GetSpawnIndexFromGroup(group) + env.info("RAT debug index = "..index) self.ratcraft[index].group:Destroy() self.ratcraft[index].group=nil - + env.info("RAT debug _despawn 1") -- Decrease group alive counter. self.alive=self.alive-1 @@ -2330,7 +2459,7 @@ function RAT:_Despawn(group) if self.f10menu then self.Menu[self.SubMenuName]["groups"][index]:Remove() end - + env.info("RAT debug _despawn 2") --TODO: Maybe here could be some more arrays deleted? end @@ -2410,6 +2539,10 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) _Altitude = 0 _alttype="RADIO" _AID = Airport:GetID() + elseif Type==RAT.wp.finalwp then + _Type="Turning Point" + _Action="Fly Over Point" + _alttype="BARO" else env.error("Unknown waypoint type in RAT:Waypoint() function!") _Type="Turning Point" @@ -2478,9 +2611,6 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) --env.error(RAT.id.."Unknown Airport categoryin _Waypoint()!") end end --- if _AID then --- RoutePoint.airdromeId=_AID --- end -- properties RoutePoint.properties = { ["vnav"] = 1, @@ -2494,6 +2624,16 @@ function RAT:_Waypoint(Type, Coord, Speed, Altitude, Airport) -- Duration of holing. Between 10 and 170 seconds. local Duration=self:_Randomize(90,0.9) RoutePoint.task=self:_TaskHolding({x=Coord.x, y=Coord.z}, Altitude, Speed, Duration) + elseif Type==RAT.wp.finalwp then + local TaskRespawn, argkey, arg = self:_TaskFunction("RAT._FinalWaypoint", self) + self.argkey=argkey + self.arg=arg + local TaskCombo = {TaskRespawn} + RoutePoint.task = {} + RoutePoint.task.id = "ComboTask" + RoutePoint.task.params = {} + RoutePoint.task.params.tasks = TaskCombo + self:E(TaskRespawn) else RoutePoint.task = {} RoutePoint.task.id = "ComboTask" @@ -2551,9 +2691,6 @@ function RAT:_Routeinfo(waypoints, comment) return total end -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - - ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Orbit at a specified position at a specified alititude with a specified speed. @@ -2607,6 +2744,68 @@ function RAT:_TaskHolding(P1, Altitude, Speed, Duration) return DCSTask end + +--- Function called if aircraft reached its final waypoint. Aircraft gets destroyed and respawned. +-- @param Core.Group#GROUP group Group of aircraft. +-- @param #RAT rat RAT object. +function RAT._FinalWaypoint(group, rat) + env.info(RAT.id.."FinalWaypoint:") + BASE:E(group) + BASE:E(rat) + + -- Spawn new group. + rat:_Respawn(group) + + -- Despawn old group. + rat:_Despawn(group) +end + +--- Orbit at a specified position at a specified alititude with a specified speed. +-- @param #RAT self +-- @param #string FunctionString Name of the function to be called. +function RAT:_TaskFunction(FunctionString, ... ) + self:F2({FunctionString, arg}) + + local DCSTask + local ArgumentKey + + local templatename=self.templategroup:GetName() + local groupname=self:_AnticipatedGroupName() + + env.info(RAT.id.."template name "..templatename) + env.info(RAT.id.."anticipated name "..groupname) + + local DCSScript = {} + --DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:Find( ... ) " + DCSScript[#DCSScript+1] = "env.info(\"RAT blabla\") " + DCSScript[#DCSScript+1] = "local MissionControllable = GROUP:FindByName(\""..groupname.."\") " + DCSScript[#DCSScript+1] = "local RATtemplateControllable = GROUP:FindByName(\""..templatename.."\") " + + if arg and arg.n > 0 then + ArgumentKey = '_' .. tostring(arg):match("table: (.*)") + env.info(RAT.id.."Argumentkey: "..ArgumentKey) + self.templategroup:SetState(self.templategroup, ArgumentKey, arg) + DCSScript[#DCSScript+1] = "local Arguments = RATtemplateControllable:GetState(RATtemplateControllable, '" .. ArgumentKey .. "' ) " + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable, unpack( Arguments ) )" + else + DCSScript[#DCSScript+1] = FunctionString .. "( MissionControllable )" + end + + DCSTask = self.templategroup:TaskWrappedAction(self.templategroup:CommandDoScript(table.concat(DCSScript))) + + env.info(RAT.id.."Taskfunction:") + self:E( DCSTask ) + + return DCSTask, ArgumentKey, arg +end + +--- Anticipated group name from alias and spawn index. +-- @param #RAT self +-- @return #string Name the group will get after it is spawned. +function RAT:_AnticipatedGroupName() + return string.format("%s#%03d", self.alias, self.SpawnIndex+1) +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Calculate the max flight level for a given distance and fixed climb and descent rates. @@ -2855,9 +3054,11 @@ function RAT:_PlaceMarkers(waypoints) self:_SetMarker("Climb", waypoints[2]) self:_SetMarker("Begin of Cruise", waypoints[3]) self:_SetMarker("End of Cruise", waypoints[4]) - self:_SetMarker("Descent", waypoints[5]) - self:_SetMarker("Holding Point", waypoints[6]) - self:_SetMarker("Destination", waypoints[7]) + if #waypoints>4 then + self:_SetMarker("Descent", waypoints[5]) + self:_SetMarker("Holding Point", waypoints[6]) + self:_SetMarker("Destination", waypoints[7]) + end end @@ -2949,7 +3150,8 @@ function RAT:_ModifySpawnTemplate(waypoints) -- Parking spot. UnitTemplate.parking = nil - UnitTemplate.parking_id = nil + UnitTemplate.parking_id = self.parking_id + --env.info(RAT.id.."Parking ID "..tostring(self.parking_id)) -- Initial altitude UnitTemplate.alt=PointVec3.y @@ -2969,7 +3171,7 @@ function RAT:_ModifySpawnTemplate(waypoints) --SpawnTemplate.uncontrolled=true -- Update modified template for spawn group. - self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate + --self.SpawnGroups[self.SpawnIndex].SpawnTemplate=SpawnTemplate self:T(SpawnTemplate) end diff --git a/Moose Development/Moose/Functional/SuppressionFire.lua b/Moose Development/Moose/Functional/SuppressionFire.lua index 6a02a0d90..5e21a8de0 100644 --- a/Moose Development/Moose/Functional/SuppressionFire.lua +++ b/Moose Development/Moose/Functional/SuppressionFire.lua @@ -36,11 +36,14 @@ -- @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 life Relative life in precent of the group. --- @field #number Tsuppress Time in seconds the groups is suppressed. Randomly chosen between Tsuppress_min and Tsuppress_max. +-- @field #number TsuppressionStart Time at which the suppression started. +-- @field #number TsuppressionOver Time at which the suppression will be over. -- @field #number Thit Last time the unit was hit. -- @field #number Nhit Number of times the unit was hit since it last was in state "CombatReady". -- @field Core.Zone#ZONE Zone_Retreat Zone into which a group retreats. --- @field #number LifeMin Life of group in percent at which the group will be ordered to retreat. +-- @field #number LifeThreshold Life of group in percent at which the group will be ordered to retreat. +-- @field #number IniGroupStrength Number of units in a group at start. +-- @field #number GroupStrengthThreshold Threshold of group strength before retreat is ordered. -- @extends Core.Fsm#FSM_CONTROLLABLE -- @@ -54,11 +57,14 @@ AI_Suppression={ ClassName = "AI_Suppression", Tsuppress_min = 5, Tsuppress_max = 20, - Tsuppress = nil, + TsuppressStart = nil, + TsuppressOver = nil, Thit = nil, Nhit = 0, Zone_Retreat = nil, - LifeMin = 25, + LifeThreshold = 25, + IniGroupStrength = nil, + GroupStrengthThreshold=80, } --- Some ID to identify who we are in output of the DCS.log file. @@ -67,7 +73,7 @@ AI_Suppression.id="SFX | " ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---TODO: Figure out who was shooting and move away from him <== Not possible! +--TODO: Figure out who was shooting and move away from him. --TODO: Move behind a scenery building if there is one nearby. --TODO: Retreat to a given zone or point. @@ -81,38 +87,52 @@ function AI_Suppression:New(Group) -- Check that group is present. if Group then - env.info("Suppression fire for group "..Group:GetName()) + env.info(AI_Suppression.id.."Suppression fire for group "..Group:GetName()) else - env.info("Suppression fire: Group does not exist!") + env.info(AI_Suppression.id.."Suppression fire: 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 - env.error("Suppression fire group "..Group:GetName().." has to be a GROUND group!") + env.error(AI_Suppression.id.."Suppression fire group "..Group:GetName().." has to be a GROUND group!") return nil end -- Inherits from FSM_CONTROLLABLE local self=BASE:Inherit(self, FSM_CONTROLLABLE:New()) -- #AI_Suppression + -- Set the controllable for the FSM. self:SetControllable(Group) + -- Initial group strength. + self.IniGroupStrength=#Group:GetUnits() + + + -- Get life of group in %. + local life_min, life_max, life_ave, groupstrength=self:_GetLife() + -- Group is initially in state CombatReady. - self:SetStartState("CombatReady") + self:SetStartState("none") -- Transitions: --------------- - -- Transition from anything to "Suppressed" after event "Hit". - self:AddTransition("*", "Hit", "Suppressed") + -- Transition from anything to "Suppressed" after event "Hit". + self:AddTransition("*", "Start", "CombatReady") + + -- Transition from anything to "Suppressed" after event "Hit". + self:AddTransition("*", "Hit", "*") -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. - self:AddTransition("Suppressed", "Recovered", "CombatReady") + self:AddTransition("*", "Recovered", "*") + + -- Transition from "Suppressed" back to "CombatReady after the unit had time to recover. + self:AddTransition("*", "Suppress", "Suppressed") -- Transition from "Suppressed" to "Hiding" after event "Hit". - self:AddTransition("Suppressed", "TakeCover", "Hiding") + self:AddTransition("*", "TakeCover", "Hiding") -- Transition from anything to "Retreating" after e.g. being severely damaged. self:AddTransition("*", "Retreat", "Retreating") @@ -123,12 +143,7 @@ function AI_Suppression:New(Group) -- Check status of the group. self:AddTransition("*", "Status", "*") - - -- Handle DCS event hit. - self:HandleEvent(EVENTS.Hit, self.OnEventHit) - - -- Handle DCS event dead. - self:HandleEvent(EVENTS.Dead, self.OnEventDead) + --self:TakeCover() -- return self return self @@ -158,6 +173,21 @@ end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- After "Start" event. +-- @param #AI_Suppression self +function AI_Suppression:onafterStart(Controlable, From, Event, To) + env.info(AI_Suppression.id..string.format("onafterStart: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) + + -- Handle DCS event hit. + self:HandleEvent(EVENTS.Hit, self._OnHit) + + -- Handle DCS event dead. + self:HandleEvent(EVENTS.Dead, self._OnDead) + +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Before "Status" event. -- @param #AI_Suppression self function AI_Suppression:OnBeforeStatus(Controlable, From, Event, To) @@ -174,10 +204,16 @@ function AI_Suppression:OnAfterStatus(Controlable, From, Event, To) self:__Status(30) end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- Before "Hit" event. (Of course, this is not really before the group got hit.) -- @param #AI_Suppression self -function AI_Suppression:OnBeforeHit(Controlable, From, Event, To) +-- @param Wrapper.Controllable#CONTROLLABLE Controlable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Point#COORDINATE Fallback Fallback coordinates (or nil if no attacker could be found). +function AI_Suppression:OnBeforeHit(Controlable, From, Event, To, Fallback) env.info(AI_Suppression.id..string.format("OnBeforeHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) -- Increase Hit counter. @@ -185,16 +221,51 @@ function AI_Suppression:OnBeforeHit(Controlable, From, Event, To) -- Info on hit times. env.info(AI_Suppression.id..string.format("Group has just been hit %d times.", self.Nhit)) + end --- After "Hit" event. -- @param #AI_Suppression self -function AI_Suppression:OnAfterHit(Controlable, From, Event, To) +function AI_Suppression:OnAfterHit(Controlable, From, Event, To, Fallback) env.info(AI_Suppression.id..string.format("OnAfterHit: %s event %s from %s to %s", Controlable:GetName(), Event, From, To)) - - -- Nothing to do yet. Just monitoring the event. + + -- Suppress fire of group. + self:_Suppress() + + -- Get life of group in %. + local life_min, life_max, life_ave, groupstrength=self:_GetLife() + + if self:is("CombatReady") then + env.info(AI_Suppression.id..string.format("Group %s is currently CombatReady.", Controlable:GetName())) + self:Suppress() + elseif self:Is("Suppressed") then + env.info(AI_Suppression.id..string.format("Group %s is currently Suppressed.", Controlable:GetName())) + elseif self:Is("Retreating") then + env.info(AI_Suppression.id..string.format("Group %s is currently Retreating.", Controlable:GetName())) + elseif self:is("Hiding") then + env.info(AI_Suppression.id..string.format("Group %s is currently Hiding.", Controlable:GetName())) + end + + -- After three hits fall back a bit. + local nfallback=3 + if self.Nhit==nfallback then + env.info(AI_Suppression.id..string.format("Group %s is falling back after %d hits.", Controlable:GetName(), nfallback)) + Fallback:SmokeGreen() + local FallbackMarkerID=Fallback:MarkToAll("Fall back position for group "..Controlable:GetName():GetName()) + self:_FallBack(Fallback) + end + + -- If life of one unit is below threshold, the group is ordered to retreat (if a zone has been specified). + if not self:Is("Retreating") then + if groupstrengthself.TsuppressionOver then + self.TsuppressionOver=Tnow+Tsuppress + else + renew=false + end + else + self.TsuppressionOver=Tnow+Tsuppress + end + + -- Recovery event will be called in Tsuppress seconds. (We add one second to be sure the time has really passed when recovery is checked.) + if renew then + self:__Recovered(self.TsuppressionOver-Tnow) + end + + -- Debug message. + local text=string.format("Group %s is suppressed for %d seconds.", Controlable:GetName(), Tsuppress) + MESSAGE:New(text, 30):ToAll() + env.info(AI_Suppression.id..text) + text=string.format("Suppression starts at %f and ends at %f.", Tnow, self.TsuppressionOver) + env.info(AI_Suppression.id..text) + +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 #AI_Suppression self -- @param Wrapper.Group#GROUP group Group of unit. @@ -440,10 +595,12 @@ function AI_Suppression:_GetLife() local life_ave=0 local n=0 local units=group:GetUnits() + local groupstrength=#units/self.IniGroupStrength*100 for _,unit in pairs(units) do - if unit then + local unit=unit -- Wrapper.Unit#UNIT + if unit and unit:IsActive() then n=n+1 - local life=unit:GetLife()/unit:GetLife0()*100 + local life=unit:GetLife()/(unit:GetLife0()+1)*100 if life < life_min then life_min=life end @@ -451,10 +608,13 @@ function AI_Suppression:_GetLife() life_max=life end life_ave=life_ave+life + local text=string.format("n=%d: 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) + env.info(AI_Suppression.id..text) end end life_ave=life_ave/n - return life_min, life_max, life_ave + + return life_min, life_max, life_ave, groupstrength else return 0, 0, 0 end @@ -473,12 +633,102 @@ function AI_Suppression:_RetreatToZone(zone, speed, formation) speed = speed or 999 formation = formation or "Vee" + -- + env.info(AI_Suppression.id.."Retreat zone : "..zone:GetName()) + -- Get a random point in the retreat zone. - local ZonePoint=zone:GetRandomPointVec2() + local ZoneCoord=zone:GetRandomCoordinate() -- Core.Point#COORDINATE + local ZoneVec2=ZoneCoord:GetVec2() + + -- Debug smoke zone and point. + ZoneCoord:SmokeBlue() + zone:SmokeZone(SMOKECOLOR.Red, 12) -- Set task to go to zone. - self.Controllable:TaskRouteToVec2(ZonePoint, speed, formation) + self.Controllable:TaskRouteToVec2(ZoneVec2, speed, formation) end +--- Determine the coordinate to which a unit should fall back. +--@param #AI_Suppression self +--@param Core.Point#COORDINATE a Coordinate of the defending group. +--@param Core.Point#COORDINATE b Coordinate of the attacking group. +--@return Core.Point#COORDINATE Fallback coordinates. +function AI_Suppression:_FallBackCoord(a, b, distance) + local dx = b.x-a.x + -- take the right value for y-coordinate (if we have "alt" then "y" if not "z") + local ay + if a.alt then + ay=a.y + else + ay=a.z + end + local by + if b.alt then + by=b.y + else + by=b.z + end + local dy = by-ay + local angle = math.deg(math.atan2(dy,dx)) + if angle < 0 then + angle = 360 + angle + end + angle=angle-180 + local fbp=a:Translate(distance, angle) + return fbp +end + + +--- Fall back (move away) from enemy who is shooting on the group. +--@param #AI_Suppression self +--@param Core.Point#COORDINATE coord_fbp Coordinate of the fall back point. +function AI_Suppression:_FallBack(coord_fbp) + + local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE + + local Waypoints = group:GetTemplateRoutePoints() + + local coord_grp = group:GetCoordinate() + local wp1 = coord_grp:WaypointGround(99, "Vee") + local wp2 = coord_fbp:WaypointGround(99, "Vee") + + table.insert(Waypoints, 1, wp1) + table.insert(Waypoints, 2, wp2) + + -- Condition to wait. + local ConditionWait=group:TaskCondition(nil, nil, nil, nil, 30, nil) + + -- Task to hold. + local TaskHold = group:TaskHold() + + local TaskRoute1 = group:TaskFunction("AI_Suppression._Passing_Waypoint", self, 0) + local TaskCombo2 = {} + TaskCombo2[#TaskCombo2+1] = group:TaskFunction("AI_Suppression._Passing_Waypoint", self, 1) + TaskCombo2[#TaskCombo2+1] = group:TaskControlled(TaskHold, ConditionWait) + local TaskRoute2 = group:TaskCombo(TaskCombo2) + + group:SetTaskWaypoint(Waypoints[1], TaskRoute1) + group:SetTaskWaypoint(Waypoints[2], TaskRoute2) + + group:Route(Waypoints) + +end + + +--- Group has reached a waypoint. +--@param #AI_Suppression self +--@param #number i Waypoint number that has been reached. +function AI_Suppression._Passing_Waypoint(group, Fsm, i) + env.info(AI_Suppression.id.."Passing waypoint") + BASE:E(group) + BASE:E(Fsm) + BASE:E(i) + + MESSAGE:New(string.format("Group %s passing waypoint %d", group:GetName(), i),30):ToAll() + if i==1 then + MESSAGE:New(string.format("Group %s has reached fallback point.", group:GetName(), i),30):ToAll() + end +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- \ No newline at end of file