From 6ac119474668212036854703d9f2936997e8632e Mon Sep 17 00:00:00 2001 From: Frank Date: Tue, 11 Jun 2019 22:57:38 +0200 Subject: [PATCH] Skipper v0.0.1 - Added skipper class. - Other minor improvements. --- .../Moose/Functional/Artillery.lua | 158 +++- Moose Development/Moose/Modules.lua | 1 + Moose Development/Moose/Ops/Skipper.lua | 744 ++++++++++++++++++ .../Moose/Wrapper/Controllable.lua | 12 +- Moose Setup/Moose.files | 1 + 5 files changed, 906 insertions(+), 10 deletions(-) create mode 100644 Moose Development/Moose/Ops/Skipper.lua diff --git a/Moose Development/Moose/Functional/Artillery.lua b/Moose Development/Moose/Functional/Artillery.lua index 637560d03..3c24e381a 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 @@ -3318,9 +3428,42 @@ function ARTY:_FireAtCoord(coord, radius, nshells, weapontype) local fire=group:TaskFireAtPoint(vec2, radius, nshells, weapontype) -- Execute task. - group:SetTask(fire) + group:PushTask(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/Modules.lua b/Moose Development/Moose/Modules.lua index 56dd5a090..fb1f44800 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -68,6 +68,7 @@ __Moose.Include( 'Scripts/Moose/Ops/Airboss.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RecoveryTanker.lua' ) __Moose.Include( 'Scripts/Moose/Ops/RescueHelo.lua' ) __Moose.Include( 'Scripts/Moose/Ops/FlightControl.lua' ) +__Moose.Include( 'Scripts/Moose/Ops/Skipper.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Balancer.lua' ) __Moose.Include( 'Scripts/Moose/AI/AI_Air.lua' ) diff --git a/Moose Development/Moose/Ops/Skipper.lua b/Moose Development/Moose/Ops/Skipper.lua new file mode 100644 index 000000000..3d6ce938e --- /dev/null +++ b/Moose Development/Moose/Ops/Skipper.lua @@ -0,0 +1,744 @@ +--- **ATC** - (R2.5) - Manage behavior of the Carrier Strike Group. +-- 2 +-- **Main Features:** +-- +-- * Nice stuff. +-- +-- === +-- +-- ### Author: **funkyfranky** +-- @module Ops.Skipper +-- @image OPS_Skipper.png + + +--- SKIPPER class. +-- @type SKIPPER +-- @field #string ClassName Name of the class. +-- @field #boolean Debug Debug mode. Messages to all about status. +-- @field #string theatre The DCS map used in the mission. +-- @field #string lid Class id string for output to DCS log file. +-- @field Wrapper.Group#GROUP group The carrier strike group. +-- @field Wrapper.Unit#UNIT carrier The carrier unit. +-- @field #string carriername The name of the carrier unit. +-- @field #table waypoints Table of waypoint coordinates as defined in the mission editor. +-- @field #number currentwp Current waypoint, i.e. the one that was passed last. Counting starts a one. +-- @field Ops.Airboss#AIRBOSS airboss The airboss of the carrier. +-- @field Functional.Warehouse#WAREHOUSE warehouse The warehouse of the carrier. +-- @field Functional.Artillery#ARTY arty The artillery object of the carrier. +-- @field Core.Zone#ZONE_UNIT zoneCCA Carrier Controlled Area, 50 NM zone around the carrier. +-- @field #table intruders Table of intruders, i.e. groups inside the CCA. Each element is of type #SKIPPPER.Intruder. +-- @extends Core.Fsm#FSM + +--- Be surprised! +-- +-- === +-- +-- ![Banner Image](..\Presentations\SKIPPER\Skipper_Main.jpg) +-- +-- # The SKIPPER Concept +-- +-- +-- +-- @field #SKIPPER +SKIPPER = { + ClassName = "SKIPPER", + Debug = false, + lid = nil, + theatre = nil, + carriername = nil, + carrier = nil, + group = nil, + waypoints = nil, + currentwp = nil, + airboss = nil, + warehouse = nil, + arty = nil, + zoneCCA = nil, + zoneCCZ = nil, + intruders = {}, +} + +--- Intruder. +-- @type SKIPPER.Intruder +-- @field Wrapper.Group#GROUP group Intruder group object. +-- @field #string groupname Name of the intruder group. +-- @field #number time0 Abs. mission time first detected inside CCA. +-- @field #number dist0 Distance first detected inside CCA. +-- @field DCS#Coalition.Side coalition Coalition side. +-- @field #number threadlevel Thread level of intruder. +-- @field #string threadtext Thread text. +-- @field #number category Group category. +-- @field #string categoryname Group category name. +-- @field #string typename Type name of group. + +--- FlightControl class version. +-- @field #string version +SKIPPER.version="0.0.1" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot! + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new SKIPPER class object for a specific aircraft carrier unit. +-- @param #SKIPPER self +-- @param #string carriername Name of the carrier. +-- @return #SKIPPER self +function SKIPPER:New(carriername) + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, FSM:New()) -- #SKIPPER + + self.carriername=carriername + self.carrier=UNIT:FindByName(carriername) + + if not self.carrier then + BASE:E(string.format("ERROR: Could not find carrier %s!", carriername)) + return nil + end + + self.group=self.carrier:GetGroup() + + self.arty=ARTY:New(self.group, carriername) + + self.warehouse=WAREHOUSE:New(carriername) + + self.airboss=AIRBOSS:New(carriername) + + -- Set some string id for output to DCS.log file. + self.lid=string.format("SKIPPER %s |", self.carriername) + + -- Current map. + self.theatre=env.mission.theatre + + -- 30 NM zone around the airbase. + self.zoneCCA=ZONE_UNIT:New("CCA", self.carrier, UTILS.NMToMeters(50)) + + -- Initialize ME waypoints. + self:_InitWaypoints() + + -- Current waypoint. + self.currentwp=1 + + -- Patrol route. + self:_PatrolRoute() + + -- Start State. + self:SetStartState("Stopped") + + -- Add FSM transitions. + -- From State --> Event --> To State + self:AddTransition("Stopped", "Start", "Running") -- Start FSM. + self:AddTransition("*", "Status", "*") -- Update status. + + -- Debug trace. + if true then + self.Debug=true + BASE:TraceOnOff(true) + BASE:TraceClass(self.ClassName) + BASE:TraceLevel(3) + end + + self.arty:GetAmmo(true) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Get carrier coalition. +-- @param #SKIPPER self +-- @return #number Coalition side of carrier. +function SKIPPER:GetCoalition() + return self.carrier:GetCoalition() +end + +--- Get carrier coordinate. +-- @param #SKIPPER self +-- @return Core.Point#COORDINATE Carrier coordinate. +function SKIPPER:GetCoordinate() + return self.carrier:GetCoordinate() +end + + +--- Get AIRBOSS object associated with the carrier. +-- @param #SKIPPER self +-- @return Ops.Airboss#AIRBOSS Airboss object. +function SKIPPER:GetAirboss() + return self.airboss +end + +--- Get WAREHOUSE object associated with the carrier. +-- @param #SKIPPER self +-- @return Functional.Warehouse#WAREHOUSE Warehouse object. +function SKIPPER:GetWarehouseCarrier() + return self.warehouse +end + +--- Get ARTY object associated with the carrier strike group. +-- @param #SKIPPER self +-- @return Functional.Artillery#ARTY Arty object. +function SKIPPER:GetArty() + return self.arty +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Status +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Start SKIPPER FSM. Handle events. +-- @param #SKIPPER self +function SKIPPER:onafterStart() + + -- Events are handled my MOOSE. + self:I(self.lid..string.format("Starting SKIPPER v%s for carrier %s on map %s.", SKIPPER.version, self.carriername, self.theatre)) + + -- Start ARTY. + self.arty:Start() + + -- Start Warehouse. + self.warehouse:Start() + + -- Start Airboss. + self.airboss:Start() + + -- Add F10 radio menu. + self:_SetMenuCoalition() + + -- Handle events. + self:HandleEvent(EVENTS.Birth) + self:HandleEvent(EVENTS.EngineStartup) + self:HandleEvent(EVENTS.Takeoff) + self:HandleEvent(EVENTS.Land) + self:HandleEvent(EVENTS.EngineShutdown) + self:HandleEvent(EVENTS.Crash) + + -- Init status updates. + self:__Status(-1) +end + +--- Update status. +-- @param #SKIPPER self +function SKIPPER:onafterStatus() + + local fsmstate=self:GetState() + + -- Check zone for flights inbound. + self:_CheckIntruder() + + -- Check parking spots. + --self:_CheckParking() + + -- Check waiting and landing queue. + --self:_CheckQueues() + + -- Info text. + local text=string.format("State %s", fsmstate) + self:I(self.lid..text) + + self:__Status(-30) +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- FSM Events +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- CCA Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Scan carrier zone for (new) units. +-- @param #SKIPPER self +function SKIPPER:_CheckIntruder() + + -- Carrier position. + local coord=self:GetCoordinate() + + -- Scan radius = radius of the CCA. + local RCCZ=self.zoneCCA:GetRadius() + + -- Debug info. + self:T(self.lid..string.format("Scanning Carrier Controlled Area. Radius=%.1f NM.", UTILS.MetersToNM(RCCZ))) + + -- Scan units in carrier zone. + local _,_,_,unitscan=coord:ScanObjects(RCCZ, true, false, false) + + + -- Make a table with all groups currently in the CCA zone. + local insideCCA={} + for _,_unit in pairs(unitscan) do + local unit=_unit --Wrapper.Unit#UNIT + + -- Necessary conditions to be met: + local airborne=unit:IsAir() + local inzone=unit:IsInZone(self.zoneCCA) + local friendly=self:GetCoalition()==unit:GetCoalition() + + -- Check if this an aircraft and that it is airborne and closing in. + if inzone then + + local group=unit:GetGroup() + local groupname=group:GetName() + + if insideCCA[groupname]==nil and groupname~=self.group:GetName() then + insideCCA[groupname]=group + end + + end + end + + -- Find out if any known intruder is not in the CCA any more. + for i=#self.intruders,1,-1 do + local intruder=self.intruders[i] --#SKIPPER.Intruder + + -- Loop over current groups in CCA. + local gotit=false + for groupname,_group in pairs(insideCCA) do + local group=_group --Wrapper.Group#GROUP + + if groupname==intruder.groupname then + gotit=true + end + end + + if not gotit then + table.remove(self.intruders, i) + end + + end + + for groupname,_group in pairs(insideCCA) do + local group=_group --Wrapper.Group#GROUP + + -- Find out if any known intruder is not in the CCA any more. + local gotit=false + for i=1,#self.intruders do + local intruder=self.intruders[i] --#SKIPPER.Intruder + if groupname==intruder.groupname then + gotit=true + end + end + + if not gotit then + + -- Get thread level. + local tl, tt=group:GetThreatLevel() + + -- Create a new intruder table. + local intruder={} --#SKIPPER.Intruder + intruder.coalition=group:GetCoalition() + intruder.group=group + intruder.threadlevel=tl + intruder.threadtext=tt + intruder.time0=timer.getAbsTime() + intruder.dist0=self:GetCoordinate():Get2DDistance(group:GetCoordinate()) + intruder.groupname=groupname + intruder.typename=group:GetTypeName() + intruder.category=group:GetCategory() + intruder.categoryname=group:GetCategoryName() + + -- Add intruder to list. + table.insert(self.intruders, intruder) + + end + end + + +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Misc Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Patrol carrier. +-- @param #SKIPPER self +-- @return #SKIPPER self +function SKIPPER:_InitWaypoints() + + -- Waypoints of group. + local Waypoints=self.group: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 #SKIPPER self +-- @param #number n Next waypoint number. +-- @return #SKIPPER self +function SKIPPER:_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.group + + -- 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("SKIPPER._PassingWaypoint", self, i, #self.waypoints) + + -- Call task function when carrier arrives at waypoint. + CarrierGroup:SetTaskWaypoint(wp, TaskPassingWP) + + -- + table.insert(Waypoints, wp) + end + + -- Route carrier group. + CarrierGroup:Route(Waypoints) + + return self +end + +--- Function called when a group is passing a waypoint. +--@param Wrapper.Group#GROUP group Group that passed the waypoint. +--@param #SKIPPER skipper skipper object. +--@param #number i Waypoint number that has been reached. +--@param #number final Final waypoint number. +function SKIPPER._PassingWaypoint(group, skipper, i, final) + + -- Debug message. + local text=string.format("Group %s passing waypoint %d of %d.", group:GetName(), i, final) + + -- Debug smoke and marker. + if skipper.Debug and false then + local pos=group:GetCoordinate() + pos:SmokeRed() + local MarkerID=pos:MarkToAll(string.format("Group %s reached waypoint %d", group:GetName(), i)) + end + + -- Debug message. + MESSAGE:New(text,10):ToAllIf(skipper.Debug) + skipper:T(skipper.lid..text) + + -- Set current waypoint. + skipper.currentwp=i + + -- Passing Waypoint event. + --skipper:PassingWaypoint(i) + + -- If final waypoint reached, do route all over again. + if i==final and final>1 and skipper.adinfinitum then + skipper:_PatrolRoute(i) + end +end + +--- Get next waypoint of the carrier. +-- @param #SKIPPER self +-- @return Core.Point#COORDINATE Coordinate of the next waypoint. +-- @return #number Number of waypoint. +function SKIPPER:_GetNextWaypoint() + + -- Next waypoint. + local Nextwp=nil + if self.currentwp==#self.waypoints then + Nextwp=1 + else + Nextwp=self.currentwp+1 + end + + -- Debug output + local text=string.format("Current WP=%d/%d, next WP=%d", self.currentwp, #self.waypoints, Nextwp) + self:T2(self.lid..text) + + -- Next waypoint. + local nextwp=self.waypoints[Nextwp] --Core.Point#COORDINATE + + return nextwp,Nextwp +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Menu Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Patrol carrier. +-- @param #SKIPPER self +-- @return #SKIPPER self +function SKIPPER:_SetMenuCoalition() + + local Coalition=self:GetCoalition() + + local menu={} + + menu.Skipper=MENU_COALITION:New(Coalition, "Skipper") + + menu.SetROE = MENU_COALITION:New(Coalition, "Set ROE", menu.Skipper) + menu.SetROE_Hold = MENU_COALITION_COMMAND:New(Coalition, "Weapon Hold", menu.SetROE, self._SetROE, self, "Hold") + menu.SetROE_Free = MENU_COALITION_COMMAND:New(Coalition, "Weapon Free", menu.SetROE, self._SetROE, self, "Free") + menu.SetROE_Return= MENU_COALITION_COMMAND:New(Coalition, "Return Fire", menu.SetROE, self._SetROE, self, "Return") + + -- Alarm state does not seem to apply for ships. + --menu.SetROE_Green = MENU_COALITION_COMMAND:New(Coalition, "State Green", menu.SetROE, self._SetALS, self, "Green") + --menu.SetROE_Red = MENU_COALITION_COMMAND:New(Coalition, "State Red", menu.SetROE, self._SetALS, self, "Red") + --menu.SetROE_Auto = MENU_COALITION_COMMAND:New(Coalition, "State Auto", menu.SetROE, self._SetALS, self, "Auto") + + menu.SetSpeed = MENU_COALITION:New(Coalition, "Set Speed", menu.Skipper) + menu.SetSpeed_00 = MENU_COALITION_COMMAND:New(Coalition, "Hold Position", menu.SetSpeed, self._SetSpeed, self, 0) + menu.SetSpeed_05 = MENU_COALITION_COMMAND:New(Coalition, "5 knots", menu.SetSpeed, self._SetSpeed, self, 5) + menu.SetSpeed_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetSpeed, self._SetSpeed, self, 10) + menu.SetSpeed_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetSpeed, self._SetSpeed, self, 15) + menu.SetSpeed_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetSpeed, self._SetSpeed, self, 20) + menu.SetSpeed_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetSpeed, self._SetSpeed, self, 25) + menu.SetSpeed_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetSpeed, self._SetSpeed, self, 30) + menu.SetSpeed_99 = MENU_COALITION_COMMAND:New(Coalition, "Restore Route", menu.SetSpeed, self.CarrierResume, self) + + menu.Defence = MENU_COALITION:New(Coalition, "Defence", menu.Skipper) + menu.Defence_Ammo = MENU_COALITION_COMMAND:New(Coalition, "Report Ammo", menu.Defence, self.arty.GetAmmo, self.arty, true) + menu.Defence_Intruders = MENU_COALITION_COMMAND:New(Coalition, "Report Intruders", menu.Defence, self._ListIntruders, self) + + if self.airboss then + menu.Recovery=MENU_COALITION:New(Coalition, "Recovery", menu.Skipper) + + -- Set wind on deck. + menu.SetWoD = MENU_COALITION:New(Coalition, "Wind on Deck", menu.Recovery) + menu.SetWoD_10 = MENU_COALITION_COMMAND:New(Coalition, "10 knots", menu.SetWoD, self._SetWoD, self, 10) + menu.SetWoD_15 = MENU_COALITION_COMMAND:New(Coalition, "15 knots", menu.SetWoD, self._SetWoD, self, 15) + menu.SetWoD_20 = MENU_COALITION_COMMAND:New(Coalition, "20 knots", menu.SetWoD, self._SetWoD, self, 20) + menu.SetWoD_25 = MENU_COALITION_COMMAND:New(Coalition, "25 knots", menu.SetWoD, self._SetWoD, self, 25) + menu.SetWoD_30 = MENU_COALITION_COMMAND:New(Coalition, "30 knots", menu.SetWoD, self._SetWoD, self, 30) + + -- Set Duration. + menu.SetRtime = MENU_COALITION:New(Coalition, "Duration", menu.Recovery) + menu.SetRtime_15 = MENU_COALITION_COMMAND:New(Coalition, "15 min", menu.SetRtime, self._SetRtime, self, 15) + menu.SetRtime_30 = MENU_COALITION_COMMAND:New(Coalition, "30 min", menu.SetRtime, self._SetRtime, self, 30) + menu.SetRtime_45 = MENU_COALITION_COMMAND:New(Coalition, "45 min", menu.SetRtime, self._SetRtime, self, 45) + menu.SetRtime_60 = MENU_COALITION_COMMAND:New(Coalition, "60 min", menu.SetRtime, self._SetRtime, self, 60) + menu.SetRtime_90 = MENU_COALITION_COMMAND:New(Coalition, "90 min", menu.SetRtime, self._SetRtime, self, 90) + + -- Start/Stop. + menu.SetUturn = MENU_COALITION_COMMAND:New(Coalition, "U-turn On/Off", menu.Recovery, self._SetUturn, self) + menu.CaseI = MENU_COALITION_COMMAND:New(Coalition, "Start CASE I", menu.Recovery, self._StartCaseX, self, 1) + menu.CaseII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE II", menu.Recovery, self._StartCaseX, self, 2) + menu.CaseIII = MENU_COALITION_COMMAND:New(Coalition, "Start CASE III", menu.Recovery, self._StartCaseX, self, 3) + menu.Rstop = MENU_COALITION_COMMAND:New(Coalition, "Stop Recovery", menu.Recovery, self._Rstop, self) + end + +end + +--- Intruders. +-- @param #SKIPPER self +function SKIPPER:_ListIntruders() + + local text="Current Intruders:" + + for i,_intruder in pairs(self.intruders) do + local intruder=_intruder --#SKIPPER.Intruder + text=text..string.format("\n[%d] %s*%d, %s [%d/10]", i, intruder.typename, #intruder.group:GetUnits(), intruder.categoryname, intruder.threadlevel) + end + + if #self.intruders==0 then + text=text.." none." + end + + MESSAGE:New(text, 10, self.ClassName):ToCoalition(self:GetCoalition()) +end + +--- Start Case X recovery. +-- @param #SKIPPER self +-- @param #number case Recovery case (1,2,3). +function SKIPPER:_StartCaseX(case) + + if self.airboss then + + self.airboss.skipperTime=self.airboss.skipperTime or 30 + self.airboss.skipperSpeed=self.airboss.skipperSpeed or 25 + if self.airboss.skipperUturn==nil then + self.airboss.skipperUturn=false + end + + -- Inform player. + local text=string.format("Case %d recovery will start in 5 min for %d min. Wind on deck %d knots. U-turn=%s.", case, self.airboss.skipperTime, self.airboss.skipperSpeed, tostring(self.airboss.skipperUturn)) + + if self.airboss:IsRecovering() then + text="negative, carrier is already recovering." + MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition()) + return + end + + -- Recovery staring in 5 min for 30 min. + local t0=timer.getAbsTime()+5*60 + local t9=t0+self.airboss.skipperTime*60 + local C0=UTILS.SecondsToClock(t0) + local C9=UTILS.SecondsToClock(t9) + + -- Carrier will turn into the wind. Wind on deck 25 knots. U-turn on. + self.airboss:AddRecoveryWindow(C0, C9, case, 30, true, self.airboss.skipperSpeed, self.airboss.skipperUturn) + + MESSAGE:New(string.format(text), 5, self.ClassName):ToCoalition(self:GetCoalition()) + end + +end + + + +--- Toggle recovery U-turn option. +-- @param #SKIPPER self +function SKIPPER:_SetUturn() + + if self.airboss then + self.airboss.skipperUturn=not self.airboss.skipperUturn + + MESSAGE:New(string.format("Recovery U-turn is now %s.", tostring(self.airboss.skipperUturn)), 5, self.ClassName):ToCoalition(self:GetCoalition()) + end + +end + +--- Set manual recovery duration. +-- @param #SKIPPER self +-- @param #number time Duration in minutes. +function SKIPPER:_SetRtime(time) + + if self.airboss then + self.airboss.skipperTime=time + + MESSAGE:New(string.format("Recovery duration set to %d min.", time), 5, self.ClassName):ToCoalition(self:GetCoalition()) + end + +end + + +--- Set wind on deck for manual recovery start. +-- @param #SKIPPER self +-- @param #number speed Speed in knots. +function SKIPPER:_SetWoD(speed) + + if self.airboss then + self.airboss.skipperSpeed=speed + + MESSAGE:New(string.format("Wind on Deck set to %d knots.", speed), 5, self.ClassName):ToCoalition(self:GetCoalition()) + end + +end + +--- Set new speed for all waypoints. +-- @param #SKIPPER self +-- @param #number speed Speed in knots. +function SKIPPER:_SetSpeed(speed) + + -- Loop over waypoints. + for n=1,#self.waypoints do + local coord=self.waypoints[n] --Core.Point#COORDINATE + + coord.Velocity=UTILS.KnotsToMps(speed) + end + + self:_PatrolRoute() + +end + +--- Set rules of engagement. +-- @param #SKIPPER self +-- @param #string roe "Hold", "Free", "Return". +function SKIPPER:_SetROE(roe) + + if roe=="Hold" then + self.group:OptionROEHoldFire() + elseif roe=="Free" then + self.group:OptionROEOpenFire() + elseif roe=="Return" then + self.group:OptionROEReturnFire() + end + + MESSAGE:New(string.format("ROE set to %s", roe), 5, self.ClassName):ToCoalition(self:GetCoalition()) +end + +--- Set alaram state. +-- @param #SKIPPER self +-- @param #string state "Green", "Red", "Auto". +function SKIPPER:_SetALS(state) + + if state=="Green" then + self.group:OptionAlarmStateGreen() + elseif state=="Red" then + self.group:OptionAlarmStateRed() + elseif state=="Auto" then + self.group:OptionAlarmStateAuto() + end + + MESSAGE:New(string.format("Alarm state set to %s", state), 5, self.ClassName):ToCoalition(self:GetCoalition()) +end + +--- Function to stop the carrier. +-- @param #SKIPPER self +function SKIPPER:CarrierHold() + env.info("Carrier Hold!") + + -- Get current position. + local pos=self.group:GetCoordinate() + + -- Create a new waypoint. + local wp=pos:WaypointGround(0) + + -- Create new route consisting of only this position ==> Stop! + self.group:Route({wp}) + + MESSAGE:New(string.format("Carrier is holding current position."), 5, self.ClassName):ToCoalition(self:GetCoalition()) +end + +--- Function to stop the carrier. +-- @param #SKIPPER self +function SKIPPER:CarrierResume() + env.info("Carrier Resume Route!") + + self:_InitWaypoints() + + local nextWP,n=self:_GetNextWaypoint() + + self:_PatrolRoute(n) + + MESSAGE:New(string.format("Carrier is resuming route to waypoint #%d.", n), 5, self.ClassName):ToCoalition(self:GetCoalition()) +end + + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 7cfb56abb..b2c2d663e 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -863,11 +863,17 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At local DirectionEnabled = nil if Direction then DirectionEnabled = true + else + DirectionEnabled = false + Direction=0 end local AltitudeEnabled = nil if Altitude then AltitudeEnabled = true + else + AltitudeEnabled = false + Altitude=0 end local DCSTask @@ -3165,7 +3171,7 @@ function CONTROLLABLE:OptionAlarmStateAuto() if self:IsGround() then Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) elseif self:IsShip() then - Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.AUTO) + Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.AUTO) end return self @@ -3188,7 +3194,7 @@ function CONTROLLABLE:OptionAlarmStateGreen() Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) elseif self:IsShip() then -- AI.Option.Naval.id.ALARM_STATE does not seem to exist! - --Controller:setOption( AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.GREEN ) + Controller:setOption( AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.GREEN ) end return self @@ -3210,7 +3216,7 @@ function CONTROLLABLE:OptionAlarmStateRed() if self:IsGround() then Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) elseif self:IsShip() then - Controller:setOption(AI.Option.Naval.id.ALARM_STATE, AI.Option.Naval.val.ALARM_STATE.RED) + Controller:setOption(AI.Option.Ground.id.ALARM_STATE, AI.Option.Ground.val.ALARM_STATE.RED) end return self diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index 069b652fc..2f0a20e61 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -66,6 +66,7 @@ Ops/Airboss.lua Ops/RecoveryTanker.lua Ops/RescueHelo.lua Ops/FlightControl.lua +Ops/Skipper.lua AI/AI_Balancer.lua AI/AI_Air.lua