From c46028ff2df67093b6f66391e7cdd519229f6c2b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 10 Jul 2019 22:29:49 +0200 Subject: [PATCH 1/5] FOX v0.6.0 FOX v0.6.0 - Missile target constantly updated. - Increased safety distance to 200 m. - Added safety distance for BIG missiles > 50 kg TNT as 400 m. - More output to dcs.log file. SPAWN - enabled uncontrolled UTILS - LLDMS accurracy fix. AI_A2A_DISPATCHER - Squadron visible improvements ZONE - Added MarkZone function for F10 zone markings. AI_PATROL_ZONE - Some WP code improvements. --- Moose Development/Moose/AI/AI_A2A_Cap.lua | 4 +- .../Moose/AI/AI_A2A_Dispatcher.lua | 96 +++++- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 51 ++-- Moose Development/Moose/Core/Spawn.lua | 2 +- Moose Development/Moose/Core/Zone.lua | 27 ++ .../Moose/Functional/Detection.lua | 12 +- Moose Development/Moose/Functional/Fox.lua | 282 +++++++++++++++--- Moose Development/Moose/Utilities/Utils.lua | 12 +- 8 files changed, 397 insertions(+), 89 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Cap.lua b/Moose Development/Moose/AI/AI_A2A_Cap.lua index de9e184da..49b87b3c0 100644 --- a/Moose Development/Moose/AI/AI_A2A_Cap.lua +++ b/Moose Development/Moose/AI/AI_A2A_Cap.lua @@ -386,7 +386,7 @@ function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit ) if FirstAttackUnit and FirstAttackUnit:IsAlive() then -- If there is no attacker anymore, stop the engagement. - if AICap:IsAlive() then + if AICap and AICap:IsAlive() then local EngageRoute = {} @@ -417,6 +417,8 @@ function AI_A2A_CAP:onafterEngage( AICap, From, Event, To, AttackSetUnit ) local AttackUnit = AttackUnit -- Wrapper.Unit#UNIT self:T( { "Attacking Unit:", AttackUnit:GetName(), AttackUnit:IsAlive(), AttackUnit:IsAir() } ) if AttackUnit:IsAlive() and AttackUnit:IsAir() then + -- TODO: Add coalition check? Only attack units of if AttackUnit:GetCoalition()~=AICap:GetCoalition() + -- Maybe the detected set also contains AttackTasks[#AttackTasks+1] = AICap:TaskAttackUnit( AttackUnit ) end end diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index cafbfd139..c19b2fec7 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1092,8 +1092,9 @@ do -- AI_A2A_DISPATCHER self:GetParent( self, AI_A2A_DISPATCHER ).onafterStart( self, From, Event, To ) -- Spawn the resources. - for SquadronName, DefenderSquadron in pairs( self.DefenderSquadrons ) do - DefenderSquadron.Resource = {} + for SquadronName,_DefenderSquadron in pairs( self.DefenderSquadrons ) do + local DefenderSquadron=_DefenderSquadron --#AI_A2A_DISPATCHER.Squadron + DefenderSquadron.Resources = {} if DefenderSquadron.ResourceCount then for Resource = 1, DefenderSquadron.ResourceCount do self:ParkDefender( DefenderSquadron ) @@ -1107,18 +1108,39 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The squadron. function AI_A2A_DISPATCHER:ParkDefender( DefenderSquadron ) + local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) + local Spawn = DefenderSquadron.Spawn[ TemplateID ] -- Core.Spawn#SPAWN + Spawn:InitGrouping( 1 ) + local SpawnGroup + if self:IsSquadronVisible( DefenderSquadron.Name ) then + + local Grouping=DefenderSquadron.Grouping or self.DefenderDefault.Grouping + + Grouping=1 + + Spawn:InitGrouping(Grouping) + SpawnGroup = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, SPAWN.Takeoff.Cold ) + local GroupName = SpawnGroup:GetName() + DefenderSquadron.Resources = DefenderSquadron.Resources or {} + DefenderSquadron.Resources[TemplateID] = DefenderSquadron.Resources[TemplateID] or {} DefenderSquadron.Resources[TemplateID][GroupName] = {} DefenderSquadron.Resources[TemplateID][GroupName] = SpawnGroup + + self.uncontrolled=self.uncontrolled or {} + self.uncontrolled[DefenderSquadron.Name]=self.uncontrolled[DefenderSquadron.Name] or {} + + table.insert(self.uncontrolled[DefenderSquadron.Name], {group=SpawnGroup, name=GroupName, grouping=Grouping}) end + end @@ -1701,10 +1723,23 @@ do -- AI_A2A_DISPATCHER local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_DISPATCHER.Squadron DefenderSquadron.Uncontrolled = true + + -- For now, grouping is forced to 1 due to other parts of the class which would not work well with grouping>1. + DefenderSquadron.Grouping=1 + + -- Get free parking for fighter aircraft. + local nfreeparking=DefenderSquadron.Airbase:GetFreeParkingSpotsNumber(AIRBASE.TerminalType.FighterAircraft, true) + + -- Take number of free parking spots if no resource count was specifed. + DefenderSquadron.ResourceCount=DefenderSquadron.ResourceCount or nfreeparking + + -- Check that resource count is not larger than free parking spots. + DefenderSquadron.ResourceCount=math.min(DefenderSquadron.ResourceCount, nfreeparking) + -- Set uncontrolled spawning option. for SpawnTemplate,_DefenderSpawn in pairs( self.DefenderSpawns ) do local DefenderSpawn=_DefenderSpawn --Core.Spawn#SPAWN - DefenderSpawn:InitUnControlled() + DefenderSpawn:InitUnControlled(true) end end @@ -2751,7 +2786,7 @@ do -- AI_A2A_DISPATCHER --- Get squadron from defender. -- @param #AI_A2A_DISPATCHER self -- @param Wrapper.Group#GROUP Defender The defender group. - -- @return ? + -- @return #AI_A2A_DISPATCHER.Squadron Squadron The squadron. function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() @@ -2800,8 +2835,9 @@ do -- AI_A2A_DISPATCHER if AIGroup and AIGroup:IsAlive() then -- Check if the CAP is patrolling or engaging. If not, this is not a valid CAP, even if it is alive! -- The CAP could be damaged, lost control, or out of fuel! - if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) - or DefenderTask.Fsm:Is( "Started" ) then + --env.info("FF fsm state "..tostring(DefenderTask.Fsm:GetState())) + if DefenderTask.Fsm:Is( "Patrolling" ) or DefenderTask.Fsm:Is( "Engaging" ) or DefenderTask.Fsm:Is( "Refuelling" ) or DefenderTask.Fsm:Is( "Started" ) then + --env.info("FF capcount "..CapCount) CapCount = CapCount + 1 end end @@ -2908,16 +2944,48 @@ do -- AI_A2A_DISPATCHER function AI_A2A_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded ) local SquadronName = DefenderSquadron.Name + DefendersNeeded = DefendersNeeded or 4 + local DefenderGrouping = DefenderSquadron.Grouping or self.DefenderDefault.Grouping + DefenderGrouping = ( DefenderGrouping < DefendersNeeded ) and DefenderGrouping or DefendersNeeded - if self:IsSquadronVisible( SquadronName ) then + --env.info(string.format("FF resource activate: Squadron=%s grouping=%d needed=%d visible=%s", SquadronName, DefenderGrouping, DefendersNeeded, tostring(self:IsSquadronVisible( SquadronName )))) + if self:IsSquadronVisible( SquadronName ) then + + local n=#self.uncontrolled[SquadronName] + + if n>0 then + -- Random number 1,...n + local id=math.random(n) + + -- Pick a random defender group. + local Defender=self.uncontrolled[SquadronName][id].group --Wrapper.Group#GROUP + + -- Start uncontrolled group. + Defender:StartUncontrolled() + + -- Get grouping. + DefenderGrouping=self.uncontrolled[SquadronName][id].grouping + + -- Add defender to squadron. + self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) + + -- Remove defender from uncontrolled table. + table.remove(self.uncontrolled[SquadronName], id) + + return Defender, DefenderGrouping + else + return nil,0 + end + -- Here we CAP the new planes. -- The Resources table is filled in advance. local TemplateID = math.random( 1, #DefenderSquadron.Spawn ) -- Choose the template. - + + --[[ -- We determine the grouping based on the parameters set. self:F( { DefenderGrouping = DefenderGrouping } ) @@ -2960,8 +3028,16 @@ do -- AI_A2A_DISPATCHER self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) return Defender, DefenderGrouping end + ]] + else + + ---------------------------- + --- Squadron not visible --- + ---------------------------- + local Spawn = DefenderSquadron.Spawn[ math.random( 1, #DefenderSquadron.Spawn ) ] -- Core.Spawn#SPAWN + if DefenderGrouping then Spawn:InitGrouping( DefenderGrouping ) else @@ -2969,8 +3045,11 @@ do -- AI_A2A_DISPATCHER end local TakeoffMethod = self:GetSquadronTakeoff( SquadronName ) + local Defender = Spawn:SpawnAtAirbase( DefenderSquadron.Airbase, TakeoffMethod, DefenderSquadron.TakeoffAltitude or self.DefenderDefault.TakeoffAltitude ) -- Wrapper.Group#GROUP + self:AddDefenderToSquadron( DefenderSquadron, Defender, DefenderGrouping ) + return Defender, DefenderGrouping end @@ -3261,6 +3340,7 @@ do -- AI_A2A_DISPATCHER Dispatcher:ParkDefender( Squadron ) end end + end -- if DefenderGCI then end -- while ( DefendersNeeded > 0 ) do end diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 5071d1729..6b1315440 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -349,13 +349,16 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) local CurrentCoord = AIPatrol:GetCoordinate() - if self.racetrack then - - -- Random altitude. - local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude) + -- Random altitude. + local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude) - -- Random speed in km/h. - local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed) + -- Random speed in km/h. + local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed) + + -- First waypoint is current position. + PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current") + + if self.racetrack then -- Random heading. local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) @@ -379,6 +382,8 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE local c2=c1:Translate(leg, heading):SetAltitude(altitude) + self:SetTargetDistance(c0) -- For RTB status check + -- Debug: self:T(string.format("Patrol zone race track: v=%.1f knots, h=%.1f ft, heading=%03d, leg=%d m, t=%s sec", UTILS.KmphToKnots(speedkmh), UTILS.MetersToFeet(altitude), heading, leg, tostring(duration))) --c1:MarkToAll("Race track c1") @@ -390,37 +395,25 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) -- Task function to redo the patrol at other random position. local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self) - + -- Controlled task with task condition. local taskCond=AIPatrol:TaskCondition(nil, nil, nil, nil, duration, nil) local taskCont=AIPatrol:TaskControlled(taskOrbit, taskCond) - PatrolRoute[1]=CurrentCoord:WaypointAirTurningPoint(nil, speedkmh, {}, "Current") - PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "Orbit") + -- Second waypoint + PatrolRoute[2]=c1:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskCont, taskPatrol}, "CAP Orbit") else - - local ToTargetCoord = self.PatrolZone:GetRandomPointVec2() - ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) ) + + -- Target coordinate. + local ToTargetCoord=self.PatrolZone:GetRandomCoordinate() --Core.Point#COORDINATE + ToTargetCoord:SetAltitude(altitude) + self:SetTargetDistance( ToTargetCoord ) -- For RTB status check - local ToTargetSpeed = math.random( self.PatrolMinSpeed, self.PatrolMaxSpeed ) - - --- Create a route point of type air. - local ToPatrolRoutePoint = ToTargetCoord:WaypointAir( - self.PatrolAltType, - POINT_VEC3.RoutePointType.TurningPoint, - POINT_VEC3.RoutePointAction.TurningPoint, - ToTargetSpeed, - true - ) - - PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint - PatrolRoute[#PatrolRoute+1] = ToPatrolRoutePoint - - local Tasks = {} - Tasks[#Tasks+1] = AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self ) - PatrolRoute[#PatrolRoute].task = AIPatrol:TaskCombo( Tasks ) + local taskReRoute=AIPatrol:TaskFunction( "AI_A2A_PATROL.PatrolRoute", self ) + PatrolRoute[2]=ToTargetCoord:WaypointAirTurningPoint(self.PatrolAltType, speedkmh, {taskReRoute}, "Patrol Point") + end -- ROE diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index ff6387c5a..f9cd1b97b 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -1816,7 +1816,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT SpawnTemplate.x = PointVec3.x SpawnTemplate.y = PointVec3.z - SpawnTemplate.uncontrolled = nil + SpawnTemplate.uncontrolled = self.SpawnUnControlled -- Spawn group. local GroupSpawned = self:SpawnWithIndex( self.SpawnIndex ) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 871404e96..52c27fd05 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -442,6 +442,33 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius ) return self end +--- Mark the zone with markers on the F10 map. +-- @param #ZONE_RADIUS self +-- @param #number Points (Optional) The amount of points in the circle. Default 360. +-- @return #ZONE_RADIUS self +function ZONE_RADIUS:MarkZone(Points) + + local Point = {} + local Vec2 = self:GetVec2() + + Points = Points and Points or 360 + + local Angle + local RadialBase = math.pi*2 + + for Angle = 0, 360, (360 / Points ) do + + local Radial = Angle * RadialBase / 360 + + Point.x = Vec2.x + math.cos( Radial ) * self:GetRadius() + Point.y = Vec2.y + math.sin( Radial ) * self:GetRadius() + + COORDINATE:NewFromVec2(Point):MarkToAll(self:GetName()) + + end + +end + --- Bounds the zone with tires. -- @param #ZONE_RADIUS self -- @param #number Points (optional) The amount of points in the circle. Default 360. diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 851977790..d711ad9b6 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -458,15 +458,18 @@ do -- DETECTION_BASE -- @param #string From The From State string. -- @param #string Event The Event string. -- @param #string To The To State string. + -- @param #table Units Table of detected units. --- Synchronous Event Trigger for Event Detected. -- @function [parent=#DETECTION_BASE] Detected -- @param #DETECTION_BASE self + -- @param #table Units Table of detected units. --- Asynchronous Event Trigger for Event Detected. -- @function [parent=#DETECTION_BASE] __Detected -- @param #DETECTION_BASE self -- @param #number Delay The delay in seconds. + -- @param #table Units Table of detected units. self:AddTransition( "Detecting", "DetectedItem", "Detecting" ) @@ -586,7 +589,7 @@ do -- DETECTION_BASE local HasDetectedObjects = false - if Detection:IsAlive() then + if Detection and Detection:IsAlive() then --self:T( { "DetectionGroup is Alive", DetectionGroup:GetName() } ) @@ -817,12 +820,17 @@ do -- DETECTION_BASE end self:CreateDetectionItems() -- Polymorphic call to Create/Update the DetectionItems list for the DETECTION_ class grouping method. + for DetectedItemID, DetectedItem in pairs( self.DetectedItems ) do + self:UpdateDetectedItemDetection( DetectedItem ) + self:CleanDetectionItem( DetectedItem, DetectedItemID ) -- Any DetectionItem that has a Set with zero elements in it, must be removed from the DetectionItems list. + if DetectedItem then self:__DetectedItem( 0.1, DetectedItem ) end + end end @@ -834,7 +842,7 @@ do -- DETECTION_BASE do -- DetectionItems Creation - -- Clean the DetectedItem table. + --- Clean the DetectedItem table. -- @param #DETECTION_BASE self -- @return #DETECTION_BASE function DETECTION_BASE:CleanDetectionItem( DetectedItem, DetectedItemID ) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 69afa5761..e2ab614f3 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -40,7 +40,9 @@ -- @field #table launchzones Table of launch zones. -- @field Core.Set#SET_GROUP protectedset Set of protected groups. -- @field #number explosionpower Power of explostion when destroying the missile in kg TNT. Default 5 kg TNT. --- @field #number explosiondist Missile player distance in meters for destroying the missile. Default 100 m. +-- @field #number explosiondist Missile player distance in meters for destroying smaller missiles. Default 200 m. +-- @field #number explosiondist2 Missile player distance in meters for destroying big missiles. Default 400 m. +-- @field #number bigmissilemass Explosion power of big missiles. Default 50 kg TNT. Big missiles will be destroyed earlier. -- @field #number dt50 Time step [sec] for missile position updates if distance to target > 50 km. Default 5 sec. -- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec. -- @field #number dt05 Time step [sec] for missile position updates if distance to target > 5 km and < 10 km. Default 0.5 sec. @@ -136,8 +138,10 @@ FOX = { safezones = {}, launchzones = {}, protectedset = nil, - explosionpower = 5, - explosiondist = 100, + explosionpower = 0.5, + explosiondist = 200, + explosiondist2 = 400, + bigmissilemass = 50, destroy = nil, dt50 = 5, dt10 = 1, @@ -169,14 +173,19 @@ FOX = { -- @field Wrapper.Unit#UNIT weapon Missile weapon unit. -- @field #boolean active If true the missile is active. -- @field #string missileType Type of missile. +-- @field #string missileName Name of missile. -- @field #number missileRange Range of missile in meters. +-- @field #number fuseDist Fuse distance in meters. +-- @field #number explosive Explosive mass in kg TNT. -- @field Wrapper.Unit#UNIT shooterUnit Unit that shot the missile. -- @field Wrapper.Group#GROUP shooterGroup Group that shot the missile. -- @field #number shooterCoalition Coalition side of the shooter. -- @field #string shooterName Name of the shooter unit. --- @field #number shotTime Abs mission time in seconds the missile was fired. +-- @field #number shotTime Abs. mission time in seconds the missile was fired. -- @field Core.Point#COORDINATE shotCoord Coordinate where the missile was fired. -- @field Wrapper.Unit#UNIT targetUnit Unit that was targeted. +-- @field #string targetName Name of the target unit or "unknown". +-- @field #string targetOrig Name of the "original" target, i.e. the one right after launched. -- @field #FOX.PlayerData targetPlayer Player that was targeted or nil. --- Main radio menu on group level. @@ -189,7 +198,7 @@ FOX.MenuF10Root=nil --- FOX class version. -- @field #string version -FOX.version="0.5.1" +FOX.version="0.6.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -217,6 +226,10 @@ function FOX:New() self:SetDefaultMissileDestruction(true) self:SetDefaultLaunchAlerts(true) self:SetDefaultLaunchMarks(true) + + -- Explosion/destruction defaults. + self:SetExplosionDistance() + self:SetExplosionDistanceBigMissiles() self:SetExplosionPower() -- Start State. @@ -358,12 +371,14 @@ function FOX:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.Shot) + --self:HandleEvent(EVENTS.Hit) + if self.Debug then self:TraceClass(self.ClassName) self:TraceLevel(2) end - self:__Status(-10) + self:__Status(-20) end --- On after Stop event. Stops the missile trainer and unhandles events. @@ -378,8 +393,10 @@ function FOX:onafterStop(From, Event, To) env.info(text) -- Handle events: - self:UnhandleEvent(EVENTS.Birth) - self:UnhandleEvent(EVENTS.Shot) + self:UnHandleEvent(EVENTS.Birth) + self:UnHandleEvent(EVENTS.Shot) + + --self:UnhandleEvent(EVENTS.Hit) end @@ -433,28 +450,42 @@ function FOX:AddProtectedGroup(group) return self end ---- Set explosion power. +--- Set explosion power. This is an "artificial" explosion generated when the missile is destroyed. Just for the visual effect. +-- Don't set the explosion power too big or it will harm the aircraft in the vicinity. -- @param #FOX self --- @param #number power Explosion power in kg TNT. Default 5. +-- @param #number power Explosion power in kg TNT. Default 0.5 kg. -- @return #FOX self function FOX:SetExplosionPower(power) - self.explosionpower=power or 5 + self.explosionpower=power or 0.5 return self end --- Set missile-player distance when missile is destroyed. -- @param #FOX self --- @param #number distance Distance in meters. Default 100 m. +-- @param #number distance Distance in meters. Default 200 m. -- @return #FOX self function FOX:SetExplosionDistance(distance) - self.explosiondist=distance or 100 + self.explosiondist=distance or 200 return self end +--- Set missile-player distance when BIG missiles are destroyed. +-- @param #FOX self +-- @param #number distance Distance in meters. Default 400 m. +-- @param #number explosivemass Explosive mass of missile in kg TNT. Default 50 kg. +-- @return #FOX self +function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass) + + self.explosiondist2=distance or 400 + + self.bigmissilemass=explosivemass or 50 + + return self +end --- Disable F10 menu for all players. -- @param #FOX self @@ -558,8 +589,11 @@ function FOX:onafterStatus(From, Event, To) -- Get FSM state. local fsmstate=self:GetState() + local time=timer.getAbsTime() + local clock=UTILS.SecondsToClock(time) + -- Status. - self:I(self.lid..string.format("Missile trainer status: %s", fsmstate)) + self:I(self.lid..string.format("Missile trainer status %s: %s", clock, fsmstate)) -- Check missile status. self:_CheckMissileStatus() @@ -664,6 +698,9 @@ function FOX:_CheckMissileStatus() text=text..string.format("\n[%d] %s: active=%s, range=%.1f NM, heading=%03d, target=%s, player=%s, missilename=%s", i, mtype, active, range, heading, targetname, playername, missile.missileName) end + if #self.missiles==0 then + text=text.." none" + end self:I(self.lid..text) -- Remove inactive missiles. @@ -722,7 +759,7 @@ end function FOX:onafterMissileLaunch(From, Event, To, missile) -- Tracking info and init of last bomb position. - self:I(FOX.lid..string.format("FOX: Tracking %s - %s.", missile.missileType, missile.missileName)) + self:I(FOX.lid..string.format("FOX: Tracking %s - %s - target %s", missile.missileType, missile.missileName, tostring(missile.targetName))) -- Loop over players. for _,_player in pairs(self.players) do @@ -803,6 +840,9 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Missile velocity in m/s. local missileVelocity=UTILS.VecNorm(_ordnance:getVelocity()) + -- Update missile target if necessary. + self:GetMissileTarget(missile) + if missile.targetUnit then ----------------------------------- @@ -825,7 +865,31 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) ------------------------------------ -- Missile has NO specific target -- - ------------------------------------ + ------------------------------------ + + -- TODO: This might cause a problem with wingman. Even if the shooter itself is excluded from the check, it's wingmen are not. + -- That would trigger the distance check right after missile launch if things to wrong. + -- + -- Possible solutions: + -- * Time check: enable this check after X seconds after missile was fired. What is X? + -- * Coalition check. But would not work in training situations where blue on blue is valid! + -- * At least enable it for surface-to-air missiles. + + --[[ + local function _GetTarget(_unit) + local unit=_unit --Wrapper.Unit#UNIT + + -- Player position. + local playerCoord=unit:GetCoordinate() + + -- Distance. + local dist=missileCoord:Get3DDistance(playerCoord) + + -- Update mindist if necessary. Only include players in range of missile + 50% safety margin. + if dist<=self.explosiondist then + return unit + end + end -- Distance to closest player. local mindist=nil @@ -843,17 +907,59 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Distance. local dist=missileCoord:Get3DDistance(playerCoord) - -- Maxrange from launch point to player. - local maxrange=playerCoord:Get3DDistance(missile.shotCoord) + -- Distance from shooter to player. + local Dshooter2player=playerCoord:Get3DDistance(missile.shotCoord) - -- Update mindist if necessary. Only include players in range of missile. - if (mindist==nil or dist=self.bigmissilemass + end - -- If missile is 100 m from target ==> destroy missile if in safe zone. - if distance<=self.explosiondist and self:_CheckCoordSafe(targetCoord)then + -- If missile is 150 m from target ==> destroy missile if in safe zone. + if destroymissile and self:_CheckCoordSafe(targetCoord) then -- Destroy missile. - self:T(self.lid..string.format("Destroying missile at distance %.1f m", distance)) + self:I(self.lid..string.format("Destroying missile %s(%s) fired by %s aimed at %s [player=%s] at distance %.1f m", + missile.missileType, missile.missileName, missile.shooterName, target:GetName(), tostring(missile.targetPlayer~=nil), distance)) _ordnance:destroy() -- Missile is not active any more. missile.active=false + -- Debug smoke. + missileCoord:SmokeRed() + targetCoord:SmokeGreen() + -- Create event. self:MissileDestroyed(missile) -- Little explosion for the visual effect. - if self.explosionpower>0 then + if self.explosionpower>0 and distance>=50 then missileCoord:Explosion(self.explosionpower) end + -- Message to target. local text=string.format("Destroying missile. %s", self:_DeadText()) MESSAGE:New(text, 10):ToGroup(target:GetGroup()) @@ -898,6 +1023,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Terminate timer. return nil + else -- Time step. @@ -922,10 +1048,15 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Check again in dt seconds. return timer.getTime()+dt end + else + -- Destroy missile. + self:I(self.lid..string.format("Missile %s(%s) fired by %s has no current target. Checking back in 0.1 sec.", missile.missileType, missile.missileName, missile.shooterName)) + return timer.getTime()+0.1 + -- No target ==> terminate timer. - return nil + --return nil end else @@ -945,7 +1076,7 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) MESSAGE:New(text, 10):ToClient(player.client) -- Increase defeated counter. - player.defeated=player.defeated+1 + player.defeated=player.defeated+1 end end @@ -1041,11 +1172,52 @@ function FOX:OnEventBirth(EventData) end end +--- Get missile target. +-- @param #FOX self +-- @param #FOX.MissileData missile The missile data table. +function FOX:GetMissileTarget(missile) + + local target=nil + local targetName="unknown" + local targetUnit=nil --Wrapper.Unit#UNIT + + if missile.weapon and missile.weapon:isExist() then + + -- Get target of missile. + target=missile.weapon:getTarget() + + -- Get the target unit. Note if if _target is not nil, the unit can sometimes not be found! + if target then + self:T2({missiletarget=target}) + + -- Get target unit. + targetUnit=UNIT:Find(target) + + if targetUnit then + targetName=targetUnit:GetName() + + missile.targetUnit=targetUnit + missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit) + end + + end + end + + -- Missile got new target. + if missile.targetName and missile.targetName~=targetName then + self:I(self.lid..string.format("Missile %s(%s) changed target to %s. Previous target was %s.", missile.missileType, missile.missileName, targetName, missile.targetName)) + end + + -- Set target name. + missile.targetName=targetName + +end + --- FOX event handler for event shot (when a unit releases a rocket or bomb (but not a fast firing gun). -- @param #FOX self -- @param Core.Event#EVENTDATA EventData function FOX:OnEventShot(EventData) - self:I({eventshot = EventData}) + self:T2({eventshot=EventData}) if EventData.Weapon==nil then return @@ -1062,7 +1234,7 @@ function FOX:OnEventShot(EventData) -- Weapon descriptor. local desc=EventData.Weapon:getDesc() - self:E({desc=desc}) + self:T2({desc=desc}) -- Weapon category: 0=Shell, 1=Missile, 2=Rocket, 3=BOMB local weaponcategory=desc.category @@ -1091,15 +1263,6 @@ function FOX:OnEventShot(EventData) return end - -- Get the target unit. Note if if _target is not nil, the unit can sometimes not be found! - if _target then - self:E({target=_target}) - --_targetName=Unit.getName(_target) - --_targetUnit=UNIT:FindByName(_targetName) - _targetUnit=UNIT:Find(_target) - end - self:E(FOX.lid..string.format("EVENT SHOT: Target name = %s", tostring(_targetName))) - -- Track missiles of type AAM=1, SAM=2 or OTHER=6 local _track = weaponcategory==1 and missilecategory and (missilecategory==1 or missilecategory==2 or missilecategory==6) @@ -1119,8 +1282,13 @@ function FOX:OnEventShot(EventData) missile.shooterName=EventData.IniUnitName missile.shotTime=timer.getAbsTime() missile.shotCoord=EventData.IniUnit:GetCoordinate() - missile.targetUnit=_targetUnit - missile.targetPlayer=self:_GetPlayerFromUnit(missile.targetUnit) + missile.fuseDist=desc.fuseDist + missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass + + -- Set missile target name, unit and player. + self:GetMissileTarget(missile) + + self:I(FOX.lid..string.format("EVENT SHOT: Target name = %s, fuse dist=%s, explosive=%s", tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive))) -- Only track if target was a player or target is protected. if missile.targetPlayer or self:_IsProtected(missile.targetUnit) then @@ -1137,6 +1305,36 @@ function FOX:OnEventShot(EventData) end +--- FOX event handler for event hit. +-- @param #FOX self +-- @param Core.Event#EVENTDATA EventData +function FOX:OnEventHit(EventData) + self:T({eventhit = EventData}) + + -- Nil checks. + if EventData.Weapon==nil then + return + end + if EventData.IniUnit==nil then + return + end + if EventData.TgtUnit==nil then + return + end + + local weapon=EventData.Weapon + local weaponname=weapon:getName() + + for i,_missile in pairs(self.missiles) do + local missile=_missile --#FOX.MissileData + if missile.missileName==weaponname then + self:I(self.lid..string.format("WARNING: Missile %s (%s) hit target %s. Missile trainer target was %s.", missile.missileType, missile.missileName, EventData.TgtUnitName, missile.targetName)) + self:I({missile=missile}) + return + end + end + +end ----------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- RADIO MENU Functions diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 873b1b4b3..f32caa744 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -429,12 +429,12 @@ UTILS.tostringLL = function( lat, lon, acc, DMS) local secFrmtStr -- create the formatting string for the seconds place secFrmtStr = '%02d' --- if acc <= 0 then -- no decimal place. --- secFrmtStr = '%02d' --- else --- local width = 3 + acc -- 01.310 - that's a width of 6, for example. --- secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' --- end + if acc <= 0 then -- no decimal place. + secFrmtStr = '%02d' + else + local width = 3 + acc -- 01.310 - that's a width of 6, for example. + secFrmtStr = '%0' .. width .. '.' .. acc .. 'f' + end return string.format('%03d', latDeg) .. ' ' .. string.format('%02d', latMin) .. '\' ' .. string.format(secFrmtStr, latSec) .. '"' .. latHemi .. ' ' .. string.format('%03d', lonDeg) .. ' ' .. string.format('%02d', lonMin) .. '\' ' .. string.format(secFrmtStr, lonSec) .. '"' .. lonHemi From 03042c8282d44016e9b0aa93cd02eb2a3fb40855 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Jul 2019 09:41:13 +0200 Subject: [PATCH 2/5] AIRBOSS v1.0.4 One stack per flight group in CASE II/III --- Moose Development/Moose/Functional/Fox.lua | 2 +- Moose Development/Moose/Ops/Airboss.lua | 11 ++++++----- 2 files changed, 7 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index e2ab614f3..91518375e 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -476,7 +476,7 @@ end --- Set missile-player distance when BIG missiles are destroyed. -- @param #FOX self -- @param #number distance Distance in meters. Default 400 m. --- @param #number explosivemass Explosive mass of missile in kg TNT. Default 50 kg. +-- @param #number explosivemass Explosive mass of missile threshold in kg TNT. Default 50 kg. -- @return #FOX self function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 74b3778a3..82a088c08 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -1681,12 +1681,13 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.0.3" +AIRBOSS.version="1.0.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- - + +-- TODO: Handle tanker and AWACS. Put them into pattern. -- TODO: Handle cases where AI crashes on carrier deck ==> Clean up deck. -- TODO: Player eject and crash debrief "gradings". -- TODO: PWO during case 2/3. @@ -6862,8 +6863,8 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) local n=flight.flag if n>0 then - if flight.ai then - stack[n]=0 -- AI get one stack on their own. + if flight.ai or flight.case>1 then + stack[n]=0 -- AI get one stack on their own. Also CASE II/III get one stack each. else stack[n]=stack[n]-1 end @@ -6878,7 +6879,7 @@ function AIRBOSS:_GetFreeStack(ai, case, empty) local nfree=nil for i=1,nmaxstacks do self:T2(self.lid..string.format("FF Stack[%d]=%d", i, stack[i])) - if ai or empty then + if ai or empty or case>1 then -- AI need the whole stack. if stack[i]==self.NmaxStack then nfree=i From 5caab9c6f37a3335fc9af691220cf3ae15bf6e3a Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Jul 2019 18:38:13 +0200 Subject: [PATCH 3/5] FOX - adjustment of destruction distance - tracking missiles with "unknown" target --- Moose Development/Moose/Functional/Fox.lua | 45 ++++++++++++---------- 1 file changed, 25 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 91518375e..99c838a06 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -41,7 +41,7 @@ -- @field Core.Set#SET_GROUP protectedset Set of protected groups. -- @field #number explosionpower Power of explostion when destroying the missile in kg TNT. Default 5 kg TNT. -- @field #number explosiondist Missile player distance in meters for destroying smaller missiles. Default 200 m. --- @field #number explosiondist2 Missile player distance in meters for destroying big missiles. Default 400 m. +-- @field #number explosiondist2 Missile player distance in meters for destroying big missiles. Default 500 m. -- @field #number bigmissilemass Explosion power of big missiles. Default 50 kg TNT. Big missiles will be destroyed earlier. -- @field #number dt50 Time step [sec] for missile position updates if distance to target > 50 km. Default 5 sec. -- @field #number dt10 Time step [sec] for missile position updates if distance to target > 10 km and < 50 km. Default 1 sec. @@ -138,9 +138,9 @@ FOX = { safezones = {}, launchzones = {}, protectedset = nil, - explosionpower = 0.5, + explosionpower = 0.1, explosiondist = 200, - explosiondist2 = 400, + explosiondist2 = 500, bigmissilemass = 50, destroy = nil, dt50 = 5, @@ -371,7 +371,9 @@ function FOX:onafterStart(From, Event, To) self:HandleEvent(EVENTS.Birth) self:HandleEvent(EVENTS.Shot) - --self:HandleEvent(EVENTS.Hit) + if self.Debug then + self:HandleEvent(EVENTS.Hit) + end if self.Debug then self:TraceClass(self.ClassName) @@ -396,7 +398,9 @@ function FOX:onafterStop(From, Event, To) self:UnHandleEvent(EVENTS.Birth) self:UnHandleEvent(EVENTS.Shot) - --self:UnhandleEvent(EVENTS.Hit) + if self.Debug then + self:UnhandleEvent(EVENTS.Hit) + end end @@ -453,11 +457,11 @@ end --- Set explosion power. This is an "artificial" explosion generated when the missile is destroyed. Just for the visual effect. -- Don't set the explosion power too big or it will harm the aircraft in the vicinity. -- @param #FOX self --- @param #number power Explosion power in kg TNT. Default 0.5 kg. +-- @param #number power Explosion power in kg TNT. Default 0.1 kg. -- @return #FOX self function FOX:SetExplosionPower(power) - self.explosionpower=power or 0.5 + self.explosionpower=power or 0.1 return self end @@ -475,12 +479,12 @@ end --- Set missile-player distance when BIG missiles are destroyed. -- @param #FOX self --- @param #number distance Distance in meters. Default 400 m. +-- @param #number distance Distance in meters. Default 500 m. -- @param #number explosivemass Explosive mass of missile threshold in kg TNT. Default 50 kg. -- @return #FOX self function FOX:SetExplosionDistanceBigMissiles(distance, explosivemass) - self.explosiondist2=distance or 400 + self.explosiondist2=distance or 500 self.bigmissilemass=explosivemass or 50 @@ -875,7 +879,6 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- * Coalition check. But would not work in training situations where blue on blue is valid! -- * At least enable it for surface-to-air missiles. - --[[ local function _GetTarget(_unit) local unit=_unit --Wrapper.Unit#UNIT @@ -920,8 +923,8 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) if self.protectedset then - -- Distance to closest player. - local mindist=nil + -- Distance to closest protected unit. + mindist=nil for _,_group in pairs(self.protectedset:GetSet()) do local group=_group --Wrapper.Group#GROUP @@ -955,10 +958,8 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) end if target then - self:I(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s.", missile.missileType, target:GetName())) + self:I(self.lid..string.format("Missile %s with NO explicit target got closest unit to missile as target %s. Dist=%s m", missile.missileType, target:GetName(), tostring(mindist))) end - - --]] end @@ -1001,8 +1002,10 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) missile.active=false -- Debug smoke. - missileCoord:SmokeRed() - targetCoord:SmokeGreen() + if self.Debug then + missileCoord:SmokeRed() + targetCoord:SmokeGreen() + end -- Create event. self:MissileDestroyed(missile) @@ -1284,14 +1287,16 @@ function FOX:OnEventShot(EventData) missile.shotCoord=EventData.IniUnit:GetCoordinate() missile.fuseDist=desc.fuseDist missile.explosive=desc.warhead.explosiveMass or desc.warhead.shapedExplosiveMass + missile.targetOrig=missile.targetName -- Set missile target name, unit and player. self:GetMissileTarget(missile) - self:I(FOX.lid..string.format("EVENT SHOT: Target name = %s, fuse dist=%s, explosive=%s", tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive))) + self:I(FOX.lid..string.format("EVENT SHOT: Shooter=%s %s(%s) ==> Target=%s, fuse dist=%s, explosive=%s", + tostring(missile.shooterName), tostring(missile.missileType), tostring(missile.missileName), tostring(missile.targetName), tostring(missile.fuseDist), tostring(missile.explosive))) - -- Only track if target was a player or target is protected. - if missile.targetPlayer or self:_IsProtected(missile.targetUnit) then + -- Only track if target was a player or target is protected. Saw the 9M311 missiles have no target! + if missile.targetPlayer or self:_IsProtected(missile.targetUnit) or missile.targetName=="unknown" then -- Add missile table. table.insert(self.missiles, missile) From 7fdc049079a40f77a22ad0bd55c3bea46665a10f Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Jul 2019 20:39:07 +0200 Subject: [PATCH 4/5] Update Fox.lua --- Moose Development/Moose/Functional/Fox.lua | 22 +++++++++++++++------- 1 file changed, 15 insertions(+), 7 deletions(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 99c838a06..153c2c1b3 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -972,6 +972,12 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) -- Distance from missile to target. local distance=missileCoord:Get3DDistance(targetCoord) + -- Distance missile to shooter. + local distShooter=nil + if missile.shooterUnit and missile.shooterUnit:IsAlive() then + distShooter=missileCoord:Get3DDistance(missile.shooterUnit:GetCoordinate()) + end + -- Debug output. if self.Debug then @@ -1011,16 +1017,18 @@ function FOX:onafterMissileLaunch(From, Event, To, missile) self:MissileDestroyed(missile) -- Little explosion for the visual effect. - if self.explosionpower>0 and distance>=50 then + if self.explosionpower>0 and distance>50 and (distShooter==nil or (distShooter and distShooter>50)) then missileCoord:Explosion(self.explosionpower) end - - -- Message to target. - local text=string.format("Destroying missile. %s", self:_DeadText()) - MESSAGE:New(text, 10):ToGroup(target:GetGroup()) - - -- Increase dead counter. + + -- Target was a player. if missile.targetPlayer then + + -- Message to target. + local text=string.format("Destroying missile. %s", self:_DeadText()) + MESSAGE:New(text, 10):ToGroup(target:GetGroup()) + + -- Increase dead counter. missile.targetPlayer.dead=missile.targetPlayer.dead+1 end From 345b0055f320b018c89fd92737d233f34935b5f1 Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 12 Jul 2019 21:53:52 +0200 Subject: [PATCH 5/5] Update Fox.lua --- Moose Development/Moose/Functional/Fox.lua | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Fox.lua b/Moose Development/Moose/Functional/Fox.lua index 153c2c1b3..47faf90bf 100644 --- a/Moose Development/Moose/Functional/Fox.lua +++ b/Moose Development/Moose/Functional/Fox.lua @@ -763,7 +763,9 @@ end function FOX:onafterMissileLaunch(From, Event, To, missile) -- Tracking info and init of last bomb position. - self:I(FOX.lid..string.format("FOX: Tracking %s - %s - target %s", missile.missileType, missile.missileName, tostring(missile.targetName))) + local text=string.format("FOX: Tracking missile %s(%s) - target %s - shooter %s", missile.missileType, missile.missileName, tostring(missile.targetName), missile.shooterName) + self:I(FOX.lid..text) + MESSAGE:New(text, 10):ToAllIf(self.Debug) -- Loop over players. for _,_player in pairs(self.players) do