From 1a534108bf8afc050a6e466be8ff7edfcbc20dd3 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 24 Jun 2019 14:19:36 +0200 Subject: [PATCH 01/22] Updates ARTY v1.1.0 - added attack group - target data struckture WAREHOUSE v0.9.4 - assignment as descriptor AIRBOSS v1.0.3 - removed warehouse table - no stoping of recovery window if pattern is not empty - changes in patrol route --- .../Moose/Functional/Artillery.lua | 156 +++++++++++++++- .../Moose/Functional/Warehouse.lua | 19 +- Moose Development/Moose/Ops/Airboss.lua | 172 ++++++++++++++---- 3 files changed, 303 insertions(+), 44 deletions(-) diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 637560d03..9e389bebe 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -40,7 +40,7 @@ -- @field #boolean Debug Write Debug messages to DCS log file and send Debug messages to all players. -- @field #table targets All targets assigned. -- @field #table moves All moves assigned. --- @field #table currentTarget Holds the current target, if there is one assigned. +-- @field #ARTY.Target currentTarget Holds the current target, if there is one assigned. -- @field #table currentMove Holds the current commanded move, if there is one assigned. -- @field #number Nammo0 Initial amount total ammunition (shells+rockets+missiles) of the whole group. -- @field #number Nshells0 Initial amount of shells of the whole group. @@ -668,13 +668,28 @@ ARTY.db={ }, } +--- Target. +-- @type ARTY.Target +-- @field #string name Name of target. +-- @field Core.Point#COORDINATE coord Target coordinates. +-- @field #number radius Shelling radius in meters. +-- @field #number nshells Number of shells (or other weapon types) fired upon target. +-- @field #number engaged Number of times this target was engaged. +-- @field #boolean underfire If true, target is currently under fire. +-- @field #number prio Priority of target. +-- @field #number maxengage Max number of times, the target will be engaged. +-- @field #number time Abs. mission time in seconds, when the target is scheduled to be attacked. +-- @field #number weapontype Type of weapon used for engagement. See #ARTY.WeaponType. +-- @field #number Tassigned Abs. mission time when target was assigned. +-- @field #boolean attackgroup If true, use task attack group rather than fire at point for engagement. + --- Some ID to identify who we are in output of the DCS.log file. -- @field #string id ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="1.0.7" +ARTY.version="1.1.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1209,6 +1224,97 @@ function ARTY:AssignTargetCoord(coord, prio, radius, nshells, maxengage, time, w return _name end +--- Assign a target group to the ARTY group. Note that this will use the Attack Group Task rather than the Fire At Point Task. +-- @param #ARTY self +-- @param Wrapper.Group#GROUP group Target group. +-- @param #number prio (Optional) Priority of target. Number between 1 (high) and 100 (low). Default 50. +-- @param #number radius (Optional) Radius. Default is 100 m. +-- @param #number nshells (Optional) How many shells (or rockets) are fired on target per engagement. Default 5. +-- @param #number maxengage (Optional) How many times a target is engaged. Default 1. +-- @param #string time (Optional) Day time at which the target should be engaged. Passed as a string in format "08:13:45". Current task will be canceled. +-- @param #number weapontype (Optional) Type of weapon to be used to attack this target. Default ARTY.WeaponType.Auto, i.e. the DCS logic automatically determins the appropriate weapon. +-- @param #string name (Optional) Name of the target. Default is LL DMS coordinate of the target. If the name was already given, the numbering "#01", "#02",... is appended automatically. +-- @param #boolean unique (Optional) Target is unique. If the target name is already known, the target is rejected. Default false. +-- @return #string Name of the target. Can be used for further reference, e.g. deleting the target from the list. +-- @usage paladin=ARTY:New(GROUP:FindByName("Blue Paladin")) +-- paladin:AssignTargetCoord(GROUP:FindByName("Red Targets 1"):GetCoordinate(), 10, 300, 10, 1, "08:02:00", ARTY.WeaponType.Auto, "Target 1") +-- paladin:Start() +function ARTY:AssignAttackGroup(group, prio, radius, nshells, maxengage, time, weapontype, name, unique) + + -- Set default values. + nshells=nshells or 5 + radius=radius or 100 + maxengage=maxengage or 1 + prio=prio or 50 + prio=math.max( 1, prio) + prio=math.min(100, prio) + if unique==nil then + unique=false + end + weapontype=weapontype or ARTY.WeaponType.Auto + + -- TODO Check if we have a group object. + if type(group)=="string" then + group=GROUP:FindByName(group) + end + + if group and group:IsAlive() then + + local coord=group:GetCoordinate() + + -- Name of the target. + local _name=group:GetName() + local _unique=true + + -- Check if the name has already been used for another target. If so, the function returns a new unique name. + _name,_unique=self:_CheckName(self.targets, _name, not unique) + + -- Target name should be unique and is not. + if unique==true and _unique==false then + self:T(ARTY.id..string.format("%s: target %s should have a unique name but name was already given. Rejecting target!", self.groupname, _name)) + return nil + end + + -- Time in seconds. + local _time + if type(time)=="string" then + _time=self:_ClockToSeconds(time) + elseif type(time)=="number" then + _time=timer.getAbsTime()+time + else + _time=timer.getAbsTime() + end + + -- Prepare target array. + local target={} --#ARTY.Target + target.attackgroup=true + target.name=_name + target.coord=coord + target.radius=radius + target.nshells=nshells + target.engaged=0 + target.underfire=false + target.prio=prio + target.time=_time + target.maxengage=maxengage + target.weapontype=weapontype + + -- Add to table. + table.insert(self.targets, target) + + -- Trigger new target event. + self:__NewTarget(1, target) + + return _name + else + self:E("ERROR: Group does not exist!") + end + + return nil +end + + + --- Assign coordinate to where the ARTY group should move. -- @param #ARTY self -- @param Core.Point#COORDINATE coord Coordinates of the new position. @@ -2766,7 +2872,7 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. --- @param #table target Array holding the target info. +-- @param #ARTY.Target target Array holding the target info. function ARTY:onafterOpenFire(Controllable, From, Event, To, target) self:_EventFromTo("onafterOpenFire", Event, From, To) @@ -2828,7 +2934,11 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) --end -- Start firing. - self:_FireAtCoord(target.coord, target.radius, target.nshells, target.weapontype) + if target.attackgroup then + self:_AttackGroup(target) + else + self:_FireAtCoord(target.coord, target.radius, target.nshells, target.weapontype) + end end @@ -3321,6 +3431,39 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype) group:SetTask(fire) end +--- Set task for firing at a coordinate. +-- @param #ARTY self +-- @param #ARTY.Target target Target data. +function ARTY:_AttackGroup(target) + + -- Controllable. + local group=self.Controllable --Wrapper.Group#GROUP + + local weapontype=target.weapontype + + -- Tactical nukes are actually cannon shells. + if weapontype==ARTY.WeaponType.TacticalNukes or weapontype==ARTY.WeaponType.IlluminationShells or weapontype==ARTY.WeaponType.SmokeShells then + weapontype=ARTY.WeaponType.Cannon + end + + -- Set ROE to weapon free. + group:OptionROEOpenFire() + + -- Target group. + local targetgroup=GROUP:FindByName(target.name) + + -- Get task. + local fire=group:TaskAttackGroup(targetgroup, weapontype, AI.Task.WeaponExpend.ONE, 1) + + self:E("FF") + self:E(fire) + + -- Execute task. + group:PushTask(fire) +end + + + --- Model a nuclear blast/destruction by creating fires and destroy scenery. -- @param #ARTY self -- @param Core.Point#COORDINATE _coord Coordinate of the impact point (center of the blast). @@ -4860,13 +5003,14 @@ end --- Returns the target parameters as formatted string. -- @param #ARTY self +-- @param #ARTY.Target target The target data. -- @return #string name, prio, radius, nshells, engaged, maxengage, time, weapontype function ARTY:_TargetInfo(target) local clock=tostring(self:_SecondsToClock(target.time)) local weapon=self:_WeaponTypeName(target.weapontype) local _underfire=tostring(target.underfire) - return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s", - target.name, target.prio, target.radius, target.nshells, target.engaged, target.maxengage, weapon, clock,_underfire) + return string.format("%s: prio=%d, radius=%d, nshells=%d, engaged=%d/%d, weapontype=%s, time=%s, underfire=%s, attackgroup=%s", + target.name, target.prio, target.radius, target.nshells, target.engaged, target.maxengage, weapon, clock,_underfire, tostring(target.attackgroup)) end --- Returns a formatted string with information about all move parameters. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 802e22275..0ff01cfbd 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -1634,11 +1634,13 @@ WAREHOUSE = { -- @field #string UNITTYPE Typename of the DCS unit, e.g. "A-10C". -- @field #string ATTRIBUTE Generalized attribute @{#WAREHOUSE.Attribute}. -- @field #string CATEGORY Asset category of type DCS#Group.Category, i.e. GROUND, AIRPLANE, HELICOPTER, SHIP, TRAIN. +-- @field #string ASSIGNMENT Assignment of asset when it was added. WAREHOUSE.Descriptor = { GROUPNAME="templatename", UNITTYPE="unittype", ATTRIBUTE="attribute", CATEGORY="category", + ASSIGNMENT="assignment", } --- Generalized asset attributes. Can be used to request assets with certain general characteristics. See [DCS attributes](https://wiki.hoggitworld.com/view/DCS_enum_attributes) on hoggit. @@ -1743,7 +1745,7 @@ _WAREHOUSEDB = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.9.2" +WAREHOUSE.version="0.9.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -3684,7 +3686,7 @@ function WAREHOUSE:onafterAddAsset(From, Event, To, group, ngroups, forceattribu self:_DebugMessage(string.format("Warehouse %s: Adding %d NEW assets of group %s to stock.", self.alias, n, tostring(group:GetName())), 5) -- This is a group that is not in the db yet. Add it n times. - local assets=self:_RegisterAsset(group, n, forceattribute, forcecargobay, forceweight, loadradius, liveries, skill) + local assets=self:_RegisterAsset(group, n, forceattribute, forcecargobay, forceweight, loadradius, liveries, skill, assignment) -- Add created assets to stock of this warehouse. for _,asset in pairs(assets) do @@ -3720,8 +3722,9 @@ end -- @param #number loadradius Radius in meters when cargo is loaded into the carrier. -- @param #table liveries Table of liveries. -- @param DCS#AI.Skill skill Skill of AI. +-- @param #string assignment Assignment attached to the asset item. -- @return #table A table containing all registered assets. -function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, forceweight, loadradius, liveries, skill) +function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, forceweight, loadradius, liveries, skill, assignment) self:F({groupname=group:GetName(), ngroups=ngroups, forceattribute=forceattribute, forcecargobay=forcecargobay, forceweight=forceweight}) -- Set default. @@ -3823,6 +3826,7 @@ function WAREHOUSE:_RegisterAsset(group, ngroups, forceattribute, forcecargobay, asset.livery=liveries[math.random(#liveries)] end asset.skill=skill + asset.assignment=assignment if i==1 then self:_AssetItemInfo(asset) @@ -3937,8 +3941,15 @@ function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescripto okay=false end + elseif AssetDescriptor==WAREHOUSE.Descriptor.ASSIGNMENT then + + if type(AssetDescriptorValue)~="string" then + self:_ErrorMessage("ERROR: Invalid request. Asset assignment type must be passed as a string!", 5) + okay=false + end + else - self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, GROUPNAME or UNITTYPE!", 5) + self:_ErrorMessage("ERROR: Invalid request. Asset descriptor is not ATTRIBUTE, CATEGORY, GROUPNAME, UNITTYPE or ASSIGNMENT!", 5) okay=false end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index e6b13ad69..385016112 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -16,7 +16,7 @@ -- * Advanced F10 radio menu including carrier info, weather, radio frequencies, TACAN/ICLS channels, player LSO grades, marking of zones etc. -- * Recovery tanker and refueling option via integration of @{Ops.RecoveryTanker} class. -- * Rescue helicopter option via @{Ops.RescueHelo} class. --- * Combine multiple human players to sections (WIP). +-- * Combine multiple human players to sections. -- * Many parameters customizable by convenient user API functions. -- * Multiple carrier support due to object oriented approach. -- * Unlimited number of players. @@ -32,7 +32,7 @@ -- **Supported Aircraft:** -- -- * [F/A-18C Hornet Lot 20](https://forums.eagle.ru/forumdisplay.php?f=557) (Player & AI) --- * [F-14B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) [**WIP**] +-- * [F-14B Tomcat](https://forums.eagle.ru/forumdisplay.php?f=395) (Player & AI) -- * [A-4E Skyhawk Community Mod](https://forums.eagle.ru/showthread.php?t=224989) (Player & AI) -- * [AV-8B N/A Harrier](https://forums.eagle.ru/forumdisplay.php?f=555) (Player & AI) [**WIP**] -- * F/A-18C Hornet (AI) @@ -46,10 +46,8 @@ -- the no other fixed wing aircraft (human or AI controlled) are supposed to land on the Tarawa. Currently only Case I is supported. Case II/III take slightly steps from the CVN carrier. -- However, the two Case II/III pattern are very similar so this is not a big drawback. -- --- Heatblur's mighty F-14B Tomcat has just been added (March 13th 2019). Beware that this is currently WIP - both the module and the AIRBOSS implementation. +-- Heatblur's mighty F-14B Tomcat has been added (March 13th 2019) as well. -- --- **PLEASE NOTE** that his class is work in progress. Many/most things work already very nicely but there a lot of cases I did not run into yet. --- Therefore, your *constructive* feedback is both necessary and appreciated! -- -- ## Discussion -- @@ -174,7 +172,6 @@ -- @field #number NmaxStack Number of max flights per stack. Default 2. -- @field #boolean handleai If true (default), handle AI aircraft. -- @field Ops.RecoveryTanker#RECOVERYTANKER tanker Recovery tanker flying overhead of carrier. --- @field Functional.Warehouse#WAREHOUSE warehouse Warehouse object of the carrier. -- @field DCS#Vec3 Corientation Carrier orientation in space. -- @field DCS#Vec3 Corientlast Last known carrier orientation. -- @field Core.Point#COORDINATE Cposition Carrier position. @@ -1182,7 +1179,6 @@ AIRBOSS = { NmaxStack = nil, handleai = nil, tanker = nil, - warehouse = nil, Corientation = nil, Corientlast = nil, Cposition = nil, @@ -1685,7 +1681,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.0.2" +AIRBOSS.version="1.0.3" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1802,6 +1798,15 @@ function AIRBOSS:New(carriername, alias) -- Init player scores table. self.playerscores={} + + -- Initialize ME waypoints. + self:_InitWaypoints() + + -- Current waypoint. + self.currentwp=1 + + -- Patrol route. + self:_PatrolRoute() ------------- --- Defaults: @@ -2432,6 +2437,19 @@ function AIRBOSS:SetExcludeAI(setgroup) return self end +--- Add a group to the exclude set. If no set exists, it is created. +-- @param #AIRBOSS self +-- @param Wrapper.Group#GROUP group The group to be excluded. +-- @return #AIRBOSS self +function AIRBOSS:AddExcludeAI(group) + + self.excludesetAI=self.excludesetAI or SET_GROUP:New() + + self.excludesetAI:AddGroup(group) + + return self +end + --- Close currently running recovery window and stop recovery ops. Recovery window is deleted. -- @param #AIRBOSS self -- @param #number delay (Optional) Delay in seconds before the window is deleted. @@ -3044,15 +3062,6 @@ function AIRBOSS:SetRecoveryTanker(recoverytanker) return self end ---- Define warehouse associated with the carrier. --- @param #AIRBOSS self --- @param Functional.Warehouse#WAREHOUSE warehouse Warehouse object of the carrier. --- @return #AIRBOSS self -function AIRBOSS:SetWarehouse(warehouse) - self.warehouse=warehouse - return self -end - --- Set default player skill. New players will be initialized with this skill. -- -- * "Flight Student" = @{#AIRBOSS.Difficulty.Easy} @@ -3202,9 +3211,6 @@ function AIRBOSS:onafterStart(From, Event, To) self.Corientlast=self.Corientation self.Tpupdate=timer.getTime() - -- Init patrol route of carrier. - self:_PatrolRoute(1) - -- Check if no recovery window is set. DISABLED! if #self.recoverytimes==0 and false then @@ -3232,9 +3238,6 @@ function AIRBOSS:onafterStart(From, Event, To) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._PlayerLeft) self:HandleEvent(EVENTS.MissionEnd) - -- DCS event handler. - --world.addEventHandler(self) - -- Start status check in 1 second. self:__Status(1) end @@ -3617,15 +3620,27 @@ function AIRBOSS:_CheckRecoveryTimes() if self:IsRecovering() and not recovery.OVER then - -- Set carrier to idle. - self:RecoveryStop() - state="closing now" + if #self.Qpattern>0 then + + local extmin=5*#self.Qpattern + recovery.STOP=recovery.STOP+extmin*60 + + local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!", extmin) + self:MessageToPattern(text, "AIRBOSS", "99", 10, false, nil) - -- Closed. - recovery.OPEN=false + else + + -- Set carrier to idle. + self:RecoveryStop() + state="closing now" + + -- Closed. + recovery.OPEN=false + + -- Window just closed. + recovery.OVER=true - -- Window just closed. - recovery.OVER=true + end else -- Carrier is already idle. @@ -3689,10 +3704,11 @@ function AIRBOSS:_CheckRecoveryTimes() --Debug info self:T(self.lid..string.format("Heading=%03d°, Wind=%03d° %.1f kts, Delta=%03d° ==> U-turn=%s", hdg, wind,UTILS.MpsToKnots(vwind), delta, tostring(uturn))) + + -- Time into the wind 1 day or if longer recovery time + the 5 min early. + local t=math.max(nextwindow.STOP-nextwindow.START+300, 60*60*24) - - -- Time into the wind + the 5 min early. - local t=nextwindow.STOP-nextwindow.START+300 + -- Recovery wind on deck in knots. local v=UTILS.KnotsToMps(nextwindow.SPEED) -- Check that we do not go above max possible speed. @@ -13206,6 +13222,42 @@ function AIRBOSS:_GetNextWaypoint() return nextwp,Nextwp end + +--- Initialize Mission Editor waypoints. +-- @param #AIRBOSS self +-- @return #AIRBOSS self +function AIRBOSS:_InitWaypoints() + + -- Waypoints of group as defined in the ME. + local Waypoints=self.carrier:GetGroup():GetTemplateRoutePoints() + + -- Init array. + self.waypoints={} + + -- Set waypoint table. + for i,point in ipairs(Waypoints) do + + -- Coordinate of the waypoint + local coord=COORDINATE:New(point.x, point.alt, point.y) + + -- Set velocity of the coordinate. + coord:SetVelocity(point.speed) + + -- Add to table. + table.insert(self.waypoints, coord) + + -- Debug info. + if self.Debug then + coord:MarkToAll(string.format("Carrier Waypoint %d, Speed=%.1f knots", i, UTILS.MpsToKnots(point.speed))) + end + + end + + return self +end + +--[[ + --- Patrol carrier. -- @param #AIRBOSS self -- @param #number n Current waypoint. @@ -13259,6 +13311,58 @@ function AIRBOSS:_PatrolRoute(n) return self end +]] + +--- Patrol carrier. +-- @param #AIRBOSS self +-- @param #number n Next waypoint number. +-- @return #AIRBOSS self +function AIRBOSS:_PatrolRoute(n) + + -- Get next waypoint coordinate and number. + local nextWP, N=self:_GetNextWaypoint() + + -- Default resume is to next waypoint. + n=n or N + + -- Get carrier group. + local CarrierGroup=self.carrier:GetGroup() + + -- Waypoints table. + local Waypoints={} + + -- Create a waypoint from the current coordinate. + local wp=self:GetCoordinate():WaypointGround(CarrierGroup:GetVelocityKMH()) + + -- Add current position as first waypoint. + table.insert(Waypoints, wp) + + -- Loop over waypoints. + for i=n,#self.waypoints do + local coord=self.waypoints[i] --Core.Point#COORDINATE + + -- Create a waypoint from the coordinate. + local wp=coord:WaypointGround(UTILS.MpsToKmph(coord.Velocity)) + + -- Passing waypoint taskfunction + local TaskPassingWP=CarrierGroup:TaskFunction("AIRBOSS._PassingWaypoint", self, i, #self.waypoints) + + -- Call task function when carrier arrives at waypoint. + CarrierGroup:SetTaskWaypoint(wp, TaskPassingWP) + + -- Add waypoint to table. + table.insert(Waypoints, wp) + end + + -- Route carrier group. + CarrierGroup:Route(Waypoints) + + return self +end + + + + --- Estimated the carrier position at some point in the future given the current waypoints and speeds. -- @param #AIRBOSS self -- @return DCS#time ETA abs. time in seconds. @@ -13484,7 +13588,7 @@ function AIRBOSS._PassingWaypoint(group, airboss, i, final) -- If final waypoint reached, do route all over again. if i==final and final>1 and airboss.adinfinitum then - airboss:_PatrolRoute(i) + airboss:_PatrolRoute() end end From ac1d07ec66831a7b56e43457ceb0ed1b541dc2a8 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 24 Jun 2019 23:17:18 +0200 Subject: [PATCH 02/22] updates --- Moose Development/Moose/AI/AI_Formation.lua | 2 +- .../Moose/Functional/Artillery.lua | 345 ++++++++++-------- Moose Development/Moose/Wrapper/Airbase.lua | 6 +- 3 files changed, 187 insertions(+), 166 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Formation.lua b/Moose Development/Moose/AI/AI_Formation.lua index fea9c7c0e..21482e930 100644 --- a/Moose Development/Moose/AI/AI_Formation.lua +++ b/Moose Development/Moose/AI/AI_Formation.lua @@ -1102,7 +1102,7 @@ function AI_FORMATION:onenterFollowing( FollowGroupSet ) --R2.1 if FollowGroup:GetState( FollowGroup, "Mode" ) == self.__Enum.Mode.Formation then - self:I({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) + self:T({Mode=FollowGroup:GetState( FollowGroup, "Mode" )}) FollowGroup:OptionROTEvadeFire() FollowGroup:OptionROEReturnFire() diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 9e389bebe..e206d9962 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -98,7 +98,8 @@ -- @field #boolean markreadonly Marks for targets are readonly and cannot be removed by players. Default is false. -- @field #boolean autorelocate ARTY group will automatically move to within the max/min firing range. -- @field #number autorelocatemaxdist Max distance [m] the ARTY group will travel to get within firing range. Default 50000 m = 50 km. --- @field #boolean autorelocateonroad ARTY group will use mainly road to automatically get within firing range. Default is false. +-- @field #boolean autorelocateonroad ARTY group will use mainly road to automatically get within firing range. Default is false. +-- @field #number coalition The coalition of the arty group. -- @extends Core.Fsm#FSM_CONTROLLABLE --- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can @@ -587,6 +588,7 @@ ARTY={ autorelocate=false, autorelocatemaxdist=50000, autorelocateonroad=false, + coalition=nil, } --- Weapong type ID. See [here](http://wiki.hoggit.us/view/DCS_enum_weapon_flag). @@ -689,7 +691,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="1.1.0" +ARTY.version="1.1.1" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -782,6 +784,9 @@ function ARTY:New(group, alias) -- Set the group name self.groupname=group:GetName() + -- Get coalition. + self.coalition=group:GetCoalition() + -- Set an alias name. if alias~=nil then self.alias=tostring(alias) @@ -1931,8 +1936,8 @@ function ARTY:onafterStart(Controllable, From, Event, To) self.Controllable:OptionROEHoldFire() -- Add event handler. - self:HandleEvent(EVENTS.Shot, self._OnEventShot) - self:HandleEvent(EVENTS.Dead, self._OnEventDead) + self:HandleEvent(EVENTS.Shot) --, self._OnEventShot) + self:HandleEvent(EVENTS.Dead) --, self._OnEventDead) --self:HandleEvent(EVENTS.MarkAdded, self._OnEventMarkAdded) -- Add DCS event handler - necessary for S_EVENT_MARK_* events. So we only start it, if this was requested. @@ -2010,7 +2015,7 @@ function ARTY:_StatusReport(display) end text=text..string.format("******************************************************") env.info(ARTY.id..text) - MESSAGE:New(text, 20):Clear():ToCoalitionIf(self.Controllable:GetCoalition(), display) + MESSAGE:New(text, 20):Clear():ToCoalitionIf(self.coalition, display) end @@ -2021,7 +2026,7 @@ end --- Eventhandler for shot event. -- @param #ARTY self -- @param Core.Event#EVENTDATA EventData -function ARTY:_OnEventShot(EventData) +function ARTY:OnEventShot(EventData) self:F(EventData) -- Weapon data. @@ -2299,7 +2304,7 @@ end function ARTY:_OnEventMarkRemove(Event) -- Get battery coalition and name. - local batterycoalition=self.Controllable:GetCoalition() + local batterycoalition=self.coalition --local batteryname=self.groupname if Event.text~=nil and Event.text:find("BATTERY") then @@ -2385,7 +2390,7 @@ function ARTY:_OnEventMarkChange(Event) _coord.y=_coord:GetLandHeight() -- Get battery coalition and name. - local batterycoalition=self.Controllable:GetCoalition() + local batterycoalition=self.coalition local batteryname=self.groupname -- Check if the coalition is the same or an authorization key has been defined. @@ -2496,7 +2501,7 @@ function ARTY:_OnEventMarkChange(Event) if _assign.setrearmingplace and self.ismobile then self:SetRearmingPlace(_coord) _coord:RemoveMark(Event.idx) - _coord:MarkToCoalition(string.format("Rearming place for battery %s", self.groupname), self.Controllable:GetCoalition(), false, string.format("New rearming place for battery %s defined.", self.groupname)) + _coord:MarkToCoalition(string.format("Rearming place for battery %s", self.groupname), self.coalition, false, string.format("New rearming place for battery %s defined.", self.groupname)) if self.Debug then _coord:SmokeOrange() end @@ -2504,7 +2509,7 @@ function ARTY:_OnEventMarkChange(Event) if _assign.setrearminggroup then _coord:RemoveMark(Event.idx) local rearminggroupcoord=_assign.setrearminggroup:GetCoordinate() - rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s", self.groupname), self.Controllable:GetCoalition(), false, string.format("New rearming group for battery %s defined.", self.groupname)) + rearminggroupcoord:MarkToCoalition(string.format("Rearming group for battery %s", self.groupname), self.coalition, false, string.format("New rearming group for battery %s defined.", self.groupname)) self:SetRearmingGroup(_assign.setrearminggroup) if self.Debug then rearminggroupcoord:SmokeOrange() @@ -2608,17 +2613,19 @@ end --- Event handler for event Dead. -- @param #ARTY self -- @param Core.Event#EVENTDATA EventData -function ARTY:_OnEventDead(EventData) +function ARTY:OnEventDead(EventData) self:F(EventData) -- Name of controllable. local _name=self.groupname + + env.info("FF Dead event for arty group") -- Check for correct group. if EventData.IniGroupName==_name then -- Dead Unit. - self:T2(string.format("%s: Captured dead event for unit %s.", _name, EventData.IniUnitName)) + self:I(string.format("%s: Captured dead event for unit %s.", _name, tostring(EventData.IniUnitName))) -- FSM Dead event. We give one second for update of data base. self:__Dead(1) @@ -2639,145 +2646,155 @@ end function ARTY:onafterStatus(Controllable, From, Event, To) self:_EventFromTo("onafterStatus", Event, From, To) - -- We have a cargo group ==> check if group was loaded into a carrier. - if self.cargogroup then - if self.cargogroup:IsLoaded() and not self:is("InTransit") then - -- Group is now InTransit state. Current target is canceled. - self:T(ARTY.id..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias)) - self:Loaded() - elseif self.cargogroup:IsUnLoaded() then - -- Group has been unloaded and is combat ready again. - self:T(ARTY.id..string.format("Group %s has been unloaded from the carrier.", self.alias)) - self:UnLoaded() - end - end - - -- Debug current status info. - if self.Debug then - self:_StatusReport() - end - - -- Group is being transported as cargo ==> skip everything and check again in 5 seconds. - if self:is("InTransit") then - self:__Status(-5) - return - end + -- FSM state. + local fsmstate=self:GetState() + self:I(ARTY.id..string.format("Status of group %s: %s", self.alias, fsmstate)) - -- Group on the move. - if self:is("Moving") then - self:T2(ARTY.id..string.format("%s: Moving", Controllable:GetName())) - end + if self.Controllable and self.Controllable:IsAlive() then - -- Group is rearming. - if self:is("Rearming") then - local _rearmed=self:_CheckRearmed() - if _rearmed then - self:T2(ARTY.id..string.format("%s: Rearming ==> Rearmed", Controllable:GetName())) - self:Rearmed() - end - end - - -- Group finished rearming. - if self:is("Rearmed") then - local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) - self:T2(ARTY.id..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m", Controllable:GetName(), distance)) - -- Check that ARTY group is back and set it to combat ready. - if distance <= self.RearmingDistance then - self:T2(ARTY.id..string.format("%s: Rearmed ==> CombatReady", Controllable:GetName())) - self:CombatReady() - end - end - - -- Group arrived at destination. - if self:is("Arrived") then - self:T2(ARTY.id..string.format("%s: Arrived ==> CombatReady", Controllable:GetName())) - self:CombatReady() - end - - -- Group is firing on target. - if self:is("Firing") then - -- Check that firing started after ~5 min. If not, target is removed. - self:_CheckShootingStarted() - end - - -- Check if targets are in range and update target.inrange value. - self:_CheckTargetsInRange() - - -- Check if selected weapon type for target is possible at all. E.g. request rockets for Paladin. - local notpossible={} - for i=1,#self.targets do - local _target=self.targets[i] - local possible=self:_CheckWeaponTypePossible(_target) - if not possible then - table.insert(notpossible, _target.name) - end - end - for _,targetname in pairs(notpossible) do - self:E(ARTY.id..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.groupname, targetname)) - self:RemoveTarget(targetname) - end - - -- Get a valid timed target if it is due to be attacked. - local _timedTarget=self:_CheckTimedTargets() - - -- Get a valid normal target (one that is not timed). - local _normalTarget=self:_CheckNormalTargets() - - -- Get a commaned move to another location. - local _move=self:_CheckMoves() - - if _move then - - -- Command to move. - self:Move(_move) - - elseif _timedTarget then - - -- Cease fire on current target first. - if self.currentTarget then - self:CeaseFire(self.currentTarget) - end - - -- Open fire on timed target. - self:OpenFire(_timedTarget) - - elseif _normalTarget then - - -- Open fire on normal target. - self:OpenFire(_normalTarget) - - end - - -- Get ammo. - local nammo, nshells, nrockets, nmissiles=self:GetAmmo() - - -- Check if we have a target in the queue for which weapons are still available. - local gotsome=false - if #self.targets>0 then - for i=1,#self.targets do - local _target=self.targets[i] - if self:_CheckWeaponTypeAvailable(_target)>0 then - gotsome=true + -- We have a cargo group ==> check if group was loaded into a carrier. + if self.cargogroup then + if self.cargogroup:IsLoaded() and not self:is("InTransit") then + -- Group is now InTransit state. Current target is canceled. + self:T(ARTY.id..string.format("Group %s has been loaded into a carrier and is now transported.", self.alias)) + self:Loaded() + elseif self.cargogroup:IsUnLoaded() then + -- Group has been unloaded and is combat ready again. + self:T(ARTY.id..string.format("Group %s has been unloaded from the carrier.", self.alias)) + self:UnLoaded() end end - else - -- No targets in the queue. - gotsome=true - end - -- No ammo available. Either completely blank or only queued targets for ammo which is out. - if (nammo==0 or not gotsome) and not (self:is("Moving") or self:is("Rearming") or self:is("OutOfAmmo")) then - self:Winchester() + -- Debug current status info. + if self.Debug then + self:_StatusReport() + end + + -- Group is being transported as cargo ==> skip everything and check again in 5 seconds. + if self:is("InTransit") then + self:__Status(-5) + return + end + + -- Group on the move. + if self:is("Moving") then + self:T2(ARTY.id..string.format("%s: Moving", Controllable:GetName())) + end + + -- Group is rearming. + if self:is("Rearming") then + local _rearmed=self:_CheckRearmed() + if _rearmed then + self:T2(ARTY.id..string.format("%s: Rearming ==> Rearmed", Controllable:GetName())) + self:Rearmed() + end + end + + -- Group finished rearming. + if self:is("Rearmed") then + local distance=self.Controllable:GetCoordinate():Get2DDistance(self.InitialCoord) + self:T2(ARTY.id..string.format("%s: Rearmed. Distance ARTY to InitalCoord = %d m", Controllable:GetName(), distance)) + -- Check that ARTY group is back and set it to combat ready. + if distance <= self.RearmingDistance then + self:T2(ARTY.id..string.format("%s: Rearmed ==> CombatReady", Controllable:GetName())) + self:CombatReady() + end + end + + -- Group arrived at destination. + if self:is("Arrived") then + self:T2(ARTY.id..string.format("%s: Arrived ==> CombatReady", Controllable:GetName())) + self:CombatReady() + end + + -- Group is firing on target. + if self:is("Firing") then + -- Check that firing started after ~5 min. If not, target is removed. + self:_CheckShootingStarted() + end + + -- Check if targets are in range and update target.inrange value. + self:_CheckTargetsInRange() + + -- Check if selected weapon type for target is possible at all. E.g. request rockets for Paladin. + local notpossible={} + for i=1,#self.targets do + local _target=self.targets[i] + local possible=self:_CheckWeaponTypePossible(_target) + if not possible then + table.insert(notpossible, _target.name) + end + end + for _,targetname in pairs(notpossible) do + self:E(ARTY.id..string.format("%s: Removing target %s because requested weapon is not possible with this type of unit.", self.groupname, targetname)) + self:RemoveTarget(targetname) + end + + -- Get a valid timed target if it is due to be attacked. + local _timedTarget=self:_CheckTimedTargets() + + -- Get a valid normal target (one that is not timed). + local _normalTarget=self:_CheckNormalTargets() + + -- Get a commaned move to another location. + local _move=self:_CheckMoves() + + if _move then + + -- Command to move. + self:Move(_move) + + elseif _timedTarget then + + -- Cease fire on current target first. + if self.currentTarget then + self:CeaseFire(self.currentTarget) + end + + -- Open fire on timed target. + self:OpenFire(_timedTarget) + + elseif _normalTarget then + + -- Open fire on normal target. + self:OpenFire(_normalTarget) + + end + + -- Get ammo. + local nammo, nshells, nrockets, nmissiles=self:GetAmmo() + + -- Check if we have a target in the queue for which weapons are still available. + local gotsome=false + if #self.targets>0 then + for i=1,#self.targets do + local _target=self.targets[i] + if self:_CheckWeaponTypeAvailable(_target)>0 then + gotsome=true + end + end + else + -- No targets in the queue. + gotsome=true + end + + -- No ammo available. Either completely blank or only queued targets for ammo which is out. + if (nammo==0 or not gotsome) and not (self:is("Moving") or self:is("Rearming") or self:is("OutOfAmmo")) then + self:Winchester() + end + + -- Group is out of ammo. + if self:is("OutOfAmmo") then + self:T2(ARTY.id..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming", Controllable:GetName())) + self:Rearm() + end + + -- Call status again in ~10 sec. + self:__Status(self.StatusInterval) + + else + self:E(string.format("Arty group %s is not alive!", self.groupname)) end - - -- Group is out of ammo. - if self:is("OutOfAmmo") then - self:T2(ARTY.id..string.format("%s: OutOfAmmo ==> Rearm ==> Rearming", Controllable:GetName())) - self:Rearm() - end - - -- Call status again in ~10 sec. - self:__Status(self.StatusInterval) end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2925,7 +2942,7 @@ function ARTY:onafterOpenFire(Controllable, From, Event, To, target) -- Send message. local text=string.format("%s, opening fire on target %s with %d %s. Distance %.1f km.", Controllable:GetName(), target.name, target.nshells, _type, range/1000) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report) --if self.Debug then -- local _coord=target.coord --Core.Point#COORDINATE @@ -2959,7 +2976,7 @@ function ARTY:onafterCeaseFire(Controllable, From, Event, To, target) -- Send message. local text=string.format("%s, ceasing fire on target %s.", Controllable:GetName(), target.name) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report) -- Get target array index. local id=self:_GetTargetIndexByName(target.name) @@ -3013,7 +3030,7 @@ function ARTY:onafterWinchester(Controllable, From, Event, To) -- Send message. local text=string.format("%s, winchester!", Controllable:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) end @@ -3079,7 +3096,7 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Send message. local text=string.format("%s, %s, request rearming at rearming place.", Controllable:GetName(), self.RearmingGroup:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) -- Distances. local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) @@ -3104,7 +3121,7 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Send message. local text=string.format("%s, %s, request rearming.", Controllable:GetName(), self.RearmingGroup:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) -- Distance between ARTY group and rearming unit. local distance=coordARTY:Get2DDistance(coordRARM) @@ -3123,7 +3140,7 @@ function ARTY:onafterRearm(Controllable, From, Event, To) -- Send message. local text=string.format("%s, moving to rearming place.", Controllable:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) -- Distance. local dA=coordARTY:Get2DDistance(self.RearmingPlaceCoord) @@ -3152,7 +3169,7 @@ function ARTY:onafterRearmed(Controllable, From, Event, To) -- Send message. local text=string.format("%s, rearming complete.", Controllable:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) -- "Rearm" tactical nukes as well. self.Nukes=self.Nukes0 @@ -3204,7 +3221,7 @@ function ARTY:_CheckRearmed() if _rearmpc>1 then local text=string.format("%s, rearming %d %% complete.", self.alias, _rearmpc) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) end -- Return if ammo is full. @@ -3299,7 +3316,7 @@ function ARTY:onafterArrived(Controllable, From, Event, To) -- Send message local text=string.format("%s, arrived at destination.", Controllable:GetName()) self:T(ARTY.id..text) - MESSAGE:New(text, 10):ToCoalitionIf(Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text, 10):ToCoalitionIf(self.coalition, self.report or self.Debug) -- Remove executed move from queue. if self.currentMove then @@ -3354,16 +3371,20 @@ function ARTY:onafterDead(Controllable, From, Event, To) self:_EventFromTo("onafterDead", Event, From, To) -- Number of units left in the group. + --[[ local units=self.Controllable:GetUnits() local nunits=0 if units~=nil then nunits=#units end + ]] + + local nunits=self.Controllable:CountAliveUnits() -- Message. local text=string.format("%s, one of our units just died! %d units left.", self.groupname, nunits) MESSAGE:New(text, 5):ToAllIf(self.Debug) - self:T(ARTY.id..text) + self:I(ARTY.id..text) -- Go to stop state. if nunits==0 then @@ -3382,7 +3403,7 @@ function ARTY:onafterStop(Controllable, From, Event, To) self:_EventFromTo("onafterStop", Event, From, To) -- Debug info. - self:T(ARTY.id..string.format("Stopping ARTY FSM for group %s.", Controllable:GetName())) + self:I(ARTY.id..string.format("Stopping ARTY FSM for group %s.", tostring(Controllable:GetName()))) -- Cease Fire on current target. if self.currentTarget then @@ -3933,7 +3954,7 @@ function ARTY:_MarkerKeyAuthentification(text) -- Set battery and coalition. --local batteryname=self.groupname - local batterycoalition=self.Controllable:GetCoalition() + local batterycoalition=self.coalition -- Get assignment. local mykey=nil @@ -4248,7 +4269,7 @@ function ARTY:_MarkRequestMoves() else text=text..string.format("\n- no queued relocations") end - MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) + MESSAGE:New(text, 20):Clear():ToCoalition(self.coalition) end --- Request Targets. @@ -4266,7 +4287,7 @@ function ARTY:_MarkRequestTargets() else text=text..string.format("\n- no queued targets") end - MESSAGE:New(text, 20):Clear():ToCoalition(self.Controllable:GetCoalition()) + MESSAGE:New(text, 20):Clear():ToCoalition(self.coalition) end --- Create a name for an engagement initiated by placing a marker. @@ -4448,7 +4469,7 @@ function ARTY:_CheckTargetsInRange() -- Inform coalition that target is now in range. local text=string.format("%s, target %s is now in range.", self.alias, _target.name) self:T(ARTY.id..text) - MESSAGE:New(text,10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(text,10):ToCoalitionIf(self.coalition, self.report or self.Debug) end end @@ -4485,7 +4506,7 @@ function ARTY:_CheckTargetsInRange() end -- Send info message. - MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.Controllable:GetCoalition(), self.report or self.Debug) + MESSAGE:New(_name.." assigned.", 10):ToCoalitionIf(self.coalition, self.report or self.Debug) -- Assign relocation move. self:AssignMoveCoord(_tocoord, nil, nil, self.autorelocateonroad, false, _name, true) @@ -4918,7 +4939,7 @@ function ARTY:_TargetInRange(target, message) -- Debug output. if not _inrange then self:T(ARTY.id..text) - MESSAGE:New(text, 5):ToCoalitionIf(self.Controllable:GetCoalition(), (self.report and message) or (self.Debug and message)) + MESSAGE:New(text, 5):ToCoalitionIf(self.coalition, (self.report and message) or (self.Debug and message)) end -- Remove target if ARTY group cannot move, e.g. Mortas. No chance to be ever in range - unless they are cargo. diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index ab38769f4..2de2ef7e2 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -553,14 +553,14 @@ function AIRBASE:GetParkingSpotsTable(termtype) local spots={} for _,_spot in pairs(parkingdata) do if AIRBASE._CheckTerminalType(_spot.Term_Type, termtype) then - self:I({_spot=_spot}) + self:T2({_spot=_spot}) local _free=_isfree(_spot) local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) table.insert(spots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=_free, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) end end - self:I({ spots = spots } ) + self:T2({ spots = spots } ) return spots end @@ -711,7 +711,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE local _termid=parkingspot.TerminalID - self:I({_termid=_termid}) + self:T2({_termid=_termid}) if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) then From 2a7b9cf8986073b1b809164ffcc576e0e8301d12 Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 26 Jun 2019 19:19:17 +0200 Subject: [PATCH 03/22] DETECTION, SET, ARTY, WAREHOUSE, AIRBOSS, A2A_DISPATCHER DETECTION - Fixed bug with late activated groups. ARTY v1.1.2 - Added attack group task for ships. - Added respawn option. WAREHOUSE v0.9.5 - Added respawn option. AIRBOSS v1.0.3 - Recovery time extended if flights are still in the pattern. SET - Added CountAlive() function. A2A_DISPATCHER - Bug fixes in :ParkDefender() function. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 8 +- Moose Development/Moose/Core/Set.lua | 29 +++ .../Moose/Functional/Artillery.lua | 184 +++++++++++++++--- .../Moose/Functional/Detection.lua | 3 +- .../Moose/Functional/Warehouse.lua | 89 +++++++-- Moose Development/Moose/Ops/Airboss.lua | 8 +- 6 files changed, 269 insertions(+), 52 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 7396cb7a6..cd1836f6f 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1095,7 +1095,7 @@ do -- AI_A2A_DISPATCHER self:RemoveDefenderFromSquadron( Squadron, Defender ) end DefenderUnit:Destroy() - self:ParkDefender( Squadron, Defender ) + self:ParkDefender( Squadron ) return end if DefenderUnit:GetLife() ~= DefenderUnit:GetLife0() then @@ -1122,7 +1122,7 @@ do -- AI_A2A_DISPATCHER self:RemoveDefenderFromSquadron( Squadron, Defender ) end DefenderUnit:Destroy() - self:ParkDefender( Squadron, Defender ) + self:ParkDefender( Squadron ) end end end @@ -2873,7 +2873,7 @@ do -- AI_A2A_DISPATCHER if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Defender:Destroy() - self:ParkDefender( Squadron, Defender ) + Dispatcher:ParkDefender( Squadron ) end end end @@ -3060,7 +3060,7 @@ do -- AI_A2A_DISPATCHER if Dispatcher:GetSquadronLanding( Squadron.Name ) == AI_A2A_DISPATCHER.Landing.NearAirbase then Dispatcher:RemoveDefenderFromSquadron( Squadron, Defender ) Defender:Destroy() - self:ParkDefender( Squadron, Defender ) + Dispatcher:ParkDefender( Squadron ) end end end -- if DefenderGCI then diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f0e24adf5..20554631b 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1571,6 +1571,35 @@ do -- SET_GROUP return Count end + --- Iterate the SET_GROUP and count how many GROUPs and UNITs are alive. + -- @param #SET_GROUP self + -- @return #number The number of GROUPs completely in the Zone + -- @return #number The number of UNITS alive. + function SET_GROUP:CountAlive() + local CountG = 0 + local CountU = 0 + + local Set = self:GetSet() + + for GroupID, GroupData in pairs(Set) do -- For each GROUP in SET_GROUP + if GroupData and GroupData:IsAlive() then + + CountG = CountG + 1 + + --Count Units. + for _,_unit in pairs(GroupData:GetUnits()) do + local unit=_unit --Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + CountU=CountU+1 + end + end + end + + end + + return CountG,CountU + end + ----- Iterate the SET_GROUP and call an interator function for each **alive** player, providing the Group of the player and optional parameters. ---- @param #SET_GROUP self ---- @param #function IteratorFunction The function that will be called when there is an alive player in the SET_GROUP. The function needs to accept a GROUP parameter. diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index e206d9962..e5d30114c 100644 --- a/Moose Development/Moose/Functional/Artillery.lua +++ b/Moose Development/Moose/Functional/Artillery.lua @@ -100,6 +100,8 @@ -- @field #number autorelocatemaxdist Max distance [m] the ARTY group will travel to get within firing range. Default 50000 m = 50 km. -- @field #boolean autorelocateonroad ARTY group will use mainly road to automatically get within firing range. Default is false. -- @field #number coalition The coalition of the arty group. +-- @field #boolean respawnafterdeath Respawn arty group after all units are dead. +-- @field #number respawndelay Respawn delay in seconds. -- @extends Core.Fsm#FSM_CONTROLLABLE --- Enables mission designers easily to assign targets for artillery units. Since the implementation is based on a Finite State Model (FSM), the mission designer can @@ -589,6 +591,8 @@ ARTY={ autorelocatemaxdist=50000, autorelocateonroad=false, coalition=nil, + respawnafterdeath=false, + respawndelay=nil } --- Weapong type ID. See [here](http://wiki.hoggit.us/view/DCS_enum_weapon_flag). @@ -691,7 +695,7 @@ ARTY.id="ARTY | " --- Arty script version. -- @field #string version -ARTY.version="1.1.1" +ARTY.version="1.1.2" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -863,6 +867,7 @@ function ARTY:New(group, alias) self:AddTransition("*", "Status", "*") self:AddTransition("*", "NewMove", "*") self:AddTransition("*", "Dead", "*") + self:AddTransition("*", "Respawn", "CombatReady") -- Transport as cargo (not in diagram). self:AddTransition("*", "Loaded", "InTransit") @@ -973,7 +978,15 @@ function ARTY:New(group, alias) -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. + -- @param #string Unitname Name of the dead unit. + --- User function for OnAfter "Respawn" event. + -- @function [parent=#ARTY] OnAfterRespawn + -- @param #ARTY self + -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. --- User function for OnEnter "CombatReady" state. -- @function [parent=#ARTY] OnEnterCombatReady @@ -1021,7 +1034,7 @@ function ARTY:New(group, alias) -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. -- @param #string From From state. -- @param #string Event Event. - -- @param #string To To state. + -- @param #string To To state. --- Function to start the ARTY FSM process. @@ -1045,11 +1058,13 @@ function ARTY:New(group, alias) --- Function called when a unit of the ARTY group died. Triggers the FSM event "Dead". -- @function [parent=#ARTY] Dead -- @param #ARTY self + -- @param #string unitname Name of the unit that died. --- Function called when a unit of the ARTY group died after a delay. Triggers the FSM event "Dead". -- @function [parent=#ARTY] __Dead -- @param #ARTY self -- @param #number Delay in seconds. + -- @param #string unitname Name of the unit that died. --- Add a new target for the ARTY group. Triggers the FSM event "NewTarget". -- @function [parent=#ARTY] NewTarget @@ -1133,7 +1148,15 @@ function ARTY:New(group, alias) -- @param #ARTY self -- @param #number delay Delay in seconds. - + --- Respawn ARTY group. + -- @function [parent=#ARTY] Respawn + -- @param #ARTY self + + --- Respawn ARTY group after a delay. + -- @function [parent=#ARTY] __Respawn + -- @param #ARTY self + -- @param #number delay Delay in seconds. + return self end @@ -1399,14 +1422,17 @@ end --- Set alias, i.e. the name the group will use when sending messages. -- @param #ARTY self -- @param #string alias The alias for the group. +-- @return self function ARTY:SetAlias(alias) self:F({alias=alias}) self.alias=tostring(alias) + return self end --- Add ARTY group to one or more clusters. Enables addressing all ARTY groups within a cluster simultaniously via marker assignments. -- @param #ARTY self -- @param #table clusters Table of cluster names the group should belong to. +-- @return self function ARTY:AddToCluster(clusters) self:F({clusters=clusters}) @@ -1425,99 +1451,122 @@ function ARTY:AddToCluster(clusters) -- Add names to cluster array. for _,cluster in pairs(names) do table.insert(self.clusters, cluster) - end + end + + return self end --- Set minimum firing range. Targets closer than this distance are not engaged. -- @param #ARTY self -- @param #number range Min range in kilometers. Default is 0.1 km. +-- @return self function ARTY:SetMinFiringRange(range) self:F({range=range}) self.minrange=range*1000 or 100 + return self end --- Set maximum firing range. Targets further away than this distance are not engaged. -- @param #ARTY self -- @param #number range Max range in kilometers. Default is 1000 km. +-- @return self function ARTY:SetMaxFiringRange(range) self:F({range=range}) self.maxrange=range*1000 or 1000*1000 + return self end --- Set time interval between status updates. During the status check, new events are triggered. -- @param #ARTY self -- @param #number interval Time interval in seconds. Default 10 seconds. +-- @return self function ARTY:SetStatusInterval(interval) self:F({interval=interval}) self.StatusInterval=interval or 10 + return self end --- Set time how it is waited a unit the first shot event happens. If no shot is fired after this time, the task to fire is aborted and the target removed. -- @param #ARTY self -- @param #number waittime Time in seconds. Default 300 seconds. +-- @return self function ARTY:SetWaitForShotTime(waittime) self:F({waittime=waittime}) self.WaitForShotTime=waittime or 300 + return self end --- Define the safe distance between ARTY group and rearming unit or rearming place at which rearming process is possible. -- @param #ARTY self --- @param #number distance Safe distance in meters. Default is 100 m. +-- @param #number distance Safe distance in meters. Default is 100 m. +-- @return self function ARTY:SetRearmingDistance(distance) self:F({distance=distance}) self.RearmingDistance=distance or 100 + return self end --- Assign a group, which is responsible for rearming the ARTY group. If the group is too far away from the ARTY group it will be guided towards the ARTY group. -- @param #ARTY self -- @param Wrapper.Group#GROUP group Group that is supposed to rearm the ARTY group. For the blue coalition, this is often a unarmed M818 transport whilst for red an unarmed Ural-375 transport can be used. +-- @return self function ARTY:SetRearmingGroup(group) self:F({group=group}) self.RearmingGroup=group + return self end --- Set the speed the rearming group moves at towards the ARTY group or the rearming place. -- @param #ARTY self -- @param #number speed Speed in km/h. +-- @return self function ARTY:SetRearmingGroupSpeed(speed) self:F({speed=speed}) self.RearmingGroupSpeed=speed + return self end --- Define if rearming group uses mainly roads to drive to the ARTY group or rearming place. -- @param #ARTY self -- @param #boolean onroad If true, rearming group uses mainly roads. If false, it drives directly to the ARTY group or rearming place. +-- @return self function ARTY:SetRearmingGroupOnRoad(onroad) self:F({onroad=onroad}) if onroad==nil then onroad=true end self.RearmingGroupOnRoad=onroad + return self end --- Define if ARTY group uses mainly roads to drive to the rearming place. -- @param #ARTY self -- @param #boolean onroad If true, ARTY group uses mainly roads. If false, it drives directly to the rearming place. +-- @return self function ARTY:SetRearmingArtyOnRoad(onroad) self:F({onroad=onroad}) if onroad==nil then onroad=true end self.RearmingArtyOnRoad=onroad + return self end --- Defines the rearming place of the ARTY group. If the place is too far away from the ARTY group it will be routed to the place. -- @param #ARTY self -- @param Core.Point#COORDINATE coord Coordinates of the rearming place. +-- @return self function ARTY:SetRearmingPlace(coord) self:F({coord=coord}) self.RearmingPlaceCoord=coord + return self end --- Set automatic relocation of ARTY group if a target is assigned which is out of range. The unit will drive automatically towards or away from the target to be in max/min firing range. -- @param #ARTY self -- @param #number maxdistance (Optional) The maximum distance in km the group will travel to get within firing range. Default is 50 km. No automatic relocation is performed if targets are assigned which are further away. --- @param #boolean onroad (Optional) If true, ARTY group uses roads whenever possible. Default false, i.e. group will move in a straight line to the assigned coordinate. +-- @param #boolean onroad (Optional) If true, ARTY group uses roads whenever possible. Default false, i.e. group will move in a straight line to the assigned coordinate. +-- @return self function ARTY:SetAutoRelocateToFiringRange(maxdistance, onroad) self:F({distance=maxdistance, onroad=onroad}) self.autorelocate=true @@ -1527,12 +1576,14 @@ function ARTY:SetAutoRelocateToFiringRange(maxdistance, onroad) onroad=false end self.autorelocateonroad=onroad + return self end --- Set relocate after firing. Group will find a new location after each engagement. Default is off -- @param #ARTY self -- @param #number rmax (Optional) Max distance in meters, the group will move to relocate. Default is 800 m. -- @param #number rmin (Optional) Min distance in meters, the group will move to relocate. Default is 300 m. +-- @return self function ARTY:SetAutoRelocateAfterEngagement(rmax, rmin) self.relocateafterfire=true self.relocateRmax=rmax or 800 @@ -1540,37 +1591,59 @@ function ARTY:SetAutoRelocateAfterEngagement(rmax, rmin) -- Ensure that Rmin<=Rmax self.relocateRmin=math.min(self.relocateRmin, self.relocateRmax) + + return self end --- Report messages of ARTY group turned on. This is the default. -- @param #ARTY self +-- @return self function ARTY:SetReportON() self.report=true + return self end --- Report messages of ARTY group turned off. Default is on. -- @param #ARTY self +-- @return self function ARTY:SetReportOFF() self.report=false + return self +end + +--- Respawn group once all units are dead. +-- @param #ARTY self +-- @param #number delay (Optional) Delay before respawn in seconds. +-- @return self +function ARTY:SetRespawnOnDeath(delay) + self.respawnafterdeath=true + self.respawndelay=delay + return self end --- Turn debug mode on. Information is printed to screen. -- @param #ARTY self +-- @return self function ARTY:SetDebugON() self.Debug=true + return self end --- Turn debug mode off. This is the default setting. -- @param #ARTY self +-- @return self function ARTY:SetDebugOFF() self.Debug=false + return self end --- Set default speed the group is moving at if not specified otherwise. -- @param #ARTY self -- @param #number speed Speed in km/h. +-- @return self function ARTY:SetSpeed(speed) self.Speed=speed + return self end --- Delete a target from target list. If the target is currently engaged, it is cancelled. @@ -1639,50 +1712,60 @@ end --- Define shell types that are counted to determine the ammo amount the ARTY group has. -- @param #ARTY self -- @param #table tableofnames Table of shell type names. +-- @return self function ARTY:SetShellTypes(tableofnames) self:F2(tableofnames) self.ammoshells={} for _,_type in pairs(tableofnames) do table.insert(self.ammoshells, _type) end + return self end --- Define rocket types that are counted to determine the ammo amount the ARTY group has. -- @param #ARTY self -- @param #table tableofnames Table of rocket type names. +-- @return self function ARTY:SetRocketTypes(tableofnames) self:F2(tableofnames) self.ammorockets={} for _,_type in pairs(tableofnames) do table.insert(self.ammorockets, _type) end + return self end --- Define missile types that are counted to determine the ammo amount the ARTY group has. -- @param #ARTY self -- @param #table tableofnames Table of rocket type names. +-- @return self function ARTY:SetMissileTypes(tableofnames) self:F2(tableofnames) self.ammomissiles={} for _,_type in pairs(tableofnames) do table.insert(self.ammomissiles, _type) end + return self end --- Set number of tactical nuclear warheads available to the group. -- Note that it can be max the number of normal shells. Also if all normal shells are empty, firing nuclear shells is also not possible any more until group gets rearmed. -- @param #ARTY self -- @param #number n Number of warheads for the whole group. +-- @return self function ARTY:SetTacNukeShells(n) self.Nukes=n + return self end --- Set nuclear warhead explosion strength. -- @param #ARTY self -- @param #number strength Explosion strength in kilo tons TNT. Default is 0.075 kt. +-- @return self function ARTY:SetTacNukeWarhead(strength) self.nukewarhead=strength or 0.075 self.nukewarhead=self.nukewarhead*1000*1000 -- convert to kg TNT. + return self end --- Set number of illumination shells available to the group. @@ -1690,10 +1773,12 @@ end -- @param #ARTY self -- @param #number n Number of illumination shells for the whole group. -- @param #number power (Optional) Power of illumination warhead in mega candela. Default 1.0 mcd. +-- @return self function ARTY:SetIlluminationShells(n, power) self.Nillu=n self.illuPower=power or 1.0 self.illuPower=self.illuPower * 1000000 + return self end --- Set minimum and maximum detotation altitude for illumination shells. A value between min/max is selected randomly. @@ -1701,6 +1786,7 @@ end -- @param #ARTY self -- @param #number minalt (Optional) Minium altitude in meters. Default 500 m. -- @param #number maxalt (Optional) Maximum altitude in meters. Default 1000 m. +-- @return self function ARTY:SetIlluminationMinMaxAlt(minalt, maxalt) self.illuMinalt=minalt or 500 self.illuMaxalt=maxalt or 1000 @@ -1708,6 +1794,7 @@ function ARTY:SetIlluminationMinMaxAlt(minalt, maxalt) if self.illuMinalt>self.illuMaxalt then self.illuMinalt=self.illuMaxalt end + return self end --- Set number of smoke shells available to the group. @@ -1715,38 +1802,46 @@ end -- @param #ARTY self -- @param #number n Number of smoke shells for the whole group. -- @param Utilities.Utils#SMOKECOLOR color (Optional) Color of the smoke. Default SMOKECOLOR.Red. +-- @return self function ARTY:SetSmokeShells(n, color) self.Nsmoke=n self.smokeColor=color or SMOKECOLOR.Red + return self end --- Set nuclear fires and extra demolition explosions. -- @param #ARTY self -- @param #number nfires (Optional) Number of big smoke and fire objects created in the demolition zone. -- @param #number demolitionrange (Optional) Demolition range in meters. +-- @return self function ARTY:SetTacNukeFires(nfires, range) self.nukefire=true self.nukefires=nfires self.nukerange=range + return self end --- Enable assigning targets and moves by placing markers on the F10 map. -- @param #ARTY self -- @param #number key (Optional) Authorization key. Only players knowing this key can assign targets. Default is no authorization required. -- @param #boolean readonly (Optional) Marks are readonly and cannot be removed by players. This also means that targets cannot be cancelled by removing the mark. Default false. +-- @return self function ARTY:SetMarkAssignmentsOn(key, readonly) self.markkey=key self.markallow=true if readonly==nil then self.markreadonly=false end + return self end --- Disable assigning targets by placing markers on the F10 map. -- @param #ARTY self +-- @return self function ARTY:SetMarkTargetsOff() self.markallow=false self.markkey=nil + return self end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -2618,17 +2713,19 @@ function ARTY:OnEventDead(EventData) -- Name of controllable. local _name=self.groupname - - env.info("FF Dead event for arty group") -- Check for correct group. - if EventData.IniGroupName==_name then + if EventData and EventData.IniGroupName and EventData.IniGroupName==_name then + + -- Name of the dead unit. + local unitname=tostring(EventData.IniUnitName) -- Dead Unit. - self:I(string.format("%s: Captured dead event for unit %s.", _name, tostring(EventData.IniUnitName))) + self:T(ARTY.id..string.format("%s: Captured dead event for unit %s.", _name, unitname)) -- FSM Dead event. We give one second for update of data base. - self:__Dead(1) + --self:__Dead(1, unitname) + self:Dead(unitname) end end @@ -2793,7 +2890,7 @@ function ARTY:onafterStatus(Controllable, From, Event, To) self:__Status(self.StatusInterval) else - self:E(string.format("Arty group %s is not alive!", self.groupname)) + self:E(ARTY.id..string.format("Arty group %s is not alive!", self.groupname)) end end @@ -3367,32 +3464,64 @@ end -- @param #string From From state. -- @param #string Event Event. -- @param #string To To state. -function ARTY:onafterDead(Controllable, From, Event, To) +-- @param #string Unitname Name of the unit that died. +function ARTY:onafterDead(Controllable, From, Event, To, Unitname) self:_EventFromTo("onafterDead", Event, From, To) - -- Number of units left in the group. - --[[ - local units=self.Controllable:GetUnits() - local nunits=0 - if units~=nil then - nunits=#units - end - ]] - + -- Number of units still alive. + --local nunits=self.Controllable and self.Controllable:CountAliveUnits() or 0 local nunits=self.Controllable:CountAliveUnits() -- Message. - local text=string.format("%s, one of our units just died! %d units left.", self.groupname, nunits) + local text=string.format("%s, our unit %s just died! %d units left.", self.groupname, Unitname, nunits) MESSAGE:New(text, 5):ToAllIf(self.Debug) self:I(ARTY.id..text) -- Go to stop state. if nunits==0 then - self:Stop() + + -- Cease Fire on current target. + if self.currentTarget then + self:CeaseFire(self.currentTarget) + end + + if self.respawnafterdeath then + -- Respawn group. + if not self.respawning then + self.respawning=true + self:__Respawn(self.respawndelay or 1) + end + else + -- Stop FSM. + self:Stop() + end end end + +--- After "Dead" event, when a unit has died. When all units of a group are dead trigger "Stop" event. +-- @param #ARTY self +-- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function ARTY:onafterRespawn(Controllable, From, Event, To) + self:_EventFromTo("onafterRespawn", Event, From, To) + + env.info("FF Respawning arty group") + + local group=self.Controllable --Wrapper.Group#GROUP + + -- Respawn group. + self.Controllable=group:Respawn() + + self.respawning=false + + -- Call status again. + self:__Status(-1) +end + --- After "Stop" event. Unhandle events and cease fire on current target. -- @param #ARTY self -- @param Wrapper.Controllable#CONTROLLABLE Controllable Controllable of the group. @@ -3452,7 +3581,7 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype) group:SetTask(fire) end ---- Set task for firing at a coordinate. +--- Set task for attacking a group. -- @param #ARTY self -- @param #ARTY.Target target Target data. function ARTY:_AttackGroup(target) @@ -3476,11 +3605,8 @@ function ARTY:_AttackGroup(target) -- Get task. local fire=group:TaskAttackGroup(targetgroup, weapontype, AI.Task.WeaponExpend.ONE, 1) - self:E("FF") - self:E(fire) - -- Execute task. - group:PushTask(fire) + group:SetTask(fire) end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index 133706fb9..d6f7c70fd 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -541,7 +541,8 @@ do -- DETECTION_BASE end - self.DetectionCount = self.DetectionSet:Count() + -- Count alive(!) groups only. Solves issue #1173 https://github.com/FlightControl-Master/MOOSE/issues/1173 + self.DetectionCount = self.DetectionSet:CountAlive() self.DetectionSet:ForEachGroupAlive( function( DetectionGroup ) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 0ff01cfbd..871da0168 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -77,6 +77,8 @@ -- @field #boolean safeparking If true, parking spots for aircraft are considered as occupied if e.g. a client aircraft is parked there. Default false. -- @field #boolean isunit If true, warehouse is represented by a unit instead of a static. -- @field #number lowfuelthresh Low fuel threshold. Triggers the event AssetLowFuel if for any unit fuel goes below this number. +-- @field #boolean respawnafterdestroyed If true, warehouse is respawned after it was destroyed. Assets are kept. +-- @field #number respawndelay Delay before respawn in seconds. -- @extends Core.Fsm#FSM --- Have your assets at the right place at the right time - or not! @@ -1569,6 +1571,8 @@ WAREHOUSE = { saveparking = false, isunit = false, lowfuelthresh = 0.15, + respawnafterdestroyed=false, + respawndelay = nil, } --- Item of the warehouse stock table. @@ -1745,7 +1749,7 @@ _WAREHOUSEDB = { --- Warehouse class version. -- @field #string version -WAREHOUSE.version="0.9.4" +WAREHOUSE.version="0.9.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Warehouse todo list. @@ -1908,6 +1912,7 @@ function WAREHOUSE:New(warehouse, alias) self:AddTransition("*", "AirbaseRecaptured", "*") -- Airbase was re-captured from other coalition. self:AddTransition("*", "AssetDead", "*") -- An asset group died. self:AddTransition("*", "Destroyed", "Destroyed") -- Warehouse was destroyed. All assets in stock are gone and warehouse is stopped. + self:AddTransition("Destroyed", "Respawn", "Running") -- Respawn warehouse after it was destroyed. ------------------------ --- Pseudo Functions --- @@ -1940,6 +1945,22 @@ function WAREHOUSE:New(warehouse, alias) -- @param #WAREHOUSE self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Respawn". + -- @function [parent=#WAREHOUSE] Respawn + -- @param #WAREHOUSE self + + --- Triggers the FSM event "Respawn" after a delay. + -- @function [parent=#WAREHOUSE] __Respawn + -- @param #WAREHOUSE self + -- @param #number delay Delay in seconds. + + --- On after "Respawn" event user function. + -- @function [parent=#WAREHOUSE] OnAfterRespawn + -- @param #WAREHOUSE self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "Pause". Pauses the warehouse. Assets can still be added and requests be made. However, requests are not processed. -- @function [parent=#WAREHOUSE] Pause -- @param #WAREHOUSE self @@ -2533,6 +2554,15 @@ function WAREHOUSE:SetSaveOnMissionEnd(path, filename) return self end +--- Set respawn after destroy. +-- @param #WAREHOUSE self +-- @return #WAREHOUSE self +function WAREHOUSE:SetRespawnAfterDestroyed(delay) + self.respawnafterdestroyed=true + self.respawndelay=delay + return self +end + --- Set the airbase belonging to this warehouse. -- Note that it has to be of the same coalition as the warehouse. @@ -3960,7 +3990,7 @@ function WAREHOUSE:onbeforeAddRequest(From, Event, To, warehouse, AssetDescripto end -- Warehouse is destroyed? - if self:IsDestroyed() then + if self:IsDestroyed() and not self.respawnafterdestroyed then self:_ErrorMessage("ERROR: Invalid request. Warehouse is destroyed!", 0) okay=false end @@ -4827,6 +4857,21 @@ function WAREHOUSE:onbeforeChangeCountry(From, Event, To, Country) return false end +--- Respawn warehouse. +-- @param #WAREHOUSE self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +function WAREHOUSE:onafterRespawn(From, Event, To) + + -- Info message. + local text=string.format("Respawning warehouse %s.", self.alias) + self:_InfoMessage(text) + + -- Respawn warehouse. + self.warehouse:ReSpawn() + +end --- On after "ChangeCountry" event. Warehouse is respawned with the specified country. All queued requests are deleted and the owned airbase is reset if the coalition is changed by changing the -- country. @@ -4963,22 +5008,34 @@ end function WAREHOUSE:onafterDestroyed(From, Event, To) -- Message. - local text=string.format("Warehouse %s was destroyed! Assets lost %d.", self.alias, #self.stock) + local text=string.format("Warehouse %s was destroyed! Assets lost %d. Respawn=%s", self.alias, #self.stock, tostring(self.respawnafterdestroyed)) self:_InfoMessage(text) - -- Remove all table entries from waiting queue and stock. - for k,_ in pairs(self.queue) do - self.queue[k]=nil + if self.respawnafterdestroyed then + + if self.respawndelay then + self:Pause() + self:__Respawn(self.respawndelay) + else + self:Respawn() + end + + else + + -- Remove all table entries from waiting queue and stock. + for k,_ in pairs(self.queue) do + self.queue[k]=nil + end + for k,_ in pairs(self.stock) do + self.stock[k]=nil + end + + --self.queue=nil + --self.queue={} + + --self.stock=nil + --self.stock={} end - for k,_ in pairs(self.stock) do - self.stock[k]=nil - end - - --self.queue=nil - --self.queue={} - - --self.stock=nil - --self.stock={} end @@ -6343,7 +6400,7 @@ function WAREHOUSE:_CheckRequestConsistancy(queue) end -- Is receiving warehouse destroyed? - if request.warehouse:IsDestroyed() then + if request.warehouse:IsDestroyed() and not self.respawnafterdestroyed then self:E(self.wid..string.format("ERROR: INVALID request. Requesting warehouse is destroyed!")) valid=false end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 385016112..846ed502a 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -3620,9 +3620,13 @@ function AIRBOSS:_CheckRecoveryTimes() if self:IsRecovering() and not recovery.OVER then - if #self.Qpattern>0 then + -- Get number of airborne aircraft units(!) currently in pattern. + local _,npattern=self:_GetQueueInfo(self.Qpattern) + + if npattern>0 then - local extmin=5*#self.Qpattern + -- Extend recovery time. 5 min per flight. + local extmin=5*npattern recovery.STOP=recovery.STOP+extmin*60 local text=string.format("We still got flights in the pattern.\nRecovery time prolonged by %d minutes.\nNow get your act together and no more bolters!", extmin) From 35653898cd2fa7387f7aefc9c862d38d9a476cbc Mon Sep 17 00:00:00 2001 From: FlightControl Date: Fri, 28 Jun 2019 17:15:40 +0300 Subject: [PATCH 04/22] WIP --- .../Moose/AI/AI_Escort_Dispatcher.lua | 152 ++++++++++++++++++ .../Moose/AI/AI_Escort_Dispatcher_Request.lua | 149 +++++++++++++++++ .../Moose/AI/AI_Escort_Request.lua | 8 +- Moose Development/Moose/Core/Event.lua | 8 +- Moose Development/Moose/Modules.lua | 2 + 5 files changed, 311 insertions(+), 8 deletions(-) create mode 100644 Moose Development/Moose/AI/AI_Escort_Dispatcher.lua create mode 100644 Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua new file mode 100644 index 000000000..686805d5f --- /dev/null +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua @@ -0,0 +1,152 @@ +--- **AI** -- (R2.4) - Models the assignment of AI escorts to player flights. +-- +-- ## Features: +-- -- +-- * Provides the facilities to trigger escorts when players join flight units. +-- * Provide different means how escorts can be triggered: +-- * Directly when a player joins a plane. +-- * Through the menu. +-- +-- === +-- +-- ## Test Missions: +-- +-- Test missions can be located on the main GITHUB site. +-- +-- [FlightControl-Master/MOOSE_MISSIONS] +-- +-- === +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_Escort_Dispatcher +-- @image AI_Escort_Dispatcher.JPG + + +--- @type AI_ESCORT_DISPATCHER +-- @field Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. +-- @field Core.Set#SET_GROUP EscortGroupSet The set of group AI escorting the EscortUnit. +-- @field #string EscortName Name of the escort. +-- @field #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. +-- @extends Core.Fsm#FSM + + +--- A dynamic cargo handling capability for AI groups. +-- +-- === +-- +-- @field #AI_ESCORT_DISPATCHER +AI_ESCORT_DISPATCHER = { + ClassName = "AI_ESCORT_DISPATCHER", +} + +--- @field #list +AI_ESCORT_DISPATCHER.AI_Escorts = {} + + +--- Creates a new AI_ESCORT_DISPATCHER object. +-- @param #AI_ESCORT_DISPATCHER self +-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. +-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts. +-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned. +-- @param #string EscortName Name of the escort. +-- @param #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. +-- @return #AI_ESCORT_DISPATCHER +function AI_ESCORT_DISPATCHER:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) + + local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER + + self.CarrierSet = CarrierSet + self.EscortSpawn = EscortSpawn + self.EscortAirbase = EscortAirbase + self.EscortName = EscortName + self.EscortBriefing = EscortBriefing + + self:SetStartState( "Idle" ) + + self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) + + self:AddTransition( "Idle", "Start", "Monitoring" ) + self:AddTransition( "Monitoring", "Stop", "Idle" ) + + -- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset. + function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier ) + self:F( { Carrier = Carrier:GetName() } ) + end + + return self +end + +function AI_ESCORT_DISPATCHER:onafterStart( From, Event, To ) + + self:HandleEvent( EVENTS.Birth ) + + self:HandleEvent( EVENTS.PlayerLeaveUnit ) + +end + + +--- @param #AI_ESCORT_DISPATCHER self +-- @param Core.Event#EVENTDATA EventData +function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) + + local PlayerGroupName = EventData.IniGroupName + local PlayerGroup = EventData.IniGroup + local PlayerUnit = EventData.IniUnit + + self:I({PlayerGroupName = PlayerGroupName } ) + self:I({PlayerGroup = PlayerGroup}) + self:I({FirstGroup = self.CarrierSet:GetFirst()}) + self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) + + if self.CarrierSet:FindGroup( PlayerGroupName ) then + if not self.AI_Escorts[PlayerGroupName] then + local LeaderUnit = PlayerUnit + local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase ) + local EscortSet = SET_GROUP:New() + EscortSet:AddGroup( EscortGroup ) + self:ScheduleOnce( 0.1, + function() + self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing ) + self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) + if EscortGroup:IsHelicopter() then + self.AI_Escorts[PlayerGroupName]:MenusHelicopters() + else + self.AI_Escorts[PlayerGroupName]:MenusAirplanes() + end + self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) + end + ) + end + end + +end + + +--- Start Trigger for AI_ESCORT_DISPATCHER +-- @function [parent=#AI_ESCORT_DISPATCHER] Start +-- @param #AI_ESCORT_DISPATCHER self + +--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER +-- @function [parent=#AI_ESCORT_DISPATCHER] __Start +-- @param #AI_ESCORT_DISPATCHER self +-- @param #number Delay + +--- Stop Trigger for AI_ESCORT_DISPATCHER +-- @function [parent=#AI_ESCORT_DISPATCHER] Stop +-- @param #AI_ESCORT_DISPATCHER self + +--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER +-- @function [parent=#AI_ESCORT_DISPATCHER] __Stop +-- @param #AI_ESCORT_DISPATCHER self +-- @param #number Delay + + + + + + diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua new file mode 100644 index 000000000..05f503fe7 --- /dev/null +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher_Request.lua @@ -0,0 +1,149 @@ +--- **AI** -- (R2.4) - Models the assignment of AI escorts to player flights upon request using the radio menu. +-- +-- ## Features: +-- -- +-- * Provides the facilities to trigger escorts when players join flight units. +-- * Provide different means how escorts can be triggered: +-- * Directly when a player joins a plane. +-- * Through the menu. +-- +-- === +-- +-- ## Test Missions: +-- +-- Test missions can be located on the main GITHUB site. +-- +-- [FlightControl-Master/MOOSE_MISSIONS] +-- +-- === +-- +-- === +-- +-- ### Author: **FlightControl** +-- +-- === +-- +-- @module AI.AI_ESCORT_DISPATCHER_REQUEST +-- @image AI_ESCORT_DISPATCHER_REQUEST.JPG + + +--- @type AI_ESCORT_DISPATCHER_REQUEST +-- @field Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. +-- @field Core.Set#SET_GROUP EscortGroupSet The set of group AI escorting the EscortUnit. +-- @field #string EscortName Name of the escort. +-- @field #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. +-- @extends Core.Fsm#FSM + + +--- A dynamic cargo handling capability for AI groups. +-- +-- === +-- +-- @field #AI_ESCORT_DISPATCHER_REQUEST +AI_ESCORT_DISPATCHER_REQUEST = { + ClassName = "AI_ESCORT_DISPATCHER_REQUEST", +} + +--- @field #list +AI_ESCORT_DISPATCHER_REQUEST.AI_Escorts = {} + + +--- Creates a new AI_ESCORT_DISPATCHER_REQUEST object. +-- @param #AI_ESCORT_DISPATCHER_REQUEST self +-- @param Core.Set#SET_GROUP CarrierSet The set of @{Wrapper.Group#GROUP} objects of carriers that will transport the cargo. +-- @param Core.Spawn#SPAWN EscortSpawn The spawn object that will spawn in the Escorts. +-- @param Wrapper.Airbase#AIRBASE EscortAirbase The airbase where the escorts are spawned. +-- @param #string EscortName Name of the escort. +-- @param #string EscortBriefing A text showing the AI_ESCORT briefing to the player. Note that if no EscortBriefing is provided, the default briefing will be shown. +-- @return #AI_ESCORT_DISPATCHER_REQUEST +function AI_ESCORT_DISPATCHER_REQUEST:New( CarrierSet, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) + + local self = BASE:Inherit( self, FSM:New() ) -- #AI_ESCORT_DISPATCHER_REQUEST + + self.CarrierSet = CarrierSet + self.EscortSpawn = EscortSpawn + self.EscortAirbase = EscortAirbase + self.EscortName = EscortName + self.EscortBriefing = EscortBriefing + + self:SetStartState( "Idle" ) + + self:AddTransition( "Monitoring", "Monitor", "Monitoring" ) + + self:AddTransition( "Idle", "Start", "Monitoring" ) + self:AddTransition( "Monitoring", "Stop", "Idle" ) + + -- Put a Dead event handler on CarrierSet, to ensure that when a carrier is destroyed, that all internal parameters are reset. + function self.CarrierSet.OnAfterRemoved( CarrierSet, From, Event, To, CarrierName, Carrier ) + self:F( { Carrier = Carrier:GetName() } ) + end + + return self +end + +function AI_ESCORT_DISPATCHER_REQUEST:onafterStart( From, Event, To ) + + self:HandleEvent( EVENTS.Birth ) + + self:HandleEvent( EVENTS.PlayerLeaveUnit ) + +end + + +--- @param #AI_ESCORT_DISPATCHER_REQUEST self +-- @param Core.Event#EVENTDATA EventData +function AI_ESCORT_DISPATCHER_REQUEST:OnEventBirth( EventData ) + + local PlayerGroupName = EventData.IniGroupName + local PlayerGroup = EventData.IniGroup + local PlayerUnit = EventData.IniUnit + + self:I({PlayerGroupName = PlayerGroupName } ) + self:I({PlayerGroup = PlayerGroup}) + self:I({FirstGroup = self.CarrierSet:GetFirst()}) + self:I({FindGroup = self.CarrierSet:FindGroup( PlayerGroupName )}) + + if self.CarrierSet:FindGroup( PlayerGroupName ) then + if not self.AI_Escorts[PlayerGroupName] then + local LeaderUnit = PlayerUnit + self:ScheduleOnce( 0.1, + function() + self.AI_Escorts[PlayerGroupName] = AI_ESCORT_REQUEST:New( LeaderUnit, self.EscortSpawn, self.EscortAirbase, self.EscortName, self.EscortBriefing ) + self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) + if PlayerGroup:IsHelicopter() then + self.AI_Escorts[PlayerGroupName]:MenusHelicopters() + else + self.AI_Escorts[PlayerGroupName]:MenusAirplanes() + end + self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) + end + ) + end + end + +end + + +--- Start Trigger for AI_ESCORT_DISPATCHER_REQUEST +-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Start +-- @param #AI_ESCORT_DISPATCHER_REQUEST self + +--- Start Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST +-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Start +-- @param #AI_ESCORT_DISPATCHER_REQUEST self +-- @param #number Delay + +--- Stop Trigger for AI_ESCORT_DISPATCHER_REQUEST +-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] Stop +-- @param #AI_ESCORT_DISPATCHER_REQUEST self + +--- Stop Asynchronous Trigger for AI_ESCORT_DISPATCHER_REQUEST +-- @function [parent=#AI_ESCORT_DISPATCHER_REQUEST] __Stop +-- @param #AI_ESCORT_DISPATCHER_REQUEST self +-- @param #number Delay + + + + + + diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua index 1b7a4288f..9cc537b4c 100644 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Request.lua @@ -206,7 +206,7 @@ AI_ESCORT_REQUEST = { -- Escort:__Start( 5 ) function AI_ESCORT_REQUEST:New( EscortUnit, EscortSpawn, EscortAirbase, EscortName, EscortBriefing ) - self.EscortGroupSet = SET_GROUP:New() + self.EscortGroupSet = SET_GROUP:New():FilterDeads():FilterCrashes() self.EscortSpawn = EscortSpawn self.EscortAirbase = EscortAirbase @@ -227,12 +227,12 @@ function AI_ESCORT_REQUEST:SpawnEscort() local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) - EscortGroup:OptionROTVertical() - EscortGroup:OptionROEHoldFire() - self:ScheduleOnce( 0.1, function( EscortGroup ) + EscortGroup:OptionROTVertical() + EscortGroup:OptionROEHoldFire() + self.EscortGroupSet:AddGroup( EscortGroup ) local LeaderEscort = self.EscortGroupSet:GetFirst() -- Wrapper.Group#GROUP diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 83c344e7a..0729dd498 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -864,9 +864,9 @@ function EVENT:onEvent( Event ) if Event.IniDCSGroup and Event.IniDCSGroup:isExist() then Event.IniDCSGroupName = Event.IniDCSGroup:getName() Event.IniGroup = GROUP:FindByName( Event.IniDCSGroupName ) - if Event.IniGroup then + --if Event.IniGroup then Event.IniGroupName = Event.IniDCSGroupName - end + --end end Event.IniPlayerName = Event.IniDCSUnit:getPlayerName() Event.IniCoalition = Event.IniDCSUnit:getCoalition() @@ -918,9 +918,9 @@ function EVENT:onEvent( Event ) if Event.TgtDCSGroup and Event.TgtDCSGroup:isExist() then Event.TgtDCSGroupName = Event.TgtDCSGroup:getName() Event.TgtGroup = GROUP:FindByName( Event.TgtDCSGroupName ) - if Event.TgtGroup then + --if Event.TgtGroup then Event.TgtGroupName = Event.TgtDCSGroupName - end + --end end Event.TgtPlayerName = Event.TgtDCSUnit:getPlayerName() Event.TgtCoalition = Event.TgtDCSUnit:getCoalition() diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index 2c485f2a2..a5e12f901 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -88,6 +88,8 @@ __Moose.Include( 'Scripts/Moose/AI/AI_Bai.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Formation.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Escort.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Escort_Request.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Dispatcher.lua' ) +__Moose.Include( 'Scripts/Moose/AI/AI_Escort_Dispatcher_Request.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_APC.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Cargo_Helicopter.lua' ) From 376a3553fab4299149c3e27aa15cd5f46f24d9d0 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 30 Jun 2019 17:34:00 +0200 Subject: [PATCH 05/22] SPOT - Fixed bug in LaseOnCoordinate. - Minor stuff. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 4 ++-- Moose Development/Moose/Core/Set.lua | 6 +++++- Moose Development/Moose/Core/Spot.lua | 19 ++++++++----------- .../Moose/Functional/Detection.lua | 2 +- .../Moose/Functional/Warehouse.lua | 2 +- Moose Development/Moose/Ops/Airboss.lua | 4 +++- 6 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index cd1836f6f..000327939 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1617,7 +1617,7 @@ do -- AI_A2A_DISPATCHER --- Check if the Squadron is visible before startup of the dispatcher. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @return #bool true if visible. + -- @return #boolean true if visible. -- @usage -- -- -- Set the Squadron visible before startup of dispatcher. @@ -2733,7 +2733,7 @@ do -- AI_A2A_DISPATCHER end - --- + --- Activate resource. -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded ) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 20554631b..23b148ad3 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -558,6 +558,10 @@ do -- SET_BASE --- Iterate the SET_BASE and derived classes and call an iterator function for the given SET_BASE, providing the Object for each element within the set and optional parameters. -- @param #SET_BASE self -- @param #function IteratorFunction The function that will be called. + -- @param #table arg Arguments of the IteratorFunction. + -- @param #SET_BASE Set (Optional) The set to use. Default self:GetSet(). + -- @param #function Function (Optional) A function returning a #boolean true/false. Only if true, the IteratorFunction is called. + -- @param #table FunctionArguments (Optional) Function arguments. -- @return #SET_BASE self function SET_BASE:ForEach( IteratorFunction, arg, Set, Function, FunctionArguments ) self:F3( arg ) @@ -571,7 +575,7 @@ do -- SET_BASE local Object = ObjectData self:T3( Object ) if Function then - if Function( unpack( FunctionArguments ), Object ) == true then + if Function( unpack( FunctionArguments or {} ), Object ) == true then IteratorFunction( Object, unpack( arg ) ) end else diff --git a/Moose Development/Moose/Core/Spot.lua b/Moose Development/Moose/Core/Spot.lua index 6395cd7b4..144c83ca3 100644 --- a/Moose Development/Moose/Core/Spot.lua +++ b/Moose Development/Moose/Core/Spot.lua @@ -285,11 +285,8 @@ do local RecceDcsUnit = self.Recce:GetDCSObject() - local aimat=Coordinate - aimat.y=aimat.y+1 - - self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 2, z = 0 }, aimat:GetVec3() ) - self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 2, z = 0 }, aimat:GetVec3(), LaserCode ) + self.SpotIR = Spot.createInfraRed( RecceDcsUnit, { x = 0, y = 1, z = 0 }, Coordinate:GetVec3() ) + self.SpotLaser = Spot.createLaser( RecceDcsUnit, { x = 0, y = 1, z = 0 }, Coordinate:GetVec3(), LaserCode ) if Duration then self.ScheduleID = self.LaseScheduler:Schedule( self, StopLase, {self}, Duration ) @@ -323,14 +320,14 @@ do self:__Lasing( -0.2 ) elseif self.TargetCoord then - local aimat=self.TargetCoord --Core.Point#COORDINATE - aimat.y=aimat.y+1+math.random(-100,100)/100 - aimat.x=aimat.x+math.random(-100,100)/100 + -- Wiggle the IR spot a bit. + local irvec3={x=self.TargetCoord.x+math.random(-100,100)/100, y=self.TargetCoord.y+math.random(-100,100)/100, z=self.TargetCoord.z} --#DCS.Vec3 + local lsvec3={x=self.TargetCoord.x, y=self.TargetCoord.y, z=self.TargetCoord.z} --#DCS.Vec3 - self.SpotIR:setPoint(aimat:GetVec3()) - self.SpotLaser:setPoint(self.TargetCoord:GetVec3()) + self.SpotIR:setPoint(irvec3) + self.SpotLaser:setPoint(lsvec3) - self:__Lasing( -0.2 ) + self:__Lasing(-0.25) else self:F( { "Target is not alive", self.Target:IsAlive() } ) end diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index d6f7c70fd..d57b1add6 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -1898,7 +1898,7 @@ do -- DETECTION_BASE --- Get the Detection Set. -- @param #DETECTION_BASE self - -- @return Core.Set#SET_BASE + -- @return #DETECTION_BASE self function DETECTION_BASE:GetDetectionSet() local DetectionSet = self.DetectionSet diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 871da0168..527138506 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -4986,7 +4986,7 @@ function WAREHOUSE:onafterAirbaseRecaptured(From, Event, To, Coalition) end ---- On after "AssetDead" event triggerd when an asset group died. +--- On after "AssetDead" event triggered when an asset group died. -- @param #WAREHOUSE self -- @param #string From From state. -- @param #string Event Event. diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 846ed502a..74b3778a3 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -12519,7 +12519,7 @@ end -- @param #number optdist Optimal distance in meters. -- @return #string Feedback message text. -- @return #string Debriefing text. --- @#AIRBOSS.RadioCall Distance radio call. Not implemented yet. +-- @return #AIRBOSS.RadioCall Distance radio call. Not implemented yet. function AIRBOSS:_DistanceCheck(playerData, optdist) if optdist==nil then @@ -12554,8 +12554,10 @@ function AIRBOSS:_DistanceCheck(playerData, optdist) hint=hint..string.format(" Optimal distance is %.1f NM.", UTILS.MetersToNM(optdist)) elseif playerData.difficulty==AIRBOSS.Difficulty.NORMAL then -- We keep it short normally. + hint="" elseif playerData.difficulty==AIRBOSS.Difficulty.HARD then -- No hint at all for the pros. + hint="" end -- Debriefing text. From 1afe1c48b4bd32ecde3ab131298aa57415ff288a Mon Sep 17 00:00:00 2001 From: FlightControl Date: Mon, 1 Jul 2019 17:28:49 +0300 Subject: [PATCH 06/22] Updates - Much improved version! --- .../Moose/AI/AI_Escort_Dispatcher.lua | 15 +++++++++------ Moose Development/Moose/AI/AI_Escort_Request.lua | 1 + Moose Development/Moose/Core/Spawn.lua | 5 +++-- 3 files changed, 13 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua index 686805d5f..a4000ccf8 100644 --- a/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_Escort_Dispatcher.lua @@ -98,6 +98,7 @@ function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) local PlayerGroup = EventData.IniGroup local PlayerUnit = EventData.IniUnit + self:I({EscortAirbase= self.EscortAirbase } ) self:I({PlayerGroupName = PlayerGroupName } ) self:I({PlayerGroup = PlayerGroup}) self:I({FirstGroup = self.CarrierSet:GetFirst()}) @@ -106,11 +107,13 @@ function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) if self.CarrierSet:FindGroup( PlayerGroupName ) then if not self.AI_Escorts[PlayerGroupName] then local LeaderUnit = PlayerUnit - local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase ) - local EscortSet = SET_GROUP:New() - EscortSet:AddGroup( EscortGroup ) - self:ScheduleOnce( 0.1, - function() + local EscortGroup = self.EscortSpawn:SpawnAtAirbase( self.EscortAirbase, SPAWN.Takeoff.Hot ) + self:I({EscortGroup = EscortGroup}) + + self:ScheduleOnce( 1, + function( EscortGroup ) + local EscortSet = SET_GROUP:New() + EscortSet:AddGroup( EscortGroup ) self.AI_Escorts[PlayerGroupName] = AI_ESCORT:New( LeaderUnit, EscortSet, self.EscortName, self.EscortBriefing ) self.AI_Escorts[PlayerGroupName]:FormationTrail( 0, 100, 0 ) if EscortGroup:IsHelicopter() then @@ -119,7 +122,7 @@ function AI_ESCORT_DISPATCHER:OnEventBirth( EventData ) self.AI_Escorts[PlayerGroupName]:MenusAirplanes() end self.AI_Escorts[PlayerGroupName]:__Start( 0.1 ) - end + end, EscortGroup ) end end diff --git a/Moose Development/Moose/AI/AI_Escort_Request.lua b/Moose Development/Moose/AI/AI_Escort_Request.lua index 9cc537b4c..5eb274278 100644 --- a/Moose Development/Moose/AI/AI_Escort_Request.lua +++ b/Moose Development/Moose/AI/AI_Escort_Request.lua @@ -269,6 +269,7 @@ function AI_ESCORT_REQUEST:onafterStart( EscortGroupSet ) self:F() if not self.MenuRequestEscort then + self.MainMenu = MENU_GROUP:New( self.PlayerGroup, self.EscortName ) self.MenuRequestEscort = MENU_GROUP_COMMAND:New( self.LeaderGroup, "Request new escort ", self.MainMenu, function() self:SpawnEscort() diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index ff6387c5a..dc30d2325 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -2215,10 +2215,11 @@ function SPAWN:ParkAtAirbase( SpawnAirbase, TerminalType, Parkingdata ) -- R2.2, self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, 1 ) for SpawnIndex = 2, self.SpawnMaxGroups do - self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + self:ParkAircraft( SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) + --self:ScheduleOnce( SpawnIndex * 0.1, SPAWN.ParkAircraft, self, SpawnAirbase, TerminalType, Parkingdata, SpawnIndex ) end - self:SetSpawnIndex() + self:SetSpawnIndex( 0 ) return nil end From 8780ec368723966e4bc461119dc521b32e96ea7d Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 3 Jul 2019 22:13:56 +0200 Subject: [PATCH 07/22] Docs Updated docs and function argument description of DETECTION and A2A_DISPATCHER. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 248 ++++++++++++------ .../Moose/Functional/Detection.lua | 56 ++-- 2 files changed, 207 insertions(+), 97 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 000327939..a96d3e970 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -832,6 +832,27 @@ do -- AI_A2A_DISPATCHER } + --- Squadron data structure. + -- @type AI_A2A_Dispatcher.Squadron + -- @field #string Name Name of the squadron. + -- @field #number ResourceCount Number of resources. + -- @field #string AirbaseName Name of the home airbase. + -- @field Wrapper.Airbase#AIRBASE Airbase The home airbase of the squadron. + -- @field #boolean Captured If true, airbase of the squadron was captured. + -- @field #table Resources Flight group resources Resources[TemplateID][GroupName] = SpawnGroup. + -- @field #boolean Uncontrolled If true, flight groups are spawned uncontrolled and later activated. + -- @field #table Gci GCI. + -- @field #number Overhead Squadron overhead. + -- @field #number Grouping Squadron flight group size. + -- @field #number Takeoff Takeoff type. + -- @field #number TakeoffAltitude Altitude in meters for spawn in air. + -- @field #number Landing Landing type. + -- @field #number FuelThreshold Fuel threshold [0,1] for RTB. + -- @field #string TankerName Name of the refuelling tanker. + -- @field #table Table of template group names of the squadron. + -- @field #table Spawn Table of spaws Core.Spawn#SPAWN. + -- @field #table TemplatePrefixes + --- Enumerator for spawns at airbases -- @type AI_A2A_DISPATCHER.Takeoff -- @extends Wrapper.Group#GROUP.Takeoff @@ -1021,7 +1042,8 @@ do -- AI_A2A_DISPATCHER end - --- @param #AI_A2A_DISPATCHER self + --- On after "Start" event. + -- @param #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:onafterStart( From, Event, To ) self:GetParent( self, AI_A2A_DISPATCHER ).onafterStart( self, From, Event, To ) @@ -1038,7 +1060,9 @@ do -- AI_A2A_DISPATCHER end - --- @param #AI_A2A_DISPATCHER self + --- Park defender. + -- @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 @@ -1055,7 +1079,8 @@ do -- AI_A2A_DISPATCHER end - --- @param #AI_A2A_DISPATCHER self + --- Event base captured. + -- @param #AI_A2A_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2A_DISPATCHER:OnEventBaseCaptured( EventData ) @@ -1073,13 +1098,15 @@ do -- AI_A2A_DISPATCHER end end - --- @param #AI_A2A_DISPATCHER self + --- Event dead or crash. + -- @param #AI_A2A_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2A_DISPATCHER:OnEventCrashOrDead( EventData ) self.Detection:ForgetDetectedUnit( EventData.IniUnitName ) end - --- @param #AI_A2A_DISPATCHER self + --- Event land. + -- @param #AI_A2A_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2A_DISPATCHER:OnEventLand( EventData ) self:F( "Landed" ) @@ -1106,7 +1133,8 @@ do -- AI_A2A_DISPATCHER end end - --- @param #AI_A2A_DISPATCHER self + --- Event engine shutdown. + -- @param #AI_A2A_DISPATCHER self -- @param Core.Event#EVENTDATA EventData function AI_A2A_DISPATCHER:OnEventEngineShutdown( EventData ) local DefenderUnit = EventData.IniUnit @@ -1163,7 +1191,7 @@ do -- AI_A2A_DISPATCHER --- Define the radius to disengage any target when the distance to the home base is larger than the specified meters. -- @param #AI_A2A_DISPATCHER self - -- @param #number DisengageRadius (Optional, Default = 300000) The radius to disengage a target when too far from the home base. + -- @param #number DisengageRadius (Optional, Default = 300000) The radius in meters to disengage a target when too far from the home base. -- @return #AI_A2A_DISPATCHER -- @usage -- @@ -1196,7 +1224,7 @@ do -- AI_A2A_DISPATCHER -- -- @param #AI_A2A_DISPATCHER self -- @param #number GciRadius (Optional, Default = 200000) The radius to ground control intercept detected targets from the nearest airbase. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1224,7 +1252,7 @@ do -- AI_A2A_DISPATCHER -- If a hot war is chosen then **no borders** actually need to be defined using the helicopter units other than it makes it easier sometimes for the mission maker to envisage where the red and blue territories roughly are. In a hot war the borders are effectively defined by the ground based radar coverage of a coalition. Set the noborders parameter to 1 -- @param #AI_A2A_DISPATCHER self -- @param Core.Zone#ZONE_BASE BorderZone An object derived from ZONE_BASE, or a list of objects derived from ZONE_BASE. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1258,7 +1286,7 @@ do -- AI_A2A_DISPATCHER -- * ... -- @param #AI_A2A_DISPATCHER self -- @param #boolean TacticalDisplay Provide a value of **true** to display every 30 seconds a tactical overview. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1279,7 +1307,7 @@ do -- AI_A2A_DISPATCHER -- The default damage treshold is by default set to 40%, which means that when the airplane is 40% damaged, it will go RTB. -- @param #AI_A2A_DISPATCHER self -- @param #number DamageThreshold A decimal number between 0 and 1, that expresses the %-tage of the damage treshold before going RTB. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1301,7 +1329,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #number CapMinSeconds The minimum amount of seconds for the random time interval. -- @param #number CapMaxSeconds The maximum amount of seconds for the random time interval. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1323,7 +1351,7 @@ do -- AI_A2A_DISPATCHER -- The default CAP limit is 1 CAP, which means one CAP group being spawned. -- @param #AI_A2A_DISPATCHER self -- @param #number CapLimit The maximum amount of CAP that can be airborne at the same time for the squadron. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -1339,7 +1367,10 @@ do -- AI_A2A_DISPATCHER return self end - + --- Set intercept. + -- @param #AI_A2A_DISPATCHER self + -- @param #number InterceptDelay Delay in seconds before intercept. + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetIntercept( InterceptDelay ) self.DefenderDefault.InterceptDelay = InterceptDelay @@ -1362,40 +1393,50 @@ do -- AI_A2A_DISPATCHER return FriendliesNearBy end - --- + --- Return the defender tasks table. -- @param #AI_A2A_DISPATCHER self + -- @return #table Defender tasks as table. function AI_A2A_DISPATCHER:GetDefenderTasks() return self.DefenderTasks or {} end - --- + --- Get defender task. -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @return #table Defender task. function AI_A2A_DISPATCHER:GetDefenderTask( Defender ) return self.DefenderTasks[Defender] end - --- + --- Get defender task FSM. -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @return Core.Fsm#FSM The FSM. function AI_A2A_DISPATCHER:GetDefenderTaskFsm( Defender ) return self:GetDefenderTask( Defender ).Fsm end - --- + --- Get target of defender. -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @return Target function AI_A2A_DISPATCHER:GetDefenderTaskTarget( Defender ) return self:GetDefenderTask( Defender ).Target end --- -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @return #string Squadron name of the defender task. function AI_A2A_DISPATCHER:GetDefenderTaskSquadronName( Defender ) return self:GetDefenderTask( Defender ).SquadronName end --- -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. function AI_A2A_DISPATCHER:ClearDefenderTask( Defender ) - if Defender:IsAlive() and self.DefenderTasks[Defender] then + if Defender and Defender:IsAlive() and self.DefenderTasks[Defender] then local Target = self.DefenderTasks[Defender].Target local Message = "Clearing (" .. self.DefenderTasks[Defender].Type .. ") " Message = Message .. Defender:GetName() @@ -1410,11 +1451,12 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. function AI_A2A_DISPATCHER:ClearDefenderTaskTarget( Defender ) local DefenderTask = self:GetDefenderTask( Defender ) - if Defender:IsAlive() and DefenderTask then + if Defender and Defender:IsAlive() and DefenderTask then local Target = DefenderTask.Target local Message = "Clearing (" .. DefenderTask.Type .. ") " Message = Message .. Defender:GetName() @@ -1437,8 +1479,14 @@ do -- AI_A2A_DISPATCHER end - --- + --- Set defender task. -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName Name of the squadron. + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @param #table Type Type of the defender task + -- @param Core.Fsm#FSM Fsm The defender task FSM. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem Target The defender detected item. + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target ) self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } ) @@ -1455,9 +1503,11 @@ do -- AI_A2A_DISPATCHER end - --- + --- Set defender task target. -- @param #AI_A2A_DISPATCHER self - -- @param Wrapper.Group#GROUP AIGroup + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection The detection object. + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefenderTaskTarget( Defender, AttackerDetection ) local Message = "(" .. self.DefenderTasks[Defender].Type .. ") " @@ -1540,13 +1590,13 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadron( "23th", "Batumi", "Su-27" ) -- -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadron( SquadronName, AirbaseName, TemplatePrefixes, ResourceCount ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self.DefenderSquadrons[SquadronName] + local DefenderSquadron = self.DefenderSquadrons[SquadronName] --#AI_A2A_Dispatcher.Squadron DefenderSquadron.Name = SquadronName DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) @@ -1577,7 +1627,8 @@ do -- AI_A2A_DISPATCHER --- Get an item from the Squadron table. -- @param #AI_A2A_DISPATCHER self - -- @return #table + -- @param #string SquadronName Name of the squadron. + -- @return #AI_A2A_Dispatcher.Squadron Defender squadron table. function AI_A2A_DISPATCHER:GetSquadron( SquadronName ) local DefenderSquadron = self.DefenderSquadrons[SquadronName] @@ -1594,7 +1645,7 @@ do -- AI_A2A_DISPATCHER -- They will lock the parking spot. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Set the Squadron visible before startup of dispatcher. @@ -1604,7 +1655,7 @@ do -- AI_A2A_DISPATCHER self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) + local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_Dispatcher.Squadron DefenderSquadron.Uncontrolled = true @@ -1627,7 +1678,7 @@ do -- AI_A2A_DISPATCHER self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) + local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_Dispatcher.Squadron if DefenderSquadron then return DefenderSquadron.Uncontrolled == true @@ -1755,7 +1806,7 @@ do -- AI_A2A_DISPATCHER --- -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:GetCAPDelay( SquadronName ) self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} @@ -1771,7 +1822,7 @@ do -- AI_A2A_DISPATCHER end end - --- + --- Check if squadron can do CAP. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. -- @return #table DefenderSquadron @@ -1804,7 +1855,7 @@ do -- AI_A2A_DISPATCHER end - --- + --- Check if squadron can do GCI. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. -- @return #table DefenderSquadron @@ -1829,11 +1880,11 @@ do -- AI_A2A_DISPATCHER end - --- + --- Set squadron GCI. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @param #number EngageMinSpeed The minimum speed at which the gci can be executed. - -- @param #number EngageMaxSpeed The maximum speed at which the gci can be executed. + -- @param #number EngageMinSpeed The minimum speed [km/h] at which the GCI can be executed. + -- @param #number EngageMaxSpeed The maximum speed [km/h] at which the GCI can be executed. -- @usage -- -- -- GCI Squadron execution. @@ -1923,7 +1974,7 @@ do -- AI_A2A_DISPATCHER -- -- A2ADispatcher:SetSquadronOverhead( "SquadronName", 1.5 ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronOverhead( SquadronName, Overhead ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -1945,7 +1996,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetDefaultGrouping( 2 ) -- -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultGrouping( Grouping ) self.DefenderDefault.Grouping = Grouping @@ -1967,7 +2018,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadronGrouping( "SquadronName", 2 ) -- -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronGrouping( SquadronName, Grouping ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -1997,7 +2048,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetDefaultTakeoff( AI_A2A_Dispatcher.Takeoff.Cold ) -- -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetDefaultTakeoff( Takeoff ) @@ -2027,7 +2078,7 @@ do -- AI_A2A_DISPATCHER -- A2ADispatcher:SetSquadronTakeoff( "SquadronName", AI_A2A_Dispatcher.Takeoff.Cold ) -- -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetSquadronTakeoff( SquadronName, Takeoff ) @@ -2086,7 +2137,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off in the air. -- A2ADispatcher:SetDefaultTakeoffInAir() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetDefaultTakeoffInAir() @@ -2107,7 +2158,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off in the air. -- A2ADispatcher:SetSquadronTakeoffInAir( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetSquadronTakeoffInAir( SquadronName, TakeoffAltitude ) @@ -2130,7 +2181,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off from the runway. -- A2ADispatcher:SetDefaultTakeoffFromRunway() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromRunway() @@ -2150,7 +2201,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from the runway. -- A2ADispatcher:SetSquadronTakeoffFromRunway( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromRunway( SquadronName ) @@ -2169,7 +2220,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default take-off at a hot parking spot. -- A2ADispatcher:SetDefaultTakeoffFromParkingHot() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingHot() @@ -2188,7 +2239,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off in the air. -- A2ADispatcher:SetSquadronTakeoffFromParkingHot( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingHot( SquadronName ) @@ -2207,7 +2258,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from a cold parking spot. -- A2ADispatcher:SetDefaultTakeoffFromParkingCold() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetDefaultTakeoffFromParkingCold() @@ -2227,7 +2278,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights take-off from a cold parking spot. -- A2ADispatcher:SetSquadronTakeoffFromParkingCold( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetSquadronTakeoffFromParkingCold( SquadronName ) @@ -2247,7 +2298,7 @@ do -- AI_A2A_DISPATCHER -- -- Set the default takeoff altitude when taking off in the air. -- A2ADispatcher:SetDefaultTakeoffInAirAltitude( 2000 ) -- This makes planes start at 2000 meters above the ground. -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetDefaultTakeoffInAirAltitude( TakeoffAltitude ) @@ -2267,7 +2318,7 @@ do -- AI_A2A_DISPATCHER -- -- Set the default takeoff altitude when taking off in the air. -- A2ADispatcher:SetSquadronTakeoffInAirAltitude( "SquadronName", 2000 ) -- This makes planes start at 2000 meters above the ground. -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- function AI_A2A_DISPATCHER:SetSquadronTakeoffInAirAltitude( SquadronName, TakeoffAltitude ) @@ -2294,7 +2345,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights by default despawn after landing and parking, and after engine shutdown. -- A2ADispatcher:SetDefaultLanding( AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLanding( Landing ) self.DefenderDefault.Landing = Landing @@ -2320,7 +2371,7 @@ do -- AI_A2A_DISPATCHER -- -- Let new flights despawn after landing and parking, and after engine shutdown. -- A2ADispatcher:SetSquadronLanding( "SquadronName", AI_A2A_Dispatcher.Landing.AtEngineShutdown ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLanding( SquadronName, Landing ) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -2379,7 +2430,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default to land near the airbase and despawn. -- A2ADispatcher:SetDefaultLandingNearAirbase() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingNearAirbase() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.NearAirbase ) @@ -2398,7 +2449,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights to land near the airbase and despawn. -- A2ADispatcher:SetSquadronLandingNearAirbase( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingNearAirbase( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.NearAirbase ) @@ -2416,7 +2467,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land at the runway and despawn. -- A2ADispatcher:SetDefaultLandingAtRunway() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingAtRunway() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtRunway ) @@ -2435,7 +2486,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights land at the runway and despawn. -- A2ADispatcher:SetSquadronLandingAtRunway( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingAtRunway( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtRunway ) @@ -2453,7 +2504,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights by default land and despawn at engine shutdown. -- A2ADispatcher:SetDefaultLandingAtEngineShutdown() -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefaultLandingAtEngineShutdown() self:SetDefaultLanding( AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) @@ -2472,7 +2523,7 @@ do -- AI_A2A_DISPATCHER -- -- Let flights land and despawn at engine shutdown. -- A2ADispatcher:SetSquadronLandingAtEngineShutdown( "SquadronName" ) -- - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetSquadronLandingAtEngineShutdown( SquadronName ) self:SetSquadronLanding( SquadronName, AI_A2A_DISPATCHER.Landing.AtEngineShutdown ) @@ -2484,7 +2535,7 @@ do -- AI_A2A_DISPATCHER -- The fuel treshold is by default set to 15%, which means that an airplane will stay in the air until 15% of its fuel has been consumed. -- @param #AI_A2A_DISPATCHER self -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -2506,7 +2557,7 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The name of the squadron. -- @param #number FuelThreshold A decimal number between 0 and 1, that expresses the %-tage of the treshold of fuel remaining in the tank when the plane will go RTB or Refuel. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -2526,7 +2577,7 @@ do -- AI_A2A_DISPATCHER --- Set the default tanker where defenders will Refuel in the air. -- @param #AI_A2A_DISPATCHER self -- @param #string TankerName A string defining the group name of the Tanker as defined within the Mission Editor. - -- @return #AI_A2A_DISPATCHER + -- @return #AI_A2A_DISPATCHER self -- @usage -- -- -- Now Setup the A2A dispatcher, and initialize it using the Detection object. @@ -2570,8 +2621,11 @@ do -- AI_A2A_DISPATCHER - - --- @param #AI_A2A_DISPATCHER self + --- Add defender to squadron. Resource count will get smaller. + -- @param #AI_A2A_DISPATCHER self + -- @param #AI_A2A_DISPATCHER.Squadron Squadron The squadron. + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @param #number Size Size of the group. function AI_A2A_DISPATCHER:AddDefenderToSquadron( Squadron, Defender, Size ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() @@ -2582,7 +2636,10 @@ do -- AI_A2A_DISPATCHER self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) end - --- @param #AI_A2A_DISPATCHER self + --- Remove defender from squadron. Resource count will increase. + -- @param #AI_A2A_DISPATCHER self + -- @param #AI_A2A_DISPATCHER.Squadron Squadron The squadron. + -- @param Wrapper.Group#GROUP Defender The defender group. function AI_A2A_DISPATCHER:RemoveDefenderFromSquadron( Squadron, Defender ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() @@ -2592,7 +2649,11 @@ do -- AI_A2A_DISPATCHER self.Defenders[ DefenderName ] = nil self:F( { DefenderName = DefenderName, SquadronResourceCount = Squadron.ResourceCount } ) end - + + --- Get squadron from defender. + -- @param #AI_A2A_DISPATCHER self + -- @param Wrapper.Group#GROUP Defender The defender group. + -- @return ? function AI_A2A_DISPATCHER:GetSquadronFromDefender( Defender ) self.Defenders = self.Defenders or {} local DefenderName = Defender:GetName() @@ -2605,7 +2666,6 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. function AI_A2A_DISPATCHER:EvaluateSWEEP( DetectedItem ) self:F( { DetectedItem.ItemID } ) @@ -2626,8 +2686,10 @@ do -- AI_A2A_DISPATCHER return nil end - --- + --- Count number of airborne CAP flights. -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName Name of the squadron. + -- @return #number Number of defender CAP groups. function AI_A2A_DISPATCHER:CountCapAirborne( SquadronName ) local CapCount = 0 @@ -2637,7 +2699,7 @@ do -- AI_A2A_DISPATCHER for AIGroup, DefenderTask in pairs( self:GetDefenderTasks() ) do if DefenderTask.SquadronName == SquadronName then if DefenderTask.Type == "CAP" then - if AIGroup:IsAlive() then + 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" ) @@ -2654,8 +2716,10 @@ do -- AI_A2A_DISPATCHER end - --- + --- Count number of engaging defender groups. -- @param #AI_A2A_DISPATCHER self + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detection object. + -- @return #number Number of defender groups engaging. function AI_A2A_DISPATCHER:CountDefendersEngaged( AttackerDetection ) -- First, count the active AIGroups Units, targetting the DetectedSet @@ -2665,9 +2729,10 @@ do -- AI_A2A_DISPATCHER --DetectedSet:Flush() local DefenderTasks = self:GetDefenderTasks() + for DefenderGroup, DefenderTask in pairs( DefenderTasks ) do local Defender = DefenderGroup -- Wrapper.Group#GROUP - local DefenderTaskTarget = DefenderTask.Target + local DefenderTaskTarget = DefenderTask.Target --Functional.Detection#DETECTION_BASE.DetectedItem local DefenderSquadronName = DefenderTask.SquadronName if DefenderTaskTarget and DefenderTaskTarget.Index == AttackerDetection.Index then @@ -2689,8 +2754,11 @@ do -- AI_A2A_DISPATCHER return DefenderCount end - --- + --- Count defenders to be engaged if number of attackers larger than number of defenders. -- @param #AI_A2A_DISPATCHER self + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #number DefenderCount Number of defenders. + -- @return #table Table of friendly groups. function AI_A2A_DISPATCHER:CountDefendersToBeEngaged( AttackerDetection, DefenderCount ) local Friendlies = nil @@ -2735,6 +2803,10 @@ do -- AI_A2A_DISPATCHER --- Activate resource. -- @param #AI_A2A_DISPATCHER self + -- @param #AI_A2A_DISPATCHER.Squadron DefenderSquadron The defender squadron. + -- @param #number DefendersNeeded Number of defenders needed. Default 4. + -- @return Wrapper.Group#GROUP The defender group. + -- @return #boolean Grouping. function AI_A2A_DISPATCHER:ResourceActivate( DefenderSquadron, DefendersNeeded ) local SquadronName = DefenderSquadron.Name @@ -2807,8 +2879,12 @@ do -- AI_A2A_DISPATCHER return nil, nil end - --- + --- On after "CAP" event. -- @param #AI_A2A_DISPATCHER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string SquadronName Name of the squadron. function AI_A2A_DISPATCHER:onafterCAP( From, Event, To, SquadronName ) self:F({SquadronName = SquadronName}) @@ -2883,8 +2959,13 @@ do -- AI_A2A_DISPATCHER end - --- + --- On after "ENGAGE" event. -- @param #AI_A2A_DISPATCHER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #table Defenders Defenders table. function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) if Defenders then @@ -2900,8 +2981,14 @@ do -- AI_A2A_DISPATCHER end end - --- + --- On after "GCI" event. -- @param #AI_A2A_DISPATCHER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #number DefendersMissing Number of missing defenders. + -- @param #table DefenderFriendlies Friendly defenders. function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) @@ -3085,8 +3172,7 @@ do -- AI_A2A_DISPATCHER --- Creates an ENGAGE task when there are human friendlies airborne near the targets. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. + -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units or nil. function AI_A2A_DISPATCHER:EvaluateENGAGE( DetectedItem ) self:F( { DetectedItem.ItemID } ) @@ -3106,14 +3192,14 @@ do -- AI_A2A_DISPATCHER return DefenderGroups end - return nil, nil + return nil end --- Creates an GCI task when there are targets for it. -- @param #AI_A2A_DISPATCHER self -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. - -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units. - -- @return #nil If there are no targets to be set. + -- @return Core.Set#SET_UNIT TargetSetUnit: The target set of units or nil if there are no targets to be set. + -- @return #table Table of friendly groups. function AI_A2A_DISPATCHER:EvaluateGCI( DetectedItem ) self:F( { DetectedItem.ItemID } ) @@ -3273,7 +3359,7 @@ do --- Calculates which HUMAN friendlies are nearby the area. -- @param #AI_A2A_DISPATCHER self - -- @param DetectedItem The detected item. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. function AI_A2A_DISPATCHER:GetPlayerFriendliesNearBy( DetectedItem ) @@ -3319,7 +3405,7 @@ do --- Calculates which friendlies are nearby the area. -- @param #AI_A2A_DISPATCHER self - -- @param DetectedItem The detected item. + -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #number, Core.Report#REPORT The amount of friendlies and a text string explaining which friendlies of which type. function AI_A2A_DISPATCHER:GetFriendliesNearBy( DetectedItem ) diff --git a/Moose Development/Moose/Functional/Detection.lua b/Moose Development/Moose/Functional/Detection.lua index d57b1add6..851977790 100644 --- a/Moose Development/Moose/Functional/Detection.lua +++ b/Moose Development/Moose/Functional/Detection.lua @@ -291,17 +291,34 @@ do -- DETECTION_BASE --- @type DETECTION_BASE.DetectedItems -- @list <#DETECTION_BASE.DetectedItem> - --- @type DETECTION_BASE.DetectedItem + --- Detected item data structrue. + -- @type DETECTION_BASE.DetectedItem -- @field #boolean IsDetected Indicates if the DetectedItem has been detected or not. - -- @field Core.Set#SET_UNIT Set - -- @field Core.Set#SET_UNIT Set -- The Set of Units in the detected area. - -- @field Core.Zone#ZONE_UNIT Zone -- The Zone of the detected area. - -- @field #boolean Changed Documents if the detected area has changes. + -- @field Core.Set#SET_UNIT Set The Set of Units in the detected area. + -- @field Core.Zone#ZONE_UNIT Zone The Zone of the detected area. + -- @field #boolean Changed Documents if the detected area has changed. -- @field #table Changes A list of the changes reported on the detected area. (It is up to the user of the detected area to consume those changes). - -- @field #number ID -- The identifier of the detected area. + -- @field #number ID The identifier of the detected area. -- @field #boolean FriendliesNearBy Indicates if there are friendlies within the detected area. -- @field Wrapper.Unit#UNIT NearestFAC The nearest FAC near the Area. -- @field Core.Point#COORDINATE Coordinate The last known coordinate of the DetectedItem. + -- @field Core.Point#COORDINATE InterceptCoord Intercept coordiante. + -- @field #number DistanceRecce Distance in meters of the Recce. + -- @field #number Index Detected item key. Could also be a string. + -- @field #string ItemID ItemPrefix .. "." .. self.DetectedItemMax. + -- @field #boolean Locked Lock detected item. + -- @field #table PlayersNearBy Table of nearby players. + -- @field #table FriendliesDistance Table of distances to friendly units. + -- @field #string TypeName Type name of the detected unit. + -- @field #string CategoryName Catetory name of the detected unit. + -- @field #string Name Name of the detected object. + -- @field #boolean IsVisible If true, detected object is visible. + -- @field #number LastTime Last time the detected item was seen. + -- @field DCS#Vec3 LastPos Last known position of the detected item. + -- @field DCS#Vec3 LastVelocity Last recorded 3D velocity vector of the detected item. + -- @field #boolean KnowType Type of detected item is known. + -- @field #boolean KnowDistance Distance to the detected item is known. + -- @field #number Distance Distance to the detected item. --- DETECTION constructor. -- @param #DETECTION_BASE self @@ -1234,7 +1251,7 @@ do -- DETECTION_BASE --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self - -- @param DetectedItem + -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @param DCS#Unit.Category Category The category of the unit. -- @return #boolean true if there are friendlies nearby function DETECTION_BASE:IsFriendliesNearBy( DetectedItem, Category ) @@ -1244,7 +1261,7 @@ do -- DETECTION_BASE --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self - -- @param DetectedItem + -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @param DCS#Unit.Category Category The category of the unit. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearBy( DetectedItem, Category ) @@ -1254,6 +1271,7 @@ do -- DETECTION_BASE --- Returns if there are friendlies nearby the intercept ... -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean trhe if there are friendlies near the intercept. function DETECTION_BASE:IsFriendliesNearIntercept( DetectedItem ) @@ -1262,6 +1280,7 @@ do -- DETECTION_BASE --- Returns friendly units nearby the intercept point ... -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetFriendliesNearIntercept( DetectedItem ) @@ -1270,7 +1289,8 @@ do -- DETECTION_BASE --- Returns the distance used to identify friendlies near the deteted item ... -- @param #DETECTION_BASE self - -- @return #number The distance. + -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. + -- @return #table A table of distances to friendlies. function DETECTION_BASE:GetFriendliesDistance( DetectedItem ) return DetectedItem.FriendliesDistance @@ -1278,6 +1298,7 @@ do -- DETECTION_BASE --- Returns if there are friendlies nearby the FAC units ... -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean trhe if there are friendlies nearby function DETECTION_BASE:IsPlayersNearBy( DetectedItem ) @@ -1286,6 +1307,7 @@ do -- DETECTION_BASE --- Returns friendly units nearby the FAC units ... -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem DetectedItem The detected item. -- @return #map<#string,Wrapper.Unit#UNIT> The map of Friendly UNITs. function DETECTION_BASE:GetPlayersNearBy( DetectedItem ) @@ -1294,10 +1316,11 @@ do -- DETECTION_BASE --- Background worker function to determine if there are friendlies nearby ... -- @param #DETECTION_BASE self + -- @param #table TargetData function DETECTION_BASE:ReportFriendliesNearBy( TargetData ) --self:F( { "Search Friendlies", DetectedItem = TargetData.DetectedItem } ) - local DetectedItem = TargetData.DetectedItem -- Functional.Detection#DETECTION_BASE.DetectedItem + local DetectedItem = TargetData.DetectedItem --#DETECTION_BASE.DetectedItem local DetectedSet = TargetData.DetectedItem.Set local DetectedUnit = DetectedSet:GetFirst() -- Wrapper.Unit#UNIT @@ -1519,13 +1542,13 @@ do -- DETECTION_BASE --- Adds a new DetectedItem to the DetectedItems list. -- The DetectedItem is a table and contains a SET_UNIT in the field Set. -- @param #DETECTION_BASE self - -- @param ItemPrefix - -- @param DetectedItemKey The key of the DetectedItem. + -- @param #string ItemPrefix Prefix of detected item. + -- @param #number DetectedItemKey The key of the DetectedItem. Default self.DetectedItemMax. Could also be a string in principle. -- @param Core.Set#SET_UNIT Set (optional) The Set of Units to be added. -- @return #DETECTION_BASE.DetectedItem function DETECTION_BASE:AddDetectedItem( ItemPrefix, DetectedItemKey, Set ) - local DetectedItem = {} + local DetectedItem = {} --#DETECTION_BASE.DetectedItem self.DetectedItemCount = self.DetectedItemCount + 1 self.DetectedItemMax = self.DetectedItemMax + 1 @@ -1706,6 +1729,7 @@ do -- DETECTION_BASE --- Checks if there is at least one UNIT detected in the Set of the the DetectedItem. -- @param #DETECTION_BASE self + -- @param #DETECTION_BASE.DetectedItem DetectedItem -- @return #boolean true if at least one UNIT is detected from the DetectedSet, false if no UNIT was detected from the DetectedSet. function DETECTION_BASE:IsDetectedItemDetected( DetectedItem ) @@ -1832,8 +1856,7 @@ do -- DETECTION_BASE --- Get a list of the detected item coordinates. -- @param #DETECTION_BASE self - -- @param #DETECTION_BASE.DetectedItem DetectedItem The DetectedItem to set the coordinate at. - -- @return Core.Point#COORDINATE + -- @return #table A table of Core.Point#COORDINATE function DETECTION_BASE:GetDetectedItemCoordinates() local Coordinates = {} @@ -2033,7 +2056,8 @@ do -- DETECTION_UNITS function DETECTION_UNITS:CreateDetectionItems() -- Loop the current detected items, and check if each object still exists and is detected. - for DetectedItemKey, DetectedItem in pairs( self.DetectedItems ) do + for DetectedItemKey, _DetectedItem in pairs( self.DetectedItems ) do + local DetectedItem=_DetectedItem --#DETECTION_BASE.DetectedItem local DetectedItemSet = DetectedItem.Set -- Core.Set#SET_UNIT From 6f140be7108e91f737c7b330d76188d2f207690e Mon Sep 17 00:00:00 2001 From: Frank Date: Fri, 5 Jul 2019 20:01:26 +0200 Subject: [PATCH 08/22] Update A2A_DISPATCHER - Docs CONTROLLABLE - OptionROEOpenFireWeaponFree() - Fixed OptionRTBBingoFuel() --- .../Moose/AI/AI_A2A_Dispatcher.lua | 53 +++++++++++++------ .../Moose/Wrapper/Controllable.lua | 52 ++++++++++++++++-- 2 files changed, 85 insertions(+), 20 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index a96d3e970..2114b1fce 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -833,7 +833,7 @@ do -- AI_A2A_DISPATCHER --- Squadron data structure. - -- @type AI_A2A_Dispatcher.Squadron + -- @type AI_A2A_DISPATCHER.Squadron -- @field #string Name Name of the squadron. -- @field #number ResourceCount Number of resources. -- @field #string AirbaseName Name of the home airbase. @@ -978,15 +978,24 @@ do -- AI_A2A_DISPATCHER -- @param #string From -- @param #string Event -- @param #string To + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #number DefendersMissing Number of missing defenders. + -- @param #table DefenderFriendlies Friendly defenders. --- GCI Trigger for AI_A2A_DISPATCHER -- @function [parent=#AI_A2A_DISPATCHER] GCI -- @param #AI_A2A_DISPATCHER self + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #number DefendersMissing Number of missing defenders. + -- @param #table DefenderFriendlies Friendly defenders. --- GCI Asynchronous Trigger for AI_A2A_DISPATCHER -- @function [parent=#AI_A2A_DISPATCHER] __GCI -- @param #AI_A2A_DISPATCHER self -- @param #number Delay + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #number DefendersMissing Number of missing defenders. + -- @param #table DefenderFriendlies Friendly defenders. self:AddTransition( "*", "ENGAGE", "*" ) @@ -996,6 +1005,8 @@ do -- AI_A2A_DISPATCHER -- @param #string From -- @param #string Event -- @param #string To + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #table Defenders Defenders table. -- @return #boolean --- ENGAGE Handler OnAfter for AI_A2A_DISPATCHER @@ -1004,15 +1015,21 @@ do -- AI_A2A_DISPATCHER -- @param #string From -- @param #string Event -- @param #string To - + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #table Defenders Defenders table. + --- ENGAGE Trigger for AI_A2A_DISPATCHER -- @function [parent=#AI_A2A_DISPATCHER] ENGAGE -- @param #AI_A2A_DISPATCHER self + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #table Defenders Defenders table. --- ENGAGE Asynchronous Trigger for AI_A2A_DISPATCHER -- @function [parent=#AI_A2A_DISPATCHER] __ENGAGE -- @param #AI_A2A_DISPATCHER self -- @param #number Delay + -- @param Functional.Detection#DETECTION_BASE.DetectedItem AttackerDetection Detected item. + -- @param #table Defenders Defenders table. -- Subscribe to the CRASH event so that when planes are shot @@ -1062,7 +1079,7 @@ do -- AI_A2A_DISPATCHER --- Park defender. -- @param #AI_A2A_DISPATCHER self - -- @param #AI_A2A_Dispatcher.Squadron DefenderSquadron The squadron. + -- @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 @@ -1384,7 +1401,7 @@ do -- AI_A2A_DISPATCHER --- Calculates which AI friendlies are nearby the area -- @param #AI_A2A_DISPATCHER self - -- @param DetectedItem + -- @param Functional.Detection#DETECTION_BASE.DetectedItem DetectedItem -- @return #table A list of the friendlies nearby. function AI_A2A_DISPATCHER:GetAIFriendliesNearBy( DetectedItem ) @@ -1489,7 +1506,7 @@ do -- AI_A2A_DISPATCHER -- @return #AI_A2A_DISPATCHER self function AI_A2A_DISPATCHER:SetDefenderTask( SquadronName, Defender, Type, Fsm, Target ) - self:F( { SquadronName = SquadronName, Defender = Defender:GetName() } ) + self:F( { SquadronName = SquadronName, Defender = Defender:GetName(), Type=Type, Target=Target } ) self.DefenderTasks[Defender] = self.DefenderTasks[Defender] or {} self.DefenderTasks[Defender].Type = Type @@ -1596,7 +1613,7 @@ do -- AI_A2A_DISPATCHER self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self.DefenderSquadrons[SquadronName] --#AI_A2A_Dispatcher.Squadron + local DefenderSquadron = self.DefenderSquadrons[SquadronName] --#AI_A2A_DISPATCHER.Squadron DefenderSquadron.Name = SquadronName DefenderSquadron.Airbase = AIRBASE:FindByName( AirbaseName ) @@ -1628,7 +1645,7 @@ do -- AI_A2A_DISPATCHER --- Get an item from the Squadron table. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName Name of the squadron. - -- @return #AI_A2A_Dispatcher.Squadron Defender squadron table. + -- @return #AI_A2A_DISPATCHER.Squadron Defender squadron table. function AI_A2A_DISPATCHER:GetSquadron( SquadronName ) local DefenderSquadron = self.DefenderSquadrons[SquadronName] @@ -1655,7 +1672,7 @@ do -- AI_A2A_DISPATCHER self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_Dispatcher.Squadron + local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_DISPATCHER.Squadron DefenderSquadron.Uncontrolled = true @@ -1678,7 +1695,7 @@ do -- AI_A2A_DISPATCHER self.DefenderSquadrons[SquadronName] = self.DefenderSquadrons[SquadronName] or {} - local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_Dispatcher.Squadron + local DefenderSquadron = self:GetSquadron( SquadronName ) --#AI_A2A_DISPATCHER.Squadron if DefenderSquadron then return DefenderSquadron.Uncontrolled == true @@ -2968,6 +2985,8 @@ do -- AI_A2A_DISPATCHER -- @param #table Defenders Defenders table. function AI_A2A_DISPATCHER:onafterENGAGE( From, Event, To, AttackerDetection, Defenders ) + self:F("ENGAGING "..tostring(AttackerDetection.Name)) + if Defenders then for DefenderID, Defender in pairs( Defenders ) do @@ -2990,6 +3009,8 @@ do -- AI_A2A_DISPATCHER -- @param #number DefendersMissing Number of missing defenders. -- @param #table DefenderFriendlies Friendly defenders. function AI_A2A_DISPATCHER:onafterGCI( From, Event, To, AttackerDetection, DefendersMissing, DefenderFriendlies ) + + self:F("GCI "..tostring(AttackerDetection.Name)) self:F( { From, Event, To, AttackerDetection.Index, DefendersMissing, DefenderFriendlies } ) @@ -3262,7 +3283,7 @@ do -- AI_A2A_DISPATCHER end end - local Report = REPORT:New( "\nTactical Overview" ) + local Report = REPORT:New( "Tactical Overview" ) local DefenderGroupCount = 0 @@ -3299,15 +3320,15 @@ do -- AI_A2A_DISPATCHER if self.TacticalDisplay then -- Show tactical situation - Report:Add( string.format( "\n - Target %s ( %s ): ( #%d ) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) + Report:Add( string.format( "\n- Target %s ( %s ): ( #%d ) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then - if Defender:IsAlive() then + if Defender and Defender:IsAlive() then DefenderGroupCount = DefenderGroupCount + 1 local Fuel = Defender:GetFuelMin() * 100 local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", Defender:GetName(), DefenderTask.Type, DefenderTask.Fsm:GetState(), @@ -3322,7 +3343,7 @@ do -- AI_A2A_DISPATCHER end if self.TacticalDisplay then - Report:Add( "\n - No Targets:") + Report:Add( "\n- No Targets:") local TaskCount = 0 for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do TaskCount = TaskCount + 1 @@ -3333,7 +3354,7 @@ do -- AI_A2A_DISPATCHER local Fuel = Defender:GetFuelMin() * 100 local Damage = Defender:GetLife() / Defender:GetLife0() * 100 DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", Defender:GetName(), DefenderTask.Type, DefenderTask.Fsm:GetState(), @@ -3344,7 +3365,7 @@ do -- AI_A2A_DISPATCHER end end end - Report:Add( string.format( "\n - %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) + Report:Add( string.format( "\n- %d Tasks - %d Defender Groups", TaskCount, DefenderGroupCount ) ) self:F( Report:Text( "\n" ) ) trigger.action.outText( Report:Text( "\n" ), 25 ) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index a1302776d..3b322abac 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2806,6 +2806,8 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad return nil end +--- Check if a target is detected. +-- @param Wrapper.Controllable#CONTROLLABLE self function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK ) self:F2( self.ControllableName ) @@ -2851,7 +2853,7 @@ function CONTROLLABLE:OptionROEHoldFirePossible() return nil end ---- Holding weapons. +--- Weapons Hold: AI will hold fire under all circumstances. -- @param Wrapper.Controllable#CONTROLLABLE self -- @return Wrapper.Controllable#CONTROLLABLE self function CONTROLLABLE:OptionROEHoldFire() @@ -2893,7 +2895,7 @@ function CONTROLLABLE:OptionROEReturnFirePossible() return nil end ---- Return fire. +--- Return Fire: AI will only engage threats that shoot first. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:OptionROEReturnFire() @@ -2935,7 +2937,7 @@ function CONTROLLABLE:OptionROEOpenFirePossible() return nil end ---- Openfire. +--- Open Fire (Only Designated): AI will engage only targets specified in its taskings. -- @param #CONTROLLABLE self -- @return #CONTROLLABLE self function CONTROLLABLE:OptionROEOpenFire() @@ -2959,6 +2961,45 @@ function CONTROLLABLE:OptionROEOpenFire() return nil end +--- Can the CONTROLLABLE attack priority designated targets? Only for AIR! +-- @param #CONTROLLABLE self +-- @return #boolean +function CONTROLLABLE:OptionROEOpenFireWeaponFreePossible() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + if self:IsAir() then + return true + end + + return false + end + + return nil +end + +--- Open Fire, Weapons Free (Priority Designated): AI will engage any enemy group it detects, but will prioritize targets specified in the groups tasking. +-- **Only for AIR units!** +-- @param #CONTROLLABLE self +-- @return #CONTROLLABLE self +function CONTROLLABLE:OptionROEOpenFireWeaponFree() + self:F2( { self.ControllableName } ) + + local DCSControllable = self:GetDCSObject() + if DCSControllable then + local Controller = self:_GetController() + + if self:IsAir() then + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE_WEAPON_FREE ) + end + + return self + end + + return nil +end + --- Can the CONTROLLABLE attack targets of opportunity? -- @param #CONTROLLABLE self -- @return #boolean @@ -3231,7 +3272,10 @@ end function CONTROLLABLE:OptionRTBBingoFuel( RTB ) --R2.2 self:F2( { self.ControllableName } ) - RTB = RTB or true + --RTB = RTB or true + if RTB==nil then + RTB=true + end local DCSControllable = self:GetDCSObject() if DCSControllable then From 6973e8c028e525a57bbe4e6a48d45a728dface1f Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 6 Jul 2019 20:23:07 +0200 Subject: [PATCH 09/22] CAP AI_A2A_PATROL: - Added optional race track pattern. AI_A2A_DISPATCHER: - Added option for CAP patrolling in a race track pattern. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 97 ++++++++++++++- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 117 ++++++++++++++---- 2 files changed, 187 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 2114b1fce..ecc9005b6 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -643,6 +643,25 @@ do -- AI_A2A_DISPATCHER -- -- Setup the Refuelling for squadron "Gelend", at tanker (group) "TankerGelend" when the fuel in the tank of the CAP defenders is less than 80%. -- A2ADispatcher:SetSquadronFuelThreshold( "Gelend", 0.8 ) -- A2ADispatcher:SetSquadronTanker( "Gelend", "TankerGelend" ) + -- + -- ## 7.4 Set up race track pattern + -- + -- By default, flights patrol randomly within the CAP zone. It is also possible to let them fly a race track pattern using the + -- @{#AI_A2A_DISPATCHER.SetDefaultCapRacetrack}(*LeglengthMin*, *LeglengthMax*, *HeadingMin*, *HeadingMax*, *DurationMin*, *DurationMax*) or + -- @{#AI_A2A_DISPATCHER.SetSquadronCapRacetrack}(*SquadronName*, *LeglengthMin*, *LeglengthMax*, *HeadingMin*, *HeadingMax*, *DurationMin*, *DurationMax*) functions. + -- The first function enables this for all squadrons, the latter only for specific squadrons. For example, + -- + -- -- Enable race track pattern for CAP squadron "Mineralnye". + -- A2ADispatcher:SetSquadronCapRacetrack("Mineralnye", 10000, 20000, 90, 180, 10*60, 20*60) + -- + -- In this case the squadron "Mineralnye" will a race track pattern at a random point in the CAP zone. The leg length will be randomly selected between 10,000 and 20,000 meters. The heading + -- of the race track will randomly selected between 90 (West to East) and 180 (North to South) degrees. + -- After a random duration between 10 and 20 minutes, the flight will get a new random orbit location. + -- + -- Note that all parameters except the squadron name are optional. If not specified, default values are taken. Speed and altitude are taken from the + -- + -- Also note that the center of the race track pattern is chosen randomly within the patrol zone and can be close the the boarder of the zone. Hence, it cannot be guaranteed that the + -- whole pattern lies within the patrol zone. -- -- ## 8. Setup a squadron for GCI: -- @@ -852,6 +871,13 @@ do -- AI_A2A_DISPATCHER -- @field #table Table of template group names of the squadron. -- @field #table Spawn Table of spaws Core.Spawn#SPAWN. -- @field #table TemplatePrefixes + -- @field #boolean Racetrack If true, CAP flights will perform a racetrack pattern rather than randomly patrolling the zone. + -- @field #number RacetrackLengthMin Min Length of race track in meters. Default 10,000 m. + -- @field #number RacetrackLengthMax Max Length of race track in meters. Default 15,000 m. + -- @field #number RacetrackHeadingMin Min heading of race track in degrees. Default 0 deg, i.e. from South to North. + -- @field #number RacetrackHeadingMax Max heading of race track in degrees. Default 180 deg, i.e. from North to South. + -- @field #number RacetrackDurationMin Min duration in seconds before the CAP flight changes its orbit position. Default never. + -- @field #number RacetrackDurationMax Max duration in seconds before the CAP flight changes its orbit position. Default never. --- Enumerator for spawns at airbases -- @type AI_A2A_DISPATCHER.Takeoff @@ -1676,7 +1702,8 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.Uncontrolled = true - for SpawnTemplate, DefenderSpawn in pairs( self.DefenderSpawns ) do + for SpawnTemplate,_DefenderSpawn in pairs( self.DefenderSpawns ) do + local DefenderSpawn=_DefenderSpawn --Core.Spawn#SPAWN DefenderSpawn:InitUnControlled() end @@ -1842,7 +1869,7 @@ do -- AI_A2A_DISPATCHER --- Check if squadron can do CAP. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. - -- @return #table DefenderSquadron + -- @return #AI_A2A_DISPATCHER.Squadron DefenderSquadron function AI_A2A_DISPATCHER:CanCAP( SquadronName ) self:F({SquadronName = SquadronName}) @@ -1872,6 +1899,54 @@ do -- AI_A2A_DISPATCHER end + --- Set race track pattern as default when any squadron is performing CAP. + -- @param #AI_A2A_DISPATCHER self + -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. + -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. + -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from North to South. + -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. + -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. + -- @return #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:SetDefaultCapRacetrack(LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax) + + self.DefenderDefault.Racetrack=true + self.DefenderDefault.RacetrackLengthMin=LeglengthMin + self.DefenderDefault.RacetrackLengthMax=LeglengthMax + self.DefenderDefault.RacetrackHeadingMin=HeadingMin + self.DefenderDefault.RacetrackHeadingMax=HeadingMax + self.DefenderDefault.RacetrackDurationMin=DurationMin + self.DefenderDefault.RacetrackDurationMax=DurationMax + + return self + end + + --- Set race track pattern when squadron is performing CAP. + -- @param #AI_A2A_DISPATCHER self + -- @param #string SquadronName Name of the squadron. + -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. + -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. + -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from North to South. + -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. + -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. + -- @return #AI_A2A_DISPATCHER self + function AI_A2A_DISPATCHER:SetSquadronCapRacetrack(SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax) + + local DefenderSquadron = self:GetSquadron( SquadronName ) + + if DefenderSquadron then + DefenderSquadron.Racetrack=true + DefenderSquadron.RacetrackLengthMin=LeglengthMin + DefenderSquadron.RacetrackLengthMax=LeglengthMax + DefenderSquadron.RacetrackHeadingMin=HeadingMin + DefenderSquadron.RacetrackHeadingMax=HeadingMax + DefenderSquadron.RacetrackDurationMin=DurationMin + DefenderSquadron.RacetrackDurationMax=DurationMax + end + + return self + end + + --- Check if squadron can do GCI. -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName The squadron name. @@ -2927,6 +3002,14 @@ do -- AI_A2A_DISPATCHER Fsm:SetDamageThreshold( self.DefenderDefault.DamageThreshold ) Fsm:SetDisengageRadius( self.DisengageRadius ) Fsm:SetTanker( DefenderSquadron.TankerName or self.DefenderDefault.TankerName ) + if DefenderSquadron.Racetrack or self.DefenderDefault.Racetrack then + Fsm:SetRaceTrackPattern(DefenderSquadron.RacetrackLengthMin or self.DefenderDefault.RacetrackLengthMin, + DefenderSquadron.RacetrackLengthMax or self.DefenderDefault.RacetrackLengthMax, + DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, + DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, + DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, + DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax) + end Fsm:Start() self:SetDefenderTask( SquadronName, DefenderCAP, "CAP", Fsm ) @@ -3328,8 +3411,10 @@ do -- AI_A2A_DISPATCHER DefenderGroupCount = DefenderGroupCount + 1 local Fuel = Defender:GetFuelMin() * 100 local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", - Defender:GetName(), + Report:Add( string.format( " - %s*%d/%d ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Defender:GetName(), + Defender:GetSize(), + Defender:GetInitialSize(), DefenderTask.Type, DefenderTask.Fsm:GetState(), Defender:GetSize(), @@ -3354,8 +3439,10 @@ do -- AI_A2A_DISPATCHER local Fuel = Defender:GetFuelMin() * 100 local Damage = Defender:GetLife() / Defender:GetLife0() * 100 DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Report:Add( string.format( " - %s*%d/%d ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", Defender:GetName(), + Defender:GetSize(), + Defender:GetInitialSize(), DefenderTask.Type, DefenderTask.Fsm:GetState(), Defender:GetSize(), diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 7e57c24d0..68baf0085 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -257,6 +257,31 @@ function AI_A2A_PATROL:SetAltitude( PatrolFloorAltitude, PatrolCeilingAltitude ) self.PatrolCeilingAltitude = PatrolCeilingAltitude end +--- Set race track parameters. CAP flights will perform race track patterns rather than randomly patrolling the zone. +-- @param #AI_A2A_PATROL self +-- @param #number LegMin Min Length of the race track leg in meters. Default 10,000 m. +-- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m. +-- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. +-- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North. +-- @param #number duration (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @param #number duration (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @return #AI_A2A_PATROL self +function AI_A2A_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax) + self:F2({leglength, duration}) + + self.racetrack=true + self.racetracklegmin=LegMin or 10000 + self.racetracklegmax=LegMax or 15000 + self.racetrackheadingmin=HeadingMin or 0 + self.racetrackheadingmax=HeadingMax or 180 + self.racetrackdurationmin=DurationMin + self.racetrackdurationmax=DurationMax + + if self.racetrackdurationmax and not self.racetrackdurationmin then + self.racetrackdurationmin=self.racetrackdurationmax + end +end + --- Defines a new patrol route using the @{Process_PatrolZone} parameters and settings. -- @param #AI_A2A_PATROL self @@ -312,7 +337,7 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) end - if AIPatrol:IsAlive() then + if AIPatrol and AIPatrol:IsAlive() then local PatrolRoute = {} @@ -320,31 +345,79 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) local CurrentCoord = AIPatrol:GetCoordinate() - local ToTargetCoord = self.PatrolZone:GetRandomPointVec2() - ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) ) - self:SetTargetDistance( ToTargetCoord ) -- For RTB status check + if self.racetrack then - 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 - ) + -- Random altitude. + local altitude=math.random(self.PatrolFloorAltitude, self.PatrolCeilingAltitude) + + -- Random speed in km/h. + local speedkmh = math.random(self.PatrolMinSpeed, self.PatrolMaxSpeed) + + -- Random heading. + local heading = math.random(self.racetrackheadingmin, self.racetrackheadingmax) + + -- Random leg length. + local leg=math.random(self.racetracklegmin, self.racetracklegmax) + + -- Random duration if any. + local duration = self.racetrackdurationmin + if self.racetrackdurationmax then + duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) + end + + -- Race track points. + local c1=self.PatrolZone:GetRandomCoordinate():SetAltitude(altitude) --Core.Point#COORDINATE + local c2=c1:Translate(leg, heading):SetAltitude(altitude) + + -- 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") + --c2:MarkToAll("Race track c2") - 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 ) - + -- Task to orbit. + local taskOrbit=AIPatrol:TaskOrbit(c1, altitude, UTILS.KmphToMps(speedkmh), c2) + + -- Task function to redo the patrol at other random position. + local taskPatrol=AIPatrol:TaskFunction("AI_A2A_PATROL.PatrolRoute", self) + + + 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") + + else + + local ToTargetCoord = self.PatrolZone:GetRandomPointVec2() + ToTargetCoord:SetAlt( math.random( self.PatrolFloorAltitude, self.PatrolCeilingAltitude ) ) + 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 ) + + end + + -- ROE AIPatrol:OptionROEReturnFire() AIPatrol:OptionROTEvadeFire() - + + -- Patrol. AIPatrol:Route( PatrolRoute, 0.5) end From 183c05bcf5dfe78fb65a0d751a1e96a6281954ec Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 6 Jul 2019 23:39:48 +0200 Subject: [PATCH 10/22] CAP AI_A2A_PATROL - Added CAP coordinates option. AI_A2A_DISPATCHER - Added CAP coordinates option. --- .../Moose/AI/AI_A2A_Dispatcher.lua | 25 ++++++++++++------- Moose Development/Moose/AI/AI_A2A_Patrol.lua | 20 +++++++++++---- 2 files changed, 31 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index ecc9005b6..cafbfd139 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -1902,12 +1902,14 @@ do -- AI_A2A_DISPATCHER --- Set race track pattern as default when any squadron is performing CAP. -- @param #AI_A2A_DISPATCHER self -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. - -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. - -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from North to South. + -- @param #number LeglengthMax Max length of the race track leg in meters. Default 15,000 m. + -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. counter clockwise from South to North. + -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. counter clockwise from North to South. -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. + -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetDefaultCapRacetrack(LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax) + function AI_A2A_DISPATCHER:SetDefaultCapRacetrack(LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) self.DefenderDefault.Racetrack=true self.DefenderDefault.RacetrackLengthMin=LeglengthMin @@ -1916,6 +1918,7 @@ do -- AI_A2A_DISPATCHER self.DefenderDefault.RacetrackHeadingMax=HeadingMax self.DefenderDefault.RacetrackDurationMin=DurationMin self.DefenderDefault.RacetrackDurationMax=DurationMax + self.DefenderDefault.RacetrackCoordinates=CapCoordinates return self end @@ -1924,12 +1927,14 @@ do -- AI_A2A_DISPATCHER -- @param #AI_A2A_DISPATCHER self -- @param #string SquadronName Name of the squadron. -- @param #number LeglengthMin Min length of the race track leg in meters. Default 10,000 m. + -- @param #number LeglengthMax Max length of the race track leg in meters. Default 15,000 m. -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from North to South. -- @param #number DurationMin (Optional) Min duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. -- @param #number DurationMax (Optional) Max duration in seconds before switching the orbit position. Default is keep same orbit until RTB or engage. + -- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_DISPATCHER self - function AI_A2A_DISPATCHER:SetSquadronCapRacetrack(SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax) + function AI_A2A_DISPATCHER:SetSquadronCapRacetrack(SquadronName, LeglengthMin, LeglengthMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) local DefenderSquadron = self:GetSquadron( SquadronName ) @@ -1941,6 +1946,7 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.RacetrackHeadingMax=HeadingMax DefenderSquadron.RacetrackDurationMin=DurationMin DefenderSquadron.RacetrackDurationMax=DurationMax + DefenderSquadron.RacetrackCoordinates=CapCoordinates end return self @@ -3008,7 +3014,8 @@ do -- AI_A2A_DISPATCHER DefenderSquadron.RacetrackHeadingMin or self.DefenderDefault.RacetrackHeadingMin, DefenderSquadron.RacetrackHeadingMax or self.DefenderDefault.RacetrackHeadingMax, DefenderSquadron.RacetrackDurationMin or self.DefenderDefault.RacetrackDurationMin, - DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax) + DefenderSquadron.RacetrackDurationMax or self.DefenderDefault.RacetrackDurationMax, + DefenderSquadron.RacetrackCoordinates or self.DefenderDefault.RacetrackCoordinates) end Fsm:Start() @@ -3366,7 +3373,7 @@ do -- AI_A2A_DISPATCHER end end - local Report = REPORT:New( "Tactical Overview" ) + local Report = REPORT:New( "Tactical Overviews" ) local DefenderGroupCount = 0 @@ -3403,7 +3410,7 @@ do -- AI_A2A_DISPATCHER if self.TacticalDisplay then -- Show tactical situation - Report:Add( string.format( "\n- Target %s ( %s ): ( #%d ) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) + Report:Add( string.format( "\n- Target %s (%s): (#%d) %s" , DetectedItem.ItemID, DetectedItem.Index, DetectedItem.Set:Count(), DetectedItem.Set:GetObjectNames() ) ) for Defender, DefenderTask in pairs( self:GetDefenderTasks() ) do local Defender = Defender -- Wrapper.Group#GROUP if DefenderTask.Target and DefenderTask.Target.Index == DetectedItem.Index then @@ -3411,7 +3418,7 @@ do -- AI_A2A_DISPATCHER DefenderGroupCount = DefenderGroupCount + 1 local Fuel = Defender:GetFuelMin() * 100 local Damage = Defender:GetLife() / Defender:GetLife0() * 100 - Report:Add( string.format( " - %s*%d/%d ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", Defender:GetName(), Defender:GetSize(), Defender:GetInitialSize(), @@ -3439,7 +3446,7 @@ do -- AI_A2A_DISPATCHER local Fuel = Defender:GetFuelMin() * 100 local Damage = Defender:GetLife() / Defender:GetLife0() * 100 DefenderGroupCount = DefenderGroupCount + 1 - Report:Add( string.format( " - %s*%d/%d ( %s - %s ): ( #%d ) F: %3d, D:%3d - %s", + Report:Add( string.format( " - %s*%d/%d (%s - %s): (#%d) F: %3d, D:%3d - %s", Defender:GetName(), Defender:GetSize(), Defender:GetInitialSize(), diff --git a/Moose Development/Moose/AI/AI_A2A_Patrol.lua b/Moose Development/Moose/AI/AI_A2A_Patrol.lua index 68baf0085..5071d1729 100644 --- a/Moose Development/Moose/AI/AI_A2A_Patrol.lua +++ b/Moose Development/Moose/AI/AI_A2A_Patrol.lua @@ -263,10 +263,11 @@ end -- @param #number LegMax Max length of the race track leg in meters. Default 15,000 m. -- @param #number HeadingMin Min heading of the race track in degrees. Default 0 deg, i.e. from South to North. -- @param #number HeadingMax Max heading of the race track in degrees. Default 180 deg, i.e. from South to North. --- @param #number duration (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. --- @param #number duration (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @param #number DurationMin (Optional) Min duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @param #number DurationMax (Optional) Max duration before switching the orbit position. Default is keep same orbit until RTB or engage. +-- @param #table CapCoordinates Table of coordinates of first race track point. Second point is determined by leg length and heading. -- @return #AI_A2A_PATROL self -function AI_A2A_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax) +function AI_A2A_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMax, DurationMin, DurationMax, CapCoordinates) self:F2({leglength, duration}) self.racetrack=true @@ -279,7 +280,10 @@ function AI_A2A_PATROL:SetRaceTrackPattern(LegMin, LegMax, HeadingMin, HeadingMa if self.racetrackdurationmax and not self.racetrackdurationmin then self.racetrackdurationmin=self.racetrackdurationmax - end + end + + self.racetrackcapcoordinates=CapCoordinates + end @@ -365,8 +369,14 @@ function AI_A2A_PATROL:onafterRoute( AIPatrol, From, Event, To ) duration=math.random(self.racetrackdurationmin, self.racetrackdurationmax) end + -- CAP coordinate. + local c0=self.PatrolZone:GetRandomCoordinate() + if self.racetrackcapcoordinates and #self.racetrackcapcoordinates>0 then + c0=self.racetrackcapcoordinates[math.random(#self.racetrackcapcoordinates)] + end + -- Race track points. - local c1=self.PatrolZone:GetRandomCoordinate():SetAltitude(altitude) --Core.Point#COORDINATE + local c1=c0:SetAltitude(altitude) --Core.Point#COORDINATE local c2=c1:Translate(leg, heading):SetAltitude(altitude) -- Debug: From c46028ff2df67093b6f66391e7cdd519229f6c2b Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 10 Jul 2019 22:29:49 +0200 Subject: [PATCH 11/22] 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 12/22] 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 13/22] 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 14/22] 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 15/22] 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 From d4b9fc9e40bc7a9d8ae4fb6ae7f45e782cddd63e Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Jul 2019 23:35:09 +0200 Subject: [PATCH 16/22] Updates --- Moose Development/Moose/Core/Spawn.lua | 98 ++++++++++-- Moose Development/Moose/Functional/Sead.lua | 45 +++++- Moose Development/Moose/Ops/Airboss.lua | 86 +++++++---- .../Moose/Ops/RecoveryTanker.lua | 146 ++++++++++++++++-- Moose Development/Moose/Ops/RescueHelo.lua | 87 ++++------- .../Moose/Utilities/Routines.lua | 5 + Moose Development/Moose/Wrapper/Airbase.lua | 31 +++- Moose Development/Moose/Wrapper/Unit.lua | 34 +++- 8 files changed, 407 insertions(+), 125 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index f9cd1b97b..867a4c3f8 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -319,7 +319,7 @@ function SPAWN:New( SpawnTemplatePrefix ) self.SpawnUnControlled = false self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. self.DelayOnOff = false -- No intial delay when spawning the first group. - self.Grouping = nil -- No grouping. + self.SpawnGrouping = nil -- No grouping. self.SpawnInitLivery = nil -- No special livery. self.SpawnInitSkill = nil -- No special skill. self.SpawnInitFreq = nil -- No special frequency. @@ -371,7 +371,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix ) self.SpawnUnControlled = false self.SpawnInitKeepUnitNames = false -- Overwrite unit names by default with group name. self.DelayOnOff = false -- No intial delay when spawning the first group. - self.Grouping = nil -- No grouping. + self.SpawnGrouping = nil -- No grouping. self.SpawnInitLivery = nil -- No special livery. self.SpawnInitSkill = nil -- No special skill. self.SpawnInitFreq = nil -- No special frequency. @@ -549,7 +549,7 @@ end --- Sets the country of the spawn group. Note that the country determins the coalition of the group depending on which country is defined to be on which side for each specific mission! -- @param #SPAWN self --- @param #DCS.country Country Country id as number or enumerator: +-- @param #number Country Country id as number or enumerator: -- -- * @{DCS#country.id.RUSSIA} -- * @{DCS#county.id.USA} @@ -1438,7 +1438,7 @@ end -- @param Wrapper.Airbase#AIRBASE.TerminalType TerminalType (optional) The terminal type the aircraft should be spawned at. See @{Wrapper.Airbase#AIRBASE.TerminalType}. -- @param #boolean EmergencyAirSpawn (optional) If true (default), groups are spawned in air if there is no parking spot at the airbase. If false, nothing is spawned if no parking spot is available. -- @param #table Parkingdata (optional) Table holding the coordinates and terminal ids for all units of the group. Spawning will be forced to happen at exactily these spots! --- @return Wrapper.Group#GROUP that was spawned or nil when nothing was spawned. +-- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. -- @usage -- Spawn_Plane = SPAWN:New( "Plane" ) -- Spawn_Plane:SpawnAtAirbase( AIRBASE:FindByName( AIRBASE.Caucasus.Krymsk ), SPAWN.Takeoff.Cold ) @@ -1498,11 +1498,7 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT local TemplateGroup = GROUP:FindByName(self.SpawnTemplatePrefix) local TemplateUnit=TemplateGroup:GetUnit(1) - --local ishelo=TemplateUnit:HasAttribute("Helicopters") - --local isbomber=TemplateUnit:HasAttribute("Bombers") - --local istransport=TemplateUnit:HasAttribute("Transports") - --local isfighter=TemplateUnit:HasAttribute("Battleplanes") - + -- General category of spawned group. local group=TemplateGroup local istransport=group:HasAttribute("Transports") and group:HasAttribute("Planes") local isawacs=group:HasAttribute("AWACS") @@ -1577,8 +1573,17 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Set terminal type. local termtype=TerminalType - if spawnonrunway then - termtype=AIRBASE.TerminalType.Runway + if spawnonrunway then + if spawnonship then + -- Looks like there are no runway spawn spots on the stennis! + if ishelo then + termtype=AIRBASE.TerminalType.HelicopterUsable + else + termtype=AIRBASE.TerminalType.OpenMedOrBig + end + else + termtype=AIRBASE.TerminalType.Runway + end end -- Scan options. Might make that input somehow. @@ -1647,9 +1652,9 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT -- Get parking data. local parkingdata=SpawnAirbase:GetParkingSpotsTable(termtype) - self:T2(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) + self:T(string.format("Parking at %s, terminal type %s:", SpawnAirbase:GetName(), tostring(termtype))) for _,_spot in pairs(parkingdata) do - self:T2(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", + self:T(string.format("%s, Termin Index = %3d, Term Type = %03d, Free = %5s, TOAC = %5s, Term ID0 = %3d, Dist2Rwy = %4d", SpawnAirbase:GetName(), _spot.TerminalID, _spot.TerminalType,tostring(_spot.Free),tostring(_spot.TOAC),_spot.TerminalID0,_spot.DistToRwy)) end self:T(string.format("%s at %s: free parking spots = %d - number of units = %d", self.SpawnTemplatePrefix, SpawnAirbase:GetName(), nfree, nunits)) @@ -1802,8 +1807,8 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT end -- Debug output. - self:T2(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) - self:T2(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) + self:T(string.format("Group %s unit number %d: Parking = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking))) + self:T(string.format("Group %s unit number %d: Parking ID = %s",self.SpawnTemplatePrefix, UnitID, tostring(UnitTemplate.parking_id))) self:T2('After Translation SpawnTemplate.units['..UnitID..'].x = '..SpawnTemplate.units[UnitID].x..', SpawnTemplate.units['..UnitID..'].y = '..SpawnTemplate.units[UnitID].y) end end @@ -1840,6 +1845,66 @@ function SPAWN:SpawnAtAirbase( SpawnAirbase, Takeoff, TakeoffAltitude, TerminalT return nil end +--- Spawn a group on an @{Wrapper.Airbase} at a specific parking spot. +-- @param #SPAWN self +-- @param Wrapper.Airbase#AIRBASE Airbase The @{Wrapper.Airbase} where to spawn the group. +-- @param #table Spots Table of parking spot IDs. Note that these in general are different from the numbering in the mission editor! +-- @param #SPAWN.Takeoff Takeoff (Optional) Takeoff type, i.e. either SPAWN.Takeoff.Cold or SPAWN.Takeoff.Hot. Default is Hot. +-- @return Wrapper.Group#GROUP The group that was spawned or nil when nothing was spawned. +function SPAWN:SpawnAtParkingSpot(Airbase, Spots, Takeoff) -- R2.5 + self:F({Airbase=Airbase, Spots=Spots, Takeoff=Takeoff}) + + -- Ensure that Spots parameter is a table. + if type(Spots)~="table" then + Spots={Spots} + end + + -- Get template group. + local group=GROUP:FindByName(self.SpawnTemplatePrefix) + + -- Get number of units in group. + local nunits=self.SpawnGrouping or #group:GetUnits() + + -- Quick check. + if nunits then + + -- Check that number of provided parking spots is large enough. + if #Spots=nunits then + return self:SpawnAtAirbase(Airbase, Takeoff, nil, nil, nil, Parkingdata) + else + self:E("ERROR: Could not find enough free parking spots!") + end + + + else + self:E("ERROR: Could not get number of units in group!") + end + + return nil +end --- Will park a group at an @{Wrapper.Airbase}. -- @@ -3023,6 +3088,9 @@ function SPAWN:_TranslateRotate( SpawnIndex, SpawnRootX, SpawnRootY, SpawnX, Spa end --- Get the next index of the groups to be spawned. This method is complicated, as it is used at several spaces. +-- @param #SPAWN self +-- @param #number SpawnIndex Spawn index. +-- @return #number self.SpawnIndex function SPAWN:_GetSpawnIndex( SpawnIndex ) self:F2( { self.SpawnTemplatePrefix, SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive, self.AliveUnits, #self.SpawnTemplate.units } ) diff --git a/Moose Development/Moose/Functional/Sead.lua b/Moose Development/Moose/Functional/Sead.lua index 55a791023..6edb99312 100644 --- a/Moose Development/Moose/Functional/Sead.lua +++ b/Moose Development/Moose/Functional/Sead.lua @@ -57,8 +57,10 @@ SEAD = { -- -- Defends the Russian SA installations from SEAD attacks. -- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } ) function SEAD:New( SEADGroupPrefixes ) + local self = BASE:Inherit( self, BASE:New() ) - self:F( SEADGroupPrefixes ) + self:F( SEADGroupPrefixes ) + if type( SEADGroupPrefixes ) == 'table' then for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do self.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix @@ -85,7 +87,29 @@ function SEAD:OnEventShot( EventData ) local SEADWeaponName = EventData.WeaponName -- return weapon type -- Start of the 2nd loop self:T( "Missile Launched = " .. SEADWeaponName ) - if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD + + --if SEADWeaponName == "KH-58" or SEADWeaponName == "KH-25MPU" or SEADWeaponName == "AGM-88" or SEADWeaponName == "KH-31A" or SEADWeaponName == "KH-31P" then -- Check if the missile is a SEAD + if SEADWeaponName == "weapons.missiles.X_58" --Kh-58U anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.Kh25MP_PRGS1VP" --Kh-25MP anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.X_25MP" --Kh-25MPU anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.X_28" --Kh-28 anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.X_31P" --Kh-31P anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_45A" --AGM-45A anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_45" --AGM-45B anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_88" --AGM-88C anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.AGM_122" --AGM-122 Sidearm anti-radiation missiles fired + or + SEADWeaponName == "weapons.missiles.ALARM" --ALARM anti-radiation missiles fired + then + local _evade = math.random (1,100) -- random number for chance of evading action local _targetMim = EventData.Weapon:getTarget() -- Identify target local _targetMimname = Unit.getName(_targetMim) @@ -111,47 +135,62 @@ function SEAD:OnEventShot( EventData ) self:T( _targetskill ) if self.TargetSkill[_targetskill] then if (_evade > self.TargetSkill[_targetskill].Evade) then + self:T( string.format("Evading, target skill " ..string.format(_targetskill)) ) + local _targetMim = Weapon.getTarget(SEADWeapon) local _targetMimname = Unit.getName(_targetMim) local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimcont= _targetMimgroup:getController() + routines.groupRandomDistSelf(_targetMimgroup,300,'Diamond',250,20) -- move randomly + local SuppressedGroups1 = {} -- unit suppressed radar off for a random time + local function SuppressionEnd1(id) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) SuppressedGroups1[id.groupName] = nil end + local id = { groupName = _targetMimgroup, ctrl = _targetMimcont } + local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2]) + if SuppressedGroups1[id.groupName] == nil then + SuppressedGroups1[id.groupName] = { SuppressionEndTime1 = timer.getTime() + delay1, SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function - } + } + Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN) timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function --trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20) end local SuppressedGroups = {} + local function SuppressionEnd(id) id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED) SuppressedGroups[id.groupName] = nil end + local id = { groupName = _targetMimgroup, ctrl = _targetMimcont } + local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2]) + if SuppressedGroups[id.groupName] == nil then SuppressedGroups[id.groupName] = { SuppressionEndTime = timer.getTime() + delay, SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function } + timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function --trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20) end diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 82a088c08..da29434fe 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -205,7 +205,7 @@ -- @field Core.Set#SET_GROUP excludesetAI AI groups in this set will be explicitly excluded from handling by the airboss and not forced into the Marshal pattern. -- @field #boolean menusingle If true, menu is optimized for a single carrier. -- @field #number collisiondist Distance up to which collision checks are done. --- @field #nubmer holdtimestamp Timestamp when the carrier first came to an unexpected hold. +-- @field #number holdtimestamp Timestamp when the carrier first came to an unexpected hold. -- @field #number Tmessage Default duration in seconds messages are displayed to players. -- @field #string soundfolder Folder within the mission (miz) file where airboss sound files are located. -- @field #string soundfolderLSO Folder withing the mission (miz) file where LSO sound files are stored. @@ -1559,7 +1559,7 @@ AIRBOSS.Difficulty={ -- @field #number Time Time in seconds. -- @field #number Rho Distance in meters. -- @field #number X Distance in meters. --- @field #nubmer Z Distance in meters. +-- @field #number Z Distance in meters. -- @field #number AoA Angle of Attack. -- @field #number Alt Altitude in meters. -- @field #number GSE Glideslope error in degrees. @@ -1681,7 +1681,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.0.4" +AIRBOSS.version="1.0.5" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -2128,6 +2128,13 @@ function AIRBOSS:New(carriername, alias) -- @param #AIRBOSS self -- @param #number delay Delay in seconds. + --- On after "RecoveryStop" user function. Called when recovery of aircraft is stopped. + -- @function [parent=#AIRBOSS] OnAfterRecoveryStop + -- @param #AIRBOSS self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + --- Triggers the FSM event "RecoveryPause" that pauses the recovery of aircraft. -- @function [parent=#AIRBOSS] RecoveryPause @@ -3063,6 +3070,15 @@ function AIRBOSS:SetRecoveryTanker(recoverytanker) return self end +--- Define an AWACS associated with the carrier. +-- @param #AIRBOSS self +-- @param Ops.RecoveryTanker#RECOVERYTANKER awacs AWACS (recovery tanker) object. +-- @return #AIRBOSS self +function AIRBOSS:SetAWACS(awacs) + self.awacs=awacs + return self +end + --- Set default player skill. New players will be initialized with this skill. -- -- * "Flight Student" = @{#AIRBOSS.Difficulty.Easy} @@ -5914,38 +5930,48 @@ function AIRBOSS:_ScanCarrierZone() -- Debug info. self:T3(self.lid..string.format("Known AI flight group %s closed in by %.1f NM", knownflight.groupname, UTILS.MetersToNM(closein))) - -- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value. - if closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad then + -- Is this group the tanker? + local istanker=self.tanker and self.tanker.tanker:GetName()==groupname - -- Check that we do not add a recovery tanker for marshaling. - if self.tanker and self.tanker.tanker:GetName()==groupname then + -- Is this group the AWACS? + local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname + + -- Send tanker to marshal stack? + local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 + + -- Send AWACS to marhsal stack? + local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 + + -- Put flight into Marshal. + local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and istanker==false and isawacs==false + + -- Send AI flight to marshal stack if group closes in more than 5 and has initial flag value. + if putintomarshal or tanker2marshal or awacs2marshal then + + -- Get the next free stack for current recovery case. + local stack=self:_GetFreeStack(knownflight.ai) + + -- Repawn. + local respawn=self.respawnAI --or tanker2marshal - -- Don't touch the recovery tanker! + if stack then + + -- Send AI to marshal stack. We respawn the group to clean possible departure and destination airbases. + self:_MarshalAI(knownflight, stack, respawn) else - -- Get the next free stack for current recovery case. - local stack=self:_GetFreeStack(knownflight.ai) - - if stack then - - -- Send AI to marshal stack. We respawn the group to clean possible departure and destination airbases. - self:_MarshalAI(knownflight, stack, self.respawnAI) - - else - - -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. - if not self:_InQueue(self.Qwaiting, knownflight.group) then - self:_WaitAI(knownflight, self.respawnAI) -- Group is respawned to clear any attached airfields. - end - + -- Send AI to orbit outside 10 NM zone and wait until the next Marshal stack is available. + if not self:_InQueue(self.Qwaiting, knownflight.group) then + self:_WaitAI(knownflight, respawn) -- Group is respawned to clear any attached airfields. end - - -- Break the loop to not have all flights at once! Spams the message screen. - break - - end -- Tanker - end -- Closed in + + end + + -- Break the loop to not have all flights at once! Spams the message screen. + break + + end -- Closed in or tanker/AWACS end -- AI else @@ -14329,7 +14355,7 @@ end -- @param #number delay Delay in seconds, before the message is broadcasted. -- @param #number interval Interval in seconds after the last sound has been played. -- @param #boolean click If true, play radio click at the end. --- @param #booelan pilotcall If true, it's a pilot call. +-- @param #boolean pilotcall If true, it's a pilot call. function AIRBOSS:RadioTransmission(radio, call, loud, delay, interval, click, pilotcall) self:F2({radio=radio, call=call, loud=loud, delay=delay, interval=interval, click=click}) diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index 3c4d781dc..a4ef844df 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -60,6 +60,8 @@ -- @field #number callsignname Number for the callsign name. -- @field #number callsignnumber Number of the callsign name. -- @field #string modex Tail number of the tanker. +-- @field #boolean eplrs If true, enable data link, e.g. if used as AWACS. +-- @field #boolean recovery If true, tanker will recover using the AIRBOSS marshal pattern. -- @extends Core.Fsm#FSM --- Recovery Tanker. @@ -295,15 +297,16 @@ RECOVERYTANKER = { callsignnumber = nil, modex = nil, eplrs = nil, + recovery = nil, } --- Unique ID (global). -- @field #number UID Unique ID (global). -RECOVERYTANKER.UID=0 +_RECOVERYTANKERID=0 --- Class version. -- @field #string version -RECOVERYTANKER.version="1.0.7" +RECOVERYTANKER.version="1.0.8" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -349,16 +352,16 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) self.tankergroupname=tankergroupname -- Increase unique ID. - RECOVERYTANKER.UID=RECOVERYTANKER.UID+1 + _RECOVERYTANKERID=_RECOVERYTANKERID+1 -- Unique ID of this tanker. - self.uid=RECOVERYTANKER.UID + self.uid=_RECOVERYTANKERID -- Save self in static object. Easier to retrieve later. self.carrier:SetState(self.carrier, string.format("RECOVERYTANKER_%d", self.uid) , self) -- Set unique spawn alias. - self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.tankergroupname, RECOVERYTANKER.UID) + self.alias=string.format("%s_%s_%02d", self.carrier:GetName(), self.tankergroupname, _RECOVERYTANKERID) -- Log ID. self.lid=string.format("RECOVERYTANKER %s | ", self.alias) @@ -377,6 +380,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) self:SetPatternUpdateHeading() self:SetPatternUpdateInterval() self:SetAWACS(false) + self:SetRecoveryAirboss(false) -- Debug trace. if false then @@ -399,6 +403,7 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) self:AddTransition("*", "RefuelStop", "Running") -- Tanker starts to refuel. self:AddTransition("*", "Run", "Running") -- Tanker starts normal operation again. self:AddTransition("Running", "RTB", "Returning") -- Tanker is returning to base (for fuel). + self:AddTransition("Returning", "Returned", "Returned") -- Tanker has returned to its airbase (i.e. landed). self:AddTransition("*", "Status", "*") -- Status update. self:AddTransition("Running", "PatternUpdate", "*") -- Update pattern wrt to carrier. self:AddTransition("*", "Stop", "Stopped") -- Stop the FSM. @@ -472,6 +477,26 @@ function RECOVERYTANKER:New(carrierunit, tankergroupname) -- @param Wrapper.Airbase#AIRBASE airbase The airbase where the tanker should return to. + --- Triggers the FSM event "Returned" after the tanker has landed. + -- @function [parent=#RECOVERYTANKER] Returned + -- @param #RECOVERYTANKER self + -- @param Wrapper.Airbase#AIRBASE airbase The airbase the tanker has landed. + + --- Triggers the delayed FSM event "Returned" after the tanker has landed. + -- @function [parent=#RECOVERYTANKER] __Returned + -- @param #RECOVERYTANKER self + -- @param #number delay Delay in seconds. + -- @param Wrapper.Airbase#AIRBASE airbase The airbase the tanker has landed. + + --- On after "Returned" event user function. Called when a the the tanker has landed at an airbase. + -- @function [parent=#RECOVERYTANKER] OnAfterReturned + -- @param #RECOVERYTANKER self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param Wrapper.Airbase#AIRBASE airbase The airbase the tanker has landed. + + --- Triggers the FSM event "Status" that updates the tanker status. -- @function [parent=#RECOVERYTANKER] Status -- @param #RECOVERYTANKER self @@ -596,6 +621,19 @@ function RECOVERYTANKER:SetHomeBase(airbase) return self end +--- Activate recovery by the AIRBOSS class. Tanker will get a Marshal stack and perform a CASE I, II or III recovery when RTB. +-- @param #RECOVERYTANKER self +-- @param #boolean switch If true or nil, recovery is done by AIRBOSS. +-- @return #RECOVERYTANKER self +function RECOVERYTANKER:SetRecoveryAirboss(switch) + if switch==true or switch==nil then + self.recovery=true + else + self.recovery=false + end + return self +end + --- Set that the group takes the roll of an AWACS instead of a refueling tanker. -- @param #RECOVERYTANKER self -- @param #boolean switch If true or nil, set roll AWACS. @@ -830,6 +868,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To) -- Handle events. self:HandleEvent(EVENTS.EngineShutdown) + self:HandleEvent(EVENTS.Land) self:HandleEvent(EVENTS.Refueling, self._RefuelingStart) --Need explicit functions since OnEventRefueling and OnEventRefuelingStop did not hook! self:HandleEvent(EVENTS.RefuelingStop, self._RefuelingStop) self:HandleEvent(EVENTS.Crash, self._OnEventCrashOrDead) @@ -865,7 +904,7 @@ function RECOVERYTANKER:onafterStart(From, Event, To) else -- Check if an uncontrolled tanker group was requested. - if self.useuncontrolled then + if self.uncontrolledac then -- Use an uncontrolled aircraft group. self.tanker=GROUP:FindByName(self.tankergroupname) @@ -884,14 +923,14 @@ function RECOVERYTANKER:onafterStart(From, Event, To) else -- Spawn tanker at airbase. - self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff) + self.tanker=Spawn:SpawnAtAirbase(self.airbase, self.takeoff, nil, AIRBASE.TerminalType.OpenMedOrBig) end end -- Initialize route. self.distStern<0! - SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 1) + self:ScheduleOnce(1, self._InitRoute, self, -self.distStern+UTILS.NMToMeters(3)) -- Create tanker beacon. if self.TACANon then @@ -929,7 +968,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) -- Get current time. local time=timer.getTime() - if self.tanker:IsAlive() then + if self.tanker and self.tanker:IsAlive() then --------------------- -- TANKER is ALIVE -- @@ -937,8 +976,14 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) -- Get fuel of tanker. local fuel=self.tanker:GetFuel()*100 - local text=string.format("Recovery tanker %s: state=%s fuel=%.1f", self.tanker:GetName(), self:GetState(), fuel) + local life=self.tanker:GetUnit(1):GetLife() + local life0=self.tanker:GetUnit(1):GetLife0() + local lifeR=self.tanker:GetUnit(1):GetLifeRelative() + + -- Report fuel and life. + local text=string.format("Recovery tanker %s: state=%s fuel=%.1f, life=%.1f/%.1f=%d", self.tanker:GetName(), self:GetState(), fuel, life, life0, lifeR*100) self:T(self.lid..text) + MESSAGE:New(text, 10):ToAllIf(self.Debug) -- Check if tanker is running and not RTBing or refueling. if self:IsRunning() then @@ -947,7 +992,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) if fuel0 then if (allowTOAC and allowTOAC==true) or _spot.TO_AC==false then local _coord=COORDINATE:NewFromVec3(_spot.vTerminalPos) table.insert(freespots, {Coordinate=_coord, TerminalID=_spot.Term_Index, TerminalType=_spot.Term_Type, TOAC=_spot.TO_AC, Free=true, TerminalID0=_spot.Term_Index_0, DistToRwy=_spot.fDistToRW}) @@ -589,6 +589,31 @@ function AIRBASE:GetFreeParkingSpotsTable(termtype, allowTOAC) return freespots end +--- Get a table containing the coordinates, terminal index and terminal type of free parking spots at an airbase. +-- @param #AIRBASE self +-- @param #number TerminalID The terminal ID of the parking spot. +-- @return #AIRBASE.ParkingSpot Table free parking spots. Table has the elements ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". +function AIRBASE:GetParkingSpotData(TerminalID) + self:F({TerminalID=TerminalID}) + + -- Get parking data. + local parkingdata=self:GetParkingSpotsTable() + + -- Debug output. + self:T2({parkingdata=parkingdata}) + + for _,_spot in pairs(parkingdata) do + local spot=_spot --#AIRBASE.ParkingSpot + self:E({TerminalID=spot.TerminalID,TerminalType=spot.TerminalType}) + if TerminalID==spot.TerminalID then + return spot + end + end + + self:E("ERROR: Could not find spot with Terminal ID="..tostring(TerminalID)) + return nil +end + --- Place markers of parking spots on the F10 map. -- @param #AIRBASE self -- @param #AIRBASE.TerminalType termtype Terminal type for which marks should be placed. @@ -893,7 +918,7 @@ function AIRBASE:CheckOnRunWay(group, radius, despawn) end --- Get category of airbase. --- @param #WAREHOUSE self +-- @param #AIRBASE self -- @return #number Category of airbase from GetDesc().category. function AIRBASE:GetAirbaseCategory() return self:GetDesc().category diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 1ef55065d..6e28f3323 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -612,8 +612,7 @@ end --- Returns the unit's health. Dead units has health <= 1.0. -- @param #UNIT self --- @return #number The Unit's health value. --- @return #nil The DCS Unit is not existing or alive. +-- @return #number The Unit's health value or -1 if unit does not exist any more. function UNIT:GetLife() self:F2( self.UnitName ) @@ -629,8 +628,7 @@ end --- Returns the Unit's initial health. -- @param #UNIT self --- @return #number The Unit's initial health value. --- @return #nil The DCS Unit is not existing or alive. +-- @return #number The Unit's initial health value or 0 if unit does not exist any more. function UNIT:GetLife0() self:F2( self.UnitName ) @@ -644,6 +642,34 @@ function UNIT:GetLife0() return 0 end +--- Returns the unit's relative health. +-- @param #UNIT self +-- @return #number The Unit's relative health value, i.e. a number in [0,1] or -1 if unit does not exist any more. +function UNIT:GetLifeRelative() + self:F2(self.UnitName) + + if self and self:IsAlive() then + local life0=self:GetLife0() + local lifeN=self:GetLife() + return lifeN/life0 + end + + return -1 +end + +--- Returns the unit's relative damage, i.e. 1-life. +-- @param #UNIT self +-- @return #number The Unit's relative health value, i.e. a number in [0,1] or 1 if unit does not exist any more. +function UNIT:GetDamageRelative() + self:F2(self.UnitName) + + if self and self:IsAlive() then + return 1-self:GetLifeRelative() + end + + return 1 +end + --- Returns the category name of the #UNIT. -- @param #UNIT self -- @return #string Category name = Helicopter, Airplane, Ground Unit, Ship From e17e6357103bd512e183c384f83fe47059f71708 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Jul 2019 23:59:05 +0200 Subject: [PATCH 17/22] Updates --- Moose Development/Moose/Ops/RecoveryTanker.lua | 3 +-- Moose Development/Moose/Ops/RescueHelo.lua | 2 +- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index a4ef844df..2007d3b60 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -1312,8 +1312,7 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) group:InitRadioModulation(self.RadioModu) group:InitModex(self.modex) - -- Respawn tanker. - -- Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 + -- Respawn tanker. Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1) -- Create tanker beacon and activate TACAN. diff --git a/Moose Development/Moose/Ops/RescueHelo.lua b/Moose Development/Moose/Ops/RescueHelo.lua index 41751fb58..d033713b5 100644 --- a/Moose Development/Moose/Ops/RescueHelo.lua +++ b/Moose Development/Moose/Ops/RescueHelo.lua @@ -1178,7 +1178,7 @@ function RESCUEHELO:onafterReturned(From, Event, To, airbase) if airbase then local airbasename=airbase:GetName() - self:T(self.lid..string.format("Helo returned to airbase %s", tostring(airbasename))) + self:I(self.lid..string.format("Helo returned to airbase %s", tostring(airbasename))) else self:E(self.lid..string.format("WARNING: Helo landed but airbase (EventData.Place) is nil!")) end From d264c6f6a5f78a72696e3744a9a635fa9668ab49 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 21 Jul 2019 23:59:35 +0200 Subject: [PATCH 18/22] Update RecoveryTanker.lua --- Moose Development/Moose/Ops/RecoveryTanker.lua | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index 2007d3b60..10e132e73 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -1361,8 +1361,7 @@ function RECOVERYTANKER:_RefuelingStart(EventData) self:T(self.lid..text) -- FMS state "Refueling". - self:RefuelStart(receiver) - + self:RefuelStart(receiver) end end From 96fe9d51d6c60af28214dc772ae9529d1e3d833e Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 22 Jul 2019 16:55:17 +0200 Subject: [PATCH 19/22] Typo bug fix --- Moose Development/Moose/Ops/RecoveryTanker.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index 10e132e73..bd2adab50 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -1267,7 +1267,7 @@ function RECOVERYTANKER:OnEventLand(EventData) local airbase=nil --Wrapper.Airbase#AIRBASE local airbasename="unknown" if EventData.Place then - airbase=EventData.Plase + airbase=EventData.Place airbasename=airbase:GetName() end From f931af2ee99296bf2d8277fbc5c8454829a05f09 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 22 Jul 2019 21:06:12 +0200 Subject: [PATCH 20/22] Updates --- Moose Development/Moose/Core/Radio.lua | 2 +- Moose Development/Moose/Ops/Airboss.lua | 37 +++++++++++-------- .../Moose/Ops/RecoveryTanker.lua | 29 +++++++++++++-- Moose Development/Moose/Wrapper/Group.lua | 3 +- 4 files changed, 50 insertions(+), 21 deletions(-) diff --git a/Moose Development/Moose/Core/Radio.lua b/Moose Development/Moose/Core/Radio.lua index 412e18430..b6c13185c 100644 --- a/Moose Development/Moose/Core/Radio.lua +++ b/Moose Development/Moose/Core/Radio.lua @@ -517,7 +517,7 @@ end -- local myUnit = UNIT:FindByName("MyUnit") -- local myBeacon = myUnit:GetBeacon() -- Creates the beacon -- --- myBeacon:TACAN(20, "Y", "TEXACO", true) -- Activate the beacon +-- myBeacon:ActivateTACAN(20, "Y", "TEXACO", true) -- Activate the beacon function BEACON:ActivateTACAN(Channel, Mode, Message, Bearing, Duration) self:T({channel=Channel, mode=Mode, callsign=Message, bearing=Bearing, duration=Duration}) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index da29434fe..966afb94d 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -8395,28 +8395,35 @@ function AIRBOSS:OnEventEngineShutdown(EventData) -- Debug message. self:T(self.lid..string.format("AI unit %s shut down its engines!", _unitName)) - if self.despawnshutdown then + -- Get flight. + local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) + + -- Only AI flights. + if flight and flight.ai then - -- Get flight. - local flight=self:_GetFlightFromGroupInQueue(EventData.IniGroup, self.flights) - - -- Only AI flights. - if flight and flight.ai then + -- Check if all elements were recovered. + local recovered=self:_CheckSectionRecovered(flight) - -- Check if all elements were recovered. - local recovered=self:_CheckSectionRecovered(flight) + -- Despawn group and completely remove flight. + if recovered then + self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.", tostring(EventData.IniGroupName))) - -- Despawn group and completely remove flight. - if recovered then - self:T(self.lid..string.format("AI group %s completely recovered. Despawning group after engine shutdown event as requested in 5 seconds.", tostring(EventData.IniGroupName))) + -- Remove flight. + self:_RemoveFlight(flight) + + -- Check if this is a tanker or AWACS associated with the carrier. + local istanker=self.tanker and self.tanker.tanker:GetName()==EventData.IniGroupName + local isawacs=self.awacs and self.awacs.tanker:GetName()==EventData.IniGroupName + + -- Destroy group if desired. Recovery tankers have their own logic for despawning. + if self.despawnshutdown and not (istanker or isawacs) then EventData.IniGroup:Destroy(nil, 5) - self:_RemoveFlight(flight) end + end + end - - end - + end end --- Airboss event handler for event that a unit takes off. diff --git a/Moose Development/Moose/Ops/RecoveryTanker.lua b/Moose Development/Moose/Ops/RecoveryTanker.lua index bd2adab50..e77f91ad2 100644 --- a/Moose Development/Moose/Ops/RecoveryTanker.lua +++ b/Moose Development/Moose/Ops/RecoveryTanker.lua @@ -813,6 +813,13 @@ function RECOVERYTANKER:IsReturning() return self:is("Returning") end +--- Check if tanker has returned to base. +-- @param #RECOVERYTANKER self +-- @return #boolean If true, tanker has returned to base. +function RECOVERYTANKER:IsReturned() + return self:is("Returned") +end + --- Check if tanker is currently operating. -- @param #RECOVERYTANKER self -- @return #boolean If true, tanker is operating. @@ -1058,6 +1065,16 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) end end + elseif self:IsReturning() then + + -- Tanker is returning to its base. + self:T2(self.lid.."Tanker is returning.") + + elseif self:IsReturned() then + + -- Tanker landed. Waiting for engine shutdown... + self:T2(self.lid.."Tanker returned. waiting for engine shutdown.") + end -- Call status again in 30 seconds. @@ -1084,6 +1101,7 @@ function RECOVERYTANKER:onafterStatus(From, Event, To) end end + end end @@ -1313,7 +1331,8 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) group:InitModex(self.modex) -- Respawn tanker. Delaying respawn due to DCS bug https://github.com/FlightControl-Master/MOOSE/issues/1076 - SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1) + --SCHEDULER:New(nil , group.RespawnAtCurrentAirbase, {group}, 1) + self:ScheduleOnce(1, GROUP.RespawnAtCurrentAirbase, group) -- Create tanker beacon and activate TACAN. if self.TACANon then @@ -1331,7 +1350,8 @@ function RECOVERYTANKER:OnEventEngineShutdown(EventData) end -- Initial route. - SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2) + --SCHEDULER:New(nil, self._InitRoute, {self, -self.distStern+UTILS.NMToMeters(3)}, 2) + self:ScheduleOnce(2, RECOVERYTANKER._InitRoute, self, -self.distStern+UTILS.NMToMeters(3)) end end @@ -1593,7 +1613,8 @@ function RECOVERYTANKER:_ActivateTACAN(delay) if delay and delay>0 then -- Schedule TACAN activation. - SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay) + --SCHEDULER:New(nil, self._ActivateTACAN, {self}, delay) + self:ScheduleOnce(delay, RECOVERYTANKER._ActivateTACAN, self) else @@ -1604,7 +1625,7 @@ function RECOVERYTANKER:_ActivateTACAN(delay) if unit and unit:IsAlive() then -- Debug message. - local text=string.format("Activating recovery tanker TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse) + local text=string.format("Activating TACAN beacon: channel=%d mode=%s, morse=%s.", self.TACANchannel, self.TACANmode, self.TACANmorse) MESSAGE:New(text, 10, "DEBUG"):ToAllIf(self.Debug) self:T(self.lid..text) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 5d1ff66c5..a6f372e1a 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -349,7 +349,8 @@ function GROUP:Destroy( GenerateEvent, delay ) self:F2( self.GroupName ) if delay and delay>0 then - SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay) + --SCHEDULER:New(nil, GROUP.Destroy, {self, GenerateEvent}, delay) + self:ScheduleOnce(delay, GROUP.Destroy, self, GenerateEvent) else local DCSGroup = self:GetDCSObject() From 20cf69c182683602fe6e7262d8afe557cf737138 Mon Sep 17 00:00:00 2001 From: Frank Date: Mon, 22 Jul 2019 21:20:05 +0200 Subject: [PATCH 21/22] Update Airboss.lua --- Moose Development/Moose/Ops/Airboss.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 966afb94d..9f4c8494c 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -5937,10 +5937,10 @@ function AIRBOSS:_ScanCarrierZone() local isawacs=self.awacs and self.awacs.tanker:GetName()==groupname -- Send tanker to marshal stack? - local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 + local tanker2marshal = istanker and self.tanker:IsReturning() and self.tanker.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.tanker.recovery==true -- Send AWACS to marhsal stack? - local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 + local awacs2marshal = isawacs and self.awacs:IsReturning() and self.awacs.airbase:GetName()==self.airbase:GetName() and knownflight.flag==-100 and self.awacs.recovery==true -- Put flight into Marshal. local putintomarshal=closein>UTILS.NMToMeters(5) and knownflight.flag==-100 and iscarriersquad and istanker==false and isawacs==false From d08d8db29868ed2ef09154bbe1e4e38d2b554783 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 28 Jul 2019 22:43:10 +0200 Subject: [PATCH 22/22] AIRBOSS v1.0.6 - Added Marshal radial to skipper menu. - Adjusted grading and groove time start for LUL issue. --- Moose Development/Moose/Ops/Airboss.lua | 96 +++++++++++++++++++++---- 1 file changed, 82 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/Airboss.lua b/Moose Development/Moose/Ops/Airboss.lua index 9f4c8494c..04f531bd1 100644 --- a/Moose Development/Moose/Ops/Airboss.lua +++ b/Moose Development/Moose/Ops/Airboss.lua @@ -231,6 +231,7 @@ -- @field #number skipperSpeed Speed in knots for manual recovery start. -- @field #number skipperCase Manual recovery case. -- @field #boolean skipperUturn U-turn on/off via menu. +-- @field #number skipperOffset Holding offset angle in degrees for Case II/III manual recoveries. -- @field #number skipperTime Recovery time in min for manual recovery. -- @extends Core.Fsm#FSM @@ -1234,6 +1235,7 @@ AIRBOSS = { skipperMenu = nil, skipperSpeed = nil, skipperTime = nil, + skipperOffset = nil, skipperUturn = nil, } @@ -1681,7 +1683,7 @@ AIRBOSS.MenuF10Root=nil --- Airboss class version. -- @field #string version -AIRBOSS.version="1.0.5" +AIRBOSS.version="1.0.6" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1951,6 +1953,7 @@ function AIRBOSS:New(carriername, alias) local case=2 self.holdingoffset=30 self:_GetZoneGroove():SmokeZone(SMOKECOLOR.Red, 5) + self:_GetZoneLineup():SmokeZone(SMOKECOLOR.Green, 5) self:_GetZoneBullseye(case):SmokeZone(SMOKECOLOR.White, 45) self:_GetZoneDirtyUp(case):SmokeZone(SMOKECOLOR.Orange, 45) self:_GetZoneArcIn(case):SmokeZone(SMOKECOLOR.Blue, 45) @@ -2340,12 +2343,15 @@ end -- @param #number duration Default duration of the recovery in minutes. Default 30 min. -- @param #number windondeck Default wind on deck in knots. Default 25 knots. -- @param #boolean uturn U-turn after recovery window closes on=true or off=false/nil. Default off. +-- @param #number offset Relative Marshal radial in degrees for Case II/III recoveries. Default 30°. -- @return #AIRBOSS self -function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn) +function AIRBOSS:SetMenuRecovery(duration, windondeck, uturn, offset) self.skipperMenu=true self.skipperTime=duration or 30 self.skipperSpeed=windondeck or 25 + self.skipperOffset=offset or 30 + if uturn then self.skipperUturn=true else @@ -9534,7 +9540,7 @@ function AIRBOSS:_Final(playerData, nocheck) local inzone=playerData.unit:IsInZone(zone) -- Check. - if inzone then + if inzone then --and math.abs(groovedata.Roll)<5 then -- Hint for player about altitude, AoA etc. Sound is off. self:_PlayerHint(playerData, nil, true) @@ -9593,12 +9599,11 @@ function AIRBOSS:_Groove(playerData) local glideslopeError=groovedata.GSE local AoA=groovedata.AoA - -- Start time in groove when "wings are level", i.e. <= 5°. - if playerData.TIG0==nil and math.abs(groovedata.Roll)<=5.0 then - playerData.TIG0=timer.getTime() - end - if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX then + if rho<=RXX and playerData.step==AIRBOSS.PatternStep.GROOVE_XX and (math.abs(groovedata.Roll)<=4.0 or playerData.unit:IsInZone(self:_GetZoneLineup())) then + + -- Start time in groove + playerData.TIG0=timer.getTime() -- LSO "Call the ball" call. self:RadioTransmission(self.LSORadio, self.LSOCall.CALLTHEBALL, nil, nil, nil, true) @@ -9806,7 +9811,7 @@ function AIRBOSS:_Groove(playerData) -- Wait until player passed the 0.75 NM distance. local _advice=true - if rho>RXX and playerData.difficulty~=AIRBOSS.Difficulty.EASY then + if playerData.TIG0==nil and playerData.difficulty~=AIRBOSS.Difficulty.EASY then --rho>RXX _advice=false end @@ -10317,15 +10322,45 @@ function AIRBOSS:_GetZoneInitial(case) return zone end +--- Get lineup groove zone. +-- @param #AIRBOSS self +-- @return Core.Zone#ZONE_POLYGON_BASE Lineup zone. +function AIRBOSS:_GetZoneLineup() + + -- Get radial, i.e. inverse of BRC. + local fbi=self:GetRadial(1, false, false) + + -- Stern coordinate. + local st=self:_GetOptLandingCoordinate() + + -- Zone points. + local c1=st + local c2=st:Translate(UTILS.NMToMeters(0.50), fbi+15) + local c3=st:Translate(UTILS.NMToMeters(0.50), fbi+self.lue._max-0.05) + local c4=st:Translate(UTILS.NMToMeters(0.77), fbi+self.lue._max-0.05) + local c5=c4:Translate(UTILS.NMToMeters(0.25), fbi-90) + + -- Vec2 array. + local vec2={c1:GetVec2(), c2:GetVec2(), c3:GetVec2(), c4:GetVec2(), c5:GetVec2()} + + -- Polygon zone. + local zone=ZONE_POLYGON_BASE:New("Zone Lineup", vec2) + + return zone +end + + --- Get groove zone. -- @param #AIRBOSS self -- @param #number l Length of the groove in NM. Default 1.5 NM. -- @param #number w Width of the groove in NM. Default 0.25 NM. --- @return Core.Zone#ZONE_POLYGON_BASE Initial zone. -function AIRBOSS:_GetZoneGroove(l, w) +-- @param #number b Width of the beginning in NM. Default 0.10 NM. +-- @return Core.Zone#ZONE_POLYGON_BASE Groove zone. +function AIRBOSS:_GetZoneGroove(l, w, b) l=l or 1.50 w=w or 0.25 + b=b or 0.10 -- Get radial, i.e. inverse of BRC. local fbi=self:GetRadial(1, false, false) @@ -10336,9 +10371,9 @@ function AIRBOSS:_GetZoneGroove(l, w) -- Zone points. local c1=st:Translate(self.carrierparam.totwidthstarboard, fbi-90) local c2=st:Translate(UTILS.NMToMeters(0.10), fbi-90):Translate(UTILS.NMToMeters(0.3), fbi) - local c3=st:Translate(UTILS.NMToMeters(w/2), fbi-90):Translate(UTILS.NMToMeters(l), fbi) + local c3=st:Translate(UTILS.NMToMeters(0.25), fbi-90):Translate(UTILS.NMToMeters(l), fbi) local c4=st:Translate(UTILS.NMToMeters(w/2), fbi+90):Translate(UTILS.NMToMeters(l), fbi) - local c5=st:Translate(UTILS.NMToMeters(0.10), fbi+90):Translate(UTILS.NMToMeters(0.3), fbi) + local c5=st:Translate(UTILS.NMToMeters(b), fbi+90):Translate(UTILS.NMToMeters(0.3), fbi) local c6=st:Translate(self.carrierparam.totwidthport, fbi+90) -- Vec2 array. @@ -15505,6 +15540,12 @@ function AIRBOSS:_AddF10Commands(_unitName) missionCommands.addCommandForGroup(gid, "45 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 45) missionCommands.addCommandForGroup(gid, "60 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 60) missionCommands.addCommandForGroup(gid, "90 min", _menusetrtime, self._SkipperRecoveryTime, self, _unitName, 90) + local _menusetrtime=missionCommands.addSubMenuForGroup(gid, "Set Marshal Radial", _skipperPath) + missionCommands.addCommandForGroup(gid, "+30°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, 30) + missionCommands.addCommandForGroup(gid, "+15°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, 15) + missionCommands.addCommandForGroup(gid, "0°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, 0) + missionCommands.addCommandForGroup(gid, "-15°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, -15) + missionCommands.addCommandForGroup(gid, "-30°", _menusetrtime, self._SkipperRecoveryOffset, self, _unitName, -30) missionCommands.addCommandForGroup(gid, "U-turn On/Off", _skipperPath, self._SkipperRecoveryUturn, self, _unitName) missionCommands.addCommandForGroup(gid, "Start CASE I", _skipperPath, self._SkipperStartRecovery, self, _unitName, 1) missionCommands.addCommandForGroup(gid, "Start CASE II", _skipperPath, self._SkipperStartRecovery, self, _unitName, 2) @@ -15560,6 +15601,9 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case) -- Inform player. local text=string.format("affirm, Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.skipperTime, self.skipperSpeed, tostring(self.skipperUturn)) + if case>1 then + text=text..string.format(" Marshal radial %d°.", self.skipperOffset) + end if self:IsRecovering() then text="negative, carrier is already recovering." self:MessageToPlayer(playerData, text, "AIRBOSS") @@ -15574,7 +15618,7 @@ function AIRBOSS:_SkipperStartRecovery(_unitName, case) local C9=UTILS.SecondsToClock(t9) -- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on. - self:AddRecoveryWindow(C0, C9, case, 30, true, self.skipperSpeed, self.skipperUturn) + self:AddRecoveryWindow(C0, C9, case, self.skipperOffset, true, self.skipperSpeed, self.skipperUturn) end end @@ -15608,6 +15652,30 @@ function AIRBOSS:_SkipperStopRecovery(_unitName) end end +--- Skipper set recovery offset angle. +-- @param #AIRBOSS self +-- @param #string _unitName Name fo the player unit. +-- @param #number offset Recovery holding offset angle in degrees for Case II/III. +function AIRBOSS:_SkipperRecoveryOffset(_unitName, offset) + + -- Get player unit and name. + local _unit, _playername = self:_GetPlayerUnitAndName(_unitName) + + -- Check if we have a unit which is a player. + if _unit and _playername then + local playerData=self.players[_playername] --#AIRBOSS.PlayerData + + if playerData then + + -- Inform player. + local text=string.format("roger, relative CASE II/III Marshal radial set to %d°.", offset) + self:MessageToPlayer(playerData, text, "AIRBOSS") + + self.skipperOffset=offset + end + end +end + --- Skipper set recovery time. -- @param #AIRBOSS self -- @param #string _unitName Name fo the player unit.