diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 3a5160f86..1b2603f00 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -744,7 +744,7 @@ do -- COORDINATE -- @return #string MGRS coordinates. function COORDINATE:GetName() local name=self:ToStringMGRS() - return rname + return name end --- Return velocity text of the COORDINATE. @@ -1751,9 +1751,9 @@ do -- COORDINATE self:F2( { ExplosionIntensity } ) ExplosionIntensity=ExplosionIntensity or 100 if Delay and Delay>0 then - SCHEDULER:New(nil, self.Explosion, {self,ExplosionIntensity}, Delay) + self:ScheduleOnce(Delay, self.Explosion, self, ExplosionIntensity) else - trigger.action.explosion( self:GetVec3(), ExplosionIntensity ) + trigger.action.explosion(self:GetVec3(), ExplosionIntensity) end return self end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index feda32b72..88d487622 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -817,6 +817,32 @@ function ZONE_RADIUS:GetScannedSetUnit() return SetUnit end +--- Get a set of scanned units. +-- @param #ZONE_RADIUS self +-- @return Core.Set#SET_GROUP Set of groups. +function ZONE_RADIUS:GetScannedSetGroup() + + self.ScanSetGroup=self.ScanSetGroup or SET_GROUP:New() --Core.Set#SET_GROUP + + self.ScanSetGroup.Set={} + + if self.ScanData then + for ObjectID, UnitObject in pairs( self.ScanData.Units ) do + local UnitObject = UnitObject -- DCS#Unit + if UnitObject:isExist() then + + local FoundUnit=UNIT:FindByName(UnitObject:getName()) + if FoundUnit then + local group=FoundUnit:GetGroup() + self.ScanSetGroup:AddGroup(group) + end + end + end + end + + return self.ScanSetGroup +end + --- Count the number of different coalitions inside the zone. -- @param #ZONE_RADIUS self diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index e6a07e323..07eea7e21 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -890,12 +890,14 @@ do -- ZONE_CAPTURE_COALITION end -- Status text. - local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State) - local NewState = self:GetState() - if NewState~=State then - text=text..string.format(" --> %s", NewState) + if false then + local text=string.format("CAPTURE ZONE %s: Owner=%s (Previous=%s): #blue=%d, #red=%d, Status %s", self:GetZoneName(), self:GetCoalitionName(), UTILS.GetCoalitionName(self:GetPreviousCoalition()), nBlue, nRed, State) + local NewState = self:GetState() + if NewState~=State then + text=text..string.format(" --> %s", NewState) + end + self:I(text) end - self:I(text) end diff --git a/Moose Development/Moose/Ops/ArmyGroup.lua b/Moose Development/Moose/Ops/ArmyGroup.lua index e834f8d4d..28857197e 100644 --- a/Moose Development/Moose/Ops/ArmyGroup.lua +++ b/Moose Development/Moose/Ops/ArmyGroup.lua @@ -33,6 +33,9 @@ -- @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 isMobile If true, group is mobile. +-- @field #ARMYGROUP.Target engage Engage target. +-- @field #boolean retreatOnOutOfAmmo If true, the group will automatically retreat when out of ammo. Needs a retreat zone! +-- @field Core.Set#SET_ZONE retreatZones Set of retreat zones. -- @extends Ops.OpsGroup#OPSGROUP --- *Your soul may belong to Jesus, but your ass belongs to the marines.* -- Eugene B. Sledge @@ -49,6 +52,7 @@ ARMYGROUP = { ClassName = "ARMYGROUP", formationPerma = nil, + engage = {}, } --- Army group element. @@ -61,14 +65,20 @@ ARMYGROUP = { -- @field #number width Width of element in meters. -- @field #number height Height of element in meters. +--- Target +-- @type ARMYGROUP.Target +-- @field Ops.Target#TARGET Target The target. +-- @field Core.Point#COORDINATE Coordinate Last known coordinate of the target. + --- Army Group version. -- @field #string version -ARMYGROUP.version="0.3.0" +ARMYGROUP.version="0.4.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO: Retreat. -- TODO: Suppression of fire. -- TODO: Check if group is mobile. -- TODO: F10 menu. @@ -95,6 +105,7 @@ function ARMYGROUP:New(Group) self:SetDefaultAlarmstate() self:SetDetection() self:SetPatrolAdInfinitum(false) + self:SetRetreatZones() -- Add FSM transitions. -- From State --> Event --> To State @@ -103,6 +114,14 @@ function ARMYGROUP:New(Group) 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("*", "Retreat", "Retreating") -- + self:AddTransition("Retreating", "Retreated", "Retreated") -- + + self:AddTransition("Cruising", "EngageTarget", "Engaging") -- Engage a target + self:AddTransition("Holding", "EngageTarget", "Engaging") -- Engage a target + self:AddTransition("OnDetour", "EngageTarget", "Engaging") -- Engage a target + self:AddTransition("Engaging", "Disengage", "Cruising") -- Engage a target 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. @@ -244,6 +263,24 @@ function ARMYGROUP:AddTaskAttackGroup(TargetGroup, WeaponExpend, WeaponType, Clo return task end +--- Define a set of possible retreat zones. +-- @param #ARMYGROUP self +-- @param Core.Set#SET_ZONE RetreatZoneSet The retreat zone set. Default is an empty set. +-- @return #ARMYGROUP self +function ARMYGROUP:SetRetreatZones(RetreatZoneSet) + self.retreatZones=RetreatZoneSet or SET_ZONE:New() + return self +end + +--- Add a zone to the retreat zone set. +-- @param #ARMYGROUP self +-- @param Core.Zone#ZONE_BASE RetreatZone The retreat zone. +-- @return #ARMYGROUP self +function ARMYGROUP:AddRetreatZone(RetreatZone) + self.retreatZones:AddZone(RetreatZone) + return self +end + --- Check if the group is currently holding its positon. -- @param #ARMYGROUP self -- @return #boolean If true, group was ordered to hold. @@ -265,6 +302,20 @@ function ARMYGROUP:IsOnDetour() return self:Is("OnDetour") end +--- Check if the group is ready for combat. I.e. not reaming, retreating, retreated, out of ammo or engaging. +-- @param #ARMYGROUP self +-- @return #boolean If true, group is on a combat ready. +function ARMYGROUP:IsCombatReady() + local combatready=true + + if self:IsRearming() or self:IsRetreating() or self.outofAmmo or self:IsEngaging() or self:is("Retreated") or self:IsDead() or self:IsStopped() or self:IsInUtero() then + combatready=false + end + + return combatready +end + + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -311,6 +362,14 @@ function ARMYGROUP:onafterStatus(From, Event, To) -- Check if group got stuck. self:_CheckStuck() + -- Check damage of elements and group. + self:_CheckDamage() + + -- Update engagement. + if self:IsEngaging() then + self:_UpdateEngageTarget() + end + if self.verbose>=1 then -- Get number of tasks and missions. @@ -321,11 +380,12 @@ function ARMYGROUP:onafterStatus(From, Event, To) local alarm=self:GetAlarmstate() local speed=UTILS.MpsToKnots(self.velocity) local speedEx=UTILS.MpsToKnots(self:GetExpectedSpeed()) - local formation=self.option.Formation or "unknown" + local formation=self.option.Formation or "unknown" + local ammo=self:GetAmmoTot() -- Info text. - 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, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), speed, speedEx, self.heading) + local text=string.format("%s [ROE-AS=%d-%d T/M=%d/%d]: Wp=%d/%d-->%d (final %s), Life=%.1f, Speed=%.1f (%d), Heading=%03d, Ammo=%d", + fsmstate, roe, alarm, nTaskTot, nMissions, self.currentwp, #self.waypoints, self:GetWaypointIndexNext(), tostring(self.passedfinalwp), self.life or 0, speed, speedEx, self.heading, ammo.Total) self:I(self.lid..text) end @@ -506,7 +566,7 @@ function ARMYGROUP:onafterUpdateRoute(From, Event, To, n, Speed, Formation) end end - if not self.passedfinalwp then + if self:IsEngaging() or not self.passedfinalwp then -- Debug info. self:T(self.lid..string.format("Updateing route: WP %d-->%d (%d/%d), Speed=%.1f knots, Formation=%s", @@ -576,6 +636,13 @@ end -- @param #number ResumeRoute If true, resume route after detour point was reached. If false, the group will stop at the detour point and wait for futher commands. function ARMYGROUP:onafterDetour(From, Event, To, Coordinate, Speed, Formation, ResumeRoute) + for _,_wp in pairs(self.waypoints) do + local wp=_wp --Ops.OpsGroup#OPSGROUP.Waypoint + if wp.detour then + self:RemoveWaypointByID(wp.uid) + end + end + -- Speed in knots. Speed=Speed or self:GetSpeedCruise() @@ -632,6 +699,185 @@ function ARMYGROUP:onafterRearming(From, Event, To) end +--- On before "Retreat" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Core.Zone#ZONE_BASE Zone (Optional) Zone where to retreat. Default is the closest retreat zone. +-- @param #number Formation (Optional) Formation of the group. +function ARMYGROUP:onbeforeRetreat(From, Event, To, Zone, Formation) + + if not Zone then + + local a=self:GetVec2() + + local distmin=math.huge + local zonemin=nil + for _,_zone in pairs(self.retreatZones:GetSet()) do + local zone=_zone --Core.Zone#ZONE_BASE + + local b=zone:GetVec2() + + local dist=UTILS.VecDist2D(a, b) + + if dist Stop! + self:Route({wp}) + +end + +--- On after "EngageTarget" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group the group to be engaged. +function ARMYGROUP:onbeforeEngageTarget(From, Event, To, Target) + + local ammo=self:GetAmmoTot() + + if ammo.Total==0 then + self:E(self.lid.."WARNING: Cannot engage TARGET because no ammo left!") + return false + end + + return true +end + +--- On after "EngageTarget" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param Wrapper.Group#GROUP Group the group to be engaged. +function ARMYGROUP:onafterEngageTarget(From, Event, To, Target) + + if Target:IsInstanceOf("TARGET") then + self.engage.Target=Target + else + self.engage.Target=TARGET:New(Target) + end + + -- Target coordinate. + self.engage.Coordinate=UTILS.DeepCopy(self.engage.Target:GetCoordinate()) + + -- TODO: Backup current ROE and alarm state and reset after disengage. + + -- Switch ROE and alarm state. + self:SwitchAlarmstate(ENUMS.AlarmState.Auto) + self:SwitchROE(ENUMS.ROE.WeaponFree) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + -- Add waypoint after current. + self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + self.engage.Waypoint.detour=1 + +end + +--- On after "EngageTarget" event. +-- @param #ARMYGROUP self +function ARMYGROUP:_UpdateEngageTarget() + + if self.engage.Target and self.engage.Target:IsAlive() then + + --env.info("FF Update Engage Target "..self.engage.Target:GetName()) + + local vec3=self.engage.Target:GetCoordinate():GetVec3() + + local dist=UTILS.VecDist2D(vec3, self.engage.Coordinate:GetVec3()) + + if dist>100 then + + --env.info("FF Update Engage Target Moved "..self.engage.Target:GetName()) + + self.engage.Coordinate:UpdateFromVec3(vec3) + + -- ID of current waypoint. + local uid=self:GetWaypointCurrent().uid + + -- Remove current waypoint + self:RemoveWaypointByID(self.engage.Waypoint.uid) + + -- Add waypoint after current. + self.engage.Waypoint=self:AddWaypoint(self.engage.Coordinate, nil, uid, Formation, true) + + -- Set if we want to resume route after reaching the detour waypoint. + self.engage.Waypoint.detour=0 + + end + + else + self:Disengage() + end + +end + +--- On after "Disengage" event. +-- @param #ARMYGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARMYGROUP:onafterDisengage(From, Event, To) + -- TODO: Reset ROE and alarm state. + self:_CheckGroupDone(1) +end + --- On after "Rearmed" event. -- @param #ARMYGROUP self -- @param #string From From state. @@ -932,11 +1178,13 @@ function ARMYGROUP:_InitGroup() element.categoryname=element.unit:GetCategoryName() element.size, element.length, element.height, element.width=unit:GetObjectSize() element.ammo0=self:GetAmmoUnit(unit, false) + element.life0=unit:GetLife0() + element.life=element.life0 -- 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) + local text=string.format("Adding element %s: status=%s, skill=%s, life=%.3f category=%s (%d), size: %.1f (L=%.1f H=%.1f W=%.1f)", + element.name, element.status, element.skill, element.life, element.categoryname, element.category, element.size, element.length, element.height, element.width) self:I(self.lid..text) end diff --git a/Moose Development/Moose/Ops/Auftrag.lua b/Moose Development/Moose/Ops/Auftrag.lua index 1153ba5f6..dce4fe3be 100644 --- a/Moose Development/Moose/Ops/Auftrag.lua +++ b/Moose Development/Moose/Ops/Auftrag.lua @@ -47,6 +47,7 @@ -- @field #number markerCoaliton Coalition to which the marker is dispayed. -- @field #table DCStask DCS task structure. -- @field #number Ncasualties Number of own casualties during mission. +-- @field #number Nkills Number of (enemy) units killed by assets of this 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. @@ -504,8 +505,9 @@ function AUFTRAG:New(Type) self.NrepeatFailure=0 self.NrepeatSuccess=0 self.nassets=1 - self.dTevaluate=0 + self.dTevaluate=5 self.Ncasualties=0 + self.Nkills=0 self.Nelements=0 -- FMS start state is PLANNED. @@ -1697,6 +1699,14 @@ function AUFTRAG:GetCasualties() return self.Ncasualties or 0 end +--- Get kills, i.e. number of units that were destroyed by assets of this mission. +-- @param #AUFTRAG self +-- @return #number Number of units destroyed. +function AUFTRAG:GetKills() + return self.Nkills or 0 +end + + --- Check if mission is "urgent". -- @param #AUFTRAG self -- @return #boolean If `true`, mission is "urgent". @@ -2101,6 +2111,11 @@ function AUFTRAG:onafterStatus(From, Event, To) -- Ready to evaluate mission outcome? local ready2evaluate=self.Tover and Tnow-self.Tover>=self.dTevaluate or false + + --env.info("FF Tover="..tostring(self.Tover)) + --if self.Tover then + -- env.info("FF Tnow-Tover="..tostring(Tnow-self.Tover)) + --end -- Check if mission is OVER (done or cancelled) and enough time passed to evaluate the result. if self:IsOver() and ready2evaluate then @@ -2199,13 +2214,12 @@ function AUFTRAG:Evaluate() local text=string.format("Evaluating mission:\n") text=text..string.format("Own casualties = %d/%d\n", self.Ncasualties, self.Nelements) text=text..string.format("Own losses = %.1f %%\n", owndamage) + text=text..string.format("Killed units = %d\n", self.Nkills) text=text..string.format("--------------------------\n") text=text..string.format("Targets left = %d/%d\n", Ntargets, Ntargets0) text=text..string.format("Targets life = %.1f/%.1f\n", Life, Life0) text=text..string.format("Enemy losses = %.1f %%\n", targetdamage) text=text..string.format("--------------------------\n") - --text=text..string.format("Loss ratio = %.1f %%\n", targetdamage) - --text=text..string.format("--------------------------\n") text=text..string.format("Success Cond = %s\n", tostring(successCondition)) text=text..string.format("Failure Cond = %s\n", tostring(failureCondition)) text=text..string.format("--------------------------\n") @@ -2563,6 +2577,8 @@ function AUFTRAG:onafterAssetDead(From, Event, To, Asset) -- Number of groups alive. local N=self:CountOpsGroups() + self:I(self.lid..string.format("Asset %s dead! Number of ops groups remaining %d", tostring(Asset.spawngroupname), N)) + -- All assets dead? if N==0 then @@ -3045,7 +3061,7 @@ function AUFTRAG:CountOpsGroups() local N=0 for _,_groupdata in pairs(self.groupdata) do local groupdata=_groupdata --#AUFTRAG.GroupData - if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive() then + if groupdata and groupdata.opsgroup and groupdata.opsgroup:IsAlive() and not groupdata.opsgroup:IsDead() then N=N+1 end end diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index 87e207d8f..eb312d51e 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -336,6 +336,7 @@ function FLIGHTGROUP:New(group) self:HandleEvent(EVENTS.Crash, self.OnEventCrash) self:HandleEvent(EVENTS.RemoveUnit, self.OnEventRemoveUnit) self:HandleEvent(EVENTS.UnitLost, self.OnEventUnitLost) + self:HandleEvent(EVENTS.Kill, self.OnEventKill) -- Init waypoints. self:InitWaypoints() @@ -715,6 +716,12 @@ function FLIGHTGROUP:GetFuelMin() return fuelmin*100 end +--- Get number of kills of this group. +-- @param #FLIGHTGROUP self +-- @return #number Number of units killed. +function FLIGHTGROUP:GetKills() + return self.Nkills +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Status ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -741,18 +748,22 @@ function FLIGHTGROUP:onbeforeStatus(From, Event, To) -- Units with life <=1 are dead. if life<=1 then + --env.info(string.format("FF unit %s: live<=1 in status at T=%.3f", unit:GetName(), timer.getTime())) isdead=true end else -- Not alive any more. + --env.info(string.format("FF unit %s: NOT alive in status at T=%.3f", unit:GetName(), timer.getTime())) isdead=true end -- This one is dead. if isdead then - self:E(self.lid..string.format("Element %s is dead! Probably despawned without notice or landed at a too small airbase", tostring(element.name))) - self:ElementDead(element) + local text=string.format("Element %s is dead at t=%.3f! Maybe despawned without notice or landed at a too small airbase. Calling ElementDead in 60 sec to give other events a chance", + tostring(element.name), timer.getTime()) + self:E(self.lid..text) + self:__ElementDead(60, element) end end @@ -854,9 +865,9 @@ function FLIGHTGROUP:onafterStatus(From, Event, To) local parking=element.parking and tostring(element.parking.TerminalID) or "X" -- Check if element is not dead and we missed an event. - if life<=0 and element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then - self:ElementDead(element) - end + --if life<=0 and element.status~=OPSGROUP.ElementStatus.DEAD and element.status~=OPSGROUP.ElementStatus.INUTERO then + -- self:ElementDead(element) + --end -- Get ammo. local ammo=self:GetAmmoElement(element) @@ -1177,9 +1188,9 @@ function FLIGHTGROUP:OnEventCrash(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then - self:T3(self.lid..string.format("EVENT: Element %s crashed ==> dead", element.name)) - self:ElementDead(element) + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + self:T(self.lid..string.format("EVENT: Element %s crashed ==> destroyed", element.name)) + self:ElementDestroyed(element) end end @@ -1193,7 +1204,7 @@ function FLIGHTGROUP:OnEventUnitLost(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 - self:T(self.lid..string.format("EVENT: Unit %s lost!", EventData.IniUnitName)) + self:T2(self.lid..string.format("EVENT: Unit %s lost at t=%.3f", EventData.IniUnitName, timer.getTime())) local unit=EventData.IniUnit local group=EventData.IniGroup @@ -1202,8 +1213,8 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) -- Get element. local element=self:GetElementByName(unitname) - if element then - self:T3(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed", element.name)) + if element and element.status~=OPSGROUP.ElementStatus.DEAD then + self:T(self.lid..string.format("EVENT: Element %s unit lost ==> destroyed t=%.3f", element.name, timer.getTime())) self:ElementDestroyed(element) end @@ -1211,6 +1222,30 @@ function FLIGHTGROUP:OnEventUnitLost(EventData) end +--- Flightgroup event function handling the crash of a unit. +-- @param #FLIGHTGROUP self +-- @param Core.Event#EVENTDATA EventData Event data. +function FLIGHTGROUP:OnEventKill(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 + self:T2(self.lid..string.format("EVENT: Unit %s killed unit %s!", tostring(EventData.IniUnitName), tostring(EventData.TgtUnitName))) + + local unit=EventData.IniUnit + local group=EventData.IniGroup + local unitname=EventData.IniUnitName + + self.Nkills=self.Nkills+1 + + local mission=self:GetMissionCurrent() + if mission then + mission.Nkills=mission.Nkills+1 + end + + end + +end + --- Flightgroup event function handling the crash of a unit. -- @param #FLIGHTGROUP self -- @param Core.Event#EVENTDATA EventData Event data. @@ -2439,7 +2474,7 @@ function FLIGHTGROUP:onafterFuelLow(From, Event, To) self:I(self.lid..string.format("Send to refuel at tanker %s", tanker:GetName())) -- Get a coordinate towards the tanker. - local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker.flightgroup:GetCoordinate(), 0.75) + local coordinate=self:GetCoordinate():GetIntermediateCoordinate(tanker:GetCoordinate(), 0.75) self:Refuel(coordinate) diff --git a/Moose Development/Moose/Ops/OpsGroup.lua b/Moose Development/Moose/Ops/OpsGroup.lua index bfc737cf7..e61a371b0 100644 --- a/Moose Development/Moose/Ops/OpsGroup.lua +++ b/Moose Development/Moose/Ops/OpsGroup.lua @@ -55,6 +55,7 @@ -- @field #boolean detectionOn If true, detected units of the group are analyzed. -- @field Ops.Auftrag#AUFTRAG missionpaused Paused mission. -- @field #number Ndestroyed Number of destroyed units. +-- @field #number Nkills Number kills of this groups. -- -- @field Core.Point#COORDINATE coordinate Current coordinate. -- @@ -88,7 +89,7 @@ -- -- @field #OPSGROUP.Spot spot Laser and IR spot. -- --- @field #OPSGROUP.Ammo ammo Initial ammuont of ammo. +-- @field #OPSGROUP.Ammo ammo Initial ammount of ammo. -- -- @extends Core.Fsm#FSM @@ -142,6 +143,7 @@ OPSGROUP = { icls = {}, callsign = {}, Ndestroyed = 0, + Nkills = 0, } @@ -154,6 +156,8 @@ OPSGROUP = { -- @field #number length Length of element in meters. -- @field #number width Width of element in meters. -- @field #number height Height of element in meters. +-- @field #number life0 Initial life points. +-- @field #number life Life points when last updated. --- Status of group element. -- @type OPSGROUP.ElementStatus @@ -352,9 +356,16 @@ function OPSGROUP:New(Group) self.group=Group self.groupname=Group:GetName() end - + -- Set some string id for output to DCS.log file. - self.lid=string.format("OPSGROUP %s | ", self.groupname) + self.lid=string.format("OPSGROUP %s | ", tostring(self.groupname)) + + if self.group then + if not self:IsExist() then + self:E(self.lid.."ERROR: GROUP does not exist! Returning nil") + return nil + end + end -- Init set of detected units. self.detectedunits=SET_UNIT:New() @@ -382,10 +393,13 @@ function OPSGROUP:New(Group) -- Add FSM transitions. -- From State --> Event --> To State self:AddTransition("InUtero", "Spawned", "Spawned") -- The whole group was spawned. - self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. + self:AddTransition("*", "Dead", "Dead") -- The whole group is dead. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. self:AddTransition("*", "Status", "*") -- Status update. + + self:AddTransition("*", "Destroyed", "*") -- The whole group is dead. + self:AddTransition("*", "Damaged", "*") -- Someone in the group took damage. self:AddTransition("*", "UpdateRoute", "*") -- Update route of group. Only if airborne. self:AddTransition("*", "Respawn", "*") -- Respawn group. @@ -436,6 +450,7 @@ function OPSGROUP:New(Group) self:AddTransition("*", "ElementSpawned", "*") -- An element was spawned. self:AddTransition("*", "ElementDestroyed", "*") -- An element was destroyed. self:AddTransition("*", "ElementDead", "*") -- An element is dead. + self:AddTransition("*", "ElementDamaged", "*") -- An element was damaged. ------------------------ --- Pseudo Functions --- @@ -531,7 +546,11 @@ end -- @return #OPSGROUP self function OPSGROUP:SetLaser(Code, CheckLOS, IROff, UpdateTime) self.spot.Code=Code or 1688 - self.spot.CheckLOS=CheckLOS and CheckLOS or true + if CheckLOS~=nil then + self.spot.CheckLOS=CheckLOS + else + self.spot.CheckLOS=true + end self.spot.IRon=not IROff self.spot.dt=UpdateTime or 0.5 return self @@ -923,8 +942,23 @@ function OPSGROUP:DespawnElement(Element, Delay, NoEventRemoveUnit) return self end +--- Get current 2D position vector of the group. +-- @param #OPSGROUP self +-- @return DCS#Vec2 Vector with x,y components. +function OPSGROUP:GetVec2() ---- Get current 3D vector of the group. + local vec3=self:GetVec3() + + if vec3 then + local vec2={x=vec3.x, y=vec3.z} + return vec2 + end + + return nil +end + + +--- Get current 3D position vector of the group. -- @param #OPSGROUP self -- @return DCS#Vec3 Vector with x,y,z components. function OPSGROUP:GetVec3() @@ -960,6 +994,7 @@ function OPSGROUP:GetCoordinate(NewObject) if NewObject then local coord=COORDINATE:NewFromCoordinate(self.coordinate) + return coord else return self.coordinate end @@ -1232,6 +1267,20 @@ function OPSGROUP:IsLasing() return self.spot.On end +--- Check if the group is currently retreating. +-- @param #OPSGROUP self +-- @return #boolean If true, group is retreating. +function OPSGROUP:IsRetreating() + return self:is("Retreating") +end + +--- Check if the group is engaging another unit or group. +-- @param #OPSGROUP self +-- @return #boolean If true, group is engaging. +function OPSGROUP:IsEngaging() + return self:is("Engaging") +end + ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Waypoint Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1303,7 +1352,7 @@ end -- @return #OPSGROUP.Waypoint Waypoint data. function OPSGROUP:GetWaypointByID(uid) - for _,_waypoint in pairs(self.waypoints) do + for _,_waypoint in pairs(self.waypoints or {}) do local waypoint=_waypoint --#OPSGROUP.Waypoint if waypoint.uid==uid then return waypoint @@ -2190,11 +2239,16 @@ function OPSGROUP:onafterTaskCancel(From, Event, To, Task) -- Set stop flag. When the flag is true, the _TaskDone function is executed and calls :TaskDone() Task.stopflag:Set(1) + local done=false if Task.dcstask.id=="Formation" then Task.formation:Stop() - self:TaskDone(Task) - elseif stopflag==1 or not self:IsAlive() then + done=true + elseif stopflag==1 or (not self:IsAlive()) or self:IsDead() or self:IsStopped() then -- Manual call TaskDone if setting flag to one was not successful. + done=true + end + + if done then self:TaskDone(Task) end @@ -3173,7 +3227,7 @@ function OPSGROUP:onbeforeLaserOn(From, Event, To, Target) self.spot.element=element -- Height offset. No offset for aircraft. We take the height for ground or naval. - local offsetY=0 + local offsetY=0 if self.isGround or self.isNaval then offsetY=element.height end @@ -3291,7 +3345,7 @@ end function OPSGROUP:onafterLaserPause(From, Event, To) -- Debug message. - self:I(self.lid.."Switching LASER off temporarily") + self:T(self.lid.."Switching LASER off temporarily") -- "Destroy" the laser beam. self.spot.Laser:destroy() @@ -3326,7 +3380,7 @@ end function OPSGROUP:onafterLaserResume(From, Event, To) -- Debug info. - self:I(self.lid.."Resuming LASER") + self:T(self.lid.."Resuming LASER") -- Unset paused. self.spot.Paused=false @@ -3345,7 +3399,7 @@ function OPSGROUP:onafterLaserResume(From, Event, To) if target then -- Debug message. - self:I(self.lid.."Switching LASER on again at target ".. target:GetName()) + self:T(self.lid.."Switching LASER on again") self:LaserOn(target) end @@ -3480,6 +3534,10 @@ function OPSGROUP:SetLaserTarget(Target) -- Coordinate as target. self.spot.TargetType=0 self.spot.offsetTarget={x=0, y=0, z=0} + elseif Target:IsInstanceOf("SCENERY") then + -- Coordinate as target. + self.spot.TargetType=0 + self.spot.offsetTarget={x=0, y=1, z=0} else self:E(self.lid.."ERROR: LASER target should be a POSITIONABLE (GROUP, UNIT or STATIC) or a COORDINATE object!") return @@ -3543,13 +3601,13 @@ function OPSGROUP:_UpdateLaser() local unit=self.spot.TargetGroup:GetHighestThreat() if unit then - self:I(self.lid..string.format("Switching to target unit %s in the group", unit:GetName())) + self:T(self.lid..string.format("Switching to target unit %s in the group", unit:GetName())) self.spot.TargetUnit=unit -- We update the laser position in the next update cycle and then check the LOS. return else -- Switch laser off. - self:I(self.lid.."Target is not alive any more ==> switching LASER off") + self:T(self.lid.."Target is not alive any more ==> switching LASER off") self:LaserOff() return end @@ -3557,7 +3615,7 @@ function OPSGROUP:_UpdateLaser() else -- Switch laser off. - self:I(self.lid.."Target is not alive any more ==> switching LASER off") + self:T(self.lid.."Target is not alive any more ==> switching LASER off") self:LaserOff() return end @@ -3625,7 +3683,7 @@ end -- @param #string To To state. -- @param #OPSGROUP.Element Element The flight group element. function OPSGROUP:onafterElementDead(From, Event, To, Element) - self:T(self.lid..string.format("Element dead %s", Element.name)) + self:T(self.lid..string.format("Element dead %s at t=%.3f", Element.name, timer.getTime())) -- Set element status. self:_UpdateStatus(Element, OPSGROUP.ElementStatus.DEAD) @@ -3666,13 +3724,24 @@ function OPSGROUP:onafterElementDead(From, Event, To, Element) end +--- On before "Dead" event. +-- @param #OPSGROUP self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function OPSGROUP:onbeforeDead(From, Event, To) + if self.Ndestroyed==#self.elements then + self:Destroyed() + end +end + --- On after "Dead" event. -- @param #OPSGROUP self -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. function OPSGROUP:onafterDead(From, Event, To) - self:T(self.lid..string.format("Group dead!")) + self:T(self.lid..string.format("Group dead at t=%.3f", timer.getTime())) -- Delete waypoints so they are re-initialized at the next spawn. self.waypoints=nil @@ -3682,6 +3751,8 @@ function OPSGROUP:onafterDead(From, Event, To) for _,_mission in pairs(self.missionqueue) do local mission=_mission --Ops.Auftrag#AUFTRAG + self:T(self.lid.."Cancelling mission because group is dead! Mission name "..tostring(mission:GetName())) + self:MissionCancel(mission) mission:GroupDead(self) @@ -3713,7 +3784,7 @@ function OPSGROUP:onafterStop(From, Event, To) end -- Debug output. - self:T(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") + self:I(self.lid.."STOPPED! Unhandled events, cleared scheduler and removed from database.") end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -3885,6 +3956,11 @@ function OPSGROUP:_CheckGroupDone(delay) self:ScheduleOnce(delay, self._CheckGroupDone, self) else + if self:IsEngaging() then + self:UpdateRoute() + return + end + -- Get current waypoint. local waypoint=self:GetWaypoint(self.currentwp) @@ -4016,6 +4092,37 @@ function OPSGROUP:_CheckStuck() end + +--- Check damage. +-- @param #OPSGROUP self +-- @return #OPSGROUP self +function OPSGROUP:_CheckDamage() + + self.life=0 + local damaged=false + for _,_element in pairs(self.elements) do + local element=_element --Ops.OpsGroup#OPSGROUP + + -- Current life points. + local life=element.unit:GetLife() + + self.life=self.life+life + + if life0 and not self.outofMissiles then self.outofMissiles=true self:OutOfMissiles() - end - + end + + -- Check if group is engaging. + if self:IsEngaging() and ammo.Total==0 then + self:Disengage() + end + end end @@ -4241,6 +4354,9 @@ function OPSGROUP:InitWaypoints() -- Coordinate of the waypoint. local coordinate=COORDINATE:New(wp.x, wp.alt, wp.y) + -- Strange! + wp.speed=wp.speed or 0 + -- Speed at the waypoint. local speedknots=UTILS.MpsToKnots(wp.speed) @@ -4399,6 +4515,15 @@ function OPSGROUP._PassingWaypoint(group, opsgroup, uid) -- Trigger Rearming event. opsgroup:Rearming() + elseif opsgroup:IsRetreating() then + + -- Trigger Retreated event. + opsgroup:Retreated() + + elseif opsgroup:IsEngaging() then + + -- Nothing to do really. + else -- Trigger DetourReached event. diff --git a/Moose Development/Moose/Ops/Target.lua b/Moose Development/Moose/Ops/Target.lua index b6f9c396c..2c53abd12 100644 --- a/Moose Development/Moose/Ops/Target.lua +++ b/Moose Development/Moose/Ops/Target.lua @@ -230,7 +230,13 @@ function TARGET:AddObject(Object) for _,object in pairs(set.Set) do self:AddObject(object) - end + end + + elseif Object:IsInstanceOf("GROUP") then + + for _,unit in pairs(Object:GetUnits()) do + self:_AddObject(unit) + end else @@ -244,6 +250,19 @@ function TARGET:AddObject(Object) end +--- Check if TARGET is alive. +-- @param #TARGET self +-- @param #boolean If true, target is alive. +function TARGET:IsAlive() + return self:Is("Alive") +end + +--- Check if TARGET is dead. +-- @param #TARGET self +-- @param #boolean If true, target is dead. +function TARGET:IsDead() + return self:Is("Dead") +end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Start & Status @@ -302,7 +321,7 @@ function TARGET:onafterStatus(From, Event, To) end -- Log output verbose=1. - if self.verbose>=1 then + if self.verbose>=0 then local text=string.format("%s: Targets=%d/%d Life=%.1f/%.1f Damage=%.1f", fsmstate, self:CountTargets(), self.Ntargets0, self:GetLife(), self:GetLife0(), self:GetDamage()) if damaged then text=text.." Damaged!" @@ -311,7 +330,7 @@ function TARGET:onafterStatus(From, Event, To) end -- Log output verbose=2. - if self.verbose>=2 then + if self.verbose>=0 then local text="Target:" for i,_target in pairs(self.targets) do local target=_target --#TARGET.Object @@ -349,7 +368,8 @@ end -- @param #TARGET.Object Target Target object. function TARGET:onafterObjectDestroyed(From, Event, To, Target) - self:T(self.lid..string.format("Object %s destroyed", Target.Name)) + -- Debug message. + self:I(self.lid..string.format("Object %s destroyed", Target.Name)) -- Set target status. Target.Status=TARGET.ObjectStatus.DEAD @@ -405,16 +425,20 @@ function TARGET:OnEventUnitDeadOrLost(EventData) if EventData and EventData.IniUnitName then -- Debug info. - --self:T3(self.lid..string.format("EVENT: Unit %s dead or lost!", EventData.IniUnitName)) + self:I(self.lid..string.format("EVENT: Unit %s dead or lost!", EventData.IniUnitName)) -- Get target. local target=self:GetTargetByName(EventData.IniUnitName) + + if not target then + target=self:GetTargetByName(EventData.IniGroupName) + end -- Check if this is one of ours. if target and target.Status==TARGET.ObjectStatus.ALIVE then -- Debug message. - self:T3(self.lid..string.format("EVENT: target unit %s dead or lost ==> destroyed", target.Name)) + self:I(self.lid..string.format("EVENT: target unit %s dead or lost ==> destroyed", target.Name)) -- Trigger object destroyed event. self:ObjectDestroyed(target) @@ -525,12 +549,12 @@ function TARGET:_AddObject(Object) elseif Object:IsInstanceOf("COORDINATE") then - local coord=Object --Core.Point#COORDINATE + local coord=UTILS.DeepCopy(Object) --Core.Point#COORDINATE target.Type=TARGET.ObjectType.COORDINATE target.Name=coord:ToStringMGRS() - target.Coordinate=Object + target.Coordinate=coord target.Life0=1 target.Life=1 @@ -683,8 +707,12 @@ function TARGET:GetTargetVec3(Target) local object=Target.Object --Wrapper.Group#GROUP if object and object:IsAlive() then - - return object:GetVec3() + local vec3=object:GetVec3() + return vec3 + + else + + return nil end @@ -693,7 +721,10 @@ function TARGET:GetTargetVec3(Target) local object=Target.Object --Wrapper.Unit#UNIT if object and object:IsAlive() then - return object:GetVec3() + local vec3=object:GetVec3() + return vec3 + else + return nil end elseif Target.Type==TARGET.ObjectType.STATIC then @@ -701,7 +732,10 @@ function TARGET:GetTargetVec3(Target) local object=Target.Object --Wrapper.Static#STATIC if object and object:IsAlive() then - return object:GetVec3() + local vec3=object:GetVec3() + return vec3 + else + return nil end elseif Target.Type==TARGET.ObjectType.SCENERY then @@ -709,14 +743,18 @@ function TARGET:GetTargetVec3(Target) local object=Target.Object --Wrapper.Scenery#SCENERY if object then - return object:GetVec3() + local vec3=object:GetVec3() + return vec3 + else + return nil end elseif Target.Type==TARGET.ObjectType.AIRBASE then local object=Target.Object --Wrapper.Airbase#AIRBASE - return object:GetVec3() + local vec3=object:GetVec3() + return vec3 --if Target.Status==TARGET.ObjectStatus.ALIVE then --end @@ -725,7 +763,8 @@ function TARGET:GetTargetVec3(Target) local object=Target.Object --Core.Point#COORDINATE - return {x=object.x, y=object.y, z=object.z} + local vec3={x=object.x, y=object.y, z=object.z} + return vec3 end @@ -1007,7 +1046,7 @@ function TARGET:CountTargets() -- No target we can check! else - self:E(self.lid.."ERROR unknown target type") + self:E(self.lid.."ERROR: Unknown target type! Cannot count targets") end end