From 560e3d643f8f7b32e854a3bf43ae3af1ef13d235 Mon Sep 17 00:00:00 2001 From: FlightControl Date: Thu, 24 Mar 2016 11:00:49 +0100 Subject: [PATCH] Worked on DCS function prototyping Found in LDT the means to create using luadoc function prototypes using a documentation mechanism. Am reworking now the DCS function manual to a luadoc structure. --- DCS/Airbase.doclua | 53 ++++++++ DCS/CoalitionObject.doclua | 17 +++ DCS/Controller.doclua | 116 +++++++++++++++++ DCS/DCSTypes.lua | 231 ++++++++++++++++++++++++++++++++++ DCS/Group.doclua | 82 ++++++++++++ DCS/Object.doclua | 73 +++++++++++ DCS/StaticObject.doclua | 34 +++++ DCS/Time.doclua | 2 + DCS/Unit.doclua | 241 ++++++++++++++++++++++++++++++++++++ DCS/env.doclua | 27 ++++ DCS/timer.doclua | 45 +++++++ Embedded/Moose_Embedded.lua | 32 ++--- Moose/Base.lua | 16 ++- Moose/Cargo.lua | 18 +-- Moose/Client.lua | 92 ++++++++------ Moose/Escort.lua | 159 ++++++++++++++++++++++-- Moose/Group.lua | 185 ++++++++++++++++++++++++++- Moose/Menu.lua | 34 ++++- Moose/Message.lua | 11 +- Moose/Mission.lua | 4 +- 20 files changed, 1381 insertions(+), 91 deletions(-) create mode 100644 DCS/Airbase.doclua create mode 100644 DCS/CoalitionObject.doclua create mode 100644 DCS/Controller.doclua create mode 100644 DCS/DCSTypes.lua create mode 100644 DCS/Group.doclua create mode 100644 DCS/Object.doclua create mode 100644 DCS/StaticObject.doclua create mode 100644 DCS/Time.doclua create mode 100644 DCS/Unit.doclua create mode 100644 DCS/env.doclua create mode 100644 DCS/timer.doclua diff --git a/DCS/Airbase.doclua b/DCS/Airbase.doclua new file mode 100644 index 000000000..679a09981 --- /dev/null +++ b/DCS/Airbase.doclua @@ -0,0 +1,53 @@ +------------------------------------------------------------------------------- +-- @module Airbase +-- @extends CoalitionObject#CoalitionObject + +--- Represents airbases: airdromes, helipads and ships with flying decks or landing pads. +-- @type Airbase +-- @field #Airbase.ID ID Identifier of an airbase. It assigned to an airbase by the Mission Editor automatically. This identifier is used in AI tasks to refer an airbase that exists (spawned and not dead) or not. +-- @field #Airbase.Category Category enum contains identifiers of airbase categories. +-- @field #Airbase.Desc Desc Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. + +--- Enum contains identifiers of airbase categories. +-- @type Airbase.Category +-- @field AIRDROME +-- @field HELIPAD +-- @field SHIP + +--- Airbase descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. +-- @type Airbase.Desc +-- @extends #Desc +-- @field #Airbase.Category category Category of the airbase type. + +--- Returns airbase by its name. If no airbase found the function will return nil. +-- @function [parent=#Airbase] getByName +-- @param #string name +-- @return #Airbase + +--- Returns airbase descriptor by type name. If no descriptor is found the function will return nil. +-- @function [parent=#Airbase] getDescByName +-- @param #TypeName typeName Airbase type name. +-- @return #Airbase.Desc + +--- Returns Unit that is corresponded to the airbase. Works only for ships. +-- @function [parent=#Airbase] getUnit +-- @param self +-- @return Unit#Unit + +--- Returns identifier of the airbase. +-- @function [parent=#Airbase] getID +-- @param self +-- @return #Airbase.ID + +--- Returns the airbase's callsign - the localized string. +-- @function [parent=#Airbase] getCallsign +-- @param self +-- @return #string + +--- Returns descriptor of the airbase. +-- @function [parent=#Airbase] getDesc +-- @param self +-- @return #Airbase.Desc + + +Airbase = {} --#Airbase diff --git a/DCS/CoalitionObject.doclua b/DCS/CoalitionObject.doclua new file mode 100644 index 000000000..ee189fd77 --- /dev/null +++ b/DCS/CoalitionObject.doclua @@ -0,0 +1,17 @@ +------------------------------------------------------------------------------- +-- @module CoalitionObject +-- @extends Object#Object + +--- @type CoalitionObject + +--- Returns coalition of the object. +-- @function [parent=#CoalitionObject] getCoalition +-- @param self +-- @return #coalition.side + +--- Returns object country. +-- @function [parent=#CoalitionObject] getCountry +-- @param self +-- @return #country.id + +CoalitionObject = {} --#CoalitionObject diff --git a/DCS/Controller.doclua b/DCS/Controller.doclua new file mode 100644 index 000000000..0f434cd30 --- /dev/null +++ b/DCS/Controller.doclua @@ -0,0 +1,116 @@ +------------------------------------------------------------------------------- +-- @module Controller +-- TODO Add Task templates + +--- Controller is an object that performs A.I.-routines. Other words controller is an instance of A.I.. Controller stores current main task, active enroute tasks and behavior options. Controller performs commands. Please, read DCS A-10C GUI Manual EN.pdf chapter "Task Planning for Unit Groups", page 91 to understand A.I. system of DCS:A-10C. +-- +-- This class has 2 types of functions: +-- +-- * Tasks +-- * Commands: Commands are instant actions those required zero time to perform. Commands may be used both for control unit/group behavior and control game mechanics. +-- @type Controller +-- @field #Controller.Detection Detection Enum contains identifiers of surface types. + +--- Enables and disables the controller. +-- Note: Now it works only for ground / naval groups! +-- @function [parent=#Controller] setOnOff +-- @param self +-- @param #boolean value Enable / Disable. + +-- Tasks + +--- Resets current task and then sets the task to the controller. Task is a table that contains task identifier and task parameters. +-- @function [parent=#Controller] setTask +-- @param self +-- @param #Task task + +--- Resets current task of the controller. +-- @function [parent=#Controller] resetTask +-- @param self + +--- Pushes the task to the front of the queue and makes the task active. Further call of function Controller.setTask() function will stop current task, clear the queue and set the new task active. If the task queue is empty the function will work like function Controller.setTask() function. +-- @function [parent=#Controller] pushTask +-- @param self +-- @param #Task task + +--- Pops current (front) task from the queue and makes active next task in the queue (if exists). If no more tasks in the queue the function works like function Controller.resetTask() function. Does nothing if the queue is empty. +-- @function [parent=#Controller] popTask +-- @param self + +--- Returns true if the controller has a task. +-- @function [parent=#Controller] hasTask +-- @param self +-- @return #boolean + +-- Commands + +--TODO: describe #Command structure +--- Sets the command to perform by controller. +-- @function [parent=#Controller] setCommand +-- @param self +-- @param #Command command Table that contains command identifier and command parameters. + + +-- Behaviours + +--- Sets the option to the controller. +-- Option is a pair of identifier and value. Behavior options are global parameters those affect controller behavior in all tasks it performs. +-- Option identifiers and values are stored in table AI.Option in subtables Air, Ground and Naval. +-- +-- OptionId = @{#AI.Option.Air.id} or @{#AI.Option.Ground.id} or @{#AI.Option.Naval.id} +-- OptionValue = AI.Option.Air.val[optionName] or AI.Option.Ground.val[optionName] or AI.Option.Naval.val[optionName] +-- +-- @function [parent=#Controller] setOption +-- @param self +-- @param #OptionId optionId Option identifier. +-- @param #OptionValue optionValue Value of the option. + + +-- Detection + +--- Enum contains identifiers of surface types. +-- @type Controller.Detection +-- @field VISUAL +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR +-- @field DLINK + +--- Detected target. +-- @type DetectedTarget +-- @field Object#Object object The target +-- @field #boolean visible The target is visible +-- @field #boolean type The target type is known +-- @field #boolean distance Distance to the target is known + + +--- Checks if the target is detected or not. If one or more detection method is specified the function will return true if the target is detected by at least one of these methods. If no detection methods are specified the function will return true if the target is detected by any method. +-- @function [parent=#Controller] isTargetDetected +-- @param self +-- @param Object#Object target Target to check +-- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN +-- @return #boolean detected True if the target is detected. +-- @return #boolean visible Has effect only if detected is true. True if the target is visible now. +-- @return #ModelTime lastTime Has effect only if visible is false. Last time when target was seen. +-- @return #boolean type Has effect only if detected is true. True if the target type is known. +-- @return #boolean distance Has effect only if detected is true. True if the distance to the target is known. +-- @return #Vec3 lastPos Has effect only if visible is false. Last position of the target when it was seen. +-- @return #Vec3 lastVel Has effect only if visible is false. Last velocity of the target when it was seen. + + +--- Returns list of detected targets. If one or more detection method is specified the function will return targets which were detected by at least one of these methods. If no detection methods are specified the function will return targets which were detected by any method. +-- @function [parent=#Controller] getDetectedTargets +-- @param self +-- @param #Controller.Detection detection Controller.Detection detection1, Controller.Detection detection2, ... Controller.Detection detectionN +-- @return #list<#DetectedTarget> array of DetectedTarget + +--- Know a target. +-- @function [parent=#Controller] knowTarget +-- @param self +-- @param Object#Object object The target. +-- @param #boolean type Target type is known. +-- @param #boolean distance Distance to target is known. + + +Controller = {} --#Controller \ No newline at end of file diff --git a/DCS/DCSTypes.lua b/DCS/DCSTypes.lua new file mode 100644 index 000000000..1bb654f73 --- /dev/null +++ b/DCS/DCSTypes.lua @@ -0,0 +1,231 @@ + +--- Time is given in seconds. +-- @type Time +-- @extends #number + +--- Model time is the time that drives the simulation. Model time may be stopped, accelerated and decelerated relative real time. +-- @type ModelTime +-- @extends #number + +--- Mission time is a model time plus time of the mission start. +-- @type MissionTime +-- @extends #number + + +--- Distance is given in meters. +-- @type Distance +-- @extends #number + +--- Angle is given in radians. +-- @type Angle +-- @extends #number + +--- Azimuth is an angle of rotation around world axis y counter-clockwise. +-- @type Azimuth +-- @extends #number + +--- Mass is given in kilograms. +-- @type Mass +-- @extends #number + +--- Vec3 type is a 3D-vector. +-- DCS world has 3-dimensional coordinate system. DCS ground is an infinite plain. +-- @type Vec3 +-- @field #Distance x is directed to the north +-- @field #Distance z is directed to the east +-- @field #Distance y is directed up + +--- Vec2 is a 2D-vector for the ground plane as a reference plane. +-- @type Vec2 +-- @field #Distance x Vec2.x = Vec3.x +-- @field #Distance y Vec2.y = Vec3.z + +--- Position is a composite structure. It consists of both coordinate vector and orientation matrix. Position3 (also known as "Pos3" for short) is a table that has following format: +-- @type Position3 +-- @field #Vec3 p +-- @field #Vec3 x +-- @field #Vec3 y +-- @field #Vec3 z + +--- 3-dimensional box. +-- @type Box3 +-- @field #Vec3 min +-- @field #Vec3 max + +--- Each object belongs to a type. Object type is a named couple of properties those independent of mission and common for all units of the same type. Name of unit type is a string. Samples of unit type: "Su-27", "KAMAZ" and "M2 Bradley". +-- @type TypeName +-- @extends #string + + +--- @type AI +-- @field #AI.Skill Skill +-- @field #AI.Task Task +-- @field #AI.Option Option + +--- @type AI.Skill +-- @field AVERAGE +-- @field GOOD +-- @field HIGH +-- @field EXCELLENT +-- @field PLAYER +-- @field CLIENT + +--- @type AI.Task +-- @field #AI.Task.WeaponExpend WeaponExpend +-- @field #AI.Task.OrbitPattern OrbitPattern +-- @field #AI.Task.Designation Designation +-- @field #AI.Task.WaypointType WaypointType +-- @field #AI.Task.TurnMethod TurnMethod +-- @field #AI.Task.AltitudeType AltitudeType +-- @field #AI.Task.VehicleFormation VehicleFormation + +--- @type AI.Task.WeaponExpend +-- @field ONE +-- @field TWO +-- @field FOUR +-- @field QUARTER +-- @field HALF +-- @field ALL + +--- @type AI.Task.OrbitPattern +-- @field CIRCLE +-- @field RACE_TRACK + +--- @type AI.Task.Designation +-- @field NO +-- @field AUTO +-- @field WP +-- @field IR_POINTER +-- @field LASER + +--- @type AI.Task.WaypointType +-- @field TAKEOFF +-- @field TAKEOFF_PARKING +-- @field TURNING_POINT +-- @field LAND + +--- @type AI.Task.TurnMethod +-- @field FLY_OVER_POINT +-- @field FIN_POINT + +--- @type AI.Task.AltitudeType +-- @field BARO +-- @field RADIO + +--- @type AI.Task.VehicleFormation +-- @field OFF_ROAD +-- @field ON_ROAD +-- @field RANK +-- @field CONE +-- @field DIAMOND +-- @field VEE +-- @field ECHELON_LEFT +-- @field ECHELON_RIGHT + +--- @type AI.Option +-- @field #AI.Option.Air Air +-- @field #AI.Option.Ground Ground +-- @field #AI.Option.Naval Naval + +--- @type AI.Option.Air +-- @field #AI.Option.Air.id id +-- @field #AI.Option.Air.val val + +--- @type AI.Option.Ground +-- @field #AI.Option.Ground.id id +-- @field #AI.Option.Ground.val val + +--- @type AI.Option.Naval +-- @field #AI.Option.Naval.id id +-- @field #AI.Option.Naval.val val + +--TODO: work on formation +--- @type AI.Option.Air.id +-- @field NO_OPTION +-- @field ROE +-- @field REACTION_ON_THREAT +-- @field RADAR_USING +-- @field FLARE_USING +-- @field FORMATION +-- @field RTB_ON_BINGO +-- @field SILENCE + +--- @type AI.Option.Air.val +-- @field #AI.Option.Air.val.ROE ROE +-- @field #AI.Option.Air.val.REACTION_ON_THREAT REACTION_ON_THREAT +-- @field #AI.Option.Air.val.RADAR_USING RADAR_USING +-- @field #AI.Option.Air.val.FLARE_USING FLARE_USING + +--- @type AI.Option.Air.val.ROE +-- @field WEAPON_FREE +-- @field OPEN_FIRE_WEAPON_FREE +-- @field OPEN_FIRE +-- @field RETURN_FIRE +-- @field WEAPON_HOLD + +--- @type AI.Option.Air.val.REACTION_ON_THREAT +-- @field NO_REACTION +-- @field PASSIVE_DEFENCE +-- @field EVADE_FIRE +-- @field BYPASS_AND_ESCAPE +-- @field ALLOW_ABORT_MISSION + +--- @type AI.Option.Air.val.RADAR_USING +-- @field NEVER +-- @field FOR_ATTACK_ONLY +-- @field FOR_SEARCH_IF_REQUIRED +-- @field FOR_CONTINUOUS_SEARCH + +--- @type AI.Option.Air.val.FLARE_USING +-- @field NEVER +-- @field AGAINST_FIRED_MISSILE +-- @field WHEN_FLYING_IN_SAM_WEZ +-- @field WHEN_FLYING_NEAR_ENEMIES + +--- @type AI.Option.Ground.id +-- @field NO_OPTION +-- @field ROE @{#AI.Option.Ground.val.ROE} +-- @field DISPERSE_ON_ATTACK true or false +-- @field ALARM_STATE @{#AI.Option.Ground.val.ALARM_STATE} + +--- @type AI.Option.Ground.val +-- @field #AI.Option.Ground.val.ROE ROE +-- @field #AI.Option.Ground.val.ALARM_STATE ALARM_STATE + +--- @type AI.Option.Ground.val.ROE +-- @field OPEN_FIRE +-- @field RETURN_FIRE +-- @field WEAPON_HOLD + +--- @type AI.Option.Ground.val.ALARM_STATE +-- @field AUTO +-- @field GREEN +-- @field RED + +--- @type AI.Option.Naval.id +-- @field NO_OPTION +-- @field ROE + +--- @type AI.Option.Naval.val +-- @field #AI.Option.Naval.val.ROE ROE + +--- @type AI.Option.Naval.val.ROE +-- @field OPEN_FIRE +-- @field RETURN_FIRE +-- @field WEAPON_HOLD + +AI = {} --#AI + + +--- @type Desc +-- @field #TypeName typeName type name +-- @field #string displayName localized display name +-- @field #table attributes object type attributes + +--- A distance type +-- @type Distance + +--- An angle type +-- @type Angle + +env.info( 'AI types created' ) \ No newline at end of file diff --git a/DCS/Group.doclua b/DCS/Group.doclua new file mode 100644 index 000000000..4aeb17653 --- /dev/null +++ b/DCS/Group.doclua @@ -0,0 +1,82 @@ +------------------------------------------------------------------------------- +-- @module Group + + +--- Represents group of Units. +-- @type Group +-- @field #ID ID Identifier of a group. It is assigned to a group by Mission Editor automatically. +-- @field #Group.Category Category Enum contains identifiers of group types. + +--- Enum contains identifiers of group types. +-- @type Group.Category +-- @field AIRPLANE +-- @field HELICOPTER +-- @field GROUND +-- @field SHIP + +-- Static Functions + +--- Returns group by the name assigned to the group in Mission Editor. +-- @function [parent=#Airbase] getByName +-- @param #string name +-- @return #Group + +-- Member Functions + +--- returns true if the group exist or false otherwise. +-- @function [parent=#Airbase] isExist +-- @param self +-- @return #boolean + +--- Destroys the group and all of its units. +-- @function [parent=#Airbase] destroy +-- @param self + +--- Returns category of the group. +-- @function [parent=#Airbase] getCategory +-- @param self +-- @return #Group.Category + +--TODO check coalition.side +--- Returns coalition of the group. +-- @function [parent=#Airbase] getCoalition +-- @param self +-- @return #coalition.side + +--- Returns the group's name. This is the same name assigned to the group in Mission Editor. +-- @function [parent=#Airbase] getName +-- @param self +-- @return #string + +--- Returns the group identifier. +-- @function [parent=#Airbase] getID +-- @param self +-- @return #ID + +--- Returns the unit with number unitNumber. If the unit is not exists the function will return nil. +-- @function [parent=#Airbase] getUnit +-- @param self +-- @param #number unitNumber +-- @return Unit#Unit + +--- Returns current size of the group. If some of the units will be destroyed, As units are destroyed the size of the group will be changed. +-- @function [parent=#Airbase] getSize +-- @param self +-- @return #number + +--- Returns initial size of the group. If some of the units will be destroyed, initial size of the group will not be changed. Initial size limits the unitNumber parameter for Group.getUnit() function. +-- @function [parent=#Airbase] getInitialSize +-- @param self +-- @return #number + +--- Returns array of the units present in the group now. Destroyed units will not be enlisted at all. +-- @function [parent=#Airbase] getUnits +-- @param self +-- @return #list array of Units + +--- Returns controller of the group. +-- @function [parent=#Airbase] getController +-- @param self +-- @return Controller#Controller + +Group = {} --#Group diff --git a/DCS/Object.doclua b/DCS/Object.doclua new file mode 100644 index 000000000..fcd3c2416 --- /dev/null +++ b/DCS/Object.doclua @@ -0,0 +1,73 @@ +------------------------------------------------------------------------------- +-- @module Object + +--- @type Object +-- @field #Object.Category Category +-- @field #Object.Desc Desc + +--- @type Object.Category +-- @field UNIT +-- @field WEAPON +-- @field STATIC +-- @field SCENERY +-- @field BASE + +--- @type Object.Desc +-- @extends #Desc +-- @field #number life initial life level +-- @field #Box3 box bounding box of collision geometry + +--- @function [parent=#Object] isExist +-- @param self +-- @return #boolean + +--- @function [parent=#Object] destroy +-- @param self + +--- @function [parent=#Object] getCategory +-- @param self +-- @return #Object.Category + +--- Returns type name of the Object. +-- @function [parent=#Object] getTypeName +-- @param self +-- @return #string + +--- Returns object descriptor. +-- @function [parent=#Object] getDesc +-- @param self +-- @return #Object.Desc + +--- Returns true if the object belongs to the category. +-- @function [parent=#Object] hasAttribute +-- @param self +-- @param #AttributeName attributeName Attribute name to check. +-- @return #boolean + +--- Returns name of the object. This is the name that is assigned to the object in the Mission Editor. +-- @function [parent=#Object] getName +-- @param self +-- @return #string + +--- Returns object coordinates for current time. +-- @function [parent=#Object] getPoint +-- @param self +-- @return #Vec3 + +--- Returns object position for current time. +-- @function [parent=#Object] getPosition +-- @param self +-- @return #Position3 + +--- Returns the unit's velocity vector. +-- @function [parent=#Object] getVelocity +-- @param self +-- @return #Vec3 + +--- Returns true if the unit is in air. +-- @function [parent=#Object] inAir +-- @param self +-- @return #boolean + +Object = {} --#Object + diff --git a/DCS/StaticObject.doclua b/DCS/StaticObject.doclua new file mode 100644 index 000000000..2aa818548 --- /dev/null +++ b/DCS/StaticObject.doclua @@ -0,0 +1,34 @@ +------------------------------------------------------------------------------- +-- @module StaticObject + + +------------------------------------------------------------------------------- +-- @module StaticObject +-- @extends CoalitionObject#CoalitionObject + +--- Represents static object added in the Mission Editor. +-- @type StaticObject +-- @field #StaticObject.ID ID Identifier of a StaticObject. It assigned to an StaticObject by the Mission Editor automatically. +-- @field #StaticObject.Desc Desc Descriptor of StaticObject and Unit are equal. StaticObject is just a passive variant of Unit. + +--- StaticObject descriptor. Airdromes are unique and their types are unique, but helipads and ships are not always unique and may have the same type. +-- @type StaticObject.Desc +-- @extends Unit#Unit.Desc + +--- Returns static object by its name. If no static object found nil will be returned. +-- @function [parent=#StaticObject] getByName +-- @param #string name Name of static object to find. +-- @return #StaticObject + +--- returns identifier of the static object. +-- @function [parent=#StaticObject] getID +-- @param self +-- @return #StaticObject.ID + +--- Returns descriptor of the StaticObject. +-- @function [parent=#StaticObject] getDesc +-- @param self +-- @return #StaticObject.Desc + + +StaticObject = {} --#StaticObject diff --git a/DCS/Time.doclua b/DCS/Time.doclua new file mode 100644 index 000000000..95b1efcc1 --- /dev/null +++ b/DCS/Time.doclua @@ -0,0 +1,2 @@ +-- @type ModelTime +-- @extends #number diff --git a/DCS/Unit.doclua b/DCS/Unit.doclua new file mode 100644 index 000000000..1e420182e --- /dev/null +++ b/DCS/Unit.doclua @@ -0,0 +1,241 @@ +------------------------------------------------------------------------------- +-- @module Unit +-- @extends CoalitionObject#CoalitionObject + +--- @type Unit +-- @field ID Identifier of an unit. It assigned to an unit by the Mission Editor automatically. +-- @field #Unit.Category Category +-- @field #Unit.RefuelingSystem RefuelingSystem +-- @field #Unit.SensorType SensorType +-- @field #Unit.OpticType OpticType +-- @field #Unit.RadarType RadarType +-- @field #Unit.Desc Desc +-- @field #Unit.DescAircraft DescAircraft +-- @field #Unit.DescAirplane DescAirplane +-- @field #Unit.DescHelicopter DescHelicopter +-- @field #Unit.DescVehicle DescVehicle +-- @field #Unit.DescShip DescShip +-- @field #Unit.AmmoItem AmmoItem +-- @field #list<#Unit.AmmoItem> Ammo +-- @field #Unit.Sensor Sensor +-- @field #Unit.Optic Optic +-- @field #Unit.Radar Radar +-- @field #Unit.IRST IRST + + +--- Enum that stores unit categories. +-- @type Unit.Category +-- @field AIRPLANE +-- @field HELICOPTER +-- @field GROUND_UNIT +-- @field SHIP +-- @field STRUCTURE + +--- Enum that stores aircraft refueling system types. +-- @type Unit.RefuelingSystem +-- @field BOOM_AND_RECEPTACLE +-- @field PROBE_AND_DROGUE + +--- Enum that stores sensor types. +-- @type Unit.SensorType +-- @field OPTIC +-- @field RADAR +-- @field IRST +-- @field RWR + +--- Enum that stores types of optic sensors. +-- @type Unit.OpticType +-- @field TV TV-sensor +-- @field LLTV Low-level TV-sensor +-- @field IR Infra-Red optic sensor + +--- Enum that stores radar types. +-- @type Unit.RadarType +-- @field AS air search radar +-- @field SS surface/land search radar + + +--- A unit descriptor. +-- @type Unit.Desc +-- @extends Object#Object.Desc +-- @field #Unit.Category category Unit Category +-- @field #Mass massEmpty mass of empty unit +-- @field #number speedMax istance / Time, --maximal velocity + +--- An aircraft descriptor. +-- @type Unit.DescAircraft +-- @extends Unit#Unit.Desc +-- @field #Mass fuelMassMax maximal inner fuel mass +-- @field #Distance range Operational range +-- @field #Distance Hmax Ceiling +-- @field #number VyMax #Distance / #Time, --maximal climb rate +-- @field #number NyMin minimal safe acceleration +-- @field #number NyMax maximal safe acceleration +-- @field #Unit.RefuelingSystem tankerType refueling system type + +--- An airplane descriptor. +-- @type Unit.DescAirplane +-- @extends Unit#Unit.DescAircraft +-- @field #number speedMax0 Distance / Time maximal TAS at ground level +-- @field #number speedMax10K Distance / Time maximal TAS at altitude of 10 km + +--- A helicopter descriptor. +-- @type Unit.DescHelicopter +-- @extends Unit#Unit.DescAircraft +-- @field #Distance HmaxStat static ceiling + +--- A vehicle descriptor. +-- @type Unit.DescVehicle +-- @extends Unit#Unit.Desc +-- @field #Angle maxSlopeAngle maximal slope angle +-- @field #boolean riverCrossing can the vehicle cross a rivers + +--- A ship descriptor. +-- @type Unit.DescShip +-- @extends #Unit.Desc + +--- ammunition item: "type-count" pair. +-- @type Unit.AmmoItem +-- @field #Weapon.Desc desc ammunition descriptor +-- @field #number count ammunition count + +--- A unit sensor. +-- @type Unit.Sensor +-- @field #TypeName typeName +-- @field #Unit.SensorType type + +--- An optic sensor. +-- @type Unit.Optic +-- @extends Unit#Unit.Sensor +-- @field #Unit.OpticType opticType + +--- A radar. +-- @type Unit.Radar +-- @extends Unit#Unit.Sensor +-- @field #Distance detectionDistanceRBM detection distance for RCS=1m^2 in real-beam mapping mode, nil if radar doesn't support surface/land search +-- @field #Distance detectionDistanceHRM detection distance for RCS=1m^2 in high-resolution mapping mode, nil if radar has no HRM +-- @field #Unit.Radar.detectionDistanceAir detectionDistanceAir detection distance for RCS=1m^2 airborne target, nil if radar doesn't support air search + +--- @type Unit.Radar.detectionDistanceAir +-- @field #Unit.Radar.detectionDistanceAir.upperHemisphere upperHemisphere +-- @field #Unit.Radar.detectionDistanceAir.lowerHemisphere lowerHemisphere + +--- @type Unit.Radar.detectionDistanceAir.upperHemisphere +-- @field #Distance headOn +-- @field #Distance tailOn + +--- @type Unit.Radar.detectionDistanceAir.lowerHemisphere +-- @field #Distance headOn +-- @field #Distance tailOn + +--- An IRST. +-- @type Unit#Unit.IRST +-- @extends Unit.Sensor +-- @field #Distance detectionDistanceIdle detection of tail-on target with heat signature = 1 in upper hemisphere, engines are in idle +-- @field #Distance detectionDistanceMaximal ..., engines are in maximal mode +-- @field #Distance detectionDistanceAfterburner ..., engines are in afterburner mode + +--- An RWR. +-- @type Unit.RWR +-- @extends Unit#Unit.Sensor + +--- table that stores all unit sensors. +-- TODO @type Sensors +-- + + +--- Returns unit object by the name assigned to the unit in Mission Editor. If there is unit with such name or the unit is destroyed the function will return nil. The function provides access to non-activated units too. +-- @function [parent=#Unit] getByName +-- @param #string name +-- @return #Unit + +--- Returns if the unit is activated. +-- @function [parent=#Unit] isActive +-- @param self +-- @return #boolean + +--- Returns name of the player that control the unit or nil if the unit is controlled by A.I. +-- @function [parent=#Unit] getPlayerName +-- @param self +-- @return #string + +--- returns the unit's unique identifier. +-- @function [parent=#Unit] getID +-- @param self +-- @return #Unit.ID + + +--- Returns the unit's number in the group. The number is the same number the unit has in ME. It may not be changed during the mission. If any unit in the group is destroyed, the numbers of another units will not be changed. +-- @function [parent=#Unit] getNumber +-- @param self +-- @return #number + +--- Returns controller of the unit if it exist and nil otherwise +-- @function [parent=#Unit] getController +-- @param self +-- @return #Controller + +--- Returns the unit's group if it exist and nil otherwise +-- @function [parent=#Unit] getGroup +-- @param self +-- @return Group#Group + +--- Returns the unit's callsign - the localized string. +-- @function [parent=#Unit] getCallsign +-- @param self +-- @return #string + +--- Returns the unit's health. Dead units has health <= 1.0 +-- @function [parent=#Unit] getLife +-- @param self +-- @return #number + +--- returns the unit's initial health. +-- @function [parent=#Unit] getLife0 +-- @param self +-- @return #number + +--- Returns relative amount of fuel (from 0.0 to 1.0) the unit has in its internal tanks. If there are additional fuel tanks the value may be greater than 1.0. +-- @function [parent=#Unit] getFuel +-- @param self +-- @return #number + +--- Returns the unit ammunition. +-- @function [parent=#Unit] getAmmo +-- @param self +-- @return #Unit.Ammo + +--- Returns the unit sensors. +-- @function [parent=#Unit] getSensors +-- @param self +-- @return #Unit.Sensors + +--- Returns true if the unit has specified types of sensors. This function is more preferable than Unit.getSensors() if you don't want to get information about all the unit's sensors, and just want to check if the unit has specified types of sensors. +-- @function [parent=#Unit] hasSensors +-- @param self +-- @param #Unit.SensorType sensorType (= nil) Sensor type. +-- @param ... Additional parameters. +-- @return #boolean +-- @usage +-- If sensorType is Unit.SensorType.OPTIC, additional parameters are optic sensor types. Following example checks if the unit has LLTV or IR optics: +-- unit:hasSensors(Unit.SensorType.OPTIC, Unit.OpticType.LLTV, Unit.OpticType.IR) +-- If sensorType is Unit.SensorType.RADAR, additional parameters are radar types. Following example checks if the unit has air search radars: +-- unit:hasSensors(Unit.SensorType.RADAR, Unit.RadarType.AS) +-- If no additional parameters are specified the function returns true if the unit has at least one sensor of specified type. +-- If sensor type is not specified the function returns true if the unit has at least one sensor of any type. +-- + +--- returns two values: +-- First value indicates if at least one of the unit's radar(s) is on. +-- Second value is the object of the radar's interest. Not nil only if at least one radar of the unit is tracking a target. +-- @function [parent=#Unit] getRadar +-- @param self +-- @return #boolean, Object#Object + +--- Returns unit descriptor. Descriptor type depends on unit category. +-- @function [parent=#Unit] getDesc +-- @param self +-- @return Unit#Unit.Desc + + +Unit = {} --#Unit diff --git a/DCS/env.doclua b/DCS/env.doclua new file mode 100644 index 000000000..c6fe98776 --- /dev/null +++ b/DCS/env.doclua @@ -0,0 +1,27 @@ +------------------------------------------------------------------------------- +-- @module env + +--- @type env + +--- Add message to simulator log with caption "INFO". Message box is optional. +-- @function [parent=#env] info +-- @field #string message message string to add to log. +-- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. + +--- Add message to simulator log with caption "WARNING". Message box is optional. +-- @function [parent=#env] warning +-- @field #string message message string to add to log. +-- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. + +--- Add message to simulator log with caption "ERROR". Message box is optional. +-- @function [parent=#env] error +-- @field #string message message string to add to log. +-- @field #boolean showMessageBox If the parameter is true Message Box will appear. Optional. + +--- Enables/disables appearance of message box each time lua error occurs. +-- @function [parent=#env] setErrorMessageBoxEnabled +-- @field #boolean on if true message box appearance is enabled. + + + +env = {} --#env diff --git a/DCS/timer.doclua b/DCS/timer.doclua new file mode 100644 index 000000000..56127eb4a --- /dev/null +++ b/DCS/timer.doclua @@ -0,0 +1,45 @@ +------------------------------------------------------------------------------- +-- @module timer + +--- @type timer + + +--- Returns model time in seconds. +-- @function [parent=#timer] getTime +-- @return #Time + +--- Returns mission time in seconds. +-- @function [parent=#timer] getAbsTime +-- @return #Time + +--- Returns mission start time in seconds. +-- @function [parent=#timer] getTime0 +-- @return #Time + +--- Schedules function to call at desired model time. +-- Time function FunctionToCall(any argument, Time time) +-- +-- ... +-- +-- return ... +-- +-- end +-- +-- Must return model time of next call or nil. Note that the DCS scheduler calls the function in protected mode and any Lua errors in the called function will be trapped and not reported. If the function triggers a Lua error then it will be terminated and not scheduled to run again. +-- @function [parent=#timer] scheduleFunction +-- @param #FunctionToCall functionToCall Lua-function to call. Must have prototype of FunctionToCall. +-- @param functionArgument Function argument of any type to pass to functionToCall. +-- @param #Time time Model time of the function call. +-- @return functionId + +--- Re-schedules function to call at another model time. +-- @function [parent=#timer] setFunctionTime +-- @param functionId Lua-function to call. Must have prototype of FunctionToCall. +-- @param #Time time Model time of the function call. + + +--- Removes the function from schedule. +-- @function [parent=#timer] removeFunction +-- @param functionId Function identifier to remove from schedule + +timer = {} --#timer diff --git a/Embedded/Moose_Embedded.lua b/Embedded/Moose_Embedded.lua index 73d4157cb..cb2282790 100644 --- a/Embedded/Moose_Embedded.lua +++ b/Embedded/Moose_Embedded.lua @@ -5287,7 +5287,7 @@ function CARGO_GROUP:Spawn( Client ) elseif self:IsStatusLoading() then local Client = self:IsLoadingToClient() - if Client and Client:ClientGroup() then + if Client and Client:GetDCSGroup() then SpawnCargo = false else local CargoGroup = Group.getByName( self.CargoName ) @@ -5301,7 +5301,7 @@ function CARGO_GROUP:Spawn( Client ) local ClientLoaded = self:IsLoadedInClient() -- Now test if another Client is alive (not this one), and it has the CARGO, then this cargo does not need to be initialized and spawned. if ClientLoaded and ClientLoaded ~= Client then - local ClientGroup = Client:ClientGroup() + local ClientGroup = Client:GetDCSGroup() if ClientLoaded:GetClientGroupDCSUnit() and ClientLoaded:GetClientGroupDCSUnit():isExist() then SpawnCargo = false else @@ -5487,7 +5487,7 @@ function CARGO_PACKAGE:Spawn( Client ) -- this needs to be checked thoroughly - local CargoClientGroup = self.CargoClient:ClientGroup() + local CargoClientGroup = self.CargoClient:GetDCSGroup() if not CargoClientGroup then if not self.CargoClientSpawn then self.CargoClientSpawn = SPAWN:New( self.CargoClient:GetClientGroupName() ):Limit( 1, 1 ) @@ -5502,7 +5502,7 @@ function CARGO_PACKAGE:Spawn( Client ) elseif self:IsStatusLoading() or self:IsStatusLoaded() then local CargoClientLoaded = self:IsLoadedInClient() - if CargoClientLoaded and CargoClientLoaded:ClientGroup() then + if CargoClientLoaded and CargoClientLoaded:GetDCSGroup() then SpawnCargo = false end @@ -5527,7 +5527,7 @@ self:T() local Near = false - if self.CargoClient and self.CargoClient:ClientGroup() then + if self.CargoClient and self.CargoClient:GetDCSGroup() then self:T( self.CargoClient.ClientName ) self:T( 'Client Exists.' ) @@ -5553,8 +5553,8 @@ self:T() local CarrierPosOnBoard = ClientUnit:getPoint() local CarrierPosMoveAway = ClientUnit:getPoint() - local CargoHostGroup = self.CargoClient:ClientGroup() - local CargoHostName = self.CargoClient:ClientGroup():getName() + local CargoHostGroup = self.CargoClient:GetDCSGroup() + local CargoHostName = self.CargoClient:GetDCSGroup():getName() local CargoHostUnits = CargoHostGroup:getUnits() local CargoPos = CargoHostUnits[1]:getPoint() @@ -5637,7 +5637,7 @@ self:T() local OnBoarded = false - if self.CargoClient and self.CargoClient:ClientGroup() then + if self.CargoClient and self.CargoClient:GetDCSGroup() then if routines.IsUnitInRadius( self.CargoClient:GetClientGroupDCSUnit(), self.CargoClient:ClientPosition(), 10 ) then -- Switch Cargo from self.CargoClient to Client ... Each cargo can have only one client. So assigning the new client for the cargo is enough. @@ -5658,7 +5658,7 @@ self:T() self:T( 'self.CargoName = ' .. self.CargoName ) --self:T( 'self.CargoHostName = ' .. self.CargoHostName ) - --self.CargoSpawn:FromCarrier( Client:ClientGroup(), TargetZoneName, self.CargoHostName ) + --self.CargoSpawn:FromCarrier( Client:GetDCSGroup(), TargetZoneName, self.CargoHostName ) self:StatusUnLoaded() return Cargo @@ -5883,7 +5883,7 @@ end --- ClientGroup returns the Group of a Client. -- This function is modified to deal with a couple of bugs in DCS 1.5.3 -- @return Group -function CLIENT:ClientGroup() +function CLIENT:GetDCSGroup() --self:T() -- local ClientData = Group.getByName( self.ClientName ) @@ -5955,7 +5955,7 @@ end function CLIENT:GetClientGroupID() self:T() - ClientGroup = self:ClientGroup() + ClientGroup = self:GetDCSGroup() if ClientGroup then if ClientGroup:isExist() then @@ -5972,7 +5972,7 @@ end function CLIENT:GetClientGroupName() self:T() - ClientGroup = self:ClientGroup() + ClientGroup = self:GetDCSGroup() if ClientGroup then if ClientGroup:isExist() then @@ -5992,7 +5992,7 @@ end function CLIENT:GetClientGroupUnit() self:T() - local ClientGroup = self:ClientGroup() + local ClientGroup = self:GetDCSGroup() if ClientGroup then if ClientGroup:isExist() then @@ -6010,7 +6010,7 @@ end function CLIENT:GetClientGroupDCSUnit() self:T() - local ClientGroup = self:ClientGroup() + local ClientGroup = self:GetDCSGroup() if ClientGroup then if ClientGroup:isExist() then @@ -8483,7 +8483,7 @@ function MISSION:ReportToAll() local AlivePlayers = '' for ClientID, Client in pairs( self._Clients ) do - if Client:ClientGroup() then + if Client:GetDCSGroup() then if Client:GetClientGroupDCSUnit() then if Client:GetClientGroupDCSUnit():getLife() > 0.0 then if AlivePlayers == '' then @@ -8716,7 +8716,7 @@ trace.scheduled("MISSIONSCHEDULER","Scheduler") trace.i( "MISSIONSCHEDULER", "Client: " .. Client.ClientName ) - if Client:ClientGroup() then + if Client:GetDCSGroup() then -- There is at least one Client that is alive... So the Mission status is set to Ongoing. ClientsAlive = true diff --git a/Moose/Base.lua b/Moose/Base.lua index 3a1a56dc2..bc06b56de 100644 --- a/Moose/Base.lua +++ b/Moose/Base.lua @@ -13,15 +13,17 @@ _TraceClass = { --SPAWN = true, --STAGE = true, --ZONE = true, - --GROUP = true, + GROUP = true, --UNIT = true, - --CLIENT = true, + CLIENT = true, --CARGO = true, --CARGO_GROUP = true, --CARGO_PACKAGE = true, --CARGO_SLINGLOAD = true, --CARGO_ZONE = true, --CLEANUP = true, + MENU_SUB_GROUP = true, + MENU_COMMAND_GROUP = true, ESCORT = true, } @@ -247,13 +249,14 @@ function BASE:T( Arguments ) end local LineCurrent = DebugInfoCurrent.currentline - local LineFrom = DebugInfoFrom.currentline - + local LineFrom = 0 + if DebugInfoFrom then + LineFrom = DebugInfoFrom.currentline + end env.info( string.format( "%6d\(%6d\)/%1s:%20s%05d.%s\(%s\)" , LineCurrent, LineFrom, "T", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) end end - -- Log an exception function BASE:E( Arguments ) @@ -270,3 +273,6 @@ function BASE:E( Arguments ) env.info( string.format( "%6d\(%6d\)/%1s:%20s%05d.%s\(%s\)" , LineCurrent, LineFrom, "E", self.ClassName, self.ClassID, Function, routines.utils.oneLineSerialize( Arguments ) ) ) end + + + diff --git a/Moose/Cargo.lua b/Moose/Cargo.lua index c243f17ad..812cacf7f 100644 --- a/Moose/Cargo.lua +++ b/Moose/Cargo.lua @@ -480,7 +480,7 @@ function CARGO_GROUP:Spawn( Client ) elseif self:IsStatusLoading() then local Client = self:IsLoadingToClient() - if Client and Client:ClientGroup() then + if Client and Client:GetDCSGroup() then SpawnCargo = false else local CargoGroup = Group.getByName( self.CargoName ) @@ -494,7 +494,7 @@ function CARGO_GROUP:Spawn( Client ) local ClientLoaded = self:IsLoadedInClient() -- Now test if another Client is alive (not this one), and it has the CARGO, then this cargo does not need to be initialized and spawned. if ClientLoaded and ClientLoaded ~= Client then - local ClientGroup = Client:ClientGroup() + local ClientGroup = Client:GetDCSGroup() if ClientLoaded:GetClientGroupDCSUnit() and ClientLoaded:GetClientGroupDCSUnit():isExist() then SpawnCargo = false else @@ -680,7 +680,7 @@ function CARGO_PACKAGE:Spawn( Client ) -- this needs to be checked thoroughly - local CargoClientGroup = self.CargoClient:ClientGroup() + local CargoClientGroup = self.CargoClient:GetDCSGroup() if not CargoClientGroup then if not self.CargoClientSpawn then self.CargoClientSpawn = SPAWN:New( self.CargoClient:GetClientGroupName() ):Limit( 1, 1 ) @@ -695,7 +695,7 @@ function CARGO_PACKAGE:Spawn( Client ) elseif self:IsStatusLoading() or self:IsStatusLoaded() then local CargoClientLoaded = self:IsLoadedInClient() - if CargoClientLoaded and CargoClientLoaded:ClientGroup() then + if CargoClientLoaded and CargoClientLoaded:GetDCSGroup() then SpawnCargo = false end @@ -720,7 +720,7 @@ self:T() local Near = false - if self.CargoClient and self.CargoClient:ClientGroup() then + if self.CargoClient and self.CargoClient:GetDCSGroup() then self:T( self.CargoClient.ClientName ) self:T( 'Client Exists.' ) @@ -746,8 +746,8 @@ self:T() local CarrierPosOnBoard = ClientUnit:getPoint() local CarrierPosMoveAway = ClientUnit:getPoint() - local CargoHostGroup = self.CargoClient:ClientGroup() - local CargoHostName = self.CargoClient:ClientGroup():getName() + local CargoHostGroup = self.CargoClient:GetDCSGroup() + local CargoHostName = self.CargoClient:GetDCSGroup():getName() local CargoHostUnits = CargoHostGroup:getUnits() local CargoPos = CargoHostUnits[1]:getPoint() @@ -830,7 +830,7 @@ self:T() local OnBoarded = false - if self.CargoClient and self.CargoClient:ClientGroup() then + if self.CargoClient and self.CargoClient:GetDCSGroup() then if routines.IsUnitInRadius( self.CargoClient:GetClientGroupDCSUnit(), self.CargoClient:ClientPosition(), 10 ) then -- Switch Cargo from self.CargoClient to Client ... Each cargo can have only one client. So assigning the new client for the cargo is enough. @@ -851,7 +851,7 @@ self:T() self:T( 'self.CargoName = ' .. self.CargoName ) --self:T( 'self.CargoHostName = ' .. self.CargoHostName ) - --self.CargoSpawn:FromCarrier( Client:ClientGroup(), TargetZoneName, self.CargoHostName ) + --self.CargoSpawn:FromCarrier( Client:GetDCSGroup(), TargetZoneName, self.CargoHostName ) self:StatusUnLoaded() return Cargo diff --git a/Moose/Client.lua b/Moose/Client.lua index 332bce256..ff1c58ad8 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -46,7 +46,7 @@ CLIENT = { -- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() ) function CLIENT:New( ClientName, ClientBriefing ) local self = BASE:Inherit( self, BASE:New() ) - self:T() + self:T( ClientName, ClientBriefing ) self.ClientName = ClientName self:AddBriefing( ClientBriefing ) @@ -62,10 +62,22 @@ self:T() self._Menus = {} end ---- ClientGroup returns the Group of a Client. +--- Return the Group of a Client. -- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @return Group -function CLIENT:ClientGroup() +-- @return #GROUP +function CLIENT:GetGroup() + + if not self.ClientGroup then + self.ClientGroup = GROUP:New( self:GetDCSGroup() ) + end + + return self.ClientGroup +end + +--- Return the DCSGroup of a Client. +-- This function is modified to deal with a couple of bugs in DCS 1.5.3 +-- @return DCSGroup +function CLIENT:GetDCSGroup() --self:T() -- local ClientData = Group.getByName( self.ClientName ) @@ -102,7 +114,7 @@ function CLIENT:ClientGroup() self:T( { tonumber(UnitData:getID()), ClientUnitData.unitId } ) if tonumber(UnitData:getID()) == ClientUnitData.unitId then local ClientGroupTemplate = _Database.Groups[self.ClientName].Template - self.ClientGroupID = ClientGroupTemplate.groupId + self.ClientID = ClientGroupTemplate.groupId self.ClientGroupUnit = UnitData self:T( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) return ClientGroup @@ -127,7 +139,7 @@ function CLIENT:ClientGroup() end end - self.ClientGroupID = nil + self.ClientID = nil self.ClientGroupUnit = nil return nil @@ -135,46 +147,48 @@ end function CLIENT:GetClientGroupID() -self:T() - ClientGroup = self:ClientGroup() - - if ClientGroup then - if ClientGroup:isExist() then - return ClientGroup:getID() - else - return self.ClientGroupID - end - end - - return nil + if not self.ClientID then + if not self.ClientGroup then + self.ClientGroup = GROUP:New( self:GetDCSGroup() ) + end + if self.ClientGroup:IsAlive() then + self.ClientGroupID = self.ClientGroup:GetID() + else + self.ClientGroupID = self.ClientID + self.ClientGroup.GroupID = self.ClientID + end + end + + self:T( self.ClientGroupID ) + return self.ClientGroupID end function CLIENT:GetClientGroupName() -self:T() - ClientGroup = self:ClientGroup() - - if ClientGroup then - if ClientGroup:isExist() then - self:T( ClientGroup:getName() ) - return ClientGroup:getName() - else - self:T( self.ClientName ) - return self.ClientName - end - end - - return nil + if not self.ClientGroupName then + if not self.ClientGroup then + self.ClientGroup = GROUP:New( self:GetDCSGroup() ) + end + if self.ClientGroup:IsAlive() then + self.ClientGroupName = self.ClientGroup:GetName() + else + self.ClientGroupName = self.ClientName + self.ClientGroup.GroupName = self.ClientGroupName + end + end + + self:T( self.ClientGroupName ) + return self.ClientGroupName end --- Returns the Unit of the @{CLIENT}. -- @return Unit function CLIENT:GetClientGroupUnit() -self:T() + self:T() - local ClientGroup = self:ClientGroup() + local ClientGroup = self:GetDCSGroup() if ClientGroup then if ClientGroup:isExist() then @@ -192,7 +206,7 @@ end function CLIENT:GetClientGroupDCSUnit() self:T() - local ClientGroup = self:ClientGroup() + local ClientGroup = self:GetDCSGroup() if ClientGroup then if ClientGroup:isExist() then @@ -291,9 +305,9 @@ self:T() if not self.MenuMessages then if self:GetClientGroupID() then - self.MenuMessages = MENU_SUB_GROUP:New( self:GetClientGroupID(), 'Messages' ) - self.MenuRouteMessageOn = MENU_COMMAND_GROUP:New( self:GetClientGroupID(), 'Messages On', self.MenuMessages, CLIENT.SwitchMessages, { self, true } ) - self.MenuRouteMessageOff = MENU_COMMAND_GROUP:New( self:GetClientGroupID(),'Messages Off', self.MenuMessages, CLIENT.SwitchMessages, { self, false } ) + self.MenuMessages = MENU_SUB_GROUP:New( self:GetGroup(), 'Messages' ) + self.MenuRouteMessageOn = MENU_COMMAND_GROUP:New( self:GetGroup(), 'Messages On', self.MenuMessages, CLIENT.SwitchMessages, { self, true } ) + self.MenuRouteMessageOff = MENU_COMMAND_GROUP:New( self:GetGroup(),'Messages Off', self.MenuMessages, CLIENT.SwitchMessages, { self, false } ) end end @@ -307,7 +321,7 @@ self:T() self.Messages[MessageId].MessageTime = timer.getTime() self.Messages[MessageId].MessageDuration = MessageDuration if MessageInterval == nil then - self.Messages[MessageId].MessageInterval = 0 + self.Messages[MessageId].MessageInterval = 600 else self.Messages[MessageId].MessageInterval = MessageInterval end diff --git a/Moose/Escort.lua b/Moose/Escort.lua index a9fbd3722..13e6d3458 100644 --- a/Moose/Escort.lua +++ b/Moose/Escort.lua @@ -39,12 +39,112 @@ function ESCORT:New( EscortClient, EscortGroup, EscortName ) self.EscortClient = EscortClient self.EscortGroup = EscortGroup self.EscortName = EscortName + self.ReportTargets = true + + -- Escort Navigation + self.EscortMenu = MENU_SUB_GROUP:New( self.EscortClient:GetGroup(), "Escort" .. self.EscortName ) + self.EscortMenuHoldPosition = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Hold Position and Stay Low", self.EscortMenu, ESCORT._HoldPosition, { ParamSelf = self } ) + + -- Report Targets + self.EscortMenuReportNearbyTargets = MENU_SUB_GROUP:New( self.EscortClient:GetGroup(), "Report Targets", self.EscortMenu ) + self.EscortMenuReportNearbyTargetsOn = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Report Targets On", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargets, { ParamSelf = self, ParamReportTargets = true } ) + self.EscortMenuReportNearbyTargetsOff = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Report Targets Off", self.EscortMenuReportNearbyTargets, ESCORT._ReportNearbyTargets, { ParamSelf = self, ParamReportTargets = false, } ) + + -- Attack Targets + self.EscortMenuAttackNearbyTargets = MENU_SUB_GROUP:New( self.EscortClient:GetGroup(), "Attack nearby targets", self.EscortMenu ) + self.EscortMenuAttackTargets = {} + self.Targets = {} + + -- Rules of Engagement + self.EscortMenuROE = MENU_SUB_GROUP:New( self.EscortClient:GetGroup(), "ROE", self.EscortMenu ) + self.EscortMenuROEHoldFire = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Hold Fire", self.EscortMenuROE, ESCORT._ROEHoldFire, { ParamSelf = self, } ) + self.EscortMenuROEReturnFire = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Return Fire", self.EscortMenuROE, ESCORT._ROEReturnFire, { ParamSelf = self, } ) + self.EscortMenuROEOpenFire = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Open Fire", self.EscortMenuROE, ESCORT._ROEOpenFire, { ParamSelf = self, } ) + self.EscortMenuROEWeaponFree = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Weapon Free", self.EscortMenuROE, ESCORT._ROEWeaponFree, { ParamSelf = self, } ) - self.ScanForTargetsFunction = routines.scheduleFunction( self._ScanForTargets, { self }, timer.getTime() + 1, 10 ) + -- Reaction to Threats + self.EscortMenuEvasion = MENU_SUB_GROUP:New( self.EscortClient:GetGroup(), "Evasion", self.EscortMenu ) + self.EscortMenuEvasionNoReaction = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Fight until death", self.EscortMenuEvasion, ESCORT._EvasionNoReaction, { ParamSelf = self, } ) + self.EscortMenuEvasionPassiveDefense = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Use flares, chaff and jammers", self.EscortMenuEvasion, ESCORT._EvasionPassiveDefense, { ParamSelf = self, } ) + self.EscortMenuEvasionEvadeFire = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Evade enemy fire", self.EscortMenuEvasion, ESCORT._EvasionEvadeFire, { ParamSelf = self, } ) + self.EscortMenuEvasionVertical = MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), "Go below radar and evade fire", self.EscortMenuEvasion, ESCORT._EvasionVertical, { ParamSelf = self, } ) + + + self.ScanForTargetsFunction = routines.scheduleFunction( self._ScanForTargets, { self }, timer.getTime() + 1, 30 ) end +function ESCORT._HoldPosition( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:HoldPosition( 300 ) + MESSAGE:New( "Holding Position at ... for 5 minutes.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/HoldPosition" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._ReportNearbyTargets( MenuParam ) + MenuParam.ParamSelf:T() + + MenuParam.ParamSelf.ReportTargets = MenuParam.ParamReportTargets + +end + +function ESCORT._AttackTarget( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:AttackUnit( MenuParam.ParamUnit ) + MESSAGE:New( "Attacking Unit", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._ROEHoldFire( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:HoldFire() + MESSAGE:New( "Holding weapons.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._ROEReturnFire( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:ReturnFire() + MESSAGE:New( "Returning enemy fire.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._ROEOpenFire( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:OpenFire() + MESSAGE:New( "Open fire on ordered targets.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._ROEWeaponFree( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:WeaponFree() + MESSAGE:New( "Engaging targets.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._EvasionNoReaction( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:EvasionNoReaction() + MESSAGE:New( "We'll fight until death.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._EvasionPassiveDefense( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:EvasionPassiveDefense() + MESSAGE:New( "We will use flares, chaff and jammers.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._EvasionEvadeFire( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:EvasionEvadeFire() + MESSAGE:New( "We'll evade enemy fire.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + +function ESCORT._EvasionVertical( MenuParam ) + + MenuParam.ParamSelf.EscortGroup:EvasionVertical() + MESSAGE:New( "We'll perform vertical evasive manoeuvres.", MenuParam.ParamSelf.EscortName, 10, "ESCORT/AttackUnit" ):ToClient( MenuParam.ParamSelf.EscortClient ) +end + + function ESCORT:_ScanForTargets() self:T() + + self.Targets = {} if self.EscortGroup:IsAlive() then local EscortTargets = self.EscortGroup:GetDetectedTargets() @@ -85,12 +185,25 @@ function ESCORT:_ScanForTargets() local EscortPositionVec3 = self.EscortGroup:GetPositionVec3() local Distance = routines.utils.get3DDist( EscortTargetUnitPositionVec3, EscortPositionVec3 ) / 1000 self:T( { self.EscortGroup:GetName(), EscortTargetUnit:GetName(), Distance, EscortTarget.visible } ) + if Distance <= 8 then - EscortTargetMessage = EscortTargetMessage .. " - " .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") " - EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" - if EscortTarget.visible then - EscortTargetMessage = EscortTargetMessage .. " visual contact" + + if EscortTarget.type then + EscortTargetMessage = EscortTargetMessage .. " - " .. EscortTargetCategoryName .. " (" .. EscortTargetCategoryType .. ") at " + else + EscortTargetMessage = EscortTargetMessage .. " - Unknown target at " end + + EscortTargetMessage = EscortTargetMessage .. string.format( "%.2f", Distance ) .. " km" + + if EscortTarget.visible then + EscortTargetMessage = EscortTargetMessage .. ", visual" + end + + local TargetIndex = Distance*1000 + self.Targets[TargetIndex] = {} + self.Targets[TargetIndex].AttackMessage = EscortTargetMessage + self.Targets[TargetIndex].AttackUnit = EscortTargetUnit end end @@ -100,9 +213,41 @@ function ESCORT:_ScanForTargets() end end - if EscortTargetMessages ~= "" then - self.EscortClient:Message( EscortTargetMessages:gsub("\n$",""), 20, "/ESCORT.DetectedTargets", self.EscortName .. " reporting detected targets within 8 km range:" ) + if EscortTargetMessages ~= "" and self.ReportTargets == true then + self.EscortClient:Message( EscortTargetMessages:gsub("\n$",""), 20, "/ESCORT.DetectedTargets", self.EscortName .. " reporting detected targets within 8 km range:", 0 ) end + + self:T() + + self:T( { "Sorting Targets Table:", self.Targets } ) + table.sort( self.Targets ) + self:T( { "Sorted Targets Table:", self.Targets } ) + + for MenuIndex = 1, #self.EscortMenuAttackTargets do + self:T( { "Remove Menu:", self.EscortMenuAttackTargets[MenuIndex] } ) + self.EscortMenuAttackTargets[MenuIndex] = self.EscortMenuAttackTargets[MenuIndex]:Remove() + end + + local MenuIndex = 1 + for TargetID, TargetData in pairs( self.Targets ) do + self:T( { "Adding menu:", TargetID, "for Unit", self.Targets[TargetID].AttackUnit } ) + if MenuIndex <= 10 then + self.EscortMenuAttackTargets[MenuIndex] = + MENU_COMMAND_GROUP:New( self.EscortClient:GetGroup(), + self.Targets[TargetID].AttackMessage, + self.EscortMenuAttackNearbyTargets, + ESCORT._AttackTarget, + { ParamSelf = self, + ParamUnit = self.Targets[TargetID].AttackUnit + } + ) + self:T( { "New Menu:", self.EscortMenuAttackTargets[TargetID] } ) + MenuIndex = MenuIndex + 1 + else + break + end + end + else routines.removeFunction( self.ScanForTargetsFunction ) end diff --git a/Moose/Group.lua b/Moose/Group.lua index d1768fd21..dd75f356a 100644 --- a/Moose/Group.lua +++ b/Moose/Group.lua @@ -35,12 +35,16 @@ GROUPS = {} -- @return #GROUP self function GROUP:New( DCSGroup ) local self = BASE:Inherit( self, BASE:New() ) - self:T( DCSGroup:getName() ) + self:T( DCSGroup ) self.DCSGroup = DCSGroup - self.GroupName = DCSGroup:getName() - self.GroupID = DCSGroup:getID() - self.Controller = DCSGroup:getController() + if self.DCSGroup and self.DCSGroup:isExist() then + self.GroupName = DCSGroup:getName() + self.GroupID = DCSGroup:getID() + self.Controller = DCSGroup:getController() + else + self:E( { "DCSGroup is nil or does not exist, cannot initialize GROUP!", self.DCSGroup } ) + end return self end @@ -111,6 +115,15 @@ function GROUP:Activate() return self:GetDCSGroup() end +--- Gets the ID of the GROUP. +-- @param self +-- @return #number The ID of the GROUP. +function GROUP:GetID() + self:T( self.GroupName ) + + return self.GroupID +end + --- Gets the name of the GROUP. -- @param self -- @return #string The name of the GROUP. @@ -253,6 +266,39 @@ self:T() end +--- Hold position at the current position of the first unit of the group. +-- @param self +-- @param #number Duration The maximum duration in seconds to hold the position. +-- @return #GROUP self +function GROUP:HoldPosition( Duration ) +trace.f( self.ClassName, { self.GroupName, Duration } ) + + local Controller = self:_GetController() + +-- pattern = enum AI.Task.OribtPattern, +-- point = Vec2, +-- point2 = Vec2, +-- speed = Distance, +-- altitude = Distance + + local GroupPoint = self:GetPoint() + --id = 'Orbit', params = { pattern = AI.Task.OrbitPattern.RACE_TRACK } }, stopCondition = { duration = 600 } } + Controller:pushTask( { id = 'ControlledTask', + params = { task = { id = 'Orbit', + params = { pattern = AI.Task.OrbitPattern.CIRCLE, + point = GroupPoint, + speed = 0, + altitude = 30 + } + }, + stopCondition = { duration = Duration + } + } + } + ) + return self +end + --- Land the group at a Vec2Point. -- @param self -- @param #Vec2 Point The point where to land. @@ -272,6 +318,137 @@ trace.f( self.ClassName, { self.GroupName, Point, Duration } ) return self end +--- Attack the Unit. +-- @param self +-- @param #UNIT The unit. +-- @return #GROUP self +function GROUP:AttackUnit( AttackUnit ) + self:T( { self.GroupName, AttackUnit } ) + + local Controller = self:_GetController() + +-- AttackUnit = { +-- id = 'AttackUnit', +-- params = { +-- unitId = Unit.ID, +-- weaponType = number, +-- expend = enum AI.Task.WeaponExpend +-- attackQty = number, +-- direction = Azimuth, +-- attackQtyLimit = boolean, +-- groupAttack = boolean, +-- } +-- } + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) + + Controller:pushTask( { id = 'AttackUnit', + params = { unitId = AttackUnit:GetID(), + expend = AI.Task.WeaponExpend.TWO, + groupAttack = true, + } + } + ) + return self +end + +--- Holding weapons. +-- @param self +-- @return #GROUP self +function GROUP:HoldFire() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPONS_HOLD ) + return self +end + +--- Return fire. +-- @param self +-- @return #GROUP self +function GROUP:ReturnFire() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.RETURN_FIRE ) + return self +end + +--- Openfire. +-- @param self +-- @return #GROUP self +function GROUP:OpenFire() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.OPEN_FIRE ) + return self +end + +--- Weapon free. +-- @param self +-- @return #GROUP self +function GROUP:WeaponFree() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.ROE, AI.Option.Air.val.ROE.WEAPON_FREE ) + return self +end + +--- No evasion on enemy threats. +-- @param self +-- @return #GROUP self +function GROUP:EvasionNoReaction() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.NO_REACTION ) + return self +end + +--- Evasion passive defense. +-- @param self +-- @return #GROUP self +function GROUP:EvasionPassiveDefense() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.PASSIVE_DEFENSE ) + return self +end + +--- Evade fire. +-- @param self +-- @return #GROUP self +function GROUP:EvasionEvadeFire() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.EVADE_FIRE ) + return self +end + +--- Vertical manoeuvres. +-- @param self +-- @return #GROUP self +function GROUP:EvasionVertical() + self:T( { self.GroupName } ) + + local Controller = self:_GetController() + + Controller:setOption( AI.Option.Air.id.REACTION_ON_THREAT, AI.Option.Air.val.REACTION_ON_THREAT.BYPASS_AND_ESCAPE ) + return self +end + + --- Move the group to a Vec2 Point, wait for a defined duration and embark a group. -- @param self -- @param #Vec2 Point The point where to wait. diff --git a/Moose/Menu.lua b/Moose/Menu.lua index a8d980568..bd8e54b8a 100644 --- a/Moose/Menu.lua +++ b/Moose/Menu.lua @@ -76,7 +76,13 @@ MENU_SUB_GROUP = { ClassName = "MENU_SUB_GROUP" } -function MENU_SUB_GROUP:New( GroupID, MenuText, ParentMenu ) +--- Creates a new menu item for a group +-- @param self +-- @param MenuGroup The group owning the menu. +-- @param MenuText The text for the menu. +-- @param ParentMenu The parent menu. +-- @return #MENU_SUB_GROUP self +function MENU_SUB_GROUP:New( MenuGroup, MenuText, ParentMenu ) -- Arrange meta tables local MenuParentPath = nil @@ -86,7 +92,10 @@ function MENU_SUB_GROUP:New( GroupID, MenuText, ParentMenu ) local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) - self.MenuPath = missionCommands.addSubMenuForGroup( GroupID, MenuText, MenuParentPath ) + self:T( { MenuGroup, MenuText, ParentMenu } ) + + self.MenuGroup = MenuGroup + self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuGroup:GetID(), MenuText, MenuParentPath ) return self end @@ -96,7 +105,15 @@ MENU_COMMAND_GROUP = { ClassName = "MENU_COMMAND_GROUP" } -function MENU_COMMAND_GROUP:New( GroupID, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) +--- Creates a new radio command item for a group +-- @param self +-- @param MenuGroup The group owning the menu. +-- @param MenuText The text for the menu. +-- @param ParentMenu The parent menu. +-- @param CommandMenuFunction A function that is called when the menu key is pressed. +-- @param CommandMenuArgument An argument for the function. +-- @return #MENU_COMMAND_GROUP self +function MENU_COMMAND_GROUP:New( MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument ) -- Arrange meta tables @@ -106,9 +123,18 @@ function MENU_COMMAND_GROUP:New( GroupID, MenuText, ParentMenu, CommandMenuFunct end local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) ) + + self:T( { MenuGroup, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument } ) - self.MenuPath = missionCommands.addCommandForGroup( GroupID, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) + self.MenuGroup = MenuGroup + self.MenuPath = missionCommands.addCommandForGroup( self.MenuGroup:GetID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument ) self.CommandMenuFunction = CommandMenuFunction self.CommandMenuArgument = CommandMenuArgument return self end + +function MENU_COMMAND_GROUP:Remove() + + missionCommands.removeItemForGroup( self.MenuGroup:GetID(), self.MenuPath ) + return nil +end diff --git a/Moose/Message.lua b/Moose/Message.lua index d17c07053..7c5f581f5 100644 --- a/Moose/Message.lua +++ b/Moose/Message.lua @@ -21,11 +21,12 @@ MESSAGE = { --- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients. --- @param string MessageText is the text of the Message. --- @param string MessageCategory is a string expressing the Category of the Message. Messages are grouped on the display panel per Category to improve readability. --- @param number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. --- @param string MessageID is a string expressing the ID of the Message. --- @return MESSAGE +-- @param self +-- @param #string MessageText is the text of the Message. +-- @param #string MessageCategory is a string expressing the Category of the Message. Messages are grouped on the display panel per Category to improve readability. +-- @param #number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel. +-- @param #string MessageID is a string expressing the ID of the Message. +-- @return #MESSAGE -- @usage -- -- Create a series of new Messages. -- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score". diff --git a/Moose/Mission.lua b/Moose/Mission.lua index 273b7755b..7e2eb696b 100644 --- a/Moose/Mission.lua +++ b/Moose/Mission.lua @@ -174,7 +174,7 @@ function MISSION:ReportToAll() local AlivePlayers = '' for ClientID, Client in pairs( self._Clients ) do - if Client:ClientGroup() then + if Client:GetDCSGroup() then if Client:GetClientGroupDCSUnit() then if Client:GetClientGroupDCSUnit():getLife() > 0.0 then if AlivePlayers == '' then @@ -407,7 +407,7 @@ trace.scheduled("MISSIONSCHEDULER","Scheduler") trace.i( "MISSIONSCHEDULER", "Client: " .. Client.ClientName ) - if Client:ClientGroup() then + if Client:GetDCSGroup() then -- There is at least one Client that is alive... So the Mission status is set to Ongoing. ClientsAlive = true