This commit is contained in:
Frank 2020-06-27 22:44:50 +02:00
parent 5c6e50e7f9
commit 682c1f5ef2
9 changed files with 14552 additions and 0 deletions

View File

@ -37,6 +37,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Client.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Airbase.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' )
__Moose.Include( 'Scripts/Moose/Wrapper/Marker.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' )
__Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' )
@ -69,6 +70,11 @@ __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' )
__Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' )
__Moose.Include( 'Scripts/Moose/Ops/ATIS.lua' )
__Moose.Include( 'Scripts/Moose/Ops/AirWing.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Auftrag.lua' )
__Moose.Include( 'Scripts/Moose/Ops/FlightGroup.lua' )
__Moose.Include( 'Scripts/Moose/Ops/NavyGroup.lua' )
__Moose.Include( 'Scripts/Moose/Ops/Squadron.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' )
__Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' )

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,852 @@
--- **Ops** - Enhanced Naval Group.
--
-- **Main Features:**
--
-- * Dynamically add and remove waypoints
-- * Let the group steam into the wind
-- * Command a full stop
-- * Let a submarine dive and surface
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.NavyGroup
-- @image OPS_NavyGroup.png
--- NAVYGROUP class.
-- @type NAVYGROUP
-- @field #boolean turning If true, group is currently turning.
-- @field #NAVYGROUP.IntoWind intowind Into wind info.
-- @extends Ops.OpsGroup#OPSGROUP
--- *Something must be left to chance; nothing is sure in a sea fight above all.* -- Horatio Nelson
--
-- ===
--
-- ![Banner Image](..\Presentations\NAVYGROUP\NavyGroup_Main.jpg)
--
-- # The NAVYGROUP Concept
--
-- This class enhances naval groups.
--
-- @field #NAVYGROUP
NAVYGROUP = {
ClassName = "NAVYGROUP",
verbose = 2,
turning = false,
intowind = nil,
}
--- Navy group element.
-- @type NAVYGROUP.Element
-- @field #string name Name of the element, i.e. the unit.
-- @field #string typename Type name.
--- Navy group element.
-- @type NAVYGROUP.IntoWind
-- @field #number Tstart Time to start.
-- @field #number Tstop Time to stop.
-- @field #boolean Uturn U-turn.
-- @field #number Speed Speed in knots.
--- NavyGroup version.
-- @field #string version
NAVYGROUP.version="0.0.1"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: Stop and resume route.
-- TODO: Add waypoints.
-- TODO: Add tasks.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new NAVYGROUP class object.
-- @param #NAVYGROUP self
-- @param #string GroupName Name of the group.
-- @return #NAVYGROUP self
function NAVYGROUP:New(GroupName)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, OPSGROUP:New(GroupName)) -- #NAVYGROUP
-- Set some string id for output to DCS.log file.
self.lid=string.format("NAVYGROUP %s | ", self.groupname)
-- Defaults
self:SetDefaultROE()
self:SetDetection()
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("*", "FullStop", "Holding") -- Hold position.
self:AddTransition("*", "Cruise", "Cruising") -- Hold position.
self:AddTransition("*", "TurnIntoWind", "*") -- Command the group to turn into the wind.
self:AddTransition("*", "TurningStarted", "*") -- Group started turning.
self:AddTransition("*", "TurningStopped", "*") -- Group stopped turning.
self:AddTransition("*", "Dive", "Diving") -- Command a submarine to dive.
self:AddTransition("Diving", "Surface", "Cruising") -- Command a submarine to go to the surface.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Stop". Stops the NAVYGROUP and all its event handlers.
-- @param #NAVYGROUP self
--- Triggers the FSM event "Stop" after a delay. Stops the NAVYGROUP and all its event handlers.
-- @function [parent=#NAVYGROUP] __Stop
-- @param #NAVYGROUP self
-- @param #number delay Delay in seconds.
-- TODO: Add pseudo functions.
-- Init waypoints.
self:InitWaypoints()
-- Initialize the group.
self:_InitGroup()
-- Debug trace.
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
-- Handle events:
self:HandleEvent(EVENTS.Birth, self.OnEventBirth)
self:HandleEvent(EVENTS.Dead, self.OnEventDead)
self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit)
-- Start the status monitoring.
self:__CheckZone(-1)
self:__Status(-2)
self:__QueueUpdate(-3)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add a *scheduled* task.
-- @param #NAVYGROUP self
-- @param Core.Point#COORDINATE Coordinate Coordinate of the target.
-- @param #number Radius Radius in meters. Default 100 m.
-- @param #number Nshots Number of shots to fire. Default 3.
-- @param #number WeaponType Type of weapon. Default auto.
-- @param #string Clock Time when to start the attack.
-- @param #number Prio Priority of the task.
function NAVYGROUP:AddTaskFireAtPoint(Coordinate, Radius, Nshots, WeaponType, Clock, Prio)
local DCStask=CONTROLLABLE.TaskFireAtPoint(nil, Coordinate:GetVec2(), Radius, Nshots, WeaponType)
self:AddTask(DCStask, Clock, nil, Prio)
end
--- Add a *scheduled* task.
-- @param #NAVYGROUP self
-- @param Wrapper.Group#GROUP TargetGroup Target group.
-- @param #number WeaponExpend How much weapons does are used.
-- @param #number WeaponType Type of weapon. Default auto.
-- @param #string Clock Time when to start the attack.
-- @param #number Prio Priority of the task.
function NAVYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clock, Prio)
local DCStask=CONTROLLABLE.TaskAttackGroup(nil, TargetGroup, WeaponType, WeaponExpend, AttackQty, Direction, Altitude, AttackQtyLimit, GroupAttack)
self:AddTask(DCStask, Clock, nil, Prio)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Update status.
-- @param #NAVYGROUP self
function NAVYGROUP:onafterStatus(From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
---
-- Detection
---
-- Check if group has detected any units.
if self.detectionOn then
self:_CheckDetectedUnits()
end
-- Current heading and position of the carrier.
local hdg=self:GetHeading()
local pos=self:GetCoordinate()
local speed=self.group:GetVelocityKNOTS()
-- Check if group started or stopped turning.
self:_CheckTurning()
-- Check water is ahead.
local collision=self:_CheckCollisionCoord(pos:Translate(self.collisiondist or 5000, hdg))
local intowind=false
if self.intowind then
if timer.getAbsTime()>=self.intowind.Tstop then
if self.intowind.Uturn then
self:UpdateRoute(self.currentwp)
else
self:UpdateRoute()
end
self.intowind=nil
else
intowind=true
end
end
-- Get number of tasks and missions.
local nTaskTot, nTaskSched, nTaskWP=self:CountRemainingTasks()
local nMissions=self:CountRemainingMissison()
-- Info text.
local text=string.format("State %s: Speed=%.1f knots Heading=%03d intowind=%s turning=%s collision=%s Tasks=%d Missions=%d", fsmstate, speed, hdg, tostring(intowind), tostring(self.turning), tostring(collision), nTaskTot, nMissions)
self:I(self.lid..text)
---
-- Tasks
---
-- Task queue.
if #self.taskqueue>0 and self.verbose>1 then
local text=string.format("Tasks #%d", #self.taskqueue)
for i,_task in pairs(self.taskqueue) do
local task=_task --Ops.OpsGroup#OPSGROUP.Task
local name=task.description
local taskid=task.dcstask.id or "unknown"
local status=task.status
local clock=UTILS.SecondsToClock(task.time, true)
local eta=task.time-timer.getAbsTime()
local started=task.timestamp and UTILS.SecondsToClock(task.timestamp, true) or "N/A"
local duration=-1
if task.duration then
duration=task.duration
if task.timestamp then
-- Time the task is running.
duration=task.duration-(timer.getAbsTime()-task.timestamp)
else
-- Time the task is supposed to run.
duration=task.duration
end
end
-- Output text for element.
if task.type==OPSGROUP.TaskType.SCHEDULED then
text=text..string.format("\n[%d] %s (%s): status=%s, scheduled=%s (%d sec), started=%s, duration=%d", i, taskid, name, status, clock, eta, started, duration)
elseif task.type==OPSGROUP.TaskType.WAYPOINT then
text=text..string.format("\n[%d] %s (%s): status=%s, waypoint=%d, started=%s, duration=%d, stopflag=%d", i, taskid, name, status, task.waypoint, started, duration, task.stopflag:Get())
end
end
self:I(self.lid..text)
end
---
-- Missions
---
-- Current mission name.
if self.verbose>0 then
local Mission=self:GetMissionByID(self.currentmission)
-- Current status.
local text=string.format("Missions %d, Current: %s", self:CountRemainingMissison(), Mission and Mission.name or "none")
for i,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
local Cstart= UTILS.SecondsToClock(mission.Tstart, true)
local Cstop = mission.Tstop and UTILS.SecondsToClock(mission.Tstop, true) or "INF"
text=text..string.format("\n[%d] %s (%s) status=%s (%s), Time=%s-%s, prio=%d wp=%s targets=%d",
i, tostring(mission.name), mission.type, mission:GetGroupStatus(self), tostring(mission.status), Cstart, Cstop, mission.prio, tostring(mission:GetGroupWaypointIndex(self)), mission:CountMissionTargets())
end
self:I(self.lid..text)
end
-- Next check in ~30 seconds.
if not self:IsStopped() then
self:__Status(-10)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Events
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "ElementSpawned" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #NAVYGROUP.Element Element The group element.
function NAVYGROUP:onafterElementSpawned(From, Event, To, Element)
self:I(self.lid..string.format("Element spawned %s", Element.name))
-- Set element status.
self:_UpdateStatus(Element, OPSGROUP.ElementStatus.SPAWNED)
end
--- On after "Spawned" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function NAVYGROUP:onafterSpawned(From, Event, To)
self:I(self.lid..string.format("Group spawned!"))
if self.ai then
-- Set default ROE and ROT options.
self:SetOptionROE(self.roe)
end
-- Get orientation.
self.Corientlast=self.group:GetUnit(1):GetOrientationX()
-- Update route.
self:__Cruise(-1)
end
--- On after "UpdateRoute" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number n Waypoint number. Default is next waypoint.
-- @param #number Speed Speed in knots. Default cruise speed.
-- @param #number Depth Depth in meters. Default 0 meters.
function NAVYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Depth)
-- Update route from this waypoint number onwards.
n=n or self.currentwp+1
-- Update waypoint tasks, i.e. inject WP tasks into waypoint table.
self:_UpdateWaypointTasks()
-- Waypoints.
local waypoints={}
-- Speed.
local speed=Speed and UTILS.KnotsToKmph(Speed) or self.speedCruise
-- Depth for submarines.
local depth=Depth or 0
local current=self:GetCoordinate():WaypointNaval(speed, depth)
table.insert(waypoints, current)
-- Add remaining waypoints to route.
for i=n, #self.waypoints do
local wp=self.waypoints[i]
-- Set speed.
wp.speed=UTILS.KmphToMps(speed)
wp.alt=-depth --Depth and -Depth or wp.alt
-- Add waypoint.
table.insert(waypoints, wp)
end
if #waypoints>1 then
self:I(self.lid..string.format("Updateing route: WP=%d, Speed=%.1f knots, depth=%d meters", #self.waypoints-n+1, UTILS.KmphToKnots(speed), depth))
-- Route group to all defined waypoints remaining.
self:Route(waypoints)
else
---
-- No waypoints left
---
self:I(self.lid..string.format("No waypoints left"))
-- TODO: Switch to waypoint 1
--self:UpdateRoute(1)
end
end
--- On after "TurnIntoWind" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number Duration Duration in seconds.
-- @param #number Speed Speed in knots.
-- @param #boolean Uturn Return to the place we came from.
function NAVYGROUP:onafterTurnIntoWind(From, Event, To, Duration, Speed, Uturn)
local headingTo=self:GetCoordinate():GetWind(50)
local intowind={} --#NAVYGROUP.IntoWind
intowind.Speed=Speed
intowind.Tstart=timer.getAbsTime()
intowind.Tstop=intowind.Tstart+Duration
intowind.Uturn=Uturn or false
intowind.Heading=headingTo
self.intowind=intowind
self:I(self.lid..string.format("Steaming into wind: Heading=%03d Speed=%.1f knots, Tstart=%d Tstop=%d", intowind.Heading, intowind.Speed, intowind.Tstart, intowind.Tstop))
local distance=UTILS.NMToMeters(1000)
local wp={}
local coord=self:GetCoordinate()
local Coord=coord:Translate(distance, headingTo)
wp[1]=coord:WaypointNaval(Speed)
wp[2]=Coord:WaypointNaval(Speed)
self:Route(wp)
end
--- On after "FullStop" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function NAVYGROUP:onafterFullStop(From, Event, To)
-- Get current position.
local pos=self:GetCoordinate()
-- Create a new waypoint.
local wp=pos:WaypointNaval(0)
-- Create new route consisting of only this position ==> Stop!
self:Route({wp})
end
--- On after "Cruise" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function NAVYGROUP:onafterCruise(From, Event, To)
self:UpdateRoute()
end
--- On after "Dive" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number Depth Dive depth in meters.
function NAVYGROUP:onafterDive(From, Event, To, Depth)
env.info("FF Diving")
self:UpdateRoute(nil, nil, Depth)
end
--- On after "Surface" event.
-- @param #NAVYGROUP self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #number Depth Dive depth in meters.
function NAVYGROUP:onafterSurface(From, Event, To)
self:UpdateRoute(nil, nil, 0)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Events DCS
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Event function handling the birth of a unit.
-- @param #NAVYGROUP self
-- @param Core.Event#EVENTDATA EventData Event data.
function NAVYGROUP:OnEventBirth(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
if self.respawning then
local function reset()
self.respawning=nil
end
-- Reset switch in 1 sec. This should allow all birth events of n>1 groups to have passed.
-- TODO: Can I do this more rigorously?
self:ScheduleOnce(1, reset)
else
-- Get element.
local element=self:GetElementByName(unitname)
-- Set element to spawned state.
self:T3(self.lid..string.format("EVENT: Element %s born ==> spawned", element.name))
self:ElementSpawned(element)
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Routing
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Add an a waypoint to the route.
-- @param #NAVYGROUP self
-- @param Core.Point#COORDINATE coordinate The coordinate of the waypoint. Use COORDINATE:SetAltitude(altitude) to define the altitude.
-- @param #number wpnumber Waypoint number. Default at the end.
-- @param #number speed Speed in knots. Default 11 kts.
-- @param #boolean updateroute If true or nil, call UpdateRoute. If false, no call.
-- @return #number Waypoint index.
function NAVYGROUP:AddWaypoint(coordinate, wpnumber, speed, updateroute)
-- Waypoint number. Default is at the end.
wpnumber=wpnumber or #self.waypoints+1
if wpnumber>self.currentwp then
self.passedfinalwp=false
end
-- Speed in knots.
speed=speed or 11
-- Speed at waypoint.
local speedkmh=UTILS.KnotsToKmph(speed)
-- Create a Naval waypoint.
local wp=coordinate:WaypointNaval(speedkmh)
-- Add to table.
table.insert(self.waypoints, wpnumber, wp)
-- Debug info.
self:T(self.lid..string.format("Adding NAVAL waypoint #%d, speed=%.1f knots. Last waypoint passed was #%s. Total waypoints #%d", wpnumber, speed, self.currentwp, #self.waypoints))
-- Shift all waypoint tasks after the inserted waypoint.
for _,_task in pairs(self.taskqueue) do
local task=_task --Ops.OpsGroup#OPSGROUP.Task
if task.type==OPSGROUP.TaskType.WAYPOINT and task.waypoint and task.waypoint>=wpnumber then
task.waypoint=task.waypoint+1
end
end
-- Shift all mission waypoints after the inserted waypoint.
for _,_mission in pairs(self.missionqueue) do
local mission=_mission --Ops.Auftrag#AUFTRAG
-- Get mission waypoint index.
local wpidx=mission:GetGroupWaypointIndex(self)
-- Increase number if this waypoint lies in the future.
if wpidx and wpidx>=wpnumber then
mission:SetGroupWaypointIndex(self, wpidx+1)
end
end
-- Update route.
if updateroute==nil or updateroute==true then
self:_CheckGroupDone(1)
end
return wpnumber
end
--- Initialize group parameters. Also initializes waypoints if self.waypoints is nil.
-- @param #NAVYGROUP self
-- @return #NAVYGROUP self
function NAVYGROUP:_InitGroup()
-- First check if group was already initialized.
if self.groupinitialized then
self:E(self.lid.."WARNING: Group was already initialized!")
return
end
-- Get template of group.
self.template=self.group:GetTemplate()
-- Define category.
self.isAircraft=false
self.isNaval=true
self.isGround=false
-- Helo group.
--self.isSubmarine=self.group:IsSubmarine()
-- Ships are always AI.
self.ai=true
-- Is (template) group late activated.
self.isLateActivated=self.template.lateActivation
-- Naval groups cannot be uncontrolled.
self.isUncontrolled=false
-- Max speed in km/h.
self.speedmax=self.group:GetSpeedMax()
-- Cruise speed: 70% of max speed but within limit.
self.speedCruise=self.speedmax*0.7
-- Group ammo.
--self.ammo=self:GetAmmoTot()
self.traveldist=0
self.traveltime=timer.getAbsTime()
self.position=self:GetCoordinate()
-- Radio parameters from template.
self.radioOn=true -- Radio is always on for ships.
self.radioFreq=tonumber(self.template.units[1].frequency)/1000000
self.radioModu=tonumber(self.template.units[1].modulation)/1000000
-- If not set by the use explicitly yet, we take the template values as defaults.
if not self.radioFreqDefault then
self.radioFreqDefault=self.radioFreq
self.radioModuDefault=self.radioModu
end
-- Set default formation.
if not self.formationDefault then
if self.ishelo then
self.formationDefault=ENUMS.Formation.RotaryWing.EchelonLeft.D300
else
self.formationDefault=ENUMS.Formation.FixedWing.EchelonLeft.Group
end
end
local units=self.group:GetUnits()
for _,_unit in pairs(units) do
local unit=_unit --Wrapper.Unit#UNIT
local element={} --#NAVYGROUP.Element
element.name=unit:GetName()
element.typename=unit:GetTypeName()
element.status=OPSGROUP.ElementStatus.INUTERO
table.insert(self.elements, element)
self:GetAmmoUnit(unit, true)
if unit:IsAlive() then
self:ElementSpawned(element)
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.
local text=string.format("Initialized Navy Group %s:\n", self.groupname)
text=text..string.format("AC 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.radioFreq, UTILS.GetModulationName(self.radioModu), tostring(self.radioOn))
--text=text..string.format("Ammo = %d (G=%d/R=%d/B=%d/M=%d)\n", self.ammo.Total, self.ammo.Guns, self.ammo.Rockets, self.ammo.Bombs, self.ammo.Missiles)
text=text..string.format("FSM state = %s\n", self:GetState())
text=text..string.format("Is alive = %s\n", tostring(self.group:IsAlive()))
text=text..string.format("LateActivate = %s\n", tostring(self:IsLateActivated()))
self:I(self.lid..text)
-- Init done.
self.groupinitialized=true
end
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check for possible collisions between two coordinates.
-- @param #NAVYGROUP self
-- @param Core.Point#COORDINATE coordto Coordinate to which the collision is check.
-- @param Core.Point#COORDINATE coordfrom Coordinate from which the collision is check.
-- @return #boolean If true, surface type ahead is not deep water.
-- @return #number Max free distance in meters.
function NAVYGROUP:_CheckCollisionCoord(coordto, coordfrom)
-- Increment in meters.
local dx=100
-- From coordinate. Default 500 in front of the carrier.
local d=0
if coordfrom then
d=0
else
d=250
coordfrom=self:GetCoordinate():Translate(d, self:GetHeading())
end
-- Distance between the two coordinates.
local dmax=coordfrom:Get2DDistance(coordto)
-- Direction.
local direction=coordfrom:HeadingTo(coordto)
-- Scan path between the two coordinates.
local clear=true
while d<=dmax do
-- Check point.
local cp=coordfrom:Translate(d, direction)
-- Check if surface type is water.
if not cp:IsSurfaceTypeWater() then
-- Debug mark points.
if self.Debug or true then
local st=cp:GetSurfaceType()
cp:MarkToAll(string.format("Collision check surface type %d", st))
end
-- Collision WARNING!
clear=false
break
end
-- Increase distance.
d=d+dx
end
local text=""
if clear then
text=string.format("Path into direction %03d° is clear for the next %.1f NM.", direction, UTILS.MetersToNM(d))
else
text=string.format("Detected obstacle at distance %.1f NM into direction %03d°.", UTILS.MetersToNM(d), direction)
end
self:T(self.lid..text)
return not clear, d
end
--- Check if group is turning.
-- @param #NAVYGROUP self
function NAVYGROUP:_CheckTurning()
-- Current orientation of carrier.
local vNew=self.group:GetUnit(1):GetOrientationX()
-- Last orientation from 30 seconds ago.
local vLast=self.Corientlast or vNew
-- We only need the X-Z plane.
vNew.y=0 ; vLast.y=0
-- Angle between current heading and last time we checked ~30 seconds ago.
local deltaLast=math.deg(math.acos(UTILS.VecDot(vNew,vLast)/UTILS.VecNorm(vNew)/UTILS.VecNorm(vLast)))
-- Last orientation becomes new orientation
self.Corientlast=vNew
-- Carrier is turning when its heading changed by at least two degrees since last check.
local turning=math.abs(deltaLast)>=2
-- Check if turning stopped.
if self.turning and not turning then
-- Carrier was turning but is not any more.
self:TurningStopped()
elseif turning and not self.turning then
-- Carrier was not turning but is now.
self:TurningStarted()
end
-- Update turning.
self.turning=turning
end
--- Check if group is done, i.e.
--
-- * passed the final waypoint,
-- * no current task
-- * no current mission
-- * number of remaining tasks is zero
-- * number of remaining missions is zero
--
-- @param #NAVYGROUP self
-- @param #number delay Delay in seconds.
function NAVYGROUP:_CheckGroupDone(delay)
if self:IsAlive() and self.ai then
if delay and delay>0 then
-- Delayed call.
self:ScheduleOnce(delay, NAVYGROUP._CheckGroupDone, self)
else
if not self.passedfinalwp then
self:UpdateRoute()
end
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,792 @@
--- **Ops** - Airwing Squadron.
--
-- **Main Features:**
--
-- * Set parameters like livery, skill valid for all squadron members.
-- * Define modex and callsigns.
-- * Define mission types, this squadron can perform (see Ops.Auftrag#AUFTRAG).
-- * Pause/unpause squadron operations.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Ops.Squadron
-- @image OPS_Squadron.png
--- SQUADRON class.
-- @type SQUADRON
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #string name Name of the squadron.
-- @field #string templatename Name of the template group.
-- @field #string aircrafttype Type of the airframe the squadron is using.
-- @field Wrapper.Group#GROUP templategroup Template group.
-- @field #table assets Squadron assets.
-- @field #table missiontypes Capabilities (mission types and performances) of the squadron.
-- @field #string livery Livery of the squadron.
-- @field #number skill Skill of squadron members.
-- @field #number modex Modex.
-- @field #number modexcounter Counter to incease modex number for assets.
-- @field #string callsignName Callsign name.
-- @field #number callsigncounter Counter to increase callsign names for new assets.
-- @field Ops.AirWing#AIRWING airwing The AIRWING object the squadron belongs to.
-- @field #number Ngroups Number of asset flight groups this squadron has.
-- @field #number engageRange Engagement range in meters.
-- @field #string attribute Generalized attribute of the squadron template group.
-- @field #number tankerSystem For tanker squads, the refuel system used (boom=0 or probpe=1). Default nil.
-- @field #number refuelSystem For refuelable squads, the refuel system used (boom=0 or probpe=1). Default nil.
-- @field #number TACANmin TACAN min channel.
-- @field #number TACANmax TACAN max channel.
-- @field #table TACANused Table of used TACAN channels.
-- @field #number radioFreq Radio frequency in MHz the squad uses.
-- @field #number radioModu Radio modulation the squad uses.
-- @extends Core.Fsm#FSM
--- *It is unbelievable what a squadron of twelve aircraft did to tip the balance.* -- Adolf Galland
--
-- ===
--
-- ![Banner Image](..\Presentations\Squadron\SQUADRON_Main.jpg)
--
-- # The SQUADRON Concept
--
-- A SQUADRON is essential part of an AIRWING and consists of **one** type of aircraft.
--
--
--
-- @field #SQUADRON
SQUADRON = {
ClassName = "SQUADRON",
Debug = nil,
lid = nil,
name = nil,
templatename = nil,
aircrafttype = nil,
assets = {},
missiontypes = {},
livery = nil,
skill = nil,
modex = nil,
modexcounter = 0,
callsignName = nil,
callsigncounter= 11,
airwing = nil,
Ngroups = nil,
engageRange = nil,
tankerSystem = nil,
refuelSystem = nil,
TACANmin = nil,
TACANmax = nil,
TACANused = {},
}
--- SQUADRON class version.
-- @field #string version
SQUADRON.version="0.0.7"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- DONE: Engage radius.
-- DONE: Modex.
-- DONE: Call signs.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new SQUADRON object and start the FSM.
-- @param #SQUADRON self
-- @param #string TemplateGroupName Name of the template group.
-- @param #number Ngroups Number of asset groups of this squadron. Default 3.
-- @param #string SquadronName Name of the squadron, e.g. "VFA-37".
-- @return #SQUADRON self
function SQUADRON:New(TemplateGroupName, Ngroups, SquadronName)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #SQUADRON
-- Name of the template group.
self.templatename=TemplateGroupName
-- Squadron name.
self.name=tostring(SquadronName or TemplateGroupName)
-- Set some string id for output to DCS.log file.
self.lid=string.format("SQUADRON %s | ", self.name)
-- Template group.
self.templategroup=GROUP:FindByName(self.templatename)
-- Check if template group exists.
if not self.templategroup then
self:E(self.lid..string.format("ERROR: Template group %s does not exist!", tostring(self.templatename)))
return nil
end
-- Defaults.
self.Ngroups=Ngroups or 3
self:SetEngagementRange()
-- Everyone can ORBIT.
self:AddMissonCapability(AUFTRAG.Type.ORBIT)
self.attribute=self.templategroup:GetAttribute()
self.aircrafttype=self.templategroup:GetTypeName()
self.refuelSystem=select(2, self.templategroup:GetUnit(1):IsRefuelable())
self.tankerSystem=select(2, self.templategroup:GetUnit(1):IsTanker())
-- Start State.
self:SetStartState("Stopped")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Stopped", "Start", "OnDuty") -- Start FSM.
self:AddTransition("*", "Status", "*") -- Status update.
self:AddTransition("OnDuty", "Pause", "Paused") -- Pause squadron.
self:AddTransition("Paused", "Unpause", "OnDuty") -- Unpause squadron.
self:AddTransition("*", "Stop", "Stopped") -- Stop squadron.
------------------------
--- Pseudo Functions ---
------------------------
--- Triggers the FSM event "Start". Starts the SQUADRON. Initializes parameters and starts event handlers.
-- @function [parent=#SQUADRON] Start
-- @param #SQUADRON self
--- Triggers the FSM event "Start" after a delay. Starts the SQUADRON. Initializes parameters and starts event handlers.
-- @function [parent=#SQUADRON] __Start
-- @param #SQUADRON self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Stop". Stops the SQUADRON and all its event handlers.
-- @param #SQUADRON self
--- Triggers the FSM event "Stop" after a delay. Stops the SQUADRON and all its event handlers.
-- @function [parent=#SQUADRON] __Stop
-- @param #SQUADRON self
-- @param #number delay Delay in seconds.
--- Triggers the FSM event "Status".
-- @function [parent=#SQUADRON] Status
-- @param #SQUADRON self
--- Triggers the FSM event "Status" after a delay.
-- @function [parent=#SQUADRON] __Status
-- @param #SQUADRON self
-- @param #number delay Delay in seconds.
-- Debug trace.
if false then
self.Debug=true
BASE:TraceOnOff(true)
BASE:TraceClass(self.ClassName)
BASE:TraceLevel(1)
end
self.Debug=true
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Set livery painted on all squadron aircraft.
-- Note that the livery name in general is different from the name shown in the mission editor.
--
-- Valid names are the names of the **livery directories**. Check out the folder in your DCS installation for:
--
-- * Full modules: `DCS World OpenBeta\CoreMods\aircraft\<Aircraft Type>\Liveries\<Aircraft Type>\<Livery Name>`
-- * AI units: `DCS World OpenBeta\Bazar\Liveries\<Aircraft Type>\<Livery Name>`
--
-- The folder name `<Livery Name>` is the string you want.
--
-- Or personal liveries you have installed somewhere in your saved games folder.
--
-- @param #SQUADRON self
-- @param #string LiveryName Name of the livery.
-- @return #SQUADRON self
function SQUADRON:SetLivery(LiveryName)
self.livery=LiveryName
return self
end
--- Set skill level of all squadron team members.
-- @param #SQUADRON self
-- @param #string Skill Skill of all flights.
-- @usage mysquadron:SetSkill(AI.Skill.EXCELLENT)
-- @return #SQUADRON self
function SQUADRON:SetSkill(Skill)
self.skill=Skill
return self
end
--- Set radio frequency and modulation the squad uses.
-- @param #SQUADRON self
-- @param #number Frequency Radio frequency in MHz. Default 251 MHz.
-- @param #number Modulation Radio modulation. Default 0=AM.
-- @usage mysquadron:SetSkill(AI.Skill.EXCELLENT)
-- @return #SQUADRON self
function SQUADRON:SetRadio(Frequency, Modulation)
self.radioFreq=Frequency or 251
self.radioModu=Modulation or radio.modulation.AM
return self
end
--- Set mission types this squadron is able to perform.
-- @param #SQUADRON self
-- @param #table MissionTypes Table of mission types. Can also be passed as a #string if only one type.
-- @param #number Performance Performance describing how good this mission can be performed. Higher is better. Default 50. Max 100.
-- @return #SQUADRON self
function SQUADRON:AddMissonCapability(MissionTypes, Performance)
-- Ensure Missiontypes is a table.
if MissionTypes and type(MissionTypes)~="table" then
MissionTypes={MissionTypes}
end
-- Set table.
self.missiontypes=self.missiontypes or {}
for _,missiontype in pairs(MissionTypes) do
-- Check not to add the same twice.
if self:CheckMissionCapability(missiontype, self.missiontypes) then
self:E(self.lid.."WARNING: Mission capability already present! No need to add it twice.")
-- TODO: update performance.
else
local capability={} --Ops.Auftrag#AUFTRAG.Capability
capability.MissionType=missiontype
capability.Performance=Performance or 50
table.insert(self.missiontypes, capability)
end
end
-- Debug info.
self:I(self.missiontypes)
return self
end
--- Get mission types this squadron is able to perform.
-- @param #SQUADRON self
-- @return #table Table of mission types. Could be empty {}.
function SQUADRON:GetMissionTypes()
local missiontypes={}
for _,Capability in pairs(self.missiontypes) do
local capability=Capability --Ops.Auftrag#AUFTRAG.Capability
table.insert(missiontypes, capability.MissionType)
end
return missiontypes
end
--- Get mission capabilities of this squadron.
-- @param #SQUADRON self
-- @return #table Table of mission capabilities.
function SQUADRON:GetMissionCapabilities()
return self.missiontypes
end
--- Get mission performance for a given type of misson.
-- @param #SQUADRON self
-- @param #string MissionType Type of mission.
-- @return #number Performance or -1.
function SQUADRON:GetMissionPeformance(MissionType)
for _,Capability in pairs(self.missiontypes) do
local capability=Capability --Ops.Auftrag#AUFTRAG.Capability
if capability.MissionType==MissionType then
return capability.Performance
end
end
return -1
end
--- Set max engagement range.
-- @param #SQUADRON self
-- @param #number EngageRange Engagement range in NM. Default 80 NM.
-- @return #SQUADRON self
function SQUADRON:SetEngagementRange(EngageRange)
self.engageRange=UTILS.NMToMeters(EngageRange or 80)
return self
end
--- Set call sign.
-- @param #SQUADRON self
-- @param #number Callsign Callsign from CALLSIGN.Aircraft, e.g. "Chevy" for CALLSIGN.Aircraft.CHEVY.
-- @param #number Index Callsign index, Chevy-**1**.
-- @return #SQUADRON self
function SQUADRON:SetCallsign(Callsign, Index)
self.callsignName=Callsign
self.callsignIndex=Index
return self
end
--- Set modex.
-- @param #SQUADRON self
-- @param #number Modex A number like 100.
-- @param #string Prefix A prefix string, which is put before the `Modex` number.
-- @param #string Suffix A suffix string, which is put after the `Modex` number.
-- @return #SQUADRON self
function SQUADRON:SetModex(Modex, Prefix, Suffix)
self.modex=Modex
self.modexPrefix=Prefix
self.modexSuffix=Suffix
return self
end
--- Set airwing.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING Airwing The airwing.
-- @return #SQUADRON self
function SQUADRON:SetAirwing(Airwing)
self.airwing=Airwing
return self
end
--- Add airwing asset to squadron.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset.
-- @return #SQUADRON self
function SQUADRON:AddAsset(Asset)
self:T(self.lid..string.format("Adding asset %s of type %s", Asset.spawngroupname, Asset.unittype))
Asset.squadname=self.name
table.insert(self.assets, Asset)
return self
end
--- Remove airwing asset from squadron.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset.
-- @return #SQUADRON self
function SQUADRON:DelAsset(Asset)
for i,_asset in pairs(self.assets) do
local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset
if Asset.uid==asset.uid then
self:T2(self.lid..string.format("Removing asset %s", asset.spawngroupname))
table.remove(self.assets, i)
break
end
end
return self
end
--- Get radio frequency and modulation.
-- @param #SQUADRON self
-- @return #number Radio frequency in MHz.
-- @return #number Radio Modulation (0=AM, 1=FM).
function SQUADRON:GetRadio()
return self.radioFreq, self.radioModu
end
--- Create a callsign for the asset.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset.
-- @return #SQUADRON self
function SQUADRON:GetCallsign(Asset)
if self.callsignName then
Asset.callsign={}
for i=1,Asset.nunits do
local callsign={}
callsign[1]=self.callsignName
callsign[2]=math.floor(self.callsigncounter / 10)
callsign[3]=self.callsigncounter % 10
if callsign[3]==0 then
callsign[3]=1
self.callsigncounter=self.callsigncounter+2
else
self.callsigncounter=self.callsigncounter+1
end
Asset.callsign[i]=callsign
self:T3({callsign=callsign})
--TODO: there is also a table entry .name, which is a string.
end
end
end
--- Create a modex for the asset.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset.
-- @return #SQUADRON self
function SQUADRON:GetModex(Asset)
if self.modex then
Asset.modex={}
for i=1,Asset.nunits do
Asset.modex[i]=string.format("%03d", self.modex+self.modexcounter)
self.modexcounter=self.modexcounter+1
self:T3({modex=Asset.modex[i]})
end
end
end
--- Get an unused TACAN channel.
-- @param #SQUADRON self
-- @param Ops.AirWing#AIRWING.SquadronAsset Asset The airwing asset.
-- @return #number TACAN channel or *nil* if no channel is free.
function SQUADRON:GetTACAN()
if self.TACANmin and self.TACANmax then
for channel=self.TACANmin, self.TACANmax do
if not self.TACANused[channel] then
self.TACANused[channel]=true
return channel
end
end
end
return nil
end
--- "Return" a used TACAN channel.
-- @param #SQUADRON self
-- @param #number channel The channel that is available again.
function SQUADRON:ReturnTACAN(channel)
self.TACANused[channel]=false
end
--- Check if squadron is "OnDuty".
-- @param #SQUADRON self
-- @return #boolean If true, squdron is in state "OnDuty".
function SQUADRON:IsOnDuty()
return self:Is("OnDuty")
end
--- Check if squadron is "Stopped".
-- @param #SQUADRON self
-- @return #boolean If true, squdron is in state "Stopped".
function SQUADRON:IsStopped()
return self:Is("Stopped")
end
--- Check if squadron is "Paused".
-- @param #SQUADRON self
-- @return #boolean If true, squdron is in state "Paused".
function SQUADRON:IsPaused()
return self:Is("Paused")
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Start & Status
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after Start event. Starts the FLIGHTGROUP FSM and event handlers.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStart(From, Event, To)
-- Short info.
local text=string.format("Starting SQUADRON", self.name)
self:I(self.lid..text)
-- Start the status monitoring.
self:__Status(-1)
end
--- On after "Status" event.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStatus(From, Event, To)
-- FSM state.
local fsmstate=self:GetState()
-- Check if group has detected any units.
--self:_CheckAssetStatus()
-- Short info.
local text=string.format("Status %s: Assets %d", fsmstate, #self.assets)
self:I(self.lid..text)
if not self:IsStopped() then
self:__Status(-30)
end
end
--- Check asset status.
-- @param #SQUADRON self
function SQUADRON:_CheckAssetStatus()
for _,_asset in pairs(self.assets) do
local asset=_asset
end
end
--- On after "Stop" event.
-- @param #SQUADRON self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
function SQUADRON:onafterStop(From, Event, To)
self:I(self.lid.."STOPPING Squadron!")
-- Remove all assets.
for i=#self.assets,1,-1 do
local asset=self.assets[i]
self:DelAsset(asset)
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Misc Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Check if there is a squadron that can execute a given mission.
-- We check the mission type, the refuelling system, engagement range
-- @param #SQUADRON self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return #boolean If true, Squadron can do that type of mission.
function SQUADRON:CanMission(Mission)
local cando=true
-- On duty?=
if not self:IsOnDuty() then
self:I(self.lid..string.format("Squad in not OnDuty but in state %s. Cannot do mission %s with target %s", self:GetState(), Mission.name, Mission:GetTargetName()))
return false
end
-- Check mission type. WARNING: This assumes that all assets of the squad can do the same mission types!
if not self:CheckMissionType(Mission.type, self:GetMissionTypes()) then
self:I(self.lid..string.format("INFO: Squad cannot do mission type %s (%s, %s)", Mission.type, Mission.name, Mission:GetTargetName()))
return false
end
-- Check that tanker mission
if Mission.type==AUFTRAG.Type.TANKER then
if Mission.refuelSystem and Mission.refuelSystem==self.tankerSystem then
-- Correct refueling system.
else
self:I(self.lid..string.format("INFO: Wrong refueling system requested=%s != %s=available", tostring(Mission.refuelSystem), tostring(self.tankerSystem)))
return false
end
end
-- Distance to target.
local TargetDistance=Mission:GetTargetDistance(self.airwing:GetCoordinate())
-- Max engage range.
local engagerange=Mission.engageRange and math.max(self.engageRange, Mission.engageRange) or self.engageRange
-- Set range is valid. Mission engage distance can overrule the squad engage range.
if TargetDistance>engagerange then
self:I(self.lid..string.format("INFO: Squad is not in range. Target dist=%d > %d NM max engage Range", UTILS.MetersToNM(TargetDistance), UTILS.MetersToNM(engagerange)))
return false
end
return true
end
--- Get assets for a mission.
-- @param #SQUADRON self
-- @return #number Assets not spawned.
function SQUADRON:CountAssetsInStock()
local N=0
for _,_asset in pairs(self.assets) do
local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset
if asset.spawned then
else
N=N+1
end
end
return N
end
--- Get assets for a mission.
-- @param #SQUADRON self
-- @param Ops.Auftrag#AUFTRAG Mission The mission.
-- @return #table Assets that can do the required mission.
function SQUADRON:RecruitAssets(Mission)
-- Number of payloads available.
local Npayloads=self.airwing:CountPayloadsInStock(Mission.type, self.aircrafttype)
local assets={}
-- Loop over assets.
for _,_asset in pairs(self.assets) do
local asset=_asset --Ops.AirWing#AIRWING.SquadronAsset
-- Check if asset is currently on a mission (STARTED or QUEUED).
if self.airwing:IsAssetOnMission(asset) then
---
-- Asset is already on a mission.
---
-- Check if this asset is currently on a PATROL mission (STARTED or EXECUTING).
if self.airwing:IsAssetOnMission(asset, AUFTRAG.Type.PATROL) and Mission.type==AUFTRAG.Type.INTERCEPT then
-- Check if the payload of this asset is compatible with the mission.
-- Note: we do not check the payload as an asset that is on a PATROL mission should be able to do an INTERCEPT as well!
self:I(self.lid.."Adding asset on PATROL mission for an INTERCEPT mission")
table.insert(assets, asset)
end
else
---
-- Asset as no current mission
---
if asset.spawned then
---
-- Asset is already SPAWNED (could be uncontrolled on the airfield or inbound after another mission)
---
local flightgroup=asset.flightgroup
-- Firstly, check if it has the right payload.
if self:CheckMissionCapability(Mission.type, asset.payload.capabilities) and flightgroup and flightgroup:IsAlive() then
-- Assume we are ready and check if any condition tells us we are not.
local combatready=true
if Mission.type==AUFTRAG.Type.INTERCEPT then
combatready=flightgroup:CanAirToAir()
else
combatready=flightgroup:CanAirToGround()
end
-- No more attacks if fuel is already low. Safety first!
if flightgroup:IsFuelLow() then
combatready=false
end
-- Check if in a state where we really do not want to fight any more.
if flightgroup:IsLanding() or flightgroup:IsLanded() or flightgroup:IsArrived() or flightgroup:IsDead() then
combatready=false
end
-- This asset is "combatready".
if combatready then
self:I(self.lid.."Adding SPAWNED asset to ANOTHER mission as it is COMBATREADY")
table.insert(assets, asset)
end
end
else
---
-- Asset is still in STOCK
---
-- Check that asset is not already requested for another mission.
if Npayloads>0 and not asset.requested then
-- Add this asset to the selection.
table.insert(assets, asset)
-- Reduce number of payloads so we only return the number of assets that could do the job.
Npayloads=Npayloads-1
end
end
end
end -- loop over assets
return assets
end
--- Checks if a mission type is contained in a table of possible types.
-- @param #SQUADRON self
-- @param #string MissionType The requested mission type.
-- @param #table PossibleTypes A table with possible mission types.
-- @return #boolean If true, the requested mission type is part of the possible mission types.
function SQUADRON:CheckMissionType(MissionType, PossibleTypes)
if type(PossibleTypes)=="string" then
PossibleTypes={PossibleTypes}
end
for _,canmission in pairs(PossibleTypes) do
if canmission==MissionType then
return true
end
end
return false
end
--- Check if a mission type is contained in a list of possible capabilities.
-- @param #SQUADRON self
-- @param #string MissionType The requested mission type.
-- @param #table Capabilities A table with possible capabilities.
-- @return #boolean If true, the requested mission type is part of the possible mission types.
function SQUADRON:CheckMissionCapability(MissionType, Capabilities)
for _,cap in pairs(Capabilities) do
local capability=cap --Ops.Auftrag#AUFTRAG.Capability
if capability.MissionType==MissionType then
return true
end
end
return false
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -0,0 +1,776 @@
--- **Wrapper** - Markers On the F10 map.
--
-- **Main Features:**
--
-- * Convenient handling of markers via multiple user API functions.
-- * Update text and position of marker easily via scripting.
-- * Delay creation and removal of markers via (optional) parameters.
-- * Retrieve data such as text and coordinate.
-- * Marker specific FSM events when a marker is added, removed or changed.
-- * Additional FSM events when marker text or position is changed.
--
-- ===
--
-- ### Author: **funkyfranky**
-- @module Wrapper.Marker
-- @image Wrapper_Marker.png
--- Marker class.
-- @type MARKER
-- @field #string ClassName Name of the class.
-- @field #boolean Debug Debug mode. Messages to all about status.
-- @field #string lid Class id string for output to DCS log file.
-- @field #number mid Marker ID.
-- @field Core.Point#COORDINATE coordinate Coordinate of the mark.
-- @field #string text Text displayed in the mark panel.
-- @field #string message Message dispayed when the mark is added.
-- @field #boolean readonly Marker is read-only.
-- @field #number coalition Coalition to which the marker is displayed.
-- @extends Core.Fsm#FSM
--- Just because...
--
-- ===
--
-- ![Banner Image](..\Presentations\MARKER\Marker_Main.jpg)
--
-- # The MARKER Class Idea
--
-- The MARKER class simplifies creating, updating and removing of markers on the F10 map.
--
-- # Create a Marker
--
-- -- Create a MARKER object at Batumi with a trivial text.
-- local Coordinate=AIRBASE:FindByName("Batumi"):GetCoordinate()
-- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield")
--
-- Now this does **not** show the marker yet. We still need to specifiy to whom it is shown. There are several options, i.e.
-- show the marker to everyone, to a speficic coaliton only, or only to a specific group.
--
-- ## For Everyone
--
-- If the marker should be visible to everyone, you can use the :ToAll() function.
--
-- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToAll()
--
-- ## For a Coaliton
--
-- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function.
--
-- mymarker=MARKER:New(Coordinate, "I am Batumi Airfield"):ToCoaliton(coaliton.side.BLUE)
--
-- ### To Blue Coaliton
--
-- ### To Red Coalition
--
-- This would show the marker only to the Blue coaliton.
--
-- ## For a Group
--
--
-- # Removing a Marker
--
--
-- # Updating a Marker
--
-- The marker text and coordinate can be updated easily as shown below.
--
-- However, note that **updateing involves to remove and recreate the marker if either text or its coordinate is changed**.
-- *This is a DCS scripting engine limitation.*
--
-- ## Update Text
--
-- If you created a marker "mymarker" as shown above, you can update the dispayed test by
--
-- mymarker:UpdateText("I am the new text at Batumi")
--
-- The update can also be delayed by, e.g. 90 seconds, using
--
-- mymarker:UpdateText("I am the new text at Batumi", 90)
--
-- ## Update Coordinate
--
-- If you created a marker "mymarker" as shown above, you can update its coordinate on the F10 map by
--
-- mymarker:UpdateCoordinate(NewCoordinate)
--
-- The update can also be delayed by, e.g. 60 seconds, using
--
-- mymarker:UpdateCoordinate(NewCoordinate, 60)
--
-- # Retrieve Data
--
-- The important data as the displayed text and the coordinate of the marker can be retrieved easily.
--
-- ## Text
--
-- local text=mymarker:GetText()
-- env.info("Marker Text = " .. text)
--
-- ## Coordinate
--
-- local Coordinate=mymarker:GetCoordinate()
-- env.info("Marker Coordinate LL DSM = " .. Coordinate:ToStringLLDMS())
--
--
-- # FSM Events
--
-- Moose creates addditonal events, so called FSM event, when markers are added, changed, removed, and text or the coordianteis updated.
--
-- These events can be captured and used for processing via OnAfter functions as shown below.
--
-- ## Added
--
-- ## Changed
--
-- ## Removed
--
-- ## TextUpdate
--
-- ## CoordUpdate
--
--
-- # Examples
--
--
-- @field #MARKER
MARKER = {
ClassName = "MARKER",
Debug = false,
lid = nil,
mid = nil,
coordinate = nil,
text = nil,
message = nil,
readonly = nil,
coalition = nil,
}
--- Marker ID. Running number.
_MARKERID=0
--- Marker class version.
-- @field #string version
MARKER.version="0.1.0"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: User "Get" functions. E.g., :GetCoordinate()
-- DONE: Add delay to user functions.
-- DONE: Handle events.
-- DONE: Create FSM events.
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new MARKER class object.
-- @param #MARKER self
-- @param Core.Point#COORDINATE Coordinate Coordinate where to place the marker.
-- @param #string Text Text displayed on the mark panel.
-- @return #MARKER self
function MARKER:New(Coordinate, Text)
-- Inherit everything from FSM class.
local self=BASE:Inherit(self, FSM:New()) -- #MARKER
self.coordinate=Coordinate
self.text=Text
-- Defaults
self.readonly=false
self.message=""
-- New marker ID. This is not the one of the actual marker.
_MARKERID=_MARKERID+1
self.myid=_MARKERID
-- Log ID.
self.lid=string.format("Marker #%d | ", self.myid)
-- Start State.
self:SetStartState("Invisible")
-- Add FSM transitions.
-- From State --> Event --> To State
self:AddTransition("Invisible", "Added", "Visible") -- Marker was added.
self:AddTransition("Visible", "Removed", "Invisible") -- Marker was removed.
self:AddTransition("*", "Changed", "*") -- Marker was changed.
self:AddTransition("*", "TextUpdate", "*") -- Text updated.
self:AddTransition("*", "CoordUpdate", "*") -- Coordinates updated.
--- Triggers the FSM event "Added".
-- @function [parent=#MARKER] Added
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData Event data table.
--- Triggers the delayed FSM event "Added".
-- @function [parent=#MARKER] __Added
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData Event data table.
--- On after "Added" event user function.
-- @function [parent=#MARKER] OnAfterAdded
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
--- Triggers the FSM event "Removed".
-- @function [parent=#MARKER] Removed
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData Event data table.
--- Triggers the delayed FSM event "Removed".
-- @function [parent=#MARKER] __Removed
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData Event data table.
--- On after "Removed" event user function.
-- @function [parent=#MARKER] OnAfterRemoved
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
--- Triggers the FSM event "Changed".
-- @function [parent=#MARKER] Changed
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData Event data table.
--- Triggers the delayed FSM event "Changed".
-- @function [parent=#MARKER] __Changed
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData Event data table.
--- On after "Changed" event user function.
-- @function [parent=#MARKER] OnAfterChanged
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
--- Triggers the FSM event "TextUpdate".
-- @function [parent=#MARKER] TextUpdate
-- @param #MARKER self
-- @param #string Text The new text.
--- Triggers the delayed FSM event "TextUpdate".
-- @function [parent=#MARKER] __TextUpdate
-- @param #MARKER self
-- @param #string Text The new text.
--- On after "TextUpdate" event user function.
-- @function [parent=#MARKER] OnAfterTextUpdate
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Text The new text.
--- Triggers the FSM event "CoordUpdate".
-- @function [parent=#MARKER] CoordUpdate
-- @param #MARKER self
-- @param Core.Point#COORDINATE Coordinate The new Coordinate.
--- Triggers the delayed FSM event "CoordUpdate".
-- @function [parent=#MARKER] __CoordUpdate
-- @param #MARKER self
-- @param Core.Point#COORDINATE Coordinate The updated Coordinate.
--- On after "CoordUpdate" event user function.
-- @function [parent=#MARKER] OnAfterCoordUpdate
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The updated Coordinate.
-- Handle events.
self:HandleEvent(EVENTS.MarkAdded)
self:HandleEvent(EVENTS.MarkRemoved)
self:HandleEvent(EVENTS.MarkChange)
return self
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Marker is readonly. Text cannot be changed and marker cannot be removed.
-- @param #MARKER self
-- @return #MARKER self
function MARKER:ReadOnly()
self.readonly=true
return self
end
--- Set message that is displayed on screen if the marker is added.
-- @param #MARKER self
-- @param #string Text Message displayed when the marker is added.
-- @return #MARKER self
function MARKER:Message(Text)
self.message=Text or ""
return self
end
--- Place marker visible for everyone.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToAll(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.ToAll, self)
else
self.toall=true
self.tocoaliton=nil
self.coalition=nil
self.togroup=nil
self.groupname=nil
self.groupid=nil
-- First remove an existing mark.
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
-- Call DCS function.
trigger.action.markToAll(self.mid, self.text, self.coordinate:GetVec3(), self.readonly, self.message)
end
return self
end
--- Place marker visible for a specific coalition only.
-- @param #MARKER self
-- @param #number Coalition Coalition 1=Red, 2=Blue, 0=Neutral. See `coaliton.side.RED`.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToCoalition(Coalition, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.ToCoalition, self, Coalition)
else
self.coalition=Coalition
self.tocoaliton=true
self.toall=false
self.togroup=false
self.groupname=nil
self.groupid=nil
-- First remove an existing mark.
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
-- Call DCS function.
trigger.action.markToCoalition(self.mid, self.text, self.coordinate:GetVec3(), self.coalition, self.readonly, self.message)
end
return self
end
--- Place marker visible for the blue coalition only.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToBlue(Delay)
self:ToCoalition(coalition.side.BLUE, Delay)
return self
end
--- Place marker visible for the blue coalition only.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToRed(Delay)
self:ToCoalition(coalition.side.RED, Delay)
return self
end
--- Place marker visible for the neutral coalition only.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToNeutral(Delay)
self:ToCoalition(coalition.side.NEUTRAL, Delay)
return self
end
--- Place marker visible for a specific group only.
-- @param #MARKER self
-- @param Wrapper.Group#GROUP Group The group to which the marker is displayed.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:ToGroup(Group, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.ToGroup, self, Group)
else
-- Check if group exists.
if Group and Group:IsAlive()~=nil then
self.groupid=Group:GetID()
if self.groupid then
self.groupname=Group:GetName()
self.togroup=true
self.tocoaliton=nil
self.coalition=nil
self.toall=nil
-- First remove an existing mark.
if self.shown then
self:Remove()
end
self.mid=UTILS.GetMarkID()
-- Call DCS function.
trigger.action.markToGroup(self.mid, self.text, self.coordinate:GetVec3(), self.groupid, self.readonly, self.message)
end
else
--TODO: Warning!
end
end
return self
end
--- Update the text displayed on the mark panel.
-- @param #MARKER self
-- @param #string Text Updated text.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:UpdateText(Text, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.UpdateText, self, Text)
else
self.text=tostring(Text)
self:Refresh()
self:TextUpdate(tostring(Text))
end
return self
end
--- Update the coordinate where the marker is displayed.
-- @param #MARKER self
-- @param Core.Point#COORDINATE Coordinate The new coordinate.
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:UpdateCoordinate(Coordinate, Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.UpdateCoordinate, self, Coordinate)
else
self.coordinate=Coordinate
self:Refresh()
self:CoordUpdate(Coordinate)
end
return self
end
--- Refresh the marker.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is created.
-- @return #MARKER self
function MARKER:Refresh(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.Refresh, self)
else
if self.toall then
self:ToAll()
elseif self.tocoaliton then
self:ToCoalition(self.coalition)
elseif self.togroup then
local group=GROUP:FindByName(self.groupname)
self:ToGroup(group)
else
self:E(self.lid.."ERROR: unknown To in :Refresh()!")
end
end
return self
end
--- Remove a marker.
-- @param #MARKER self
-- @param #number Delay (Optional) Delay in seconds, before the marker is removed.
-- @return #MARKER self
function MARKER:Remove(Delay)
if Delay and Delay>0 then
self:ScheduleOnce(Delay, MARKER.Remove, self)
else
if self.shown then
-- Call DCS function.
trigger.action.removeMark(self.mid)
end
end
return self
end
--- Get position of the marker.
-- @param #MARKER self
-- @return Core.Point#COORDINATE The coordinate of the marker.
function MARKER:GetCoordinate()
return self.coordinate
end
--- Get text that is displayed in the marker panel.
-- @param #MARKER self
-- @return #string Marker text.
function MARKER:GetText()
return self.text
end
--- Set text that is displayed in the marker panel. Note this does not show the marker.
-- @param #MARKER self
-- @param #string Text Marker text. Default is an empty sting "".
-- @return #MARKER self
function MARKER:SetText(Text)
self.text=Text and tostring(Text) or ""
return self
end
--- Check if marker is currently visible on the F10 map.
-- @param #MARKER self
-- @return #boolean True if the marker is currently visible.
function MARKER:IsVisible()
return self:Is("Visible")
end
--- Check if marker is currently invisible on the F10 map.
-- @param #MARKER self
-- @return
function MARKER:IsInvisible()
return self:Is("Invisible")
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Event function when a MARKER is added.
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData
function MARKER:OnEventMarkAdded(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID)))
if MarkID==self.mid then
self.shown=true
self:Added(EventData)
end
end
end
--- Event function when a MARKER is removed.
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData
function MARKER:OnEventMarkRemoved(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkAdded for Mark ID=%s", tostring(MarkID)))
if MarkID==self.mid then
self.shown=false
self:Removed(EventData)
end
end
end
--- Event function when a MARKER changed.
-- @param #MARKER self
-- @param Core.Event#EVENTDATA EventData
function MARKER:OnEventMarkChange(EventData)
if EventData and EventData.MarkID then
local MarkID=EventData.MarkID
self:T3(self.lid..string.format("Captured event MarkChange for Mark ID=%s", tostring(MarkID)))
if MarkID==self.mid then
self:Changed(EventData)
self:TextChanged(tostring(EventData.MarkText))
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- FSM Event Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- On after "Added" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
function MARKER:onafterAdded(From, Event, To, EventData)
-- Debug info.
local text=string.format("Captured event MarkAdded for myself:\n")
text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID))
text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition))
text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID))
text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody")
text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere")
text=text..string.format("Text: \n%s", tostring(EventData.MarkText))
self:T2(self.lid..text)
end
--- On after "Removed" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
function MARKER:onafterRemoved(From, Event, To, EventData)
-- Debug info.
local text=string.format("Captured event MarkRemoved for myself:\n")
text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID))
text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition))
text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID))
text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody")
text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere")
text=text..string.format("Text: \n%s", tostring(EventData.MarkText))
self:T2(self.lid..text)
end
--- On after "Changed" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Event#EVENTDATA EventData Event data table.
function MARKER:onafterChanged(From, Event, To, EventData)
-- Debug info.
local text=string.format("Captured event MarkChange for myself:\n")
text=text..string.format("Marker ID = %s\n", tostring(EventData.MarkID))
text=text..string.format("Coalition = %s\n", tostring(EventData.MarkCoalition))
text=text..string.format("Group ID = %s\n", tostring(EventData.MarkGroupID))
text=text..string.format("Initiator = %s\n", EventData.IniUnit and EventData.IniUnit:GetName() or "Nobody")
text=text..string.format("Coordinate = %s\n", EventData.MarkCoordinate and EventData.MarkCoordinate:ToStringLLDMS() or "Nowhere")
text=text..string.format("Text: \n%s", tostring(EventData.MarkText))
self:T2(self.lid..text)
end
--- On after "TextUpdate" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param #string Text The updated text, displayed in the mark panel.
function MARKER:onafterTextUpdate(From, Event, To, Text)
self:I(self.lid..string.format("New Marker Text:\n%s", Text))
end
--- On after "CoordUpdate" event.
-- @param #MARKER self
-- @param #string From From state.
-- @param #string Event Event.
-- @param #string To To state.
-- @param Core.Point#COORDINATE Coordinate The updated coordinates.
function MARKER:onafterCoordUpdate(From, Event, To, Coordinate)
self:I(self.lid..string.format("New Marker Coordinate in LL DMS: %s", Coordinate:ToStringLLDMS()))
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

View File

@ -33,6 +33,7 @@ Wrapper/Client.lua
Wrapper/Static.lua
Wrapper/Airbase.lua
Wrapper/Scenery.lua
Wrapper/Marker.lua
Cargo/Cargo.lua
Cargo/CargoUnit.lua
@ -64,6 +65,11 @@ Ops/Airboss.lua
Ops/RecoveryTanker.lua
Ops/RescueHelo.lua
Ops/ATIS.lua
Ops/AirWing.lua
Ops/Auftrag.lua
Ops/FlightGroup.lua
Ops/NavyGroup.lua
Ops/Squadron.lua
AI/AI_Balancer.lua
AI/AI_Air.lua