Merge pull request #1362 from FlightControl-Master/FF/Ops

Ops Enhancements
This commit is contained in:
Frank 2020-10-31 22:05:58 +01:00 committed by GitHub
commit bad8dc2220
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 1718 additions and 324 deletions

View File

@ -913,7 +913,7 @@ do -- FSM
--- Check if FSM is in state. --- Check if FSM is in state.
-- @param #FSM self -- @param #FSM self
-- @param #string State State name. -- @param #string State State name.
-- @param #boolean If true, FSM is in this state. -- @return #boolean If true, FSM is in this state.
function FSM:Is( State ) function FSM:Is( State )
return self.current == State return self.current == State
end end
@ -921,7 +921,7 @@ do -- FSM
--- Check if FSM is in state. --- Check if FSM is in state.
-- @param #FSM self -- @param #FSM self
-- @param #string State State name. -- @param #string State State name.
-- @param #boolean If true, FSM is in this state. -- @return #boolean If true, FSM is in this state.
function FSM:is(state) function FSM:is(state)
return self.current == state return self.current == state
end end

View File

@ -283,7 +283,13 @@ do -- COORDINATE
return self return self
end end
--- Return the coordinates itself. Sounds stupid but can be useful for compatibility.
-- @param #COORDINATE self
-- @return #COORDINATE self
function COORDINATE:GetCoordinate()
return self
end
--- Return the coordinates of the COORDINATE in Vec3 format. --- Return the coordinates of the COORDINATE in Vec3 format.
-- @param #COORDINATE self -- @param #COORDINATE self
@ -733,6 +739,13 @@ do -- COORDINATE
return Velocity or 0 return Velocity or 0
end end
--- Return the "name" of the COORDINATE. Obviously, a coordinate does not have a name like a unit, static or group. So here we take the MGRS coordinates of the position.
-- @param #COORDINATE self
-- @return #string MGRS coordinates.
function COORDINATE:GetName()
local name=self:ToStringMGRS()
return rname
end
--- Return velocity text of the COORDINATE. --- Return velocity text of the COORDINATE.
-- @param #COORDINATE self -- @param #COORDINATE self

View File

@ -27,6 +27,7 @@
-- @field #number dT Time interval between function calls in seconds. -- @field #number dT Time interval between function calls in seconds.
-- @field #number ncalls Counter of function calls. -- @field #number ncalls Counter of function calls.
-- @field #number ncallsMax Max number of function calls. If reached, timer is stopped. -- @field #number ncallsMax Max number of function calls. If reached, timer is stopped.
-- @field #boolean isrunning If `true`, timer is running. Else it was not started yet or was stopped.
-- @extends Core.Base#BASE -- @extends Core.Base#BASE
--- *Better three hours too soon than a minute too late.* - William Shakespeare --- *Better three hours too soon than a minute too late.* - William Shakespeare
@ -111,7 +112,7 @@ _TIMERID=0
--- TIMER class version. --- TIMER class version.
-- @field #string version -- @field #string version
TIMER.version="0.1.0" TIMER.version="0.1.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
@ -143,6 +144,9 @@ function TIMER:New(Function, ...)
-- Number of function calls. -- Number of function calls.
self.ncalls=0 self.ncalls=0
-- Not running yet.
self.isrunning=false
-- Increase counter -- Increase counter
_TIMERID=_TIMERID+1 _TIMERID=_TIMERID+1
@ -186,6 +190,9 @@ function TIMER:Start(Tstart, dT, Duration)
-- Set log id. -- Set log id.
self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid) self.lid=string.format("TIMER UID=%d/%d | ", self.uid, self.tid)
-- Is now running.
self.isrunning=true
-- Debug info. -- Debug info.
self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop))) self:T(self.lid..string.format("Starting Timer in %.3f sec, dT=%s, Tstop=%s", self.Tstart-Tnow, tostring(self.dT), tostring(self.Tstop)))
@ -210,6 +217,9 @@ function TIMER:Stop(Delay)
self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls)) self:T(self.lid..string.format("Stopping timer by removing timer function after %d calls!", self.ncalls))
timer.removeFunction(self.tid) timer.removeFunction(self.tid)
-- Not running any more.
self.isrunning=false
-- Remove DB entry. -- Remove DB entry.
--_TIMERDB[self.uid]=nil --_TIMERDB[self.uid]=nil
@ -229,6 +239,13 @@ function TIMER:SetMaxFunctionCalls(Nmax)
return self return self
end end
--- Check if the timer has been started and was not stopped.
-- @param #TIMER self
-- @return #boolean If `true`, the timer is running.
function TIMER:IsRunning()
return self.isrunning
end
--- Call timer function. --- Call timer function.
-- @param #TIMER self -- @param #TIMER self
-- @param #number time DCS model time in seconds. -- @param #number time DCS model time in seconds.

View File

@ -736,7 +736,66 @@ do -- Airbase
end -- Airbase end -- Airbase
do -- Spot
--- [DCS Class Spot](https://wiki.hoggitworld.com/view/DCS_Class_Spot)
-- Represents a spot from laser or IR-pointer.
-- @type Spot
-- @field #Spot.Category Category enum that stores spot categories.
--- Enum that stores spot categories.
-- @type Spot.Category
-- @field #string INFRA_RED
-- @field #string LASER
--- Creates a laser ray emanating from the given object to a point in 3d space.
-- @function [parent=#Spot] createLaser
-- @param DCS#Object Source The source object of the laser.
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
-- @param #number LaserCode Any 4 digit number between 1111 and 1788.
-- @return #Spot
--- Creates an infrared ray emanating from the given object to a point in 3d space. Can be seen with night vision goggles.
-- @function [parent=#Spot] createInfraRed
-- @param DCS#Object Source Source position of the IR ray.
-- @param DCS#Vec3 LocalRef An optional 3D offset for the source.
-- @param DCS#Vec3 Vec3 Target coordinate where the ray is pointing at.
-- @return #Spot
--- Returns a vec3 table of the x, y, and z coordinates for the position of the given object in 3D space. Coordinates are dependent on the position of the maps origin.
-- @function [parent=#Spot] getPoint
-- @param #Spot self
-- @return DCS#Vec3 Point in 3D, where the beam is pointing at.
--- Sets the destination point from which the source of the spot is drawn toward.
-- @function [parent=#Spot] setPoint
-- @param #Spot self
-- @param DCS#Vec3 Vec3 Point in 3D, where the beam is pointing at.
--- Returns the number that is used to define the laser code for which laser designation can track.
-- @function [parent=#Spot] getCode
-- @param #Spot self
-- @return #number Code The laser code used.
--- Sets the number that is used to define the laser code for which laser designation can track.
-- @function [parent=#Spot] setCode
-- @param #Spot self
-- @param #number Code The laser code. Default value is 1688.
--- Destroys the spot.
-- @function [parent=#Spot] destroy
-- @param #Spot self
--- Gets the category of the spot (laser or IR).
-- @function [parent=#Spot] getCategory
-- @param #Spot self
-- @return #string Category.
Spot = {} --#Spot
end -- Spot
do -- Controller do -- Controller
--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C. --- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C.

View File

@ -152,7 +152,7 @@ AIRWING = {
--- AIRWING class version. --- AIRWING class version.
-- @field #string version -- @field #string version
AIRWING.version="0.5.0" AIRWING.version="0.5.1"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- ToDo list -- ToDo list
@ -472,11 +472,19 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads)
return nil return nil
elseif #payloads==1 then elseif #payloads==1 then
-- Only one payload anyway. -- Only one payload anyway.
return payloads[1] local payload=payloads[1] --#AIRWING.Payload
if not payload.unlimited then
payload.navail=payload.navail-1
end
return payload
else else
-- Sort payloads. -- Sort payloads.
table.sort(payloads, sortpayloads) table.sort(payloads, sortpayloads)
return payloads[1] local payload=payloads[1] --#AIRWING.Payload
if not payload.unlimited then
payload.navail=payload.navail-1
end
return payload
end end
end end

View File

@ -1,12 +1,29 @@
--- **Ops** - Enhanced Ground Group. --- **Ops** - Enhanced Ground Group.
-- --
-- **Main Features:** -- ## Main Features:
-- --
-- * Dynamically add and remove waypoints. -- * Patrol waypoints *ad infinitum*
-- -- * Easy change of ROE and alarm state, formation and other settings
-- * Dynamically add and remove waypoints
-- * Sophisticated task queueing system (know when DCS tasks start and end)
-- * Convenient checks when the group enters or leaves a zone
-- * Detection events for new, known and lost units
-- * Simple LASER and IR-pointer setup
-- * Compatible with AUFTRAG class
-- * Many additional events that the mission designer can hook into
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Armygroup).
--
-- === -- ===
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
--
-- ==
--
-- @module Ops.ArmyGroup -- @module Ops.ArmyGroup
-- @image OPS_ArmyGroup.png -- @image OPS_ArmyGroup.png
@ -15,6 +32,7 @@
-- @type ARMYGROUP -- @type ARMYGROUP
-- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached. -- @field #boolean adinfinitum Resume route at first waypoint when final waypoint is reached.
-- @field #boolean formationPerma Formation that is used permanently and overrules waypoint formations. -- @field #boolean formationPerma Formation that is used permanently and overrules waypoint formations.
-- @field #boolean isMobile If true, group is mobile.
-- @extends Ops.OpsGroup#OPSGROUP -- @extends Ops.OpsGroup#OPSGROUP
--- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge
@ -36,7 +54,12 @@ ARMYGROUP = {
--- Army group element. --- Army group element.
-- @type ARMYGROUP.Element -- @type ARMYGROUP.Element
-- @field #string name Name of the element, i.e. the unit. -- @field #string name Name of the element, i.e. the unit.
-- @field Wrapper.Unit#UNIT unit The UNIT object.
-- @field #string status The element status.
-- @field #string typename Type name. -- @field #string typename Type name.
-- @field #number length Length of element in meters.
-- @field #number width Width of element in meters.
-- @field #number height Height of element in meters.
--- Army Group version. --- Army Group version.
-- @field #string version -- @field #string version
@ -45,8 +68,11 @@ ARMYGROUP.version="0.3.0"
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot. -- TODO: Suppression of fire.
-- TODO: Check if group is mobile.
-- TODO: F10 menu.
-- DONE: Rearm. Specify a point where to go and wait until ammo is full.
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor -- Constructor
@ -54,12 +80,12 @@ ARMYGROUP.version="0.3.0"
--- Create a new ARMYGROUP class object. --- Create a new ARMYGROUP class object.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param #string GroupName Name of the group. -- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`.
-- @return #ARMYGROUP self -- @return #ARMYGROUP self
function ARMYGROUP:New(GroupName) function ARMYGROUP:New(Group)
-- Inherit everything from FSM class. -- Inherit everything from FSM class.
local self=BASE:Inherit(self, OPSGROUP:New(GroupName)) -- #ARMYGROUP local self=BASE:Inherit(self, OPSGROUP:New(Group)) -- #ARMYGROUP
-- Set some string id for output to DCS.log file. -- Set some string id for output to DCS.log file.
self.lid=string.format("ARMYGROUP %s | ", self.groupname) self.lid=string.format("ARMYGROUP %s | ", self.groupname)
@ -73,10 +99,14 @@ function ARMYGROUP:New(GroupName)
-- Add FSM transitions. -- Add FSM transitions.
-- From State --> Event --> To State -- From State --> Event --> To State
self:AddTransition("*", "FullStop", "Holding") -- Hold position. self:AddTransition("*", "FullStop", "Holding") -- Hold position.
self:AddTransition("*", "Cruise", "Cruising") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Cruise along the given route of waypoints.
self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards. self:AddTransition("*", "Detour", "OnDetour") -- Make a detour to a coordinate and resume route afterwards.
self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate. self:AddTransition("OnDetour", "DetourReached", "Cruising") -- Group reached the detour coordinate.
self:AddTransition("*", "Rearm", "Rearm") -- Group is send to a coordinate and waits until ammo is refilled.
self:AddTransition("Rearm", "Rearming", "Rearming") -- Group has arrived at the rearming coodinate and is waiting to be fully rearmed.
self:AddTransition("Rearming", "Rearmed", "Cruising") -- Group was rearmed.
------------------------ ------------------------
--- Pseudo Functions --- --- Pseudo Functions ---
@ -102,7 +132,9 @@ function ARMYGROUP:New(GroupName)
-- Handle events: -- Handle events:
self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.Dead, self.OnEventDead)
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
--self:HandleEvent(EVENTS.Hit, self.OnEventHit)
-- Start the status monitoring. -- Start the status monitoring.
self:__Status(-1) self:__Status(-1)
@ -164,6 +196,8 @@ end
-- @return Ops.OpsGroup#OPSGROUP.Task The task table. -- @return Ops.OpsGroup#OPSGROUP.Task The task table.
function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio) function ARMYGROUP:AddTaskFireAtPoint(Coordinate, Clock, Radius, Nshots, WeaponType, Prio)
Coordinate=self:_CoordinateFromObject(Coordinate)
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType)
local task=self:AddTask(DCStask, Clock, nil, Prio) local task=self:AddTask(DCStask, Clock, nil, Prio)
@ -182,6 +216,8 @@ end
-- @return Ops.OpsGroup#OPSGROUP.Task The task table. -- @return Ops.OpsGroup#OPSGROUP.Task The task table.
function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio) function ARMYGROUP:AddTaskWaypointFireAtPoint(Coordinate, Waypoint, Radius, Nshots, WeaponType, Prio)
Coordinate=self:_CoordinateFromObject(Coordinate)
Waypoint=Waypoint or self:GetWaypointNext() Waypoint=Waypoint or self:GetWaypointNext()
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType) local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType)
@ -224,7 +260,7 @@ end
--- Check if the group is currently on a detour. --- Check if the group is currently on a detour.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @return #boolean If true, group is on a detour -- @return #boolean If true, group is on a detour.
function ARMYGROUP:IsOnDetour() function ARMYGROUP:IsOnDetour()
return self:Is("OnDetour") return self:Is("OnDetour")
end end
@ -265,10 +301,13 @@ function ARMYGROUP:onafterStatus(From, Event, To)
if self.detectionOn then if self.detectionOn then
self:_CheckDetectedUnits() self:_CheckDetectedUnits()
end end
-- Check ammo status.
self:_CheckAmmoStatus()
-- Update position etc. -- Update position etc.
self:_UpdatePosition() self:_UpdatePosition()
-- Check if group got stuck. -- Check if group got stuck.
self:_CheckStuck() self:_CheckStuck()
@ -282,11 +321,11 @@ function ARMYGROUP:onafterStatus(From, Event, To)
local alarm=self:GetAlarmstate() local alarm=self:GetAlarmstate()
local speed=UTILS.MpsToKnots(self.velocity) local speed=UTILS.MpsToKnots(self.velocity)
local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed())
local formation=self.option.Formation local formation=self.option.Formation or "unknown"
-- Info text. -- Info text.
local text=string.format("%s: Wp=%d/%d-->%d Speed=%.1f (%d) Heading=%03d ROE=%d Alarm=%d Formation=%s Tasks=%d Missions=%d", local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Speed=%.1f (%d), Heading=%03d",
fsmstate, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), speed, speedEx, self.heading, roe, alarm, formation, nTaskTot, nMissions) fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), speed, speedEx, self.heading)
self:I(self.lid..text) self:I(self.lid..text)
end end
@ -340,7 +379,7 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
-- Update position. -- Update position.
self:_UpdatePosition() self:_UpdatePosition()
if self.ai then if self.isAI then
-- Set default ROE. -- Set default ROE.
self:SwitchROE(self.option.ROE) self:SwitchROE(self.option.ROE)
@ -358,10 +397,19 @@ function ARMYGROUP:onafterSpawned(From, Event, To)
self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, true) self:SetDefaultRadio(self.radio.Freq, self.radio.Modu, true)
end end
-- Formation
if not self.option.Formation then
self.option.Formation=self.optionDefault.Formation
end
end end
-- Update route. -- Update route.
self:Cruise(nil, self.option.Formation or self.optionDefault.Formation) if #self.waypoints>1 then
self:Cruise(nil, self.option.Formation or self.optionDefault.Formation)
else
self:FullStop()
end
end end
@ -375,6 +423,10 @@ end
-- @param #number Formation Formation of the group. -- @param #number Formation Formation of the group.
function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation)
-- Debug info.
local text=string.format("Update route n=%s, Speed=%s, Formation=%s", tostring(n), tostring(Speed), tostring(Formation))
self:T(self.lid..text)
-- Update route from this waypoint number onwards. -- Update route from this waypoint number onwards.
n=n or self:GetWaypointIndexNext(self.adinfinitum) n=n or self:GetWaypointIndexNext(self.adinfinitum)
@ -451,9 +503,6 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation)
local wp=_wp local wp=_wp
local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or 0, wp.type, wp.speed, wp.alt, wp.action) local text=string.format("WP #%d UID=%d type=%s: Speed=%d m/s, alt=%d m, Action=%s", i, wp.uid and wp.uid or 0, wp.type, wp.speed, wp.alt, wp.action)
self:T(text) self:T(text)
if false and wp.coordinate then
wp.coordinate:MarkToAll(text)
end
end end
end end
@ -479,6 +528,43 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation)
end end
--- On after "GotoWaypoint" event. Group will got to the given waypoint and execute its route from there.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number UID The goto waypoint unique ID.
-- @param #number Speed (Optional) Speed to waypoint in knots.
-- @param #number Formation (Optional) Formation to waypoint.
function ARMYGROUP:onafterGotoWaypoint(From, Event, To, UID, Speed, Formation)
local n=self:GetWaypointIndex(UID)
--env.info(string.format("FF AG Goto waypoint UID=%s Index=%s, Speed=%s, Formation=%s", tostring(UID), tostring(n), tostring(Speed), tostring(Formation)))
if n then
-- TODO: switch to re-enable waypoint tasks.
if false then
local tasks=self:GetTasksWaypoint(n)
for _,_task in pairs(tasks) do
local task=_task --Ops.OpsGroup#OPSGROUP.Task
task.status=OPSGROUP.TaskStatus.SCHEDULED
end
end
-- Speed to waypoint.
Speed=Speed or self:GetSpeedToWaypoint(n)
-- Update the route.
self:UpdateRoute(n, Speed, Formation)
end
end
--- On after "Detour" event. --- On after "Detour" event.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param #string From From state. -- @param #string From From state.
@ -508,6 +594,55 @@ function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation,
end end
--- On after "Rearm" event.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate Coordinate where to rearm.
-- @param #number Formation Formation of the group.
function ARMYGROUP:onafterRearm(From, Event, To, Coordinate, Formation)
-- ID of current waypoint.
local uid=self:GetWaypointCurrent().uid
-- Add waypoint after current.
local wp=self:AddWaypoint(Coordinate, nil, uid, Formation, true)
-- Set if we want to resume route after reaching the detour waypoint.
wp.detour=0
end
--- On after "Rearming" event.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function ARMYGROUP:onafterRearming(From, Event, To)
-- Get current position.
local pos=self:GetCoordinate()
-- Create a new waypoint.
local wp=pos:WaypointGround(0)
-- Create new route consisting of only this position ==> Stop!
self:Route({wp})
end
--- On after "Rearmed" event.
-- @param #ARMYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function ARMYGROUP:onafterRearmed(From, Event, To)
self:_CheckGroupDone(1)
end
--- On after "DetourReached" event. --- On after "DetourReached" event.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param #string From From state. -- @param #string From From state.
@ -549,7 +684,7 @@ function ARMYGROUP:onafterCruise(From, Event, To, Speed, Formation)
end end
--- On after Start event. Starts the ARMYGROUP FSM and event handlers. --- On after "Stop" event.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
@ -631,7 +766,7 @@ function ARMYGROUP:OnEventDead(EventData)
end end
--- Event function handling the crash of a unit. --- Event function handling when a unit is removed from the game.
-- @param #ARMYGROUP self -- @param #ARMYGROUP self
-- @param Core.Event#EVENTDATA EventData Event data. -- @param Core.Event#EVENTDATA EventData Event data.
function ARMYGROUP:OnEventRemoveUnit(EventData) function ARMYGROUP:OnEventRemoveUnit(EventData)
@ -654,6 +789,22 @@ function ARMYGROUP:OnEventRemoveUnit(EventData)
end end
--- Event function handling when a unit is hit.
-- @param #ARMYGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function ARMYGROUP:OnEventHit(EventData)
-- Check that this is the right group.
if EventData and EventData.IniGroup and EventData.IniUnit and EventData.IniGroupName and EventData.IniGroupName==self.groupname then
local unit=EventData.IniUnit
local group=EventData.IniGroup
local unitname=EventData.IniUnitName
-- TODO: suppression
end
end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Routing -- Routing
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@ -668,6 +819,8 @@ end
-- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table. -- @return Ops.OpsGroup#OPSGROUP.Waypoint Waypoint table.
function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute) function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation, Updateroute)
local coordinate=self:_CoordinateFromObject(Coordinate)
-- Set waypoint index. -- Set waypoint index.
local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID) local wpnumber=self:GetWaypointIndexAfterID(AfterWaypointWithID)
@ -680,7 +833,7 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation
Speed=Speed or self:GetSpeedCruise() Speed=Speed or self:GetSpeedCruise()
-- Create a Naval waypoint. -- Create a Naval waypoint.
local wp=Coordinate:WaypointGround(UTILS.KnotsToKmph(Speed), Formation) local wp=coordinate:WaypointGround(UTILS.KnotsToKmph(Speed), Formation)
-- Create waypoint data table. -- Create waypoint data table.
local waypoint=self:_CreateWaypoint(wp) local waypoint=self:_CreateWaypoint(wp)
@ -689,15 +842,15 @@ function ARMYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Formation
self:_AddWaypoint(waypoint, wpnumber) self:_AddWaypoint(waypoint, wpnumber)
-- Get closest point to road. -- Get closest point to road.
waypoint.roadcoord=Coordinate:GetClosestPointToRoad(false) waypoint.roadcoord=coordinate:GetClosestPointToRoad(false)
if waypoint.roadcoord then if waypoint.roadcoord then
waypoint.roaddist=Coordinate:Get2DDistance(waypoint.roadcoord) waypoint.roaddist=coordinate:Get2DDistance(waypoint.roadcoord)
else else
waypoint.roaddist=1000*1000 --1000 km. waypoint.roaddist=1000*1000 --1000 km.
end end
-- Debug info. -- Debug info.
self:I(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action)) self:T(self.lid..string.format("Adding waypoint UID=%d (index=%d), Speed=%.1f knots, Dist2Road=%d m, Action=%s", waypoint.uid, wpnumber, Speed, waypoint.roaddist, waypoint.action))
-- Update route. -- Update route.
if Updateroute==nil or Updateroute==true then if Updateroute==nil or Updateroute==true then
@ -726,8 +879,8 @@ function ARMYGROUP:_InitGroup()
self.isNaval=false self.isNaval=false
self.isGround=true self.isGround=true
-- Ships are always AI. -- Ground are always AI.
self.ai=true self.isAI=true
-- Is (template) group late activated. -- Is (template) group late activated.
self.isLateActivated=self.template.lateActivation self.isLateActivated=self.template.lateActivation
@ -764,51 +917,63 @@ function ARMYGROUP:_InitGroup()
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT local unit=_unit --Wrapper.Unit#UNIT
-- TODO: this is wrong when grouping is used!
local unittemplate=unit:GetTemplate()
local element={} --#ARMYGROUP.Element local element={} --#ARMYGROUP.Element
element.name=unit:GetName() element.name=unit:GetName()
element.typename=unit:GetTypeName() element.unit=unit
element.status=OPSGROUP.ElementStatus.INUTERO element.status=OPSGROUP.ElementStatus.INUTERO
element.unit=unit element.typename=unit:GetTypeName()
element.skill=unittemplate.skill or "Unknown"
element.ai=true
element.category=element.unit:GetUnitCategory()
element.categoryname=element.unit:GetCategoryName()
element.size, element.length, element.height, element.width=unit:GetObjectSize()
element.ammo0=self:GetAmmoUnit(unit, false)
-- Debug text.
if self.verbose>=2 then
local text=string.format("Adding element %s: status=%s, skill=%s, category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)",
element.name, element.status, element.skill, element.categoryname, element.category, element.size, element.length, element.height, element.width)
self:I(self.lid..text)
end
-- Add element to table.
table.insert(self.elements, element) table.insert(self.elements, element)
self:GetAmmoUnit(unit, false) -- Get Descriptors.
self.descriptors=self.descriptors or unit:GetDesc()
if unit:IsAlive() then -- Set type name.
self.actype=self.actype or unit:GetTypeName()
if unit:IsAlive() then
-- Trigger spawned event.
self:ElementSpawned(element) self:ElementSpawned(element)
end end
end end
-- Get first unit. This is used to extract other parameters. -- Debug info.
local unit=self.group:GetUnit(1) if self.verbose>=1 then
local text=string.format("Initialized Army Group %s:\n", self.groupname)
if unit then text=text..string.format("Unit type = %s\n", self.actype)
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
self.descriptors=unit:GetDesc() text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
text=text..string.format("Elements = %d\n", #self.elements)
self.actype=unit:GetTypeName() text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
-- Debug info. text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles)
if self.verbose>=1 then text=text..string.format("FSM state = %s\n", self:GetState())
local text=string.format("Initialized Army Group %s:\n", self.groupname) text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
text=text..string.format("Unit type = %s\n", self.actype) text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax)) self:I(self.lid..text)
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles)
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
self:I(self.lid..text)
end
-- Init done.
self.groupinitialized=true
end end
-- Init done.
self.groupinitialized=true
return self return self
end end

View File

@ -1,18 +1,26 @@
--- **Ops** - Auftrag (mission) for Ops. --- **Ops** - Auftrag (mission) for Ops.
-- --
-- **Main Features:** -- ## Main Features:
-- --
-- * Simplifies defining and executing DCS tasks. -- * Simplifies defining and executing DCS tasks
-- * Additional useful events. -- * Additional useful events
-- * Set mission start/stop times. -- * Set mission start/stop times
-- * Set mission priority and urgency (can cancel running missions). -- * Set mission priority and urgency (can cancel running missions)
-- * Specific mission options for ROE, ROT, formation, etc. -- * Specific mission options for ROE, ROT, formation, etc.
-- * Interface to FLIGHTGROUP, AIRWING and WINGCOMMANDER classes. -- * Compatible with FLIGHTGROUP, NAVYGROUP, ARMYGROUP, AIRWING, WINGCOMMANDER and CHIEF classes
-- * FSM events when a mission is done, successful or failed. -- * FSM events when a mission is done, successful or failed
-- --
-- === -- ===
-- --
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Auftrag).
--
-- ===
--
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.Auftrag -- @module Ops.Auftrag
-- @image OPS_Auftrag.png -- @image OPS_Auftrag.png
@ -1682,6 +1690,13 @@ function AUFTRAG:GetPriority()
return self.prio return self.prio
end end
--- Get casualties, i.e. number of units that died during this mission.
-- @param #AUFTRAG self
-- @return #number Number of dead units.
function AUFTRAG:GetCasualties()
return self.Ncasualties or 0
end
--- Check if mission is "urgent". --- Check if mission is "urgent".
-- @param #AUFTRAG self -- @param #AUFTRAG self
-- @return #boolean If `true`, mission is "urgent". -- @return #boolean If `true`, mission is "urgent".
@ -1942,7 +1957,7 @@ function AUFTRAG:IsReadyToCancel()
return true return true
end end
-- Evaluate failure condition. One is enough.
local failure=self:EvalConditionsAny(self.conditionFailure) local failure=self:EvalConditionsAny(self.conditionFailure)
if failure then if failure then
@ -1950,6 +1965,7 @@ function AUFTRAG:IsReadyToCancel()
return true return true
end end
-- Evaluate success consitions. One is enough.
local success=self:EvalConditionsAny(self.conditionSuccess) local success=self:EvalConditionsAny(self.conditionSuccess)
if success then if success then

View File

@ -1,16 +1,30 @@
--- **Ops** - Enhanced Airborne Group. --- **Ops** - Enhanced Airborne Group.
-- --
-- **Main Features:** -- ## Main Features:
-- --
-- * Monitor flight status of elements and/or the entire group. -- * Monitor flight status of elements and/or the entire group
-- * Monitor fuel and ammo status. -- * Monitor fuel and ammo status
-- * Conveniently set radio freqencies, TACAN, ROE etc. -- * Conveniently set radio freqencies, TACAN, ROE etc
-- * Sophisticated task queueing system. -- * Order helos to land at specifc coordinates
-- * Many additional events for each element and the whole group. -- * Dynamically add and remove waypoints
-- * Sophisticated task queueing system (know when DCS tasks start and end)
-- * Convenient checks when the group enters or leaves a zone
-- * Detection events for new, known and lost units
-- * Simple LASER and IR-pointer setup
-- * Compatible with AUFTRAG class
-- * Many additional events that the mission designer can hook into
-- --
-- === -- ===
-- --
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Flightgroup).
--
-- ===
--
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.FlightGroup -- @module Ops.FlightGroup
-- @image OPS_FlightGroup.png -- @image OPS_FlightGroup.png
@ -50,7 +64,7 @@
-- --
-- @extends Ops.OpsGroup#OPSGROUP -- @extends Ops.OpsGroup#OPSGROUP
--- *To invent an airplane is nothing. To build one is something. To fly is everything.* -- Otto Lilienthal --- *To invent an airplane is nothing; to build one is something; to fly is everything.* -- Otto Lilienthal
-- --
-- === -- ===
-- --
@ -186,6 +200,7 @@ FLIGHTGROUP.version="0.6.0"
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: VTOL aircraft.
-- TODO: Use new UnitLost event instead of crash/dead. -- TODO: Use new UnitLost event instead of crash/dead.
-- TODO: Options EPLRS, Afterburner restrict etc. -- TODO: Options EPLRS, Afterburner restrict etc.
-- DONE: Add TACAN beacon. -- DONE: Add TACAN beacon.
@ -210,7 +225,7 @@ FLIGHTGROUP.version="0.6.0"
--- Create a new FLIGHTGROUP object and start the FSM. --- Create a new FLIGHTGROUP object and start the FSM.
-- @param #FLIGHTGROUP self -- @param #FLIGHTGROUP self
-- @param Wrapper.Group#GROUP group The group object. Can also be given as #string with the name of the group. -- @param Wrapper.Group#GROUP Group The group object. Can also be given by its group name as `#string`.
-- @return #FLIGHTGROUP self -- @return #FLIGHTGROUP self
function FLIGHTGROUP:New(group) function FLIGHTGROUP:New(group)
@ -396,7 +411,7 @@ function FLIGHTGROUP:SetFlightControl(flightcontrol)
table.insert(flightcontrol.flights, self) table.insert(flightcontrol.flights, self)
-- Update flight's F10 menu. -- Update flight's F10 menu.
if self.ai==false then if self.isAI==false then
self:_UpdateMenu(0.5) self:_UpdateMenu(0.5)
end end
@ -705,7 +720,7 @@ end
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
---- Update status. ---- Update status.
-- @param #FLIHGTGROUP self -- @param #FLIGHTGROUP self
function FLIGHTGROUP:onbeforeStatus(From, Event, To) function FLIGHTGROUP:onbeforeStatus(From, Event, To)
-- First we check if elements are still alive. Could be that they were despawned without notice, e.g. when landing on a too small airbase. -- First we check if elements are still alive. Could be that they were despawned without notice, e.g. when landing on a too small airbase.
@ -1040,7 +1055,7 @@ function FLIGHTGROUP:OnEventEngineStartup(EventData)
-- TODO: could be that this element is part of a human flight group. -- TODO: could be that this element is part of a human flight group.
-- Problem: when player starts hot, the AI does too and starts to taxi immidiately :( -- Problem: when player starts hot, the AI does too and starts to taxi immidiately :(
-- when player starts cold, ? -- when player starts cold, ?
if self.ai then if self.isAI then
self:ElementEngineOn(element) self:ElementEngineOn(element)
else else
if element.ai then if element.ai then
@ -1447,7 +1462,7 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To)
-- Update position. -- Update position.
self:_UpdatePosition() self:_UpdatePosition()
if self.ai then if self.isAI then
-- Set ROE. -- Set ROE.
self:SwitchROE(self.option.ROE) self:SwitchROE(self.option.ROE)
@ -1522,7 +1537,7 @@ function FLIGHTGROUP:onafterParking(From, Event, To)
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.PARKING) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.PARKING)
-- Update player menu. -- Update player menu.
if not self.ai then if not self.isAI then
self:_UpdateMenu(0.5) self:_UpdateMenu(0.5)
end end
@ -1547,7 +1562,7 @@ function FLIGHTGROUP:onafterTaxiing(From, Event, To)
if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then if self.flightcontrol and airbase and self.flightcontrol.airbasename==airbase:GetName() then
-- Add AI flight to takeoff queue. -- Add AI flight to takeoff queue.
if self.ai then if self.isAI then
-- AI flights go directly to TAKEOFF as we don't know when they finished taxiing. -- AI flights go directly to TAKEOFF as we don't know when they finished taxiing.
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAKEOFF) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.TAKEOFF)
else else
@ -1586,7 +1601,7 @@ end
function FLIGHTGROUP:onafterAirborne(From, Event, To) function FLIGHTGROUP:onafterAirborne(From, Event, To)
self:T(self.lid..string.format("Flight airborne")) self:T(self.lid..string.format("Flight airborne"))
if self.ai then if self.isAI then
self:_CheckGroupDone(1) self:_CheckGroupDone(1)
else else
self:_UpdateMenu() self:_UpdateMenu()
@ -1737,7 +1752,7 @@ function FLIGHTGROUP:onbeforeUpdateRoute(From, Event, To, n)
--end --end
-- Only AI flights. -- Only AI flights.
if not self.ai then if not self.isAI then
allowed=false allowed=false
end end
@ -1840,7 +1855,7 @@ end
-- @param #number delay Delay in seconds. -- @param #number delay Delay in seconds.
function FLIGHTGROUP:_CheckGroupDone(delay) function FLIGHTGROUP:_CheckGroupDone(delay)
if self:IsAlive() and self.ai then if self:IsAlive() and self.isAI then
if delay and delay>0 then if delay and delay>0 then
-- Delayed call. -- Delayed call.
@ -2099,7 +2114,7 @@ function FLIGHTGROUP:onafterRTB(From, Event, To, airbase, SpeedTo, SpeedHold, Sp
end end
if self.ai then if self.isAI then
local routeto=false local routeto=false
if fc or world.event.S_EVENT_KILL then if fc or world.event.S_EVENT_KILL then
@ -2279,7 +2294,7 @@ function FLIGHTGROUP:onafterHolding(From, Event, To)
-- Set flight status to holding -- Set flight status to holding
self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.HOLDING) self.flightcontrol:SetFlightStatus(self, FLIGHTCONTROL.FlightStatus.HOLDING)
if not self.ai then if not self.isAI then
self:_UpdateMenu() self:_UpdateMenu()
end end
@ -2626,10 +2641,10 @@ function FLIGHTGROUP:_InitGroup()
self.tacan=UTILS.DeepCopy(self.tacanDefault) self.tacan=UTILS.DeepCopy(self.tacanDefault)
-- Is this purely AI? -- Is this purely AI?
self.ai=not self:_IsHuman(group) self.isAI=not self:_IsHuman(group)
-- Create Menu. -- Create Menu.
if not self.ai then if not self.isAI then
self.menu=self.menu or {} self.menu=self.menu or {}
self.menu.atc=self.menu.atc or {} self.menu.atc=self.menu.atc or {}
self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC") self.menu.atc.root=self.menu.atc.root or MENU_GROUP:New(self.group, "ATC")
@ -2665,7 +2680,7 @@ function FLIGHTGROUP:_InitGroup()
text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling)) text=text..string.format("Ceiling = %.1f feet\n", UTILS.MetersToFeet(self.ceiling))
text=text..string.format("Tanker type = %s\n", tostring(self.tankertype)) text=text..string.format("Tanker type = %s\n", tostring(self.tankertype))
text=text..string.format("Refuel type = %s\n", tostring(self.refueltype)) text=text..string.format("Refuel type = %s\n", tostring(self.refueltype))
text=text..string.format("AI = %s\n", tostring(self.ai)) text=text..string.format("AI = %s\n", tostring(self.isAI))
text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter())) text=text..string.format("Helicopter = %s\n", tostring(self.group:IsHelicopter()))
text=text..string.format("Elements = %d\n", #self.elements) text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints) text=text..string.format("Waypoints = %d\n", #self.waypoints)
@ -2729,6 +2744,7 @@ function FLIGHTGROUP:AddElementByName(unitname)
element.ai=true element.ai=true
end end
-- Debug text.
local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s", local text=string.format("Adding element %s: status=%s, skill=%s, modex=%s, fuelmass=%.1f (%d), category=%d, categoryname=%s, callsign=%s, ai=%s",
element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai)) element.name, element.status, element.skill, element.modex, element.fuelmass, element.fuelrel*100, element.category, element.categoryname, element.callsign, tostring(element.ai))
self:T(self.lid..text) self:T(self.lid..text)

View File

@ -1,15 +1,33 @@
--- **Ops** - Enhanced Naval Group. --- **Ops** - Enhanced Naval Group.
-- --
-- **Main Features:** -- ## Main Features:
-- --
-- * Dynamically add and remove waypoints. -- * Let the group steam into the wind
-- * Let the group steam into the wind. -- * Command a full stop
-- * Command a full stop. -- * Patrol waypoints *ad infinitum*
-- * Let a submarine dive and surface. -- * Collision warning, if group is heading towards a land mass
-- -- * Automatic pathfinding, e.g. around islands
-- * Let a submarine dive and surface
-- * Manage TACAN and ICLS beacons
-- * Dynamically add and remove waypoints
-- * Sophisticated task queueing system (know when DCS tasks start and end)
-- * Convenient checks when the group enters or leaves a zone
-- * Detection events for new, known and lost units
-- * Simple LASER and IR-pointer setup
-- * Compatible with AUFTRAG class
-- * Many additional events that the mission designer can hook into
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/OPS%20-%20Navygroup)
--
-- === -- ===
-- --
-- ### Author: **funkyfranky** -- ### Author: **funkyfranky**
--
-- ===
-- @module Ops.NavyGroup -- @module Ops.NavyGroup
-- @image OPS_NavyGroup.png -- @image OPS_NavyGroup.png
@ -19,6 +37,7 @@
-- @field #boolean turning If true, group is currently turning. -- @field #boolean turning If true, group is currently turning.
-- @field #NAVYGROUP.IntoWind intowind Into wind info. -- @field #NAVYGROUP.IntoWind intowind Into wind info.
-- @field #table Qintowind Queue of "into wind" turns. -- @field #table Qintowind Queue of "into wind" turns.
-- @field #number intowindcounter Counter of into wind IDs.
-- @field #number depth Ordered depth in meters. -- @field #number depth Ordered depth in meters.
-- @field #boolean collisionwarning If true, collition warning. -- @field #boolean collisionwarning If true, collition warning.
-- @field #boolean pathfindingOn If true, enable pathfining. -- @field #boolean pathfindingOn If true, enable pathfining.
@ -74,7 +93,9 @@ NAVYGROUP.version="0.5.0"
-- TODO list -- TODO list
------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Collision warning. -- TODO: Extend, shorten turn into wind windows
-- TODO: Skipper menu.
-- DONE: Collision warning.
-- DONE: Detour, add temporary waypoint and resume route. -- DONE: Detour, add temporary waypoint and resume route.
-- DONE: Stop and resume route. -- DONE: Stop and resume route.
-- DONE: Add waypoints. -- DONE: Add waypoints.
@ -109,7 +130,9 @@ function NAVYGROUP:New(GroupName)
self:AddTransition("*", "Cruise", "Cruising") -- Hold position. self:AddTransition("*", "Cruise", "Cruising") -- Hold position.
self:AddTransition("*", "TurnIntoWind", "IntoWind") -- Command the group to turn into the wind. self:AddTransition("*", "TurnIntoWind", "IntoWind") -- Command the group to turn into the wind.
self:AddTransition("*", "TurnIntoWindOver", "Cruising") -- Turn into wind is over. self:AddTransition("IntoWind", "TurnedIntoWind", "IntoWind") -- Group turned into wind.
self:AddTransition("IntoWind", "TurnIntoWindStop", "IntoWind") -- Stop a turn into wind.
self:AddTransition("IntoWind", "TurnIntoWindOver", "Cruising") -- Turn into wind is over.
self:AddTransition("*", "TurningStarted", "*") -- Group started turning. self:AddTransition("*", "TurningStarted", "*") -- Group started turning.
self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning. self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning.
@ -144,14 +167,6 @@ function NAVYGROUP:New(GroupName)
-- Initialize the group. -- Initialize the group.
self:_InitGroup() self:_InitGroup()
-- Debug trace.
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
-- Handle events: -- Handle events:
self:HandleEvent(EVENTS.Birth, self.OnEventBirth) self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
self:HandleEvent(EVENTS.Dead, self.OnEventDead) self:HandleEvent(EVENTS.Dead, self.OnEventDead)
@ -272,7 +287,7 @@ function NAVYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo
return task return task
end end
--- Add aircraft recovery time window and recovery case. --- Create a turn into wind window. Note that this is not executed as it not added to the queue.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. -- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now.
-- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. -- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time.
@ -280,7 +295,7 @@ end
-- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. -- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint.
-- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg. -- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg.
-- @return #NAVYGROUP.IntoWind Recovery window. -- @return #NAVYGROUP.IntoWind Recovery window.
function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) function NAVYGROUP:_CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset)
-- Absolute mission time in seconds. -- Absolute mission time in seconds.
local Tnow=timer.getAbsTime() local Tnow=timer.getAbsTime()
@ -335,17 +350,17 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset)
return recovery return recovery
end end
--- Add aircraft recovery time window and recovery case. --- Add a time window, where the groups steams into the wind.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now. -- @param #string starttime Start time, e.g. "8:00" for eight o'clock. Default now.
-- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time. -- @param #string stoptime Stop time, e.g. "9:00" for nine o'clock. Default 90 minutes after start time.
-- @param #number speed Speed in knots during turn into wind leg. -- @param #number speed Speed in knots during turn into wind leg.
-- @param #boolean uturn If true (or nil), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint. -- @param #boolean uturn If `true` (or `nil`), carrier wil perform a U-turn and go back to where it came from before resuming its route to the next waypoint. If false, it will go directly to the next waypoint.
-- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg. -- @param #number offset Offset angle in degrees, e.g. to account for an angled runway. Default 0 deg.
-- @return #NAVYGROUP.IntoWind Recovery window. -- @return #NAVYGROUP.IntoWind Turn into window data table.
function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset) function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset)
local recovery=self:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) local recovery=self:_CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset)
--TODO: check if window is overlapping with an other and if extend the window. --TODO: check if window is overlapping with an other and if extend the window.
@ -355,6 +370,31 @@ function NAVYGROUP:AddTurnIntoWind(starttime, stoptime, speed, uturn, offset)
return recovery return recovery
end end
--- Remove steam into wind window from queue. If the window is currently active, it is stopped first.
-- @param #NAVYGROUP self
-- @param #NAVYGROUP.IntoWind IntoWindData Turn into window data table.
-- @return #NAVYGROUP self
function NAVYGROUP:RemoveTurnIntoWind(IntoWindData)
-- Check if this is a window currently open.
if self.intowind and self.intowind.Id==IntoWindData.Id then
--env.info("FF stop in remove")
self:TurnIntoWindStop()
return
end
for i,_tiw in pairs(self.Qintowind) do
local tiw=_tiw --#NAVYGROUP.IntoWind
if tiw.Id==IntoWindData.Id then
--env.info("FF removing window "..tiw.Id)
table.remove(self.Qintowind, i)
break
end
end
return self
end
--- Check if the group is currently holding its positon. --- Check if the group is currently holding its positon.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
@ -527,6 +567,37 @@ function NAVYGROUP:onafterStatus(From, Event, To)
end end
---
-- Recovery Windows
---
if self.verbose>=2 then
-- Debug output:
local text=string.format(self.lid.."Turn into wind time windows:")
-- Handle case with no recoveries.
if #self.Qintowind==0 then
text=text.." none!"
end
-- Loop over all slots.
for i,_recovery in pairs(self.Qintowind) do
local recovery=_recovery --#NAVYGROUP.IntoWind
-- Get start/stop clock strings.
local Cstart=UTILS.SecondsToClock(recovery.Tstart)
local Cstop=UTILS.SecondsToClock(recovery.Tstop)
-- Debug text.
text=text..string.format("\n[%d] ID=%d Start=%s Stop=%s Open=%s Over=%s", i, recovery.Id, Cstart, Cstop, tostring(recovery.Open), tostring(recovery.Over))
end
-- Debug output.
self:I(self.lid..text)
end
--- ---
-- Tasks & Missions -- Tasks & Missions
@ -568,7 +639,7 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
-- Update position. -- Update position.
self:_UpdatePosition() self:_UpdatePosition()
if self.ai then if self.isAI then
-- Set default ROE. -- Set default ROE.
self:SwitchROE(self.option.ROE) self:SwitchROE(self.option.ROE)
@ -592,7 +663,11 @@ function NAVYGROUP:onafterSpawned(From, Event, To)
end end
-- Update route. -- Update route.
self:Cruise() if #self.waypoints>1 then
self:Cruise()
else
self:FullStop()
end
end end
@ -757,36 +832,83 @@ function NAVYGROUP:onafterTurnIntoWind(From, Event, To, IntoWind)
end end
--- On before "TurnIntoWindStop" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function NAVYGROUP:onbeforeTurnIntoWindStop(From, Event, To)
if self.intowind then
return true
else
return false
end
end
--- On after "TurnIntoWindStop" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function NAVYGROUP:onafterTurnIntoWindStop(From, Event, To)
self:TurnIntoWindOver(self.intowind)
end
--- On after "TurnIntoWindOver" event. --- On after "TurnIntoWindOver" event.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @param #string From From state. -- @param #string From From state.
-- @param #string Event Event. -- @param #string Event Event.
-- @param #string To To state. -- @param #string To To state.
-- @param #number Duration Duration in seconds. -- @param #NAVYGROUP.IntoWind IntoWindData Data table.
-- @param #number Speed Speed in knots. function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To, IntoWindData)
-- @param #boolean Uturn Return to the place we came from.
function NAVYGROUP:onafterTurnIntoWindOver(From, Event, To)
-- Debug message. if IntoWindData and self.intowind and IntoWindData.Id==self.intowind.Id then
self:T2(self.lid.."Turn Into Wind Over!")
self.intowind.Over=true -- Debug message.
self.intowind.Open=false self:T2(self.lid.."Turn Into Wind Over!")
-- Remove additional waypoint. -- Window over and not open anymore.
self:RemoveWaypointByID(self.intowind.waypoint.uid) self.intowind.Over=true
self.intowind.Open=false
-- Remove additional waypoint.
self:RemoveWaypointByID(self.intowind.waypoint.uid)
if self.intowind.Uturn then
---
-- U-turn ==> Go to coordinate where we left the route.
---
-- Detour to where we left the route.
self:T(self.lid.."FF Turn Into Wind Over ==> Uturn!")
self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true)
else
---
-- Go directly to next waypoint.
---
-- Next waypoint index and speed.
local indx=self:GetWaypointIndexNext()
local speed=self:GetWaypointSpeed(indx)
-- Update route.
self:T(self.lid..string.format("FF Turn Into Wind Over ==> Next WP Index=%d at %.1f knots via update route!", indx, speed))
self:__UpdateRoute(-1, indx, speed)
end
-- Set current window to nil.
self.intowind=nil
-- Remove window from queue.
self:RemoveTurnIntoWind(IntoWindData)
if self.intowind.Uturn then
self:T(self.lid.."Turn Into Wind Over ==> Uturn!")
self:Detour(self.intowind.Coordinate, self:GetSpeedCruise(), 0, true)
else
self:T(self.lid.."FF Turn Into Wind Over ==> Next WP!")
local indx=self:GetWaypointIndexNext()
local speed=self:GetWaypointSpeed(indx)
self:__UpdateRoute(-1, indx, speed)
end end
self.intowind=nil
end end
@ -874,6 +996,11 @@ end
function NAVYGROUP:onafterTurningStopped(From, Event, To) function NAVYGROUP:onafterTurningStopped(From, Event, To)
self.turning=false self.turning=false
self.collisionwarning=false self.collisionwarning=false
if self:IsSteamingIntoWind() then
self:TurnedIntoWind()
end
end end
--- On after "CollisionWarning" event. --- On after "CollisionWarning" event.
@ -1009,10 +1136,10 @@ function NAVYGROUP:AddWaypoint(Coordinate, Speed, AfterWaypointWithID, Depth, Up
-- Check if a coordinate was given or at least a positionable. -- Check if a coordinate was given or at least a positionable.
if not Coordinate:IsInstanceOf("COORDINATE") then if not Coordinate:IsInstanceOf("COORDINATE") then
if Coordinate:IsInstanceOf("POSITIONABLE") or Coordinate:IsInstanceOf("ZONE_BASE") then if Coordinate:IsInstanceOf("POSITIONABLE") or Coordinate:IsInstanceOf("ZONE_BASE") then
self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE. Trying to get coordinate") self:T(self.lid.."WARNING: Coordinate is not a COORDINATE but a POSITIONABLE or ZONE. Trying to get coordinate")
Coordinate=Coordinate:GetCoordinate() Coordinate=Coordinate:GetCoordinate()
else else
self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE!") self:E(self.lid.."ERROR: Coordinate is neither a COORDINATE nor any POSITIONABLE or ZONE!")
return nil return nil
end end
end end
@ -1071,7 +1198,7 @@ function NAVYGROUP:_InitGroup()
--self.isSubmarine=self.group:IsSubmarine() --self.isSubmarine=self.group:IsSubmarine()
-- Ships are always AI. -- Ships are always AI.
self.ai=true self.isAI=true
-- Is (template) group late activated. -- Is (template) group late activated.
self.isLateActivated=self.template.lateActivation self.isLateActivated=self.template.lateActivation
@ -1111,51 +1238,64 @@ function NAVYGROUP:_InitGroup()
for _,_unit in pairs(units) do for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT local unit=_unit --Wrapper.Unit#UNIT
-- Get unit template.
local unittemplate=unit:GetTemplate()
local element={} --#NAVYGROUP.Element local element={} --#NAVYGROUP.Element
element.name=unit:GetName() element.name=unit:GetName()
element.typename=unit:GetTypeName()
element.status=OPSGROUP.ElementStatus.INUTERO
element.unit=unit element.unit=unit
element.status=OPSGROUP.ElementStatus.INUTERO
element.typename=unit:GetTypeName()
element.skill=unittemplate.skill or "Unknown"
element.ai=true
element.category=element.unit:GetUnitCategory()
element.categoryname=element.unit:GetCategoryName()
element.size, element.length, element.height, element.width=unit:GetObjectSize()
element.ammo0=self:GetAmmoUnit(unit, false)
-- Debug text.
if self.verbose>=2 then
local text=string.format("Adding element %s: status=%s, skill=%s, category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)",
element.name, element.status, element.skill, element.categoryname, element.category, element.size, element.length, element.height, element.width)
self:I(self.lid..text)
end
-- Add element to table.
table.insert(self.elements, element) table.insert(self.elements, element)
-- Get Descriptors.
self.descriptors=self.descriptors or unit:GetDesc()
self:GetAmmoUnit(unit, false) -- Set type name.
self.actype=self.actype or unit:GetTypeName()
if unit:IsAlive() then if unit:IsAlive() then
-- Trigger spawned event.
self:ElementSpawned(element) self:ElementSpawned(element)
end end
end end
-- Get first unit. This is used to extract other parameters.
local unit=self.group:GetUnit(1)
if unit then
self.descriptors=unit:GetDesc()
self.actype=unit:GetTypeName()
-- Debug info.
if self.verbose>=1 then
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
text=text..string.format("Unit type = %s\n", self.actype)
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
self:I(self.lid..text)
end
-- Init done.
self.groupinitialized=true
-- Debug info.
if self.verbose>=1 then
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
text=text..string.format("Unit type = %s\n", self.actype)
text=text..string.format("Speed max = %.1f Knots\n", UTILS.KmphToKnots(self.speedMax))
text=text..string.format("Speed cruise = %.1f Knots\n", UTILS.KmphToKnots(self.speedCruise))
text=text..string.format("Elements = %d\n", #self.elements)
text=text..string.format("Waypoints = %d\n", #self.waypoints)
text=text..string.format("Radio = %.1f MHz %s %s\n", self.radio.Freq, UTILS.GetModulationName(self.radio.Modu), tostring(self.radio.On))
text=text..string.format("Ammo = %d (G=%d/R=%d/M=%d/T=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Missiles, self.ammo.Torpedos)
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
self:I(self.lid..text)
end end
-- Init done.
self.groupinitialized=true
return self return self
end end
@ -1306,66 +1446,60 @@ function NAVYGROUP:_CheckTurnsIntoWind()
-- Get current abs time. -- Get current abs time.
local time=timer.getAbsTime() local time=timer.getAbsTime()
local Cnow=UTILS.SecondsToClock(time)
-- Debug output: if self.intowind then
local text=string.format(self.lid.."Recovery time windows:")
-- Handle case with no recoveries. -- Check if time is over.
if #self.Qintowind==0 then if time>=self.intowind.Tstop then
text=text.." none!" self:TurnIntoWindOver(self.intowind)
end end
else
-- Get next window.
local IntoWind=self:GetTurnIntoWindNext()
-- Sort windows wrt to start time. -- Start turn into wind.
table.sort(self.Qintowind, function(a, b) return a.Tstart<b.Tstart end) if IntoWind then
self:TurnIntoWind(IntoWind)
-- Loop over all slots.
for _,_recovery in pairs(self.Qintowind) do
local recovery=_recovery --#NAVYGROUP.IntoWind
-- Get start/stop clock strings.
local Cstart=UTILS.SecondsToClock(recovery.Tstart)
local Cstop=UTILS.SecondsToClock(recovery.Tstop)
-- Debug text.
text=text..string.format("\n- Start=%s Stop=%s Open=%s Closed=%s", Cstart, Cstop, tostring(recovery.Open), tostring(recovery.Over))
end
-- Debug output.
self:T(self.lid..text)
-- Loop over all slots.
for _,_recovery in pairs(self.Qintowind) do
local recovery=_recovery --#NAVYGROUP.IntoWind
if time>=recovery.Tstart and time<recovery.Tstop and not recovery.Open then
self:TurnIntoWind(recovery)
break
end end
end end
-- If into wind, check if over.
if self.intowind then
if timer.getAbsTime()>=self.intowind.Tstop then
self:TurnIntoWindOver()
end
end
end end
--- Check queued turns into wind. --- Get the next turn into wind window, which is not yet running.
-- @param #NAVYGROUP self -- @param #NAVYGROUP self
-- @return #NAVYGROUP.IntoWind Next into wind data. -- @return #NAVYGROUP.IntoWind Next into wind data. Could be `nil` if there is not next window.
function NAVYGROUP:GetNextTurnIntoWind() function NAVYGROUP:GetTurnIntoWindNext()
-- Loop over all windows. if #self.Qintowind>0 then
for _,_recovery in pairs(self.Qintowind) do
local recovery=_recovery --#NAVYGROUP.IntoWind -- Get current abs time.
local time=timer.getAbsTime()
-- Sort windows wrt to start time.
table.sort(self.Qintowind, function(a, b) return a.Tstart<b.Tstart end)
-- Loop over all slots.
for _,_recovery in pairs(self.Qintowind) do
local recovery=_recovery --#NAVYGROUP.IntoWind
if time>=recovery.Tstart and time<recovery.Tstop and not (recovery.Open or recovery.Over) then
return recovery
end
end
end end
return nil
end
--- Get the turn into wind window, which is currently open.
-- @param #NAVYGROUP self
-- @return #NAVYGROUP.IntoWind Current into wind data. Could be `nil` if there is no window currenly open.
function NAVYGROUP:GetTurnIntoWindCurrent()
return self.intowind
end end
--- Get wind direction and speed at current position. --- Get wind direction and speed at current position.

File diff suppressed because it is too large Load Diff

View File

@ -2169,6 +2169,40 @@ function GROUP:GetThreatLevel()
return threatlevelMax return threatlevelMax
end end
--- Get the unit in the group with the highest threat level, which is still alive.
-- @param #GROUP self
-- @return Wrapper.Unit#UNIT The most dangerous unit in the group.
-- @return #number Threat level of the unit.
function GROUP:GetHighestThreat()
-- Get units of the group.
local units=self:GetUnits()
if units then
local threat=nil ; local maxtl=0
for _,_unit in pairs(units or {}) do
local unit=_unit --Wrapper.Unit#UNIT
if unit and unit:IsAlive() then
-- Threat level of group.
local tl=unit:GetThreatLevel()
-- Check if greater the current threat.
if tl>maxtl then
maxtl=tl
threat=unit
end
end
end
return threat, maxtl
end
return nil, nil
end
--- Returns true if the first unit of the GROUP is in the air. --- Returns true if the first unit of the GROUP is in the air.
-- @param Wrapper.Group#GROUP self -- @param Wrapper.Group#GROUP self

View File

@ -492,8 +492,7 @@ end
--- Returns the unit's group if it exist and nil otherwise. --- Returns the unit's group if it exist and nil otherwise.
-- @param Wrapper.Unit#UNIT self -- @param Wrapper.Unit#UNIT self
-- @return Wrapper.Group#GROUP The Group of the Unit. -- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist.
-- @return #nil The DCS Unit is not existing or alive.
function UNIT:GetGroup() function UNIT:GetGroup()
self:F2( self.UnitName ) self:F2( self.UnitName )