diff --git a/Moose Development/Moose/Core/Astar.lua b/Moose Development/Moose/Core/Astar.lua index 59a199e62..984f67df0 100644 --- a/Moose Development/Moose/Core/Astar.lua +++ b/Moose Development/Moose/Core/Astar.lua @@ -475,7 +475,7 @@ end -- @return #boolean If true, two nodes have LoS. function ASTAR.LoS(nodeA, nodeB, corridor) - local offset=0.1 + local offset=1 local dx=corridor and corridor/2 or nil local dy=dx diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index 703a0e708..bbea2df42 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -761,6 +761,7 @@ function ARMYGROUP:_InitGroup() -- We set some values. self.radioDefault.Freq=133 self.radioDefault.Modu=radio.modulation.AM + self.radio.Freq=133 self.radio.Modu=radio.modulation.AM diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 2c3b83010..b707b1a85 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -37,6 +37,8 @@ -- @field #number markerCoaliton Coalition to which the marker is dispayed. -- @field #table DCStask DCS task structure. -- @field #number Ntargets Number of mission targets. +-- @field #number Ncasualties Number of own casualties during mission. +-- @field #number Nelements Number of elements (units) assigned to mission. -- @field #number dTevaluate Time interval in seconds before the mission result is evaluated after mission is over. -- @field #number Tover Mission abs. time stamp, when mission was over. -- @field #table conditionStart Condition(s) that have to be true, before the mission will be started. @@ -486,35 +488,38 @@ function AUFTRAG:New(Type) self.missionRepeatMax=0 self.nassets=1 self.dTevaluate=0 + self.Ncasualties=0 + self.Nelements=0 -- FMS start state is PLANNED. self:SetStartState(self.status) -- PLANNED --> (QUEUED) --> (REQUESTED) --> SCHEDULED --> STARTED --> EXECUTING --> DONE - self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. - self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. - self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. + self:AddTransition(AUFTRAG.Status.PLANNED, "Queued", AUFTRAG.Status.QUEUED) -- Mission is in queue of an AIRWING. + self:AddTransition(AUFTRAG.Status.QUEUED, "Requested", AUFTRAG.Status.REQUESTED) -- Mission assets have been requested from the warehouse. + self:AddTransition(AUFTRAG.Status.REQUESTED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- Mission added to the first ops group queue. - self:AddTransition(AUFTRAG.Status.PLANNED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- From planned directly to scheduled. + self:AddTransition(AUFTRAG.Status.PLANNED, "Scheduled", AUFTRAG.Status.SCHEDULED) -- From planned directly to scheduled. - self:AddTransition(AUFTRAG.Status.SCHEDULED, "Started", AUFTRAG.Status.STARTED) -- First asset has started the mission - self:AddTransition(AUFTRAG.Status.STARTED, "Executing", AUFTRAG.Status.EXECUTING) -- First asset is executing the mission. + self:AddTransition(AUFTRAG.Status.SCHEDULED, "Started", AUFTRAG.Status.STARTED) -- First asset has started the mission + self:AddTransition(AUFTRAG.Status.STARTED, "Executing", AUFTRAG.Status.EXECUTING) -- First asset is executing the mission. - self:AddTransition("*", "Done", AUFTRAG.Status.DONE) -- All assets have reported that mission is done. + self:AddTransition("*", "Done", AUFTRAG.Status.DONE) -- All assets have reported that mission is done. - self:AddTransition("*", "Cancel", "*") -- Command to cancel the mission. + self:AddTransition("*", "Cancel", "*") -- Command to cancel the mission. - self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS) - self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED) + self:AddTransition("*", "Success", AUFTRAG.Status.SUCCESS) + self:AddTransition("*", "Failed", AUFTRAG.Status.FAILED) - self:AddTransition("*", "Status", "*") - self:AddTransition("*", "Stop", "*") + self:AddTransition("*", "Status", "*") + self:AddTransition("*", "Stop", "*") - self:AddTransition("*", "Repeat", AUFTRAG.Status.PLANNED) + self:AddTransition("*", "Repeat", AUFTRAG.Status.PLANNED) - self:AddTransition("*", "GroupDead", "*") - self:AddTransition("*", "AssetDead", "*") + self:AddTransition("*", "ElementDestroyed", "*") + self:AddTransition("*", "GroupDead", "*") + self:AddTransition("*", "AssetDead", "*") -- Init status update. self:__Status(-1) @@ -1836,7 +1841,7 @@ function AUFTRAG:onafterStatus(From, Event, To) local commander=self.wingcommander and tostring(self.wingcommander.coalition) or "N/A" -- Info message. - self:T(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) + self:I(self.lid..string.format("Status %s: Target=%s, T=%s-%s, assets=%d, groups=%d, targets=%d, wing=%s, commander=%s", self.status, targetname, Cstart, Cstop, #self.assets, Ngroups, Ntargets, airwing, commander)) -- Check for error. if fsmstate~=self.status then @@ -1902,6 +1907,11 @@ function AUFTRAG:Evaluate() failed=true end + -- No targets and everybody died ==> mission failed. Well, unless success condition is true. + if self.Ntargets==0 and self.Nelements==self.Ncasualties then + failed=true + end + end --TODO: all assets dead? Is this a FAILED criterion even if all targets have been destroyed? What if there are no initial targets (e.g. when ORBIT, PATROL, RECON missions). @@ -1910,15 +1920,18 @@ function AUFTRAG:Evaluate() failed=true elseif successCondition then failed=false - end + end -- Debug text. local text=string.format("Evaluating mission:\n") - text=text..string.format("Targets = %d/%d\n", self.Ntargets, Ntargets) - text=text..string.format("Damage = %.1f %%\n", targetdamage) - text=text..string.format("Success Cond = %s\n", tostring(successCondition)) - text=text..string.format("Failure Cond = %s\n", tostring(failureCondition)) - text=text..string.format("Failed = %s", tostring(failed)) + text=text..string.format("Units assigned = %d\n", self.Nelements) + text=text..string.format("Own casualties = %d\n", self.Ncasualties) + text=text..string.format("Own losses = %.1f %%\n", self.Ncasualties/self.Nelements*100) + text=text..string.format("Targets = %d/%d\n", self.Ntargets, Ntargets) + text=text..string.format("Damage = %.1f %%\n", targetdamage) + text=text..string.format("Success Cond = %s\n", tostring(successCondition)) + text=text..string.format("Failure Cond = %s\n", tostring(failureCondition)) + text=text..string.format("Failed = %s", tostring(failed)) self:I(self.lid..text) if failed then @@ -2224,6 +2237,15 @@ function AUFTRAG:onafterDone(From, Event, To) end +--- On after "ElementDestroyed" event. +-- @param #AUFTRAG self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Ops.OpsGroup#OPSGROUP OpsGroup The ops group that is dead now. +function AUFTRAG:onafterElementDestroyed(From, Event, To, OpsGroup, Element) + self.Ncasualties=self.Ncasualties+1 +end --- On after "GroupDead" event. -- @param #AUFTRAG self @@ -2412,6 +2434,10 @@ function AUFTRAG:onafterRepeat(From, Event, To) -- No flight data. self.groupdata={} + -- Reset casualties and units assigned. + self.Ncasualties=0 + self.Nelements=0 + -- Call status again. self:__Status(-30) diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index be20612b7..cdc676150 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -1154,8 +1154,9 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) local element=self:GetElementByName(unitname) if element then - self:I(self.lid..string.format("EVENT: Element %s unit lost ==> dead", element.name)) - self:ElementDead(element) + self:I(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed", element.name)) + --self:ElementDead(element) + self:ElementDestroyed(element) end end @@ -1359,6 +1360,25 @@ function FLIGHTGROUP:onafterElementArrived(From, Event, To, Element, airbase, Pa self:_UpdateStatus(Element, OPSGROUP.ElementStatus.ARRIVED) end +--- On after "ElementDestroyed" event. +-- @param #FLIGHTGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #FLIGHTGROUP.Element Element The flight group element. +function FLIGHTGROUP:onafterElementDestroyed(From, Event, To, Element) + self:T(self.lid..string.format("Element dead %s.", Element.name)) + + -- Cancel all missions. + for _,_mission in pairs(self.missionqueue) do + local mission=_mission --Ops.Auftrag#AUFTRAG + + mission:ElementDestroyed(self, Element) + + end + +end + --- On after "ElementDead" event. -- @param #FLIGHTGROUP self -- @param #string From From state. @@ -1395,8 +1415,8 @@ function FLIGHTGROUP:onafterSpawned(From, Event, To) self:SwitchROT(self.option.ROT) -- Turn TACAN beacon on. - if self.tacanDefault and self.tacanDefault.Channel then - self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse) + if self.tacanDefault then + self:SwitchTACAN(self.tacanDefault.Channel, self.tacanDefault.Morse, self.tacanDefault.BeaconName, self.tacanDefault.Band) end -- Turn on the radio. @@ -1578,7 +1598,7 @@ function FLIGHTGROUP:onafterArrived(From, Event, To) self:__Stop(5*60) end ---- On after "FlightDead" event. +--- On after "Dead" event. -- @param #FLIGHTGROUP self -- @param #string From From state. -- @param #string Event Event. @@ -1596,7 +1616,7 @@ function FLIGHTGROUP:onafterDead(From, Event, To) self.flightcontrol=nil end - -- Cancel all mission. + -- Cancel all missions. for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG @@ -2510,8 +2530,10 @@ function FLIGHTGROUP:_InitGroup() -- Radio parameters from template. self.radioOn=self.template.communication + self.radio.Freq=self.template.frequency self.radio.Modu=self.template.modulation + self.radioDefault.Freq=self.radio.Freq self.radioDefault.Modu=self.radio.Modu @@ -3221,7 +3243,15 @@ function FLIGHTGROUP:GetClosestAirbase() local coord=group:GetCoordinate() local coalition=self:GetCoalition() - return coord:GetClosestAirbase(nil, coalition) + local airbase=coord:GetClosestAirbase() --(nil, coalition) + + if airbase then + env.info("FF Got closest airbase ".. airbase:GetName()) + else + env.info("FF no closest airbase!") + end + + return airbase end --- Search unoccupied parking spots at the airbase for all flight elements. diff --git a/Moose Development/Moose/Ops/NavyGroup.lua b/Moose Development/Moose/Ops/NavyGroup.lua index 8dcd054f6..df904b6eb 100644 --- a/Moose Development/Moose/Ops/NavyGroup.lua +++ b/Moose Development/Moose/Ops/NavyGroup.lua @@ -219,14 +219,11 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) -- Absolute mission time in seconds. local Tnow=timer.getAbsTime() + -- Convert number to Clock. if starttime and type(starttime)=="number" then starttime=UTILS.SecondsToClock(Tnow+starttime) end - if stoptime and type(stoptime)=="number" then - stoptime=UTILS.SecondsToClock(Tnow+stoptime) - end - -- Input or now. starttime=starttime or UTILS.SecondsToClock(Tnow) @@ -234,7 +231,16 @@ function NAVYGROUP:CreateTurnIntoWind(starttime, stoptime, speed, uturn, offset) local Tstart=UTILS.ClockToSeconds(starttime) -- Set stop time. - local Tstop=stoptime and UTILS.ClockToSeconds(stoptime) or Tstart+90*60 + local Tstop=Tstart+90*60 + + if stoptime==nil then + Tstop=Tstart+90*60 + elseif type(stoptime)=="number" then + Tstop=Tstart+stoptime + else + Tstop=UTILS.ClockToSeconds(stoptime) + end + -- Consistancy check for timing. if Tstart>Tstop then @@ -537,10 +543,16 @@ function NAVYGROUP:onafterSpawned(From, Event, To) if self.ai then - -- Set default ROE and Alarmstate options. - self:SwitchROE(self.option.ROE) + -- Set default ROE. + self:SwitchROE(self.option.ROE) + + -- Set default Alarm State. self:SwitchAlarmstate(self.option.Alarm) + if self.tacanDefault then + + end + end -- Get orientation. @@ -1063,6 +1075,7 @@ function NAVYGROUP:_InitGroup() self.radioOn=true -- Radio is always on for ships. self.radio.Freq=tonumber(self.template.units[1].frequency)/1000000 self.radio.Modu=tonumber(self.template.units[1].modulation) + self.radioDefault.Freq=self.radio.Freq self.radioDefault.Modu=self.radio.Modu @@ -1145,15 +1158,20 @@ function NAVYGROUP:_CheckFreePath(DistanceMax, dx) return distance end + local offsetY=1 + -- Current coordinate. - local coordinate=self:GetCoordinate():SetAltitude(0, true) + local coordinate=self:GetCoordinate():SetAltitude(offsetY, true) -- Current heading. local heading=self:GetHeading() + -- Check from 500 meters in front. + coordinate=coordinate:Translate(500, heading, true) + local function LoS(dist) local checkcoord=coordinate:Translate(dist, heading, true) - return coordinate:IsLOS(checkcoord, 0.001) + return coordinate:IsLOS(checkcoord, offsetY) end -- First check if everything is clear. diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index b262cf3ce..ef08d4837 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -124,12 +124,9 @@ OPSGROUP = { option = {}, optionDefault = {}, tacan = {}, - tacanDefault = {}, icls = {}, - iclsDefault = {}, callsign = {}, callsignDefault = {}, - } --- Status of group element. @@ -259,6 +256,7 @@ OPSGROUP.TaskType={ -- @field #boolean intowind If true, this waypoint is a turn into wind route point. -- @field #boolean astar If true, this waypint was found by A* pathfinding algorithm. -- @field Core.Point#COORDINATE coordinate Waypoint coordinate. +-- @field Wrapper.Marker#MARKER marker Marker on the F10 map. --- NavyGroup version. -- @field #string version @@ -352,6 +350,7 @@ function OPSGROUP:New(Group) self:AddTransition("*", "MissionDone", "*") -- Mission is over. self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. + self:AddTransition("*", "ElementDestroyed", "*") -- An element was destroyed. self:AddTransition("*", "ElementDead", "*") -- An element is dead. ------------------------ @@ -637,6 +636,67 @@ end -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +--- Get the waypoints. +-- @param #OPSGROUP self +-- @return #table Table of all waypoints. +function OPSGROUP:GetWaypoints() + return self.waypoints +end + +--- Mark waypoints on F10 map. +-- @param #OPSGROUP self +-- @param #number Duration Duration in seconds how long the waypoints are displayed before they are automatically removed. Default is that they are never removed. +-- @return #OPSGROUP self +function OPSGROUP:MarkWaypoints(Duration) + + for i,_waypoint in pairs(self.waypoints or {}) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + + local text=string.format("Waypoint ID=%d of %s", waypoint.uid, self.groupname) + text=text..string.format("\nSpeed=%.1f kts, Alt=%d ft (%s)", UTILS.MpsToKnots(waypoint.speed), UTILS.MetersToFeet(waypoint.alt), "BARO") + + if waypoint.marker then + if waypoint.marker.text~=text then + waypoint.marker.text=text + end + + else + waypoint.marker=MARKER:New(waypoint.coordinate, text):ToCoalition(self:GetCoalition()) + end + end + + + if Duration then + self:RemoveWaypointMarkers(Duration) + end + + return self +end + +--- Remove waypoints markers on the F10 map. +-- @param #OPSGROUP self +-- @param #number Delay Delay in seconds before the markers are removed. Default is immediately. +-- @return #OPSGROUP self +function OPSGROUP:RemoveWaypointMarkers(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, OPSGROUP.RemoveWaypointMarkers, self) + else + + for i,_waypoint in pairs(self.waypoints or {}) do + local waypoint=_waypoint --#OPSGROUP.Waypoint + + if waypoint.marker then + waypoint.marker:Remove() + end + end + + end + + return self +end + + --- Get the waypoint from its unique ID. -- @param #OPSGROUP self -- @param #number uid Waypoint unique ID. @@ -923,6 +983,12 @@ function OPSGROUP:RemoveWaypoint(wpindex) -- Number of waypoints before delete. local N=#self.waypoints + + -- Remove waypoint marker. + local wp=self:GetWaypoint(wpindex) + if wp and wp.marker then + wp.marker:Remove() + end -- Remove waypoint. table.remove(self.waypoints, wpindex) @@ -1494,7 +1560,7 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) if Task.dcstask.id=="Formation" then Task.formation:Stop() self:TaskDone(Task) - elseif stopflag==1 then + elseif stopflag==1 or not self:IsAlive() then -- Manual call TaskDone if setting flag to one was not successful. self:TaskDone(Task) end @@ -1595,6 +1661,9 @@ function OPSGROUP:AddMission(Mission) -- Set mission status to SCHEDULED. Mission:Scheduled() + + -- Add elements. + Mission.Nelements=Mission.Nelements+#self.elements -- Add mission to queue. table.insert(self.missionqueue, Mission) @@ -2934,7 +3003,7 @@ end --- Activate/switch TACAN beacon settings. -- @param #OPSGROUP self -- @param #number Channel TACAN Channel. --- @param #string Morse TACAN morse code. +-- @param #string Morse TACAN morse code. Default is the value set in @{#OPSGROUP.SetDefaultTACAN} or if not set "XXX". -- @param #string UnitName Name of the unit in the group which should activate the TACAN beacon. Can also be given as #number to specify the unit number. Default is the first unit of the group. -- @param #string Band TACAN channel mode "X" or "Y". Default is "Y" for aircraft and "X" for ground and naval groups. -- @return #OPSGROUP self @@ -2956,6 +3025,10 @@ function OPSGROUP:SwitchTACAN(Channel, Morse, UnitName, Band) self:E(self.lid.."ERROR: Could not get TACAN unit. Trying first unit in the group.") unit=self.group:GetUnit(1) end + + if not Morse then + Morse=self.tacanDefault and self.tacanDefault.Morse or "XXX" + end if unit and unit:IsAlive() then @@ -3046,7 +3119,7 @@ function OPSGROUP:SwitchRadio(Frequency, Modulation) if self:IsAlive() and Frequency then - Modulation=Modulation or (self.radioDefault.Modu or radio.Modulation.AM) + Modulation=Modulation or self.radioDefault.Modu local group=self.group --Wrapper.Group#GROUP @@ -3432,7 +3505,7 @@ function OPSGROUP:_UpdateStatus(element, newstatus, airbase) --- if self:_AllSimilarStatus(newstatus) then - self:Dead() + self:__Dead(-1) end end