The #BASE class is the super class for most of the classes defined within MOOSE.
+
+
It handles:
+
+
+
The construction and inheritance of child classes.
+
The tracing of objects during mission execution within the DCS.log file (under saved games folder).
+
+
+
Note: Normally you would not use the BASE class unless you are extending the MOOSE framework with new classes.
+
+
BASE Trace functionality
+
The BASE class contains trace methods to trace progress within a mission execution of a certain object.
+Note that these trace methods are inherited by each MOOSE class interiting BASE.
+As such, each object created from derived class from BASE can use the tracing functions to trace its execution.
+
+
Trace a function call
+
There are basically 3 types of tracing methods available within BASE:
+
+
+
BASE.F: Trace the beginning of a function and its given parameters.
+
BASE.T: Trace further logic within a function giving optional variables or parameters.
+
BASE.E: Trace an execption within a function giving optional variables or parameters. An exception will always be traced.
+
+
+
Tracing levels
+
There are 3 tracing levels within MOOSE.
+These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects.
+
+
As such, the F and T methods have additional variants to trace level 2 and 3 respectively:
+
+
+
BASE.F2: Trace the beginning of a function and its given parameters with tracing level 2.
+
BASE.F3: Trace the beginning of a function and its given parameters with tracing level 3.
+
BASE.T2: Trace further logic within a function giving optional variables or parameters with tracing level 2.
+
BASE.T3: Trace further logic within a function giving optional variables or parameters with tracing level 3.
+
+
+
BASE Inheritance support
+
The following methods are available to support inheritance:
Mission designers can use the DATABASE class to build sets of units belonging to certain:
+
+
+
Coalitions
+
Categories
+
Countries
+
Unit types
+
Starting with certain prefix strings.
+
+
+
This list will grow over time. Planned developments are to include filters and iterators.
+Additional filters will be added around Zone#ZONEs, Radiuses, Active players, ...
+More iterators will be implemented in the near future ...
+
+
Administers the Initial Sets of the Mission Templates as defined within the Mission Editor.
+
+
DATABASE construction methods:
+
Create a new DATABASE object with the DATABASE.New method:
Once the filters have been defined and the DATABASE has been built, you can iterate the database with the available iterator methods.
+The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide.
+The following iterator methods are currently available within the DATABASE:
Mission designers can use the DATABASE class to build sets of units belonging to certain:
-
-
-
Coalitions
-
Categories
-
Countries
-
Unit types
-
Starting with certain prefix strings.
-
-
-
This list will grow over time. Planned developments are to include filters and iterators.
- Additional filters will be added around Zone#ZONEs, Radiuses, Active players, ...
- More iterators will be implemented in the near future ...
-
-
DATABASE construction methods:
-
Create a new DATABASE object with the DATABASE.New method:
Once the filters have been defined and the DATABASE has been built, you can iterate the database with the available iterator methods.
-The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide.
-The following iterator methods are currently available within the DATABASE:
The #ESCORT class allows you to interact with escorting AI on your flight and take the lead.
Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10).
The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes.
Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
+
RADIO MENUs that can be created:
Find a summary below of the current available commands:
-
1. Navigation ...: Escort group navigation functions:
+
Navigation ...:
+
Escort group navigation functions:
"Join-Up and Follow at x meters": The escort group fill follow you at about x meters, and they will follow you.
@@ -84,14 +88,16 @@ Ships and Ground troops will have a more limited set, but they can provide suppo
"Smoke": Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
-
2. Hold position ...: Escort group navigation functions:
+
Hold position ...:
+
Escort group navigation functions:
"At current location": Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
"At client location": Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
-
3. Report targets ...: Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
+
Report targets ...:
+
Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
"Report now": Will report the current detected targets.
@@ -99,21 +105,25 @@ Ships and Ground troops will have a more limited set, but they can provide suppo
"Report targets off": Will stop detecting targets.
-
4. Scan targets ...: Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
+
Scan targets ...:
+
Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
"Scan targets 30 seconds": Scan 30 seconds for targets.
"Scan targets 60 seconds": Scan 60 seconds for targets.
-
5. Attack targets ...: This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
+
Attack targets ...:
+
This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
-
6. Request assistance from ...: This menu item will list all detected targets within a 15km range, as with the menu item Attack Targets.
+
Request assistance from ...:
+
This menu item will list all detected targets within a 15km range, as with the menu item Attack Targets.
This menu item allows to request attack support from other escorts supporting the current client group.
eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
-
7. ROE ...: Defines the Rules of Engagement of the escort group when in flight.
+
ROE ...:
+
Sets the Rules of Engagement (ROE) of the escort group when in flight.
"Hold Fire": The escort group will hold fire.
@@ -122,7 +132,8 @@ eg. the function allows a player to request support from other Planes escorting
"Weapon Free": The escort group will engage with any target.
-
8. Evasion ...: Will define the evasion techniques that the escort group will perform during flight or combat.
+
Evasion ...:
+
Will define the evasion techniques that the escort group will perform during flight or combat.
"Fight until death": The escort group will have no reaction to threats.
@@ -131,27 +142,31 @@ eg. the function allows a player to request support from other Planes escorting
"Go below radar and evade fire": The escort group will perform evasive vertical manoeuvres.
-
9. Resume Mission ...: Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
+
Resume Mission ...:
+
Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
-
10. Abort Current Task: Cancel the current task and rejoin formation.
+
ESCORT construction methods.
+
Create a new SPAWN object with the ESCORT.New method:
-
-
ESCORT object construction methods.
-
Create a new SPAWN object with the ESCORT.New method:
The SPAWN class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
+
+
The #SPAWN class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the "Spawn Template" of the SPAWN object.
A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods.
@@ -142,6 +144,7 @@ In such a case, when the inactive group is cleaned, a new group will Re-spawned
This models AI that has succesfully returned to their airbase, to restart their combat activities.
Check the SPAWN.CleanUp for further info.
+
diff --git a/Embedded/Moose_Create_Embedded.bat b/Embedded/Moose_Create_Embedded.bat
index f434c274b..c4e58833c 100644
--- a/Embedded/Moose_Create_Embedded.bat
+++ b/Embedded/Moose_Create_Embedded.bat
@@ -5,10 +5,13 @@ echo env.info( 'Moose Embedded' ) > Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Routines.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Base.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Event.lua Moose_Embedded.lua
+copy /b Moose_Embedded.lua + ..\Moose\Menu.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Group.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Unit.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Zone.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Database.lua Moose_Embedded.lua
+copy /b Moose_Embedded.lua + ..\Moose\Moose.lua Moose_Embedded.lua
+copy /b Moose_Embedded.lua + ..\Moose\Scheduler.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Scoring.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Cargo.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Client.lua Moose_Embedded.lua
@@ -30,3 +33,4 @@ copy /b Moose_Embedded.lua + ..\Moose\Spawn.lua Moose_Embedded.l
copy /b Moose_Embedded.lua + ..\Moose\Movement.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Sead.lua Moose_Embedded.lua
copy /b Moose_Embedded.lua + ..\Moose\Escort.lua Moose_Embedded.lua
+copy /b Moose_Embedded.lua + ..\Moose\MissileTrainer.lua Moose_Embedded.lua
diff --git a/Embedded/Moose_Embedded.lua b/Embedded/Moose_Embedded.lua
index 91d90b24f..2ceb43472 100644
--- a/Embedded/Moose_Embedded.lua
+++ b/Embedded/Moose_Embedded.lua
@@ -450,14 +450,14 @@ end
do
-- THE MAIN FUNCTION -- Accessed 100 times/sec.
routines.main = function()
- timer.scheduleFunction(routines.main, {}, timer.getTime() + 0.1) --reschedule first in case of Lua error
+ timer.scheduleFunction(routines.main, {}, timer.getTime() + 2) --reschedule first in case of Lua error
----------------------------------------------------------------------------------------------------------
--area to add new stuff in
routines.do_scheduled_functions()
end -- end of routines.main
- timer.scheduleFunction(routines.main, {}, timer.getTime() + 0.1)
+ timer.scheduleFunction(routines.main, {}, timer.getTime() + 2)
end
@@ -1525,6 +1525,47 @@ function routines.IsUnitInZones( TransportUnit, LandingZones )
end
end
+function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius )
+--trace.f("", "routines.IsUnitInZones" )
+
+ local TransportZoneResult = nil
+ local TransportZonePos = nil
+ local TransportZone = nil
+
+ -- fill-up some local variables to support further calculations to determine location of units within the zone.
+ if TransportUnit then
+ local TransportUnitPos = TransportUnit:getPosition().p
+ if type( LandingZones ) == "table" then
+ for LandingZoneID, LandingZoneName in pairs( LandingZones ) do
+ TransportZone = trigger.misc.getZone( LandingZoneName )
+ if TransportZone then
+ TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z}
+ if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= ZoneRadius ) then
+ TransportZoneResult = LandingZoneID
+ break
+ end
+ end
+ end
+ else
+ TransportZone = trigger.misc.getZone( LandingZones )
+ TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z}
+ if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= ZoneRadius ) then
+ TransportZoneResult = 1
+ end
+ end
+ if TransportZoneResult then
+ --trace.i( "routines", "TransportZone:" .. TransportZoneResult )
+ else
+ --trace.i( "routines", "TransportZone:nil logic" )
+ end
+ return TransportZoneResult
+ else
+ --trace.i( "routines", "TransportZone:nil hard" )
+ return nil
+ end
+end
+
+
function routines.IsStaticInZones( TransportStatic, LandingZones )
--trace.f()
@@ -2578,7 +2619,58 @@ end
env.info(( 'Init: Scripts Loaded v1.1' ))
---- The BASE class for all the classes defined within MOOSE.
+--- BASE classes.
+--
+-- @{#BASE} class
+-- ==============
+-- The @{#BASE} class is the super class for most of the classes defined within MOOSE.
+--
+-- It handles:
+--
+-- * The construction and inheritance of child classes.
+-- * The tracing of objects during mission execution within the DCS.log file (under saved games folder).
+--
+-- Note: Normally you would not use the BASE class unless you are extending the MOOSE framework with new classes.
+--
+-- BASE Trace functionality
+-- ========================
+-- The BASE class contains trace methods to trace progress within a mission execution of a certain object.
+-- Note that these trace methods are inherited by each MOOSE class interiting BASE.
+-- As such, each object created from derived class from BASE can use the tracing functions to trace its execution.
+--
+-- Trace a function call
+-- ---------------------
+-- There are basically 3 types of tracing methods available within BASE:
+--
+-- * @{#BASE.F}: Trace the beginning of a function and its given parameters.
+-- * @{#BASE.T}: Trace further logic within a function giving optional variables or parameters.
+-- * @{#BASE.E}: Trace an execption within a function giving optional variables or parameters. An exception will always be traced.
+--
+-- Tracing levels
+-- --------------
+-- There are 3 tracing levels within MOOSE.
+-- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects.
+--
+-- As such, the F and T methods have additional variants to trace level 2 and 3 respectively:
+--
+-- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2.
+-- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3.
+-- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2.
+-- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3.
+--
+-- BASE Inheritance support
+-- ========================
+-- The following methods are available to support inheritance:
+--
+-- * @{#BASE.Inherit}: Inherits from a class.
+-- * @{#BASE.Inherited}: Returns the parent class from the class.
+--
+-- Future
+-- ======
+-- Further methods may be added to BASE whenever there is a need to make "overall" functions available within MOOSE.
+--
+-- ====
+--
-- @module Base
-- @author FlightControl
@@ -2882,6 +2974,7 @@ end
-- @param #number Level
function BASE:TraceLevel( Level )
_TraceLevel = Level
+ self:E( "Tracing level " .. Level )
end
--- Set tracing for a class
@@ -2890,6 +2983,7 @@ end
function BASE:TraceClass( Class )
_TraceClass[Class] = true
_TraceClassMethod[Class] = {}
+ self:E( "Tracing class " .. Class )
end
--- Set tracing for a specific method of class
@@ -2902,6 +2996,7 @@ function BASE:TraceClassMethod( Class, Method )
_TraceClassMethod[Class].Method = {}
end
_TraceClassMethod[Class].Method[Method] = true
+ self:E( "Tracing method " .. Method .. " of class " .. Class )
end
--- Trace a function call. Must be at the beginning of the function logic.
@@ -3505,6 +3600,377 @@ function EVENT:onEvent( Event )
end
end
+--- Encapsulation of DCS World Menu system in a set of MENU classes.
+-- @module Menu
+
+Include.File( "Routines" )
+Include.File( "Base" )
+
+--- The MENU class
+-- @type MENU
+-- @extends Base#BASE
+MENU = {
+ ClassName = "MENU",
+ MenuPath = nil,
+ MenuText = "",
+ MenuParentPath = nil
+}
+
+---
+function MENU:New( MenuText, MenuParentPath )
+
+ -- Arrange meta tables
+ local Child = BASE:Inherit( self, BASE:New() )
+
+ Child.MenuPath = nil
+ Child.MenuText = MenuText
+ Child.MenuParentPath = MenuParentPath
+ return Child
+end
+
+--- The COMMANDMENU class
+-- @type COMMANDMENU
+-- @extends Menu#MENU
+COMMANDMENU = {
+ ClassName = "COMMANDMENU",
+ CommandMenuFunction = nil,
+ CommandMenuArgument = nil
+}
+
+function COMMANDMENU:New( MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument )
+
+ -- Arrange meta tables
+
+ local MenuParentPath = nil
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local Child = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+
+ Child.MenuPath = missionCommands.addCommand( MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument )
+ Child.CommandMenuFunction = CommandMenuFunction
+ Child.CommandMenuArgument = CommandMenuArgument
+ return Child
+end
+
+--- The SUBMENU class
+-- @type SUBMENU
+-- @extends Menu#MENU
+SUBMENU = {
+ ClassName = "SUBMENU"
+}
+
+function SUBMENU:New( MenuText, ParentMenu )
+
+ -- Arrange meta tables
+ local MenuParentPath = nil
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local Child = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+
+ Child.MenuPath = missionCommands.addSubMenu( MenuText, MenuParentPath )
+ return Child
+end
+
+-- This local variable is used to cache the menus registered under clients.
+-- Menus don't dissapear when clients are destroyed and restarted.
+-- So every menu for a client created must be tracked so that program logic accidentally does not create
+-- the same menus twice during initialization logic.
+-- These menu classes are handling this logic with this variable.
+local _MENUCLIENTS = {}
+
+--- The MENU_CLIENT class
+-- @type MENU_CLIENT
+-- @extends Menu#MENU
+MENU_CLIENT = {
+ ClassName = "MENU_CLIENT"
+}
+
+--- Creates a new menu item for a group
+-- @param self
+-- @param Client#CLIENT MenuClient The Client owning the menu.
+-- @param #string MenuText The text for the menu.
+-- @param #table ParentMenu The parent menu.
+-- @return #MENU_CLIENT self
+function MENU_CLIENT:New( MenuClient, MenuText, ParentMenu )
+
+ -- Arrange meta tables
+ local MenuParentPath = {}
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+ self:F( { MenuClient, MenuText, ParentMenu } )
+
+ self.MenuClient = MenuClient
+ self.MenuClientGroupID = MenuClient:GetClientGroupID()
+ self.MenuParentPath = MenuParentPath
+ self.MenuText = MenuText
+ self.ParentMenu = ParentMenu
+
+ self.Menus = {}
+
+ if not _MENUCLIENTS[self.MenuClientGroupID] then
+ _MENUCLIENTS[self.MenuClientGroupID] = {}
+ end
+
+ local MenuPath = _MENUCLIENTS[self.MenuClientGroupID]
+
+ self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText } )
+
+ local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText
+ if MenuPath[MenuPathID] then
+ missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] )
+ end
+
+ self.MenuPath = missionCommands.addSubMenuForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath )
+ MenuPath[MenuPathID] = self.MenuPath
+
+ self:T( { MenuClient:GetClientGroupName(), self.MenuPath } )
+
+ if ParentMenu and ParentMenu.Menus then
+ ParentMenu.Menus[self.MenuPath] = self
+ end
+ return self
+end
+
+--- Removes the sub menus recursively of this MENU_CLIENT.
+-- @param #MENU_CLIENT self
+-- @return #MENU_CLIENT self
+function MENU_CLIENT:RemoveSubMenus()
+ self:F( self.MenuPath )
+
+ for MenuID, Menu in pairs( self.Menus ) do
+ Menu:Remove()
+ end
+
+end
+
+--- Removes the sub menus recursively of this MENU_CLIENT.
+-- @param #MENU_CLIENT self
+-- @return #MENU_CLIENT self
+function MENU_CLIENT:Remove()
+ self:F( self.MenuPath )
+
+ self:RemoveSubMenus()
+
+ if not _MENUCLIENTS[self.MenuClientGroupID] then
+ _MENUCLIENTS[self.MenuClientGroupID] = {}
+ end
+
+ local MenuPath = _MENUCLIENTS[self.MenuClientGroupID]
+
+ if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then
+ MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil
+ end
+
+ missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath )
+ self.ParentMenu.Menus[self.MenuPath] = nil
+ return nil
+end
+
+
+--- The MENU_CLIENT_COMMAND class
+-- @type MENU_CLIENT_COMMAND
+-- @extends Menu#MENU
+MENU_CLIENT_COMMAND = {
+ ClassName = "MENU_CLIENT_COMMAND"
+}
+
+--- Creates a new radio command item for a group
+-- @param self
+-- @param Client#CLIENT MenuClient The Client 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#MENU_CLIENT_COMMAND self
+function MENU_CLIENT_COMMAND:New( MenuClient, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument )
+
+ -- Arrange meta tables
+
+ local MenuParentPath = {}
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+
+ self.MenuClient = MenuClient
+ self.MenuClientGroupID = MenuClient:GetClientGroupID()
+ self.MenuParentPath = MenuParentPath
+ self.MenuText = MenuText
+ self.ParentMenu = ParentMenu
+
+ if not _MENUCLIENTS[self.MenuClientGroupID] then
+ _MENUCLIENTS[self.MenuClientGroupID] = {}
+ end
+
+ local MenuPath = _MENUCLIENTS[self.MenuClientGroupID]
+
+ self:T( { MenuClient:GetClientGroupName(), MenuPath[table.concat(MenuParentPath)], MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } )
+
+ local MenuPathID = table.concat(MenuParentPath) .. "/" .. MenuText
+ if MenuPath[MenuPathID] then
+ missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), MenuPath[MenuPathID] )
+ end
+
+ self.MenuPath = missionCommands.addCommandForGroup( self.MenuClient:GetClientGroupID(), MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument )
+ MenuPath[MenuPathID] = self.MenuPath
+
+ self.CommandMenuFunction = CommandMenuFunction
+ self.CommandMenuArgument = CommandMenuArgument
+
+ ParentMenu.Menus[self.MenuPath] = self
+
+ return self
+end
+
+function MENU_CLIENT_COMMAND:Remove()
+ self:F( self.MenuPath )
+
+ if not _MENUCLIENTS[self.MenuClientGroupID] then
+ _MENUCLIENTS[self.MenuClientGroupID] = {}
+ end
+
+ local MenuPath = _MENUCLIENTS[self.MenuClientGroupID]
+
+ if MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] then
+ MenuPath[table.concat(self.MenuParentPath) .. "/" .. self.MenuText] = nil
+ end
+
+ missionCommands.removeItemForGroup( self.MenuClient:GetClientGroupID(), self.MenuPath )
+ self.ParentMenu.Menus[self.MenuPath] = nil
+ return nil
+end
+
+
+--- The MENU_COALITION class
+-- @type MENU_COALITION
+-- @extends Menu#MENU
+MENU_COALITION = {
+ ClassName = "MENU_COALITION"
+}
+
+--- Creates a new coalition menu item
+-- @param #MENU_COALITION self
+-- @param DCSCoalition#coalition.side MenuCoalition The coalition owning the menu.
+-- @param #string MenuText The text for the menu.
+-- @param #table ParentMenu The parent menu.
+-- @return #MENU_COALITION self
+function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu )
+
+ -- Arrange meta tables
+ local MenuParentPath = {}
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+ self:F( { MenuCoalition, MenuText, ParentMenu } )
+
+ self.MenuCoalition = MenuCoalition
+ self.MenuParentPath = MenuParentPath
+ self.MenuText = MenuText
+ self.ParentMenu = ParentMenu
+
+ self.Menus = {}
+
+ self:T( { MenuParentPath, MenuText } )
+
+ self.MenuPath = missionCommands.addSubMenuForCoalition( self.MenuCoalition, MenuText, MenuParentPath )
+
+ self:T( { self.MenuPath } )
+
+ if ParentMenu and ParentMenu.Menus then
+ ParentMenu.Menus[self.MenuPath] = self
+ end
+ return self
+end
+
+--- Removes the sub menus recursively of this MENU_COALITION.
+-- @param #MENU_COALITION self
+-- @return #MENU_COALITION self
+function MENU_COALITION:RemoveSubMenus()
+ self:F( self.MenuPath )
+
+ for MenuID, Menu in pairs( self.Menus ) do
+ Menu:Remove()
+ end
+
+end
+
+--- Removes the sub menus recursively of this MENU_COALITION.
+-- @param #MENU_COALITION self
+-- @return #MENU_COALITION self
+function MENU_COALITION:Remove()
+ self:F( self.MenuPath )
+
+ self:RemoveSubMenus()
+ missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath )
+ self.ParentMenu.Menus[self.MenuPath] = nil
+
+ return nil
+end
+
+
+--- The MENU_COALITION_COMMAND class
+-- @type MENU_COALITION_COMMAND
+-- @extends Menu#MENU
+MENU_COALITION_COMMAND = {
+ ClassName = "MENU_COALITION_COMMAND"
+}
+
+--- Creates a new radio command item for a group
+-- @param #MENU_COALITION_COMMAND self
+-- @param DCSCoalition#coalition.side MenuCoalition The coalition 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_COALITION_COMMAND self
+function MENU_COALITION_COMMAND:New( MenuCoalition, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument )
+
+ -- Arrange meta tables
+
+ local MenuParentPath = {}
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+
+ self.MenuCoalition = MenuCoalition
+ self.MenuParentPath = MenuParentPath
+ self.MenuText = MenuText
+ self.ParentMenu = ParentMenu
+
+ self:T( { MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } )
+
+ self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument )
+
+ self.CommandMenuFunction = CommandMenuFunction
+ self.CommandMenuArgument = CommandMenuArgument
+
+ ParentMenu.Menus[self.MenuPath] = self
+
+ return self
+end
+
+--- Removes a radio command item for a coalition
+-- @param #MENU_COALITION_COMMAND self
+-- @return #MENU_COALITION_COMMAND self
+function MENU_COALITION_COMMAND:Remove()
+ self:F( self.MenuPath )
+
+ missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath )
+ self.ParentMenu.Menus[self.MenuPath] = nil
+ return nil
+end
--- A GROUP class abstraction of a DCSGroup class.
-- The GROUP class will take an abstraction of the DCSGroup class, providing more methods that can be done with a GROUP.
-- @module Group
@@ -3599,6 +4065,18 @@ function GROUP:NewFromDCSUnit( DCSUnit )
return self
end
+--- Returns the name of the Group.
+-- @param #GROUP self
+-- @return #string GroupName
+function GROUP:GetName()
+
+ local GroupName = self.DCSGroup:getName()
+
+ return GroupName
+end
+
+
+
--- Retrieve the group mission and allow to place function hooks within the mission waypoint plan.
-- Use the method @{Group#GROUP:WayPointFunction} to define the hook functions for specific waypoints.
-- Use the method @{Group@GROUP:WayPointExecute) to start the execution of the new mission plan.
@@ -3635,7 +4113,12 @@ function GROUP:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionAr
local DCSScript = {}
DCSScript[#DCSScript+1] = "local MissionGroup = GROUP.FindGroup( ... ) "
- DCSScript[#DCSScript+1] = FunctionString .. "( MissionGroup, " .. table.concat( FunctionArguments, "," ) .. ")"
+
+ if FunctionArguments.n > 0 then
+ DCSScript[#DCSScript+1] = FunctionString .. "( MissionGroup, " .. table.concat( FunctionArguments, "," ) .. ")"
+ else
+ DCSScript[#DCSScript+1] = FunctionString .. "( MissionGroup )"
+ end
DCSTask = self:TaskWrappedAction(
self:CommandDoScript(
@@ -4101,6 +4584,41 @@ function GROUP:TaskWrappedAction( DCSCommand, Index )
return DCSTaskWrappedAction
end
+--- Executes a command action
+-- @param #GROUP self
+-- @param DCSCommand#Command DCSCommand
+-- @return #GROUP self
+function GROUP:SetCommand( DCSCommand )
+ self:F( DCSCommand )
+
+ local Controller = self:_GetController()
+
+ Controller:setCommand( DCSCommand )
+
+ return self
+end
+
+--- Perform a switch waypoint command
+-- @param #GROUP self
+-- @param #number FromWayPoint
+-- @param #number ToWayPoint
+-- @return DCSTask#Task
+function GROUP:CommandSwitchWayPoint( FromWayPoint, ToWayPoint, Index )
+ self:F( { FromWayPoint, ToWayPoint, Index } )
+
+ local CommandSwitchWayPoint = {
+ id = 'SwitchWaypoint',
+ params = {
+ fromWaypointIndex = FromWayPoint,
+ goToWaypointIndex = ToWayPoint,
+ },
+ }
+
+ self:T( { CommandSwitchWayPoint } )
+ return CommandSwitchWayPoint
+end
+
+
--- Orbit at a specified position at a specified alititude during a specified duration with a specified speed.
-- @param #GROUP self
-- @param #Vec2 Point The point to hold the position.
@@ -4108,7 +4626,7 @@ end
-- @param #number Speed The speed flying when holding the position.
-- @return #GROUP self
function GROUP:TaskOrbitCircleAtVec2( Point, Altitude, Speed )
- self:F( { self.GroupName, Point, Altitude, Speed } )
+ self:F( { self.GroupName, Point, Altitude, Speed } )
-- pattern = enum AI.Task.OribtPattern,
-- point = Vec2,
@@ -4239,8 +4757,41 @@ function GROUP:TaskAttackUnit( AttackUnit )
params = { unitId = AttackUnit:GetID(),
expend = AI.Task.WeaponExpend.TWO,
groupAttack = true,
- }
- }
+ },
+ },
+
+ self:T( { DCSTask } )
+ return DCSTask
+end
+
+--- Attack a Group.
+-- @param #GROUP self
+-- @param Group#GROUP AttackGroup The Group to be attacked.
+-- @return DCSTask#Task The DCS task structure.
+function GROUP:TaskAttackGroup( AttackGroup )
+ self:F( { self.GroupName, AttackGroup } )
+
+-- AttackGroup = {
+-- id = 'AttackGroup',
+-- params = {
+-- groupId = Group.ID,
+-- weaponType = number,
+-- expend = enum AI.Task.WeaponExpend,
+-- attackQty = number,
+-- directionEnabled = boolean,
+-- direction = Azimuth,
+-- altitudeEnabled = boolean,
+-- altitude = Distance,
+-- attackQtyLimit = boolean,
+-- }
+-- }
+
+ local DCSTask
+ DCSTask = { id = 'AttackGroup',
+ params = { groupId = AttackGroup:GetID(),
+ expend = AI.Task.WeaponExpend.TWO,
+ },
+ },
self:T( { DCSTask } )
return DCSTask
@@ -5212,15 +5763,27 @@ function ZONE:GetPointVec2()
return Point
end
+function ZONE:GetPointVec3( Height )
+ self:F( self.ZoneName )
+
+ local Zone = trigger.misc.getZone( self.ZoneName )
+ local Point = { x = Zone.point.x, y = land.getHeight( self:GetPointVec2() ) + Height, z = Zone.point.z }
+
+ self:T( { Zone, Point } )
+
+ return Point
+end
+
function ZONE:GetRandomPointVec2()
self:F( self.ZoneName )
local Point = {}
local Zone = trigger.misc.getZone( self.ZoneName )
-
- Point.x = Zone.point.x + math.random( Zone.radius * -1, Zone.radius )
- Point.y = Zone.point.z + math.random( Zone.radius * -1, Zone.radius )
+
+ local angle = math.random() * math.pi*2;
+ Point.x = Zone.point.x + math.cos( angle ) * math.random() * Zone.radius;
+ Point.y = Zone.point.z + math.sin( angle ) * math.random() * Zone.radius;
self:T( { Zone, Point } )
@@ -5237,8 +5800,67 @@ function ZONE:GetRadius()
return Zone.radius
end
---- Administers the Initial Sets of the Mission Templates as defined within the Mission Editor.
--- Administers the Spawning of new Groups within the DCSRTE and administers these new Groups within the DATABASE object(s).
+--- Manage sets of units and groups.
+--
+-- @{#Database} class
+-- ==================
+-- Mission designers can use the DATABASE class to build sets of units belonging to certain:
+--
+-- * Coalitions
+-- * Categories
+-- * Countries
+-- * Unit types
+-- * Starting with certain prefix strings.
+--
+-- This list will grow over time. Planned developments are to include filters and iterators.
+-- Additional filters will be added around @{Zone#ZONEs}, Radiuses, Active players, ...
+-- More iterators will be implemented in the near future ...
+--
+-- Administers the Initial Sets of the Mission Templates as defined within the Mission Editor.
+--
+-- DATABASE construction methods:
+-- =================================
+-- Create a new DATABASE object with the @{#DATABASE.New} method:
+--
+-- * @{#DATABASE.New}: Creates a new DATABASE object.
+--
+--
+-- DATABASE filter criteria:
+-- =========================
+-- You can set filter criteria to define the set of units within the database.
+-- Filter criteria are defined by:
+--
+-- * @{#DATABASE.FilterCoalitions}: Builds the DATABASE with the units belonging to the coalition(s).
+-- * @{#DATABASE.FilterCategories}: Builds the DATABASE with the units belonging to the category(ies).
+-- * @{#DATABASE.FilterTypes}: Builds the DATABASE with the units belonging to the unit type(s).
+-- * @{#DATABASE.FilterCountries}: Builds the DATABASE with the units belonging to the country(ies).
+-- * @{#DATABASE.FilterUnitPrefixes}: Builds the DATABASE with the units starting with the same prefix string(s).
+--
+-- Once the filter criteria have been set for the DATABASE, you can start filtering using:
+--
+-- * @{#DATABASE.FilterStart}: Starts the filtering of the units within the database.
+--
+-- Planned filter criteria within development are (so these are not yet available):
+--
+-- * @{#DATABASE.FilterGroupPrefixes}: Builds the DATABASE with the groups of the units starting with the same prefix string(s).
+-- * @{#DATABASE.FilterZones}: Builds the DATABASE with the units within a @{Zone#ZONE}.
+--
+--
+-- DATABASE iterators:
+-- ===================
+-- Once the filters have been defined and the DATABASE has been built, you can iterate the database with the available iterator methods.
+-- The iterator methods will walk the DATABASE set, and call for each element within the set a function that you provide.
+-- The following iterator methods are currently available within the DATABASE:
+--
+-- * @{#DATABASE.ForEachAliveUnit}: Calls a function for each alive unit it finds within the DATABASE.
+--
+-- Planned iterators methods in development are (so these are not yet available):
+--
+-- * @{#DATABASE.ForEachUnit}: Calls a function for each unit contained within the DATABASE.
+-- * @{#DATABASE.ForEachGroup}: Calls a function for each group contained within the DATABASE.
+-- * @{#DATABASE.ForEachUnitInZone}: Calls a function for each unit within a certain zone contained within the DATABASE.
+--
+-- ====
-- @module Database
-- @author FlightControl
@@ -5248,7 +5870,7 @@ Include.File( "Menu" )
Include.File( "Group" )
Include.File( "Event" )
---- The DATABASE class
+--- DATABASE class
-- @type DATABASE
-- @extends Base#BASE
DATABASE = {
@@ -5328,7 +5950,7 @@ end
-- @param #DATABASE self
-- @param #string Coalitions Can take the following values: "red", "blue", "neutral".
-- @return #DATABASE self
-function DATABASE:FilterCoalition( Coalitions )
+function DATABASE:FilterCoalitions( Coalitions )
if not self.Filter.Coalitions then
self.Filter.Coalitions = {}
end
@@ -5346,7 +5968,7 @@ end
-- @param #DATABASE self
-- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship".
-- @return #DATABASE self
-function DATABASE:FilterCategory( Categories )
+function DATABASE:FilterCategories( Categories )
if not self.Filter.Categories then
self.Filter.Categories = {}
end
@@ -5364,7 +5986,7 @@ end
-- @param #DATABASE self
-- @param #string Types Can take those type strings known within DCS world.
-- @return #DATABASE self
-function DATABASE:FilterType( Types )
+function DATABASE:FilterTypes( Types )
if not self.Filter.Types then
self.Filter.Types = {}
end
@@ -5400,7 +6022,7 @@ end
-- @param #DATABASE self
-- @param #string Prefixes The prefix of which the unit name starts with.
-- @return #DATABASE self
-function DATABASE:FilterUnitPrefix( Prefixes )
+function DATABASE:FilterUnitPrefixes( Prefixes )
if not self.Filter.UnitPrefixes then
self.Filter.UnitPrefixes = {}
end
@@ -5418,7 +6040,7 @@ end
-- @param #DATABASE self
-- @param #string Prefixes The prefix of which the group name where the unit belongs to starts with.
-- @return #DATABASE self
-function DATABASE:FilterGroupPrefix( Prefixes )
+function DATABASE:FilterGroupPrefixes( Prefixes )
if not self.Filter.GroupPrefixes then
self.Filter.GroupPrefixes = {}
end
@@ -5443,10 +6065,10 @@ function DATABASE:FilterStart()
for UnitRegistrationID, UnitRegistration in pairs( _DATABASE.Units ) do
self:T( UnitRegistration )
local DCSUnit = Unit.getByName( UnitRegistration.UnitName )
- if self:IsIncludeDCSUnit( DCSUnit ) then
+ if self:_IsIncludeDCSUnit( DCSUnit ) then
self.DCSUnits[DCSUnit:getName()] = DCSUnit
end
- if self:IsAliveDCSUnit( DCSUnit ) then
+ if self:_IsAliveDCSUnit( DCSUnit ) then
self.DCSUnitsAlive[DCSUnit:getName()] = DCSUnit
end
end
@@ -5457,90 +6079,173 @@ function DATABASE:FilterStart()
return self
end
----
+
+--- Instantiate new Groups within the DCSRTE.
+-- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined:
+-- SpawnCountryID, SpawnCategoryID
+-- This method is used by the SPAWN class.
-- @param #DATABASE self
--- @param DCSUnit#Unit DCSUnit
+-- @param #table SpawnTemplate
-- @return #DATABASE self
-function DATABASE:IsIncludeDCSUnit( DCSUnit )
- self:F( DCSUnit )
- local DCSUnitInclude = true
+function DATABASE:Spawn( SpawnTemplate )
+ self:F( SpawnTemplate.name )
- if self.Filter.Coalitions then
- local DCSUnitCoalition = false
- for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
- self:T( { "Coalition:", DCSUnit:getCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } )
- if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == DCSUnit:getCoalition() then
- DCSUnitCoalition = true
- end
- end
- DCSUnitInclude = DCSUnitInclude and DCSUnitCoalition
- end
-
- if self.Filter.Categories then
- local DCSUnitCategory = false
- for CategoryID, CategoryName in pairs( self.Filter.Categories ) do
- self:T( { "Category:", DCSUnit:getDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } )
- if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == DCSUnit:getDesc().category then
- DCSUnitCategory = true
- end
- end
- DCSUnitInclude = DCSUnitInclude and DCSUnitCategory
- end
-
- if self.Filter.Types then
- local DCSUnitType = false
- for TypeID, TypeName in pairs( self.Filter.Types ) do
- self:T( { "Type:", DCSUnit:getTypeName(), TypeName } )
- if TypeName == DCSUnit:getTypeName() then
- DCSUnitType = true
- end
- end
- DCSUnitInclude = DCSUnitInclude and DCSUnitType
- end
-
- if self.Filter.Countries then
- local DCSUnitCountry = false
- for CountryID, CountryName in pairs( self.Filter.Countries ) do
- self:T( { "Country:", DCSUnit:getCountry(), CountryName } )
- if country.id[CountryName] == DCSUnit:getCountry() then
- DCSUnitCountry = true
- end
- end
- DCSUnitInclude = DCSUnitInclude and DCSUnitCountry
- end
+ self:T( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } )
- if self.Filter.UnitPrefixes then
- local DCSUnitPrefix = false
- for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do
- self:T( { "Unit Prefix:", string.find( DCSUnit:getName(), UnitPrefix, 1 ), UnitPrefix } )
- if string.find( DCSUnit:getName(), UnitPrefix, 1 ) then
- DCSUnitPrefix = true
- end
- end
- DCSUnitInclude = DCSUnitInclude and DCSUnitPrefix
- end
+ -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables.
+ local SpawnCoalitionID = SpawnTemplate.SpawnCoalitionID
+ local SpawnCountryID = SpawnTemplate.SpawnCountryID
+ local SpawnCategoryID = SpawnTemplate.SpawnCategoryID
- self:T( DCSUnitInclude )
- return DCSUnitInclude
+ -- Nullify
+ SpawnTemplate.SpawnCoalitionID = nil
+ SpawnTemplate.SpawnCountryID = nil
+ SpawnTemplate.SpawnCategoryID = nil
+
+ self:_RegisterGroup( SpawnTemplate )
+ coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
+
+ -- Restore
+ SpawnTemplate.SpawnCoalitionID = SpawnCoalitionID
+ SpawnTemplate.SpawnCountryID = SpawnCountryID
+ SpawnTemplate.SpawnCategoryID = SpawnCategoryID
+
+
+ local SpawnGroup = GROUP:New( Group.getByName( SpawnTemplate.name ) )
+ return SpawnGroup
end
----
--- @param #DATABASE self
--- @param DCSUnit#Unit DCSUnit
--- @return #DATABASE self
-function DATABASE:IsAliveDCSUnit( DCSUnit )
- self:F( DCSUnit )
- local DCSUnitAlive = false
- if DCSUnit and DCSUnit:isExist() and DCSUnit:isActive() then
- if self.DCSUnits[DCSUnit:getName()] then
- DCSUnitAlive = true
- end
- end
- self:T( DCSUnitAlive )
- return DCSUnitAlive
+
+--- Set a status to a Group within the Database, this to check crossing events for example.
+function DATABASE:SetStatusGroup( GroupName, Status )
+ self:F( Status )
+
+ self.Groups[GroupName].Status = Status
end
+--- Get a status to a Group within the Database, this to check crossing events for example.
+function DATABASE:GetStatusGroup( GroupName )
+ self:F( Status )
+
+ if self.Groups[GroupName] then
+ return self.Groups[GroupName].Status
+ else
+ return ""
+ end
+end
+
+--- Private method that registers new Group Templates within the DATABASE Object.
+-- @param #DATABASE self
+-- @param #table GroupTemplate
+-- @return #DATABASE self
+function DATABASE:_RegisterGroup( GroupTemplate )
+
+ local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name)
+
+ if not self.Groups[GroupTemplateName] then
+ self.Groups[GroupTemplateName] = {}
+ self.Groups[GroupTemplateName].Status = nil
+ end
+
+ -- Delete the spans from the route, it is not needed and takes memory.
+ if GroupTemplate.route and GroupTemplate.route.spans then
+ GroupTemplate.route.spans = nil
+ end
+
+ self.Groups[GroupTemplateName].GroupName = GroupTemplateName
+ self.Groups[GroupTemplateName].Template = GroupTemplate
+ self.Groups[GroupTemplateName].groupId = GroupTemplate.groupId
+ self.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units
+ self.Groups[GroupTemplateName].Units = GroupTemplate.units
+
+ self:T( { "Group", self.Groups[GroupTemplateName].GroupName, self.Groups[GroupTemplateName].UnitCount } )
+
+ for unit_num, UnitTemplate in pairs(GroupTemplate.units) do
+
+ local UnitTemplateName = env.getValueDictByKey(UnitTemplate.name)
+ self.Units[UnitTemplateName] = {}
+ self.Units[UnitTemplateName].UnitName = UnitTemplateName
+ self.Units[UnitTemplateName].Template = UnitTemplate
+ self.Units[UnitTemplateName].GroupName = GroupTemplateName
+ self.Units[UnitTemplateName].GroupTemplate = GroupTemplate
+ self.Units[UnitTemplateName].GroupId = GroupTemplate.groupId
+ if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then
+ self.ClientsByName[UnitTemplateName] = UnitTemplate
+ self.ClientsByID[UnitTemplate.unitId] = UnitTemplate
+ end
+ self:E( { "Unit", self.Units[UnitTemplateName].UnitName } )
+ end
+end
+
+--- Handles the OnBirth event for the alive units set.
+-- @param #DATABASE self
+-- @param Event#EVENTDATA Event
+function DATABASE:_EventOnBirth( Event )
+ self:F( { Event } )
+
+ if Event.IniDCSUnit then
+ if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then
+ self.DCSUnits[Event.IniDCSUnitName] = Event.IniDCSUnit
+ self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit
+ end
+ end
+end
+
+--- Handles the OnDead or OnCrash event for alive units set.
+-- @param #DATABASE self
+-- @param Event#EVENTDATA Event
+function DATABASE:_EventOnDeadOrCrash( Event )
+ self:F( { Event } )
+
+ if Event.IniDCSUnit then
+ if self.DCSUnitsAlive[Event.IniDCSUnitName] then
+ self.DCSUnits[Event.IniDCSUnitName] = nil
+ self.DCSUnitsAlive[Event.IniDCSUnitName] = nil
+ end
+ end
+end
+
+--- Interate the DATABASE and call an interator function for each **alive** unit, providing the Unit and optional parameters.
+-- @param #DATABASE self
+-- @param #function IteratorFunction The function that will be called when there is an alive unit in the database. The function needs to accept a UNIT parameter.
+-- @return #DATABASE self
+function DATABASE:ForEachAliveUnit( IteratorFunction, ... )
+ self:F( arg )
+
+ local function CoRoutine()
+ local Count = 0
+ for DCSUnitID, DCSUnit in pairs( self.DCSUnitsAlive ) do
+ self:T2( DCSUnit )
+ IteratorFunction( DCSUnit, unpack( arg ) )
+ Count = Count + 1
+ if Count % 10 == 0 then
+ coroutine.yield( false )
+ end
+ end
+ return true
+ end
+
+ local co = coroutine.create( CoRoutine )
+
+ local function Schedule()
+
+ local status, res = coroutine.resume( co )
+ self:T( { status, res } )
+
+ if status == false then
+ error( res )
+ end
+ if res == false then
+ timer.scheduleFunction( Schedule, {}, timer.getTime() + 0.001 )
+ end
+ end
+
+ timer.scheduleFunction( Schedule, {}, timer.getTime() + 1 )
+
+ return self
+end
+
function DATABASE:ScanEnvironment()
self:F()
@@ -5607,120 +6312,91 @@ function DATABASE:ScanEnvironment()
return self
end
---- Instantiate new Groups within the DCSRTE.
--- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined:
--- SpawnCountryID, SpawnCategoryID
--- This method is used by the SPAWN class.
-function DATABASE:Spawn( SpawnTemplate )
- self:F( SpawnTemplate.name )
- self:T( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } )
-
- -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables.
- local SpawnCoalitionID = SpawnTemplate.SpawnCoalitionID
- local SpawnCountryID = SpawnTemplate.SpawnCountryID
- local SpawnCategoryID = SpawnTemplate.SpawnCategoryID
-
- -- Nullify
- SpawnTemplate.SpawnCoalitionID = nil
- SpawnTemplate.SpawnCountryID = nil
- SpawnTemplate.SpawnCategoryID = nil
-
- self:_RegisterGroup( SpawnTemplate )
- coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
-
- -- Restore
- SpawnTemplate.SpawnCoalitionID = SpawnCoalitionID
- SpawnTemplate.SpawnCountryID = SpawnCountryID
- SpawnTemplate.SpawnCategoryID = SpawnCategoryID
-
-
- local SpawnGroup = GROUP:New( Group.getByName( SpawnTemplate.name ) )
- return SpawnGroup
-end
-
-
---- Set a status to a Group within the Database, this to check crossing events for example.
-function DATABASE:SetStatusGroup( GroupName, Status )
- self:F( Status )
-
- self.Groups[GroupName].Status = Status
-end
-
-
---- Get a status to a Group within the Database, this to check crossing events for example.
-function DATABASE:GetStatusGroup( GroupName )
- self:F( Status )
-
- if self.Groups[GroupName] then
- return self.Groups[GroupName].Status
- else
- return ""
- end
-end
-
---- Registers new Group Templates within the DATABASE Object.
-function DATABASE:_RegisterGroup( GroupTemplate )
-
- local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name)
-
- if not self.Groups[GroupTemplateName] then
- self.Groups[GroupTemplateName] = {}
- self.Groups[GroupTemplateName].Status = nil
- end
- self.Groups[GroupTemplateName].GroupName = GroupTemplateName
- self.Groups[GroupTemplateName].Template = GroupTemplate
- self.Groups[GroupTemplateName].groupId = GroupTemplate.groupId
- self.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units
- self.Groups[GroupTemplateName].Units = GroupTemplate.units
-
- self:T( { "Group", self.Groups[GroupTemplateName].GroupName, self.Groups[GroupTemplateName].UnitCount } )
-
- for unit_num, UnitTemplate in pairs(GroupTemplate.units) do
-
- local UnitTemplateName = env.getValueDictByKey(UnitTemplate.name)
- self.Units[UnitTemplateName] = {}
- self.Units[UnitTemplateName].UnitName = UnitTemplateName
- self.Units[UnitTemplateName].Template = UnitTemplate
- self.Units[UnitTemplateName].GroupName = GroupTemplateName
- self.Units[UnitTemplateName].GroupTemplate = GroupTemplate
- self.Units[UnitTemplateName].GroupId = GroupTemplate.groupId
- if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then
- self.ClientsByName[UnitTemplateName] = UnitTemplate
- self.ClientsByID[UnitTemplate.unitId] = UnitTemplate
- end
- self:E( { "Unit", self.Units[UnitTemplateName].UnitName } )
- end
-end
-
---- Handles the OnBirth event for the alive units set.
+---
-- @param #DATABASE self
--- @param Event#EVENTDATA Event
-function DATABASE:_EventOnBirth( Event )
- self:F( { Event } )
+-- @param DCSUnit#Unit DCSUnit
+-- @return #DATABASE self
+function DATABASE:_IsIncludeDCSUnit( DCSUnit )
+ self:F( DCSUnit )
+ local DCSUnitInclude = true
- if Event.IniDCSUnit then
- if self:IsIncludeDCSUnit( Event.IniDCSUnit ) then
- self.DCSUnits[Event.IniDCSUnitName] = Event.IniDCSUnit
- self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit
+ if self.Filter.Coalitions then
+ local DCSUnitCoalition = false
+ for CoalitionID, CoalitionName in pairs( self.Filter.Coalitions ) do
+ self:T( { "Coalition:", DCSUnit:getCoalition(), self.FilterMeta.Coalitions[CoalitionName], CoalitionName } )
+ if self.FilterMeta.Coalitions[CoalitionName] and self.FilterMeta.Coalitions[CoalitionName] == DCSUnit:getCoalition() then
+ DCSUnitCoalition = true
+ end
end
+ DCSUnitInclude = DCSUnitInclude and DCSUnitCoalition
end
+
+ if self.Filter.Categories then
+ local DCSUnitCategory = false
+ for CategoryID, CategoryName in pairs( self.Filter.Categories ) do
+ self:T( { "Category:", DCSUnit:getDesc().category, self.FilterMeta.Categories[CategoryName], CategoryName } )
+ if self.FilterMeta.Categories[CategoryName] and self.FilterMeta.Categories[CategoryName] == DCSUnit:getDesc().category then
+ DCSUnitCategory = true
+ end
+ end
+ DCSUnitInclude = DCSUnitInclude and DCSUnitCategory
+ end
+
+ if self.Filter.Types then
+ local DCSUnitType = false
+ for TypeID, TypeName in pairs( self.Filter.Types ) do
+ self:T( { "Type:", DCSUnit:getTypeName(), TypeName } )
+ if TypeName == DCSUnit:getTypeName() then
+ DCSUnitType = true
+ end
+ end
+ DCSUnitInclude = DCSUnitInclude and DCSUnitType
+ end
+
+ if self.Filter.Countries then
+ local DCSUnitCountry = false
+ for CountryID, CountryName in pairs( self.Filter.Countries ) do
+ self:T( { "Country:", DCSUnit:getCountry(), CountryName } )
+ if country.id[CountryName] == DCSUnit:getCountry() then
+ DCSUnitCountry = true
+ end
+ end
+ DCSUnitInclude = DCSUnitInclude and DCSUnitCountry
+ end
+
+ if self.Filter.UnitPrefixes then
+ local DCSUnitPrefix = false
+ for UnitPrefixId, UnitPrefix in pairs( self.Filter.UnitPrefixes ) do
+ self:T( { "Unit Prefix:", string.find( DCSUnit:getName(), UnitPrefix, 1 ), UnitPrefix } )
+ if string.find( DCSUnit:getName(), UnitPrefix, 1 ) then
+ DCSUnitPrefix = true
+ end
+ end
+ DCSUnitInclude = DCSUnitInclude and DCSUnitPrefix
+ end
+
+ self:T( DCSUnitInclude )
+ return DCSUnitInclude
end
---- Handles the OnDead or OnCrash event for alive units set.
+---
-- @param #DATABASE self
--- @param Event#EVENTDATA Event
-function DATABASE:_EventOnDeadOrCrash( Event )
- self:F( { Event } )
-
- if Event.IniDCSUnit then
- if self.DCSUnitsAlive[Event.IniDCSUnitName] then
- self.DCSUnits[Event.IniDCSUnitName] = nil
- self.DCSUnitsAlive[Event.IniDCSUnitName] = nil
+-- @param DCSUnit#Unit DCSUnit
+-- @return #DATABASE self
+function DATABASE:_IsAliveDCSUnit( DCSUnit )
+ self:F( DCSUnit )
+ local DCSUnitAlive = false
+ if DCSUnit and DCSUnit:isExist() and DCSUnit:isActive() then
+ if self.DCSUnits[DCSUnit:getName()] then
+ DCSUnitAlive = true
end
end
+ self:T( DCSUnitAlive )
+ return DCSUnitAlive
end
+
--- Traces the current database contents in the log ... (for debug reasons).
-- @param #DATABASE self
-- @return #DATABASE self
@@ -5732,6 +6408,135 @@ function DATABASE:TraceDatabase()
end
+--- The main include file for the MOOSE system.
+
+Include.File( "Routines" )
+Include.File( "Base" )
+Include.File( "Database" )
+Include.File( "Event" )
+
+-- The order of the declarations is important here. Don't touch it.
+
+--- Declare the event dispatcher based on the EVENT class
+_EVENTDISPATCHER = EVENT:New() -- #EVENT
+
+--- Declare the main database object, which is used internally by the MOOSE classes.
+_DATABASE = DATABASE:New():ScanEnvironment() -- Database#DATABASE
+
+--- Models time events calling event handing functions.
+-- @module Scheduler
+-- @author FlightControl
+
+Include.File( "Routines" )
+Include.File( "Base" )
+Include.File( "Cargo" )
+Include.File( "Message" )
+
+
+--- The SCHEDULER class
+-- @type SCHEDULER
+-- @extends Base#BASE
+SCHEDULER = {
+ ClassName = "SCHEDULER",
+}
+
+
+--- SCHEDULER constructor.
+-- @param #SCHEDULER self
+-- @param #table TimeEventObject
+-- @param #function TimeEventFunction
+-- @param #table TimeEventFunctionArguments
+-- @param #number StartSeconds
+-- @param #number RepeatSecondsInterval
+-- @param #number RandomizationFactor
+-- @param #number StopSeconds
+-- @return #SCHEDULER
+function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
+
+ self.TimeEventObject = TimeEventObject
+ self.TimeEventFunction = TimeEventFunction
+ self.TimeEventFunctionArguments = TimeEventFunctionArguments
+ self.StartSeconds = StartSeconds
+
+ if RepeatSecondsInterval then
+ self.RepeatSecondsInterval = RepeatSecondsInterval
+ else
+ self.RepeatSecondsInterval = 0
+ end
+
+ if RandomizationFactor then
+ self.RandomizationFactor = RandomizationFactor
+ else
+ self.RandomizationFactor = 0
+ end
+
+ if StopSeconds then
+ self.StopSeconds = StopSeconds
+ end
+
+ self.Repeat = false
+
+ self.StartTime = timer.getTime()
+
+ self:Start()
+
+ return self
+end
+
+function SCHEDULER:Scheduler()
+ self:F( self.TimeEventFunctionArguments )
+
+ local ErrorHandler = function( errmsg )
+
+ env.info( "Error in SCHEDULER function:" .. errmsg )
+ env.info( debug.traceback() )
+
+ return errmsg
+ end
+
+ local Status, Result
+ if self.TimeEventObject then
+ Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
+ else
+ Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
+ end
+
+ self:T( { Status, Result } )
+
+ if Status and Status == true and Result and Result == true then
+ if self.Repeat and ( not self.StopSeconds or ( self.StopSeconds and timer.getTime() <= self.StartTime + self.StopSeconds ) ) then
+ timer.scheduleFunction(
+ self.Scheduler,
+ self,
+ timer.getTime() + self.RepeatSecondsInterval + math.random( - ( self.RandomizationFactor * self.RepeatSecondsInterval / 2 ), ( self.RandomizationFactor * self.RepeatSecondsInterval / 2 ) ) + 0.01
+ )
+ end
+ end
+
+end
+
+function SCHEDULER:Start()
+ self:F( self.TimeEventObject )
+
+ self.Repeat = true
+ timer.scheduleFunction( self.Scheduler, self, timer.getTime() + self.StartSeconds + .01 )
+
+ return self
+end
+
+function SCHEDULER:Stop()
+ self:F( self.TimeEventObject )
+
+ self.Repeat = false
+end
+
+
+
+
+
+
--- Scoring system for MOOSE.
-- This scoring class calculates the hits and kills that players make within a simulation session.
-- Scoring is calculated using a defined algorithm.
@@ -5931,7 +6736,7 @@ end
function SCORING:_AddPlayerFromUnit( UnitData )
self:F( UnitData )
- if UnitData:isExist() then
+ if UnitData and UnitData:isExist() then
local UnitName = UnitData:getName()
local PlayerName = UnitData:getPlayerName()
local UnitDesc = UnitData:getDesc()
@@ -6058,7 +6863,7 @@ function SCORING:_EventOnHit( Event )
local InitUnitName = ""
local InitGroup = nil
local InitGroupName = ""
- local InitPlayerName = "dummy"
+ local InitPlayerName = nil
local InitCoalition = nil
local InitCategory = nil
@@ -6580,18 +7385,31 @@ CARGO_ZONE = {
}
}
-function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, BASE:New() )
+--- Creates a new zone where cargo can be collected or deployed.
+-- The zone functionality is useful to smoke or indicate routes for cargo pickups or deployments.
+-- Provide the zone name as declared in the mission file into the CargoZoneName in the :New method.
+-- An optional parameter is the CargoHostName, which is a Group declared with Late Activation switched on in the mission file.
+-- The CargoHostName is the "host" of the cargo zone:
+--
+-- * It will smoke the zone position when a client is approaching the zone.
+-- * Depending on the cargo type, it will assist in the delivery of the cargo by driving to and from the client.
+--
+-- @param #CARGO_ZONE self
+-- @param #string CargoZoneName The name of the zone as declared within the mission editor.
+-- @param #string CargoHostName The name of the Group "hosting" the zone. The Group MUST NOT be a static, and must be a "mobile" unit.
+function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, ZONE:New( CargoZoneName ) )
self:F( { CargoZoneName, CargoHostName } )
self.CargoZoneName = CargoZoneName
- self.CargoZone = trigger.misc.getZone( CargoZoneName )
+ self.SignalHeight = 2
+ --self.CargoZone = trigger.misc.getZone( CargoZoneName )
if CargoHostName then
self.CargoHostName = CargoHostName
end
- self:T( self.CargoZone )
+ self:T( self.CargoZoneName )
return self
end
@@ -6599,17 +7417,19 @@ end
function CARGO_ZONE:Spawn()
self:F( self.CargoHostName )
- if self.CargoHostSpawn then
- local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex()
- if CargoHostGroup and CargoHostGroup:IsAlive() then
- else
- self.CargoHostSpawn:ReSpawn( 1 )
- end
- else
- self:T( "Initialize CargoHostSpawn" )
- self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 )
- self.CargoHostSpawn:ReSpawn( 1 )
- end
+ if self.CargoHostName then -- Only spawn a host in the zone when there is one given as a parameter in the New function.
+ if self.CargoHostSpawn then
+ local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex()
+ if CargoHostGroup and CargoHostGroup:IsAlive() then
+ else
+ self.CargoHostSpawn:ReSpawn( 1 )
+ end
+ else
+ self:T( "Initialize CargoHostSpawn" )
+ self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 )
+ self.CargoHostSpawn:ReSpawn( 1 )
+ end
+ end
return self
end
@@ -6663,6 +7483,7 @@ function CARGO_ZONE:ReportCargosToClient( Client, CargoType )
end
end
+
function CARGO_ZONE:Signal()
self:F()
@@ -6697,16 +7518,15 @@ function CARGO_ZONE:Signal()
else
- local CurrentPosition = { x = self.CargoZone.point.x, y = self.CargoZone.point.z }
- self.CargoZone.point.y = land.getHeight( CurrentPosition ) + 2
+ local ZonePointVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters
if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then
- trigger.action.smoke( self.CargoZone.point, self.SignalColor.TRIGGERCOLOR )
+ trigger.action.smoke( ZonePointVec3, self.SignalColor.TRIGGERCOLOR )
Signalled = true
elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then
- trigger.action.signalFlare( self.CargoZone.point, self.SignalColor.TRIGGERCOLOR, 0 )
+ trigger.action.signalFlare( ZonePointVec3, self.SignalColor.TRIGGERCOLOR, 0 )
Signalled = false
end
@@ -6717,85 +7537,121 @@ function CARGO_ZONE:Signal()
end
-function CARGO_ZONE:WhiteSmoke()
+function CARGO_ZONE:WhiteSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE
+
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
return self
end
-function CARGO_ZONE:BlueSmoke()
+function CARGO_ZONE:BlueSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:RedSmoke()
+function CARGO_ZONE:RedSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:OrangeSmoke()
+function CARGO_ZONE:OrangeSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:GreenSmoke()
+function CARGO_ZONE:GreenSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:WhiteFlare()
+function CARGO_ZONE:WhiteFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:RedFlare()
+function CARGO_ZONE:RedFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:GreenFlare()
+function CARGO_ZONE:GreenFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:YellowFlare()
+function CARGO_ZONE:YellowFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
@@ -7066,6 +7922,7 @@ function CARGO_GROUP:Spawn( Client )
self.CargoGroupName = self.CargoSpawn:SpawnFromUnit( self.CargoZone:GetCargoHostUnit(), 60, 30, 1 ):GetName()
else
--- ReSpawn the Cargo in the CargoZone without a host ...
+ self:T( self.CargoZone )
self.CargoGroupName = self.CargoSpawn:SpawnInZone( self.CargoZone, true, 1 ):GetName()
end
self:StatusNone()
@@ -7106,64 +7963,72 @@ function CARGO_GROUP:OnBoard( Client, LandingZone, OnBoardSide )
local CargoGroup = Group.getByName( self.CargoGroupName )
- local CargoUnits = CargoGroup:getUnits()
- local CargoPos = CargoUnits[1]:getPoint()
+ local CargoUnit = CargoGroup:getUnit(1)
+ local CargoPos = CargoUnit:getPoint()
+
+ self.CargoInAir = CargoUnit:inAir()
+
+ self:T( self.CargoInAir )
+ -- Only move the group to the carrier when the cargo is not in the air
+ -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
+ if not self.CargoInAir then
- local Points = {}
-
- self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z )
- self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z )
-
- Points[#Points+1] = routines.ground.buildWP( CargoPos, "Cone", 10 )
-
- self:T( 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y )
-
- if OnBoardSide == nil then
- OnBoardSide = CLIENT.ONBOARDSIDE.NONE
- end
-
- if OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then
-
- self:T( "TransportCargoOnBoard: Onboarding LEFT" )
- CarrierPosMove.z = CarrierPosMove.z - 25
- CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then
-
- self:T( "TransportCargoOnBoard: Onboarding RIGHT" )
- CarrierPosMove.z = CarrierPosMove.z + 25
- CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.BACK then
-
- self:T( "TransportCargoOnBoard: Onboarding BACK" )
- CarrierPosMove.x = CarrierPosMove.x - 25
- CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then
-
- self:T( "TransportCargoOnBoard: Onboarding FRONT" )
- CarrierPosMove.x = CarrierPosMove.x + 25
- CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.NONE then
-
- self:T( "TransportCargoOnBoard: Onboarding CENTRAL" )
- Points[#Points+1] = routines.ground.buildWP( CarrierPos, "Cone", 10 )
-
- end
- self:T( "TransportCargoOnBoard: Routing " .. self.CargoGroupName )
-
- routines.scheduleFunction( routines.goRoute, { self.CargoGroupName, Points}, timer.getTime() + 4 )
+ local Points = {}
+
+ self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z )
+ self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z )
+
+ Points[#Points+1] = routines.ground.buildWP( CargoPos, "Cone", 10 )
+
+ self:T( 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y )
+
+ if OnBoardSide == nil then
+ OnBoardSide = CLIENT.ONBOARDSIDE.NONE
+ end
+
+ if OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then
+
+ self:T( "TransportCargoOnBoard: Onboarding LEFT" )
+ CarrierPosMove.z = CarrierPosMove.z - 25
+ CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then
+
+ self:T( "TransportCargoOnBoard: Onboarding RIGHT" )
+ CarrierPosMove.z = CarrierPosMove.z + 25
+ CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.BACK then
+
+ self:T( "TransportCargoOnBoard: Onboarding BACK" )
+ CarrierPosMove.x = CarrierPosMove.x - 25
+ CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then
+
+ self:T( "TransportCargoOnBoard: Onboarding FRONT" )
+ CarrierPosMove.x = CarrierPosMove.x + 25
+ CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.NONE then
+
+ self:T( "TransportCargoOnBoard: Onboarding CENTRAL" )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPos, "Cone", 10 )
+
+ end
+ self:T( "TransportCargoOnBoard: Routing " .. self.CargoGroupName )
+
+ routines.scheduleFunction( routines.goRoute, { self.CargoGroupName, Points}, timer.getTime() + 4 )
+ end
self:StatusLoading( Client )
@@ -7177,12 +8042,19 @@ function CARGO_GROUP:OnBoarded( Client, LandingZone )
local OnBoarded = false
- local CargoGroup = Group.getByName( self.CargoGroupName )
- if routines.IsPartOfGroupInRadius( CargoGroup, Client:ClientPosition(), 25 ) then
- CargoGroup:destroy()
- self:StatusLoaded( Client )
- OnBoarded = true
- end
+ local CargoGroup = Group.getByName( self.CargoGroupName )
+
+ if not self.CargoInAir then
+ if routines.IsPartOfGroupInRadius( CargoGroup, Client:ClientPosition(), 25 ) then
+ CargoGroup:destroy()
+ self:StatusLoaded( Client )
+ OnBoarded = true
+ end
+ else
+ CargoGroup:destroy()
+ self:StatusLoaded( Client )
+ OnBoarded = true
+ end
return OnBoarded
end
@@ -8296,7 +9168,7 @@ end
function STAGE:Execute( Mission, Client, Task )
local Valid = true
-
+
return Valid
end
@@ -8306,7 +9178,7 @@ end
function STAGE:Validate( Mission, Client, Task )
local Valid = true
-
+
return Valid
end
@@ -8515,7 +9387,7 @@ function STAGEROUTE:Validate( Mission, Client, Task )
-- check if the Client is in the landing zone
self:T( Task.LandingZones.LandingZoneNames )
- Task.CurrentLandingZoneName = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames )
+ Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 )
if Task.CurrentLandingZoneName then
@@ -8528,9 +9400,11 @@ function STAGEROUTE:Validate( Mission, Client, Task )
end
end
+ self:T( 1 )
return 1
end
+ self:T( 0 )
return 0
end
@@ -8605,8 +9479,17 @@ function STAGELANDING:Execute( Mission, Client, Task )
else
HostMessage = "Use the Radio menu and F6 to find the cargo, then fly or land near the cargo and " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "."
end
+
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
- Client:Message( HostMessage, self.MSG.TIME, Mission.Name .. "/STAGELANDING.EXEC." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")", 10 )
+ Client:Message( HostMessage, self.MSG.TIME, Mission.Name .. "/STAGELANDING.EXEC." .. Host, Host, 10 )
end
end
@@ -8614,7 +9497,7 @@ end
function STAGELANDING:Validate( Mission, Client, Task )
self:F()
- Task.CurrentLandingZoneName = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames )
+ Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 )
if Task.CurrentLandingZoneName then
-- Client is in de landing zone.
@@ -8637,14 +9520,34 @@ function STAGELANDING:Validate( Mission, Client, Task )
end
Task.Signalled = false
Task:RemoveCargoMenus( Client )
+ self:T( -1 )
return -1
end
- if Task.IsLandingRequired and Client:GetClientGroupDCSUnit():inAir() then
- return 0
- end
+
+ local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity()
+ local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5
+
+ local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint()
+ local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } )
+ local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight
+
+ self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } )
+ if Task.IsLandingRequired and not Client:GetClientGroupDCSUnit():inAir() then
+ self:T( 1 )
+ Task.IsInAirTestRequired = true
+ return 1
+ end
- return 1
+ self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } )
+ if Task.IsLandingRequired and DCSUnitVelocity <= 0.05 and DCSUnitHeight <= Task.CurrentCargoZone.SignalHeight then
+ self:T( 1 )
+ Task.IsInAirTestRequired = false
+ return 1
+ end
+
+ self:T( 0 )
+ return 0
end
STAGELANDED = {
@@ -8665,9 +9568,20 @@ function STAGELANDED:Execute( Mission, Client, Task )
self:F()
if Task.IsLandingRequired then
- Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.',
- self.MSG.TIME, Mission.Name .. "/STAGELANDED.EXEC." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
- if not self.MenusAdded then
+
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
+
+ Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.',
+ self.MSG.TIME, Mission.Name .. "/STAGELANDED.EXEC" .. Host, Host )
+
+ if not self.MenusAdded then
Task.Cargo = nil
Task:RemoveCargoMenus( Client )
Task:AddCargoMenus( Client, CARGOS, 250 )
@@ -8680,26 +9594,44 @@ end
function STAGELANDED:Validate( Mission, Client, Task )
self:F()
- if not routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName ) then
+ if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then
self:T( "Client is not anymore in the landing zone, go back to stage Route, and remove cargo menus." )
Task.Signalled = false
Task:RemoveCargoMenus( Client )
+ self:T( -2 )
return -2
end
+
+ local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity()
+ local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5
- if Task.IsLandingRequired and Client:GetClientGroupDCSUnit():inAir() then
- self:T( "Client went back in the air. Go back to stage Landing." )
- Task.Signalled = false
- return -1
- end
+ local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint()
+ local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } )
+ local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight
+
+ self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then
+ self:T( "Client went back in the air. Go back to stage Landing." )
+ self:T( -1 )
+ return -1
+ end
+
+ self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then
+ self:T( "It seems the Client went back in the air and over the boundary limits. Go back to stage Landing." )
+ self:T( -1 )
+ return -1
+ end
-- Wait until cargo is selected from the menu.
if Task.IsLandingRequired then
if not Task.Cargo then
+ self:T( 0 )
return 0
end
end
-
+
+ self:T( 1 )
return 1
end
@@ -8763,7 +9695,7 @@ function STAGEUNLOAD:Validate( Mission, Client, Task )
self:F()
env.info( 'STAGEUNLOAD:Validate()' )
- if routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName ) then
+ if routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then
else
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task:RemoveCargoMenus( Client )
@@ -8822,10 +9754,21 @@ function STAGELOAD:Execute( Mission, Client, Task )
self:F()
if not Task.IsSlingLoad then
+
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
+
Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.',
- _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.EXEC." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.EXEC." .. Host, Host )
-- Route the cargo to the Carrier
+
Task.Cargo:OnBoard( Client, Task.CurrentCargoZone, Task.OnBoardSide )
Task.ExecuteStage = _TransportExecuteStage.EXECUTING
else
@@ -8838,6 +9781,14 @@ function STAGELOAD:Executing( Mission, Client, Task )
-- If the Cargo is ready to be loaded, load it into the Client.
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
if not Task.IsSlingLoad then
self:T( Task.Cargo.CargoName)
@@ -8849,14 +9800,14 @@ function STAGELOAD:Executing( Mission, Client, Task )
-- Message to the pilot that cargo has been loaded.
Client:Message( "The cargo " .. Task.Cargo.CargoName .. " has been loaded in our helicopter.",
- 20, Mission.Name .. "/STAGELANDING.LOADING1." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ 20, Mission.Name .. "/STAGELANDING.LOADING1." .. Host, Host )
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
Client:ShowCargo()
end
else
Client:Message( "Hook the " .. Task.CargoNames .. " onto the helicopter " .. Task.TEXT[3] .. " within the landing zone.",
- _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.LOADING.1." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")", 10 )
+ _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.LOADING.1." .. Host, Host , 10 )
for CargoID, Cargo in pairs( CARGOS ) do
self:T( "Cargo.CargoName = " .. Cargo.CargoName )
@@ -8872,7 +9823,7 @@ function STAGELOAD:Executing( Mission, Client, Task )
Cargo:StatusLoaded()
Task.Cargo = Cargo
Client:Message( 'The Cargo has been successfully hooked onto the helicopter and is now being sling loaded. Fly outside the landing zone.',
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.LOADING.2." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.LOADING.2." .. Host, Host )
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
break
end
@@ -8890,32 +9841,61 @@ function STAGELOAD:Validate( Mission, Client, Task )
self:T( "Task.CurrentLandingZoneName = " .. Task.CurrentLandingZoneName )
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
+
if not Task.IsSlingLoad then
- if not routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName ) then
+ if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then
Task:RemoveCargoMenus( Client )
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task.CargoName = nil
Client:Message( "The " .. Task.CargoType .. " loading has been aborted. You flew outside the pick-up zone while loading. ",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Host, Host )
+ self:T( -1 )
return -1
end
- if not Client:GetClientGroupDCSUnit():inAir() then
- else
- -- The carrier is back in the air, undo the loading process.
- Task:RemoveCargoMenus( Client )
- Task.ExecuteStage = _TransportExecuteStage.NONE
- Task.CargoName = nil
- Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.2." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
- return -1
- end
+ local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity()
+ local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5
+
+ local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint()
+ local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } )
+ local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight
+
+ self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then
+ Task:RemoveCargoMenus( Client )
+ Task.ExecuteStage = _TransportExecuteStage.FAILED
+ Task.CargoName = nil
+ Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.",
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Host, Host )
+ self:T( -1 )
+ return -1
+ end
+
+ self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then
+ Task:RemoveCargoMenus( Client )
+ Task.ExecuteStage = _TransportExecuteStage.FAILED
+ Task.CargoName = nil
+ Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.",
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Host, Host )
+ self:T( -1 )
+ return -1
+ end
if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then
Task:RemoveCargoMenus( Client )
Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " within the landing zone.",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.3." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.3." .. Host, Host )
Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 )
+ self:T( 1 )
return 1
end
@@ -8924,8 +9904,9 @@ function STAGELOAD:Validate( Mission, Client, Task )
CargoStatic = StaticObject.getByName( Task.Cargo.CargoStaticName )
if CargoStatic and not routines.IsStaticInZones( CargoStatic, Task.CurrentLandingZoneName ) then
Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " and flown outside of the landing zone.",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.4." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.4." .. Host, Host )
Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.Cargo.CargoName, 1 )
+ self:T( 1 )
return 1
end
end
@@ -8933,6 +9914,7 @@ function STAGELOAD:Validate( Mission, Client, Task )
end
+ self:T( 0 )
return 0
end
@@ -9873,7 +10855,7 @@ function PICKUPTASK:New( CargoType, OnBoardSide )
self.CargoType = CargoType
self.GoalVerb = CargoType .. " " .. self.GoalVerb
self.OnBoardSide = OnBoardSide
- self.IsLandingRequired = false -- required to decide whether the client needs to land or not
+ self.IsLandingRequired = true -- required to decide whether the client needs to land or not
self.IsSlingLoad = false -- Indicates whether the cargo is a sling load cargo
self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGELOAD:New(), STAGEDONE:New() }
self.SetStage( self, 1 )
@@ -10698,7 +11680,9 @@ function MISSIONSCHEDULER.Scheduler()
if Mission.GoalFunction ~= nil then
Mission.GoalFunction( Mission, Client )
end
- _DATABASE:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 )
+ if MISSIONSCHEDULER.Scoring then
+ MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 )
+ end
-- if not Mission:IsCompleted() then
-- end
@@ -11217,7 +12201,10 @@ function CLEANUP:_CleanUpScheduler()
end
--- Dynamic spawning of groups (and units).
--- The SPAWN class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
+--
+-- @{#SPAWN} class
+-- ===============
+-- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
-- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object.
-- A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods.
--
@@ -11239,8 +12226,8 @@ end
-- * It is important to defined BEFORE you spawn new groups, a proper initialization of the SPAWN instance is done with the options you want to use.
-- * When designing a mission, NEVER name groups using a "#" within the name of the group Spawn Template(s), or the SPAWN module logic won't work anymore.
--
--- 1. SPAWN object construction methods:
--- -------------------------------------
+-- SPAWN construction methods:
+-- ===========================
-- Create a new SPAWN object with the @{#SPAWN.New} or the @{#SPAWN.NewWithAlias} methods:
--
-- * @{#SPAWN.New}: Creates a new SPAWN object taking the name of the group that functions as the Template.
@@ -11249,8 +12236,8 @@ end
-- The initialization functions will modify this list of groups so that when a group gets spawned, ALL information is already prepared when spawning. This is done for performance reasons.
-- So in principle, the group list will contain all parameters and configurations after initialization, and when groups get actually spawned, this spawning can be done quickly and efficient.
--
--- 2. SPAWN object initialization methods:
--- ---------------------------------------
+-- SPAWN initialization methods:
+-- =============================
-- A spawn object will behave differently based on the usage of initialization methods:
--
-- * @{#SPAWN.Limit}: Limits the amount of groups that can be alive at the same time and that can be dynamically spawned.
@@ -11258,10 +12245,10 @@ end
-- * @{#SPAWN.RandomizeTemplate}: Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
-- * @{#SPAWN.Uncontrolled}: Spawn plane groups uncontrolled.
-- * @{#SPAWN.Array}: Make groups visible before they are actually activated, and order these groups like a batallion in an array.
--- * @{#SPAWN.Repeat}: Re-spawn groups when they land at the home base. Similar functions are @{#SPAWN.RepeatOnLanding} and @{#SPAWN.RepeatOnEngineShutDown}.
+-- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar functions are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}.
--
--- 2. SPAWN object spawning methods:
--- ---------------------------------
+-- SPAWN spawning methods:
+-- =======================
-- Groups can be spawned at different times and methods:
--
-- * @{#SPAWN.Spawn}: Spawn one new group based on the last spawned index.
@@ -11273,8 +12260,8 @@ end
-- Note that @{#SPAWN.Spawn} and @{#SPAWN.ReSpawn} return a @{GROUP#GROUP.New} object, that contains a reference to the DCSGroup object.
-- You can use the @{GROUP} object to do further actions with the DCSGroup.
--
--- 3. SPAWN object cleaning:
--- -------------------------
+-- SPAWN object cleaning:
+-- =========================
-- Sometimes, it will occur during a mission run-time, that ground or especially air objects get damaged, and will while being damged stop their activities, while remaining alive.
-- In such cases, the SPAWN object will just sit there and wait until that group gets destroyed, but most of the time it won't,
-- and it may occur that no new groups are or can be spawned as limits are reached.
@@ -11285,6 +12272,7 @@ end
-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
-- Check the @{#SPAWN.CleanUp} for further info.
--
+-- ====
-- @module Spawn
-- @author FlightControl
@@ -11294,6 +12282,7 @@ Include.File( "Database" )
Include.File( "Group" )
Include.File( "Zone" )
Include.File( "Event" )
+Include.File( "Scheduler" )
--- SPAWN Class
-- @type SPAWN
@@ -11329,7 +12318,7 @@ function SPAWN:New( SpawnTemplatePrefix )
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.SpawnRepeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
@@ -11366,7 +12355,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.SpawnRepeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
@@ -11488,36 +12477,37 @@ end
-- -- RU Su-34 - AI Ship Attack
-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():RandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
-function SPAWN:Repeat()
+function SPAWN:InitRepeat()
self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
- self.SpawnRepeat = true
+ self.Repeat = true
self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true
return self
end
---- Same as the @{Repeat) method.
--- @return SPAWN
--- @see Repeat
-
-function SPAWN:RepeatOnLanding()
+--- Respawn group after landing.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnLanding()
self:F( { self.SpawnTemplatePrefix } )
- self:Repeat()
+ self:InitRepeat()
self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true
return self
end
---- Same as the @{#SPAWN.Repeat) method, but now the Group will respawn after its engines have shut down.
--- @return SPAWN
-function SPAWN:RepeatOnEngineShutDown()
+
+--- Respawn after landing when its engines have shut down.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnEngineShutDown()
self:F( { self.SpawnTemplatePrefix } )
- self:Repeat()
+ self:InitRepeat()
self.RepeatOnEngineShutDown = true
self.RepeatOnLanding = false
@@ -11536,8 +12526,8 @@ function SPAWN:CleanUp( SpawnCleanUpInterval )
self.SpawnCleanUpInterval = SpawnCleanUpInterval
self.SpawnCleanUpTimeStamps = {}
- self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
-
+ --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
+ self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
return self
end
@@ -11592,10 +12582,8 @@ function SPAWN:Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
_EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self )
_EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self )
- if self.SpawnRepeat then
+ if self.Repeat then
_EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnTakeOff, self )
- end
- if self.RepeatOnLanding then
_EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnLand, self )
end
if self.RepeatOnEngineShutDown then
@@ -11664,15 +12652,15 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
_EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self )
_EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self )
- if self.SpawnRepeat then
+ if self.Repeat then
_EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnTakeOff, self )
- end
- if self.RepeatOnLanding then
_EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnLand, self )
end
if self.RepeatOnEngineShutDown then
_EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnEngineShutDown, self )
end
+
+ self:T( self.SpawnGroups[self.SpawnIndex].SpawnTemplate )
self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( self.SpawnGroups[self.SpawnIndex].SpawnTemplate )
@@ -11681,7 +12669,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
end
-- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
- --if self.SpawnRepeat then
+ --if self.Repeat then
-- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
--end
end
@@ -11714,23 +12702,29 @@ end
function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
self:F( { SpawnTime, SpawnTimeVariation } )
- self.SpawnCurrentTimer = 0 -- The internal timer counter to trigger a scheduled spawning of SpawnTemplatePrefix.
- self.SpawnSetTimer = 0 -- The internal timer value when a scheduled spawning of SpawnTemplatePrefix occurs.
- self.AliveFactor = 1 --
- self.SpawnLowTimer = 0
- self.SpawnHighTimer = 0
-
if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
- self.SpawnLowTimer = SpawnTime - SpawnTime / 2 * SpawnTimeVariation
- self.SpawnHighTimer = SpawnTime + SpawnTime / 2 * SpawnTimeVariation
- self:SpawnScheduleStart()
+ self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, 1, SpawnTime, SpawnTimeVariation )
end
- self:T( { self.SpawnLowTimer, self.SpawnHighTimer } )
-
return self
end
+--- Will re-start the spawning scheduler.
+-- Note: This function is only required to be called when the schedule was stopped.
+function SPAWN:SpawnScheduleStart()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self.SpawnScheduler:Start()
+end
+
+--- Will stop the scheduled spawning scheduler.
+function SPAWN:SpawnScheduleStop()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self.SpawnScheduler:Stop()
+end
+
+
--- Allows to place a CallFunction hook when a new group spawns.
-- The provided function will be called when a new group is spawned, including its given parameters.
-- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned.
@@ -11752,30 +12746,6 @@ end
---- Will start the spawning scheduler.
--- Note: This function is called automatically when @{#SPAWN.Scheduled} is called.
-function SPAWN:SpawnScheduleStart()
- self:F( { self.SpawnTemplatePrefix } )
-
- --local ClientUnit = #AlivePlayerUnits()
-
- self.AliveFactor = 10 -- ( 10 - ClientUnit ) / 10
-
- if self.SpawnIsScheduled == false then
- self.SpawnIsScheduled = true
- self.SpawnInit = true
- self.SpawnSetTimer = math.random( self.SpawnLowTimer * self.AliveFactor / 10 , self.SpawnHighTimer * self.AliveFactor / 10 )
-
- self.SpawnFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, 1 )
- end
-end
-
---- Will stop the scheduled spawning scheduler.
-function SPAWN:SpawnScheduleStop()
- self:F( { self.SpawnTemplatePrefix } )
-
- self.SpawnIsScheduled = false
-end
--- Will spawn a group from a hosting unit. This function is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
@@ -11804,6 +12774,9 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex )
if SpawnTemplate then
local UnitPoint = HostUnit:GetPointVec2()
+
+ self:T( { "Current point of ", self.SpawnTemplatePrefix, UnitPoint } )
+
--for PointID, Point in pairs( SpawnTemplate.route.points ) do
--Point.x = UnitPoint.x
--Point.y = UnitPoint.y
@@ -11811,9 +12784,6 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex )
--Point.alt_type = nil
--end
- SpawnTemplate.route.points = nil
- SpawnTemplate.route.points = {}
- SpawnTemplate.route.points[1] = {}
SpawnTemplate.route.points[1].x = UnitPoint.x
SpawnTemplate.route.points[1].y = UnitPoint.y
@@ -11894,8 +12864,9 @@ function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex )
-- Apply SpawnFormation
for UnitID = 1, #SpawnTemplate.units do
- SpawnTemplate.units[UnitID].x = ZonePoint.x
- SpawnTemplate.units[UnitID].y = ZonePoint.y
+ local ZonePointUnit = Zone:GetRandomPointVec2()
+ SpawnTemplate.units[UnitID].x = ZonePointUnit.x
+ SpawnTemplate.units[UnitID].y = ZonePointUnit.y
self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
end
@@ -12124,7 +13095,6 @@ function SPAWN:_InitializeSpawnGroups( SpawnIndex )
self.SpawnGroups[SpawnIndex].Visible = false
self.SpawnGroups[SpawnIndex].Spawned = false
self.SpawnGroups[SpawnIndex].UnControlled = false
- self.SpawnGroups[SpawnIndex].Spawned = false
self.SpawnGroups[SpawnIndex].SpawnTime = 0
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
@@ -12178,6 +13148,9 @@ end
--- Gets the Group Template from the ME environment definition.
-- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @return @SPAWN self
function SPAWN:_GetTemplate( SpawnTemplatePrefix )
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
@@ -12198,6 +13171,10 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix )
end
--- Prepares the new Group Template.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @param #number SpawnIndex
+-- @return #SPAWN self
function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
@@ -12208,6 +13185,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex )
SpawnTemplate.lateActivation = false
if SpawnTemplate.SpawnCategoryID == Group.Category.GROUND then
+ self:T( "For ground units, visible needs to be false..." )
SpawnTemplate.visible = false
end
@@ -12227,7 +13205,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex )
end
---- Internal function randomizing the routes.
+--- Private method randomizing the routes.
-- @param #SPAWN self
-- @param #number SpawnIndex The index of the group to be spawned.
-- @return #SPAWN
@@ -12251,7 +13229,10 @@ function SPAWN:_RandomizeRoute( SpawnIndex )
return self
end
-
+--- Private method that randomizes the template of the group.
+-- @param #SPAWN self
+-- @param #number SpawnIndex
+-- @return #SPAWN self
function SPAWN:_RandomizeTemplate( SpawnIndex )
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
@@ -12261,6 +13242,7 @@ function SPAWN:_RandomizeTemplate( SpawnIndex )
self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time
for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading
end
@@ -12443,19 +13425,10 @@ end
function SPAWN:_Scheduler()
self:F( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
- if self.SpawnInit or self.SpawnCurrentTimer == self.SpawnSetTimer then
- -- Validate if there are still groups left in the batch...
- self:Spawn()
- self.SpawnInit = false
- if self.SpawnIsScheduled == true then
- --local ClientUnit = #AlivePlayerUnits()
- self.AliveFactor = 1 -- ( 10 - ClientUnit ) / 10
- self.SpawnCurrentTimer = 0
- self.SpawnSetTimer = math.random( self.SpawnLowTimer * self.AliveFactor , self.SpawnHighTimer * self.AliveFactor )
- end
- else
- self.SpawnCurrentTimer = self.SpawnCurrentTimer + 1
- end
+ -- Validate if there are still groups left in the batch...
+ self:Spawn()
+
+ return true
end
function SPAWN:_SpawnCleanUpScheduler()
@@ -12487,6 +13460,8 @@ function SPAWN:_SpawnCleanUpScheduler()
end
+ return true -- Repeat
+
end
--- Limit the simultaneous movement of Groups within a running Mission.
-- This module is defined to improve the performance in missions, and to bring additional realism for GROUND vehicles.
@@ -12754,71 +13729,92 @@ function SEAD:EventShot( Event )
end
end
--- Taking the lead of AI escorting your flight.
--- The ESCORT class allows you to interact with escorting AI on your flight and take the lead.
+--
+-- @{#ESCORT} class
+-- ================
+-- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead.
-- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10).
--
-- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes.
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
--
+-- RADIO MENUs that can be created:
+-- ================================
-- Find a summary below of the current available commands:
--
--- **1. Navigation ...:** Escort group navigation functions:
+-- Navigation ...:
+-- ---------------
+-- Escort group navigation functions:
--
--- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you.
--- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
--- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
+-- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you.
+-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
+-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
--
--- **2. Hold position ...:** Escort group navigation functions:
+-- Hold position ...:
+-- ------------------
+-- Escort group navigation functions:
--
--- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
--- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
+-- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
+-- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
--
--- **3. Report targets ...:** Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
+-- Report targets ...:
+-- -------------------
+-- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
--
--- * **"Report now":** Will report the current detected targets.
--- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list.
--- * **"Report targets off":** Will stop detecting targets.
+-- * **"Report now":** Will report the current detected targets.
+-- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list.
+-- * **"Report targets off":** Will stop detecting targets.
--
--- **4. Scan targets ...:** Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
+-- Scan targets ...:
+-- -----------------
+-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
--
--- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
--- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
+-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
+-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
--
--- **5. Attack targets ...:** This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
+-- Attack targets ...:
+-- -------------------
+-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
--
--- **6. Request assistance from ...:** This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**.
+-- Request assistance from ...:
+-- ----------------------------
+-- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**.
-- This menu item allows to request attack support from other escorts supporting the current client group.
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
--
--- **7. ROE ...:** Defines the Rules of Engagement of the escort group when in flight.
+-- ROE ...:
+-- --------
+-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
--
--- * **"Hold Fire":** The escort group will hold fire.
--- * **"Return Fire":** The escort group will return fire.
--- * **"Open Fire":** The escort group will open fire on designated targets.
--- * **"Weapon Free":** The escort group will engage with any target.
+-- * **"Hold Fire":** The escort group will hold fire.
+-- * **"Return Fire":** The escort group will return fire.
+-- * **"Open Fire":** The escort group will open fire on designated targets.
+-- * **"Weapon Free":** The escort group will engage with any target.
--
--- **8. Evasion ...:** Will define the evasion techniques that the escort group will perform during flight or combat.
+-- Evasion ...:
+-- ------------
+-- Will define the evasion techniques that the escort group will perform during flight or combat.
--
--- * **"Fight until death":** The escort group will have no reaction to threats.
--- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
--- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
--- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
+-- * **"Fight until death":** The escort group will have no reaction to threats.
+-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
+-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
+-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
--
--- **9. Resume Mission ...:** Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
+-- Resume Mission ...:
+-- -------------------
+-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
--
--- **10. Abort Current Task:** Cancel the current task and rejoin formation.
---
--- 1. ESCORT object construction methods.
--- --------------------------------------
+-- ESCORT construction methods.
+-- ============================
-- Create a new SPAWN object with the @{#ESCORT.New} method:
--
-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text.
--
--- 2. ESCORT object initialization methods.
--- ----------------------------------------
--- The following menus can be setup:
+-- ESCORT initialization methods.
+-- ==============================
+-- The following menus are created within the RADIO MENU of an active unit hosted by a player:
--
-- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client.
-- * @{#ESCORT.MenuHoldAtEscortPosition}: Creates a menu to hold the escort at its current position.
@@ -12832,7 +13828,7 @@ end
-- * @{#ESCORT.MenuROE: Creates a menu structure to set the rules of engagement of the escort.
-- * @{#ESCORT.MenuEvasion: Creates a menu structure to set the evasion techniques when the escort is under threat.
-- * @{#ESCORT.MenuResumeMission}: Creates a menu structure so that the escort can resume from a waypoint.
---
+--
-- @module Escort
-- @author FlightControl
@@ -12842,8 +13838,7 @@ Include.File( "Database" )
Include.File( "Group" )
Include.File( "Zone" )
-
---- ESCORT class
+---
-- @type ESCORT
-- @extends Base#BASE
-- @field Client#CLIENT EscortClient
@@ -13980,3 +14975,85 @@ function ESCORT:_ReportTargetsScheduler()
self.ReportTargetsScheduler = nil
end
end
+--- Provides missile training functions.
+-- @module MissileTrainer
+-- @author FlightControl
+
+Include.File( "Client" )
+Include.File( "Scheduler" )
+
+--- The MISSILETRAINER class
+-- @type MISSILETRAINER
+-- @extends Base#BASE
+MISSILETRAINER = {
+ ClassName = "MISSILETRAINER",
+}
+
+--- Creates the main object which is handling missile tracking.
+-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
+-- @param #MISSILETRAINER
+-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
+-- @return #MISSILETRAINER
+function MISSILETRAINER:New( Distance )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( Distance )
+
+ self.Schedulers = {}
+ self.SchedulerID = 0
+
+ self.Distance = Distance
+
+ _EVENTDISPATCHER:OnShot( self._EventShot, self )
+
+ return self
+end
+
+--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
+-- @see MISSILETRAINER
+function MISSILETRAINER:_EventShot( Event )
+ self:F( { Event } )
+
+ local TrainerSourceDCSUnit = Event.IniDCSUnit
+ local TrainerSourceDCSUnitName = Event.IniDCSUnitName
+ local TrainerWeapon = Event.Weapon -- Identify the weapon fired
+ local TrainerWeaponName = Event.WeaponName -- return weapon type
+
+ self:T( "Missile Launched = " .. TrainerWeaponName )
+
+ local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target
+ local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
+ local TrainerTargetDCSGroup = TrainerTargetDCSUnit:getGroup()
+ local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName()
+ local TrainerTargetSkill = _DATABASE.Units[TrainerTargetDCSUnitName].Template.skill
+
+ self:T( TrainerTargetSkill )
+
+ if TrainerTargetSkill == "Client" or TrainerTargetSkill == "Player" then
+ self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit }, 0.5, 0.05, 0 )
+ end
+end
+
+function MISSILETRAINER:_FollowMissile( TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit )
+ self:F( { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit } )
+
+ local TrainerSourceUnit = UNIT:New( TrainerSourceDCSUnit )
+ local TrainerTargetUnit = UNIT:New( TrainerTargetDCSUnit )
+
+ local PositionMissile = TrainerWeapon:getPoint()
+ local PositionTarget = TrainerTargetUnit:GetPositionVec3()
+
+ local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 +
+ ( PositionMissile.y - PositionTarget.y )^2 +
+ ( PositionMissile.z - PositionTarget.z )^2
+ ) ^ 0.5
+
+ MESSAGE:New( "Distance Missle = " .. Distance, nil, 0.2, "/Missile" ):ToAll()
+
+ if Distance <= self.Distance then
+ TrainerWeapon:destroy()
+ MESSAGE:New( "Missle Destroyed", nil, 5, "/Missile" ):ToAll()
+ return false
+ end
+
+ return true
+end
diff --git a/Loaders/MOOSE-Template.miz b/Loaders/MOOSE-Template.miz
index 0590654ec..14122c017 100644
Binary files a/Loaders/MOOSE-Template.miz and b/Loaders/MOOSE-Template.miz differ
diff --git a/Loaders/Moose_Create_Embedded.bat b/Loaders/Moose_Create_Embedded.bat
deleted file mode 100644
index 4627a58d0..000000000
--- a/Loaders/Moose_Create_Embedded.bat
+++ /dev/null
@@ -1,31 +0,0 @@
-rem Generate Moose_Embedded.lua
-
-copy Trace.lua ^
- + Routines.lua ^
- + Base.lua ^
- + Menu.lua ^
- + Group.lua ^
- + Unit.lua ^
- + Zone.lua ^
- + Database.lua ^
- + Cargo.lua ^
- + Client.lua ^
- + Message.lua ^
- + Stage.lua ^
- + Task.lua ^
- + GoHomeTask.lua ^
- + DestroyBaseTask.lua ^
- + DestroyGroupsTask.lua ^
- + DestroyRadarsTask.lua ^
- + DestroyUnitTypesTask.lua ^
- + PickupTask.lua ^
- + DeployTask.lua ^
- + NoTask.lua ^
- + RouteTask.lua ^
- + Mission.lua ^
- + CleanUp.lua ^
- + Spawn.lua ^
- + Movement.lua ^
- + Sead.lua ^
- Moose_Embedded.lua
-
\ No newline at end of file
diff --git a/Moose/Base.lua b/Moose/Base.lua
index fe6affc98..c96e27047 100644
--- a/Moose/Base.lua
+++ b/Moose/Base.lua
@@ -1,4 +1,55 @@
---- The BASE class for all the classes defined within MOOSE.
+--- BASE classes.
+--
+-- @{#BASE} class
+-- ==============
+-- The @{#BASE} class is the super class for most of the classes defined within MOOSE.
+--
+-- It handles:
+--
+-- * The construction and inheritance of child classes.
+-- * The tracing of objects during mission execution within the DCS.log file (under saved games folder).
+--
+-- Note: Normally you would not use the BASE class unless you are extending the MOOSE framework with new classes.
+--
+-- BASE Trace functionality
+-- ========================
+-- The BASE class contains trace methods to trace progress within a mission execution of a certain object.
+-- Note that these trace methods are inherited by each MOOSE class interiting BASE.
+-- As such, each object created from derived class from BASE can use the tracing functions to trace its execution.
+--
+-- Trace a function call
+-- ---------------------
+-- There are basically 3 types of tracing methods available within BASE:
+--
+-- * @{#BASE.F}: Trace the beginning of a function and its given parameters.
+-- * @{#BASE.T}: Trace further logic within a function giving optional variables or parameters.
+-- * @{#BASE.E}: Trace an execption within a function giving optional variables or parameters. An exception will always be traced.
+--
+-- Tracing levels
+-- --------------
+-- There are 3 tracing levels within MOOSE.
+-- These tracing levels were defined to avoid bulks of tracing to be generated by lots of objects.
+--
+-- As such, the F and T methods have additional variants to trace level 2 and 3 respectively:
+--
+-- * @{#BASE.F2}: Trace the beginning of a function and its given parameters with tracing level 2.
+-- * @{#BASE.F3}: Trace the beginning of a function and its given parameters with tracing level 3.
+-- * @{#BASE.T2}: Trace further logic within a function giving optional variables or parameters with tracing level 2.
+-- * @{#BASE.T3}: Trace further logic within a function giving optional variables or parameters with tracing level 3.
+--
+-- BASE Inheritance support
+-- ========================
+-- The following methods are available to support inheritance:
+--
+-- * @{#BASE.Inherit}: Inherits from a class.
+-- * @{#BASE.Inherited}: Returns the parent class from the class.
+--
+-- Future
+-- ======
+-- Further methods may be added to BASE whenever there is a need to make "overall" functions available within MOOSE.
+--
+-- ====
+--
-- @module Base
-- @author FlightControl
@@ -302,6 +353,7 @@ end
-- @param #number Level
function BASE:TraceLevel( Level )
_TraceLevel = Level
+ self:E( "Tracing level " .. Level )
end
--- Set tracing for a class
@@ -310,6 +362,7 @@ end
function BASE:TraceClass( Class )
_TraceClass[Class] = true
_TraceClassMethod[Class] = {}
+ self:E( "Tracing class " .. Class )
end
--- Set tracing for a specific method of class
@@ -322,6 +375,7 @@ function BASE:TraceClassMethod( Class, Method )
_TraceClassMethod[Class].Method = {}
end
_TraceClassMethod[Class].Method[Method] = true
+ self:E( "Tracing method " .. Method .. " of class " .. Class )
end
--- Trace a function call. Must be at the beginning of the function logic.
diff --git a/Moose/Cargo.lua b/Moose/Cargo.lua
index c9e09135d..eb3152f91 100644
--- a/Moose/Cargo.lua
+++ b/Moose/Cargo.lua
@@ -31,18 +31,31 @@ CARGO_ZONE = {
}
}
-function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, BASE:New() )
+--- Creates a new zone where cargo can be collected or deployed.
+-- The zone functionality is useful to smoke or indicate routes for cargo pickups or deployments.
+-- Provide the zone name as declared in the mission file into the CargoZoneName in the :New method.
+-- An optional parameter is the CargoHostName, which is a Group declared with Late Activation switched on in the mission file.
+-- The CargoHostName is the "host" of the cargo zone:
+--
+-- * It will smoke the zone position when a client is approaching the zone.
+-- * Depending on the cargo type, it will assist in the delivery of the cargo by driving to and from the client.
+--
+-- @param #CARGO_ZONE self
+-- @param #string CargoZoneName The name of the zone as declared within the mission editor.
+-- @param #string CargoHostName The name of the Group "hosting" the zone. The Group MUST NOT be a static, and must be a "mobile" unit.
+function CARGO_ZONE:New( CargoZoneName, CargoHostName ) local self = BASE:Inherit( self, ZONE:New( CargoZoneName ) )
self:F( { CargoZoneName, CargoHostName } )
self.CargoZoneName = CargoZoneName
- self.CargoZone = trigger.misc.getZone( CargoZoneName )
+ self.SignalHeight = 2
+ --self.CargoZone = trigger.misc.getZone( CargoZoneName )
if CargoHostName then
self.CargoHostName = CargoHostName
end
- self:T( self.CargoZone )
+ self:T( self.CargoZoneName )
return self
end
@@ -50,17 +63,19 @@ end
function CARGO_ZONE:Spawn()
self:F( self.CargoHostName )
- if self.CargoHostSpawn then
- local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex()
- if CargoHostGroup and CargoHostGroup:IsAlive() then
- else
- self.CargoHostSpawn:ReSpawn( 1 )
- end
- else
- self:T( "Initialize CargoHostSpawn" )
- self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 )
- self.CargoHostSpawn:ReSpawn( 1 )
- end
+ if self.CargoHostName then -- Only spawn a host in the zone when there is one given as a parameter in the New function.
+ if self.CargoHostSpawn then
+ local CargoHostGroup = self.CargoHostSpawn:GetGroupFromIndex()
+ if CargoHostGroup and CargoHostGroup:IsAlive() then
+ else
+ self.CargoHostSpawn:ReSpawn( 1 )
+ end
+ else
+ self:T( "Initialize CargoHostSpawn" )
+ self.CargoHostSpawn = SPAWN:New( self.CargoHostName ):Limit( 1, 1 )
+ self.CargoHostSpawn:ReSpawn( 1 )
+ end
+ end
return self
end
@@ -114,6 +129,7 @@ function CARGO_ZONE:ReportCargosToClient( Client, CargoType )
end
end
+
function CARGO_ZONE:Signal()
self:F()
@@ -148,16 +164,15 @@ function CARGO_ZONE:Signal()
else
- local CurrentPosition = { x = self.CargoZone.point.x, y = self.CargoZone.point.z }
- self.CargoZone.point.y = land.getHeight( CurrentPosition ) + 2
+ local ZonePointVec3 = self:GetPointVec3( self.SignalHeight ) -- Get the zone position + the landheight + 2 meters
if self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.SMOKE.ID then
- trigger.action.smoke( self.CargoZone.point, self.SignalColor.TRIGGERCOLOR )
+ trigger.action.smoke( ZonePointVec3, self.SignalColor.TRIGGERCOLOR )
Signalled = true
elseif self.SignalType.ID == CARGO_ZONE.SIGNAL.TYPE.FLARE.ID then
- trigger.action.signalFlare( self.CargoZone.point, self.SignalColor.TRIGGERCOLOR, 0 )
+ trigger.action.signalFlare( ZonePointVec3, self.SignalColor.TRIGGERCOLOR, 0 )
Signalled = false
end
@@ -168,85 +183,121 @@ function CARGO_ZONE:Signal()
end
-function CARGO_ZONE:WhiteSmoke()
+function CARGO_ZONE:WhiteSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE
+
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
return self
end
-function CARGO_ZONE:BlueSmoke()
+function CARGO_ZONE:BlueSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.BLUE
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:RedSmoke()
+function CARGO_ZONE:RedSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:OrangeSmoke()
+function CARGO_ZONE:OrangeSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.ORANGE
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:GreenSmoke()
+function CARGO_ZONE:GreenSmoke( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.SMOKE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:WhiteFlare()
+function CARGO_ZONE:WhiteFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.WHITE
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:RedFlare()
+function CARGO_ZONE:RedFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.RED
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:GreenFlare()
+function CARGO_ZONE:GreenFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.GREEN
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
-function CARGO_ZONE:YellowFlare()
+function CARGO_ZONE:YellowFlare( SignalHeight )
self:F()
self.SignalType = CARGO_ZONE.SIGNAL.TYPE.FLARE
self.SignalColor = CARGO_ZONE.SIGNAL.COLOR.YELLOW
+ if SignalHeight then
+ self.SignalHeight = SignalHeight
+ end
+
return self
end
@@ -517,6 +568,7 @@ function CARGO_GROUP:Spawn( Client )
self.CargoGroupName = self.CargoSpawn:SpawnFromUnit( self.CargoZone:GetCargoHostUnit(), 60, 30, 1 ):GetName()
else
--- ReSpawn the Cargo in the CargoZone without a host ...
+ self:T( self.CargoZone )
self.CargoGroupName = self.CargoSpawn:SpawnInZone( self.CargoZone, true, 1 ):GetName()
end
self:StatusNone()
@@ -557,64 +609,72 @@ function CARGO_GROUP:OnBoard( Client, LandingZone, OnBoardSide )
local CargoGroup = Group.getByName( self.CargoGroupName )
- local CargoUnits = CargoGroup:getUnits()
- local CargoPos = CargoUnits[1]:getPoint()
+ local CargoUnit = CargoGroup:getUnit(1)
+ local CargoPos = CargoUnit:getPoint()
+
+ self.CargoInAir = CargoUnit:inAir()
+
+ self:T( self.CargoInAir )
+ -- Only move the group to the carrier when the cargo is not in the air
+ -- (eg. cargo can be on a oil derrick, moving the cargo on the oil derrick will drop the cargo on the sea).
+ if not self.CargoInAir then
- local Points = {}
-
- self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z )
- self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z )
-
- Points[#Points+1] = routines.ground.buildWP( CargoPos, "Cone", 10 )
-
- self:T( 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y )
-
- if OnBoardSide == nil then
- OnBoardSide = CLIENT.ONBOARDSIDE.NONE
- end
-
- if OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then
-
- self:T( "TransportCargoOnBoard: Onboarding LEFT" )
- CarrierPosMove.z = CarrierPosMove.z - 25
- CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then
-
- self:T( "TransportCargoOnBoard: Onboarding RIGHT" )
- CarrierPosMove.z = CarrierPosMove.z + 25
- CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.BACK then
-
- self:T( "TransportCargoOnBoard: Onboarding BACK" )
- CarrierPosMove.x = CarrierPosMove.x - 25
- CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then
-
- self:T( "TransportCargoOnBoard: Onboarding FRONT" )
- CarrierPosMove.x = CarrierPosMove.x + 25
- CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5
- Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
- Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
-
- elseif OnBoardSide == CLIENT.ONBOARDSIDE.NONE then
-
- self:T( "TransportCargoOnBoard: Onboarding CENTRAL" )
- Points[#Points+1] = routines.ground.buildWP( CarrierPos, "Cone", 10 )
-
- end
- self:T( "TransportCargoOnBoard: Routing " .. self.CargoGroupName )
-
- routines.scheduleFunction( routines.goRoute, { self.CargoGroupName, Points}, timer.getTime() + 4 )
+ local Points = {}
+
+ self:T( 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z )
+ self:T( 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z )
+
+ Points[#Points+1] = routines.ground.buildWP( CargoPos, "Cone", 10 )
+
+ self:T( 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y )
+
+ if OnBoardSide == nil then
+ OnBoardSide = CLIENT.ONBOARDSIDE.NONE
+ end
+
+ if OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then
+
+ self:T( "TransportCargoOnBoard: Onboarding LEFT" )
+ CarrierPosMove.z = CarrierPosMove.z - 25
+ CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then
+
+ self:T( "TransportCargoOnBoard: Onboarding RIGHT" )
+ CarrierPosMove.z = CarrierPosMove.z + 25
+ CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.BACK then
+
+ self:T( "TransportCargoOnBoard: Onboarding BACK" )
+ CarrierPosMove.x = CarrierPosMove.x - 25
+ CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then
+
+ self:T( "TransportCargoOnBoard: Onboarding FRONT" )
+ CarrierPosMove.x = CarrierPosMove.x + 25
+ CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "Cone", 10 )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "Cone", 10 )
+
+ elseif OnBoardSide == CLIENT.ONBOARDSIDE.NONE then
+
+ self:T( "TransportCargoOnBoard: Onboarding CENTRAL" )
+ Points[#Points+1] = routines.ground.buildWP( CarrierPos, "Cone", 10 )
+
+ end
+ self:T( "TransportCargoOnBoard: Routing " .. self.CargoGroupName )
+
+ routines.scheduleFunction( routines.goRoute, { self.CargoGroupName, Points}, timer.getTime() + 4 )
+ end
self:StatusLoading( Client )
@@ -628,12 +688,19 @@ function CARGO_GROUP:OnBoarded( Client, LandingZone )
local OnBoarded = false
- local CargoGroup = Group.getByName( self.CargoGroupName )
- if routines.IsPartOfGroupInRadius( CargoGroup, Client:ClientPosition(), 25 ) then
- CargoGroup:destroy()
- self:StatusLoaded( Client )
- OnBoarded = true
- end
+ local CargoGroup = Group.getByName( self.CargoGroupName )
+
+ if not self.CargoInAir then
+ if routines.IsPartOfGroupInRadius( CargoGroup, Client:ClientPosition(), 25 ) then
+ CargoGroup:destroy()
+ self:StatusLoaded( Client )
+ OnBoarded = true
+ end
+ else
+ CargoGroup:destroy()
+ self:StatusLoaded( Client )
+ OnBoarded = true
+ end
return OnBoarded
end
diff --git a/Moose/Database.lua b/Moose/Database.lua
index 109566d2f..631bd665b 100644
--- a/Moose/Database.lua
+++ b/Moose/Database.lua
@@ -1,15 +1,7 @@
----
--- @module DATABASE
--- @author FlightControl
-
-Include.File( "Routines" )
-Include.File( "Base" )
-Include.File( "Menu" )
-Include.File( "Group" )
-Include.File( "Event" )
-
----
--- Administers the Initial Sets of the Mission Templates as defined within the Mission Editor.
+--- Manage sets of units and groups.
+--
+-- @{#Database} class
+-- ==================
-- Mission designers can use the DATABASE class to build sets of units belonging to certain:
--
-- * Coalitions
@@ -18,9 +10,11 @@ Include.File( "Event" )
-- * Unit types
-- * Starting with certain prefix strings.
--
--- This list will grow over time. Planned developments are to include filters and iterators.
--- Additional filters will be added around @{Zone#ZONEs}, Radiuses, Active players, ...
--- More iterators will be implemented in the near future ...
+-- This list will grow over time. Planned developments are to include filters and iterators.
+-- Additional filters will be added around @{Zone#ZONEs}, Radiuses, Active players, ...
+-- More iterators will be implemented in the near future ...
+--
+-- Administers the Initial Sets of the Mission Templates as defined within the Mission Editor.
--
-- DATABASE construction methods:
-- =================================
@@ -64,6 +58,17 @@ Include.File( "Event" )
-- * @{#DATABASE.ForEachGroup}: Calls a function for each group contained within the DATABASE.
-- * @{#DATABASE.ForEachUnitInZone}: Calls a function for each unit within a certain zone contained within the DATABASE.
--
+-- ====
+-- @module Database
+-- @author FlightControl
+
+Include.File( "Routines" )
+Include.File( "Base" )
+Include.File( "Menu" )
+Include.File( "Group" )
+Include.File( "Event" )
+
+--- DATABASE class
-- @type DATABASE
-- @extends Base#BASE
DATABASE = {
@@ -277,6 +282,9 @@ end
-- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined:
-- SpawnCountryID, SpawnCategoryID
-- This method is used by the SPAWN class.
+-- @param #DATABASE self
+-- @param #table SpawnTemplate
+-- @return #DATABASE self
function DATABASE:Spawn( SpawnTemplate )
self:F( SpawnTemplate.name )
@@ -325,7 +333,10 @@ function DATABASE:GetStatusGroup( GroupName )
end
end
---- Registers new Group Templates within the DATABASE Object.
+--- Private method that registers new Group Templates within the DATABASE Object.
+-- @param #DATABASE self
+-- @param #table GroupTemplate
+-- @return #DATABASE self
function DATABASE:_RegisterGroup( GroupTemplate )
local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name)
@@ -334,6 +345,12 @@ function DATABASE:_RegisterGroup( GroupTemplate )
self.Groups[GroupTemplateName] = {}
self.Groups[GroupTemplateName].Status = nil
end
+
+ -- Delete the spans from the route, it is not needed and takes memory.
+ if GroupTemplate.route and GroupTemplate.route.spans then
+ GroupTemplate.route.spans = nil
+ end
+
self.Groups[GroupTemplateName].GroupName = GroupTemplateName
self.Groups[GroupTemplateName].Template = GroupTemplate
self.Groups[GroupTemplateName].groupId = GroupTemplate.groupId
diff --git a/Moose/Escort.lua b/Moose/Escort.lua
index 2e654db49..e45a3c876 100644
--- a/Moose/Escort.lua
+++ b/Moose/Escort.lua
@@ -1,69 +1,90 @@
--- Taking the lead of AI escorting your flight.
--- The ESCORT class allows you to interact with escorting AI on your flight and take the lead.
+--
+-- @{#ESCORT} class
+-- ================
+-- The @{#ESCORT} class allows you to interact with escorting AI on your flight and take the lead.
-- Each escorting group can be commanded with a whole set of radio commands (radio menu in your flight, and then F10).
--
-- The radio commands will vary according the category of the group. The richest set of commands are with Helicopters and AirPlanes.
-- Ships and Ground troops will have a more limited set, but they can provide support through the bombing of targets designated by the other escorts.
--
+-- RADIO MENUs that can be created:
+-- ================================
-- Find a summary below of the current available commands:
--
--- **1. Navigation ...:** Escort group navigation functions:
+-- Navigation ...:
+-- ---------------
+-- Escort group navigation functions:
--
--- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you.
--- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
--- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
+-- * **"Join-Up and Follow at x meters":** The escort group fill follow you at about x meters, and they will follow you.
+-- * **"Flare":** Provides menu commands to let the escort group shoot a flare in the air in a color.
+-- * **"Smoke":** Provides menu commands to let the escort group smoke the air in a color. Note that smoking is only available for ground and naval troops.
--
--- **2. Hold position ...:** Escort group navigation functions:
+-- Hold position ...:
+-- ------------------
+-- Escort group navigation functions:
--
--- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
--- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
+-- * **"At current location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
+-- * **"At client location":** Stops the escort group and they will hover 30 meters above the ground at the position they stopped.
--
--- **3. Report targets ...:** Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
+-- Report targets ...:
+-- -------------------
+-- Report targets will make the escort group to report any target that it identifies within a 8km range. Any detected target can be attacked using the 4. Attack nearby targets function. (see below).
--
--- * **"Report now":** Will report the current detected targets.
--- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list.
--- * **"Report targets off":** Will stop detecting targets.
+-- * **"Report now":** Will report the current detected targets.
+-- * **"Report targets on":** Will make the escort group to report detected targets and will fill the "Attack nearby targets" menu list.
+-- * **"Report targets off":** Will stop detecting targets.
--
--- **4. Scan targets ...:** Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
+-- Scan targets ...:
+-- -----------------
+-- Menu items to pop-up the escort group for target scanning. After scanning, the escort group will resume with the mission or defined task.
--
--- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
--- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
+-- * **"Scan targets 30 seconds":** Scan 30 seconds for targets.
+-- * **"Scan targets 60 seconds":** Scan 60 seconds for targets.
--
--- **5. Attack targets ...:** This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
+-- Attack targets ...:
+-- -------------------
+-- This menu item will list all detected targets within a 15km range. Depending on the level of detection (known/unknown) and visuality, the targets type will also be listed.
--
--- **6. Request assistance from ...:** This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**.
+-- Request assistance from ...:
+-- ----------------------------
+-- This menu item will list all detected targets within a 15km range, as with the menu item **Attack Targets**.
-- This menu item allows to request attack support from other escorts supporting the current client group.
-- eg. the function allows a player to request support from the Ship escort to attack a target identified by the Plane escort with its Tomahawk missiles.
-- eg. the function allows a player to request support from other Planes escorting to bomb the unit with illumination missiles or bombs, so that the main plane escort can attack the area.
--
--- **7. ROE ...:** Defines the Rules of Engagement of the escort group when in flight.
+-- ROE ...:
+-- --------
+-- Sets the Rules of Engagement (ROE) of the escort group when in flight.
--
--- * **"Hold Fire":** The escort group will hold fire.
--- * **"Return Fire":** The escort group will return fire.
--- * **"Open Fire":** The escort group will open fire on designated targets.
--- * **"Weapon Free":** The escort group will engage with any target.
+-- * **"Hold Fire":** The escort group will hold fire.
+-- * **"Return Fire":** The escort group will return fire.
+-- * **"Open Fire":** The escort group will open fire on designated targets.
+-- * **"Weapon Free":** The escort group will engage with any target.
--
--- **8. Evasion ...:** Will define the evasion techniques that the escort group will perform during flight or combat.
+-- Evasion ...:
+-- ------------
+-- Will define the evasion techniques that the escort group will perform during flight or combat.
--
--- * **"Fight until death":** The escort group will have no reaction to threats.
--- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
--- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
--- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
+-- * **"Fight until death":** The escort group will have no reaction to threats.
+-- * **"Use flares, chaff and jammers":** The escort group will use passive defense using flares and jammers. No evasive manoeuvres are executed.
+-- * **"Evade enemy fire":** The rescort group will evade enemy fire before firing.
+-- * **"Go below radar and evade fire":** The escort group will perform evasive vertical manoeuvres.
--
--- **9. Resume Mission ...:** Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
+-- Resume Mission ...:
+-- -------------------
+-- Escort groups can have their own mission. This menu item will allow the escort group to resume their Mission from a given waypoint.
-- Note that this is really fantastic, as you now have the dynamic of taking control of the escort groups, and allowing them to resume their path or mission.
--
--- **10. Abort Current Task:** Cancel the current task and rejoin formation.
---
--- 1. ESCORT object construction methods.
--- --------------------------------------
+-- ESCORT construction methods.
+-- ============================
-- Create a new SPAWN object with the @{#ESCORT.New} method:
--
-- * @{#ESCORT.New}: Creates a new ESCORT object from a @{Group#GROUP} for a @{Client#CLIENT}, with an optional briefing text.
--
--- 2. ESCORT object initialization methods.
--- ----------------------------------------
--- The following menus can be setup:
+-- ESCORT initialization methods.
+-- ==============================
+-- The following menus are created within the RADIO MENU of an active unit hosted by a player:
--
-- * @{#ESCORT.MenuFollowAt}: Creates a menu to make the escort follow the client.
-- * @{#ESCORT.MenuHoldAtEscortPosition}: Creates a menu to hold the escort at its current position.
@@ -77,7 +98,7 @@
-- * @{#ESCORT.MenuROE: Creates a menu structure to set the rules of engagement of the escort.
-- * @{#ESCORT.MenuEvasion: Creates a menu structure to set the evasion techniques when the escort is under threat.
-- * @{#ESCORT.MenuResumeMission}: Creates a menu structure so that the escort can resume from a waypoint.
---
+--
-- @module Escort
-- @author FlightControl
@@ -87,8 +108,7 @@ Include.File( "Database" )
Include.File( "Group" )
Include.File( "Zone" )
-
---- ESCORT class
+---
-- @type ESCORT
-- @extends Base#BASE
-- @field Client#CLIENT EscortClient
diff --git a/Moose/Group.lua b/Moose/Group.lua
index c7f9faa4b..e93f50b69 100644
--- a/Moose/Group.lua
+++ b/Moose/Group.lua
@@ -92,6 +92,18 @@ function GROUP:NewFromDCSUnit( DCSUnit )
return self
end
+--- Returns the name of the Group.
+-- @param #GROUP self
+-- @return #string GroupName
+function GROUP:GetName()
+
+ local GroupName = self.DCSGroup:getName()
+
+ return GroupName
+end
+
+
+
--- Retrieve the group mission and allow to place function hooks within the mission waypoint plan.
-- Use the method @{Group#GROUP:WayPointFunction} to define the hook functions for specific waypoints.
-- Use the method @{Group@GROUP:WayPointExecute) to start the execution of the new mission plan.
@@ -128,7 +140,12 @@ function GROUP:TaskFunction( WayPoint, WayPointIndex, FunctionString, FunctionAr
local DCSScript = {}
DCSScript[#DCSScript+1] = "local MissionGroup = GROUP.FindGroup( ... ) "
- DCSScript[#DCSScript+1] = FunctionString .. "( MissionGroup, " .. table.concat( FunctionArguments, "," ) .. ")"
+
+ if FunctionArguments.n > 0 then
+ DCSScript[#DCSScript+1] = FunctionString .. "( MissionGroup, " .. table.concat( FunctionArguments, "," ) .. ")"
+ else
+ DCSScript[#DCSScript+1] = FunctionString .. "( MissionGroup )"
+ end
DCSTask = self:TaskWrappedAction(
self:CommandDoScript(
@@ -594,6 +611,41 @@ function GROUP:TaskWrappedAction( DCSCommand, Index )
return DCSTaskWrappedAction
end
+--- Executes a command action
+-- @param #GROUP self
+-- @param DCSCommand#Command DCSCommand
+-- @return #GROUP self
+function GROUP:SetCommand( DCSCommand )
+ self:F( DCSCommand )
+
+ local Controller = self:_GetController()
+
+ Controller:setCommand( DCSCommand )
+
+ return self
+end
+
+--- Perform a switch waypoint command
+-- @param #GROUP self
+-- @param #number FromWayPoint
+-- @param #number ToWayPoint
+-- @return DCSTask#Task
+function GROUP:CommandSwitchWayPoint( FromWayPoint, ToWayPoint, Index )
+ self:F( { FromWayPoint, ToWayPoint, Index } )
+
+ local CommandSwitchWayPoint = {
+ id = 'SwitchWaypoint',
+ params = {
+ fromWaypointIndex = FromWayPoint,
+ goToWaypointIndex = ToWayPoint,
+ },
+ }
+
+ self:T( { CommandSwitchWayPoint } )
+ return CommandSwitchWayPoint
+end
+
+
--- Orbit at a specified position at a specified alititude during a specified duration with a specified speed.
-- @param #GROUP self
-- @param #Vec2 Point The point to hold the position.
@@ -601,7 +653,7 @@ end
-- @param #number Speed The speed flying when holding the position.
-- @return #GROUP self
function GROUP:TaskOrbitCircleAtVec2( Point, Altitude, Speed )
- self:F( { self.GroupName, Point, Altitude, Speed } )
+ self:F( { self.GroupName, Point, Altitude, Speed } )
-- pattern = enum AI.Task.OribtPattern,
-- point = Vec2,
@@ -732,8 +784,41 @@ function GROUP:TaskAttackUnit( AttackUnit )
params = { unitId = AttackUnit:GetID(),
expend = AI.Task.WeaponExpend.TWO,
groupAttack = true,
- }
- }
+ },
+ },
+
+ self:T( { DCSTask } )
+ return DCSTask
+end
+
+--- Attack a Group.
+-- @param #GROUP self
+-- @param Group#GROUP AttackGroup The Group to be attacked.
+-- @return DCSTask#Task The DCS task structure.
+function GROUP:TaskAttackGroup( AttackGroup )
+ self:F( { self.GroupName, AttackGroup } )
+
+-- AttackGroup = {
+-- id = 'AttackGroup',
+-- params = {
+-- groupId = Group.ID,
+-- weaponType = number,
+-- expend = enum AI.Task.WeaponExpend,
+-- attackQty = number,
+-- directionEnabled = boolean,
+-- direction = Azimuth,
+-- altitudeEnabled = boolean,
+-- altitude = Distance,
+-- attackQtyLimit = boolean,
+-- }
+-- }
+
+ local DCSTask
+ DCSTask = { id = 'AttackGroup',
+ params = { groupId = AttackGroup:GetID(),
+ expend = AI.Task.WeaponExpend.TWO,
+ },
+ },
self:T( { DCSTask } )
return DCSTask
diff --git a/Moose/Menu.lua b/Moose/Menu.lua
index f386e118d..e5bdfa778 100644
--- a/Moose/Menu.lua
+++ b/Moose/Menu.lua
@@ -245,3 +245,127 @@ function MENU_CLIENT_COMMAND:Remove()
self.ParentMenu.Menus[self.MenuPath] = nil
return nil
end
+
+
+--- The MENU_COALITION class
+-- @type MENU_COALITION
+-- @extends Menu#MENU
+MENU_COALITION = {
+ ClassName = "MENU_COALITION"
+}
+
+--- Creates a new coalition menu item
+-- @param #MENU_COALITION self
+-- @param DCSCoalition#coalition.side MenuCoalition The coalition owning the menu.
+-- @param #string MenuText The text for the menu.
+-- @param #table ParentMenu The parent menu.
+-- @return #MENU_COALITION self
+function MENU_COALITION:New( MenuCoalition, MenuText, ParentMenu )
+
+ -- Arrange meta tables
+ local MenuParentPath = {}
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+ self:F( { MenuCoalition, MenuText, ParentMenu } )
+
+ self.MenuCoalition = MenuCoalition
+ self.MenuParentPath = MenuParentPath
+ self.MenuText = MenuText
+ self.ParentMenu = ParentMenu
+
+ self.Menus = {}
+
+ self:T( { MenuParentPath, MenuText } )
+
+ self.MenuPath = missionCommands.addSubMenuForCoalition( self.MenuCoalition, MenuText, MenuParentPath )
+
+ self:T( { self.MenuPath } )
+
+ if ParentMenu and ParentMenu.Menus then
+ ParentMenu.Menus[self.MenuPath] = self
+ end
+ return self
+end
+
+--- Removes the sub menus recursively of this MENU_COALITION.
+-- @param #MENU_COALITION self
+-- @return #MENU_COALITION self
+function MENU_COALITION:RemoveSubMenus()
+ self:F( self.MenuPath )
+
+ for MenuID, Menu in pairs( self.Menus ) do
+ Menu:Remove()
+ end
+
+end
+
+--- Removes the sub menus recursively of this MENU_COALITION.
+-- @param #MENU_COALITION self
+-- @return #MENU_COALITION self
+function MENU_COALITION:Remove()
+ self:F( self.MenuPath )
+
+ self:RemoveSubMenus()
+ missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath )
+ self.ParentMenu.Menus[self.MenuPath] = nil
+
+ return nil
+end
+
+
+--- The MENU_COALITION_COMMAND class
+-- @type MENU_COALITION_COMMAND
+-- @extends Menu#MENU
+MENU_COALITION_COMMAND = {
+ ClassName = "MENU_COALITION_COMMAND"
+}
+
+--- Creates a new radio command item for a group
+-- @param #MENU_COALITION_COMMAND self
+-- @param DCSCoalition#coalition.side MenuCoalition The coalition 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_COALITION_COMMAND self
+function MENU_COALITION_COMMAND:New( MenuCoalition, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument )
+
+ -- Arrange meta tables
+
+ local MenuParentPath = {}
+ if ParentMenu ~= nil then
+ MenuParentPath = ParentMenu.MenuPath
+ end
+
+ local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
+
+ self.MenuCoalition = MenuCoalition
+ self.MenuParentPath = MenuParentPath
+ self.MenuText = MenuText
+ self.ParentMenu = ParentMenu
+
+ self:T( { MenuParentPath, MenuText, CommandMenuFunction, CommandMenuArgument } )
+
+ self.MenuPath = missionCommands.addCommandForCoalition( self.MenuCoalition, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument )
+
+ self.CommandMenuFunction = CommandMenuFunction
+ self.CommandMenuArgument = CommandMenuArgument
+
+ ParentMenu.Menus[self.MenuPath] = self
+
+ return self
+end
+
+--- Removes a radio command item for a coalition
+-- @param #MENU_COALITION_COMMAND self
+-- @return #MENU_COALITION_COMMAND self
+function MENU_COALITION_COMMAND:Remove()
+ self:F( self.MenuPath )
+
+ missionCommands.removeItemForCoalition( self.MenuCoalition, self.MenuPath )
+ self.ParentMenu.Menus[self.MenuPath] = nil
+ return nil
+end
diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua
new file mode 100644
index 000000000..e805103b4
--- /dev/null
+++ b/Moose/MissileTrainer.lua
@@ -0,0 +1,82 @@
+--- Provides missile training functions.
+-- @module MissileTrainer
+-- @author FlightControl
+
+Include.File( "Client" )
+Include.File( "Scheduler" )
+
+--- The MISSILETRAINER class
+-- @type MISSILETRAINER
+-- @extends Base#BASE
+MISSILETRAINER = {
+ ClassName = "MISSILETRAINER",
+}
+
+--- Creates the main object which is handling missile tracking.
+-- When a missile is fired a SCHEDULER is set off that follows the missile. When near a certain a client player, the missile will be destroyed.
+-- @param #MISSILETRAINER
+-- @param #number Distance The distance in meters when a tracked missile needs to be destroyed when close to a player.
+-- @return #MISSILETRAINER
+function MISSILETRAINER:New( Distance )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( Distance )
+
+ self.Schedulers = {}
+ self.SchedulerID = 0
+
+ self.Distance = Distance
+
+ _EVENTDISPATCHER:OnShot( self._EventShot, self )
+
+ return self
+end
+
+--- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME.
+-- @see MISSILETRAINER
+function MISSILETRAINER:_EventShot( Event )
+ self:F( { Event } )
+
+ local TrainerSourceDCSUnit = Event.IniDCSUnit
+ local TrainerSourceDCSUnitName = Event.IniDCSUnitName
+ local TrainerWeapon = Event.Weapon -- Identify the weapon fired
+ local TrainerWeaponName = Event.WeaponName -- return weapon type
+
+ self:T( "Missile Launched = " .. TrainerWeaponName )
+
+ local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target
+ local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit )
+ local TrainerTargetDCSGroup = TrainerTargetDCSUnit:getGroup()
+ local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName()
+ local TrainerTargetSkill = _DATABASE.Units[TrainerTargetDCSUnitName].Template.skill
+
+ self:T( TrainerTargetSkill )
+
+ if TrainerTargetSkill == "Client" or TrainerTargetSkill == "Player" then
+ self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit }, 0.5, 0.05, 0 )
+ end
+end
+
+function MISSILETRAINER:_FollowMissile( TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit )
+ self:F( { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit } )
+
+ local TrainerSourceUnit = UNIT:New( TrainerSourceDCSUnit )
+ local TrainerTargetUnit = UNIT:New( TrainerTargetDCSUnit )
+
+ local PositionMissile = TrainerWeapon:getPoint()
+ local PositionTarget = TrainerTargetUnit:GetPositionVec3()
+
+ local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 +
+ ( PositionMissile.y - PositionTarget.y )^2 +
+ ( PositionMissile.z - PositionTarget.z )^2
+ ) ^ 0.5
+
+ MESSAGE:New( "Distance Missle = " .. Distance, nil, 0.2, "/Missile" ):ToAll()
+
+ if Distance <= self.Distance then
+ TrainerWeapon:destroy()
+ MESSAGE:New( "Missle Destroyed", nil, 5, "/Missile" ):ToAll()
+ return false
+ end
+
+ return true
+end
diff --git a/Moose/Mission.lua b/Moose/Mission.lua
index 61b10e7a0..65e4222f4 100644
--- a/Moose/Mission.lua
+++ b/Moose/Mission.lua
@@ -474,7 +474,9 @@ function MISSIONSCHEDULER.Scheduler()
if Mission.GoalFunction ~= nil then
Mission.GoalFunction( Mission, Client )
end
- _DATABASE:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 )
+ if MISSIONSCHEDULER.Scoring then
+ MISSIONSCHEDULER.Scoring:_AddMissionTaskScore( Client:GetClientGroupDCSUnit(), Mission.Name, 25 )
+ end
-- if not Mission:IsCompleted() then
-- end
diff --git a/Moose/PickupTask.lua b/Moose/PickupTask.lua
index f93a634f6..7cbec30a6 100644
--- a/Moose/PickupTask.lua
+++ b/Moose/PickupTask.lua
@@ -31,7 +31,7 @@ function PICKUPTASK:New( CargoType, OnBoardSide )
self.CargoType = CargoType
self.GoalVerb = CargoType .. " " .. self.GoalVerb
self.OnBoardSide = OnBoardSide
- self.IsLandingRequired = false -- required to decide whether the client needs to land or not
+ self.IsLandingRequired = true -- required to decide whether the client needs to land or not
self.IsSlingLoad = false -- Indicates whether the cargo is a sling load cargo
self.Stages = { STAGE_CARGO_INIT:New(), STAGE_CARGO_LOAD:New(), STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGELOAD:New(), STAGEDONE:New() }
self.SetStage( self, 1 )
diff --git a/Moose/Routines.lua b/Moose/Routines.lua
index 4f12b2222..059f60544 100644
--- a/Moose/Routines.lua
+++ b/Moose/Routines.lua
@@ -1524,6 +1524,47 @@ function routines.IsUnitInZones( TransportUnit, LandingZones )
end
end
+function routines.IsUnitNearZonesRadius( TransportUnit, LandingZones, ZoneRadius )
+--trace.f("", "routines.IsUnitInZones" )
+
+ local TransportZoneResult = nil
+ local TransportZonePos = nil
+ local TransportZone = nil
+
+ -- fill-up some local variables to support further calculations to determine location of units within the zone.
+ if TransportUnit then
+ local TransportUnitPos = TransportUnit:getPosition().p
+ if type( LandingZones ) == "table" then
+ for LandingZoneID, LandingZoneName in pairs( LandingZones ) do
+ TransportZone = trigger.misc.getZone( LandingZoneName )
+ if TransportZone then
+ TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z}
+ if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= ZoneRadius ) then
+ TransportZoneResult = LandingZoneID
+ break
+ end
+ end
+ end
+ else
+ TransportZone = trigger.misc.getZone( LandingZones )
+ TransportZonePos = {radius = TransportZone.radius, x = TransportZone.point.x, y = TransportZone.point.y, z = TransportZone.point.z}
+ if ((( TransportUnitPos.x - TransportZonePos.x)^2 + (TransportUnitPos.z - TransportZonePos.z)^2)^0.5 <= ZoneRadius ) then
+ TransportZoneResult = 1
+ end
+ end
+ if TransportZoneResult then
+ --trace.i( "routines", "TransportZone:" .. TransportZoneResult )
+ else
+ --trace.i( "routines", "TransportZone:nil logic" )
+ end
+ return TransportZoneResult
+ else
+ --trace.i( "routines", "TransportZone:nil hard" )
+ return nil
+ end
+end
+
+
function routines.IsStaticInZones( TransportStatic, LandingZones )
--trace.f()
diff --git a/Moose/Scheduler.lua b/Moose/Scheduler.lua
new file mode 100644
index 000000000..d3a75141e
--- /dev/null
+++ b/Moose/Scheduler.lua
@@ -0,0 +1,114 @@
+--- Models time events calling event handing functions.
+-- @module Scheduler
+-- @author FlightControl
+
+Include.File( "Routines" )
+Include.File( "Base" )
+Include.File( "Cargo" )
+Include.File( "Message" )
+
+
+--- The SCHEDULER class
+-- @type SCHEDULER
+-- @extends Base#BASE
+SCHEDULER = {
+ ClassName = "SCHEDULER",
+}
+
+
+--- SCHEDULER constructor.
+-- @param #SCHEDULER self
+-- @param #table TimeEventObject
+-- @param #function TimeEventFunction
+-- @param #table TimeEventFunctionArguments
+-- @param #number StartSeconds
+-- @param #number RepeatSecondsInterval
+-- @param #number RandomizationFactor
+-- @param #number StopSeconds
+-- @return #SCHEDULER
+function SCHEDULER:New( TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds )
+ local self = BASE:Inherit( self, BASE:New() )
+ self:F( { TimeEventObject, TimeEventFunction, TimeEventFunctionArguments, StartSeconds, RepeatSecondsInterval, RandomizationFactor, StopSeconds } )
+
+ self.TimeEventObject = TimeEventObject
+ self.TimeEventFunction = TimeEventFunction
+ self.TimeEventFunctionArguments = TimeEventFunctionArguments
+ self.StartSeconds = StartSeconds
+
+ if RepeatSecondsInterval then
+ self.RepeatSecondsInterval = RepeatSecondsInterval
+ else
+ self.RepeatSecondsInterval = 0
+ end
+
+ if RandomizationFactor then
+ self.RandomizationFactor = RandomizationFactor
+ else
+ self.RandomizationFactor = 0
+ end
+
+ if StopSeconds then
+ self.StopSeconds = StopSeconds
+ end
+
+ self.Repeat = false
+
+ self.StartTime = timer.getTime()
+
+ self:Start()
+
+ return self
+end
+
+function SCHEDULER:Scheduler()
+ self:F( self.TimeEventFunctionArguments )
+
+ local ErrorHandler = function( errmsg )
+
+ env.info( "Error in SCHEDULER function:" .. errmsg )
+ env.info( debug.traceback() )
+
+ return errmsg
+ end
+
+ local Status, Result
+ if self.TimeEventObject then
+ Status, Result = xpcall( function() return self.TimeEventFunction( self.TimeEventObject, unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
+ else
+ Status, Result = xpcall( function() return self.TimeEventFunction( unpack( self.TimeEventFunctionArguments ) ) end, ErrorHandler )
+ end
+
+ self:T( { Status, Result } )
+
+ if Status and Status == true and Result and Result == true then
+ if self.Repeat and ( not self.StopSeconds or ( self.StopSeconds and timer.getTime() <= self.StartTime + self.StopSeconds ) ) then
+ timer.scheduleFunction(
+ self.Scheduler,
+ self,
+ timer.getTime() + self.RepeatSecondsInterval + math.random( - ( self.RandomizationFactor * self.RepeatSecondsInterval / 2 ), ( self.RandomizationFactor * self.RepeatSecondsInterval / 2 ) ) + 0.01
+ )
+ end
+ end
+
+end
+
+function SCHEDULER:Start()
+ self:F( self.TimeEventObject )
+
+ self.Repeat = true
+ timer.scheduleFunction( self.Scheduler, self, timer.getTime() + self.StartSeconds + .01 )
+
+ return self
+end
+
+function SCHEDULER:Stop()
+ self:F( self.TimeEventObject )
+
+ self.Repeat = false
+end
+
+
+
+
+
+
diff --git a/Moose/Scoring.lua b/Moose/Scoring.lua
index 53f05a831..55ece4484 100644
--- a/Moose/Scoring.lua
+++ b/Moose/Scoring.lua
@@ -197,7 +197,7 @@ end
function SCORING:_AddPlayerFromUnit( UnitData )
self:F( UnitData )
- if UnitData:isExist() then
+ if UnitData and UnitData:isExist() then
local UnitName = UnitData:getName()
local PlayerName = UnitData:getPlayerName()
local UnitDesc = UnitData:getDesc()
@@ -324,7 +324,7 @@ function SCORING:_EventOnHit( Event )
local InitUnitName = ""
local InitGroup = nil
local InitGroupName = ""
- local InitPlayerName = "dummy"
+ local InitPlayerName = nil
local InitCoalition = nil
local InitCategory = nil
diff --git a/Moose/Spawn.lua b/Moose/Spawn.lua
index be92d9ea0..364fad3f9 100644
--- a/Moose/Spawn.lua
+++ b/Moose/Spawn.lua
@@ -1,5 +1,8 @@
--- Dynamic spawning of groups (and units).
--- The SPAWN class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
+--
+-- @{#SPAWN} class
+-- ===============
+-- The @{#SPAWN} class allows to spawn dynamically new groups, based on pre-defined initialization settings, modifying the behaviour when groups are spawned.
-- For each group to be spawned, within the mission editor, a group has to be created with the "late activation flag" set. We call this group the *"Spawn Template"* of the SPAWN object.
-- A reference to this Spawn Template needs to be provided when constructing the SPAWN object, by indicating the name of the group within the mission editor in the constructor methods.
--
@@ -40,7 +43,7 @@
-- * @{#SPAWN.RandomizeTemplate}: Randomize the group templates so that when a new group is spawned, a random group template is selected from one of the templates defined.
-- * @{#SPAWN.Uncontrolled}: Spawn plane groups uncontrolled.
-- * @{#SPAWN.Array}: Make groups visible before they are actually activated, and order these groups like a batallion in an array.
--- * @{#SPAWN.Repeat}: Re-spawn groups when they land at the home base. Similar functions are @{#SPAWN.RepeatOnLanding} and @{#SPAWN.RepeatOnEngineShutDown}.
+-- * @{#SPAWN.InitRepeat}: Re-spawn groups when they land at the home base. Similar functions are @{#SPAWN.InitRepeatOnLanding} and @{#SPAWN.InitRepeatOnEngineShutDown}.
--
-- SPAWN spawning methods:
-- =======================
@@ -67,6 +70,7 @@
-- This models AI that has succesfully returned to their airbase, to restart their combat activities.
-- Check the @{#SPAWN.CleanUp} for further info.
--
+-- ====
-- @module Spawn
-- @author FlightControl
@@ -76,6 +80,7 @@ Include.File( "Database" )
Include.File( "Group" )
Include.File( "Zone" )
Include.File( "Event" )
+Include.File( "Scheduler" )
--- SPAWN Class
-- @type SPAWN
@@ -111,7 +116,7 @@ function SPAWN:New( SpawnTemplatePrefix )
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.SpawnRepeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
@@ -148,7 +153,7 @@ function SPAWN:NewWithAlias( SpawnTemplatePrefix, SpawnAliasPrefix )
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnIsScheduled = false -- Reflects if the spawning for this SpawnTemplatePrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnTemplatePrefix ) -- Contains the template structure for a Group Spawn from the Mission Editor. Note that this group must have lateActivation always on!!!
- self.SpawnRepeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
+ self.Repeat = false -- Don't repeat the group from Take-Off till Landing and back Take-Off by ReSpawning.
self.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnMaxUnitsAlive = 0 -- The maximum amount of groups that can be alive of SpawnTemplatePrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
@@ -270,36 +275,37 @@ end
-- -- RU Su-34 - AI Ship Attack
-- -- Re-SPAWN the Group(s) after each landing and Engine Shut-Down automatically.
-- SpawnRU_SU34 = SPAWN:New( 'TF1 RU Su-34 Krymsk@AI - Attack Ships' ):Schedule( 2, 3, 1800, 0.4 ):SpawnUncontrolled():RandomizeRoute( 1, 1, 3000 ):RepeatOnEngineShutDown()
-function SPAWN:Repeat()
+function SPAWN:InitRepeat()
self:F( { self.SpawnTemplatePrefix, self.SpawnIndex } )
- self.SpawnRepeat = true
+ self.Repeat = true
self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true
return self
end
---- Same as the @{Repeat) method.
--- @return SPAWN
--- @see Repeat
-
-function SPAWN:RepeatOnLanding()
+--- Respawn group after landing.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnLanding()
self:F( { self.SpawnTemplatePrefix } )
- self:Repeat()
+ self:InitRepeat()
self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true
return self
end
---- Same as the @{#SPAWN.Repeat) method, but now the Group will respawn after its engines have shut down.
--- @return SPAWN
-function SPAWN:RepeatOnEngineShutDown()
+
+--- Respawn after landing when its engines have shut down.
+-- @param #SPAWN self
+-- @return #SPAWN self
+function SPAWN:InitRepeatOnEngineShutDown()
self:F( { self.SpawnTemplatePrefix } )
- self:Repeat()
+ self:InitRepeat()
self.RepeatOnEngineShutDown = true
self.RepeatOnLanding = false
@@ -318,8 +324,8 @@ function SPAWN:CleanUp( SpawnCleanUpInterval )
self.SpawnCleanUpInterval = SpawnCleanUpInterval
self.SpawnCleanUpTimeStamps = {}
- self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
-
+ --self.CleanUpFunction = routines.scheduleFunction( self._SpawnCleanUpScheduler, { self }, timer.getTime() + 1, SpawnCleanUpInterval )
+ self.CleanUpScheduler = SCHEDULER:New( self, self._SpawnCleanUpScheduler, {}, 1, SpawnCleanUpInterval, 0.2 )
return self
end
@@ -374,10 +380,8 @@ function SPAWN:Array( SpawnAngle, SpawnWidth, SpawnDeltaX, SpawnDeltaY )
_EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self )
_EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnDeadOrCrash, self )
- if self.SpawnRepeat then
+ if self.Repeat then
_EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnTakeOff, self )
- end
- if self.RepeatOnLanding then
_EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[SpawnGroupID].SpawnTemplate, self._OnLand, self )
end
if self.RepeatOnEngineShutDown then
@@ -446,15 +450,15 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
_EVENTDISPATCHER:OnCrashForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self )
_EVENTDISPATCHER:OnDeadForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnDeadOrCrash, self )
- if self.SpawnRepeat then
+ if self.Repeat then
_EVENTDISPATCHER:OnTakeOffForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnTakeOff, self )
- end
- if self.RepeatOnLanding then
_EVENTDISPATCHER:OnLandForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnLand, self )
end
if self.RepeatOnEngineShutDown then
_EVENTDISPATCHER:OnEngineShutDownForTemplate( self.SpawnGroups[self.SpawnIndex].SpawnTemplate, self._OnEngineShutDown, self )
end
+
+ self:T( self.SpawnGroups[self.SpawnIndex].SpawnTemplate )
self.SpawnGroups[self.SpawnIndex].Group = _DATABASE:Spawn( self.SpawnGroups[self.SpawnIndex].SpawnTemplate )
@@ -463,7 +467,7 @@ function SPAWN:SpawnWithIndex( SpawnIndex )
self.SpawnFunctionHook( self.SpawnGroups[self.SpawnIndex].Group, unpack( self.SpawnFunctionArguments ) )
end
-- TODO: Need to fix this by putting an "R" in the name of the group when the group repeats.
- --if self.SpawnRepeat then
+ --if self.Repeat then
-- _DATABASE:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
--end
end
@@ -496,23 +500,29 @@ end
function SPAWN:SpawnScheduled( SpawnTime, SpawnTimeVariation )
self:F( { SpawnTime, SpawnTimeVariation } )
- self.SpawnCurrentTimer = 0 -- The internal timer counter to trigger a scheduled spawning of SpawnTemplatePrefix.
- self.SpawnSetTimer = 0 -- The internal timer value when a scheduled spawning of SpawnTemplatePrefix occurs.
- self.AliveFactor = 1 --
- self.SpawnLowTimer = 0
- self.SpawnHighTimer = 0
-
if SpawnTime ~= nil and SpawnTimeVariation ~= nil then
- self.SpawnLowTimer = SpawnTime - SpawnTime / 2 * SpawnTimeVariation
- self.SpawnHighTimer = SpawnTime + SpawnTime / 2 * SpawnTimeVariation
- self:SpawnScheduleStart()
+ self.SpawnScheduler = SCHEDULER:New( self, self._Scheduler, {}, 1, SpawnTime, SpawnTimeVariation )
end
- self:T( { self.SpawnLowTimer, self.SpawnHighTimer } )
-
return self
end
+--- Will re-start the spawning scheduler.
+-- Note: This function is only required to be called when the schedule was stopped.
+function SPAWN:SpawnScheduleStart()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self.SpawnScheduler:Start()
+end
+
+--- Will stop the scheduled spawning scheduler.
+function SPAWN:SpawnScheduleStop()
+ self:F( { self.SpawnTemplatePrefix } )
+
+ self.SpawnScheduler:Stop()
+end
+
+
--- Allows to place a CallFunction hook when a new group spawns.
-- The provided function will be called when a new group is spawned, including its given parameters.
-- The first parameter of the SpawnFunction is the @{Group#GROUP} that was spawned.
@@ -534,30 +544,6 @@ end
---- Will start the spawning scheduler.
--- Note: This function is called automatically when @{#SPAWN.Scheduled} is called.
-function SPAWN:SpawnScheduleStart()
- self:F( { self.SpawnTemplatePrefix } )
-
- --local ClientUnit = #AlivePlayerUnits()
-
- self.AliveFactor = 10 -- ( 10 - ClientUnit ) / 10
-
- if self.SpawnIsScheduled == false then
- self.SpawnIsScheduled = true
- self.SpawnInit = true
- self.SpawnSetTimer = math.random( self.SpawnLowTimer * self.AliveFactor / 10 , self.SpawnHighTimer * self.AliveFactor / 10 )
-
- self.SpawnFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, 1 )
- end
-end
-
---- Will stop the scheduled spawning scheduler.
-function SPAWN:SpawnScheduleStop()
- self:F( { self.SpawnTemplatePrefix } )
-
- self.SpawnIsScheduled = false
-end
--- Will spawn a group from a hosting unit. This function is mostly advisable to be used if you want to simulate spawning from air units, like helicopters, which are dropping infantry into a defined Landing Zone.
-- Note that each point in the route assigned to the spawning group is reset to the point of the spawn.
@@ -586,6 +572,9 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex )
if SpawnTemplate then
local UnitPoint = HostUnit:GetPointVec2()
+
+ self:T( { "Current point of ", self.SpawnTemplatePrefix, UnitPoint } )
+
--for PointID, Point in pairs( SpawnTemplate.route.points ) do
--Point.x = UnitPoint.x
--Point.y = UnitPoint.y
@@ -593,9 +582,6 @@ function SPAWN:SpawnFromUnit( HostUnit, OuterRadius, InnerRadius, SpawnIndex )
--Point.alt_type = nil
--end
- SpawnTemplate.route.points = nil
- SpawnTemplate.route.points = {}
- SpawnTemplate.route.points[1] = {}
SpawnTemplate.route.points[1].x = UnitPoint.x
SpawnTemplate.route.points[1].y = UnitPoint.y
@@ -676,8 +662,9 @@ function SPAWN:SpawnInZone( Zone, ZoneRandomize, SpawnIndex )
-- Apply SpawnFormation
for UnitID = 1, #SpawnTemplate.units do
- SpawnTemplate.units[UnitID].x = ZonePoint.x
- SpawnTemplate.units[UnitID].y = ZonePoint.y
+ local ZonePointUnit = Zone:GetRandomPointVec2()
+ SpawnTemplate.units[UnitID].x = ZonePointUnit.x
+ SpawnTemplate.units[UnitID].y = ZonePointUnit.y
self:T( 'SpawnTemplate.units['..UnitID..'].x = ' .. SpawnTemplate.units[UnitID].x .. ', SpawnTemplate.units['..UnitID..'].y = ' .. SpawnTemplate.units[UnitID].y )
end
@@ -906,7 +893,6 @@ function SPAWN:_InitializeSpawnGroups( SpawnIndex )
self.SpawnGroups[SpawnIndex].Visible = false
self.SpawnGroups[SpawnIndex].Spawned = false
self.SpawnGroups[SpawnIndex].UnControlled = false
- self.SpawnGroups[SpawnIndex].Spawned = false
self.SpawnGroups[SpawnIndex].SpawnTime = 0
self.SpawnGroups[SpawnIndex].SpawnTemplatePrefix = self.SpawnTemplatePrefix
@@ -960,6 +946,9 @@ end
--- Gets the Group Template from the ME environment definition.
-- This method used the @{DATABASE} object, which contains ALL initial and new spawned object in MOOSE.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @return @SPAWN self
function SPAWN:_GetTemplate( SpawnTemplatePrefix )
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix, SpawnTemplatePrefix } )
@@ -980,6 +969,10 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix )
end
--- Prepares the new Group Template.
+-- @param #SPAWN self
+-- @param #string SpawnTemplatePrefix
+-- @param #number SpawnIndex
+-- @return #SPAWN self
function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex )
self:F( { self.SpawnTemplatePrefix, self.SpawnAliasPrefix } )
@@ -990,6 +983,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex )
SpawnTemplate.lateActivation = false
if SpawnTemplate.SpawnCategoryID == Group.Category.GROUND then
+ self:T( "For ground units, visible needs to be false..." )
SpawnTemplate.visible = false
end
@@ -1009,7 +1003,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex )
end
---- Internal function randomizing the routes.
+--- Private method randomizing the routes.
-- @param #SPAWN self
-- @param #number SpawnIndex The index of the group to be spawned.
-- @return #SPAWN
@@ -1033,7 +1027,10 @@ function SPAWN:_RandomizeRoute( SpawnIndex )
return self
end
-
+--- Private method that randomizes the template of the group.
+-- @param #SPAWN self
+-- @param #number SpawnIndex
+-- @return #SPAWN self
function SPAWN:_RandomizeTemplate( SpawnIndex )
self:F( { self.SpawnTemplatePrefix, SpawnIndex } )
@@ -1043,6 +1040,7 @@ function SPAWN:_RandomizeTemplate( SpawnIndex )
self.SpawnGroups[SpawnIndex].SpawnTemplate.route = routines.utils.deepCopy( self.SpawnTemplate.route )
self.SpawnGroups[SpawnIndex].SpawnTemplate.x = self.SpawnTemplate.x
self.SpawnGroups[SpawnIndex].SpawnTemplate.y = self.SpawnTemplate.y
+ self.SpawnGroups[SpawnIndex].SpawnTemplate.start_time = self.SpawnTemplate.start_time
for UnitID = 1, #self.SpawnGroups[SpawnIndex].SpawnTemplate.units do
self.SpawnGroups[SpawnIndex].SpawnTemplate.units[UnitID].heading = self.SpawnTemplate.units[1].heading
end
@@ -1225,19 +1223,10 @@ end
function SPAWN:_Scheduler()
self:F( { "_Scheduler", self.SpawnTemplatePrefix, self.SpawnAliasPrefix, self.SpawnIndex, self.SpawnMaxGroups, self.SpawnMaxUnitsAlive } )
- if self.SpawnInit or self.SpawnCurrentTimer == self.SpawnSetTimer then
- -- Validate if there are still groups left in the batch...
- self:Spawn()
- self.SpawnInit = false
- if self.SpawnIsScheduled == true then
- --local ClientUnit = #AlivePlayerUnits()
- self.AliveFactor = 1 -- ( 10 - ClientUnit ) / 10
- self.SpawnCurrentTimer = 0
- self.SpawnSetTimer = math.random( self.SpawnLowTimer * self.AliveFactor , self.SpawnHighTimer * self.AliveFactor )
- end
- else
- self.SpawnCurrentTimer = self.SpawnCurrentTimer + 1
- end
+ -- Validate if there are still groups left in the batch...
+ self:Spawn()
+
+ return true
end
function SPAWN:_SpawnCleanUpScheduler()
@@ -1269,4 +1258,6 @@ function SPAWN:_SpawnCleanUpScheduler()
end
+ return true -- Repeat
+
end
diff --git a/Moose/Stage.lua b/Moose/Stage.lua
index 08d0d4f06..893a3b08d 100644
--- a/Moose/Stage.lua
+++ b/Moose/Stage.lua
@@ -36,7 +36,7 @@ end
function STAGE:Execute( Mission, Client, Task )
local Valid = true
-
+
return Valid
end
@@ -46,7 +46,7 @@ end
function STAGE:Validate( Mission, Client, Task )
local Valid = true
-
+
return Valid
end
@@ -255,7 +255,7 @@ function STAGEROUTE:Validate( Mission, Client, Task )
-- check if the Client is in the landing zone
self:T( Task.LandingZones.LandingZoneNames )
- Task.CurrentLandingZoneName = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames )
+ Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 )
if Task.CurrentLandingZoneName then
@@ -268,9 +268,11 @@ function STAGEROUTE:Validate( Mission, Client, Task )
end
end
+ self:T( 1 )
return 1
end
+ self:T( 0 )
return 0
end
@@ -345,8 +347,17 @@ function STAGELANDING:Execute( Mission, Client, Task )
else
HostMessage = "Use the Radio menu and F6 to find the cargo, then fly or land near the cargo and " .. Task.TEXT[1] .. " " .. Task.CargoNames .. "."
end
+
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
- Client:Message( HostMessage, self.MSG.TIME, Mission.Name .. "/STAGELANDING.EXEC." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")", 10 )
+ Client:Message( HostMessage, self.MSG.TIME, Mission.Name .. "/STAGELANDING.EXEC." .. Host, Host, 10 )
end
end
@@ -354,7 +365,7 @@ end
function STAGELANDING:Validate( Mission, Client, Task )
self:F()
- Task.CurrentLandingZoneName = routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames )
+ Task.CurrentLandingZoneName = routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.LandingZones.LandingZoneNames, 500 )
if Task.CurrentLandingZoneName then
-- Client is in de landing zone.
@@ -377,14 +388,34 @@ function STAGELANDING:Validate( Mission, Client, Task )
end
Task.Signalled = false
Task:RemoveCargoMenus( Client )
+ self:T( -1 )
return -1
end
- if Task.IsLandingRequired and Client:GetClientGroupDCSUnit():inAir() then
- return 0
- end
+
+ local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity()
+ local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5
+
+ local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint()
+ local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } )
+ local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight
+
+ self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } )
+ if Task.IsLandingRequired and not Client:GetClientGroupDCSUnit():inAir() then
+ self:T( 1 )
+ Task.IsInAirTestRequired = true
+ return 1
+ end
- return 1
+ self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } )
+ if Task.IsLandingRequired and DCSUnitVelocity <= 0.05 and DCSUnitHeight <= Task.CurrentCargoZone.SignalHeight then
+ self:T( 1 )
+ Task.IsInAirTestRequired = false
+ return 1
+ end
+
+ self:T( 0 )
+ return 0
end
STAGELANDED = {
@@ -405,9 +436,20 @@ function STAGELANDED:Execute( Mission, Client, Task )
self:F()
if Task.IsLandingRequired then
- Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.',
- self.MSG.TIME, Mission.Name .. "/STAGELANDED.EXEC." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
- if not self.MenusAdded then
+
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
+
+ Client:Message( 'You have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType .. '.',
+ self.MSG.TIME, Mission.Name .. "/STAGELANDED.EXEC" .. Host, Host )
+
+ if not self.MenusAdded then
Task.Cargo = nil
Task:RemoveCargoMenus( Client )
Task:AddCargoMenus( Client, CARGOS, 250 )
@@ -420,26 +462,44 @@ end
function STAGELANDED:Validate( Mission, Client, Task )
self:F()
- if not routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName ) then
+ if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then
self:T( "Client is not anymore in the landing zone, go back to stage Route, and remove cargo menus." )
Task.Signalled = false
Task:RemoveCargoMenus( Client )
+ self:T( -2 )
return -2
end
+
+ local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity()
+ local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5
- if Task.IsLandingRequired and Client:GetClientGroupDCSUnit():inAir() then
- self:T( "Client went back in the air. Go back to stage Landing." )
- Task.Signalled = false
- return -1
- end
+ local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint()
+ local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } )
+ local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight
+
+ self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then
+ self:T( "Client went back in the air. Go back to stage Landing." )
+ self:T( -1 )
+ return -1
+ end
+
+ self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then
+ self:T( "It seems the Client went back in the air and over the boundary limits. Go back to stage Landing." )
+ self:T( -1 )
+ return -1
+ end
-- Wait until cargo is selected from the menu.
if Task.IsLandingRequired then
if not Task.Cargo then
+ self:T( 0 )
return 0
end
end
-
+
+ self:T( 1 )
return 1
end
@@ -503,7 +563,7 @@ function STAGEUNLOAD:Validate( Mission, Client, Task )
self:F()
env.info( 'STAGEUNLOAD:Validate()' )
- if routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName ) then
+ if routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then
else
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task:RemoveCargoMenus( Client )
@@ -562,10 +622,21 @@ function STAGELOAD:Execute( Mission, Client, Task )
self:F()
if not Task.IsSlingLoad then
+
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
+
Client:Message( 'The ' .. Task.CargoType .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.',
- _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.EXEC." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.EXEC." .. Host, Host )
-- Route the cargo to the Carrier
+
Task.Cargo:OnBoard( Client, Task.CurrentCargoZone, Task.OnBoardSide )
Task.ExecuteStage = _TransportExecuteStage.EXECUTING
else
@@ -578,6 +649,14 @@ function STAGELOAD:Executing( Mission, Client, Task )
-- If the Cargo is ready to be loaded, load it into the Client.
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
if not Task.IsSlingLoad then
self:T( Task.Cargo.CargoName)
@@ -589,14 +668,14 @@ function STAGELOAD:Executing( Mission, Client, Task )
-- Message to the pilot that cargo has been loaded.
Client:Message( "The cargo " .. Task.Cargo.CargoName .. " has been loaded in our helicopter.",
- 20, Mission.Name .. "/STAGELANDING.LOADING1." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ 20, Mission.Name .. "/STAGELANDING.LOADING1." .. Host, Host )
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
Client:ShowCargo()
end
else
Client:Message( "Hook the " .. Task.CargoNames .. " onto the helicopter " .. Task.TEXT[3] .. " within the landing zone.",
- _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.LOADING.1." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")", 10 )
+ _TransportStageMsgTime.EXECUTING, Mission.Name .. "/STAGELOAD.LOADING.1." .. Host, Host , 10 )
for CargoID, Cargo in pairs( CARGOS ) do
self:T( "Cargo.CargoName = " .. Cargo.CargoName )
@@ -612,7 +691,7 @@ function STAGELOAD:Executing( Mission, Client, Task )
Cargo:StatusLoaded()
Task.Cargo = Cargo
Client:Message( 'The Cargo has been successfully hooked onto the helicopter and is now being sling loaded. Fly outside the landing zone.',
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.LOADING.2." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.LOADING.2." .. Host, Host )
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
break
end
@@ -630,32 +709,61 @@ function STAGELOAD:Validate( Mission, Client, Task )
self:T( "Task.CurrentLandingZoneName = " .. Task.CurrentLandingZoneName )
+ local Host = "Command"
+ if Task.HostUnitName then
+ Host = Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")"
+ else
+ if Client:IsMultiSeated() then
+ Host = "Co-Pilot"
+ end
+ end
+
if not Task.IsSlingLoad then
- if not routines.IsUnitInZones( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName ) then
+ if not routines.IsUnitNearZonesRadius( Client:GetClientGroupDCSUnit(), Task.CurrentLandingZoneName, 500 ) then
Task:RemoveCargoMenus( Client )
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task.CargoName = nil
Client:Message( "The " .. Task.CargoType .. " loading has been aborted. You flew outside the pick-up zone while loading. ",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Host, Host )
+ self:T( -1 )
return -1
end
- if not Client:GetClientGroupDCSUnit():inAir() then
- else
- -- The carrier is back in the air, undo the loading process.
- Task:RemoveCargoMenus( Client )
- Task.ExecuteStage = _TransportExecuteStage.NONE
- Task.CargoName = nil
- Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.2." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
- return -1
- end
+ local DCSUnitVelocityVec3 = Client:GetClientGroupDCSUnit():getVelocity()
+ local DCSUnitVelocity = ( DCSUnitVelocityVec3.x ^2 + DCSUnitVelocityVec3.y ^2 + DCSUnitVelocityVec3.z ^2 ) ^ 0.5
+
+ local DCSUnitPointVec3 = Client:GetClientGroupDCSUnit():getPoint()
+ local LandHeight = land.getHeight( { x = DCSUnitPointVec3.x, y = DCSUnitPointVec3.z } )
+ local DCSUnitHeight = DCSUnitPointVec3.y - LandHeight
+
+ self:T( { Task.IsLandingRequired, Client:GetClientGroupDCSUnit():inAir() } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == true and Client:GetClientGroupDCSUnit():inAir() then
+ Task:RemoveCargoMenus( Client )
+ Task.ExecuteStage = _TransportExecuteStage.FAILED
+ Task.CargoName = nil
+ Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.",
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Host, Host )
+ self:T( -1 )
+ return -1
+ end
+
+ self:T( { DCSUnitVelocity, DCSUnitHeight, LandHeight, Task.CurrentCargoZone.SignalHeight } )
+ if Task.IsLandingRequired and Task.IsInAirTestRequired == false and DCSUnitVelocity >= 2 and DCSUnitHeight >= Task.CurrentCargoZone.SignalHeight then
+ Task:RemoveCargoMenus( Client )
+ Task.ExecuteStage = _TransportExecuteStage.FAILED
+ Task.CargoName = nil
+ Client:Message( "The " .. Task.CargoType .. " loading has been aborted. Re-start the " .. Task.TEXT[3] .. " process. Don't fly outside the pick-up zone.",
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.1." .. Host, Host )
+ self:T( -1 )
+ return -1
+ end
if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then
Task:RemoveCargoMenus( Client )
Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " within the landing zone.",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.3." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.3." .. Host, Host )
Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 )
+ self:T( 1 )
return 1
end
@@ -664,8 +772,9 @@ function STAGELOAD:Validate( Mission, Client, Task )
CargoStatic = StaticObject.getByName( Task.Cargo.CargoStaticName )
if CargoStatic and not routines.IsStaticInZones( CargoStatic, Task.CurrentLandingZoneName ) then
Client:Message( "Good Job. The " .. Task.CargoType .. " has been sucessfully " .. Task.TEXT[3] .. " and flown outside of the landing zone.",
- self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.4." .. Task.HostUnitName, Task.HostUnitName .. " (" .. Task.HostUnitTypeName .. ")" )
+ self.MSG.TIME, Mission.Name .. "/STAGELANDING.VALIDATE.4." .. Host, Host )
Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.Cargo.CargoName, 1 )
+ self:T( 1 )
return 1
end
end
@@ -673,6 +782,7 @@ function STAGELOAD:Validate( Mission, Client, Task )
end
+ self:T( 0 )
return 0
end
diff --git a/Moose/Zone.lua b/Moose/Zone.lua
index c1ef13b25..c85cae774 100644
--- a/Moose/Zone.lua
+++ b/Moose/Zone.lua
@@ -40,6 +40,17 @@ function ZONE:GetPointVec2()
return Point
end
+function ZONE:GetPointVec3( Height )
+ self:F( self.ZoneName )
+
+ local Zone = trigger.misc.getZone( self.ZoneName )
+ local Point = { x = Zone.point.x, y = land.getHeight( self:GetPointVec2() ) + Height, z = Zone.point.z }
+
+ self:T( { Zone, Point } )
+
+ return Point
+end
+
function ZONE:GetRandomPointVec2()
self:F( self.ZoneName )
diff --git a/Test Missions/Moose_Test_DATABASE/Moose_Test_DATABASE.miz b/Test Missions/Moose_Test_DATABASE/Moose_Test_DATABASE.miz
index 8942d4325..ba60554f5 100644
Binary files a/Test Missions/Moose_Test_DATABASE/Moose_Test_DATABASE.miz and b/Test Missions/Moose_Test_DATABASE/Moose_Test_DATABASE.miz differ
diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.lua b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.lua
new file mode 100644
index 000000000..2aaabf1ff
--- /dev/null
+++ b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.lua
@@ -0,0 +1,6 @@
+
+
+Include.File("Moose")
+Include.File("MissileTrainer")
+
+local Trainer = MISSILETRAINER:New( 200 )
\ No newline at end of file
diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz
new file mode 100644
index 000000000..b1b93909d
Binary files /dev/null and b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz differ
diff --git a/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua b/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua
index 5b832af72..71bb56e2f 100644
--- a/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua
+++ b/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.lua
@@ -46,15 +46,15 @@ Spawn_Helicopter_Scheduled = SPAWN:New( "Spawn Helicopter Scheduled" ):SpawnSche
Spawn_Ship_Scheduled = SPAWN:New( "Spawn Ship Scheduled" ):SpawnScheduled( 30, 0.5 )
Spawn_Vehicle_Scheduled = SPAWN:New( "Spawn Vehicle Scheduled" ):SpawnScheduled( 30, 0.5 )
--- Tests Tbilisi: Limited Spawning
--- -------------------------------
+-- Tests Tbilisi: Limited Spawning and repeat
+-- ------------------------------------------
-- Spawing one group, and respawning the same group when it lands ...
-Spawn_Plane_Limited_Repeat = SPAWN:New( "Spawn Plane Limited Repeat" ):Limit( 1, 1 ):Repeat():Spawn()
-Spawn_Plane_Limited_RepeatOnLanding = SPAWN:New( "Spawn Plane Limited RepeatOnLanding" ):Limit( 1, 1 ):Repeat():Spawn()
-Spawn_Plane_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Plane Limited RepeatOnEngineShutDown" ):Limit( 1, 1 ):Repeat():Spawn()
-Spawn_Helicopter_Limited_Repeat = SPAWN:New( "Spawn Helicopter Limited Repeat" ):Limit( 1, 1 ):Repeat():Spawn()
-Spawn_Helicopter_Limited_RepeatOnLanding = SPAWN:New( "Spawn Helicopter Limited RepeatOnLanding" ):Limit( 1, 1 ):Repeat():Spawn()
-Spawn_Helicopter_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Helicopter Limited RepeatOnEngineShutDown" ):Limit( 1, 1 ):Repeat():Spawn()
+Spawn_Plane_Limited_Repeat = SPAWN:New( "Spawn Plane Limited Repeat" ):Limit( 1, 1 ):InitRepeat():Spawn()
+Spawn_Plane_Limited_RepeatOnLanding = SPAWN:New( "Spawn Plane Limited RepeatOnLanding" ):Limit( 1, 1 ):InitRepeatOnLanding():Spawn()
+Spawn_Plane_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Plane Limited RepeatOnEngineShutDown" ):Limit( 1, 1 ):InitRepeatOnEngineShutDown():Spawn()
+Spawn_Helicopter_Limited_Repeat = SPAWN:New( "Spawn Helicopter Limited Repeat" ):Limit( 1, 1 ):InitRepeat():Spawn()
+Spawn_Helicopter_Limited_RepeatOnLanding = SPAWN:New( "Spawn Helicopter Limited RepeatOnLanding" ):Limit( 1, 1 ):InitRepeatOnLanding():Spawn()
+Spawn_Helicopter_Limited_RepeatOnEngineShutDown = SPAWN:New( "Spawn Helicopter Limited RepeatOnEngineShutDown" ):Limit( 1, 1 ):InitRepeatOnEngineShutDown():Spawn()
-- Tests Soganlug
@@ -67,8 +67,8 @@ Spawn_Ground_Limited_Scheduled = SPAWN:New( "Spawn Vehicle Limited Scheduled" ):
-- Tests Sukhumi
-- -------------
-- Limited spawning of groups, scheduled every seconds with route randomization.
-Spawn_Plane_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Plane Limited Scheduled RandomizeRoute" ):Limit( 2, 10 ):RandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 30, 0 )
-Spawn_Helicopter_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Helicopter Limited Scheduled RandomizeRoute" ):Limit( 2, 10 ):RandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 30, 0 )
+Spawn_Plane_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Plane Limited Scheduled RandomizeRoute" ):Limit( 5, 10 ):RandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 2, 0 )
+Spawn_Helicopter_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Helicopter Limited Scheduled RandomizeRoute" ):Limit( 5, 10 ):RandomizeRoute( 1, 1, 4000 ):SpawnScheduled( 2, 0 )
Spawn_Vehicle_Limited_Scheduled_RandomizeRoute = SPAWN:New( "Spawn Vehicle Limited Scheduled RandomizeRoute" ):Limit( 10, 10 ):RandomizeRoute( 1, 1, 1000 ):SpawnScheduled( 1, 0 )
@@ -85,7 +85,7 @@ Spawn_Vehicle_Scheduled_CleanUp = SPAWN:New( "Spawn Vehicle Scheduled CleanUp" )
-- Creates arrays of groups ready to be spawned and dynamic spawning of groups from another group.
-- SpawnTestVisible creates an array of 200 groups, every 20 groups with 20 meters space in between, and will activate a group of the array every 10 seconds with a 0.2 time randomization.
-SpawnTestVisible = SPAWN:New( "Spawn Vehicle Visible Scheduled" ):Limit( 200, 200 ):Array( 59, 20, 20, 10 ):SpawnScheduled( 10, 0.2 )
+SpawnTestVisible = SPAWN:New( "Spawn Vehicle Visible Scheduled" ):Limit( 200, 200 ):Array( 59, 20, 30, 30 ):SpawnScheduled( 10, 0.2 )
-- Spawn_Templates_Visible contains different templates...
Spawn_Templates_Visible = { "Spawn Vehicle Visible Template A",
diff --git a/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz b/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz
index ea146f2c5..f00fe66c2 100644
Binary files a/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz and b/Test Missions/Moose_Test_SPAWN/MOOSE_Test_SPAWN.miz differ
diff --git a/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua b/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua
index 47f40235d..5dbb5b963 100644
--- a/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua
+++ b/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.lua
@@ -12,10 +12,6 @@
Include.File("Spawn")
-BASE:TraceClass("GROUP")
-BASE:TraceClass("SPAWN")
-BASE:TraceClass("DATABASE")
-
do
-- Declare SPAWN objects
@@ -27,21 +23,21 @@ do
-- Choose repeat functionality
-- Repeat on landing
- Spawn_KA_50:RepeatOnLanding()
- Spawn_C_101EB:RepeatOnLanding()
+ Spawn_KA_50:InitRepeatOnLanding()
+ Spawn_C_101EB:InitRepeatOnLanding()
-- Repeat on enging shutdown (when landed on the airport)
- Spawn_MI_8MTV2:RepeatOnEngineShutDown()
- Spawn_A_10C:RepeatOnEngineShutDown()
+ Spawn_MI_8MTV2:InitRepeatOnEngineShutDown()
+ Spawn_A_10C:InitRepeatOnEngineShutDown()
-- Now SPAWN the GROUPs
Spawn_KA_50:Spawn()
- Spawn_KA_50:Spawn()
Spawn_C_101EB:Spawn()
- Spawn_C_101EB:Spawn()
- Spawn_MI_8MTV2:Spawn()
Spawn_MI_8MTV2:Spawn()
Spawn_A_10C:Spawn()
+ Spawn_KA_50:Spawn()
+ Spawn_C_101EB:Spawn()
+ Spawn_MI_8MTV2:Spawn()
Spawn_A_10C:Spawn()
-- Now run the mission and observe the behaviour.
diff --git a/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz b/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz
index acdcc9062..efdae527b 100644
Binary files a/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz and b/Test Missions/Moose_Test_SPAWN_Repeat/MOOSE_Test_SPAWN_Repeat.miz differ
diff --git a/Tools and Installation/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx b/Tools and Installation/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx
new file mode 100644
index 000000000..589ffcb07
Binary files /dev/null and b/Tools and Installation/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx differ
diff --git a/images/body-bg.jpg b/images/body-bg.jpg
new file mode 100644
index 000000000..719fb8863
Binary files /dev/null and b/images/body-bg.jpg differ
diff --git a/images/download-button.png b/images/download-button.png
new file mode 100644
index 000000000..c5ffb3a89
Binary files /dev/null and b/images/download-button.png differ
diff --git a/images/github-button.png b/images/github-button.png
new file mode 100644
index 000000000..cd4158023
Binary files /dev/null and b/images/github-button.png differ
diff --git a/images/header-bg.jpg b/images/header-bg.jpg
new file mode 100644
index 000000000..d16497a40
Binary files /dev/null and b/images/header-bg.jpg differ
diff --git a/images/highlight-bg.jpg b/images/highlight-bg.jpg
new file mode 100644
index 000000000..355e089b2
Binary files /dev/null and b/images/highlight-bg.jpg differ
diff --git a/images/sidebar-bg.jpg b/images/sidebar-bg.jpg
new file mode 100644
index 000000000..536ead91e
Binary files /dev/null and b/images/sidebar-bg.jpg differ
diff --git a/index.html b/index.html
new file mode 100644
index 000000000..9e5727e2c
--- /dev/null
+++ b/index.html
@@ -0,0 +1,60 @@
+
+
+
+
+ Moose by FlightControl-Master
+
+
+
+
+
+
+
+
+
Moose
+
Mission Object Oriented Scripting Environment (MOOSE) for lua mission scripting design in DCS World
A development has been ongoing for a while now, which allows to create missions quickly using an Object Oriented Framework developed in lua.
+MOOSE is currently still in alpha version, but will evolve over time. Right now, it has been updated to work with DCS world 1.5.
+
+
MOOSE is a Mission Object Oriented Scripting Environment, and is meant for mission designers and mission hosters. It allows to quickly setup complex missions using pre-scripted scenarios using the available classes within the framework.
The goal of MOOSE is to allow mission designers to enhance their scripting with mission orchestration objects, which can be instantiated from defined classes within the framework. This will allow to write mission scripts with minimal code embedded. Of course, the richness of the framework will determine the richness of the misson scenarios. We can expect that MOOSE will evolve over time, as more missions will be designed within the framework.
+
+
+Classes
+
+
The following classes are currently embedded within MOOSE:
+
+
+
BASE: The main class from which all MOOSE classes are derived from. The BASE class contains essential functions to support inheritance and MOOSE object execution tracing (logging within the DCS.log file in the saved games folder of the user).
+
DATABASE: Create sets of units (and groups) upon defined filter criteria. These sets can be used to iterate the DATABASE calling a defined function for each unit (or group).
+
SPAWN: Spawn new groups (and units) during mission execution.
+
ESCORT: Makes groups consisting of helicopters, airplanes, ground troops or ships within a mission joining your flight. You can control these groups through the ratio menu during your flight. Available commands are around: Navigation, Position Hold, Reporting (Target Detection), Attacking, Assisted Attacks, ROE, Evasion, Mission Execution and more ...
+
+
+
+
+
+
+
+
+
diff --git a/javascripts/main.js b/javascripts/main.js
new file mode 100644
index 000000000..d8135d37b
--- /dev/null
+++ b/javascripts/main.js
@@ -0,0 +1 @@
+console.log('This would be the main JS file.');
diff --git a/params.json b/params.json
new file mode 100644
index 000000000..1c7cd5758
--- /dev/null
+++ b/params.json
@@ -0,0 +1,6 @@
+{
+ "name": "Moose",
+ "tagline": "Mission Object Oriented Scripting Environment (MOOSE) for lua mission scripting design in DCS World",
+ "body": "# Context\r\n\r\nA development has been ongoing for a while now, which allows to create missions quickly using an Object Oriented Framework developed in lua.\r\nMOOSE is currently still in alpha version, but will evolve over time. Right now, it has been updated to work with DCS world 1.5.\r\n\r\nMOOSE is a **M**ission **O**bject **O**riented **S**cripting **E**nvironment, and is meant for mission designers and mission hosters. It allows to quickly setup complex missions using pre-scripted scenarios using the available classes within the framework.\r\n\r\nYou can find the source of MOOSE here on GITHUB. It is free for download: \r\nhttps://github.com/FlightControl-Master/MOOSE/\r\n\r\n# Goals\r\n\r\nThe goal of MOOSE is to allow mission designers to enhance their scripting with mission orchestration objects, which can be instantiated from defined classes within the framework. This will allow to write mission scripts with minimal code embedded. Of course, the richness of the framework will determine the richness of the misson scenarios. We can expect that MOOSE will evolve over time, as more missions will be designed within the framework.\r\n\r\n# Classes\r\n\r\nThe following classes are currently embedded within MOOSE:\r\n\r\n* [BASE](Documentation/Base.html): The main class from which all MOOSE classes are derived from. The BASE class contains essential functions to support inheritance and MOOSE object execution tracing (logging within the DCS.log file in the saved games folder of the user).\r\n\r\n* [DATABASE](Documentation/DATABASE.html): Create sets of units (and groups) upon defined filter criteria. These sets can be used to iterate the DATABASE calling a defined function for each unit (or group).\r\n\r\n* [SPAWN](Documentation/Spawn.html): Spawn new groups (and units) during mission execution.\r\n\r\n* [ESCORT](Documentation/Escort.html): Makes groups consisting of helicopters, airplanes, ground troops or ships within a mission joining your flight. You can control these groups through the ratio menu during your flight. Available commands are around: Navigation, Position Hold, Reporting (Target Detection), Attacking, Assisted Attacks, ROE, Evasion, Mission Execution and more ...\r\n\r\n",
+ "note": "Don't delete this file! It's used internally to help with page regeneration."
+}
\ No newline at end of file
diff --git a/stylesheets/github-light.css b/stylesheets/github-light.css
new file mode 100644
index 000000000..d64ba5df0
--- /dev/null
+++ b/stylesheets/github-light.css
@@ -0,0 +1,124 @@
+/*
+The MIT License (MIT)
+
+Copyright (c) 2015 GitHub, Inc.
+
+Permission is hereby granted, free of charge, to any person obtaining a copy
+of this software and associated documentation files (the "Software"), to deal
+in the Software without restriction, including without limitation the rights
+to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+copies of the Software, and to permit persons to whom the Software is
+furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+SOFTWARE.
+
+*/
+
+.pl-c /* comment */ {
+ color: #969896;
+}
+
+.pl-c1 /* constant, markup.raw, meta.diff.header, meta.module-reference, meta.property-name, support, support.constant, support.variable, variable.other.constant */,
+.pl-s .pl-v /* string variable */ {
+ color: #0086b3;
+}
+
+.pl-e /* entity */,
+.pl-en /* entity.name */ {
+ color: #795da3;
+}
+
+.pl-s .pl-s1 /* string source */,
+.pl-smi /* storage.modifier.import, storage.modifier.package, storage.type.java, variable.other, variable.parameter.function */ {
+ color: #333;
+}
+
+.pl-ent /* entity.name.tag */ {
+ color: #63a35c;
+}
+
+.pl-k /* keyword, storage, storage.type */ {
+ color: #a71d5d;
+}
+
+.pl-pds /* punctuation.definition.string, string.regexp.character-class */,
+.pl-s /* string */,
+.pl-s .pl-pse .pl-s1 /* string punctuation.section.embedded source */,
+.pl-sr /* string.regexp */,
+.pl-sr .pl-cce /* string.regexp constant.character.escape */,
+.pl-sr .pl-sra /* string.regexp string.regexp.arbitrary-repitition */,
+.pl-sr .pl-sre /* string.regexp source.ruby.embedded */ {
+ color: #183691;
+}
+
+.pl-v /* variable */ {
+ color: #ed6a43;
+}
+
+.pl-id /* invalid.deprecated */ {
+ color: #b52a1d;
+}
+
+.pl-ii /* invalid.illegal */ {
+ background-color: #b52a1d;
+ color: #f8f8f8;
+}
+
+.pl-sr .pl-cce /* string.regexp constant.character.escape */ {
+ color: #63a35c;
+ font-weight: bold;
+}
+
+.pl-ml /* markup.list */ {
+ color: #693a17;
+}
+
+.pl-mh /* markup.heading */,
+.pl-mh .pl-en /* markup.heading entity.name */,
+.pl-ms /* meta.separator */ {
+ color: #1d3e81;
+ font-weight: bold;
+}
+
+.pl-mq /* markup.quote */ {
+ color: #008080;
+}
+
+.pl-mi /* markup.italic */ {
+ color: #333;
+ font-style: italic;
+}
+
+.pl-mb /* markup.bold */ {
+ color: #333;
+ font-weight: bold;
+}
+
+.pl-md /* markup.deleted, meta.diff.header.from-file */ {
+ background-color: #ffecec;
+ color: #bd2c00;
+}
+
+.pl-mi1 /* markup.inserted, meta.diff.header.to-file */ {
+ background-color: #eaffea;
+ color: #55a532;
+}
+
+.pl-mdr /* meta.diff.range */ {
+ color: #795da3;
+ font-weight: bold;
+}
+
+.pl-mo /* meta.output */ {
+ color: #1d3e81;
+}
+
diff --git a/stylesheets/normalize.css b/stylesheets/normalize.css
new file mode 100644
index 000000000..30366a6e9
--- /dev/null
+++ b/stylesheets/normalize.css
@@ -0,0 +1,424 @@
+/*! normalize.css v3.0.2 | MIT License | git.io/normalize */
+
+/**
+ * 1. Set default font family to sans-serif.
+ * 2. Prevent iOS text size adjust after orientation change, without disabling
+ * user zoom.
+ */
+
+html {
+ font-family: sans-serif; /* 1 */
+ -ms-text-size-adjust: 100%; /* 2 */
+ -webkit-text-size-adjust: 100%; /* 2 */
+}
+
+/**
+ * Remove default margin.
+ */
+
+body {
+ margin: 0;
+}
+
+/* HTML5 display definitions
+ ========================================================================== */
+
+/**
+ * Correct `block` display not defined for any HTML5 element in IE 8/9.
+ * Correct `block` display not defined for `details` or `summary` in IE 10/11
+ * and Firefox.
+ * Correct `block` display not defined for `main` in IE 11.
+ */
+
+article,
+aside,
+details,
+figcaption,
+figure,
+footer,
+header,
+hgroup,
+main,
+menu,
+nav,
+section,
+summary {
+ display: block;
+}
+
+/**
+ * 1. Correct `inline-block` display not defined in IE 8/9.
+ * 2. Normalize vertical alignment of `progress` in Chrome, Firefox, and Opera.
+ */
+
+audio,
+canvas,
+progress,
+video {
+ display: inline-block; /* 1 */
+ vertical-align: baseline; /* 2 */
+}
+
+/**
+ * Prevent modern browsers from displaying `audio` without controls.
+ * Remove excess height in iOS 5 devices.
+ */
+
+audio:not([controls]) {
+ display: none;
+ height: 0;
+}
+
+/**
+ * Address `[hidden]` styling not present in IE 8/9/10.
+ * Hide the `template` element in IE 8/9/11, Safari, and Firefox < 22.
+ */
+
+[hidden],
+template {
+ display: none;
+}
+
+/* Links
+ ========================================================================== */
+
+/**
+ * Remove the gray background color from active links in IE 10.
+ */
+
+a {
+ background-color: transparent;
+}
+
+/**
+ * Improve readability when focused and also mouse hovered in all browsers.
+ */
+
+a:active,
+a:hover {
+ outline: 0;
+}
+
+/* Text-level semantics
+ ========================================================================== */
+
+/**
+ * Address styling not present in IE 8/9/10/11, Safari, and Chrome.
+ */
+
+abbr[title] {
+ border-bottom: 1px dotted;
+}
+
+/**
+ * Address style set to `bolder` in Firefox 4+, Safari, and Chrome.
+ */
+
+b,
+strong {
+ font-weight: bold;
+}
+
+/**
+ * Address styling not present in Safari and Chrome.
+ */
+
+dfn {
+ font-style: italic;
+}
+
+/**
+ * Address variable `h1` font-size and margin within `section` and `article`
+ * contexts in Firefox 4+, Safari, and Chrome.
+ */
+
+h1 {
+ font-size: 2em;
+ margin: 0.67em 0;
+}
+
+/**
+ * Address styling not present in IE 8/9.
+ */
+
+mark {
+ background: #ff0;
+ color: #000;
+}
+
+/**
+ * Address inconsistent and variable font size in all browsers.
+ */
+
+small {
+ font-size: 80%;
+}
+
+/**
+ * Prevent `sub` and `sup` affecting `line-height` in all browsers.
+ */
+
+sub,
+sup {
+ font-size: 75%;
+ line-height: 0;
+ position: relative;
+ vertical-align: baseline;
+}
+
+sup {
+ top: -0.5em;
+}
+
+sub {
+ bottom: -0.25em;
+}
+
+/* Embedded content
+ ========================================================================== */
+
+/**
+ * Remove border when inside `a` element in IE 8/9/10.
+ */
+
+img {
+ border: 0;
+}
+
+/**
+ * Correct overflow not hidden in IE 9/10/11.
+ */
+
+svg:not(:root) {
+ overflow: hidden;
+}
+
+/* Grouping content
+ ========================================================================== */
+
+/**
+ * Address margin not present in IE 8/9 and Safari.
+ */
+
+figure {
+ margin: 1em 40px;
+}
+
+/**
+ * Address differences between Firefox and other browsers.
+ */
+
+hr {
+ box-sizing: content-box;
+ height: 0;
+}
+
+/**
+ * Contain overflow in all browsers.
+ */
+
+pre {
+ overflow: auto;
+}
+
+/**
+ * Address odd `em`-unit font size rendering in all browsers.
+ */
+
+code,
+kbd,
+pre,
+samp {
+ font-family: monospace, monospace;
+ font-size: 1em;
+}
+
+/* Forms
+ ========================================================================== */
+
+/**
+ * Known limitation: by default, Chrome and Safari on OS X allow very limited
+ * styling of `select`, unless a `border` property is set.
+ */
+
+/**
+ * 1. Correct color not being inherited.
+ * Known issue: affects color of disabled elements.
+ * 2. Correct font properties not being inherited.
+ * 3. Address margins set differently in Firefox 4+, Safari, and Chrome.
+ */
+
+button,
+input,
+optgroup,
+select,
+textarea {
+ color: inherit; /* 1 */
+ font: inherit; /* 2 */
+ margin: 0; /* 3 */
+}
+
+/**
+ * Address `overflow` set to `hidden` in IE 8/9/10/11.
+ */
+
+button {
+ overflow: visible;
+}
+
+/**
+ * Address inconsistent `text-transform` inheritance for `button` and `select`.
+ * All other form control elements do not inherit `text-transform` values.
+ * Correct `button` style inheritance in Firefox, IE 8/9/10/11, and Opera.
+ * Correct `select` style inheritance in Firefox.
+ */
+
+button,
+select {
+ text-transform: none;
+}
+
+/**
+ * 1. Avoid the WebKit bug in Android 4.0.* where (2) destroys native `audio`
+ * and `video` controls.
+ * 2. Correct inability to style clickable `input` types in iOS.
+ * 3. Improve usability and consistency of cursor style between image-type
+ * `input` and others.
+ */
+
+button,
+html input[type="button"], /* 1 */
+input[type="reset"],
+input[type="submit"] {
+ -webkit-appearance: button; /* 2 */
+ cursor: pointer; /* 3 */
+}
+
+/**
+ * Re-set default cursor for disabled elements.
+ */
+
+button[disabled],
+html input[disabled] {
+ cursor: default;
+}
+
+/**
+ * Remove inner padding and border in Firefox 4+.
+ */
+
+button::-moz-focus-inner,
+input::-moz-focus-inner {
+ border: 0;
+ padding: 0;
+}
+
+/**
+ * Address Firefox 4+ setting `line-height` on `input` using `!important` in
+ * the UA stylesheet.
+ */
+
+input {
+ line-height: normal;
+}
+
+/**
+ * It's recommended that you don't attempt to style these elements.
+ * Firefox's implementation doesn't respect box-sizing, padding, or width.
+ *
+ * 1. Address box sizing set to `content-box` in IE 8/9/10.
+ * 2. Remove excess padding in IE 8/9/10.
+ */
+
+input[type="checkbox"],
+input[type="radio"] {
+ box-sizing: border-box; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Fix the cursor style for Chrome's increment/decrement buttons. For certain
+ * `font-size` values of the `input`, it causes the cursor style of the
+ * decrement button to change from `default` to `text`.
+ */
+
+input[type="number"]::-webkit-inner-spin-button,
+input[type="number"]::-webkit-outer-spin-button {
+ height: auto;
+}
+
+/**
+ * 1. Address `appearance` set to `searchfield` in Safari and Chrome.
+ * 2. Address `box-sizing` set to `border-box` in Safari and Chrome
+ * (include `-moz` to future-proof).
+ */
+
+input[type="search"] {
+ -webkit-appearance: textfield; /* 1 */ /* 2 */
+ box-sizing: content-box;
+}
+
+/**
+ * Remove inner padding and search cancel button in Safari and Chrome on OS X.
+ * Safari (but not Chrome) clips the cancel button when the search input has
+ * padding (and `textfield` appearance).
+ */
+
+input[type="search"]::-webkit-search-cancel-button,
+input[type="search"]::-webkit-search-decoration {
+ -webkit-appearance: none;
+}
+
+/**
+ * Define consistent border, margin, and padding.
+ */
+
+fieldset {
+ border: 1px solid #c0c0c0;
+ margin: 0 2px;
+ padding: 0.35em 0.625em 0.75em;
+}
+
+/**
+ * 1. Correct `color` not being inherited in IE 8/9/10/11.
+ * 2. Remove padding so people aren't caught out if they zero out fieldsets.
+ */
+
+legend {
+ border: 0; /* 1 */
+ padding: 0; /* 2 */
+}
+
+/**
+ * Remove default vertical scrollbar in IE 8/9/10/11.
+ */
+
+textarea {
+ overflow: auto;
+}
+
+/**
+ * Don't inherit the `font-weight` (applied by a rule above).
+ * NOTE: the default cannot safely be changed in Chrome and Safari on OS X.
+ */
+
+optgroup {
+ font-weight: bold;
+}
+
+/* Tables
+ ========================================================================== */
+
+/**
+ * Remove most spacing between table cells.
+ */
+
+table {
+ border-collapse: collapse;
+ border-spacing: 0;
+}
+
+td,
+th {
+ padding: 0;
+}
diff --git a/stylesheets/print.css b/stylesheets/print.css
new file mode 100644
index 000000000..7da6db04c
--- /dev/null
+++ b/stylesheets/print.css
@@ -0,0 +1,228 @@
+html, body, div, span, applet, object, iframe,
+h1, h2, h3, h4, h5, h6, p, blockquote, pre,
+a, abbr, acronym, address, big, cite, code,
+del, dfn, em, img, ins, kbd, q, s, samp,
+small, strike, strong, sub, sup, tt, var,
+b, u, i, center,
+dl, dt, dd, ol, ul, li,
+fieldset, form, label, legend,
+table, caption, tbody, tfoot, thead, tr, th, td,
+article, aside, canvas, details, embed,
+figure, figcaption, footer, header, hgroup,
+menu, nav, output, ruby, section, summary,
+time, mark, audio, video {
+ padding: 0;
+ margin: 0;
+ font: inherit;
+ font-size: 100%;
+ vertical-align: baseline;
+ border: 0;
+}
+/* HTML5 display-role reset for older browsers */
+article, aside, details, figcaption, figure,
+footer, header, hgroup, menu, nav, section {
+ display: block;
+}
+body {
+ line-height: 1;
+}
+ol, ul {
+ list-style: none;
+}
+blockquote, q {
+ quotes: none;
+}
+blockquote:before, blockquote:after,
+q:before, q:after {
+ content: '';
+ content: none;
+}
+table {
+ border-spacing: 0;
+ border-collapse: collapse;
+}
+body {
+ font-family: 'Helvetica Neue', Helvetica, Arial, serif;
+ font-size: 13px;
+ line-height: 1.5;
+ color: #000;
+}
+
+a {
+ font-weight: bold;
+ color: #d5000d;
+}
+
+header {
+ padding-top: 35px;
+ padding-bottom: 10px;
+}
+
+header h1 {
+ font-size: 48px;
+ font-weight: bold;
+ line-height: 1.2;
+ color: #303030;
+ letter-spacing: -1px;
+}
+
+header h2 {
+ font-size: 24px;
+ font-weight: normal;
+ line-height: 1.3;
+ color: #aaa;
+ letter-spacing: -1px;
+}
+#downloads {
+ display: none;
+}
+#main_content {
+ padding-top: 20px;
+}
+
+code, pre {
+ margin-bottom: 30px;
+ font-family: Monaco, "Bitstream Vera Sans Mono", "Lucida Console", Terminal;
+ font-size: 12px;
+ color: #222;
+}
+
+code {
+ padding: 0 3px;
+}
+
+pre {
+ padding: 20px;
+ overflow: auto;
+ border: solid 1px #ddd;
+}
+pre code {
+ padding: 0;
+}
+
+ul, ol, dl {
+ margin-bottom: 20px;
+}
+
+
+/* COMMON STYLES */
+
+table {
+ width: 100%;
+ border: 1px solid #ebebeb;
+}
+
+th {
+ font-weight: 500;
+}
+
+td {
+ font-weight: 300;
+ text-align: center;
+ border: 1px solid #ebebeb;
+}
+
+form {
+ padding: 20px;
+ background: #f2f2f2;
+
+}
+
+
+/* GENERAL ELEMENT TYPE STYLES */
+
+h1 {
+ font-size: 2.8em;
+}
+
+h2 {
+ margin-bottom: 8px;
+ font-size: 22px;
+ font-weight: bold;
+ color: #303030;
+}
+
+h3 {
+ margin-bottom: 8px;
+ font-size: 18px;
+ font-weight: bold;
+ color: #d5000d;
+}
+
+h4 {
+ font-size: 16px;
+ font-weight: bold;
+ color: #303030;
+}
+
+h5 {
+ font-size: 1em;
+ color: #303030;
+}
+
+h6 {
+ font-size: .8em;
+ color: #303030;
+}
+
+p {
+ margin-bottom: 20px;
+ font-weight: 300;
+}
+
+a {
+ text-decoration: none;
+}
+
+p a {
+ font-weight: 400;
+}
+
+blockquote {
+ padding: 0 0 0 30px;
+ margin-bottom: 20px;
+ font-size: 1.6em;
+ border-left: 10px solid #e9e9e9;
+}
+
+ul li {
+ padding-left: 20px;
+ list-style-position: inside;
+ list-style: disc;
+}
+
+ol li {
+ padding-left: 3px;
+ list-style-position: inside;
+ list-style: decimal;
+}
+
+dl dd {
+ font-style: italic;
+ font-weight: 100;
+}
+
+footer {
+ padding-top: 20px;
+ padding-bottom: 30px;
+ margin-top: 40px;
+ font-size: 13px;
+ color: #aaa;
+}
+
+footer a {
+ color: #666;
+}
+
+/* MISC */
+.clearfix:after {
+ display: block;
+ height: 0;
+ clear: both;
+ visibility: hidden;
+ content: '.';
+}
+
+.clearfix {display: inline-block;}
+* html .clearfix {height: 1%;}
+.clearfix {display: block;}
diff --git a/stylesheets/stylesheet.css b/stylesheets/stylesheet.css
new file mode 100644
index 000000000..b5f20c235
--- /dev/null
+++ b/stylesheets/stylesheet.css
@@ -0,0 +1,245 @@
+* {
+ box-sizing: border-box; }
+
+body {
+ padding: 0;
+ margin: 0;
+ font-family: "Open Sans", "Helvetica Neue", Helvetica, Arial, sans-serif;
+ font-size: 16px;
+ line-height: 1.5;
+ color: #606c71; }
+
+a {
+ color: #1e6bb8;
+ text-decoration: none; }
+ a:hover {
+ text-decoration: underline; }
+
+.btn {
+ display: inline-block;
+ margin-bottom: 1rem;
+ color: rgba(255, 255, 255, 0.7);
+ background-color: rgba(255, 255, 255, 0.08);
+ border-color: rgba(255, 255, 255, 0.2);
+ border-style: solid;
+ border-width: 1px;
+ border-radius: 0.3rem;
+ transition: color 0.2s, background-color 0.2s, border-color 0.2s; }
+ .btn + .btn {
+ margin-left: 1rem; }
+
+.btn:hover {
+ color: rgba(255, 255, 255, 0.8);
+ text-decoration: none;
+ background-color: rgba(255, 255, 255, 0.2);
+ border-color: rgba(255, 255, 255, 0.3); }
+
+@media screen and (min-width: 64em) {
+ .btn {
+ padding: 0.75rem 1rem; } }
+
+@media screen and (min-width: 42em) and (max-width: 64em) {
+ .btn {
+ padding: 0.6rem 0.9rem;
+ font-size: 0.9rem; } }
+
+@media screen and (max-width: 42em) {
+ .btn {
+ display: block;
+ width: 100%;
+ padding: 0.75rem;
+ font-size: 0.9rem; }
+ .btn + .btn {
+ margin-top: 1rem;
+ margin-left: 0; } }
+
+.page-header {
+ color: #fff;
+ text-align: center;
+ background-color: #159957;
+ background-image: linear-gradient(120deg, #155799, #159957); }
+
+@media screen and (min-width: 64em) {
+ .page-header {
+ padding: 5rem 6rem; } }
+
+@media screen and (min-width: 42em) and (max-width: 64em) {
+ .page-header {
+ padding: 3rem 4rem; } }
+
+@media screen and (max-width: 42em) {
+ .page-header {
+ padding: 2rem 1rem; } }
+
+.project-name {
+ margin-top: 0;
+ margin-bottom: 0.1rem; }
+
+@media screen and (min-width: 64em) {
+ .project-name {
+ font-size: 3.25rem; } }
+
+@media screen and (min-width: 42em) and (max-width: 64em) {
+ .project-name {
+ font-size: 2.25rem; } }
+
+@media screen and (max-width: 42em) {
+ .project-name {
+ font-size: 1.75rem; } }
+
+.project-tagline {
+ margin-bottom: 2rem;
+ font-weight: normal;
+ opacity: 0.7; }
+
+@media screen and (min-width: 64em) {
+ .project-tagline {
+ font-size: 1.25rem; } }
+
+@media screen and (min-width: 42em) and (max-width: 64em) {
+ .project-tagline {
+ font-size: 1.15rem; } }
+
+@media screen and (max-width: 42em) {
+ .project-tagline {
+ font-size: 1rem; } }
+
+.main-content :first-child {
+ margin-top: 0; }
+.main-content img {
+ max-width: 100%; }
+.main-content h1, .main-content h2, .main-content h3, .main-content h4, .main-content h5, .main-content h6 {
+ margin-top: 2rem;
+ margin-bottom: 1rem;
+ font-weight: normal;
+ color: #159957; }
+.main-content p {
+ margin-bottom: 1em; }
+.main-content code {
+ padding: 2px 4px;
+ font-family: Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ font-size: 0.9rem;
+ color: #383e41;
+ background-color: #f3f6fa;
+ border-radius: 0.3rem; }
+.main-content pre {
+ padding: 0.8rem;
+ margin-top: 0;
+ margin-bottom: 1rem;
+ font: 1rem Consolas, "Liberation Mono", Menlo, Courier, monospace;
+ color: #567482;
+ word-wrap: normal;
+ background-color: #f3f6fa;
+ border: solid 1px #dce6f0;
+ border-radius: 0.3rem; }
+ .main-content pre > code {
+ padding: 0;
+ margin: 0;
+ font-size: 0.9rem;
+ color: #567482;
+ word-break: normal;
+ white-space: pre;
+ background: transparent;
+ border: 0; }
+.main-content .highlight {
+ margin-bottom: 1rem; }
+ .main-content .highlight pre {
+ margin-bottom: 0;
+ word-break: normal; }
+.main-content .highlight pre, .main-content pre {
+ padding: 0.8rem;
+ overflow: auto;
+ font-size: 0.9rem;
+ line-height: 1.45;
+ border-radius: 0.3rem; }
+.main-content pre code, .main-content pre tt {
+ display: inline;
+ max-width: initial;
+ padding: 0;
+ margin: 0;
+ overflow: initial;
+ line-height: inherit;
+ word-wrap: normal;
+ background-color: transparent;
+ border: 0; }
+ .main-content pre code:before, .main-content pre code:after, .main-content pre tt:before, .main-content pre tt:after {
+ content: normal; }
+.main-content ul, .main-content ol {
+ margin-top: 0; }
+.main-content blockquote {
+ padding: 0 1rem;
+ margin-left: 0;
+ color: #819198;
+ border-left: 0.3rem solid #dce6f0; }
+ .main-content blockquote > :first-child {
+ margin-top: 0; }
+ .main-content blockquote > :last-child {
+ margin-bottom: 0; }
+.main-content table {
+ display: block;
+ width: 100%;
+ overflow: auto;
+ word-break: normal;
+ word-break: keep-all; }
+ .main-content table th {
+ font-weight: bold; }
+ .main-content table th, .main-content table td {
+ padding: 0.5rem 1rem;
+ border: 1px solid #e9ebec; }
+.main-content dl {
+ padding: 0; }
+ .main-content dl dt {
+ padding: 0;
+ margin-top: 1rem;
+ font-size: 1rem;
+ font-weight: bold; }
+ .main-content dl dd {
+ padding: 0;
+ margin-bottom: 1rem; }
+.main-content hr {
+ height: 2px;
+ padding: 0;
+ margin: 1rem 0;
+ background-color: #eff0f1;
+ border: 0; }
+
+@media screen and (min-width: 64em) {
+ .main-content {
+ max-width: 64rem;
+ padding: 2rem 6rem;
+ margin: 0 auto;
+ font-size: 1.1rem; } }
+
+@media screen and (min-width: 42em) and (max-width: 64em) {
+ .main-content {
+ padding: 2rem 4rem;
+ font-size: 1.1rem; } }
+
+@media screen and (max-width: 42em) {
+ .main-content {
+ padding: 2rem 1rem;
+ font-size: 1rem; } }
+
+.site-footer {
+ padding-top: 2rem;
+ margin-top: 2rem;
+ border-top: solid 1px #eff0f1; }
+
+.site-footer-owner {
+ display: block;
+ font-weight: bold; }
+
+.site-footer-credits {
+ color: #819198; }
+
+@media screen and (min-width: 64em) {
+ .site-footer {
+ font-size: 1rem; } }
+
+@media screen and (min-width: 42em) and (max-width: 64em) {
+ .site-footer {
+ font-size: 1rem; } }
+
+@media screen and (max-width: 42em) {
+ .site-footer {
+ font-size: 0.9rem; } }