diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 77378e7fb..38f980a88 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -80,7 +80,8 @@ -- @field #string autosavepath Path where the asset file is saved on auto save. -- @field #string autosavefile File name of the auto asset save file. Default is auto generated from warehouse id and name. -- @field #boolean safeparking If true, parking spots for aircraft are considered as occupied if e.g. a client aircraft is parked there. Default false. --- @field #boolean isunit If true, warehouse is represented by a unit instead of a static. +-- @field #boolean isUnit If `true`, warehouse is represented by a unit instead of a static. +-- @field #boolean isShip If `true`, warehouse is represented by a ship unit. -- @field #number lowfuelthresh Low fuel threshold. Triggers the event AssetLowFuel if for any unit fuel goes below this number. -- @field #boolean respawnafterdestroyed If true, warehouse is respawned after it was destroyed. Assets are kept. -- @field #number respawndelay Delay before respawn in seconds. @@ -1590,7 +1591,8 @@ WAREHOUSE = { autosavepath = nil, autosavefile = nil, saveparking = false, - isunit = false, + isUnit = false, + isShip = false, lowfuelthresh = 0.15, respawnafterdestroyed=false, respawndelay = nil, @@ -1655,6 +1657,7 @@ WAREHOUSE = { -- @field #table transportassets Table of transport carrier assets. Each element of the table is a @{#WAREHOUSE.Assetitem}. -- @field #number transportattribute Attribute of transport assets of type @{#WAREHOUSE.Attribute}. -- @field #number transportcategory Category of transport assets of type @{#WAREHOUSE.Category}. +-- @field #boolean lateActivation Assets are spawned in late activated state. --- Item of the warehouse pending queue table. -- @type WAREHOUSE.Pendingitem @@ -1857,39 +1860,45 @@ WAREHOUSE.version="1.0.2" -- @return #WAREHOUSE self function WAREHOUSE:New(warehouse, alias) + -- Inherit everthing from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #WAREHOUSE + -- Check if just a string was given and convert to static. if type(warehouse)=="string" then - local warehousename=warehouse + local warehousename=warehouse warehouse=UNIT:FindByName(warehousename) if warehouse==nil then warehouse=STATIC:FindByName(warehousename, true) - self.isunit=false - else - self.isunit=true - if warehouse:IsShip() then - env.info("FF warehouse is ship!") - self.isShip=true - end end end -- Nil check. if warehouse==nil then - BASE:E("ERROR: Warehouse does not exist!") + env.error("ERROR: Warehouse does not exist!") return nil end + + -- Check if we have a STATIC or UNIT object. + if warehouse:IsInstanceOf("STATIC") then + self.isUnit=false + elseif warehouse:IsInstanceOf("UNIT") then + self.isUnit=true + if warehouse:IsShip() then + self.isShip=true + end + else + env.error("ERROR: Warehouse is neither STATIC nor UNIT object!") + return nil + end -- Set alias. self.alias=alias or warehouse:GetName() - -- Print version. - env.info(string.format("Adding warehouse v%s for structure %s with alias %s", WAREHOUSE.version, warehouse:GetName(), self.alias)) - - -- Inherit everthing from FSM class. - local self=BASE:Inherit(self, FSM:New()) -- #WAREHOUSE - -- Set some string id for output to DCS.log file. self.lid=string.format("WAREHOUSE %s | ", self.alias) + + -- Print version. + self:I(self.lid..string.format("Adding warehouse v%s for structure %s [isUnit=%s, isShip=%s]", WAREHOUSE.version, warehouse:GetName(), tostring(self:IsUnit()), tostring(self:IsShip()))) -- Set some variables. self.warehouse=warehouse @@ -3324,7 +3333,7 @@ end --- Check if runway is operational. -- @param #WAREHOUSE self --- @return #boolean If true, runway is operational. +-- @return #boolean If `true`, runway is operational. function WAREHOUSE:IsRunwayOperational() if self.airbase then if self.runwaydestroyed then @@ -3360,6 +3369,27 @@ function WAREHOUSE:GetRunwayRepairtime() return 0 end +--- Check if warehouse physical representation is a unit (not a static) object. +-- @param #WAREHOUSE self +-- @return #boolean If `true`, warehouse object is a unit. +function WAREHOUSE:IsUnit() + return self.isUnit +end + +--- Check if warehouse physical representation is a static (not a unit) object. +-- @param #WAREHOUSE self +-- @return #boolean If `true`, warehouse object is a static. +function WAREHOUSE:IsStatic() + return not self.isUnit +end + +--- Check if warehouse physical representation is a ship. +-- @param #WAREHOUSE self +-- @return #boolean If `true`, warehouse object is a ship. +function WAREHOUSE:IsShip() + return self.isShip +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- FSM states ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -4399,7 +4429,7 @@ function WAREHOUSE:onbeforeRequest(From, Event, To, Request) -- Delete request from queue because it will never be possible. -- Unless(!) at least one is a moving warehouse, which could, e.g., be an aircraft carrier. - if not (self.isunit or Request.warehouse.isunit) then + if not (self.isUnit or Request.warehouse.isUnit) then self:_DeleteQueueItem(Request, self.queue) end @@ -5828,8 +5858,6 @@ function WAREHOUSE:_SpawnAssetGroundNaval(alias, asset, request, spawnzone, late -- Late activation. template.lateActivation=lateactivated - - --env.info("FF lateActivation="..tostring(template.lateActivation)) template.route.points[1].x = coord.x template.route.points[1].y = coord.z @@ -6108,18 +6136,10 @@ function WAREHOUSE:_RouteGround(group, request) end for n,wp in ipairs(Waypoints) do - --env.info(n) local tf=self:_SimpleTaskFunctionWP("warehouse:_PassingWaypoint",group, n, #Waypoints) group:SetTaskWaypoint(wp, tf) end - -- Task function triggering the arrived event at the last waypoint. - --local TaskFunction = self:_SimpleTaskFunction("warehouse:_Arrived", group) - - -- Put task function on last waypoint. - --local Waypoint = Waypoints[#Waypoints] - --group:SetTaskWaypoint(Waypoint, TaskFunction) - -- Route group to destination. group:Route(Waypoints, 1) @@ -7676,7 +7696,7 @@ function WAREHOUSE:_SimpleTaskFunction(Function, group) local DCSScript = {} DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...". - if self.isunit then + if self.isUnit then DCSScript[#DCSScript+1] = string.format("local mywarehouse = UNIT:FindByName(\"%s\") ", warehouse) -- The unit that holds the warehouse self object. else DCSScript[#DCSScript+1] = string.format("local mywarehouse = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object. @@ -7707,7 +7727,7 @@ function WAREHOUSE:_SimpleTaskFunctionWP(Function, group, n, N) local DCSScript = {} DCSScript[#DCSScript+1] = string.format('local mygroup = GROUP:FindByName(\"%s\") ', groupname) -- The group that executes the task function. Very handy with the "...". - if self.isunit then + if self.isUnit then DCSScript[#DCSScript+1] = string.format("local mywarehouse = UNIT:FindByName(\"%s\") ", warehouse) -- The unit that holds the warehouse self object. else DCSScript[#DCSScript+1] = string.format("local mywarehouse = STATIC:FindByName(\"%s\") ", warehouse) -- The static that holds the warehouse self object. diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index f4a7bf747..a2024e1c2 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -147,9 +147,10 @@ AIRWING.version="0.9.0" -- ToDo list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Spawn in air or hot ==> Needs WAREHOUSE update. +-- TODO: Spawn in air ==> Needs WAREHOUSE update. +-- DONE: Spawn in air. -- TODO: Make special request to transfer squadrons to anther airwing (or warehouse). --- TODO: Check that airbase has enough parking spots if a request is BIG. Alternatively, split requests. +-- TODO: Check that airbase has enough parking spots if a request is BIG. -- DONE: Add squadrons to warehouse. -- DONE: Build mission queue. -- DONE: Find way to start missions. diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index a08bd3a43..eb0d99cb8 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -55,10 +55,13 @@ ARMYGROUP = { engage = {}, } ---- Target +--- Engage Target. -- @type ARMYGROUP.Target -- @field Ops.Target#TARGET Target The target. -- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target. +-- @field Ops.OpsGroup#OPSGROUP.Waypoint Waypoint the waypoint created to go to the target. +-- @field #number roe ROE backup. +-- @field #number alarmstate Alarm state backup. --- Army Group version. -- @field #string version @@ -332,7 +335,11 @@ end function ARMYGROUP:IsCombatReady() local combatready=true - if self:IsRearming() or self:IsRetreating() or self.outofAmmo or self:IsEngaging() or self:is("Retreated") or self:IsDead() or self:IsStopped() or self:IsInUtero() then + if self:IsRearming() or self:IsRetreating() or self:IsOutOfAmmo() or self:IsEngaging() or self:IsDead() or self:IsStopped() or self:IsInUtero() then + combatready=false + end + + if self:IsPickingup() or self:IsLoading() or self:IsTransporting() or self:IsLoaded() or self:IsCargo() or self:IsCarrier() then combatready=false end @@ -356,26 +363,20 @@ function ARMYGROUP:Status() if alive then - --- - -- Detection - --- + -- Update position etc. + self:_UpdatePosition() -- Check if group has detected any units. - if self.detectionOn then - self:_CheckDetectedUnits() - end + self:_CheckDetectedUnits() -- Check ammo status. self:_CheckAmmoStatus() - - -- Update position etc. - self:_UpdatePosition() - - -- Check if group got stuck. - self:_CheckStuck() - + -- Check damage of elements and group. self:_CheckDamage() + + -- Check if group got stuck. + self:_CheckStuck() -- Update engagement. if self:IsEngaging() then @@ -462,6 +463,21 @@ function ARMYGROUP:Status() end self:I(self.lid..text) end + + --- + -- Engage Detected Targets + --- + if self:IsCruising() and self.detectionOn and self.engagedetectedOn then + + local targetgroup, targetdist=self:_GetDetectedTarget() + + -- If we found a group, we engage it. + if targetgroup then + self:I(self.lid..string.format("Engaging target group %s at distance %d meters", targetgroup:GetName(), targetdist)) + self:EngageTarget(targetgroup) + end + + end --- @@ -580,7 +596,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To) -- Update route. if #self.waypoints>1 then - self:Cruise(nil, self.option.Formation) + self:__Cruise(-0.1, nil, self.option.Formation) else self:FullStop() end @@ -1003,11 +1019,13 @@ function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) -- Target coordinate. self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) - -- TODO: Backup current ROE and alarm state and reset after disengage. + -- Backup ROE and alarm state. + self.engage.roe=self:GetROE() + self.engage.alarmstate=self:GetAlarmstate() -- Switch ROE and alarm state. self:SwitchAlarmstate(ENUMS.AlarmState.Auto) - self:SwitchROE(ENUMS.ROE.WeaponFree) + self:SwitchROE(ENUMS.ROE.OpenFire) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid @@ -1025,17 +1043,19 @@ end function ARMYGROUP:_UpdateEngageTarget() if self.engage.Target and self.engage.Target:IsAlive() then - - --env.info("FF Update Engage Target "..self.engage.Target:GetName()) - local vec3=self.engage.Target:GetCoordinate():GetVec3() + -- Get current position vector. + local vec3=self.engage.Target:GetVec3() - local dist=UTILS.VecDist2D(vec3, self.engage.Coordinate:GetVec3()) + -- Distance to last known position of target. + local dist=UTILS.VecDist3D(vec3, self.engage.Coordinate:GetVec3()) + -- Check if target moved more than 100 meters. if dist>100 then --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) + -- Update new position. self.engage.Coordinate:UpdateFromVec3(vec3) -- ID of current waypoint. @@ -1053,7 +1073,10 @@ function ARMYGROUP:_UpdateEngageTarget() end else + + -- Target not alive any more == Disengage. self:Disengage() + end end @@ -1066,8 +1089,17 @@ end function ARMYGROUP:onafterDisengage(From, Event, To) self:T(self.lid.."Disengage Target") - -- TODO: Reset ROE and alarm state. - self:_CheckGroupDone(1) + -- Restore previous ROE and alarm state. + self:SwitchROE(self.engage.roe) + self:SwitchAlarmstate(self.engage.alarmstate) + + -- Remove current waypoint + if self.engage.Waypoint then + self:RemoveWaypointByID(self.engage.Waypoint.uid) + end + + -- Check group is done + self:_CheckGroupDone(1) end --- On after "Rearmed" event. diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 853b0e9a2..7483f7435 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -152,16 +152,21 @@ -- -- === -- --- ![Banner Image](..\Presentations\OPS\Auftrag\_Main.png) --- -- # The AUFTRAG Concept -- -- The AUFTRAG class significantly simplifies the workflow of using DCS tasks. -- -- You can think of an AUFTRAG as document, which contains the mission briefing, i.e. information about the target location, mission altitude, speed and various other parameters. -- This document can be handed over directly to a pilot (or multiple pilots) via the @{Ops.FlightGroup#FLIGHTGROUP} class. The pilots will then execute the mission. --- The AUFTRAG document can also be given to an AIRWING. The airwing will then determine the best assets (pilots and payloads) available for the job. --- One more up the food chain, an AUFTRAG can be passed to a WINGCOMMANDER. The wing commander will find the best AIRWING and pass the job over to it. +-- +-- The AUFTRAG document can also be given to an AIRWING. The airwing will then determine the best assets (pilots and payloads) available for the job. +-- +-- Similarly, an AUFTRAG can be given to ground or navel groups via the @{Ops.ArmyGroup#ARMYGROUP} or @{Ops.NavyGroup#NAVYGROUP} classes, respectively. These classes have also +-- AIRWING analouges, which are called BRIGADE and FLEET. Brigades and fleets will likewise select the best assets they have available and pass on the AUFTRAG to them. +-- +-- +-- One more up the food chain, an AUFTRAG can be passed to a COMMANDER. The commander will recruit the best assets of AIRWINGs, BRIGADEs and/or FLEETs and pass the job over to it. +-- -- -- # Airborne Missions -- @@ -169,7 +174,7 @@ -- -- ## Anti-Ship -- --- An anti-ship mission can be created with the @{#AUFTRAG.NewANTISHIP}(*Target, Altitude*) function. +-- An anti-ship mission can be created with the @{#AUFTRAG.NewANTISHIP}() function. -- -- ## AWACS -- @@ -225,7 +230,7 @@ -- -- ## RECON -- --- Not implemented yet. +-- An reconnaissance mission can be created with the @{#AUFTRAG.NewRECON}() function. -- -- ## RESCUE HELO -- @@ -260,40 +265,43 @@ -- -- # Assigning Missions -- --- An AUFTRAG can be assigned to groups, airwings or wingcommanders +-- An AUFTRAG can be assigned to groups (FLIGHTGROUP, ARMYGROUP, NAVYGROUP), legions (AIRWING, BRIGADE, FLEET) or to a COMMANDER. -- -- ## Group Level -- -- ### Flight Group -- --- Assigning an AUFTRAG to a flight groups is done via the @{Ops.FlightGroup#FLIGHTGROUP.AddMission} function. See FLIGHTGROUP docs for details. +-- Assigning an AUFTRAG to a flight group is done via the @{Ops.FlightGroup#FLIGHTGROUP.AddMission} function. See FLIGHTGROUP docs for details. +-- +-- ### Army Group +-- +-- Assigning an AUFTRAG to an army group is done via the @{Ops.ArmyGroup#ARMYGROUP.AddMission} function. See ARMYGROUP docs for details. -- -- ### Navy Group -- --- Assigning an AUFTRAG to a navy groups is done via the @{Ops.NavyGroup#NAVYGROUP.AddMission} function. See NAVYGROUP docs for details. +-- Assigning an AUFTRAG to a navy group is done via the @{Ops.NavyGroup#NAVYGROUP.AddMission} function. See NAVYGROUP docs for details. -- -- ## Legion Level -- -- Adding an AUFTRAG to an airwing is done via the @{Ops.AirWing#AIRWING.AddMission} function. See AIRWING docs for further details. --- Similarly, an AUFTRAG can be added to a brigade via the @{Ops.Brigade#BRIGADE.AddMission} function +-- Similarly, an AUFTRAG can be added to a brigade via the @{Ops.Brigade#BRIGADE.AddMission} function. -- -- ## Commander Level -- --- Assigning an AUFTRAG to acommander is done via the @{Ops.Commander#COMMANDER.AddMission} function. See COMMADER docs for details. +-- Assigning an AUFTRAG to acommander is done via the @{Ops.Commander#COMMANDER.AddMission} function. See COMMANDER docs for details. -- --- ## Chief Level --- --- Assigning an AUFTRAG to a wing commander is done via the @{Ops.Chief#CHIEF.AddMission} function. See CHIEF docs for details. -- --- --- -- # Events -- --- The AUFTRAG class creates many useful (FSM) events, which can be used in the mission designers script. +-- The AUFTRAG class creates many useful (FSM) events, which can be used in the mission designers script. +-- +-- TODO -- -- -- # Examples -- +-- TODO +-- -- -- @field #AUFTRAG AUFTRAG = { @@ -599,8 +607,8 @@ function AUFTRAG:New(Type) self:SetStartState(self.status) -- PLANNED --> (QUEUED) --> (REQUESTED) --> SCHEDULED --> STARTED --> EXECUTING --> DONE - self:AddTransition("*", "Planned", AUFTRAG.Status.PLANNED) -- Mission is in planning stage. - self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. + self:AddTransition("*", "Planned", AUFTRAG.Status.PLANNED) -- Mission is in planning stage. Could be in the queue of a COMMANDER or CHIEF. + self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of a LEGION. self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. @@ -624,6 +632,171 @@ function AUFTRAG:New(Type) self:AddTransition("*", "ElementDestroyed", "*") self:AddTransition("*", "GroupDead", "*") self:AddTransition("*", "AssetDead", "*") + + ------------------------ + --- Pseudo Functions --- + ------------------------ + + --- Triggers the FSM event "Status". + -- @function [parent=#AUFTRAG] Status + -- @param #AUFTRAG self + + --- Triggers the FSM event "Status" after a delay. + -- @function [parent=#AUFTRAG] __Status + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Stop". + -- @function [parent=#AUFTRAG] Stop + -- @param #AUFTRAG self + + --- Triggers the FSM event "Stop" after a delay. + -- @function [parent=#AUFTRAG] __Stop + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Planned". + -- @function [parent=#AUFTRAG] Planned + -- @param #AUFTRAG self + + --- Triggers the FSM event "Planned" after a delay. + -- @function [parent=#AUFTRAG] __Planned + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Queued". + -- @function [parent=#AUFTRAG] Queued + -- @param #AUFTRAG self + + --- Triggers the FSM event "Queued" after a delay. + -- @function [parent=#AUFTRAG] __Queued + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Requested". + -- @function [parent=#AUFTRAG] Requested + -- @param #AUFTRAG self + + --- Triggers the FSM event "Requested" after a delay. + -- @function [parent=#AUFTRAG] __Requested + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Scheduled". + -- @function [parent=#AUFTRAG] Scheduled + -- @param #AUFTRAG self + + --- Triggers the FSM event "Scheduled" after a delay. + -- @function [parent=#AUFTRAG] __Scheduled + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Started". + -- @function [parent=#AUFTRAG] Started + -- @param #AUFTRAG self + + --- Triggers the FSM event "Started" after a delay. + -- @function [parent=#AUFTRAG] __Started + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Executing". + -- @function [parent=#AUFTRAG] Executing + -- @param #AUFTRAG self + + --- Triggers the FSM event "Executing" after a delay. + -- @function [parent=#AUFTRAG] __Executing + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + + --- Triggers the FSM event "Cancel". + -- @function [parent=#AUFTRAG] Cancel + -- @param #AUFTRAG self + + --- Triggers the FSM event "Cancel" after a delay. + -- @function [parent=#AUFTRAG] __Cancel + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Cancel" event. + -- @function [parent=#AUFTRAG] OnAfterCancel + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Done". + -- @function [parent=#AUFTRAG] Done + -- @param #AUFTRAG self + + --- Triggers the FSM event "Done" after a delay. + -- @function [parent=#AUFTRAG] __Done + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Done" event. + -- @function [parent=#AUFTRAG] OnAfterDone + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + + --- Triggers the FSM event "Success". + -- @function [parent=#AUFTRAG] Success + -- @param #AUFTRAG self + + --- Triggers the FSM event "Success" after a delay. + -- @function [parent=#AUFTRAG] __Success + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Success" event. + -- @function [parent=#AUFTRAG] OnAfterSuccess + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- Triggers the FSM event "Failure". + -- @function [parent=#AUFTRAG] Failure + -- @param #AUFTRAG self + + --- Triggers the FSM event "Failure" after a delay. + -- @function [parent=#AUFTRAG] __Failure + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Failure" event. + -- @function [parent=#AUFTRAG] OnAfterFailure + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + + --- Triggers the FSM event "Repeat". + -- @function [parent=#AUFTRAG] Repeat + -- @param #AUFTRAG self + + --- Triggers the FSM event "Repeat" after a delay. + -- @function [parent=#AUFTRAG] __Repeat + -- @param #AUFTRAG self + -- @param #number delay Delay in seconds. + + --- On after "Repeat" event. + -- @function [parent=#AUFTRAG] OnAfterRepeat + -- @param #AUFTRAG self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. -- Init status update. self:__Status(-1) @@ -1921,6 +2094,44 @@ function AUFTRAG:SetEngageAltitude(Altitude) return self end +--- Enable to automatically engage detected targets. +-- @param #AUFTRAG self +-- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. +-- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. +-- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. +-- @return #AUFTRAG self +function AUFTRAG:SetEngageDetected(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) + + -- Ensure table. + if TargetTypes then + if type(TargetTypes)~="table" then + TargetTypes={TargetTypes} + end + else + TargetTypes={"All"} + end + + -- Ensure SET_ZONE if ZONE is provided. + if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) + EngageZoneSet=zoneset + end + if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) + NoEngageZoneSet=zoneset + end + + -- Set parameters. + self.engagedetectedOn=true + self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) + self.engagedetectedTypes=TargetTypes + self.engagedetectedEngageZones=EngageZoneSet + self.engagedetectedNoEngageZones=NoEngageZoneSet + + return self +end + --- Set mission altitude. This is the altitude of the waypoint create where the DCS task is executed. -- @param #AUFTRAG self -- @param #string Altitude Altitude in feet. @@ -4039,8 +4250,9 @@ end -- @param #AUFTRAG self -- @param Wrapper.Group#GROUP group Group. -- @param #number randomradius Random radius in meters. +-- @param #table surfacetypes Surface types of random zone. -- @return Core.Point#COORDINATE Coordinate where the mission is executed. -function AUFTRAG:GetMissionWaypointCoord(group, randomradius) +function AUFTRAG:GetMissionWaypointCoord(group, randomradius, surfacetypes) -- Check if a coord has been explicitly set. if self.missionWaypointCoord then @@ -4052,12 +4264,12 @@ function AUFTRAG:GetMissionWaypointCoord(group, randomradius) end -- Create waypoint coordinate half way between us and the target. - local waypointcoord=group:GetCoordinate():GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) + local waypointcoord=group:GetCoordinate():GetIntermediateCoordinate(self:GetTargetCoordinate(), self.missionFraction) local alt=waypointcoord.y -- Add some randomization. if randomradius then - waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate():SetAltitude(alt, false) + waypointcoord=ZONE_RADIUS:New("Temp", waypointcoord:GetVec2(), randomradius):GetRandomCoordinate(nil, nil, surfacetypes):SetAltitude(alt, false) end -- Set altitude of mission waypoint. diff --git a/Moose Development/Moose/Ops/Chief.lua b/Moose Development/Moose/Ops/Chief.lua index 82ed4cdac..e19263e13 100644 --- a/Moose Development/Moose/Ops/Chief.lua +++ b/Moose Development/Moose/Ops/Chief.lua @@ -413,6 +413,47 @@ function CHIEF:GetCommander() return self.commander end + +--- Add an AIRWING to the chief's commander. +-- @param #CHIEF self +-- @param Ops.AirWing#AIRWING Airwing The airwing to add. +-- @return #CHIEF self +function CHIEF:AddAirwing(Airwing) + + -- Add airwing to the commander. + self:AddLegion(Airwing) + + return self +end + +--- Add a BRIGADE to the chief's commander. +-- @param #CHIEF self +-- @param Ops.Brigade#BRIGADE Brigade The brigade to add. +-- @return #CHIEF self +function CHIEF:AddBrigade(Brigade) + + -- Add brigade to the commander + self:AddLegion(Brigade) + + return self +end + +--- Add a LEGION to the chief's commander. +-- @param #CHIEF self +-- @param Ops.Legion#LEGION Legion The legion to add. +-- @return #CHIEF self +function CHIEF:AddLegion(Legion) + + -- Set chief of the legion. + Legion.chief=self + + -- Add legion to the commander. + self.commander:AddLegion(Legion) + + return self +end + + --- Add mission to mission queue of the COMMANDER. -- @param #CHIEF self -- @param Ops.Auftrag#AUFTRAG Mission Mission to be added. @@ -1181,10 +1222,8 @@ function CHIEF:CheckOpsZoneQueue() env.info(string.format("Zone %s is owned by coalition %d", opszone.zone:GetName(), ownercoalition)) -- Recruit ground assets that - local recruited, assets, legions=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.PATROLZONE, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) - - - + local recruited=self:RecruitAssetsForZone(opszone, AUFTRAG.Type.PATROLZONE, 1, 3, {Group.Category.GROUND}, {GROUP.Attribute.GROUND_INFANTRY}) + end end @@ -1494,8 +1533,6 @@ end -- @param #table Categories Group categories of the assets. -- @param #table Attributes Generalized group attributes. -- @return #boolean If `true` enough assets could be recruited. --- @return #table Assets that have been recruited from all legions. --- @return #table Legions that have recruited assets. function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax, Categories, Attributes) -- Cohorts. @@ -1520,7 +1557,7 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Target position. local TargetVec2=OpsZone.zone:GetVec2() - -- Recruite assets. + -- Recruite infantry assets. local recruitedInf, assetsInf, legionsInf=LEGION.RecruitCohortAssets(Cohorts, MissionType, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, nil, Categories, Attributes) if recruitedInf then @@ -1536,9 +1573,9 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax end end - -- Recruite assets. + -- Recruite carrier assets. This need to be ground or helicopters to deploy at a zone. local recruitedTrans, assetsTrans, legionsTrans= - LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, NassetsMin, NassetsMax, TargetVec2, nil, nil, nil, weightMax, {Group.Category.HELICOPTER, Group.Category.GROUND}) + LEGION.RecruitCohortAssets(Cohorts, AUFTRAG.Type.OPSTRANSPORT, nil, 1, 1, TargetVec2, nil, nil, nil, weightMax, {Group.Category.HELICOPTER, Group.Category.GROUND}) local transport=nil --Ops.OpsTransport#OPSTRANSPORT if recruitedTrans then @@ -1550,7 +1587,15 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Add cargo assets to transport. for _,_legion in pairs(legionsInf) do local legion=_legion --Ops.Legion#LEGION - local tpz=transport:AddTransportZoneCombo(legion.spawnzone, OpsZone.zone) + + -- Pickup at spawnzone or at airbase if the legion warehouse has one. + local pickupzone=legion.spawnzone + if legion.airbase and legion:IsRunwayOperational() then + pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) + end + + local tpz=transport:AddTransportZoneCombo(pickupzone, OpsZone.zone) + for _,_asset in pairs(assetsInf) do local asset=_asset --Functional.Warehouse#WAREHOUSE.Assetitem if asset.legion.alias==legion.alias then @@ -1579,6 +1624,7 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax -- Create Patrol zone mission. local mission=AUFTRAG:NewPATROLZONE(OpsZone.zone) + mission:SetEngageDetected() for _,asset in pairs(assetsInf) do mission:AddAsset(asset) @@ -1593,11 +1639,13 @@ function CHIEF:RecruitAssetsForZone(OpsZone, MissionType, NassetsMin, NassetsMax OpsZone.missionPatrol=mission + return true else LEGION.UnRecruitAssets(assetsInf) + return false end - return recruited, assets, legions + return nil end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Ops/Commander.lua b/Moose Development/Moose/Ops/Commander.lua index 4856e1a57..e73e9381d 100644 --- a/Moose Development/Moose/Ops/Commander.lua +++ b/Moose Development/Moose/Ops/Commander.lua @@ -20,7 +20,7 @@ -- @field #table legions Table of legions which are commanded. -- @field #table missionqueue Mission queue. -- @field #table transportqueue Transport queue. --- @field Ops.ChiefOfStaff#CHIEF chief Chief of staff. +-- @field Ops.Chief#CHIEF chief Chief of staff. -- @extends Core.Fsm#FSM --- Be surprised! @@ -736,6 +736,7 @@ function COMMANDER:CheckMissionQueue() for _,_legion in pairs(legions) do local legion=_legion --Ops.Legion#LEGION + -- Set pickup zone to spawn zone or airbase if the legion has one that is operational. local pickupzone=legion.spawnzone if legion.airbase and legion:IsRunwayOperational() then pickupzone=ZONE_AIRBASE:New(legion.airbasename, 4000) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index f9dc40ba8..3ab6c3257 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -501,55 +501,6 @@ function FLIGHTGROUP:SetFuelCriticalRTB(switch) return self end ---- Enable to automatically engage detected targets. --- @param #FLIGHTGROUP self --- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. --- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". --- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. --- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) - - -- Ensure table. - if TargetTypes then - if type(TargetTypes)~="table" then - TargetTypes={TargetTypes} - end - else - TargetTypes={"All"} - end - - -- Ensure SET_ZONE if ZONE is provided. - if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then - local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) - EngageZoneSet=zoneset - end - if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then - local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) - NoEngageZoneSet=zoneset - end - - -- Set parameters. - self.engagedetectedOn=true - self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) - self.engagedetectedTypes=TargetTypes - self.engagedetectedEngageZones=EngageZoneSet - self.engagedetectedNoEngageZones=NoEngageZoneSet - - -- Ensure detection is ON or it does not make any sense. - self:SetDetection(true) - - return self -end - ---- Disable to automatically engage detected targets. --- @param #FLIGHTGROUP self --- @return #FLIGHTGROUP self -function FLIGHTGROUP:SetEngageDetectedOff() - self.engagedetectedOn=false - return self -end - --- Enable that the group is despawned after landing. This can be useful to avoid DCS taxi issues with other AI or players or jamming taxiways. -- @param #FLIGHTGROUP self @@ -841,23 +792,28 @@ function FLIGHTGROUP:Status() -- FSM state. local fsmstate=self:GetState() + + -- Is group alive? + local alive=self:IsAlive() -- Update position. self:_UpdatePosition() - --- - -- Detection - --- - -- Check if group has detected any units. - if self.detectionOn then - self:_CheckDetectedUnits() - end - + self:_CheckDetectedUnits() + + -- Check ammo status. + self:_CheckAmmoStatus() + + -- Check damage. + self:_CheckDamage() + --- -- Parking --- + -- TODO: _CheckParking() function + -- Check if flight began to taxi (if it was parking). if self:IsParking() then for _,_element in pairs(self.elements) do @@ -930,11 +886,6 @@ function FLIGHTGROUP:Status() local lp0=unit:GetLife0() local parking=element.parking and tostring(element.parking.TerminalID) or "X" - -- Check if element is not dead and we missed an event. - --if life<=0 and element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then - -- self:ElementDead(element) - --end - -- Get ammo. local ammo=self:GetAmmoElement(element) @@ -952,7 +903,9 @@ function FLIGHTGROUP:Status() -- Distance travelled --- - if self.verbose>=4 and self:IsAlive() then + if self.verbose>=4 and alive then + + -- TODO: _Check distance travelled. -- Travelled distance since last check. local ds=self.travelds @@ -998,18 +951,14 @@ function FLIGHTGROUP:Status() end - --- - -- Tasks & Missions - --- - - self:_PrintTaskAndMissionStatus() - --- -- Fuel State --- + -- TODO: _CheckFuelState() function. + -- Only if group is in air. - if self:IsAlive() and self.group:IsAirborne(true) then + if alive and self.group:IsAirborne(true) then local fuelmin=self:GetFuelMin() @@ -1051,77 +1000,7 @@ function FLIGHTGROUP:Status() --- if self:IsAirborne() and self:IsFuelGood() and self.detectionOn and self.engagedetectedOn then - -- Target. - local targetgroup=nil --Wrapper.Group#GROUP - local targetdist=math.huge - - -- Loop over detected groups. - for _,_group in pairs(self.detectedgroups:GetSet()) do - local group=_group --Wrapper.Group#GROUP - - if group and group:IsAlive() then - - -- Get 3D vector of target. - local targetVec3=group:GetVec3() - - -- Distance to target. - local distance=UTILS.VecDist3D(self.position, targetVec3) - - if distance<=self.engagedetectedRmax and distance=1 then @@ -702,7 +699,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To) -- Update route. if #self.waypoints>1 then - self:Cruise() + self:__Cruise(-0.1) else self:FullStop() end diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index 7c8f64225..229a1a1b6 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -85,6 +85,12 @@ -- -- @field Core.Astar#ASTAR Astar path finding. -- @field #boolean ispathfinding If true, group is on pathfinding route. +-- +-- @field #boolean engagedetectedOn If `true`, auto engage detected targets. +-- @field #number engagedetectedRmax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. +-- @field #table engagedetectedTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @field Core.Set#SET_ZONE engagedetectedEngageZones Set of zones in which targets are engaged. Default is anywhere. +-- @field Core.Set#SET_ZONE engagedetectedNoEngageZones Set of zones in which targets are *not* engaged. Default is nowhere. -- -- @field #OPSGROUP.Radio radio Current radio settings. -- @field #OPSGROUP.Radio radioDefault Default radio settings. @@ -607,8 +613,6 @@ function OPSGROUP:New(group) self:AddTransition("*", "InUtero", "InUtero") -- Deactivated group goes back to mummy. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. - --self:AddTransition("*", "Status", "*") -- Status update. - self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. @@ -929,6 +933,7 @@ end -- @param #boolean Switch If `true`, detection is on. If `false` or `nil`, detection is off. Default is off. -- @return #OPSGROUP self function OPSGROUP:SetDetection(Switch) + self:T(self.lid..string.format("Detection is %s", tostring(Switch))) self.detectionOn=Switch return self end @@ -1112,6 +1117,59 @@ function OPSGROUP:GetHighestThreat() return threat, levelmax end +--- Enable to automatically engage detected targets. +-- @param #OPSGROUP self +-- @param #number RangeMax Max range in NM. Only detected targets within this radius from the group will be engaged. Default is 25 NM. +-- @param #table TargetTypes Types of target attributes that will be engaged. See [DCS enum attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes). Default "All". +-- @param Core.Set#SET_ZONE EngageZoneSet Set of zones in which targets are engaged. Default is anywhere. +-- @param Core.Set#SET_ZONE NoEngageZoneSet Set of zones in which targets are *not* engaged. Default is nowhere. +-- @return #OPSGROUP self +function OPSGROUP:SetEngageDetectedOn(RangeMax, TargetTypes, EngageZoneSet, NoEngageZoneSet) + + -- Ensure table. + if TargetTypes then + if type(TargetTypes)~="table" then + TargetTypes={TargetTypes} + end + else + TargetTypes={"All"} + end + + -- Ensure SET_ZONE if ZONE is provided. + if EngageZoneSet and EngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(EngageZoneSet) + EngageZoneSet=zoneset + end + if NoEngageZoneSet and NoEngageZoneSet:IsInstanceOf("ZONE_BASE") then + local zoneset=SET_ZONE:New():AddZone(NoEngageZoneSet) + NoEngageZoneSet=zoneset + end + + -- Set parameters. + self.engagedetectedOn=true + self.engagedetectedRmax=UTILS.NMToMeters(RangeMax or 25) + self.engagedetectedTypes=TargetTypes + self.engagedetectedEngageZones=EngageZoneSet + self.engagedetectedNoEngageZones=NoEngageZoneSet + + -- Debug info. + self:T(self.lid..string.format("Engage detected ON: Rmax=%d NM", UTILS.MetersToNM(self.engagedetectedRmax))) + + -- Ensure detection is ON or it does not make any sense. + self:SetDetection(true) + + return self +end + +--- Disable to automatically engage detected targets. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:SetEngageDetectedOff() + self:T(self.lid..string.format("Engage detected OFF")) + self.engagedetectedOn=false + return self +end + --- Check if an element of the group has line of sight to a coordinate. -- @param #OPSGROUP self -- @param Core.Point#COORDINATE Coordinate The position to which we check the LoS. @@ -2042,7 +2100,7 @@ function OPSGROUP:HasPassedFinalWaypoint() return self.passedfinalwp end ---- Check if the group is currently rearming. +--- Check if the group is currently rearming or on its way to the rearming place. -- @param #OPSGROUP self -- @return #boolean If true, group is rearming. function OPSGROUP:IsRearming() @@ -2050,6 +2108,42 @@ function OPSGROUP:IsRearming() return rearming end +--- Check if the group is completely out of ammo. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out-of-ammo. +function OPSGROUP:IsOutOfAmmo() + return self.outofAmmo +end + +--- Check if the group is out of bombs. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of bombs. +function OPSGROUP:IsOutOfBombs() + return self.outofBombs +end + +--- Check if the group is out of guns. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of guns. +function OPSGROUP:IsOutOfGuns() + return self.outofGuns +end + +--- Check if the group is out of missiles. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of missiles. +function OPSGROUP:IsOutOfMissiles() + return self.outofMissiles +end + +--- Check if the group is out of torpedos. +-- @param #OPSGROUP self +-- @return #boolean If `true`, group is out of torpedos. +function OPSGROUP:IsOutOfTorpedos() + return self.outofTorpedos +end + + --- Check if the group has currently switched a LASER on. -- @param #OPSGROUP self -- @return #boolean If true, LASER of the group is on. @@ -2057,14 +2151,23 @@ function OPSGROUP:IsLasing() return self.spot.On end ---- Check if the group is currently retreating. +--- Check if the group is currently retreating or retreated. -- @param #OPSGROUP self --- @return #boolean If true, group is retreating. +-- @return #boolean If true, group is retreating or retreated. function OPSGROUP:IsRetreating() - local is=self:is("Retreating") + local is=self:is("Retreating") or self:is("Retreated") return is end +--- Check if the group is retreated (has reached its retreat zone). +-- @param #OPSGROUP self +-- @return #boolean If true, group is retreated. +function OPSGROUP:IsRetreated() + local is=self:is("Retreated") + return is +end + + --- Check if the group is currently returning to a zone. -- @param #OPSGROUP self -- @return #boolean If true, group is returning. @@ -2812,15 +2915,6 @@ function OPSGROUP:SetTask(DCSTask) if self:IsAlive() then - if self.taskcurrent>0 then - - -- TODO: Why the hell did I do this? It breaks scheduled tasks. I comment it out for now to see where it fails. - --local task=self:GetTaskCurrent() - --self:RemoveTask(task) - --self.taskcurrent=0 - - end - -- Inject enroute tasks. if self.taskenroute and #self.taskenroute>0 then if tostring(DCSTask.id)=="ComboTask" then @@ -2836,7 +2930,6 @@ function OPSGROUP:SetTask(DCSTask) end -- Set task. - --self.group:SetTask(DCSTask) self.controller:setTask(DCSTask) -- Debug info. @@ -2861,7 +2954,6 @@ function OPSGROUP:PushTask(DCSTask) if self:IsAlive() then -- Push task. - --self.group:PushTask(DCSTask) self.controller:pushTask(DCSTask) -- Debug info. @@ -3315,7 +3407,6 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Insert into task queue. Not sure any more, why I added this. But probably if a task is just executed without having been put into the queue. if self:GetTaskCurrent()==nil then - --env.info("FF adding current task to queue") table.insert(self.taskqueue, Task) end @@ -3355,8 +3446,15 @@ function OPSGROUP:onafterTaskExecute(From, Event, To, Task) -- Parameters. local zone=Task.dcstask.params.zone --Core.Zone#ZONE + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND, land.SurfaceType.ROAD} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} + end + -- Random coordinate in zone. - local Coordinate=zone:GetRandomCoordinate() + local Coordinate=zone:GetRandomCoordinate(nil, nil, surfacetypes) --Coordinate:MarkToAll("Random Patrol Zone Coordinate") @@ -3968,6 +4066,11 @@ function OPSGROUP:onafterMissionExecute(From, Event, To, Mission) -- Set mission status to EXECUTING. Mission:Executing() + + -- Set auto engage detected targets. + if Mission.engagedetectedOn then + self:SetEngageDetectedOn(UTILS.MetersToNM(Mission.engagedetectedRmax), Mission.engagedetectedTypes, Mission.engagedetectedEngageZones, Mission.engagedetectedNoEngageZones) + end end @@ -4124,6 +4227,11 @@ function OPSGROUP:onafterMissionDone(From, Event, To, Mission) Mission.patroldata.noccupied=Mission.patroldata.noccupied-1 AIRWING.UpdatePatrolPointMarker(Mission.patroldata) end + + -- Switch auto engage detected off. This IGNORES that engage detected had been activated for the group! + if Mission.engagedetectedOn then + self:SetEngageDetectedOff() + end -- ROE to default. if Mission.optionROE then @@ -4201,17 +4309,20 @@ function OPSGROUP:RouteToMission(mission, delay) -- Catch dead or stopped groups. if self:IsDead() or self:IsStopped() then + self:T(self.lid..string.format("Route To Mission: I am DEAD or STOPPED! Ooops...")) return end -- OPSTRANSPORT: Just add the ops transport to the queue. if mission.type==AUFTRAG.Type.OPSTRANSPORT then + self:T(self.lid..string.format("Route To Mission: I am OPSTRANSPORT! Add transport and return...")) self:AddOpsTransport(mission.opstransport) return end - -- ALERT 5: Just set the mission to executing. + -- ALERT5: Just set the mission to executing. if mission.type==AUFTRAG.Type.ALERT5 then + self:T(self.lid..string.format("Route To Mission: I am ALERT5! Go right to MissionExecute()...")) self:MissionExecute(mission) return end @@ -4219,15 +4330,28 @@ function OPSGROUP:RouteToMission(mission, delay) -- ID of current waypoint. local uid=self:GetWaypointCurrent().uid - -- Random radius. + -- Ingress waypoint coordinate where the mission is executed. + local waypointcoord=nil --Core.Point#COORDINATE + + -- Random radius of 1000 meters. local randomradius=1000 + + -- Surface types. + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND, land.SurfaceType.ROAD} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} + end + + -- Get ingress waypoint. if mission.type==AUFTRAG.Type.PATROLZONE then - randomradius=nil + local zone=mission.engageTarget:GetObject() --Core.Zone#ZONE + waypointcoord=zone:GetRandomCoordinate(nil , nil, surfacetypes) + else + waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius, surfacetypes) end - -- Get coordinate where the mission is executed. - local waypointcoord=mission:GetMissionWaypointCoord(self.group, randomradius) - -- Add enroute tasks. for _,task in pairs(mission.enrouteTasks) do self:AddTaskEnroute(task) @@ -4532,9 +4656,17 @@ function OPSGROUP:onafterPassingWaypoint(From, Event, To, Waypoint) -- Zone object. local zone=task.dcstask.params.zone --Core.Zone#ZONE - -- Random coordinate in zone. - local Coordinate=zone:GetRandomCoordinate() + -- Surface types. + local surfacetypes=nil + if self:IsArmygroup() then + surfacetypes={land.SurfaceType.LAND, land.SurfaceType.ROAD} + elseif self:IsNavygroup() then + surfacetypes={land.SurfaceType.WATER, land.SurfaceType.SHALLOW_WATER} + end + -- Random coordinate in zone. + local Coordinate=zone:GetRandomCoordinate(nil, nil, surfacetypes) + -- Speed and altitude. local Speed=UTILS.KmphToKnots(task.dcstask.params.speed or self.speedCruise) local Altitude=task.dcstask.params.altitude and UTILS.MetersToFeet(task.dcstask.params.altitude) or nil @@ -7820,7 +7952,7 @@ end -- @param #OPSGROUP self function OPSGROUP:_CheckDetectedUnits() - if self.group and not self:IsDead() then + if self.detectionOn and self.group and not self:IsDead() then -- Get detected DCS units. local detectedtargets=self.group:GetDetectedTargets() @@ -10636,6 +10768,87 @@ function OPSGROUP:ClearWaypoints(IndexMin, IndexMax) --self.waypoints={} end +--- Get target group. +-- @param #OPSGROUP self +-- @return Wrapper.Group#GROUP Detected target group. +-- @return #number Distance to target. +function OPSGROUP:_GetDetectedTarget() + + -- Target. + local targetgroup=nil --Wrapper.Group#GROUP + local targetdist=math.huge + + -- Loop over detected groups. + for _,_group in pairs(self.detectedgroups:GetSet()) do + local group=_group --Wrapper.Group#GROUP + + if group and group:IsAlive() then + + -- Get 3D vector of target. + local targetVec3=group:GetVec3() + + -- Distance to target. + local distance=UTILS.VecDist3D(self.position, targetVec3) + + if distance<=self.engagedetectedRmax and distance0 and self.Nred>0 then + + elseif self.Nblu>0 then + + elseif self.Nred>0 then + + end + -- Status update. self.timerStatus:Start(1, 60) @@ -528,12 +563,12 @@ function OPSZONE:onenterGuarded(From, Event, To) local color=self:_GetZoneColor() - self.zone:DrawZone(nil, color, 1.0, color, 0.7) + self.zone:DrawZone(nil, color, 1.0, color, 0.5) end end ---- On enter "Guarded" state. +--- On enter "Attacked" state. -- @param #OPSZONE self -- @param #string From From state. -- @param #string Event Event. @@ -549,9 +584,10 @@ function OPSZONE:onenterAttacked(From, Event, To) if self.drawZone then self.zone:UndrawZone() - local color={1,1,1} - self.zone:DrawZone(nil, color, 1.0, color, 0.9) + local color={1,204/255,204/255} + + self.zone:DrawZone(nil, color, 1.0, color, 0.5) end end @@ -580,7 +616,7 @@ end -- Scan Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Add a platoon to the brigade. +--- Scan zone. -- @param #OPSZONE self -- @return #OPSZONE self function OPSZONE:Scan() @@ -720,7 +756,20 @@ function OPSZONE:Scan() self.Nred=Nred self.Nblu=Nblu self.Nnut=Nnut - + + return self +end + +--- Evaluate zone. +-- @param #OPSZONE self +-- @return #OPSZONE self +function OPSZONE:EvaluateZone() + + -- Set values. + local Nred=self.Nred + local Nblu=self.Nblu + local Nnut=self.Nnut + if self:IsRed() then --- @@ -746,7 +795,7 @@ function OPSZONE:Scan() else - -- Still red units in red zone. + -- Red units in red zone. if Nblu>0 then @@ -758,6 +807,9 @@ function OPSZONE:Scan() if self:IsAttacked() and self:IsContested() then self:Defeated(coalition.side.BLUE) + elseif self:IsEmpty() then + -- Red units left zone and returned (or from initial Empty state). + self:Guarded() end end @@ -810,6 +862,9 @@ function OPSZONE:Scan() if self:IsAttacked() and self:IsContested() then -- Blue defeated read attack. self:Defeated(coalition.side.RED) + elseif self:IsEmpty() then + -- Blue units left zone and returned (or from initial Empty state). + self:Guarded() end end @@ -858,7 +913,7 @@ function OPSZONE:Scan() self:E(self.lid.."ERROR!") end - return self + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -923,11 +978,11 @@ function OPSZONE:_GetZoneColor() local color={0,0,0} if self.ownerCurrent==coalition.side.NEUTRAL then - color={0, 1, 0} + color={1, 1, 1} elseif self.ownerCurrent==coalition.side.BLUE then - color={1, 0, 0} - elseif self.ownerCurrent==coalition.side.RED then color={0, 0, 1} + elseif self.ownerCurrent==coalition.side.RED then + color={1, 0, 0} else end