First Commit

This commit is contained in:
svenvandevelde 2015-10-25 10:40:52 +01:00
parent c5577d7d05
commit 10341b7045
31 changed files with 8201 additions and 0 deletions

216
.gitignore vendored Normal file
View File

@ -0,0 +1,216 @@
#################
## Eclipse
#################
*.pydevproject
.project
.metadata
bin/
tmp/
*.tmp
*.bak
*.swp
*~.nib
local.properties
.classpath
.settings/
.loadpath
# External tool builders
.externalToolBuilders/
# Locally stored "Eclipse launch configurations"
*.launch
# CDT-specific
.cproject
# PDT-specific
.buildpath
#################
## Visual Studio
#################
## Ignore Visual Studio temporary files, build results, and
## files generated by popular Visual Studio add-ons.
# User-specific files
*.suo
*.user
*.sln.docstates
# Build results
[Dd]ebug/
[Rr]elease/
x64/
build/
[Bb]in/
[Oo]bj/
# MSTest test Results
[Tt]est[Rr]esult*/
[Bb]uild[Ll]og.*
*_i.c
*_p.c
*.ilk
*.meta
*.obj
*.pch
*.pdb
*.pgc
*.pgd
*.rsp
*.sbr
*.tlb
*.tli
*.tlh
*.tmp
*.tmp_proj
*.log
*.vspscc
*.vssscc
.builds
*.pidb
*.log
*.scc
# Visual C++ cache files
ipch/
*.aps
*.ncb
*.opensdf
*.sdf
*.cachefile
# Visual Studio profiler
*.psess
*.vsp
*.vspx
# Guidance Automation Toolkit
*.gpState
# ReSharper is a .NET coding add-in
_ReSharper*/
*.[Rr]e[Ss]harper
# TeamCity is a build add-in
_TeamCity*
# DotCover is a Code Coverage Tool
*.dotCover
# NCrunch
*.ncrunch*
.*crunch*.local.xml
# Installshield output folder
[Ee]xpress/
# DocProject is a documentation generator add-in
DocProject/buildhelp/
DocProject/Help/*.HxT
DocProject/Help/*.HxC
DocProject/Help/*.hhc
DocProject/Help/*.hhk
DocProject/Help/*.hhp
DocProject/Help/Html2
DocProject/Help/html
# Click-Once directory
publish/
# Publish Web Output
*.Publish.xml
*.pubxml
*.publishproj
# NuGet Packages Directory
## TODO: If you have NuGet Package Restore enabled, uncomment the next line
#packages/
# Windows Azure Build Output
csx
*.build.csdef
# Windows Store app package directory
AppPackages/
# Others
sql/
*.Cache
ClientBin/
[Ss]tyle[Cc]op.*
~$*
*~
*.dbmdl
*.[Pp]ublish.xml
*.pfx
*.publishsettings
# RIA/Silverlight projects
Generated_Code/
# Backup & report files from converting an old project file to a newer
# Visual Studio version. Backup files are not needed, because we have git ;-)
_UpgradeReport_Files/
Backup*/
UpgradeLog*.XML
UpgradeLog*.htm
# SQL Server files
App_Data/*.mdf
App_Data/*.ldf
#############
## Windows detritus
#############
# Windows image file caches
Thumbs.db
ehthumbs.db
# Folder config file
Desktop.ini
# Recycle Bin used on file shares
$RECYCLE.BIN/
# Mac crap
.DS_Store
#############
## Python
#############
*.py[cod]
# Packages
*.egg
*.egg-info
dist/
build/
eggs/
parts/
var/
sdist/
develop-eggs/
.installed.cfg
# Installer logs
pip-log.txt
# Unit test / coverage reports
.coverage
.tox
#Translations
*.mo
#Mr Developer
.mr.developer.cfg

122
CleanUp.lua Normal file
View File

@ -0,0 +1,122 @@
--- CLEANUP Classes
-- @classmod CLEANUP
-- @author Flightcontrol
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Mission" )
Include.File( "Client" )
Include.File( "Task" )
CLEANUP = {
ClassName = "CLEANUP",
ZoneNames = {},
TimeInterval = 300,
CleanUpList = {},
}
--- Creates the main object which is handling the cleaning of the debris within the given Zone Names.
-- @tparam table{string,...}|string ZoneNames which is a table of zone names where the debris should be cleaned. Also a single string can be passed with one zone name.
-- @tparam ?number TimeInterval is the interval in seconds when the clean activity takes place. The default is 300 seconds, thus every 5 minutes.
-- @treturn CLEANUP
-- @usage
-- -- Clean these Zones.
-- CleanUpAirports = CLEANUP:New( { 'CLEAN Tbilisi', 'CLEAN Kutaisi' }, 150 )
-- or
-- CleanUpTbilisi = CLEANUP:New( 'CLEAN Tbilisi', 150 )
-- CleanUpKutaisi = CLEANUP:New( 'CLEAN Kutaisi', 600 )
function CLEANUP:New( ZoneNames, TimeInterval )
trace.f( self.ClassName, { ZoneNames, TimeInterval } )
-- Arrange meta tables
local self = BASE:Inherit( self, BASE:New() )
if type( ZoneNames ) == 'table' then
self.ZoneNames = ZoneNames
else
self.ZoneNames = { ZoneNames }
end
if TimeInterval then
self.TimeInterval = TimeInterval
end
self:AddEvent( world.event.S_EVENT_ENGINE_SHUTDOWN, self._EventAddForCleanUp )
self:AddEvent( world.event.S_EVENT_HIT, self._EventAddForCleanUp )
self:AddEvent( world.event.S_EVENT_DEAD, self._EventAddForCleanUp )
self:EnableEvents()
self.CleanUpFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, TimeInterval )
return self
end
--- Detects if the Unit has an S_EVENT_ENGINE_SHUTDOWN or an S_EVENT_HIT within the given ZoneNames. If this is the case, add the Group to the CLEANUP List.
function CLEANUP:_EventAddForCleanUp( event )
trace.f( self.ClassName, { event } )
if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT then
local CleanUpUnit = event.initiator -- the Unit
local CleanUpUnitName = event.initiator:getName() -- return the name of the Unit
local CleanUpGroup = Unit.getGroup(event.initiator)-- Identify the Group
local CleanUpGroupName = CleanUpGroup:getName() -- return the name of the Group
if not self.CleanUpList[CleanUpGroupName] then
local AddForCleanUp = false
if routines.IsUnitInZones( CleanUpUnit, self.ZoneNames ) ~= nil then
AddForCleanUp = true
env.info( "CleanUp:" .. CleanUpGroupName .. "/" .. CleanUpUnitName )
end
if AddForCleanUp == true then
self.CleanUpList[CleanUpGroupName] = CleanUpUnitName
end
end
end
if event.target and Object.getCategory(event.target) == Object.Category.UNIT then
local CleanUpTgtUnit = event.target -- the target Unit
if CleanUpTgtUnit then
local CleanUpTgtUnitName = event.target:getName() -- return the name of the target Unit
local CleanUpTgtGroup = Unit.getGroup(event.target)-- Identify the target Group
local CleanUpTgtGroupName = CleanUpTgtGroup:getName() -- return the name of the target Group
if not self.CleanUpList[CleanUpTgtGroupName] then
local AddForCleanUp = false
if routines.IsUnitInZones( CleanUpTgtUnit, self.ZoneNames ) ~= nil then
AddForCleanUp = true
env.info( "CleanUp:" .. CleanUpTgtGroupName .. "/" .. CleanUpTgtUnitName )
end
if AddForCleanUp == true then
self.CleanUpList[CleanUpTgtGroupName] = CleanUpTgtUnitName
end
end
end
end
end
--- At the defined time interval, CleanUp the Groups within the CleanUpList.
function CLEANUP:_Scheduler()
for GroupName, ListData in pairs( self.CleanUpList ) do
env.info( "CleanUp: GroupName = " .. GroupName )
env.info( "CleanUp: UnitName = " .. ListData )
local CleanUpGroup = Group.getByName( GroupName )
local CleanUpUnit = Unit.getByName( ListData )
if CleanUpUnit and CleanUpGroup then
env.info( "CleanUp: Check Database" )
if _Database:GetStatusGroup( GroupName ) ~= "ReSpawn" then
env.info( "CleanUp: Database OK" )
local CleanUpUnitVelocity = CleanUpUnit:getVelocity()
local CleanUpUnitVelocityTotal = math.abs(CleanUpUnitVelocity.x) + math.abs(CleanUpUnitVelocity.y) + math.abs(CleanUpUnitVelocity.z)
if CleanUpUnitVelocityTotal < 1 then
env.info( "CleanUp: Destroy: " .. GroupName )
CleanUpGroup:destroy()
ListData = nil
end
else
ListData = nil
end
else
ListData = nil -- Not anymore in the DCSRTE
end
end
end

240
Client.lua Normal file
View File

@ -0,0 +1,240 @@
--- CLIENT Classes
-- @classmod CLIENT
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Cargo" )
Include.File( "Message" )
--- Clients are those Groups defined within the Mission Editor that have the skillset defined as "Client" or "Player".
-- These clients are defined within the Mission Orchestration Framework (MOF)
CLIENT = {
ONBOARDSIDE = {
NONE = 0,
LEFT = 1,
RIGHT = 2,
BACK = 3,
FRONT = 4
},
ClassName = "CLIENT",
ClientName = nil,
ClientAlive = false,
ClientTransport = false,
ClientBriefingShown = false,
_Menus = {},
_Cargos = {},
_Tasks = {},
Messages = {
}
}
--- Use this method to register new Clients within the MOF.
-- @tparam string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client.
-- @tparam string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client.
-- @treturn CLIENT
-- @usage
-- -- Create new Clients.
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
-- Mission:AddGoal( DeploySA6TroopsGoal )
--
-- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*HOT-Deploy Troops 1' ):Transport() )
-- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*RAMP-Deploy Troops 3' ):Transport() )
-- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*HOT-Deploy Troops 2' ):Transport() )
-- Mission:AddClient( CLIENT:New( 'RU MI-8MTV2*RAMP-Deploy Troops 4' ):Transport() )
function CLIENT:New( ClientName, ClientBriefing )
trace.f(self.ClassName)
-- Arrange meta tables
local self = BASE:Inherit( self, BASE:New() )
self.ClientName = ClientName
self:AddBriefing( ClientBriefing )
self.MessageSwitch = true
return self
end
--- Resets a CLIENT.
-- @tparam string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client.
function CLIENT:Reset( ClientName )
trace.f(self.ClassName)
self._Menus = {}
self._Cargos = {}
end
--- ClientGroup returns the Group of a Client.
-- @treturn Group
function CLIENT:ClientGroup()
--trace.f(self.ClassName)
local GroupExists = Group.getByName( self.ClientName )
if GroupExists::isExist() then
return GroupExists
else
return nil
end
end
--- Transport defines that the Client is a Transport.
-- @treturn CLIENT
function CLIENT:Transport()
trace.f(self.ClassName)
self.ClientTransport = true
return self
end
--- AddBriefing adds a briefing to a Client when a Player joins a Mission.
-- @tparam string ClientBriefing is the text defining the Mission briefing.
-- @treturn CLIENT
function CLIENT:AddBriefing( ClientBriefing )
trace.f(self.ClassName)
self.ClientBriefing = ClientBriefing
return self
end
--- IsTransport returns if a Client is a transport.
-- @treturn bool
function CLIENT:IsTransport()
trace.f(self.ClassName)
return self.ClientTransport
end
--- FindCargo finds loaded Cargo within a CLIENT instance.
-- Cargo is loaded when certain PICK-UP or DEPLOY Tasks are properly executed.
-- @tparam string CargoName is the name of the cargo.
-- @treturn CARGO_TYPE
function CLIENT:FindCargo( CargoName )
trace.f(self.ClassName)
return self._Cargos[CargoName]
end
--- ShowCargo shows the @{CARGO} within the CLIENT to the Player.
-- The @{CARGO} is shown throught the MESSAGE system of DCS World.
function CLIENT:ShowCargo()
trace.f(self.ClassName)
local CargoMsg = ""
for CargoName, Cargo in pairs( self._Cargos ) do
if CargoMsg ~= "" then
CargoMsg = CargoMsg .. "\n"
end
CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType.TEXT .. " Weight: " .. Cargo.CargoWeight
end
if CargoMsg == '' then
CargoMsg = "empty"
end
self:Message( CargoMsg, 15, self.ClientName .. "/Cargo", "Co-Pilot: Cargo Status", 30 )
end
--- AddCargo allows to add @{CARGO} on the CLIENT.
-- @tparam string CargoName is the name of the @{CARGO}.
-- @tparam string CargoGroupName is the name of an active Group defined within the Mission Editor or Dynamically Spawned. Note that this is only applicable for Unit @{CARGO} Types.
-- @tparam CARGO_TYPE CargoType is the Type of the @{CARGO}.
-- @tparam number CargoWeight is the weight of the cargo in Kg.
-- @tparam string CargoGroupTemplate is the name of an active Group defined within the Mission Editor with "Late Activation".
-- @treturn CLIENT
function CLIENT:AddCargo( CargoName, CargoGroupName, CargoType, CargoWeight, CargoGroupTemplate )
trace.f(self.ClassName, { CargoName, CargoGroupName, CargoType, CargoWeight, CargoGroupTemplate } )
local Valid = true
Valid = routines.ValidateString( CargoName, "CargoName", Valid )
Valid = routines.ValidateEnumeration( CargoType, "CargoType", CARGO_TYPE, Valid )
Valid = routines.ValidateNumber( CargoWeight, "CargoWeight", Valid )
if Valid then
local Cargo = {}
Cargo.CargoName = CargoName
Cargo.CargoGroupName = CargoGroupName
Cargo.CargoType = CargoType
Cargo.CargoWeight = CargoWeight
Cargo.CargoGroupTemplate = CargoGroupTemplate
self._Cargos[CargoName] = Cargo
self:ShowCargo()
end
return self
end
--- RemoveCargo removes @{CARGO} from the CLIENT.
-- @tparam string CargoName is the name of the @{CARGO}.
-- @treturn Cargo
function CLIENT:RemoveCargo( CargoName )
trace.f(self.ClassName, { CargoName } )
local Valid = true
local Cargo = nil
Valid = routines.ValidateString( CargoName, "CargoName", Valid )
if Valid then
trace.i( "CLIENT", "RemoveCargo: CargoName = " .. CargoName )
Cargo = routines.utils.deepCopy( self._Cargos[CargoName] )
self._Cargos[CargoName] = nil
end
return Cargo
end
--- SwitchMessages is a local function called by the DCS World Menu system to switch off messages.
function CLIENT.SwitchMessages( PrmTable )
PrmTable[1].MessageSwitch = PrmTable[2]
end
--- Message is the key Message driver for the CLIENT class.
-- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system.
-- @tparam string Message is the text describing the message.
-- @tparam number MessageDuration is the duration in seconds that the Message should be displayed.
-- @tparam string MessageId is a text identifying the Message in the MessageQueue. The Message system overwrites Messages with the same MessageId
-- @tparam string MessageCategory is the category of the message (the title).
-- @tparam number MessageInterval is the interval in seconds between the display of the Message when the CLIENT is in the air.
function CLIENT:Message( Message, MessageDuration, MessageId, MessageCategory, MessageInterval )
trace.f( self.ClassName, { Message, MessageDuration, MessageId, MessageCategory, MessageInterval } )
if not self.MenuMessages then
if self:ClientGroup() and self:ClientGroup():getID() then
self.MenuMessages = MENU_SUB_GROUP:New( self:ClientGroup():getID(), 'Messages' )
self.MenuRouteMessageOn = MENU_COMMAND_GROUP:New( self:ClientGroup():getID(), 'Messages On', self.MenuMessages, CLIENT.SwitchMessages, { self, true } )
self.MenuRouteMessageOff = MENU_COMMAND_GROUP:New( self:ClientGroup():getID(),'Messages Off', self.MenuMessages, CLIENT.SwitchMessages, { self, false } )
end
end
if self.MessageSwitch == true then
if MessageCategory == nil then
MessageCategory = "Messages"
end
if self.Messages[MessageId] == nil then
self.Messages[MessageId] = {}
self.Messages[MessageId].MessageId = MessageId
self.Messages[MessageId].MessageTime = timer.getTime()
self.Messages[MessageId].MessageDuration = MessageDuration
if MessageInterval == nil then
self.Messages[MessageId].MessageInterval = 300
else
self.Messages[MessageId].MessageInterval = MessageInterval
end
MESSAGE:New( Message, MessageCategory, MessageDuration, MessageId ):ToClient( self )
else
if self:ClientGroup() and self:ClientGroup():getUnits() and self:ClientGroup():getUnits()[1] and not self:ClientGroup():getUnits()[1]:inAir() then
if timer.getTime() - self.Messages[MessageId].MessageTime >= self.Messages[MessageId].MessageDuration - 1 then
MESSAGE:New( Message, MessageCategory, MessageDuration, MessageId ):ToClient( self )
self.Messages[MessageId].MessageTime = timer.getTime()
end
else
if timer.getTime() - self.Messages[MessageId].MessageTime >= self.Messages[MessageId].MessageDuration + self.Messages[MessageId].MessageInterval then
MESSAGE:New( Message, MessageCategory, MessageDuration, MessageId ):ToClient( self )
self.Messages[MessageId].MessageTime = timer.getTime()
end
end
end
end
end

696
Database.lua Normal file
View File

@ -0,0 +1,696 @@
--- 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).
-- @classmod DATABASE
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Menu" )
DATABASE = {
ClassName = "DATABASE",
Units = {},
Groups = {},
NavPoints = {},
Statics = {},
Players = {},
ClientsByName = {},
ClientsByID = {},
}
DATABASECoalition =
{
[1] = "Red",
[2] = "Blue",
}
DATABASECategory =
{
[Unit.Category.AIRPLANE] = "Plane",
[Unit.Category.HELICOPTER] = "Helicopter",
[Unit.Category.GROUND_UNIT] = "Ground",
[Unit.Category.SHIP] = "Ship",
[Unit.Category.STRUCTURE] = "Structure",
}
--- Creates a new DATABASE Object to administer the Groups defined and alive within the DCSRTE.
-- @treturn DATABASE
-- @usage
-- -- Define a new DATABASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE.
-- DBObject = DATABASE:New()
function DATABASE:New()
trace.f(self.ClassName )
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() )
self.Navpoints = {}
self.Units = {}
--Build routines.db.units and self.Navpoints
for coa_name, coa_data in pairs(env.mission.coalition) do
if (coa_name == 'red' or coa_name == 'blue') and type(coa_data) == 'table' then
self.Units[coa_name] = {}
----------------------------------------------
-- build nav points DB
self.Navpoints[coa_name] = {}
if coa_data.nav_points then --navpoints
for nav_ind, nav_data in pairs(coa_data.nav_points) do
if type(nav_data) == 'table' then
self.Navpoints[coa_name][nav_ind] = routines.utils.deepCopy(nav_data)
self.Navpoints[coa_name][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory.
self.Navpoints[coa_name][nav_ind]['point'] = {} -- point is used by SSE, support it.
self.Navpoints[coa_name][nav_ind]['point']['x'] = nav_data.x
self.Navpoints[coa_name][nav_ind]['point']['y'] = 0
self.Navpoints[coa_name][nav_ind]['point']['z'] = nav_data.y
end
end
end
-------------------------------------------------
if coa_data.country then --there is a country table
for cntry_id, cntry_data in pairs(coa_data.country) do
local countryName = string.lower(cntry_data.name)
self.Units[coa_name][countryName] = {}
self.Units[coa_name][countryName]["countryId"] = cntry_data.id
if type(cntry_data) == 'table' then --just making sure
for obj_type_name, obj_type_data in pairs(cntry_data) do
if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then --should be an unncessary check
local category = obj_type_name
if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group!
self.Units[coa_name][countryName][category] = {}
for group_num, GroupTemplate in pairs(obj_type_data.group) do
if GroupTemplate and GroupTemplate.units and type(GroupTemplate.units) == 'table' then --making sure again- this is a valid group
self:_RegisterGroup( GroupTemplate )
end --if GroupTemplate and GroupTemplate.units then
end --for group_num, GroupTemplate in pairs(obj_type_data.group) do
end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then
end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then
end --for obj_type_name, obj_type_data in pairs(cntry_data) do
end --if type(cntry_data) == 'table' then
end --for cntry_id, cntry_data in pairs(coa_data.country) do
end --if coa_data.country then --there is a country table
end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then
end --for coa_name, coa_data in pairs(mission.coalition) do
--self:AddEvent( world.event.S_EVENT_BIRTH, self.OnBirth )
self:AddEvent( world.event.S_EVENT_DEAD, self.OnDeadOrCrash )
self:AddEvent( world.event.S_EVENT_CRASH, self.OnDeadOrCrash )
self:AddEvent( world.event.S_EVENT_HIT, self.OnHit)
self:EnableEvents()
self.SchedulerId = routines.scheduleFunction( DATABASE._FollowPlayers, { self }, 0, 5 )
self:ScoreMenu()
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 )
trace.f( self.ClassName, SpawnTemplate )
trace.i( self.ClassName, { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID, SpawnTemplate.name } )
local SpawnCountryID = SpawnTemplate.SpawnCountryID
local SpawnCategoryID = SpawnTemplate.SpawnCategoryID
SpawnTemplate.SpawnCoalitionID = nil
SpawnTemplate.SpawnCountryID = nil
SpawnTemplate.SpawnCategoryID = nil
self:_RegisterGroup( SpawnTemplate )
coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate )
end
--- Set a status to a Group within the Database, this to check crossing events for example.
function DATABASE:SetStatusGroup( GroupName, Status )
trace.f( self.ClassName, 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 )
trace.f( self.ClassName, Status )
if self.Groups[GroupName] then
return self.Groups[GroupName].Status
else
return ""
end
end
--- Private
-- @section Private
--- 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].UnitCount = #GroupTemplate.units
trace.i( self.ClassName, { "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
trace.i( self.ClassName, { "Unit", self.Units[UnitTemplateName].UnitName } )
end
end
--- Events
-- @section Events
--- Track DCSRTE DEAD or CRASH events for the internal scoring.
function DATABASE:OnDeadOrCrash( event )
trace.f( self.ClassName, { event } )
local TargetUnitName = nil
local TargetGroupName = nil
local TargetPlayerName = nil
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT then
TargetUnitName = event.initiator:getName()
TargetGroupName = Unit.getGroup(event.initiator):getName()
TargetPlayerName = event.initiator:getPlayerName()
TargetCoalition = Unit.getGroup(event.initiator):getCoalition()
TargetCategory = Unit.getGroup(event.initiator):getCategory()
TargetType = event.initiator:getTypeName()
TargetUnitCoalition = DATABASECoalition[TargetCoalition]
TargetUnitCategory = DATABASECategory[TargetCategory]
TargetUnitType = TargetType
trace.i( self.ClassName, { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } )
end
for PlayerName, PlayerData in pairs( self.Players ) do
if PlayerData then -- This should normally not happen, but i'll test it anyway.
trace.i( self.ClassName, "Something got killed" )
-- Some variables
local InitUnitCoalition = DATABASECoalition[PlayerData.UnitCoalition]
local InitUnitCategory = DATABASECategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
-- What is he hitting?
if TargetCategory then
if PlayerData and PlayerData.Hit and PlayerData.Hit[TargetCategory] and PlayerData.Hit[TargetCategory][TargetUnitName] then -- Was there a hit for this unit for this player before registered???
if not PlayerData.Kill[TargetCategory] then
PlayerData.Kill[TargetCategory] = {}
end
if not PlayerData.Kill[TargetCategory][TargetType] then
PlayerData.Kill[TargetCategory][TargetType] = {}
PlayerData.Kill[TargetCategory][TargetType].Score = 0
PlayerData.Kill[TargetCategory][TargetType].ScoreKill = 0
PlayerData.Kill[TargetCategory][TargetType].Penalty = 0
PlayerData.Kill[TargetCategory][TargetType].PenaltyKill = 0
end
if PlayerData.UnitCoalition == TargetCoalition then
PlayerData.Kill[TargetCategory][TargetType].Penalty = PlayerData.Kill[TargetCategory][TargetType].Penalty + 25
PlayerData.Kill[TargetCategory][TargetType].PenaltyKill = PlayerData.Kill[TargetCategory][TargetType].PenaltyKill + 1
MESSAGE:New( "Player '" .. PlayerName .. "' killed a target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
PlayerData.Kill[TargetCategory][TargetType].PenaltyKill .. " times. Score: " .. PlayerData.Kill[TargetCategory][TargetType].Penalty,
"Game Status: Score", 20, "/PENALTY" .. PlayerName .. "/" .. InitUnitName ):ToAll()
self:ScoreAdd( PlayerName, "KILL_PENALTY", 1, -125, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
PlayerData.Kill[TargetCategory][TargetType].Score = PlayerData.Kill[TargetCategory][TargetType].Score + 10
PlayerData.Kill[TargetCategory][TargetType].ScoreKill = PlayerData.Kill[TargetCategory][TargetType].ScoreKill + 1
MESSAGE:New( "Player '" .. PlayerName .. "' killed a target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
PlayerData.Kill[TargetCategory][TargetType].ScoreKill .. " times. Score: " .. PlayerData.Kill[TargetCategory][TargetType].Score,
"Game Status: Score", 20, "/SCORE" .. PlayerName .. "/" .. InitUnitName ):ToAll()
self:ScoreAdd( PlayerName, "KILL_SCORE", 1, 10, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end
end
end
end
end
end
--- Scheduled
-- @section Scheduled
--- Follows new players entering Clients within the DCSRTE.
function DATABASE:_FollowPlayers()
trace.scheduled( self.ClassName, "_FollowPlayers" )
local ClientUnit = 0
local CoalitionsData = { AlivePlayersRed = coalition.getPlayers(coalition.side.RED), AlivePlayersBlue = coalition.getPlayers(coalition.side.BLUE) }
local unitId
local unitData
local AlivePlayerUnits = {}
for CoalitionId, CoalitionData in pairs( CoalitionsData ) do
trace.l( self.ClassName, "_FollowPlayers", CoalitionData )
for UnitId, UnitData in pairs( CoalitionData ) do
self:_AddPlayerFromUnit( UnitData )
end
end
end
--- Private
-- @section Private
--- Add a new player entering a Unit.
function DATABASE:_AddPlayerFromUnit( UnitData )
trace.f( self.ClassName, UnitData )
if UnitData:isExist() then
local UnitName = UnitData:getName()
local GroupName = Unit.getGroup(UnitData):getName()
local PlayerName = UnitData:getPlayerName()
trace.i(self.ClassName, "Player : " .. PlayerName .. " Unit : " .. UnitName .. " Group : " .. GroupName )
if self.Players[PlayerName] == nil then -- I believe this is the place where a Player gets a life in a mission when he enters a unit ...
self.Players[PlayerName] = {}
self.Players[PlayerName].Hit = {}
self.Players[PlayerName].Kill = {}
self.Players[PlayerName].Mission = {}
-- for CategoryID, CategoryName in pairs( DATABASECategory ) do
-- self.Players[PlayerName].Hit[CategoryID] = {}
-- self.Players[PlayerName].Kill[CategoryID] = {}
-- end
self.Players[PlayerName].HitPlayers = {}
self.Players[PlayerName].HitUnits = {}
self.Players[PlayerName].Penalty = 0
self.Players[PlayerName].PenaltyCoalition = 0
end
if not self.Players[PlayerName].UnitCoalition then
self.Players[PlayerName].UnitCoalition = Unit.getGroup(UnitData):getCoalition()
else
if self.Players[PlayerName].UnitCoalition ~= Unit.getGroup(UnitData):getCoalition() then
self.Players[PlayerName].Penalty = self.Players[PlayerName].Penalty + 50
self.Players[PlayerName].PenaltyCoalition = self.Players[PlayerName].PenaltyCoalition + 1
MESSAGE:New( "Player '" .. PlayerName .. "' changed coalition from " .. DATABASECoalition[self.Players[PlayerName].UnitCoalition] .. " to " .. DATABASECoalition[Unit.getGroup(UnitData):getCoalition()] ..
"(changed " .. self.Players[PlayerName].PenaltyCoalition .. " times the coalition). 50 Penalty points added.",
"Game Status: Penalty", 20, "/PENALTYCOALITION" .. PlayerName ):ToAll()
self:ScoreAdd( PlayerName, "COALITION_PENALTY", 1, -50, self.Players[PlayerName].UnitName, DATABASECoalition[self.Players[PlayerName].UnitCoalition], DATABASECategory[self.Players[PlayerName].UnitCategory], self.Players[PlayerName].UnitType,
UnitName, DATABASECategory[Unit.getGroup(UnitData):getCoalition()], DATABASECategory[Unit.getGroup(UnitData):getCategory()], UnitData:getTypeName() )
end
end
self.Players[PlayerName].UnitName = UnitName
self.Players[PlayerName].GroupName = GroupName
self.Players[PlayerName].UnitCoalition = Unit.getGroup(UnitData):getCoalition()
self.Players[PlayerName].UnitCategory = Unit.getGroup(UnitData):getCategory()
self.Players[PlayerName].UnitType = UnitData:getTypeName()
end
end
--- Registers Scores the players completing a Mission Task.
function DATABASE:_AddMissionTaskScore( PlayerUnit, MissionName, Score )
trace.f( self.ClassName, { PlayerUnit, MissionName, Score } )
local PlayerName = PlayerUnit:getPlayerName()
if not self.Players[PlayerName].Mission[MissionName] then
self.Players[PlayerName].Mission[MissionName] = {}
self.Players[PlayerName].Mission[MissionName].ScoreTask = 0
self.Players[PlayerName].Mission[MissionName].ScoreMission = 0
end
self.Players[PlayerName].Mission[MissionName].ScoreTask = self.Players[PlayerName].Mission[MissionName].ScoreTask + Score
MESSAGE:New( "Player '" .. PlayerName .. "' has finished another Task in Mission '" .. MissionName .. "'. " ..
Score .. " Score points added.",
"Game Status: Task Completion", 20, "/SCORETASK" .. PlayerName ):ToAll()
_Database:ScoreAdd( PlayerName, "TASK_" .. MissionName:gsub( ' ', '_' ), 1, Score, PlayerUnit:getName() )
end
--- Registers Mission Scores for possible multiple players that contributed in the Mission.
function DATABASE:_AddMissionScore( MissionName, Score )
trace.f( self.ClassName, { PlayerUnit, MissionName, Score } )
for PlayerName, PlayerData in pairs( self.Players ) do
if PlayerData.Mission[MissionName] then
PlayerData.Mission[MissionName].ScoreMission = PlayerData.Mission[MissionName].ScoreMission + Score
MESSAGE:New( "Player '" .. PlayerName .. "' has finished Mission '" .. MissionName .. "'. " ..
Score .. " Score points added.",
"Game Status: Mission Completion", 20, "/SCOREMISSION" .. PlayerName ):ToAll()
_Database:ScoreAdd( PlayerName, "MISSION_" .. MissionName:gsub( ' ', '_' ), 1, Score )
end
end
end
--- Events
-- @section Events
function DATABASE:OnHit( event )
trace.f( self.ClassName, { event } )
local InitUnitName = nil
local InitGroupName = nil
local InitPlayerName = nil
local InitCoalition = nil
local InitCategory = nil
local InitType = nil
local InitUnitCoalition = nil
local InitUnitCategory = nil
local InitUnitType = nil
local TargetUnitName = nil
local TargetGroupName = nil
local TargetPlayerName = nil
local TargetCoalition = nil
local TargetCategory = nil
local TargetType = nil
local TargetUnitCoalition = nil
local TargetUnitCategory = nil
local TargetUnitType = nil
if event.initiator and event.initiator:getName() then
if event.initiator and Object.getCategory(event.initiator) == Object.Category.UNIT then
InitUnitName = event.initiator:getName()
InitGroupName = Unit.getGroup(event.initiator):getName()
InitPlayerName = event.initiator:getPlayerName()
InitCoalition = Unit.getGroup(event.initiator):getCoalition()
InitCategory = Unit.getGroup(event.initiator):getCategory()
InitType = event.initiator:getTypeName()
InitUnitCoalition = DATABASECoalition[InitCoalition]
InitUnitCategory = DATABASECategory[InitCategory]
InitUnitType = InitType
trace.i( self.ClassName, { InitUnitName, InitGroupName, InitPlayerName, InitCoalition, InitCategory, InitType , InitUnitCoalition, InitUnitCategory, InitUnitType } )
end
if event.target and Object.getCategory(event.target) == Object.Category.UNIT then
TargetUnitName = event.target:getName()
TargetGroupName = Unit.getGroup(event.target):getName()
TargetPlayerName = event.target:getPlayerName()
TargetCoalition = Unit.getGroup(event.target):getCoalition()
TargetCategory = Unit.getGroup(event.target):getCategory()
TargetType = event.target:getTypeName()
TargetUnitCoalition = DATABASECoalition[TargetCoalition]
TargetUnitCategory = DATABASECategory[TargetCategory]
TargetUnitType = TargetType
trace.i( self.ClassName, { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType, TargetUnitCoalition, TargetUnitCategory, TargetUnitType } )
end
if InitPlayerName ~= nil then -- It is a player that is hitting something
self:_AddPlayerFromUnit( event.initiator )
if self.Players[InitPlayerName] then -- This should normally not happen, but i'll test it anyway.
if TargetPlayerName ~= nil then -- It is a player hitting another player ...
self:_AddPlayerFromUnit( event.target )
self.Players[InitPlayerName].HitPlayers = self.Players[InitPlayerName].HitPlayers + 1
end
trace.i( self.ClassName, "Hitting Something" )
-- What is he hitting?
if TargetCategory then
if not self.Players[InitPlayerName].Hit[TargetCategory] then
self.Players[InitPlayerName].Hit[TargetCategory] = {}
end
if not self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName] then
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName] = {}
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score = 0
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty = 0
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit = 0
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit = 0
end
local Score = 0
if InitCoalition == TargetCoalition then
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty + 10
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit + 1
MESSAGE:New( "Player '" .. InitPlayerName .. "' hit a friendly " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].PenaltyHit .. " times. Penalty: " .. self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Penalty,
"Game Status: Penalty", 20, "/PENALTY" .. InitPlayerName .. "/" .. InitUnitName ):ToAll()
self:ScoreAdd( InitPlayerName, "HIT_PENALTY", 1, -25, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
else
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score + 1
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit = self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit + 1
MESSAGE:New( "Player '" .. InitPlayerName .. "' hit a target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " ..
self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].ScoreHit .. " times. Score: " .. self.Players[InitPlayerName].Hit[TargetCategory][TargetUnitName].Score,
"Game Status: Score", 20, "/SCORE" .. InitPlayerName .. "/" .. InitUnitName ):ToAll()
self:ScoreAdd( InitPlayerName, "HIT_SCORE", 1, 1, InitUnitName, InitUnitCoalition, InitUnitCategory, InitUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
end
end
end
elseif InitPlayerName == nil then -- It is an AI hitting a player???
end
end
end
function DATABASE:ReportScoreAll()
local ScoreMessage = ""
local PlayerMessage = ""
for PlayerName, PlayerData in pairs( self.Players ) do
if PlayerData then -- This should normally not happen, but i'll test it anyway.
trace.i( self.ClassName, "Score" )
-- Some variables
local InitUnitCoalition = DATABASECoalition[PlayerData.UnitCoalition]
local InitUnitCategory = DATABASECategory[PlayerData.UnitCategory]
local InitUnitType = PlayerData.UnitType
local InitUnitName = PlayerData.UnitName
local PlayerScore = 0
local PlayerPenalty = 0
ScoreMessage = ""
local ScoreMessageHits = ""
for CategoryID, CategoryName in pairs( DATABASECategory ) do
if PlayerData.Hit[CategoryID] then
local Score = 0
local ScoreHit = 0
local Penalty = 0
local PenaltyHit = 0
for UnitName, UnitData in pairs( PlayerData.Hit[CategoryID] ) do
Score = Score + UnitData.Score
ScoreHit = ScoreHit + UnitData.ScoreHit
Penalty = Penalty + UnitData.Penalty
PenaltyHit = UnitData.PenaltyHit
end
ScoreMessageHits = ScoreMessageHits .. string.format( " %s = %d score(%d;-%d) hits(#%d;#-%d)", CategoryName, Score - Penalty, Score, Penalty, ScoreHit, PenaltyHit )
PlayerScore = PlayerScore + Score
PlayerPenalty = PlayerPenalty + Penalty
else
--ScoreMessageHits = ScoreMessageHits .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 )
end
end
if ScoreMessageHits ~= "" then
ScoreMessage = ScoreMessage .. " Hits: " .. ScoreMessageHits .. " "
end
local ScoreMessageKills = ""
for CategoryID, CategoryName in pairs( DATABASECategory ) do
if PlayerData.Kill[CategoryID] then
local Score = 0
local ScoreKill = 0
local Penalty = 0
local PenaltyKill = 0
for UnitName, UnitData in pairs( PlayerData.Kill[CategoryID] ) do
Score = Score + UnitData.Score
ScoreKill = ScoreKill + UnitData.ScoreKill
Penalty = Penalty + UnitData.Penalty
PenaltyKill = PenaltyKill + UnitData.PenaltyKill
end
ScoreMessageKills = ScoreMessageKills .. string.format( " %s = %d score(%d;-%d) hits(#%d;#-%d)", CategoryName, Score - Penalty, Score, Penalty, ScoreKill, PenaltyKill )
PlayerScore = PlayerScore + Score
PlayerPenalty = PlayerPenalty + Penalty
else
--ScoreMessageKills = ScoreMessageKills .. string.format( "%s:%d ", string.format(CategoryName, 1, 1), 0 )
end
end
if ScoreMessageKills ~= "" then
ScoreMessage = ScoreMessage .. " Kills: " .. ScoreMessageKills .. " "
end
local ScoreMessageCoalitionChangePenalties = ""
if PlayerData.PenaltyCoalition ~= 0 then
ScoreMessageCoalitionChangePenalties = ScoreMessageCoalitionChangePenalties .. string.format( "-%d (%d changed)", PlayerData.Penalty, PlayerData.PenaltyCoalition )
PlayerPenalty = PlayerPenalty + PlayerData.Penalty
end
if ScoreMessageCoalitionChangePenalties ~= "" then
ScoreMessage = ScoreMessage .. " Coalition: " .. ScoreMessageCoalitionChangePenalties .. " "
end
local ScoreMessageMission = ""
local ScoreMission = 0
local ScoreTask = 0
for MissionName, MissionData in pairs( PlayerData.Mission ) do
ScoreMission = ScoreMission + MissionData.ScoreMission
ScoreTask = ScoreTask + MissionData.ScoreTask
ScoreMessageMission = ScoreMessageMission .. "'" .. MissionName .. "'; "
end
PlayerScore = PlayerScore + ScoreMission + ScoreTask
if ScoreMessageMission ~= "" then
ScoreMessage = ScoreMessage .. " Tasks: " .. ScoreTask .. " Mission: " .. ScoreMission .. " ( " .. ScoreMessageMission .. ") "
end
PlayerMessage = string.format( " Player '%s' Score = %d ( %d Score, -%d Penalties ):", PlayerName, PlayerScore - PlayerPenalty, PlayerScore, PlayerPenalty )
MESSAGE:New( PlayerMessage .. ScoreMessage, "Player Scores", 30, "/SCORE/" .. PlayerName ):ToAll()
end
end
end
function DATABASE:ReportScorePlayer()
end
function DATABASE:ScoreMenu()
local ReportScore = SUBMENU:New( 'Scoring' )
local ReportAllScores = COMMANDMENU:New( 'Score All Active Players', ReportScore, DATABASE.ReportScoreAll, self )
local ReportPlayerScores = COMMANDMENU:New('Your Current Score', ReportScore, DATABASE.ReportScorePlayer, self )
end
-- File Logic for tracking the scores
function DATABASE:SecondsToClock(sSeconds)
local nSeconds = sSeconds
if nSeconds == 0 then
--return nil;
return "00:00:00";
else
nHours = string.format("%02.f", math.floor(nSeconds/3600));
nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60)));
nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60));
return nHours..":"..nMins..":"..nSecs
end
end
function DATABASE:ScoreOpen()
local fdir = lfs.writedir() .. [[Logs\]] .. "Player_Scores_" .. os.date( "%Y-%m-%d_%H-%M-%S" ) .. ".csv"
self.StatFile, self.err = io.open(fdir,"w+")
if not self.StatFile then
error( "Error: Cannot open 'Player Scores.csv' file in " .. lfs.writedir() )
end
self.StatFile:write( '"Run-ID";Time;"PlayerName";"ScoreType";"PlayerUnitCoaltion";"PlayerUnitCategory";"PlayerUnitType"; "PlayerUnitName";"TargetUnitCoalition";"TargetUnitCategory";"TargetUnitType";"TargetUnitName";Times;Score\n' )
self.RunID = os.date("%y-%m-%d_%H-%M-%S")
end
function DATABASE:ScoreAdd( PlayerName, ScoreType, ScoreTimes, ScoreAmount, PlayerUnitName, PlayerUnitCoalition, PlayerUnitCategory, PlayerUnitType, TargetUnitName, TargetUnitCoalition, TargetUnitCategory, TargetUnitType )
--write statistic information to file
local ScoreTime = self:SecondsToClock(timer.getTime())
PlayerName = PlayerName:gsub( '"', '_' )
if PlayerUnitName and PlayerUnitName ~= '' then
local PlayerUnit = Unit.getByName( PlayerUnitName )
if PlayerUnit then
if not PlayerUnitCategory then
PlayerUnitCategory = DATABASECategory[Unit.getGroup(PlayerUnit):getCategory()]
end
if not PlayerUnitCoalition then
PlayerUnitCoalition = DATABASECoalition[Unit.getGroup(PlayerUnit):getCoalition()]
end
if not PlayerUnitType then
PlayerUnitType = PlayerUnit:getTypeName()
end
else
PlayerUnitName = ''
PlayerUnitCategory = ''
PlayerUnitCoalition = ''
PlayerUnitType = ''
end
else
PlayerUnitName = ''
PlayerUnitCategory = ''
PlayerUnitCoalition = ''
PlayerUnitType = ''
end
if not TargetUnitCoalition then
TargetUnitCoalition = ''
end
if not TargetUnitCategory then
TargetUnitCategory = ''
end
if not TargetUnitType then
TargetUnitType = ''
end
if not TargetUnitName then
TargetUnitName = ''
end
self.StatFile:write( '"' .. self.RunID .. '";' .. ScoreTime .. ';"' .. PlayerName .. '";"' .. ScoreType .. '";"' ..
PlayerUnitCoalition .. '";"' .. PlayerUnitCategory .. '";"' .. PlayerUnitType .. '";"' .. PlayerUnitName .. '";"' ..
TargetUnitCoalition .. '";"' .. TargetUnitCategory .. '";"' .. TargetUnitType .. '";"' .. TargetUnitName .. '";' ..
ScoreTimes .. ';' .. ScoreAmount )
self.StatFile:write( "\n" )
end
function LogClose()
self.StatFile:close()
end
_Database = DATABASE:New()
_Database:ScoreOpen()

119
DeployTask.lua Normal file
View File

@ -0,0 +1,119 @@
--- A DEPLOYTASK orchestrates the deployment of CARGO within a specific landing zone.
-- @classmod DEPLOYTASK
Include.File( "Task" )
DEPLOYTASK = {
ClassName = "DEPLOYTASK",
TEXT = { "Deploy", "deployed", "unloaded" },
GoalVerb = "Deployment"
}
--- Creates a new DEPLOYTASK object, which models the sequence of STAGEs to unload a cargo.
-- @tparam table{string,...}|string LandingZones Table or name of the zone(s) where Cargo is to be unloaded.
-- @tparam CARGO_TYPE CargoType Type of the Cargo.
function DEPLOYTASK:New( LandingZones, CargoType )
trace.f(self.ClassName)
-- Child holds the inherited instance of the DEPLOYTASK Class to the BASE class.
local Child = BASE:Inherit( self, TASK:New() )
local Valid = true
Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid )
Valid = routines.ValidateEnumeration( CargoType, "CargoType", CARGO_TYPE, Valid )
if Valid then
Child.Name = 'Deploy Cargo'
Child.TaskBriefing = "Task: Fly to one of the indicated landing zones and deploy " .. CargoType.TEXT .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the deployment zone."
if type( LandingZones ) == "table" then
Child.LandingZones = LandingZones
else
Child.LandingZones = { LandingZones }
end
Child.CargoType = CargoType
Child.GoalVerb = CargoType.TEXT .. " " .. self.GoalVerb
Child.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGEUNLOAD:New(), STAGEDONE:New() }
Child.SetStage( Child, 1 )
end
return Child
end
--- When the cargo is unloaded, it will move to the target zone name.
-- @tparam string TargetZoneName Name of the Zone to where the Cargo should move after unloading.
function DEPLOYTASK:SetCargoTargetZoneName( TargetZoneName )
trace.f(self.ClassName)
local Valid = true
Valid = routines.ValidateString( TargetZoneName, "TargetZoneName", Valid )
if Valid then
self.TargetZoneName = TargetZoneName
end
return Valid
end
function DEPLOYTASK:AddCargoMenus( Client, Cargos, TransportRadius )
trace.f(self.ClassName, {Client, Cargos, TransportRadius})
for CargoID, CargoData in pairs( Client._Cargos ) do
trace.i( self.ClassName, { CargoData.CargoName } )
if Client._Menus[CargoData.CargoType] == nil then
Client._Menus[CargoData.CargoType] = {}
end
if not Client._Menus[CargoData.CargoType].DeployMenu then
Client._Menus[CargoData.CargoType].DeployMenu = missionCommands.addSubMenuForGroup(
Client:ClientGroup():getID(),
self.TEXT[1],
nil
)
trace.i( self.ClassName, 'Added DeployMenu ' .. self.TEXT[1] )
end
if Client._Menus[CargoData.CargoType].DeploySubMenus == nil then
Client._Menus[CargoData.CargoType].DeploySubMenus = {}
end
if Client._Menus[CargoData.CargoType].DeployMenu == nil then
trace.i( self.ClassName, 'deploymenu is nil' )
end
Client._Menus[CargoData.CargoType].DeploySubMenus[ #Client._Menus[CargoData.CargoType].DeploySubMenus + 1 ].MenuPath = missionCommands.addCommandForGroup(
Client:ClientGroup():getID(),
CargoData.CargoName .. " ( " .. CargoData.CargoWeight .. "kg )",
Client._Menus[CargoData.CargoType].DeployMenu,
self.MenuAction,
{ ReferenceTask = self, CargoName = CargoData.CargoName }
)
trace.i( self.ClassName, 'Added DeploySubMenu ' .. CargoData.CargoType.TEXT .. ":" .. CargoData.CargoName .. " ( " .. CargoData.CargoWeight .. "kg )" )
end
end
function DEPLOYTASK:RemoveCargoMenus( Client )
trace.f(self.ClassName, { Client } )
for MenuID, MenuData in pairs( Client._Menus ) do
if MenuData.DeploySubMenus ~= nil then
for SubMenuID, SubMenuData in pairs( MenuData.DeploySubMenus ) do
missionCommands.removeItemForGroup( Client:ClientGroup():getID(), SubMenuData )
trace.i( self.ClassName, "Removed DeploySubMenu " )
SubMenuData = nil
end
end
if MenuData.DeployMenu then
missionCommands.removeItemForGroup( Client:ClientGroup():getID(), MenuData.DeployMenu )
trace.i( self.ClassName, "Removed DeployMenu " )
MenuData.DeployMenu = nil
end
end
end

79
DestroyBaseTask.lua Normal file
View File

@ -0,0 +1,79 @@
--- A DESTROYBASETASK will monitor the destruction of Groups and Units. This is a BASE class, other classes are derived from this class.
-- @classmod DESTROYBASETASK
-- @see DESTROYGROUPSTASK
-- @see DESTROYUNITTYPESTASK
-- @see DESTROY_RADARS_TASK
Include.File("Task")
DESTROYBASETASK = {
ClassName = "DESTROYBASETASK",
Destroyed = 0,
GoalVerb = "Destroy",
DestroyPercentage = 100,
}
--- Creates a new DESTROYBASETASK.
-- @tparam string DestroyGroupType Text describing the group to be destroyed. f.e. "Radar Installations", "Ships", "Vehicles", "Command Centers".
-- @tparam string DestroyUnitType Text describing the unit types to be destroyed. f.e. "SA-6", "Row Boats", "Tanks", "Tents".
-- @tparam table{string,...} DestroyGroupPrefixes Table of Prefixes of the Groups to be destroyed before task is completed.
-- @tparam ?number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}.
-- @treturn DESTROYBASETASK
function DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupPrefixes, DestroyPercentage )
trace.f(self.ClassName)
-- Inheritance
local Child = BASE:Inherit( self, TASK:New() )
Child.Name = 'Destroy'
Child.Destroyed = 0
Child.DestroyGroupPrefixes = DestroyGroupPrefixes
Child.DestroyGroupType = DestroyGroupType
Child.DestroyUnitType = DestroyUnitType
Child.TaskBriefing = "Task: Destroy " .. DestroyGroupType .. "."
Child.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEGROUPSDESTROYED:New(), STAGEDONE:New() }
Child.SetStage( Child, 1 )
--Child.AddEvent( Child, world.event.S_EVENT_DEAD, Child.EventDead )
--env.info( 'New Table Child = ' .. tostring(Child) )
--env.info( 'New Table self = ' .. tostring(self) )
return Child
end
--- Handle the S_EVENT_DEAD events to validate the destruction of units for the task monitoring.
-- @param event Event structure of DCS world.
function DESTROYBASETASK:EventDead( event )
trace.f( self.ClassName, { 'EventDead', event } )
if event.initiator then
local DestroyGroup = Unit.getGroup( event.initiator )
local DestroyGroupName = DestroyGroup:getName()
local DestroyUnit = event.initiator
local DestroyUnitName = DestroyUnit:getName()
local UnitsDestroyed = 0
trace.i( self.ClassName, DestroyGroupName )
trace.i( self.ClassName, DestroyUnitName )
for DestroyGroupPrefixID, DestroyGroupPrefix in pairs( self.DestroyGroupPrefixes ) do
trace.i( self.ClassName, DestroyGroupPrefix )
if string.find( DestroyGroupName, DestroyGroupPrefix, 1, true ) then
trace.i( self.ClassName, BASE:Inherited(self).ClassName )
UnitsDestroyed = self:ReportGoalProgress( DestroyGroup, DestroyUnit )
trace.i( self.ClassName, UnitsDestroyed )
end
end
trace.i( self.ClassName, { UnitsDestroyed } )
self:IncreaseGoalCount( UnitsDestroyed, self.GoalVerb )
end
end
--- Validate task completeness of DESTROYBASETASK.
-- @param DestroyGroup Group structure describing the group to be evaluated.
-- @param DestroyUnit Unit structure describing the Unit to be evaluated.
function DESTROYBASETASK:ReportGoalProgress( DestroyGroup, DestroyUnit )
trace.f(self.ClassName)
return 0
end

57
DestroyGroupsTask.lua Normal file
View File

@ -0,0 +1,57 @@
--- DESTROYGROUPSTASK
-- @classmod DESTROYGROUPSTASK
Include.File("DestroyBaseTask")
--- To monitor and score the destruction of Groups in the DCSRTE.
DESTROYGROUPSTASK = {
ClassName = "DESTROYGROUPSTASK",
GoalVerb = "Destroy Groups",
}
--- Creates a new DESTROYGROUPSTASK.
-- @tparam string DestroyGroupType String describing the group to be destroyed.
-- @tparam string DestroyUnitType String describing the unit to be destroyed.
-- @tparam table{string,...} DestroyGroupNames Table of string containing the name of the groups to be destroyed before task is completed.
-- @tparam ?number DestroyPercentage defines the %-tage that needs to be destroyed to achieve mission success. eg. If in the Group there are 10 units, then a value of 75 would require 8 units to be destroyed from the Group to complete the @{TASK}.
---@treturn DESTROYGROUPSTASK
function DESTROYGROUPSTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage )
trace.f(self.ClassName)
-- Inheritance
local Child = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyPercentage ) )
Child.Name = 'Destroy Groups'
Child.GoalVerb = "Destroy " .. DestroyGroupType
Child.AddEvent( Child, world.event.S_EVENT_DEAD, Child.EventDead )
Child.AddEvent( Child, world.event.S_EVENT_CRASH, Child.EventDead )
--Child.AddEvent( Child, world.event.S_EVENT_PILOT_DEAD, Child.EventDead )
return Child
end
--- Report Goal Progress.
-- @tparam Group DestroyGroup Group structure describing the group to be evaluated.
-- @tparam Unit DestroyUnit Unit structure describing the Unit to be evaluated.
function DESTROYGROUPSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit )
trace.f(self.ClassName)
trace.i( self.ClassName, DestroyGroup:getSize() )
local DestroyCount = 0
if DestroyGroup then
if ( ( DestroyGroup:getInitialSize() * self.DestroyPercentage ) / 100 ) - DestroyGroup:getSize() <= 0 then
DestroyCount = 1
--[[ else
if DestroyGroup:getSize() == 1 then
if DestroyUnit and DestroyUnit:getLife() <= 1.0 then
DestroyCount = 1
end
end
]] end
else
DestroyCount = 1
end
return DestroyCount
end

42
DestroyRadarsTask.lua Normal file
View File

@ -0,0 +1,42 @@
--- Task class to destroy radar installations.
-- @classmod DESTROYRADARSTASK
Include.File("DestroyBaseTask")
DESTROYRADARSTASK = {
ClassName = "DESTROYRADARSTASK",
GoalVerb = "Destroy Radars"
}
--- Creates a new DESTROYRADARSTASK.
-- @tparam table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed.
-- @treturn DESTROYRADARSTASK
function DESTROYRADARSTASK:New( DestroyGroupNames )
trace.f(self.ClassName)
-- Inheritance
local Child = BASE:Inherit( self, DESTROYGROUPSTASK:New( 'radar installations', 'radars', DestroyGroupNames ) )
Child.Name = 'Destroy Radars'
Child.AddEvent( Child, world.event.S_EVENT_DEAD, Child.EventDead )
return Child
end
--- Report Goal Progress.
-- @tparam Group DestroyGroup Group structure describing the group to be evaluated.
-- @tparam Unit DestroyUnit Unit structure describing the Unit to be evaluated.
function DESTROYRADARSTASK:ReportGoalProgress( DestroyGroup, DestroyUnit )
trace.f(self.ClassName)
local DestroyCount = 0
if DestroyUnit and DestroyUnit:hasSensors( Unit.SensorType.RADAR, Unit.RadarType.AS ) then
if DestroyUnit and DestroyUnit:getLife() <= 1.0 then
trace.i( self.ClassName, 'Destroyed a radar' )
DestroyCount = 1
end
end
return DestroyCount
end

55
DestroyUnitTypesTask.lua Normal file
View File

@ -0,0 +1,55 @@
--- Set TASK to destroy certain unit types.
-- @classmod DESTROYUNITTYPESTASK
Include.File("DestroyBaseTask")
DESTROYUNITTYPESTASK = {
ClassName = "DESTROYUNITTYPESTASK",
GoalVerb = "Destroy",
}
--- Creates a new DESTROYUNITTYPESTASK.
-- @tparam string DestroyGroupType String describing the group to be destroyed. f.e. "Radar Installations", "Fleet", "Batallion", "Command Centers".
-- @tparam string DestroyUnitType String describing the unit to be destroyed. f.e. "radars", "ships", "tanks", "centers".
-- @tparam table{string,...} DestroyGroupNames Table of string containing the group names of which the radars are be destroyed.
-- @tparam string DestroyUnitTypes Table of string containing the type names of the units to achieve mission success.
-- @treturn DESTROYUNITTYPESTASK
function DESTROYUNITTYPESTASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames, DestroyUnitTypes )
trace.f(self.ClassName)
-- Inheritance
local Child = BASE:Inherit( self, DESTROYBASETASK:New( DestroyGroupType, DestroyUnitType, DestroyGroupNames ) )
if type(DestroyUnitTypes) == 'table' then
Child.DestroyUnitTypes = DestroyUnitTypes
else
Child.DestroyUnitTypes = { DestroyUnitTypes }
end
Child.Name = 'Destroy Unit Types'
Child.GoalVerb = "Destroy " .. DestroyGroupType
--env.info( 'New Types Child = ' .. tostring(Child) )
--env.info( 'New Types self = ' .. tostring(self) )
Child.AddEvent( Child, world.event.S_EVENT_DEAD, Child.EventDead )
return Child
end
--- Report Goal Progress.
-- @tparam Group DestroyGroup Group structure describing the group to be evaluated.
-- @tparam Unit DestroyUnit Unit structure describing the Unit to be evaluated.
function DESTROYUNITTYPESTASK:ReportGoalProgress( DestroyGroup, DestroyUnit )
trace.f(self.ClassName)
local DestroyCount = 0
for UnitTypeID, UnitType in pairs( self.DestroyUnitTypes ) do
if DestroyUnit and DestroyUnit:getTypeName() == UnitType then
if DestroyUnit and DestroyUnit:getLife() <= 1.0 then
DestroyCount = DestroyCount + 1
end
end
end
return DestroyCount
end

36
GoHomeTask.lua Normal file
View File

@ -0,0 +1,36 @@
--- A GOHOMETASK orchestrates the travel back to the home base, which is a specific zone defined within the ME.
-- @classmod GOHOMETASK
Include.File("Task")
GOHOMETASK = {
ClassName = "GOHOMETASK",
}
--- Creates a new GOHOMETASK.
-- @tparam table{string,...}|string LandingZones Table of Landing Zone names where Home(s) are located.
-- @treturn GOHOMETASK
function GOHOMETASK:New( LandingZones )
trace.f(self.ClassName)
-- Child holds the inherited instance of the PICKUPTASK Class to the BASE class.
local Child = BASE:Inherit( self, TASK:New() )
local Valid = true
Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid )
if Valid then
Child.Name = 'Fly Home'
Child.TaskBriefing = "Task: Fly back to your home base. Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to your home base."
if type( LandingZones ) == "table" then
Child.LandingZones = LandingZones
else
Child.LandingZones = { LandingZones }
end
Child.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() }
Child.SetStage( Child, 1 )
end
return Child
end

4
Install.bat Normal file
View File

@ -0,0 +1,4 @@
copy "..\MissionScripting.lua" "..\MissionScripting_old.lua"
pause
copy "Script_DCS\MissionScripting.lua" "..\MissionScripting.lua"
pause

71
Manual.md Normal file
View File

@ -0,0 +1,71 @@
# MOOSE Framework for Eagle Dynamics [DCS World](https://www.digitalcombatsimulator.com)
## Context
**MOOSE** is a **M**ission **O**bject **O**riented **S**cripting **E**nvironment, and is meant for mission designers and mission hosters.
It allows (will allow) to quickly setup complex missions using pre-scripted scenarios.
In order to run missions using this framework, you will need to install the framework within your Eagle Dynamics program files folder.
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.
## Currently supported functions
MOOSE contains currently the following mission design functions. The words in CAPITAL letters document the classes that can be used within MOOSE to accomplish these functions.
1. @{SPAWN} Dynamic spawning and respawning of Groups with related functions.
2. @{MOVEMENT} Keeps control over the amount of units driving around the battlefield simultaneously.
3. @{CLEANUP} Clean-Up zones with air units that crashed. Can be used to prevent airports blocking air traffic and ground control operations due to crashed units near the airfield.
4. @{SEAD} Enables the defenses for SAM sites. Mobile SAMs will evade fired anti-radiation missiles by shutting down their radars (for a while). If they are mobile, they will also drive away.
5. @{MISSION} Create Taskforces or Missions within a DCS Mission. A mission will consist of @{TASK}s, and @{CLIENT}s.
6. @{TASK} Add tasks for the mission. There are many different tasks that can be given, by using the classes derived from TASK.
* @{DEPLOYTASK} Deploy Cargo within a zone.
* @{PICKUPTASK} Pick-Up Cargo from a zone.
* @{GOHOMETASK} Fly back home.
* @{DESTROYGROUPSTASK} Destroy Groups.
* @{DESTROYUNITTYPESTASK} Destroy Units by measuring their UNIT Type.
* @{DESTROYRADARSTASK} Destroy Radars of SAM Groups.
* @{SLINGLOADHOOKTASK} Hook-Up Cargo within a zone and sling-load it outside of the zone.
* @{SLINGLOADUNHOOKTASK} Sling-load Cargo to a zone and Un-Hook the Cargo within the zone.
7. @{CLIENT} Registers a client within MOOSE.
7. @{MESSAGE} Send messages to @{CLIENT}s.
8. @{MENU} Menu System.
## Installation
The installation of the MOOSE framework is straightforward.
1. Extract MOOSE.zip. You can do this quickly by right clicking the MOOSE.zip file, and select "Extract All".
2. The extraction will add a directory within the Scripts directory of the Eagle Dynamics DCS world installation.
Example, the Scripts directory of my DCS World installation folder is C:\Program Files\Eagle Dynamics\DCS World\Scripts
3. Browse within the new MOOSE directory to the DCS_Script directory and run Install.bat as an administrator.
This will make a backup of the missionScripting.lua file and replace this with a new one.
And you're done.
## What has changed?
Not much. The missionscripting.lua file has been enhanced with the following code:
----
Include = {}
Include.LoadPath = 'Scripts/MOOSE'
Include.Files = {}
Include.File = function( IncludeFile )
if not Include.Files[ IncludeFile ] then
Include.Files[IncludeFile] = IncludeFile
dofile( Include.LoadPath .. "/" .. IncludeFile .. ".lua" )
env.info( "Include:" .. IncludeFile .. " loaded." )
end
end
Include.File( "Database" )
Include.File( "StatHandler" )
----
This code allows for the inclusion of the MOOSE framework, and this now becomes part of your DCS World Simulation Engine.
Missions designed with the MOOSE framework will now run.

107
Menu.lua Normal file
View File

@ -0,0 +1,107 @@
--- Encapsulation of DCS World Menu system in a set of MENU classes.
-- @classmod MENU
Include.File( "Routines" )
Include.File( "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
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
---
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
MENU_SUB_GROUP = {
ClassName = "MENU_SUB_GROUP"
}
function MENU_SUB_GROUP:New( GroupID, MenuText, ParentMenu )
-- Arrange meta tables
local MenuParentPath = nil
if ParentMenu ~= nil then
MenuParentPath = ParentMenu.MenuPath
end
local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
self.MenuPath = missionCommands.addSubMenuForGroup( GroupID, MenuText, MenuParentPath )
return self
end
MENU_COMMAND_GROUP = {
ClassName = "MENU_COMMAND_GROUP"
}
function MENU_COMMAND_GROUP:New( GroupID, MenuText, ParentMenu, CommandMenuFunction, CommandMenuArgument )
-- Arrange meta tables
local MenuParentPath = nil
if ParentMenu ~= nil then
MenuParentPath = ParentMenu.MenuPath
end
local self = BASE:Inherit( self, MENU:New( MenuText, MenuParentPath ) )
self.MenuPath = missionCommands.addCommandForGroup( GroupID, MenuText, MenuParentPath, CommandMenuFunction, CommandMenuArgument )
self.CommandMenuFunction = CommandMenuFunction
self.CommandMenuArgument = CommandMenuArgument
return self
end

210
Message.lua Normal file
View File

@ -0,0 +1,210 @@
--- Message System to display Messages for Clients and Coalitions or All.
-- Messages are grouped on the display panel per Category to improve readability for the players.
-- Messages are shown on the display panel for an amount of seconds, and will then disappear.
-- Messages are identified by an ID. The messages with the same ID belonging to the same category will be overwritten if they were still being displayed on the display panel.
-- Messages are created with MESSAGE:@{New}().
-- Messages are sent to Clients with MESSAGE:@{ToClient}().
-- Messages are sent to Coalitions with MESSAGE:@{ToCoalition}().
-- Messages are sent to All Players with MESSAGE:@{ToAll}().
-- @classmod MESSAGE
Include.File( "Trace" )
Include.File( "Base" )
MESSAGE = {
ClassName = "MESSAGE",
MessageCategory = 0,
MessageID = 0,
}
--- Creates a new MESSAGE object. Note that these MESSAGE objects are not yet displayed on the display panel. You must use the functions @{ToClient} or @{ToCoalition} or @{ToAll} to send these Messages to the respective recipients.
-- @tparam string MessageText is the text of the Message.
-- @tparam string MessageCategory is a string expressing the Category of the Message. Messages are grouped on the display panel per Category to improve readability.
-- @tparam number MessageDuration is a number in seconds of how long the MESSAGE should be shown on the display panel.
-- @tparam string MessageID is a string expressing the ID of the Message.
-- @treturn MESSAGE
-- @usage
-- -- Create a series of new Messages.
-- -- MessageAll is meant to be sent to all players, for 25 seconds, and is classified as "Score".
-- -- MessageRED is meant to be sent to the RED players only, for 10 seconds, and is classified as "End of Mission", with ID "Win".
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
-- -- MessageClient1 is meant to be sent to a Client, for 25 seconds, and is classified as "Score", with ID "Score".
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
function MESSAGE:New( MessageText, MessageCategory, MessageDuration, MessageID )
trace.f(self.ClassName, { MessageText, MessageCategory, MessageDuration, MessageID } )
local self = BASE:Inherit( self, BASE:New() )
self.MessageCategory = MessageCategory
self.MessageDuration = MessageDuration
self.MessageID = MessageID
self.MessageTime = timer.getTime()
self.MessageText = MessageText
self.MessageSent = false
self.MessageGroup = false
self.MessageCoalition = false
return self
end
--- Sends a MESSAGE to a Client Group. Note that the Group needs to be defined within the ME with the skillset "Client" or "Player".
-- @tparam CLIENT Client is the Group of the Client.
-- @treturn MESSAGE
-- @usage
-- -- Send the 2 messages created with the @{New} method to the Client Group.
-- -- Note that the Message of MessageClient2 is overwriting the Message of MessageClient1.
-- ClientGroup = Group.getByName( "ClientGroup" )
--
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- or
-- MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" ):ToClient( ClientGroup )
-- or
-- MessageClient1 = MESSAGE:New( "Congratulations, you've just hit a target", "Score", 25, "Score" )
-- MessageClient2 = MESSAGE:New( "Congratulations, you've just killed a target", "Score", 25, "Score" )
-- MessageClient1:ToClient( ClientGroup )
-- MessageClient2:ToClient( ClientGroup )
function MESSAGE:ToClient( Client )
trace.f(self.ClassName )
if Client and Client:ClientGroup() then
local ClientGroupName = Client:ClientGroup():getName()
if not _MessageQueue.ClientGroups[ClientGroupName] then
_MessageQueue.ClientGroups[ClientGroupName] = {}
_MessageQueue.ClientGroups[ClientGroupName].Messages = {}
end
_MessageQueue.ClientGroups[ClientGroupName].Messages[self.MessageID] = self
end
return self
end
--- Sends a MESSAGE to a Coalition.
-- @param CoalitionSide needs to be filled out by the defined structure of the standard scripting engine @{coalition.side}.
-- @treturn MESSAGE
-- @usage
-- -- Send a message created with the @{New} method to the RED coalition.
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
-- or
-- MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" ):ToCoalition( coalition.side.RED )
-- or
-- MessageRED = MESSAGE:New( "To the RED Players: You receive a penalty because you've killed one of your own units", "Penalty", 25, "Score" )
-- MessageRED:ToCoalition( coalition.side.RED )
function MESSAGE:ToCoalition( CoalitionSide )
trace.f(self.ClassName )
if CoalitionSide then
if not _MessageQueue.CoalitionSides[CoalitionSide] then
_MessageQueue.CoalitionSides[CoalitionSide] = {}
_MessageQueue.CoalitionSides[CoalitionSide].Messages = {}
end
_MessageQueue.CoalitionSides[CoalitionSide].Messages[self.MessageID] = self
end
return self
end
--- Sends a MESSAGE to all players.
-- @treturn MESSAGE
-- @usage
-- -- Send a message created to all players.
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
-- or
-- MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" ):ToAll()
-- or
-- MessageAll = MESSAGE:New( "To all Players: BLUE has won! Each player of BLUE wins 50 points!", "End of Mission", 25, "Win" )
-- MessageAll:ToAll()
function MESSAGE:ToAll()
trace.f(self.ClassName )
self:ToCoalition( coalition.side.RED )
self:ToCoalition( coalition.side.BLUE )
return self
end
--- MESSAGEQUEUE
-- @type MESSAGEQUEUE
MESSAGEQUEUE = {
ClientGroups = {},
CoalitionSides = {}
}
function MESSAGEQUEUE:New( RefreshInterval )
trace.f( self.ClassName, { RefreshInterval } )
local self = BASE:Inherit( self, BASE:New() )
self.RefreshInterval = RefreshInterval
self.DisplayFunction = routines.scheduleFunction( self._DisplayMessages, { self }, 0, RefreshInterval )
return self
end
--- This function is called automatically by the MESSAGEQUEUE scheduler.
function MESSAGEQUEUE:_DisplayMessages()
-- First we display all messages that a coalition needs to receive... Also those who are not in a client (CA module clients...).
for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do
for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do
if MessageData.MessageSent == false then
trigger.action.outTextForCoalition( CoalitionSideID, MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration )
MessageData.MessageSent = true
end
local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime()
if MessageTimeLeft <= 0 then
MessageData = nil
end
end
end
-- Then we send the messages for each individual client, but also to be included are those Coalition messages for the Clients who belong to a coalition.
-- Because the Client messages will overwrite the Coalition messages (for that Client).
for ClientGroupName, ClientGroupData in pairs( self.ClientGroups ) do
for MessageID, MessageData in pairs( ClientGroupData.Messages ) do
if MessageData.MessageGroup == false then
trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration )
MessageData.MessageGroup = true
end
local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime()
if MessageTimeLeft <= 0 then
MessageData = nil
end
end
-- Now check if the Client also has messages that belong to the Coalition of the Client...
for CoalitionSideID, CoalitionSideData in pairs( self.CoalitionSides ) do
for MessageID, MessageData in pairs( CoalitionSideData.Messages ) do
local CoalitionGroup = Group.getByName( ClientGroupName )
if CoalitionGroup and CoalitionGroup:getCoalition() == CoalitionSideID then
if MessageData.MessageCoalition == false then
trigger.action.outTextForGroup( Group.getByName(ClientGroupName):getID(), MessageData.MessageCategory .. '\n' .. MessageData.MessageText:gsub("\n$",""):gsub("\n$",""), MessageData.MessageDuration )
MessageData.MessageCoalition = true
end
end
local MessageTimeLeft = ( MessageData.MessageTime + MessageData.MessageDuration ) - timer.getTime()
if MessageTimeLeft <= 0 then
MessageData = nil
end
end
end
end
end
--- The _MessageQueue object is created when the MESSAGE class module is loaded.
_MessageQueue = MESSAGEQUEUE:New( 0.5 )

751
Mission.lua Normal file
View File

@ -0,0 +1,751 @@
--- A MISSION is the main owner of a Mission orchestration within MOOSE . The Mission framework orchestrates @{CLIENT}s, @{TASK}s, @{STAGE}s etc.
-- A @{CLIENT} needs to be registered within the @{MISSION} through the function @{AddClient}. A @{TASK} needs to be registered within the @{MISSION} through the function @{AddTask}.
-- @classmod MISSION
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Mission" )
Include.File( "Client" )
Include.File( "Task" )
MISSION = {
ClassName = "MISSION",
Name = "",
MissionStatus = "PENDING",
_Clients = {},
_Tasks = {},
_ActiveTasks = {},
_Cargos = {},
GoalFunction = nil,
MissionReportTrigger = 0,
MissionProgressTrigger = 0,
MissionReportShow = false,
MissionReportFlash = false,
MissionTimeInterval = 0,
MissionCoalition = "",
SUCCESS = 1,
FAILED = 2,
REPEAT = 3,
_GoalTasks = {}
}
CARGOSTATUS = {
NONE = 0,
LOADED = 1,
UNLOADED = 2,
LOADING = 3,
LoadCount= 0,
UnloadCount = 0
}
function MISSION:Meta()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, BASE:New() )
trace.r( self.ClassName, "", { Child } )
return Child
end
--- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc.
-- @tparam string MissionName is the name of the mission. This name will be used to reference the status of each mission by the players.
-- @tparam string MissionPriority is a string indicating the "priority" of the Mission. f.e. "Primary", "Secondary" or "First", "Second". It is free format and up to the Mission designer to choose. There are no rules behind this field.
-- @tparam string MissionBriefing is a string indicating the mission briefing to be shown when a player joins a @{CLIENT}.
-- @tparam string MissionCoalition is a string indicating the coalition or party to which this mission belongs to. It is free format and can be chosen freely by the mission designer. Note that this field is not to be confused with the coalition concept of the ME. Examples of a Mission Coalition could be "NATO", "CCCP", "Intruders", "Terrorists"...
-- @treturn MISSION
-- @usage
-- -- Declare a few missions.
-- local Mission = MISSIONSCHEDULER.AddMission( 'Russia Transport Troops SA-6', 'Operational', 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.', 'Russia' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'Patriots', 'Primary', 'Our intelligence reports that 3 Patriot SAM defense batteries are located near Ruisi, Kvarhiti and Gori.', 'Russia' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'Package Delivery', 'Operational', 'In order to be in full control of the situation, we need you to deliver a very important package at a secret location. Fly undetected through the NATO defenses and deliver the secret package. The secret agent is located at waypoint 4.', 'Russia' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue General', 'Tactical', 'Our intelligence has received a remote signal behind Gori. We believe it is a very important Russian General that was captured by Georgia. Go out there and rescue him! Ensure you stay out of the battle zone, keep south. Waypoint 4 is the location of our Russian General.', 'Russia' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'SA-6 SAMs', 'Primary', 'Our intelligence reports that 3 SA-6 SAM defense batteries are located near Didmukha, Khetagurov and Berula. Eliminate the Russian SAMs.', 'NATO' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Sling Load', 'Operational', 'Fly to the cargo pickup zone at Dzegvi or Kaspi, and sling the cargo to Soganlug airbase.', 'NATO' )
-- local Mission = MISSIONSCHEDULER.AddMission( 'Rescue secret agent', 'Tactical', 'In order to be in full control of the situation, we need you to rescue a secret agent from the woods behind enemy lines. Avoid the Russian defenses and rescue the agent. Keep south until Khasuri, and keep your eyes open for any SAM presence. The agent is located at waypoint 4 on your kneeboard.', 'NATO' )
function MISSION:New( MissionName, MissionPriority, MissionBriefing, MissionCoalition )
trace.f(self.ClassName, { MissionName, MissionPriority, MissionBriefing, MissionCoalition } )
self = MISSION:Meta()
local Valid = true
Valid = routines.ValidateString( MissionName, "MissionName", Valid )
Valid = routines.ValidateString( MissionPriority, "MissionPriority", Valid )
Valid = routines.ValidateString( MissionBriefing, "MissionBriefing", Valid )
Valid = routines.ValidateString( MissionCoalition, "MissionCoalition", Valid )
if Valid then
self.Name = MissionName
self.MissionPriority = MissionPriority
self.MissionBriefing = MissionBriefing
self.MissionCoalition = MissionCoalition
end
trace.r( self.ClassName, "" )
return self
end
--- Returns if a Mission has completed.
-- @treturn bool
function MISSION:IsCompleted()
trace.f(self.ClassName)
return self.MissionStatus == "ACCOMPLISHED"
end
--- Set a Mission to completed.
function MISSION:Completed()
trace.f(self.ClassName)
self.MissionStatus = "ACCOMPLISHED"
self:StatusToClients()
end
--- Returns if a Mission is ongoing.
-- treturn bool
function MISSION:IsOngoing()
trace.f(self.ClassName)
return self.MissionStatus == "ONGOING"
end
--- Set a Mission to ongoing.
function MISSION:Ongoing()
trace.f(self.ClassName)
self.MissionStatus = "ONGOING"
self:StatusToClients()
end
--- Returns if a Mission is pending.
-- treturn bool
function MISSION:IsPending()
trace.f(self.ClassName)
return self.MissionStatus == "PENDING"
end
--- Set a Mission to pending.
function MISSION:Pending()
trace.f(self.ClassName)
self.MissionStatus = "PENDING"
self:StatusToClients()
end
--- Returns if a Mission has failed.
-- treturn bool
function MISSION:IsFailed()
trace.f(self.ClassName)
return self.MissionStatus == "FAILED"
end
--- Set a Mission to failed.
function MISSION:Failed()
trace.f(self.ClassName)
self.MissionStatus = "FAILED"
self:StatusToClients()
end
--- Send the status of the MISSION to all Clients.
function MISSION:StatusToClients()
trace.f(self.ClassName)
if timer.getTime() >= self.MissionReportTrigger then
for ClientID, Client in pairs( self._Clients ) do
Client:Message( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. '! ( ' .. self.MissionPriority .. ' mission ) ', 10, self.Name .. '/Status', "Mission Command: Mission Status")
end
end
trace.e()
end
--- Handles the reporting. After certain time intervals, a MISSION report MESSAGE will be shown to All Players.
function MISSION:ReportTrigger()
trace.f(self.ClassName)
if self.MissionReportShow == true then
self.MissionReportShow = false
trace.r( "MISSION", "1", { true } )
return true
else
if self.MissionReportFlash == true then
if timer.getTime() >= self.MissionReportTrigger then
self.MissionReportTrigger = timer.getTime() + self.MissionTimeInterval
trace.r( "MISSION", "2", { true } )
return true
else
trace.r( "MISSION", "3", { false } )
return false
end
else
trace.r( "MISSION", "4", { false } )
return false
end
end
trace.e()
end
--- Report the status of all MISSIONs to all active Clients.
function MISSION:ReportToAll()
trace.f(self.ClassName)
local AlivePlayers = ''
for ClientID, Client in pairs( self._Clients ) do
if Client:ClientGroup() then
if Client:ClientGroup():getUnit(1) then
if Client:ClientGroup():getUnit(1):getLife() > 0.0 then
if AlivePlayers == '' then
AlivePlayers = ' Players: ' .. Client:ClientGroup():getUnit(1):getPlayerName()
else
AlivePlayers = AlivePlayers .. ' / ' .. Client:ClientGroup():getUnit(1):getPlayerName()
end
end
end
end
end
local Tasks = self:GetTasks()
local TaskText = ""
for TaskID, TaskData in pairs( Tasks ) do
TaskText = TaskText .. " - Task " .. TaskID .. ": " .. TaskData.Name .. ": " .. TaskData:GetGoalProgress() .. "\n"
end
MESSAGE:New( self.MissionCoalition .. ' "' .. self.Name .. '": ' .. self.MissionStatus .. ' ( ' .. self.MissionPriority .. ' mission )' .. AlivePlayers .. "\n" .. TaskText:gsub("\n$",""), "Mission Command: Mission Report", 10, self.Name .. '/Status'):ToAll()
trace.e()
end
--- Add a goal function to a MISSION. Goal functions are called when a @{TASK} within a mission has been completed.
-- @tparam function GoalFunction is the function defined by the mission designer to evaluate whether a certain goal has been reached after a @{TASK} finishes within the @{MISSION}. A GoalFunction must accept 2 parameters: Mission, Client, which contains the current MISSION object and the current CLIENT object respectively.
-- @usage
-- PatriotActivation = {
-- { "US SAM Patriot Zerti", false },
-- { "US SAM Patriot Zegduleti", false },
-- { "US SAM Patriot Gvleti", false }
-- }
--
-- function DeployPatriotTroopsGoal( Mission, Client )
--
--
-- -- Check if the cargo is all deployed for mission success.
-- for CargoID, CargoData in pairs( Mission._Cargos ) do
-- if Group.getByName( CargoData.CargoGroupName ) then
-- CargoGroup = Group.getByName( CargoData.CargoGroupName )
-- if CargoGroup then
-- -- Check if the cargo is ready to activate
-- CurrentLandingZoneID = routines.IsUnitInZones( CargoGroup:getUnits()[1], Mission:GetTask( 2 ).LandingZones ) -- The second task is the Deploytask to measure mission success upon
-- if CurrentLandingZoneID then
-- if PatriotActivation[CurrentLandingZoneID][2] == false then
-- -- Now check if this is a new Mission Task to be completed...
-- trigger.action.setGroupAIOn( Group.getByName( PatriotActivation[CurrentLandingZoneID][1] ) )
-- PatriotActivation[CurrentLandingZoneID][2] = true
-- MessageToBlue( "Mission Command: Message to all airborne units! The " .. PatriotActivation[CurrentLandingZoneID][1] .. " is armed. Our air defenses are now stronger.", 60, "BLUE/PatriotDefense" )
-- MessageToRed( "Mission Command: Our satellite systems are detecting additional NATO air defenses. To all airborne units: Take care!!!", 60, "RED/PatriotDefense" )
-- Mission:GetTask( 2 ):AddGoalCompletion( "Patriots activated", PatriotActivation[CurrentLandingZoneID][1], 1 ) -- Register Patriot activation as part of mission goal.
-- end
-- end
-- end
-- end
-- end
-- end
--
-- local Mission = MISSIONSCHEDULER.AddMission( 'NATO Transport Troops', 'Operational', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.', 'NATO' )
-- Mission:AddGoalFunction( DeployPatriotTroopsGoal )
function MISSION:AddGoalFunction( GoalFunction )
trace.f(self.ClassName)
self.GoalFunction = GoalFunction
trace.e()
end
--- Show the briefing of the MISSION to the CLIENT.
-- @tparam CLIENT Client to show briefing to.
-- @treturn CLIENT
function MISSION:ShowBriefing( Client )
trace.f(self.ClassName, { Client } )
if not Client.ClientBriefingShown then
Client.ClientBriefingShown = true
Client:Message( '(Press the keys [LEFT ALT]+[B] to view the briefing pages. Browse through the graphical briefing.)\n' ..
self.MissionBriefing, 40, self.Name .. '/MissionBriefing', "Mission Command: Mission Briefing" )
if Client.ClientBriefing then
Client:Message( Client.ClientBriefing, 40, self.Name .. '/ClientBriefing', "Mission Command: Mission Briefing" )
end
end
trace.r( "", "", { Client } )
return Client
end
--- Register a new @{CLIENT} to participate within the mission.
-- @tparam CLIENT Client is the @{CLIENT} object. The object must have been instantiated with @{CLIENT:New}.
-- @treturn CLIENT
-- @usage
-- Add a number of Client objects to the Mission.
-- Mission:AddClient( CLIENT:New( 'US UH-1H*HOT-Deploy Troops 1', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() )
-- Mission:AddClient( CLIENT:New( 'US UH-1H*RAMP-Deploy Troops 3', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() )
-- Mission:AddClient( CLIENT:New( 'US UH-1H*HOT-Deploy Troops 2', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() )
-- Mission:AddClient( CLIENT:New( 'US UH-1H*RAMP-Deploy Troops 4', 'Transport 3 groups of air defense engineers from our barracks "Gold" and "Titan" to each patriot battery control center to activate our air defenses.' ):Transport() )
function MISSION:AddClient( Client )
trace.f(self.ClassName, { Client } )
local Valid = true
if Valid then
self._Clients[Client.ClientName] = Client
end
trace.r( self.ClassName, "" )
return Client
end
--- Find a @{CLIENT} object within the @{MISSION} by its ClientName.
-- @tparam CLIENT ClientName is a string defining the Client Group as defined within the ME.
-- @treturn CLIENT
-- @usage
-- -- Seach for Client "Bomber" within the Mission.
-- local BomberClient = Mission:FindClient( "Bomber" )
function MISSION:FindClient( ClientName )
trace.f(self.ClassName)
trace.r( "", "", { self._Clients[ClientName] } )
return self._Clients[ClientName]
end
--- Register a @{TASK} to be completed within the @{MISSION}. Note that there can be multiple @{TASK}s registered to be completed. Each TASK can be set a certain Goal. The MISSION will not be completed until all Goals are reached.
-- @tparam TASK Task is the @{TASK} object. The object must have been instantiated with @{TASK:New} or any of its inherited @{TASK}s.
-- @tparam number TaskNumber is the sequence number of the TASK within the MISSION. This number does have to be chronological.
-- @treturn TASK
-- @usage
-- -- Define a few tasks for the Mission.
-- PickupZones = { "NATO Gold Pickup Zone", "NATO Titan Pickup Zone" }
-- PickupSignalUnits = { "NATO Gold Coordination Center", "NATO Titan Coordination Center" }
--
-- -- Assign the Pickup Task
-- local PickupTask = PICKUPTASK:New( PickupZones, CARGO_TYPE.ENGINEERS, CLIENT.ONBOARDSIDE.LEFT )
-- PickupTask:AddSmokeBlue( PickupSignalUnits )
-- PickupTask:SetGoalTotal( 3 )
-- Mission:AddTask( PickupTask, 1 )
--
-- -- Assign the Deploy Task
-- local PatriotActivationZones = { "US Patriot Battery 1 Activation", "US Patriot Battery 2 Activation", "US Patriot Battery 3 Activation" }
-- local PatriotActivationZonesSmokeUnits = { "US SAM Patriot - Battery 1 Control", "US SAM Patriot - Battery 2 Control", "US SAM Patriot - Battery 3 Control" }
-- local DeployTask = DEPLOYTASK:New( PatriotActivationZones, CARGO_TYPE.ENGINEERS )
-- --DeployTask:SetCargoTargetZoneName( 'US Troops Attack ' .. math.random(2) )
-- DeployTask:AddSmokeBlue( PatriotActivationZonesSmokeUnits )
-- DeployTask:SetGoalTotal( 3 )
-- DeployTask:SetGoalTotal( 3, "Patriots activated" )
-- Mission:AddTask( DeployTask, 2 )
function MISSION:AddTask( Task, TaskNumber )
trace.f(self.ClassName)
self._Tasks[TaskNumber] = Task
self._Tasks[TaskNumber]:EnableEvents()
self._Tasks[TaskNumber].ID = TaskNumber
trace.r( self.ClassName, "" )
return Task
end
--- Get the TASK idenified by the TaskNumber from the Mission. This function is useful in GoalFunctions.
-- @tparam number TaskNumber is the number of the @{TASK} within the @{MISSION}.
-- @treturn TASK
-- @usage
-- -- Get Task 2 from the Mission.
-- Task2 = Mission:GetTask( 2 )
function MISSION:GetTask( TaskNumber )
trace.f(self.ClassName)
local Valid = true
local Task = nil
if type(TaskNumber) ~= "number" then
Valid = false
end
if Valid then
Task = self._Tasks[TaskNumber]
end
return Task
end
--- Get all the TASKs from the Mission. This function is useful in GoalFunctions.
-- @treturn {TASK,...} Structure of TASKS with the @{TASK} number as the key.
-- @usage
-- -- Get Tasks from the Mission.
-- Tasks = Mission:GetTasks()
-- env.info( "Task 2 Completion = " .. Tasks[2]:GetGoalPercentage() .. "%" )
function MISSION:GetTasks()
trace.f(self.ClassName)
return self._Tasks
end
--- Add Cargo to the mission... Cargo functionality needs to be reworked a bit, so this is still under construction. I need to make a CARGO Class...
SpawnCargo = {}
function MISSION:AddCargo( CargoName, CargoType, CargoWeight, CargoGroupControlCenter, CargoGroupTemplate, CargoZone )
trace.f(self.ClassName, { CargoName, CargoType, CargoWeight, CargoGroupControlCenter, CargoGroupTemplate, CargoZone } )
local Cargo = {}
Cargo.CargoName = CargoName
if CargoType.TRANSPORT == CARGO_TRANSPORT.UNIT then
if not SpawnCargo[CargoGroupTemplate] then
SpawnCargo[CargoGroupTemplate] = SPAWN:New( CargoGroupTemplate )
end
if CargoGroupControlCenter == nil then
--- @todo check this
Cargo.CargoGroupName = SpawnCargo[CargoGroupTemplate]:InZone( CargoZone ).name
else
--- @todo check this
env.info( "SpawnFromCarrier")
Cargo.CargoGroupName = SpawnCargo[CargoGroupTemplate]:FromCarrier( Group.getByName( CargoGroupControlCenter ), CargoZone, nil, true ).name
--trigger.action.activateGroup( Group.getByName( Cargo.CargoGroupName ) )
--trigger.action.setGroupAIOff( Cargo.CargoGroupName )
trace.i( self.ClassName, Cargo.CargoGroupName )
end
else
Cargo.CargoGroupName = CargoGroupControlCenter
end
Cargo.CargoType = CargoType
Cargo.CargoWeight = CargoWeight
Cargo.CargoGroupControlCenter = CargoGroupControlCenter
Cargo.CargoGroupTemplate = CargoGroupTemplate
Cargo.CargoZone = CargoZone
Cargo.Status = CARGOSTATUS.NONE
self._Cargos[CargoName] = Cargo
trace.r( self.ClassName, "AddCargo", { Cargo.CargoGroupName } )
return Cargo.CargoGroupName
end
--[[
_TransportExecuteStage: Defines the different stages of Transport unload/load execution. This table is internal and is used to control the validity of Transport load/unload timing.
- _TransportExecuteStage.EXECUTING
- _TransportExecuteStage.SUCCESS
- _TransportExecuteStage.FAILED
--]]
_TransportExecuteStage = {
NONE = 0,
EXECUTING = 1,
SUCCESS = 2,
FAILED = 3
}
--- The MISSIONSCHEDULER is an OBJECT and is the main scheduler of ALL active MISSIONs registered within this scheduler. It's workings are considered internal and is automatically created when the Mission.lua file is included.
-- @type MISSIONSCHEDULER
MISSIONSCHEDULER = {
Missions = {},
MissionCount = 0,
TimeIntervalCount = 0,
TimeIntervalShow = 150,
TimeSeconds = 14400,
TimeShow = 5
}
--- This is the main MISSIONSCHEDULER Scheduler function. It is considered internal and is automatically created when the Mission.lua file is included.
function MISSIONSCHEDULER.Scheduler()
trace.scheduled("MISSIONSCHEDULER","Scheduler")
-- loop through the missions in the TransportTasks
for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do
if not Mission:IsCompleted() then
-- This flag will monitor if for this mission, there are clients alive. If this flag is still false at the end of the loop, the mission status will be set to Pending (if not Failed or Completed).
local ClientsAlive = false
for ClientID, Client in pairs( Mission._Clients ) do
if Client:ClientGroup() and Client:ClientGroup():getUnits() and Client:ClientGroup():getUnits()[1] and Client:ClientGroup():getUnits()[1]:getLife() > 0.0 then
-- There is at least one Client that is alive... So the Mission status is set to Ongoing.
ClientsAlive = true
-- If this Client was not registered as Alive before:
-- 1. We register the Client as Alive.
-- 2. We initialize the Client Tasks and make a link to the original Mission Task.
-- 3. We initialize the Cargos.
-- 4. We flag the Mission as Ongoing.
if not Client.ClientAlive then
Client.ClientAlive = true
Client.ClientBriefingShown = false
for TaskNumber, Task in pairs( Mission._Tasks ) do
-- Note that this a deepCopy. Each client must have their own Tasks with own Stages!!!
Client._Tasks[TaskNumber] = routines.utils.deepCopy( Mission._Tasks[TaskNumber] )
-- Each MissionTask must point to the original Mission.
Client._Tasks[TaskNumber].MissionTask = Mission._Tasks[TaskNumber]
end
Client._Cargos = {}
Mission:Ongoing()
end
-- For each Client, check for each Task the state and evolve the mission.
-- This flag will indicate if the Task of the Client is Complete.
TaskComplete = false
for TaskNumber, Task in pairs( Client._Tasks ) do
if not Task.Stage then
Task:SetStage( 1 )
end
local TransportTime = timer.getTime()
if not Task:IsDone() then
if Task:Goal() then
Task:ShowGoalProgress( Mission, Client )
end
--env.info( 'Scheduler: Mission = ' .. Mission.Name .. ' / Client = ' .. Client.ClientName .. ' / Task = ' .. Task.Name .. ' / Stage = ' .. Task.ActiveStage .. ' - ' .. Task.Stage.Name .. ' - ' .. Task.Stage.StageType )
-- Action
if Task:StageExecute() then
Task.Stage:Execute( Mission, Client, Task )
end
-- Wait until execution is finished
if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then
Task.Stage:Executing( Mission, Client, Task )
end
-- Validate completion or reverse to earlier stage
if Task.Time + Task.Stage.WaitTime <= TransportTime then
Task:SetStage( Task.Stage:Validate( Mission, Client, Task ) )
end
if Task:IsDone() then
--env.info( 'Scheduler: Mission '.. Mission.Name .. ' Task ' .. Task.Name .. ' Stage ' .. Task.Stage.Name .. ' done. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) )
TaskComplete = true -- when a task is not yet completed, a mission cannot be completed
else
-- break only if this task is not yet done, so that future task are not yet activated.
TaskComplete = false -- when a task is not yet completed, a mission cannot be completed
--env.info( 'Scheduler: Mission "'.. Mission.Name .. '" Task "' .. Task.Name .. '" Stage "' .. Task.Stage.Name .. '" break. TaskComplete = ' .. string.format ( "%s", TaskComplete and "true" or "false" ) )
break
end
if TaskComplete then
if Mission.GoalFunction ~= nil then
Mission.GoalFunction( Mission, Client )
end
_Database:_AddMissionTaskScore( Client:ClientGroup():getUnit(1), Mission.Name, 25 )
-- if not Mission:IsCompleted() then
-- end
end
end
end
local MissionComplete = true
for TaskNumber, Task in pairs( Mission._Tasks ) do
if Task:Goal() then
-- Task:ShowGoalProgress( Mission, Client )
if Task:IsGoalReached() then
else
MissionComplete = false
end
else
MissionComplete = false -- If there is no goal, the mission should never be ended. The goal status will be set somewhere else.
end
end
if MissionComplete then
Mission:Completed()
_Database:_AddMissionScore( Mission.Name, 100 )
else
if TaskComplete then
-- Reset for new tasking of active client
Client.ClientAlive = false -- Reset the client tasks.
end
end
else
if Client.ClientAlive then
env.info( 'Scheduler: Client "' .. Client.ClientName .. '" is inactive.' )
Client.ClientAlive = false
-- This is tricky. If we sanitize Client._Tasks before sanitizing Client._Tasks[TaskNumber].MissionTask, then the original MissionTask will be sanitized, and will be lost within the garbage collector.
-- So first sanitize Client._Tasks[TaskNumber].MissionTask, after that, sanitize only the whole _Tasks structure...
--Client._Tasks[TaskNumber].MissionTask = nil
--Client._Tasks = nil
-- Sanitize the Client._Cargos. Any cargo within the Client will be lost when the client crashes. This is an important statement.
Client._Cargos = nil
end
end
end
-- If all Clients of this Mission are not activated, then the Mission status needs to be put back into Pending status.
-- But only if the Mission was Ongoing. In case the Mission is Completed or Failed, the Mission status may not be changed. In these cases, this will be the last run of this Mission in the Scheduler.
if ClientsAlive == false then
if Mission:IsOngoing() then
-- Mission status back to pending...
Mission:Pending()
end
end
end
Mission:StatusToClients()
if Mission:ReportTrigger() then
Mission:ReportToAll()
end
end
trace.e()
end
--- Start the MISSIONSCHEDULER.
function MISSIONSCHEDULER.Start()
trace.f("MISSIONSCHEDULER")
if MISSIONSCHEDULER ~= nil then
MISSIONSCHEDULER.SchedulerId = routines.scheduleFunction( MISSIONSCHEDULER.Scheduler, { }, 0, 2 )
end
trace.e()
end
--- Stop the MISSIONSCHEDULER.
function MISSIONSCHEDULER.Stop()
trace.f("MISSIONSCHEDULER")
if MISSIONSCHEDULER.SchedulerId then
routines.removeFunction(MISSIONSCHEDULER.SchedulerId)
MISSIONSCHEDULER.SchedulerId = nil
end
trace.e()
end
--- This is the main MISSION declaration method. Each Mission is like the master or a Mission orchestration between, Clients, Tasks, Stages etc.
-- @param Mission is the MISSION object instantiated by @{MISSION:New}.
-- @return MISSION
-- @usage
-- -- Declare a mission.
-- Mission = MISSION:New( 'Russia Transport Troops SA-6',
-- 'Operational',
-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.',
-- 'Russia' )
-- MISSIONSCHEDULER:AddMission( Mission )
function MISSIONSCHEDULER.AddMission( Mission )
trace.f("MISSIONSCHEDULER")
MISSIONSCHEDULER.Missions[Mission.Name] = Mission
MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount + 1
-- Add an overall AI Client for the AI tasks... This AI Client will facilitate the Events in the background for each Task.
--MissionAdd:AddClient( CLIENT:New( 'AI' ) )
trace.r( "MISSIONSCHEDULER", "" )
return Mission
end
--- Remove a MISSION from the MISSIONSCHEDULER.
-- @param MissionName is the name of the MISSION given at declaration using @{AddMission}.
-- @usage
-- -- Declare a mission.
-- Mission = MISSION:New( 'Russia Transport Troops SA-6',
-- 'Operational',
-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.',
-- 'Russia' )
-- MISSIONSCHEDULER:AddMission( Mission )
--
-- -- Now remove the Mission.
-- MISSIONSCHEDULER:RemoveMission( 'Russia Transport Troops SA-6' )
function MISSIONSCHEDULER.RemoveMission( MissionName )
trace.f("MISSIONSCHEDULER")
MISSIONSCHEDULER.Missions[MissionName] = nil
MISSIONSCHEDULER.MissionCount = MISSIONSCHEDULER.MissionCount - 1
trace.e()
end
--- Find a MISSION within the MISSIONSCHEDULER.
-- @param MissionName is the name of the MISSION given at declaration using @{AddMission}.
-- @return MISSION
-- @usage
-- -- Declare a mission.
-- Mission = MISSION:New( 'Russia Transport Troops SA-6',
-- 'Operational',
-- 'Transport troops from the control center to one of the SA-6 SAM sites to activate their operation.',
-- 'Russia' )
-- MISSIONSCHEDULER:AddMission( Mission )
--
-- -- Now find the Mission.
-- MissionFind = MISSIONSCHEDULER:FindMission( 'Russia Transport Troops SA-6' )
function MISSIONSCHEDULER.FindMission( MissionName )
trace.f("MISSIONSCHEDULER")
trace.r( "MISSIONSCHEDULER", "" )
return MISSIONSCHEDULER.Missions[MissionName]
end
-- Internal function used by the MISSIONSCHEDULER menu.
function MISSIONSCHEDULER.ReportMissionsShow( )
trace.menu("MISSIONSCHEDULER","ReportMissionsShow")
for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do
Mission.MissionReportShow = true
Mission.MissionReportFlash = false
end
trace.e()
end
-- Internal function used by the MISSIONSCHEDULER menu.
function MISSIONSCHEDULER.ReportMissionsFlash( TimeInterval )
trace.menu("MISSIONSCHEDULER","ReportMissionsFlash")
local Count = 0
for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do
Mission.MissionReportShow = false
Mission.MissionReportFlash = true
Mission.MissionReportTrigger = timer.getTime() + Count * TimeInterval
Mission.MissionTimeInterval = MISSIONSCHEDULER.MissionCount * TimeInterval
env.info( "TimeInterval = " .. Mission.MissionTimeInterval )
Count = Count + 1
end
trace.e()
end
-- Internal function used by the MISSIONSCHEDULER menu.
function MISSIONSCHEDULER.ReportMissionsHide( Prm )
trace.menu("MISSIONSCHEDULER","ReportMissionsHide")
for MissionName, Mission in pairs( MISSIONSCHEDULER.Missions ) do
Mission.MissionReportShow = false
Mission.MissionReportFlash = false
end
trace.e()
end
--- Enables a MENU option in the communications menu under F10 to control the status of the active missions.
-- This function should be called only once when starting the MISSIONSCHEDULER.
function MISSIONSCHEDULER.ReportMenu()
trace.f("MISSIONSCHEDULER")
local ReportMenu = SUBMENU:New( 'Status' )
local ReportMenuShow = COMMANDMENU:New( 'Show Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsShow, 0 )
local ReportMenuFlash = COMMANDMENU:New('Flash Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsFlash, 120 )
local ReportMenuHide = COMMANDMENU:New( 'Hide Report Missions', ReportMenu, MISSIONSCHEDULER.ReportMissionsHide, 0 )
trace.e()
end
--- Show the remaining mission time.
function MISSIONSCHEDULER:TimeShow()
trace.f("MISSIONSCHEDULER")
self.TimeIntervalCount = self.TimeIntervalCount + 1
if self.TimeIntervalCount >= self.TimeTriggerShow then
local TimeMsg = string.format("%00d", ( self.TimeSeconds / 60 ) - ( timer.getTime() / 60 )) .. ' minutes left until mission reload.'
MESSAGE:New( TimeMsg, "Mission time", self.TimeShow, '/TimeMsg' ):ToAll()
self.TimeIntervalCount = 0
end
trace.e()
end
function MISSIONSCHEDULER:Time( TimeSeconds, TimeIntervalShow, TimeShow )
trace.f("MISSIONSCHEDULER")
self.TimeIntervalCount = 0
self.TimeSeconds = TimeSeconds
self.TimeIntervalShow = TimeIntervalShow
self.TimeShow = TimeShow
trace.e()
end

37
MissionScripting.lua Normal file
View File

@ -0,0 +1,37 @@
--Initialization script for the Mission lua Environment (SSE)
dofile('Scripts/ScriptingSystem.lua')
Include = {}
Include.LoadPath = 'Scripts/MOOSE'
Include.Files = {}
Include.File = function( IncludeFile )
if not Include.Files[ IncludeFile ] then
Include.Files[IncludeFile] = IncludeFile
dofile( Include.LoadPath .. "/" .. IncludeFile .. ".lua" )
env.info( "Include:" .. IncludeFile .. " loaded." )
end
end
Include.File( "Database" )
Include.File( "StatHandler" )
--Sanitize Mission Scripting environment
--This makes unavailable some unsecure functions.
--Mission downloaded from server to client may contain potentialy harmful lua code that may use these functions.
--You can remove the code below and make availble these functions at your own risk.
local function sanitizeModule(name)
_G[name] = nil
package.loaded[name] = nil
end
do
--sanitizeModule('os')
--sanitizeModule('io')
sanitizeModule('lfs')
require = nil
loadlib = nil
end

124
Movement.lua Normal file
View File

@ -0,0 +1,124 @@
--- 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.
-- Performance: If in a DCSRTE there are a lot of moving GROUND units, then in a multi player mission, this WILL create lag if
-- the main DCS execution core of your CPU is fully utilized. So, this class will limit the amount of simultaneous moving GROUND units
-- on defined intervals (currently every minute).
-- @classmod MOVEMENT
Include.File( "Routines" )
MOVEMENT = {
ClassName = "MOVEMENT",
}
--- Creates the main object which is handling the GROUND forces movement.
-- @tparam table{string,...}|string MovePrefixes is a table of the Prefixes (names) of the GROUND Groups that need to be controlled by the MOVEMENT Object.
-- @tparam number MoveMaximum is a number that defines the maximum amount of GROUND Units to be moving during one minute.
-- @treturn MOVEMENT
-- @usage
-- -- Limit the amount of simultaneous moving units on the ground to prevent lag.
-- Movement_US_Platoons = MOVEMENT:New( { 'US Tank Platoon Left', 'US Tank Platoon Middle', 'US Tank Platoon Right', 'US CH-47D Troops' }, 15 )
function MOVEMENT:New( MovePrefixes, MoveMaximum )
trace.f(self.ClassName, { MovePrefixes, MoveMaximum } )
-- Inherits from BASE
local Child = BASE:Inherit( self, BASE:New() )
if type( MovePrefixes ) == 'table' then
Child.MovePrefixes = MovePrefixes
else
Child.MovePrefixes = { MovePrefixes }
end
Child.MoveCount = 0 -- The internal counter of the amount of Moveing the has happened since MoveStart.
Child.MoveMaximum = MoveMaximum -- Contains the Maximum amount of units that are allowed to move...
Child.AliveUnits = 0 -- Contains the counter how many units are currently alive
Child.MoveGroups = {} -- Reflects if the Moveing for this MovePrefixes is going to be scheduled or not.
Child.AddEvent( Child, world.event.S_EVENT_BIRTH, Child.OnBirth )
Child.AddEvent( Child, world.event.S_EVENT_DEAD, Child.OnDeadOrCrash )
Child.AddEvent( Child, world.event.S_EVENT_CRASH, Child.OnDeadOrCrash )
Child.EnableEvents( Child )
Child.ScheduleStart( Child )
return Child
end
--- Call this function to start the MOVEMENT scheduling.
function MOVEMENT:ScheduleStart()
trace.f( self.ClassName )
self.MoveFunction = routines.scheduleFunction( self._Scheduler, { self }, timer.getTime() + 1, 60 )
end
--- Call this function to stop the MOVEMENT scheduling.
-- @todo need to implement it ... Forgot.
function MOVEMENT:ScheduleStop()
trace.f( self.ClassName )
end
--- Captures the birth events when new Units were spawned.
-- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration.
function MOVEMENT:OnBirth( event )
trace.f( self.ClassName, { event } )
if timer.getTime0() < timer.getAbsTime() then -- dont need to add units spawned in at the start of the mission if mist is loaded in init line
if event.initiator and event.initiator:getName() then
trace.l(self.ClassName, "OnBirth", "Birth object : " .. event.initiator:getName() )
local EventGroupName = Unit.getGroup(event.initiator):getName()
for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do
if string.find( EventGroupName, MovePrefix, 1, true ) then
self.AliveUnits = self.AliveUnits + 1
self.MoveGroups[EventGroupName] = EventGroupName
trace.l(self.ClassName, "OnBirth", self.AliveUnits )
end
end
end
end
end
--- Captures the Dead or Crash events when Units crash or are destroyed.
-- @todo This method should become obsolete. The new @{DATABASE} class will handle the collection administration.
function MOVEMENT:OnDeadOrCrash( event )
trace.f( self.ClassName, { event } )
if event.initiator and event.initiator:getName() then
trace.l( self.ClassName, "OnDeadOrCrash", "Dead object : " .. event.initiator:getName() )
local EventGroupName = Unit.getGroup(event.initiator):getName()
for MovePrefixID, MovePrefix in pairs( self.MovePrefixes ) do
if string.find( EventGroupName, MovePrefix, 1, true ) then
self.AliveUnits = self.AliveUnits - 1
self.MoveGroups[EventGroupName] = nil
trace.l( self.ClassName, "OnDeadOrCrash", self.AliveUnits )
end
end
end
end
--- This function is called automatically by the MOVEMENT scheduler. A new function is scheduled when MoveScheduled is true.
function MOVEMENT:_Scheduler()
trace.l( self.ClassName, '_Scheduler', { self.MovePrefixes, self.MoveMaximum, self.AliveUnits, self.MoveGroups } )
if self.AliveUnits > 0 then
local MoveProbability = ( self.MoveMaximum * 100 ) / self.AliveUnits
trace.l( self.ClassName, '_Scheduler', 'Move Probability = ' .. MoveProbability )
for MoveGroupID, MoveGroupName in pairs( self.MoveGroups ) do
local MoveGroup = Group.getByName( MoveGroupName )
if MoveGroup then
local MoveOrStop = math.random( 1, 100 )
trace.l( self.ClassName, '_Scheduler', 'MoveOrStop = ' .. MoveOrStop )
if MoveOrStop <= MoveProbability then
trace.l( self.ClassName, '_Scheduler', 'Group continues moving = ' .. MoveGroupName )
trigger.action.groupContinueMoving( MoveGroup )
else
trace.l( self.ClassName, '_Scheduler', 'Group stops moving = ' .. MoveGroupName )
trigger.action.groupStopMoving( MoveGroup )
end
end
end
end
end

28
NoTask.lua Normal file
View File

@ -0,0 +1,28 @@
--- A NOTASK is a dummy activity... But it will show a Mission Briefing...
-- @classmod NOTASK
Include.File("Task")
--- Modeling a sequence of STAGEs to do nothing, but wait for the mission goal.
NOTASK = {
ClassName = "NOTASK",
}
--- Creates a new NOTASK.
function NOTASK:New()
trace.f(self.ClassName)
-- Child holds the inherited instance of the PICKUPTASK Class to the BASE class.
local Child = BASE:Inherit( self, TASK:New() )
local Valid = true
if Valid then
Child.Name = 'Nothing'
Child.TaskBriefing = "Task: Execute your mission."
Child.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEDONE:New() }
Child.SetStage( Child, 1 )
end
return Child
end

204
PickupTask.lua Normal file
View File

@ -0,0 +1,204 @@
--- A PICKUPTASK orchestrates the loading of CARGO at a specific landing zone.
-- @classmod PICKUPTASK
-- @parent TASK
Include.File("Task")
PICKUPTASK = {
ClassName = "PICKUPTASK",
TEXT = { "Pick-Up", "picked-up", "loaded" },
GoalVerb = "Pick-Up"
}
--- Creates a new PICKUPTASK.
-- @tparam table{string,...}|string LandingZones Table of Zone names where Cargo is to be loaded.
-- @tparam CARGO_TYPE CargoType Type of the Cargo. The type must be of the following Enumeration:..
-- @tparam number OnBoardSide Reflects from which side the cargo Group will be on-boarded on the Carrier.
function PICKUPTASK:New( LandingZones, CargoType, OnBoardSide )
trace.f(self.ClassName)
-- Child holds the inherited instance of the PICKUPTASK Class to the BASE class.
local Child = BASE:Inherit( self, TASK:New() )
local Valid = true
Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid )
Valid = routines.ValidateEnumeration( CargoType, "CargoType", CARGO_TYPE, Valid )
Valid = routines.ValidateEnumeration( CargoType, "CargoType", CARGO_TYPE, Valid )
--Valid = routines.ValidateEnumeration( OnBoardSide, "OnBoardSide", CLIENT.ONBOARDSIDE, Valid )
if Valid then
Child.Name = 'Pickup Cargo'
Child.TaskBriefing = "Task: Fly to the indicated landing zones and pickup " .. CargoType.TEXT .. ". Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the pickup zone."
if type( LandingZones ) == "table" then
Child.LandingZones = LandingZones
else
Child.LandingZones = { LandingZones }
end
Child.CargoType = CargoType
Child.GoalVerb = CargoType.TEXT .. " " .. Child.GoalVerb
Child.OnBoardSide = OnBoardSide
Child.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGELANDING:New(), STAGELANDED:New(), STAGELOAD:New(), STAGEDONE:New() }
Child.SetStage( Child, 1 )
end
return Child
end
function PICKUPTASK:AddCargoMenus( Client, Cargos, TransportRadius )
trace.f(self.ClassName, { Client, Cargos, TransportRadius } )
for CargoID, CargoData in pairs( Cargos ) do
if CargoData.Status ~= CARGOSTATUS.LOADED and CargoData.Status ~= CARGOSTATUS.LOADING then
if Group.getByName( CargoData.CargoGroupName ) then
if Group.getByName( CargoData.CargoGroupName ):getSize() >= 1 then
if Client._Menus[CargoData.CargoType] == nil then
Client._Menus[CargoData.CargoType] = {}
end
if not Client._Menus[CargoData.CargoType].PickupMenu then
Client._Menus[CargoData.CargoType].PickupMenu = missionCommands.addSubMenuForGroup(
Client:ClientGroup():getID(),
self.TEXT[1],
nil
)
trace.i( self.ClassName, 'Added PickupMenu' .. self.TEXT[1] )
end
if Client._Menus[CargoData.CargoType].PickupSubMenus == nil then
Client._Menus[CargoData.CargoType].PickupSubMenus = {}
end
local MenuAdd = false
if CargoData.CargoType.TRANSPORT == CARGO_TRANSPORT.UNIT then
CargoGroup = Group.getByName( CargoData.CargoGroupName )
if routines.IsPartOfGroupInRadius( CargoGroup, Client:ClientGroup(), TransportRadius ) then
MenuAdd = true
end
else
MenuAdd = true
end
if MenuAdd then
Client._Menus[CargoData.CargoType].PickupSubMenus[ #Client._Menus[CargoData.CargoType].PickupSubMenus + 1 ] = missionCommands.addCommandForGroup(
Client:ClientGroup():getID(),
CargoData.CargoName .. " ( " .. CargoData.CargoWeight .. "kg )",
Client._Menus[CargoData.CargoType].PickupMenu,
self.MenuAction,
{ ReferenceTask = self, CargoName = CargoData.CargoName }
)
trace.i( self.ClassName, 'Added PickupSubMenu' .. CargoData.CargoType.TEXT .. ":" .. CargoData.CargoName .. " ( " .. CargoData.CargoWeight .. "kg )" )
end
end
end
end
end
end
function PICKUPTASK:RemoveCargoMenus( Client )
trace.f(self.ClassName, { Client } )
for MenuID, MenuData in pairs( Client._Menus ) do
for SubMenuID, SubMenuData in pairs( MenuData.PickupSubMenus ) do
missionCommands.removeItemForGroup( Client:ClientGroup():getID(), SubMenuData )
trace.i( self.ClassName, "Removed PickupSubMenu " )
SubMenuData = nil
end
if MenuData.PickupMenu then
missionCommands.removeItemForGroup( Client:ClientGroup():getID(), MenuData.PickupMenu )
trace.i( self.ClassName, "Removed PickupMenu " )
MenuData.PickupMenu = nil
end
end
end
function PICKUPTASK:HasFailed( ClientDead )
trace.f(self.ClassName)
local TaskHasFailed = self.TaskFailed
return TaskHasFailed
end
function PICKUPTASK:OnBoardCargo( ClientGroup, Cargos )
trace.f(self.ClassName, { ClientGroup, Cargos } )
local Valid = true
Valid = routines.ValidateGroup( ClientGroup, "ClientGroup", Valid )
if Valid then
local CarrierPos = ClientGroup:getUnits()[1]:getPoint()
local CarrierPosMove = ClientGroup:getUnits()[1]:getPoint()
local CarrierPosOnBoard = ClientGroup:getUnits()[1]:getPoint()
local CargoGroup = Group.getByName( Cargos[ self.CargoName ].CargoGroupName )
trigger.action.activateGroup( CargoGroup )
trigger.action.setGroupAIOn( CargoGroup )
local CargoUnits = CargoGroup:getUnits()
local CargoPos = CargoUnits[1]:getPoint()
local Points = {}
trace.i( self.ClassName, 'CargoPos x = ' .. CargoPos.x .. " z = " .. CargoPos.z )
trace.i( self.ClassName, 'CarrierPosMove x = ' .. CarrierPosMove.x .. " z = " .. CarrierPosMove.z )
Points[#Points+1] = routines.ground.buildWP( CargoPos, "off road", 6 )
trace.i( self.ClassName, 'Points[1] x = ' .. Points[1].x .. " y = " .. Points[1].y )
if self.OnBoardSide == nil then
self.OnBoardSide = CLIENT.ONBOARDSIDE.NONE
end
if self.OnBoardSide == CLIENT.ONBOARDSIDE.LEFT then
trace.i( self.ClassName, "TransportCargoOnBoard: Onboarding LEFT" )
CarrierPosMove.z = CarrierPosMove.z - 50
CarrierPosOnBoard.z = CarrierPosOnBoard.z - 5
Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "diamond", 6 )
Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "diamond", 6 )
elseif self.OnBoardSide == CLIENT.ONBOARDSIDE.RIGHT then
trace.i( self.ClassName, "TransportCargoOnBoard: Onboarding RIGHT" )
CarrierPosMove.z = CarrierPosMove.z + 50
CarrierPosOnBoard.z = CarrierPosOnBoard.z + 5
Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "diamond", 6 )
Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "diamond", 6 )
elseif self.OnBoardSide == CLIENT.ONBOARDSIDE.BACK then
trace.i( self.ClassName, "TransportCargoOnBoard: Onboarding BACK" )
CarrierPosMove.x = CarrierPosMove.x - 50
CarrierPosOnBoard.x = CarrierPosOnBoard.x - 5
Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "diamond", 6 )
Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "diamond", 6 )
elseif self.OnBoardSide == CLIENT.ONBOARDSIDE.FRONT then
trace.i( self.ClassName, "TransportCargoOnBoard: Onboarding FRONT" )
CarrierPosMove.x = CarrierPosMove.x + 50
CarrierPosOnBoard.x = CarrierPosOnBoard.x + 5
Points[#Points+1] = routines.ground.buildWP( CarrierPosMove, "diamond", 6 )
Points[#Points+1] = routines.ground.buildWP( CarrierPosOnBoard, "diamond", 6 )
elseif self.OnBoardSide == CLIENT.ONBOARDSIDE.NONE then
trace.i( self.ClassName, "TransportCargoOnBoard: Onboarding CENTRAL" )
Points[#Points+1] = routines.ground.buildWP( CarrierPos, "diamond", 6 )
end
trace.i( self.ClassName, "TransportCargoOnBoard: Routing " .. Cargos[ self.CargoName ].CargoGroupName )
trace.i( self.ClassName, 'Points[2] x = ' .. Points[2].x .. " y = " .. Points[2].y )
trace.i( self.ClassName, 'Points[3] x = ' .. Points[3].x .. " y = " .. Points[3].y )
routines.scheduleFunction(routines.goRoute, {Cargos[ self.CargoName ].CargoGroupName, Points}, timer.getTime() + 8)
--routines.goRoute( Cargos[ self.CargoName ].CargoGroupName, Points )
end
return Valid
end

42
RouteTask.lua Normal file
View File

@ -0,0 +1,42 @@
--- A ROUTETASK orchestrates the travel to a specific zone defined within the ME.
-- @classmod ROUTETASK
--- Modeling a sequence of STAGEs to fly back to the home base specified by an Arrival Zone.
ROUTETASK = {
ClassName = "ROUTETASK",
GoalVerb = "Route",
}
--- Creates a new ROUTETASK.
-- @tparam table{sring,...}|string LandingZones Table of Zone Names where the target is located.
-- @tparam string TaskBriefing (optional) Defines a text describing the briefing of the task.
-- @treturn ROUTETASK
function ROUTETASK:New( LandingZones, TaskBriefing )
trace.f(self.ClassName, { LandingZones, TaskBriefing } )
-- Child holds the inherited instance of the PICKUPTASK Class to the BASE class.
local Child = BASE:Inherit( self, TASK:New() )
local Valid = true
Valid = routines.ValidateZone( LandingZones, "LandingZones", Valid )
if Valid then
Child.Name = 'Route To Zone'
if TaskBriefing then
Child.TaskBriefing = TaskBriefing .. " Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective."
else
Child.TaskBriefing = "Task: Fly to specified zone(s). Your co-pilot will provide you with the directions (required flight angle in degrees) and the distance (in km) to the target objective."
end
if type( LandingZones ) == "table" then
Child.LandingZones = LandingZones
else
Child.LandingZones = { LandingZones }
end
Child.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGEARRIVE:New(), STAGEDONE:New() }
Child.SetStage( Child, 1 )
end
return Child
end

2373
Routines.lua Normal file

File diff suppressed because it is too large Load Diff

139
Sead.lua Normal file
View File

@ -0,0 +1,139 @@
--- Provides defensive behaviour to a set of SAM sites within a running Mission.
-- @classmod SEAD
-- @author to be searched on the forum
-- @author (co) Flightcontrol (Modified and enriched with functionality)
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Mission" )
Include.File( "Client" )
Include.File( "Task" )
SEAD = {
ClassName = "SEAD",
TargetSkill = {
Average = { Evade = 50, DelayOff = { 10, 25 }, DelayOn = { 10, 30 } } ,
Good = { Evade = 30, DelayOff = { 8, 20 }, DelayOn = { 20, 40 } } ,
High = { Evade = 15, DelayOff = { 5, 17 }, DelayOn = { 30, 50 } } ,
Excellent = { Evade = 10, DelayOff = { 3, 10 }, DelayOn = { 30, 60 } }
},
SEADGroupPrefixes = {}
}
--- Creates the main object which is handling defensive actions for SA sites or moving SA vehicles.
-- When an anti radiation missile is fired (KH-58, KH-31P, KH-31A, KH-25MPU, HARM missiles), the SA will shut down their radars and will take evasive actions...
-- Chances are big that the missile will miss.
-- @tparam table{string,...}|string SEADGroupPrefixes which is a table of Prefixes of the SA Groups in the DCSRTE on which evasive actions need to be taken.
-- @treturn SEAD
-- @usage
-- -- CCCP SEAD Defenses
-- -- Defends the Russian SA installations from SEAD attacks.
-- SEAD_RU_SAM_Defenses = SEAD:New( { 'RU SA-6 Kub', 'RU SA-6 Defenses', 'RU MI-26 Troops', 'RU Attack Gori' } )
function SEAD:New( SEADGroupPrefixes )
trace.f(self.ClassName, SEADGroupPrefixes )
-- Arrange meta tables
local Child = BASE:Inherit( self, BASE:New() )
if type( SEADGroupPrefixes ) == 'table' then
for SEADGroupPrefixID, SEADGroupPrefix in pairs( SEADGroupPrefixes ) do
Child.SEADGroupPrefixes[SEADGroupPrefix] = SEADGroupPrefix
end
else
Child.SEADGroupNames[SEADGroupPrefixes] = SEADGroupPrefixes
end
Child.AddEvent( Child, world.event.S_EVENT_SHOT, Child.EventShot )
Child.EnableEvents( Child )
return Child
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 SEAD
function SEAD:EventShot( event )
trace.f( self.ClassName, { event } )
local _grp = Unit.getGroup(event.initiator)-- Identify the group that fired
local _groupname = _grp:getName() -- return the name of the group
local _unittable = {event.initiator:getName()} -- return the name of the units in the group
local _SEADmissile = event.weapon -- Identify the weapon fired
local _SEADmissileName = _SEADmissile:getTypeName() -- return weapon type
--trigger.action.outText( string.format("Alerte, depart missile " ..string.format(_SEADmissileName)), 20) --debug message
-- Start of the 2nd loop
trace.i( self.ClassName, "Missile Launched = " .. _SEADmissileName )
if _SEADmissileName == "KH-58" or _SEADmissileName == "KH-25MPU" or _SEADmissileName == "AGM-88" or _SEADmissileName == "KH-31A" or _SEADmissileName == "KH-31P" then -- Check if the missile is a SEAD
local _evade = math.random (1,100) -- random number for chance of evading action
local _targetMim = Weapon.getTarget(_SEADmissile) -- Identify target
local _targetMimname = Unit.getName(_targetMim)
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(_SEADmissile))
local _targetMimgroupName = _targetMimgroup:getName()
local _targetMimcont= _targetMimgroup:getController()
local _targetskill = _Database.Units[_targetMimname].Template.skill
trace.i( self.ClassName, self.SEADGroupPrefixes )
trace.i( self.ClassName, _targetMimgroupName )
local SEADGroupFound = false
for SEADGroupPrefixID, SEADGroupPrefix in pairs( self.SEADGroupPrefixes ) do
if string.find( _targetMimgroupName, SEADGroupPrefix, 1, true ) then
SEADGroupFound = true
trace.i( self.ClassName, 'Group Found' )
break
end
end
if SEADGroupFound == true then
if _targetskill == "Random" then -- when skill is random, choose a skill
local Skills = { "Average", "Good", "High", "Excellent" }
_targetskill = Skills[ math.random(1,4) ]
end
trace.i( self.ClassName, _targetskill ) -- debug message for skill check
if self.TargetSkill[_targetskill] then
if (_evade > self.TargetSkill[_targetskill].Evade) then
trace.i( self.ClassName, string.format("Evading, target skill " ..string.format(_targetskill)) ) --debug message
local _targetMim = Weapon.getTarget(_SEADmissile)
local _targetMimname = Unit.getName(_targetMim)
local _targetMimgroup = Unit.getGroup(Weapon.getTarget(_SEADmissile))
local _targetMimcont= _targetMimgroup:getController()
routines.groupRandomDistSelf(_targetMimgroup,300,'Rank',250,20) -- move randomly
local SuppressedGroups1 = {} -- unit suppressed radar off for a random time
local function SuppressionEnd1(id)
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
SuppressedGroups1[id.groupName] = nil
end
local id = {
groupName = _targetMimgroup,
ctrl = _targetMimcont
}
local delay1 = math.random(self.TargetSkill[_targetskill].DelayOff[1], self.TargetSkill[_targetskill].DelayOff[2])
if SuppressedGroups1[id.groupName] == nil then
SuppressedGroups1[id.groupName] = {
SuppressionEndTime1 = timer.getTime() + delay1,
SuppressionEndN1 = SuppressionEndCounter1 --Store instance of SuppressionEnd() scheduled function
}
Controller.setOption(_targetMimcont, AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.GREEN)
timer.scheduleFunction(SuppressionEnd1, id, SuppressedGroups1[id.groupName].SuppressionEndTime1) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar Off " ..string.format(delay1)), 20)
end
local SuppressedGroups = {}
local function SuppressionEnd(id)
id.ctrl:setOption(AI.Option.Ground.id.ALARM_STATE,AI.Option.Ground.val.ALARM_STATE.RED)
SuppressedGroups[id.groupName] = nil
end
local id = {
groupName = _targetMimgroup,
ctrl = _targetMimcont
}
local delay = math.random(self.TargetSkill[_targetskill].DelayOn[1], self.TargetSkill[_targetskill].DelayOn[2])
if SuppressedGroups[id.groupName] == nil then
SuppressedGroups[id.groupName] = {
SuppressionEndTime = timer.getTime() + delay,
SuppressionEndN = SuppressionEndCounter --Store instance of SuppressionEnd() scheduled function
}
timer.scheduleFunction(SuppressionEnd, id, SuppressedGroups[id.groupName].SuppressionEndTime) --Schedule the SuppressionEnd() function
--trigger.action.outText( string.format("Radar On " ..string.format(delay)), 20)
end
end
end
end
end
end

39
SlingLoadHookTask.lua Normal file
View File

@ -0,0 +1,39 @@
--- A SLINGLOADHOOKTASK will orchestrate the sling-load hook activity to slingload a CARGO from a specific landing zone(s).
-- @classmod SLINGLOADHOOKTASK
Include.File("Task")
SLINGLOADHOOKTASK = {
ClassName = "SLINGLOADHOOKTASK",
GoalVerb = "Hook and Sling Cargo"
}
--- Creates a new SLINGLOADHOOKTASK.
-- @tparam table{string,...}|string LandingZones Table or name of the zone(s) where Cargo is to be loaded.
-- @tparam table{string,...)|string CargoPrefixes is the name or prefix of the name of the Cargo objects defined within the DCS ME.
-- @treturn SLINGLOADHOOKTASK
function SLINGLOADHOOKTASK:New( LandingZones, CargoPrefixes )
trace.f(self.ClassName)
local self = BASE:Inherit( self, TASK:New() )
self.Name = 'Hook and Sling Cargo'
self.TaskBriefing = "Task: Hook"
if type( LandingZones ) == "table" then
self.LandingZones = LandingZones
else
self.LandingZones = { LandingZones }
end
if type( CargoPrefixes ) == "table" then
self.CargoPrefixes = CargoPrefixes
else
self.CargoPrefixes = { CargoPrefixes }
end
self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGE_SLINGLOAD_HOOK:New(), STAGEDONE:New() }
self:SetStage( 1 )
return self
end

38
SlingLoadUnHookTask.lua Normal file
View File

@ -0,0 +1,38 @@
--- A SLINGLOADUNHOOKTASK will orchestrate the sling-load unhook activity to (sling)load a CARGO and deploy it in a specific landing zone(s).
-- @classmod SLINGLOADUNHOOKTASK
Include.File("Task")
SLINGLOADUNHOOKTASK = {
ClassName = "SLINGLOADUNHOOKTASK",
GoalVerb = "Sling and UnHook Cargo"
}
--- Creates a new SLINGLOADUNHOOKTASK.
-- @tparam table{string,...}|string LandingZones Table or name of the zone(s) where Cargo is to be loaded.
-- @tparam table{string,...}|string CargoPrefixes is the name or prefix of the name of the Cargo objects defined within the DCS ME.
function SLINGLOADUNHOOKTASK:New( LandingZones, CargoPrefixes )
trace.f(self.ClassName)
local self = BASE:Inherit( self, TASK:New() )
self.Name = 'Sling and Unhook Cargo'
self.TaskBriefing = "Task: UnHook"
if type( LandingZones ) == "table" then
self.LandingZones = LandingZones
else
self.LandingZones = { LandingZones }
end
if type( CargoPrefixes ) == "table" then
self.CargoPrefixes = CargoPrefixes
else
self.CargoPrefixes = { CargoPrefixes }
end
self.Stages = { STAGEBRIEF:New(), STAGESTART:New(), STAGEROUTE:New(), STAGE_SLINGLOAD_UNHOOK:New(), STAGEDONE:New() }
self:SetStage( 1 )
return self
end

658
Spawn.lua Normal file
View File

@ -0,0 +1,658 @@
--- Dynamic spawning of Groups and Units.
-- @classmod SPAWN
-- @author Flightcontrol
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Database" )
SPAWN = {
ClassName = "SPAWN",
}
--- Public
-- @section Public
--- Creates the main object to spawn a Group defined in the DCS ME.
-- Spawned Groups and Units will follow the following naming convention within the DCS World run-time environment:
-- Groups will have the name SpawnPrefix#ggg, where ggg is a counter from 0 to 999 for each new spawned Group.
-- Units will have the name SpawnPrefix#ggg-uu, where uu is a counter from 0 to 99 for each new spawned Unit belonging to that Group.
-- @tparam string SpawnPrefix is the name of the Group in the ME that defines the Template. That Group must have the flag "Late Activation" set. Note that this SpawnPrefix name should not contain any # character.
-- @treturn SPAWN
-- @usage
-- -- NATO helicopters engaging in the battle field.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' )
function SPAWN:New( SpawnPrefix )
trace.f(self.ClassName, SpawnPrefix)
-- Inherits from BASE
local self = BASE:Inherit( self, BASE:New() )
local TemplateGroup = Group.getByName( SpawnPrefix )
if TemplateGroup then
self.SpawnPrefix = SpawnPrefix
self.SpawnCount = 0 -- The internal counter of the amount of spawning the has happened since SpawnStart.
self.AliveUnits = 0 -- Contains the counter how many units are currently alive
self.SpawnScheduled = false -- Reflects if the spawning for this SpawnPrefix is going to be scheduled or not.
self.SpawnTemplate = self._GetTemplate( self, SpawnPrefix ) -- 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.UnControlled = false -- When working in UnControlled mode, all planes are Spawned in UnControlled mode before the scheduler starts.
self.SpawnMaxGroupsAlive = 0 -- The maximum amount of groups that can be alive of SpawnPrefix at the same time.
self.SpawnMaxGroups = 0 -- The maximum amount of groups that can be spawned.
else
error( "SPAWN:New: There is no group declared in the mission editor with SpawnPrefix = '" .. SpawnPrefix .. "'" )
end
self.AddEvent( self, world.event.S_EVENT_BIRTH, self.OnBirth )
self.AddEvent( self, world.event.S_EVENT_DEAD, self.OnDeadOrCrash )
self.AddEvent( self, world.event.S_EVENT_CRASH, self.OnDeadOrCrash )
self.EnableEvents( self )
return self
end
--- Randomizes a defined route of the Template Group in the ME when the Group is Spawned. This is very useful to define extra variation in the DCS World run-time environment of the behaviour of Groups like Ground Units, Ships, Planes, Helicopters.
-- @tparam number SpawnStartPoint is the waypoint where the randomization begins. Note that the StartPoint = 0 equals the point where the Group is Spawned. This parameter is useful to avoid randomization to start from the first waypoint, but a bit further down the route...
-- @tparam number SpawnEndPoint is the waypoint where the randomization ends. this parameter is useful to avoid randomization to end at a waypoint earlier than the last waypoint on the route.
-- @tparam number SpawnRadius is the radius in meters, that defines the concentric circle in which the randomization of the new waypoint will take place, with the original waypoint located in the middle...
-- @treturn SPAWN
-- @usage
-- -- NATO helicopters engaging in the battle field.
-- -- The KA-50 has waypoints SP, 1, 2, 3, 4, DP.
-- -- Waypoints 2 and 3 will only be randomized. The others will remain on their original position with each new ${SPAWN} of the helicopter.
-- -- The randomization of waypoint 2 and 3 will take place within a diameter of 4000 meters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):RandomizeRoute( 2, 2, 2000 )
function SPAWN:RandomizeRoute( SpawnStartPoint, SpawnEndPoint, SpawnRadius )
trace.f( self.ClassName, { SpawnStartPoint, SpawnEndPoint, SpawnRadius } )
self.SpawnStartPoint = SpawnStartPoint -- When the spawning occurs, randomize the route points from SpawnStartPoint.
self.SpawnEndPoint = SpawnEndPoint -- When the spawning occurs, randomize the route points till SpawnEndPoint.
self.SpawnRadius = SpawnRadius -- The Radius of randomization of the route points from SpawnStartPoint till SpawnEndPoint.
return self
end
--- SPAWNs a new Group within varying time intervals. This is useful if you want to have continuity within your missions of certain (AI) Groups to be present (alive) within your missions.
-- @tparam number SpawnTime is the time interval defined in seconds between each new SPAWN of new Groups.
-- @tparam number SpawnTimeVariation is the variation to be applied on the defined time interval between each new SPAWN. The variation is defined as a value between 0 and 1, which expresses the %-tage of variation to be applied as the low and high time interval boundaries. Between these boundaries a new time interval will be applied. See usage.
-- @treturn SPAWN
-- @usage
-- -- NATO helicopters engaging in the battle field.
-- -- The time interval is set to SPAWN new helicopters between each 600 seconds, with a time variation of 50%.
-- -- The time variation in this case will be between 450 seconds and 750 seconds.
-- -- This is calculated as follows:
-- -- Low limit: 600 * ( 1 - 0.5 / 2 ) = 450
-- -- High limit: 600 * ( 1 + 0.5 / 2 ) = 750
-- -- Between these two values, a random amount of seconds will be choosen for each new SPAWN of the helicopters.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Schedule( 600, 0.5 )
function SPAWN:Schedule( SpawnTime, SpawnTimeVariation )
trace.f( self.ClassName, { SpawnTime, SpawnTimeVariation } )
self.SpawnCurrentTimer = 0 -- The internal timer counter to trigger a scheduled spawning of SpawnPrefix.
self.SpawnSetTimer = 0 -- The internal timer value when a scheduled spawning of SpawnPrefix 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:ScheduleStart()
end
trace.i( self.ClassName, { self.SpawnLowTimer, self.SpawnHighTimer } )
return self
end
--- Will start the SPAWNing timers.
-- This function is called automatically when @{Schedule} is called.
function SPAWN:ScheduleStart()
trace.f( self.ClassName )
--local ClientUnit = #AlivePlayerUnits()
self.AliveFactor = 10 -- ( 10 - ClientUnit ) / 10
if self.SpawnScheduled == false then
self.SpawnScheduled = 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 activity.
function SPAWN:ScheduleStop()
trace.f( self.ClassName )
self.SpawnScheduled = false
end
--- Limits the Maximum amount of Units to be alive, and the maximum amount of Groups to be SPAWNed within the DCS World run-time environment.
-- Note that this method is exceptionally important to balance the amount of Units alive within the DCSRTE and the performance of the mission. Depending on the machine etc, a mission can only process a maximum amount of units.
-- If the time interval must be short, but there should not be more Units or Groups alive than a maximum amount of units, then this function should be used...
-- @tparam number SpawnMaxGroupsAlive is the Maximum amount of Units to be alive. When there are more Units alive in the DCSRTE of SpawnPrefix, then no new SPAWN will happen of the Group, until some of these Units will be destroyed.
-- @tparam number SpawnMaxGroups is the Maximum amount of Groups that can be SPAWNed from SpawnPrefix. When there are more Groups alive in the DCSRTE of SpawnPrefix, then no more SPAWNs will happen of the Group. This parameter is useful to define a maximum amount of airplanes, ground troops, helicopters, ships etc within a supply area.
-- This parameter accepts the value 0, which expresses no Group count limits.
-- @treturn SPAWN
-- @usage
-- -- NATO helicopters engaging in the battle field.
-- -- This helicopter group consists of one Unit. So, this group will SPAWN maximum 2 groups simultaneously within the DCSRTE.
-- -- There will be maximum 24 groups SPAWNed during the whole mission lifetime.
-- Spawn_BE_KA50 = SPAWN:New( 'BE KA-50@RAMP-Ground Defense' ):Limit( 2, 24 )
function SPAWN:Limit( SpawnMaxGroupsAlive, SpawnMaxGroups )
trace.f( self.ClassName, { SpawnMaxGroupsAlive, SpawnMaxGroups } )
self.SpawnMaxGroupsAlive = SpawnMaxGroupsAlive -- The maximum amount of groups that can be alive of SpawnPrefix at the same time.
self.SpawnMaxGroups = SpawnMaxGroups -- The maximum amount of groups that can be spawned.
return self
end
--- This function is rather complicated to understand. But I'll try to explain...
-- This function becomes useful when you need to SPAWN random types of Groups defined within the ME, but they all need to follow the same Template route and have the same SpawnPrefix name, then this method becomes very useful.
-- @tparam table{string,...} SpawnPrefixTable is a table with the names of the Groups defined within the ME (with late activatio on), from which on a new SPAWN of SpawnPrefix (the main Group name), a NEW Group will be choosen as the Group to be SPAWNed.
-- In other words, this method randomizes between a defined set of Groups the Group to be SPAWNed for each new SPAWN.
-- @treturn SPAWN
-- @usage
-- -- NATO Tank Platoons invading Gori.
-- -- Choose between 13 different 'US Tank Platoon' configurations for each new SPAWN the Group to be SPAWNed for the
-- -- 'US Tank Platoon Left', 'US Tank Platoon Middle' and 'US Tank Platoon Right' SpawnPrefixes.
-- -- Each new SPAWN will randomize the route, with a defined time interval of 200 seconds with 40% time variation (randomization) and
-- -- with a limit set of maximum 12 Units alive simulteneously and 150 Groups to be SPAWNed during the whole mission.
-- Spawn_US_Platoon = { 'US Tank Platoon 1', 'US Tank Platoon 2', 'US Tank Platoon 3', 'US Tank Platoon 4', 'US Tank Platoon 5',
-- 'US Tank Platoon 6', 'US Tank Platoon 7', 'US Tank Platoon 8', 'US Tank Platoon 9', 'US Tank Platoon 10',
-- 'US Tank Platoon 11', 'US Tank Platoon 12', 'US Tank Platoon 13' }
-- Spawn_US_Platoon_Left = SPAWN:New( 'US Tank Platoon Left' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Middle = SPAWN:New( 'US Tank Platoon Middle' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 )
-- Spawn_US_Platoon_Right = SPAWN:New( 'US Tank Platoon Right' ):Limit( 12, 150 ):Schedule( 200, 0.4 ):RandomizeTemplate( Spawn_US_Platoon ):RandomizeRoute( 3, 3, 2000 )
function SPAWN:RandomizeTemplate( SpawnPrefixTable )
trace.f( self.ClassName, { SpawnPrefix, SpawnPrefixTable } )
self.SpawnPrefixTable = SpawnPrefixTable
return self
end
--- When a Group got SPAWNed, it has a life within the DCSRTE. For planes and helicopters, when these Units go home and land on their home airbases and farps, they normally would taxi to the parking spot, shut-down their engines and wait forever until the Group is removed by the DCSRTE.
-- This function is used to Re-Spawn automatically (so no extra call is needed anymore) the same Group after it landed. This will enable a SPAWNed group to be Re-SPAWNed after it lands, until it is destroyed...
-- Note: When the Group is respawned, it will @{ReSpawn} at the original airbase where it took off. So ensure that the paths for Groups that ReSpawn, always return to the orignal airbase.
-- @treturn SPAWN
-- @usage
-- -- 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()
trace.f( self.ClassName )
self.SpawnRepeat = true
self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true
self:AddEvent( world.event.S_EVENT_LAND, self.OnLand )
self:AddEvent( world.event.S_EVENT_TAKEOFF, self.OnTakeOff )
self:AddEvent( world.event.S_EVENT_ENGINE_SHUTDOWN, self.OnEngineShutDown )
self:EnableEvents()
return self
end
--- Same as the @{Repeat) method.
-- @treturn SPAWN
-- @see Repeat
function SPAWN:RepeatOnLanding()
trace.f( self.ClassName )
self:Repeat()
self.RepeatOnEngineShutDown = false
self.RepeatOnLanding = true
return self
end
--- Same as the @{Repeat) method, but now the Group will respawn after its engines have shut down.
-- @treturn SPAWN
-- @see Repeat
function SPAWN:RepeatOnEngineShutDown()
trace.f( self.ClassName )
self:Repeat()
self.RepeatOnEngineShutDown = true
self.RepeatOnLanding = false
return self
end
--- Will SPAWN a Group whenever you want to do this.
-- Note that the configuration with the above functions will apply when calling this method: Maxima, Randomization of routes, Scheduler, ...
-- Uses @{DATABASE} global object defined in MOOSE.
-- @treturn SPAWN
function SPAWN:Spawn()
trace.f( self.ClassName )
local SpawnTemplate = self:_Prepare( true )
if self.SpawnStartPoint ~= 0 or self.SpawnEndPoint ~= 0 then
SpawnTemplate = self:_RandomizeRoute( SpawnTemplate )
end
_Database:Spawn( SpawnTemplate )
if self.SpawnRepeat then
_Database:SetStatusGroup( SpawnTemplate.name, "ReSpawn" )
end
return self
end
--- Will Re-SPAWN a Group based on a given GroupName. The GroupName must be a group that is already alive within the DCSRTE and should have a Group Template defined in the ME (with Late Activation flag on).
-- Note that the configuration with the above functions will apply when calling this method: Maxima, Randomization of routes, Scheduler, ...
-- @tparam string SpawnGroupName
-- @treturn SPAWN
-- Uses _Database global object defined in MOOSE.
function SPAWN:ReSpawn( SpawnGroupName )
trace.f( self.ClassName, { SpawnGroupName } )
local SpawnGroup = Group.getByName( SpawnGroupName )
SpawnGroup:destroy()
local SpawnTemplate = self:_Prepare( false )
-- Give the Group the original name of the Group.
SpawnTemplate.name = SpawnGroupName
-- Give the units of the Group the name following the SPAWN naming convention, so that they don't replace other units within the ME.
local SpawnUnits = table.getn( SpawnTemplate.units )
for u = 1, SpawnUnits do
SpawnTemplate.units[u].name = string.format( '%s-%02d', SpawnGroupName, u )
SpawnTemplate.units[u].unitId = nil
end
_Database:Spawn( SpawnTemplate )
return self
end
--- Will SPAWN a Group whenever you want to do this, but for AIR Groups only to be applied, and will SPAWN the Group in Uncontrolled mode... This will be similar to the Uncontrolled flag setting in the ME.
-- @treturn SPAWN
function SPAWN:SpawnUncontrolled()
trace.f( self.ClassName )
self.UnControlled = true
local SpawnCountStart = self.SpawnCount + 1
for SpawnCount = SpawnCountStart, self.SpawnMaxGroups do
local SpawnTemplate = self:_Prepare( true )
SpawnTemplate.uncontrolled = true
_Database:Spawn( SpawnTemplate )
end
self.SpawnCount = SpawnCountStart - 1
return self
end
--- Will SPAWN a Group from a Carrier. 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.
-- @tparam Group CarrierGroup is the Group of the AIR unit or GROUND unit dropping or unloading other units.
-- @tparam string TargetZonePrefix is the Prefix of the Zone defined in the ME where the Group should be moving to after drop.
-- @tparam string NewGroupName (forgot this).
-- @tparam bool LateActivate (optional) does the SPAWNing with Lateactivation on.
function SPAWN:FromCarrier( CarrierGroup, TargetZonePrefix, NewGroupName, LateActivate )
trace.f( self.ClassName, { CarrierGroup, TargetZonePrefix, NewGroupName, LateActivate } )
local SpawnTemplate
if CarrierGroup and CarrierGroup:isExist() and CarrierGroup:getUnit(1) then -- and CarrierGroup:getUnit(1):inAir() == false then
local GroupUnits = CarrierGroup:getUnits()
local GroupUnitCount = table.getn(GroupUnits)
trace.i( self.ClassName, "CarrierGroup:getSize() = " .. CarrierGroup:getSize() )
trace.i( self.ClassName, 'GroupUnitCount = ' .. GroupUnitCount )
for UnitId, UnitData in pairs(GroupUnits) do
UnitDeploy = UnitData
SpawnTemplate = self:_Prepare( true )
if ( self.SpawnMaxGroups == 0 ) or ( self.SpawnCount <= self.SpawnMaxGroups ) then
if ( self.SpawnMaxGroupsAlive == 0 ) or ( self.AliveUnits < self.SpawnMaxGroupsAlive * #self.SpawnTemplate.units ) or self.UnControlled then
if NewGroupName ~= nil then
SpawnTemplate.name = NewGroupName
end
if LateActivate ~= nil then
if LateActivate == true then
SpawnTemplate.lateActivation = true
SpawnTemplate.visible = true
end
end
SpawnTemplate = self:_RandomizeRoute( SpawnTemplate )
local TargetZone = trigger.misc.getZone( TargetZonePrefix )
local TargetZonePos = {}
TargetZonePos.x = TargetZone.point.x + math.random(TargetZone.radius * -1, TargetZone.radius)
TargetZonePos.z = TargetZone.point.z + math.random(TargetZone.radius * -1, TargetZone.radius)
local RouteCount = table.getn( SpawnTemplate.route.points )
trace.i( self.ClassName, "RouteCount = " .. RouteCount )
local UnitDeployPosition = UnitDeploy:getPoint()
SpawnTemplate.route.points[1].x = UnitDeployPosition.x - 50
SpawnTemplate.route.points[1].y = UnitDeployPosition.z
SpawnTemplate.route.points[1].alt = nil
SpawnTemplate.route.points[1].alt_type = nil
if SpawnStartPoint ~= 0 and SpawnEndPoint ~= 0 then
SpawnTemplate.route.points[RouteCount].x = TargetZonePos.x
SpawnTemplate.route.points[RouteCount].y = TargetZonePos.z
else
SpawnTemplate.route.points[RouteCount].x = TargetZone.point.x
SpawnTemplate.route.points[RouteCount].y = TargetZone.point.z
end
trace.i( self.ClassName, 'SpawnTemplate.route.points['..RouteCount..'].x = ' .. SpawnTemplate.route.points[RouteCount].x .. ', SpawnTemplate.route.points['..RouteCount..'].y = ' .. SpawnTemplate.route.points[RouteCount].y )
for v = 1, table.getn( SpawnTemplate.units ) do
local SpawnPos = routines.getRandPointInCircle( UnitDeployPosition, 40, 10 )
SpawnTemplate.units[v].x = SpawnPos.x
SpawnTemplate.units[v].y = SpawnPos.y
trace.i( self.ClassName, 'SpawnTemplate.units['..v..'].x = ' .. SpawnTemplate.units[v].x .. ', SpawnTemplate.units['..v..'].y = ' .. SpawnTemplate.units[v].y )
end
_Database:Spawn( SpawnTemplate )
end
end
end
end
trace.r( self.ClassName, "" )
return SpawnTemplate
end
--- Will SPAWN a Group within a given ZoneName.
-- @tparam string ZonePrefix is the name of the zone where the Group is to be SPAWNed.
-- @treturn SpawnTemplate
function SPAWN:InZone( ZonePrefix )
trace.f("Spawn", ZonePrefix )
local SpawnTemplate = self:_Prepare( true )
local Zone = trigger.misc.getZone( ZonePrefix )
local ZonePos = {}
ZonePos.x = Zone.point.x + math.random(Zone.radius * -1, Zone.radius)
ZonePos.z = Zone.point.z + math.random(Zone.radius * -1, Zone.radius)
local RouteCount = table.getn(SpawnTemplate.route.points)
SpawnTemplate.route.points[1].x = ZonePos.x
SpawnTemplate.route.points[1].y = ZonePos.z
SpawnTemplate.route.points[1].alt = nil
SpawnTemplate.route.points[1].alt_type = nil
SpawnTemplate.route.points[RouteCount].x = ZonePos.x
SpawnTemplate.route.points[RouteCount].y = ZonePos.z
_Database:Spawn( SpawnTemplate )
return SpawnTemplate
end
--- Private
-- @section
--- Gets the CategoryID of the Group with the given SpawnPrefix
function SPAWN:_GetGroupCategoryID( SpawnPrefix )
local TemplateGroup = Group.getByName( SpawnPrefix )
if TemplateGroup then
return TemplateGroup:getCategory()
else
return nil
end
end
--- Gets the CoalitionID of the Group with the given SpawnPrefix
function SPAWN:_GetGroupCoalitionID( SpawnPrefix )
local TemplateGroup = Group.getByName( SpawnPrefix )
if TemplateGroup then
return TemplateGroup:getCoalition()
else
return nil
end
end
--- Gets the CountryID of the Group with the given SpawnPrefix
function SPAWN:_GetGroupCountryID( SpawnPrefix )
local TemplateGroup = Group.getByName( SpawnPrefix )
if TemplateGroup then
local TemplateUnits = TemplateGroup:getUnits()
return TemplateUnits[1]:getCountry()
else
return nil
end
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.
function SPAWN:_GetTemplate( SpawnPrefix )
trace.f( self.ClassName, SpawnPrefix )
local SpawnTemplate = nil
SpawnTemplate = routines.utils.deepCopy( _Database.Groups[SpawnPrefix].Template )
if SpawnTemplate == nil then
error( 'No Template returned for SpawnPrefix = ' .. SpawnPrefix )
end
SpawnTemplate.SpawnCoalitionID = self:_GetGroupCoalitionID( SpawnPrefix )
SpawnTemplate.SpawnCategoryID = self:_GetGroupCategoryID( SpawnPrefix )
SpawnTemplate.SpawnCountryID = self:_GetGroupCountryID( SpawnPrefix )
trace.r( self.ClassName, "", { SpawnTemplate } )
return SpawnTemplate
end
--- Prepares the new Group Template before Spawning.
function SPAWN:_Prepare( SpawnIncrement )
trace.f( self.ClassName )
local SpawnCount
local SpawnUnits
local SpawnTemplate = routines.utils.deepCopy( self.SpawnTemplate )
if self.SpawnRoute ~= nil then
local SpawnRoute = self:_GetTemplate( self.SpawnRoute ).route
SpawnTemplate.route = routines.utils.deepCopy( SpawnRoute )
end
-- Increase the spawn counter for the group
if SpawnIncrement == true then
self.SpawnCount = self.SpawnCount + 1
end
SpawnTemplate.name = string.format( self.SpawnPrefix .. '#%03d', self.SpawnCount )
SpawnTemplate.groupId = nil
SpawnTemplate.lateActivation = false
if SpawnTemplate.SpawnCategoryID == Group.Category.GROUND then
SpawnTemplate.visible = false
end
if SpawnTemplate.SpawnCategoryID == Group.Category.HELICOPTER or SpawnTemplate.SpawnCategoryID == Group.Category.AIRPLANE then
SpawnTemplate.uncontrolled = false
end
if self.SpawnPrefixTable ~= nil then
local SpawnTemplatePrefix = self.SpawnPrefixTable[ math.random( 1, #self.SpawnPrefixTable ) ]
SpawnTemplateRandom = self:_GetTemplate( SpawnTemplatePrefix )
SpawnTemplate.SpawnCoalitionID = SpawnTemplateRandom.SpawnCoalitionID
SpawnTemplate.SpawnCategoryID = SpawnTemplateRandom.SpawnCategoryID
SpawnTemplate.SpawnCountryID = SpawnTemplateRandom.SpawnCountryID
SpawnTemplate.units = routines.utils.deepCopy( SpawnTemplateRandom.units )
end
SpawnUnits = table.getn( SpawnTemplate.units )
for u = 1, SpawnUnits do
SpawnTemplate.units[u].name = string.format( self.SpawnPrefix .. '#%03d-%02d', self.SpawnCount, u )
SpawnTemplate.units[u].unitId = nil
SpawnTemplate.units[u].x = SpawnTemplate.route.points[1].x
SpawnTemplate.units[u].y = SpawnTemplate.route.points[1].y
end
trace.r( self.ClassName, "", SpawnTemplate.name )
return SpawnTemplate
end
--- Will randomize the route of the Group Template.
function SPAWN:_RandomizeRoute( SpawnTemplate )
trace.f( self.ClassName, SpawnTemplate.name )
if self.SpawnStartPoint and self.SpawnEndPoint then
local RouteCount = table.getn( SpawnTemplate.route.points )
for t = self.SpawnStartPoint, RouteCount - self.SpawnEndPoint do
SpawnTemplate.route.points[t].x = SpawnTemplate.route.points[t].x + math.random( self.SpawnRadius * -1, self.SpawnRadius )
SpawnTemplate.route.points[t].y = SpawnTemplate.route.points[t].y + math.random( self.SpawnRadius * -1, self.SpawnRadius )
SpawnTemplate.route.points[t].alt = nil
--SpawnGroup.route.points[t].alt_type = nil
trace.i( self.ClassName, 'SpawnTemplate.route.points[' .. t .. '].x = ' .. SpawnTemplate.route.points[t].x .. ', SpawnTemplate.route.points[' .. t .. '].y = ' .. SpawnTemplate.route.points[t].y )
end
end
trace.r( self.ClassName, "", SpawnTemplate.name )
return SpawnTemplate
end
--- Events
-- @section Events
--- Obscolete
-- @todo Need to delete this... _Database does this now ...
function SPAWN:OnBirth( event )
trace.f( self.ClassName, { event } )
if timer.getTime0() < timer.getAbsTime() then -- dont need to add units spawned in at the start of the mission if mist is loaded in init line
if event.initiator and event.initiator:getName() then
trace.l(self.ClassName, "OnBirth", "Birth object : " .. event.initiator:getName() )
local EventPrefix = string.match( event.initiator:getName(), ".*#" )
if EventPrefix == self.SpawnPrefix .. '#' then
--MessageToAll( "Mission command: unit " .. SpawnPrefix .. " spawned." , 5, EventPrefix .. '/Event')
self.AliveUnits = self.AliveUnits + 1
trace.l(self.ClassName, "OnBirth", self.AliveUnits )
end
end
end
end
--- Obscolete
-- @todo Need to delete this... _Database does this now ...
function SPAWN:OnDeadOrCrash( event )
trace.f( self.ClassName, { event } )
if event.initiator and event.initiator:getName() then
trace.l( self.ClassName, "OnDeadOrCrash", "Dead object : " .. event.initiator:getName() )
local EventPrefix = string.match( event.initiator:getName(), ".*#" )
if EventPrefix == self.SpawnPrefix .. '#' then
-- local DestroyedUnit = Unit.getByName( EventPrefix )
-- if DestroyedUnit and DestroyedUnit.getLife() <= 1.0 then
--MessageToAll( "Mission command: unit " .. SpawnPrefix .. " crashed." , 5, EventPrefix .. '/Event')
self.AliveUnits = self.AliveUnits - 1
trace.l( self.ClassName, "OnDeadOrCrash", self.AliveUnits )
-- end
end
end
end
--- Will detect AIR Units landing... When the event takes place, the SPAWNed Group is registered as landed.
-- This is needed to ensure that Re-SPAWNing is only done for landed AIR Groups.
-- @todo Need to test for AIR Groups only...
function SPAWN:OnLand( event )
trace.f( self.ClassName, { event } )
if event.initiator and event.initiator:getName() then
trace.l( self.ClassName, "OnLand", "Landed object : " .. event.initiator:getName() )
local EventPrefix = string.match( event.initiator:getName(), ".*#" )
if EventPrefix == self.SpawnPrefix .. '#' then
self.Landed = true
trace.l( self.ClassName, "OnLand", "self.Landed = true" )
if self.Landed and self.RepeatOnLanding then
local SpawnGroupName = Unit.getGroup(event.initiator):getName()
trace.l( self.ClassName, "OnLand", "ReSpawn " .. SpawnGroupName )
self:ReSpawn( SpawnGroupName )
end
end
end
end
--- Will detect AIR Units taking off... When the event takes place, the SPAWNed Group is registered as airborne...
-- This is needed to ensure that Re-SPAWNing only is done for landed AIR Groups.
-- @todo Need to test for AIR Groups only...
function SPAWN:OnTakeOff( event )
trace.f( self.ClassName, { event } )
if event.initiator and event.initiator:getName() then
trace.l( self.ClassName, "OnTakeOff", "TakeOff object : " .. event.initiator:getName() )
local EventPrefix = string.match( event.initiator:getName(), ".*#" )
if EventPrefix == self.SpawnPrefix .. '#' then
trace.l( self.ClassName, "OnTakeOff", "self.Landed = false" )
self.Landed = false
end
end
end
--- Will detect AIR Units shutting down their engines ...
-- When the event takes place, and the method @{RepeatOnEngineShutDown} was called, the SPAWNed Group will Re-SPAWN.
-- But only when the Unit was registered to have landed.
-- @see OnTakeOff
-- @see OnLand
-- @todo Need to test for AIR Groups only...
function SPAWN:OnEngineShutDown( event )
trace.f( self.ClassName, { event } )
if event.initiator and event.initiator:getName() then
trace.l( self.ClassName, "OnEngineShutDown", "EngineShutDown object : " .. event.initiator:getName() )
local EventPrefix = string.match( event.initiator:getName(), ".*#" )
if EventPrefix == self.SpawnPrefix .. '#' then
if self.Landed and self.RepeatOnEngineShutDown then
local SpawnGroupName = Unit.getGroup(event.initiator):getName()
trace.l( self.ClassName, "OnEngineShutDown", "ReSpawn " .. SpawnGroupName )
self:ReSpawn( SpawnGroupName )
end
end
end
end
--- Scheduled
-- @section Scheduled
--- This function is called automatically by the Spawning scheduler.
-- It is the internal worker method SPAWNing new Groups on the defined time intervals.
function SPAWN:_Scheduler()
trace.l( self.ClassName, '_Scheduler', self.SpawnPrefix )
if self.SpawnInit or self.SpawnCurrentTimer == self.SpawnSetTimer then
-- Validate if there are still groups left in the batch...
if ( self.SpawnMaxGroups == 0 ) or ( self.SpawnCount <= self.SpawnMaxGroups ) then
if self.AliveUnits < self.SpawnMaxGroupsAlive * #self.SpawnTemplate.units or self.UnControlled then
self:Spawn()
self.SpawnInit = false
end
end
if self.SpawnScheduled == 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
end

742
Stage.lua Normal file
View File

@ -0,0 +1,742 @@
--- Stages within a @{TASK} within a @{MISSION}. All of the STAGE functionality is considered internally administered and not to be used by any Mission designer.
-- @classmod STAGE
-- @author Flightcontrol
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Mission" )
Include.File( "Client" )
Include.File( "Task" )
STAGE = {
ClassName = "STAGE",
MSG = { ID = "None", TIME = 10 },
FREQUENCY = { NONE = 0, ONCE = 1, REPEAT = -1 },
Name = "NoStage",
StageType = '',
WaitTime = 1,
Frequency = 1,
MessageCount = 0,
MessageInterval = 15,
MessageShown = {},
MessageShow = false,
MessageFlash = false
}
function STAGE:New()
trace.f(self.ClassName)
local self = BASE:Inherit( self, BASE:New() )
return self
end
function STAGE:Execute( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = true
return Valid
end
function STAGE:Executing( Mission, Client, Task )
trace.f(self.ClassName)
end
function STAGE:Validate( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = true
return Valid
end
STAGEBRIEF = {
ClassName = "BRIEF",
MSG = { ID = "Brief", TIME = 30 },
Name = "Brief",
StageBriefingTime = 0,
StageBriefingDuration = 10
}
function STAGEBRIEF:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGEBRIEF:Execute( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = BASE:Inherited(self):Execute( Mission, Client, Task )
Mission:ShowBriefing( Client )
self.StageBriefingTime = timer.getTime()
return Valid
end
function STAGEBRIEF:Validate( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = STAGE:Validate( Mission, Client, Task )
if timer.getTime() - self.StageBriefingTime <= self.StageBriefingDuration then
return 0
else
self.StageBriefingTime = timer.getTime()
return 1
end
end
STAGESTART = {
ClassName = "START",
MSG = { ID = "Start", TIME = 30 },
Name = "Start",
StageStartTime = 0,
StageStartDuration = 10
}
function STAGESTART:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGESTART:Execute( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = BASE:Inherited(self):Execute( Mission, Client, Task )
if Task.TaskBriefing then
Client:Message( Task.TaskBriefing, self.StageStartDuration, Mission.Name .. "/Stage", "Mission Command: Tasking" )
else
Client:Message( 'Task ' .. Task.TaskNumber .. '.', self.StageStartDuration, Mission.Name .. "/Stage", "Mission Command: Tasking" )
end
self.StageStartTime = timer.getTime()
return Valid
end
function STAGESTART:Validate( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = STAGE:Validate( Mission, Client, Task )
if timer.getTime() - self.StageStartTime <= self.StageStartDuration then
return 0
else
self.StageStartTime = timer.getTime()
return 1
end
return 1
end
STAGEROUTE = {
ClassName = "STAGEROUTE",
MSG = { ID = "Route", TIME = 5 },
Frequency = STAGE.FREQUENCY.REPEAT,
Name = "Route"
}
function STAGEROUTE:New()
trace.f(self.ClassName)
-- Arrange meta tables
local self = BASE:Inherit( self, STAGE:New() )
self.StageType = 'CLIENT'
self.MessageSwitch = true
return self
end
function STAGEROUTE:Execute( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = BASE:Inherited(self):Execute( Mission, Client, Task )
if type( Task.LandingZones) == "table" then
local RouteMessage = "Fly to "
for LandingZoneID, LandingZoneName in pairs( Task.LandingZones ) do
RouteMessage = RouteMessage .. LandingZoneName .. ' at ' .. routines.getBRStringZone( { zone = LandingZoneName, ref = Client:ClientGroup():getUnit(1):getPoint(), true, true } ) .. ' km. '
end
Client:Message( RouteMessage, self.MSG.TIME, Mission.Name .. "/StageRoute", "Co-Pilot: Route", 10 )
else
Client:Message( "Fly to " .. Task.LandingZones .. ' at ' .. routines.getBRStringZone( { zone = Task.LandingZones, ref = Client:ClientGroup():getUnit(1):getPoint(), true, true } ) .. ' km. ', self.MSG.TIME, Mission.Name .. "/StageRoute", "Co-Pilot: Route", 10 )
end
if Client:IsTransport() then
Client:ShowCargo()
end
return Valid
end
function STAGEROUTE:Validate( Mission, Client, Task )
trace.f(self.ClassName)
local Valid = STAGE:Validate( Mission, Client, Task )
-- check if this carrier is in the landing zone
Task.CurrentLandingZoneID = routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones )
if ( Task.CurrentLandingZoneID ) then
if not Task.Signalled then
if Task.LandingZoneSignalType then
env.info( 'TransportSchedule: Task.LandingZoneSignalType = ' .. Task.LandingZoneSignalType.TEXT )
if Task.LandingZoneSignalUnitNames then
local LandingZoneSignalUnit = Task.LandingZoneSignalUnitNames[Task.CurrentLandingZoneID]
trace.i( self.ClassName, 'LandingZoneSignalUnit = ' .. LandingZoneSignalUnit )
local SignalUnit = Unit.getByName(LandingZoneSignalUnit)
if SignalUnit == nil then
SignalUnit = StaticObject.getByName( LandingZoneSignalUnit )
end
if SignalUnit ~= nil then
trace.i( self.ClassName, 'Signalling Unit' )
local SignalVehiclePos = SignalUnit:getPosition().p
SignalVehiclePos.y = SignalVehiclePos.y + Task.LandingZoneSignalHeight
if Task.LandingZoneSignalType.ID == Task.SIGNAL.TYPE.SMOKE.ID then
trigger.action.smoke( SignalVehiclePos, Task.LandingZoneSignalColor.COLOR )
elseif Task.LandingZoneSignalType.ID == Task.SIGNAL.TYPE.FLARE.ID then
trigger.action.signalFlare( SignalVehiclePos, Task.LandingZoneSignalColor.COLOR, 0 )
end
end
else
env.info( 'TransportSchedule: Signaling landing zone ' )
local LandingZone = trigger.misc.getZone( Task.LandingZones [ Task.CurrentLandingZoneID ] )
local CurrentPosition = { x = LandingZone.point.x, y = LandingZone.point.z }
LandingZone.point.y = land.getHeight( CurrentPosition ) + 10
if Task.LandingZoneSignalType.ID == Task.SIGNAL.TYPE.SMOKE.ID then
env.info( 'TransportSchedule: Smoking zone x = ' .. LandingZone.point.x .. ' y = ' .. LandingZone.point.y .. ' z = ' .. LandingZone.point.z )
trigger.action.smoke( LandingZone.point, Task.LandingZoneSignalColor.COLOR )
elseif Task.LandingZoneSignalType.ID == Task.SIGNAL.TYPE.SMOKE.FLARE.ID then
env.info( 'TransportSchedule: Flaring zone x = ' .. LandingZone.point.x .. ' y = ' .. LandingZone.point.y .. ' z = ' .. LandingZone.point.z )
trigger.action.signalFlare( LandingZone.point, Task.LandingZoneSignalColor.COLOR, 0 )
end
end
end
self.Signalled = true
end
return 1
end
return 0
end
STAGELANDING = {
ClassName = "STAGELANDING",
MSG = { ID = "Landing", TIME = 10 },
Name = "Landing",
Signalled = false
}
function STAGELANDING:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGELANDING:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'We have arrived at ' .. Task.LandingZones[Task.CurrentLandingZoneID] .. '. Land the helicopter to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType.TEXT .. '.',
self.MSG.TIME, Mission.Name .. "/Stage", "Co-Pilot: Landing" )
end
function STAGELANDING:Validate( Mission, Client, Task )
trace.f(self.ClassName)
if routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones[Task.CurrentLandingZoneID] ) then
else
Task.Signalled = false
Task:RemoveCargoMenus( Client )
return -1
end
if not Client:ClientGroup():getUnits()[1]:inAir() then
else
return 0
end
return 1
end
STAGELANDED = {
ClassName = "STAGELANDED",
MSG = { ID = "Land", TIME = 10 },
Name = "Landed",
MenusAdded = false
}
function STAGELANDED:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGELANDED:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'We have landed within the landing zone. Use the radio menu (F10) to ' .. Task.TEXT[1] .. ' the ' .. Task.CargoType.TEXT .. '.', self.MSG.TIME, Mission.Name .. "/Stage", "Co-Pilot: Landed" )
if not self.MenusAdded then
Task:RemoveCargoMenus( Client )
Task:AddCargoMenus( Client, Mission._Cargos, 250 )
end
end
function STAGELANDED:Validate( Mission, Client, Task )
trace.f(self.ClassName)
if routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones[Task.CurrentLandingZoneID] ) then
else
Task.Signalled = false
Task:RemoveCargoMenus( Client )
return -2
end
if not Client:ClientGroup():getUnits()[1]:inAir() then
else
Task.Signalled = false
return -1
end
if Task.ExecuteStage == _TransportExecuteStage.EXECUTING then
else
return 0
end
return 1
end
STAGEUNLOAD = {
ClassName = "STAGEUNLOAD",
MSG = { ID = "Unload", TIME = 10 },
Name = "Unload"
}
function STAGEUNLOAD:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGEUNLOAD:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'The ' .. Task.CargoType.TEXT .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.',
self.MSG.TIME, Mission.Name .. "/Stage", "Co-Pilot: Unload" )
Task:RemoveCargoMenus( Client )
end
function STAGEUNLOAD:Executing( Mission, Client, Task )
trace.f(self.ClassName)
env.info( 'STAGEUNLOAD:Executing() Task.CargoName = ' .. Task.CargoName )
local Cargo = Client:RemoveCargo( Task.CargoName )
if Cargo then
env.info( 'STAGEUNLOAD:Executing() Cargo.CargoName = ' .. Cargo.CargoName )
env.info( 'STAGEUNLOAD:Executing() Cargo.CargoGroupName = ' .. Cargo.CargoGroupName )
env.info( 'STAGEUNLOAD:Executing() Mission._Cargos[Cargo.CargoName].CargoGroupTemplate = ' .. Mission._Cargos[Cargo.CargoName].CargoGroupTemplate )
if Cargo.CargoType.TRANSPORT == CARGO_TRANSPORT.UNIT then
if Cargo.CargoName then
if Task.TargetZoneName then
SPAWN:New( Mission._Cargos[Cargo.CargoName].CargoGroupTemplate ):FromCarrier ( Client:ClientGroup(),
Task.TargetZoneName,
Mission._Cargos[Cargo.CargoName].CargoGroupName )
else
SPAWN:New( Mission._Cargos[Cargo.CargoName].CargoGroupTemplate ):FromCarrier ( Client:ClientGroup(),
Task.LandingZones[Task.CurrentLandingZoneID],
Mission._Cargos[Cargo.CargoName].CargoGroupName )
end
end
end
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
Client:ShowCargo()
end
end
function STAGEUNLOAD:Validate( Mission, Client, Task )
trace.f(self.ClassName)
env.info( 'STAGEUNLOAD:Validate()' )
if routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones[Task.CurrentLandingZoneID] ) then
else
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task:RemoveCargoMenus( Client )
Client:Message( 'The ' .. Task.CargoType.TEXT .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.',
_TransportStageMsgTime.DONE, Mission.Name .. "/StageFailure", "Co-Pilot: Unload" )
return 1
end
if not Client:ClientGroup():getUnits()[1]:inAir() then
else
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task:RemoveCargoMenus( Client )
Client:Message( 'The ' .. Task.CargoType.TEXT .. " haven't been successfully " .. Task.TEXT[3] .. ' within the landing zone. Task and mission has failed.',
_TransportStageMsgTime.DONE, Mission.Name .. "/StageFailure", "Co-Pilot: Unload" )
return 1
end
if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then
Client:Message( 'The ' .. Task.CargoType.TEXT .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.', _TransportStageMsgTime.DONE, Mission.Name .. "/Stage", "Co-Pilot: Unload" )
Mission._Cargos[Task.CargoName].Status = CARGOSTATUS.UNLOADED
Task:RemoveCargoMenus( Client )
Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 ) -- We set the cargo as one more goal completed in the mission.
return 1
end
return 1
end
STAGELOAD = {
ClassName = "STAGELOAD",
MSG = { ID = "Load", TIME = 10 },
Name = "Load"
}
function STAGELOAD:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGELOAD:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'The ' .. Task.CargoType.TEXT .. ' are being ' .. Task.TEXT[2] .. ' within the landing zone. Wait until the helicopter is ' .. Task.TEXT[3] .. '.',
_TransportStageMsgTime.EXECUTING, Mission.Name .. "/Stage", "Co-Pilot: Load" )
-- Route the cargo to the Carrier
if Mission._Cargos[Task.CargoName].CargoType.TRANSPORT == CARGO_TRANSPORT.UNIT then
Task:OnBoardCargo( Client:ClientGroup(), Mission._Cargos )
Task.ExecuteStage = _TransportExecuteStage.EXECUTING
else
-- Add the group to the internal cargo;
Client:AddCargo( Task.CargoName, Mission._Cargos[Task.CargoName].CargoGroupName, Mission._Cargos[Task.CargoName].CargoType, Mission._Cargos[Task.CargoName].CargoWeight, Mission._Cargos[Task.CargoName].CargoGroupTemplate )
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
end
end
function STAGELOAD:Executing( Mission, Client, Task )
trace.f(self.ClassName)
-- Remove the loaded object from the battle zone.
if routines.IsPartOfGroupInRadius( Group.getByName(Mission._Cargos[Task.CargoName].CargoGroupName), Client:ClientGroup(), 75 ) then
routines.DestroyGroupInRadiusFromGroup( Group.getByName(Mission._Cargos[Task.CargoName].CargoGroupName), Client:ClientGroup(), 75 )
env.info('trying to remove cargo')
-- Add the group to the internal cargo;
Client:AddCargo( Task.CargoName, Mission._Cargos[Task.CargoName].CargoGroupName, Mission._Cargos[Task.CargoName].CargoType, Mission._Cargos[Task.CargoName].CargoWeight, Mission._Cargos[Task.CargoName].CargoGroupTemplate )
-- Message to the pilot that cargo has been loaded.
Client:Message( "The cargo " .. Task.CargoName .. " has been loaded in our helicopter.", 20, Mission.Name .. "/Stage", "Co-Pilot: Load" )
Task.ExecuteStage = _TransportExecuteStage.SUCCESS
Client:ShowCargo()
end
end
function STAGELOAD:Validate( Mission, Client, Task )
trace.f(self.ClassName)
if routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones[Task.CurrentLandingZoneID] ) then
else
Task:RemoveCargoMenus( Client )
Task.ExecuteStage = _TransportExecuteStage.FAILED
Task.CargoName = nil
Client:Message( "The " .. Task.CargoType.TEXT .. " loading has been aborted. You flew outside the pick-up zone while loading. ",
_TransportStageMsgTime.DONE, Mission.Name .. "/StageSuccess", "Co-Pilot: Load" )
return 1
end
if not Client:ClientGroup():getUnits()[1]: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.TEXT .. " loading has been aborted. Land the helicopter and load the cargo. Don't fly outside the pick-up zone. ",
_TransportStageMsgTime.DONE, Mission.Name .. "/StageSuccess", "Co-Pilot: Load" )
return -1
end
if Task.ExecuteStage == _TransportExecuteStage.SUCCESS then
Mission._Cargos[Task.CargoName].Status = CARGOSTATUS.LOADED
Task:RemoveCargoMenus( Client )
Client:Message( 'Co-Pilot: The ' .. Task.CargoType.TEXT .. ' have been sucessfully ' .. Task.TEXT[3] .. ' within the landing zone.',
_TransportStageMsgTime.DONE, Mission.Name .. "/Stage", "Co-Pilot: Load" )
Task.MissionTask:AddGoalCompletion( Task.MissionTask.GoalVerb, Task.CargoName, 1 )
return 1
end
return 0
end
STAGE_SLINGLOAD_HOOK = {
ClassName = "STAGE_SLINGLOAD_HOOK",
MSG = { ID = "SlingLoadHook", TIME = 10 },
Name = "SlingLoadHook"
}
function STAGE_SLINGLOAD_HOOK:New()
trace.f(self.ClassName)
-- Arrange meta tables
local self = BASE:Inherit( self, STAGE:New() )
self.StageType = 'CLIENT'
return self
end
function STAGE_SLINGLOAD_HOOK:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'Hook the Cargo onto the helicopter, and fly out the pick-up zone. Due to a bug in DCS world it cannot be chacked (for the moment) ' ..
'if the cargo is in our out of the zone and attached to your helicopter...', self.MSG.TIME, Mission.Name .. "/Stage", "Co-Pilot: Hook" )
end
function STAGE_SLINGLOAD_HOOK:Validate( Mission, Client, Task )
trace.f(self.ClassName)
--[[
for CargoID, CargoName in pairs( Task.CargoPrefixes ) do
env.info( CargoName )
if StaticObject.getByName( CargoName ):inAir() then
Task.CargoName = CargoName
Task.CargoID = CargoID
Client:Message( 'Co-Pilot: The Cargo has been successfully hooked onto the helicopter within the landing zone.', self.MSG.TIME, Mission.Name .. "/StageSuccess" )
break
end
end
if Task.CargoName then
if routines.IsStaticInZones( StaticObject.getByName( Task.CargoName ), Task.LandingZones[Task.CurrentLandingZoneID] ) then
else
-- Mission._Cargos[Task.CargoName].Status = CARGOSTATUS.LOADED
return 1
end
end
--]]
return 1
end
STAGE_SLINGLOAD_UNHOOK = {
ClassName = "STAGE_SLINGLOAD_UNHOOK",
MSG = { ID = "SlingLoadUnHook", TIME = 10 },
Name = "SlingLoadUnHook"
}
function STAGE_SLINGLOAD_UNHOOK:New()
trace.f(self.ClassName)
-- Arrange meta tables
local self = BASE:Inherit( self, STAGE:New() )
self.StageType = 'CLIENT'
return self
end
function STAGE_SLINGLOAD_UNHOOK:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'Deploy the Cargo in the Landing Zone and unhook the cargo, and fly out of the drop zone.', self.MSG.TIME, Mission.Name .. "/StageUnhook", "Co-Pilot: Unhook" )
end
function STAGE_SLINGLOAD_UNHOOK:Validate( Mission, Client, Task )
trace.f(self.ClassName)
--[[
for CargoID, CargoName in pairs( Task.CargoPrefixes ) do
if StaticObject.getByName( CargoName ):inAir() then
Task.CargoName = CargoName
Task.CargoID = CargoID
Client:Message( 'Co-Pilot: Drop the cargo within the landing zone and unhook.', self.MSG.TIME, Mission.Name .. "/Stage" )
break
end
end
if Task.CargoName then
if GE_CARGO[CargoID] == 4 then
if not StaticObject.getByName( Task.CargoName ):inAir() then
if routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones[Task.CurrentLandingZoneID] ) then
else
Client:Message( 'Co-Pilot: The Cargo is Dropped in the Landing Zone, and You have flown outside of the landing zone.', self.MSG.TIME, Mission.Name .. "/Stage" )
return 1
end
end
end
--]]
return 1
end
STAGEDONE = {
ClassName = "STAGEDONE",
MSG = { ID = "Done", TIME = 10 },
Name = "Done"
}
function STAGEDONE:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'AI'
return Child
end
function STAGEDONE:Execute( Mission, Client, Task )
trace.f(self.ClassName)
end
function STAGEDONE:Validate( Mission, Client, Task )
trace.f(self.ClassName)
Task:Done()
return 0
end
STAGEARRIVE = {
ClassName = "STAGEARRIVE",
MSG = { ID = "Arrive", TIME = 10 },
Name = "Arrive"
}
function STAGEARRIVE:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'CLIENT'
return Child
end
function STAGEARRIVE:Execute( Mission, Client, Task )
trace.f(self.ClassName)
Client:Message( 'We have arrived at ' .. Task.LandingZones[Task.CurrentLandingZoneID] .. ".", self.MSG.TIME, Mission.Name .. "/Stage", "Co-Pilot: Arrived" )
end
function STAGEARRIVE:Validate( Mission, Client, Task )
trace.f(self.ClassName)
Task.CurrentLandingZoneID = routines.IsUnitInZones( Client:ClientGroup():getUnits()[1], Task.LandingZones )
if ( Task.CurrentLandingZoneID ) then
else
return -1
end
return 1
end
STAGEGROUPSDESTROYED = {
ClassName = "STAGEGROUPSDESTROYED",
DestroyGroupSize = -1,
Frequency = STAGE.FREQUENCY.REPEAT,
MSG = { ID = "DestroyGroup", TIME = 10 },
Name = "GroupsDestroyed"
}
function STAGEGROUPSDESTROYED:New()
trace.f(self.ClassName)
-- Arrange meta tables
local Child = BASE:Inherit( self, STAGE:New() )
Child.StageType = 'AI'
return Child
end
--function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task )
--
-- Client:Message( 'Task: Still ' .. DestroyGroupSize .. " of " .. Task.DestroyGroupCount .. " " .. Task.DestroyGroupType .. " to be destroyed!", self.MSG.TIME, Mission.Name .. "/Stage" )
--
--end
function STAGEGROUPSDESTROYED:Validate( Mission, Client, Task )
trace.f(self.ClassName)
if Task.MissionTask:IsGoalReached() then
return 1
else
return 0
end
end
function STAGEGROUPSDESTROYED:Execute( Mission, Client, Task )
trace.f(self.ClassName)
trace.i( self.ClassName, { Task.ClassName, Task.Destroyed } )
--env.info( 'Event Table Task = ' .. tostring(Task) )
end
--[[
_TransportStage: Defines the different stages of which of transport missions can be in. This table is internal and is used to control the sequence of messages, actions and flow.
- _TransportStage.START
- _TransportStage.ROUTE
- _TransportStage.LAND
- _TransportStage.EXECUTE
- _TransportStage.DONE
- _TransportStage.REMOVE
--]]
_TransportStage = {
HOLD = "HOLD",
START = "START",
ROUTE = "ROUTE",
LANDING = "LANDING",
LANDED = "LANDED",
EXECUTING = "EXECUTING",
LOAD = "LOAD",
UNLOAD = "UNLOAD",
DONE = "DONE",
NEXT = "NEXT"
}
_TransportStageMsgTime = {
HOLD = 10,
START = 60,
ROUTE = 5,
LANDING = 10,
LANDED = 30,
EXECUTING = 30,
LOAD = 30,
UNLOAD = 30,
DONE = 30,
NEXT = 0
}
_TransportStageTime = {
HOLD = 10,
START = 5,
ROUTE = 5,
LANDING = 1,
LANDED = 1,
EXECUTING = 5,
LOAD = 5,
UNLOAD = 5,
DONE = 1,
NEXT = 0
}
_TransportStageAction = {
REPEAT = -1,
NONE = 0,
ONCE = 1
}

246
StatHandler.lua Normal file
View File

@ -0,0 +1,246 @@
--- Provides a logging of statistics in a running DCS Mission.
-- @script eStatHandler
Include.File( "Routines" )
--Handler table
local eStatHandler = {}
local _StatRunID
--Neccessary tables for string instead of integers
SETCoalition =
{
[1] = "red",
[2] = "blue",
}
SETGroupCat =
{
[1] = "AIRPLANE",
[2] = "HELICOPTER",
[3] = "GROUND",
[4] = "SHIP",
[5] = "STRUCTURE",
}
SETWeaponCatName =
{
[0] = "SHELL",
[1] = "MISSILE",
[2] = "ROCKET",
[3] = "BOMB",
}
wEvent = {
"S_EVENT_SHOT",
"S_EVENT_HIT",
"S_EVENT_TAKEOFF",
"S_EVENT_LAND",
"S_EVENT_CRASH",
"S_EVENT_EJECTION",
"S_EVENT_REFUELING",
"S_EVENT_DEAD",
"S_EVENT_PILOT_DEAD",
"S_EVENT_BASE_CAPTURED",
"S_EVENT_MISSION_START",
"S_EVENT_MISSION_END",
"S_EVENT_TOOK_CONTROL",
"S_EVENT_REFUELING_STOP",
"S_EVENT_BIRTH",
"S_EVENT_HUMAN_FAILURE",
"S_EVENT_ENGINE_STARTUP",
"S_EVENT_ENGINE_SHUTDOWN",
"S_EVENT_PLAYER_ENTER_UNIT",
"S_EVENT_PLAYER_LEAVE_UNIT",
"S_EVENT_PLAYER_COMMENT",
"S_EVENT_SHOOTING_START",
"S_EVENT_SHOOTING_END",
"S_EVENT_MAX",
}
statEventsTable = {}
function SecondsToClock(sSeconds)
local nSeconds = sSeconds
if nSeconds == 0 then
--return nil;
return "00:00:00";
else
nHours = string.format("%02.f", math.floor(nSeconds/3600));
nMins = string.format("%02.f", math.floor(nSeconds/60 - (nHours*60)));
nSecs = string.format("%02.f", math.floor(nSeconds - nHours*3600 - nMins *60));
return nHours..":"..nMins..":"..nSecs
end
end
function eStatHandler:onEvent(e)
local InitID_ = ""
local InitName = ""
local WorldEvent = wEvent[e.id]
local InitCoa = ""
local InitGroupCat = ""
local InitType = ""
local InitPlayer = ""
local eWeaponCat = ""
local eWeaponName = ""
local TargID_ = ""
local TargName = ""
local TargType = ""
local TargPlayer = ""
local TargCoa = ""
local TargGroupCat = ""
if e.initiator and Object.getCategory(e.initiator) == Object.Category.UNIT then
--Initiator variables
local InitGroup = e.initiator:getGroup()
InitID_ = e.initiator.id_
if e.initiator:getName() then
InitName = e.initiator:getName()
end
if InitGroup:getCoalition() then
InitCoa = SETCoalition[InitGroup:getCoalition()]
end
if InitGroup:getCategory() then
InitGroupCat = SETGroupCat[InitGroup:getCategory() + 1]
end
InitType = e.initiator:getTypeName()
--Get initiator player name or AI if NIL
if e.initiator:getPlayerName() == nil then
InitPlayer = "AI"
else
InitPlayer = e.initiator:getPlayerName()
end
else
if e.initiator then
local InitGroup = e.initiator:getGroup()
InitID_ = e.initiator.id_
if e.initiator:getName() then
InitName = e.initiator:getName()
end
InitCoa = SETCoalition[InitGroup:getCoalition()]
InitGroupCat = SETGroupCat[InitGroup:getCategory() + 1]
InitType = e.initiator:getTypeName()
--Get initiator player name or AI if NIL
if e.initiator:getPlayerName() == nil then
InitPlayer = "AI"
else
InitPlayer = e.initiator:getPlayerName()
end
end
end
--Weapon variables
if e.weapon == nil then
eWeaponCat = ""
eWeaponName = ""
else
local eWeaponDesc = e.weapon:getDesc()
eWeaponCat = SETWeaponCatName[eWeaponDesc.category]
eWeaponName = eWeaponDesc.displayName
end
--Target variables
if e.target == nil then
TargID_ = ""
TargName = ""
TargType = ""
TargPlayer = ""
TargCoa = ""
TargGroupCat = ""
elseif Object.getCategory(e.target) == Object.Category.UNIT then
local TargGroup = e.target:getGroup()
TargID_ = e.target.id_
if e.target:getName() then
TargName = e.target:getName()
end
TargType = e.target:getTypeName()
TargCoa = SETCoalition[TargGroup:getCoalition()]
TargGroupCat = SETGroupCat[TargGroup:getCategory() + 1]
--Get target player name or AI if NIL
if not e.target:getPlayerName() then
TargPlayer = "AI"
else
TargPlayer = e.target:getPlayerName()
end
else
TargType = e.target:getTypeName()
TargID_ = ""
TargName = ""
TargPlayer = ""
TargCoa = ""
TargGroupCat = ""
end
--write events to table
statEventsTable[#statEventsTable + 1] =
{
[1] = _StatRunID,
[2] = SecondsToClock(timer.getTime()),
[3] = WorldEvent,
[4] = InitID_,
[5] = InitName,
[6] = InitCoa,
[7] = InitGroupCat,
[8] = InitType,
[9] = InitPlayer,
[10] = eWeaponCat,
[11] = eWeaponName,
[12] = TargID_,
[13] = TargName,
[14] = TargCoa,
[15] = TargGroupCat,
[16] = TargType,
[17] = TargPlayer,
}
env.info( 'Event: ' .. _StatRunID .. '~ ' .. SecondsToClock(timer.getTime()) .. '~ ' .. WorldEvent .. '~ ' .. InitID_ .. '~ ' .. InitName .. '~ ' .. InitCoa .. '~ ' .. InitGroupCat .. '~ ' .. InitType .. '~ ' .. InitPlayer ..
'~ ' .. eWeaponCat .. '~ ' .. eWeaponName .. '~ ' .. TargID_ .. '~ ' .. TargName .. '~ ' .. TargCoa .. '~ ' .. TargGroupCat .. '~ ' .. TargType .. '~ ' .. TargPlayer )
end
do
local StatFile,err
function StatOpen()
local fdir = lfs.writedir() .. [[Logs\]] .. "Events_" .. os.date( "%Y-%m-%d_%H-%M-%S" ) .. ".csv"
StatFile,err = io.open(fdir,"w+")
if not StatFile then
local errmsg = 'Error: No Logs folder found in the User\\Saved Games\\DCS\\Logs directory...' .. 'Save_stat . sample: C:\\Users\\youname\\Saved Games\\DCS\\Logs'
trigger.action.outText(errmsg, 10)
return print(err)
end
StatFile:write("RunID~Time~Event~Initiator ID~Initiator Name~Initiator Coalition~Initiator Group Category~Initiator Type~Initiator Player~Weapon Category~Weapon Name~Target ID~Target Name~Target Coalition~Target Group Category~Target Type~Target Player\n")
_StatRunID = os.date("%y-%m-%d_%H-%M-%S")
routines.scheduleFunction( StatSave, { }, timer.getTime() + 1, 1)
end
function StatSave()
--write statistic information to file
for Index, eDetails in ipairs(statEventsTable) do
for eInfoName, eInfoData in ipairs(eDetails) do
StatFile:write(eInfoData.."~")
end
StatFile:write("\n")
end
statEventsTable = {}
end
function StatClose()
StatFile:close()
end
end
world.addEventHandler(eStatHandler)
StatOpen()

453
Task.lua Normal file
View File

@ -0,0 +1,453 @@
--- The TASK Classes define major end-to-end activities within a MISSION. The TASK Class is the Master Class to orchestrate these activities. From this class, many concrete TASK classes are inherited.
-- @classmod TASK
Include.File( "Routines" )
Include.File( "Base" )
Include.File( "Mission" )
Include.File( "Client" )
Include.File( "Stage" )
TASK = {
-- Defines the different signal types with a Task.
SIGNAL = {
COLOR = {
RED = { ID = 1, COLOR = trigger.smokeColor.Red, TEXT = "A red" },
GREEN = { ID = 2, COLOR = trigger.smokeColor.Green, TEXT = "A green" },
BLUE = { ID = 3, COLOR = trigger.smokeColor.Blue, TEXT = "A blue" },
WHITE = { ID = 4, COLOR = trigger.smokeColor.White, TEXT = "A white" },
ORANGE = { ID = 5, COLOR = trigger.smokeColor.Orange, TEXT = "An orange" }
},
TYPE = {
SMOKE = { ID = 1, TEXT = "smoke" },
FLARE = { ID = 2, TEXT = "flare" }
}
},
ClassName = "TASK",
Mission = {}, -- Owning mission of the Task
Name = '',
Stages = {},
Stage = {},
ActiveStage = 0,
TaskDone = false,
TaskFailed = false,
GoalTasks = {}
}
--- Instantiates a new TASK Base. Should never be used. Interface Class.
-- @treturn TASK
function TASK:New()
trace.f(self.ClassName)
local self = BASE:Inherit( self, BASE:New() )
-- assign Task default values during construction
self.TaskBriefing = "Task: No Task."
self.Time = timer.getTime()
self.ExecuteStage = _TransportExecuteStage.NONE
return self
end
function TASK:SetStage( StageSequenceIncrement )
trace.f(self.ClassName, { StageSequenceIncrement } )
local Valid = false
if StageSequenceIncrement ~= 0 then
self.ActiveStage = self.ActiveStage + StageSequenceIncrement
if 1 <= self.ActiveStage and self.ActiveStage <= #self.Stages then
self.Stage = self.Stages[self.ActiveStage]
trace.i( self.ClassName, { self.Stage.Name } )
self.Frequency = self.Stage.Frequency
Valid = true
else
Valid = false
env.info( "TASK:SetStage() self.ActiveStage is smaller or larger than self.Stages array. self.ActiveStage = " .. self.ActiveStage )
end
end
self.Time = timer.getTime()
return Valid
end
function TASK:Init()
trace.f(self.ClassName)
self.ActiveStage = 0
self:SetStage(1)
self.TaskDone = false
self.TaskFailed = false
end
--- Get progress of a TASK.
-- @treturn string GoalsText
function TASK:GetGoalProgress()
trace.f(self.ClassName)
local GoalsText = ""
for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do
local Goals = self:GetGoalCompletion( GoalVerb )
if Goals and Goals ~= "" then
Goals = '(' .. Goals .. ')'
else
Goals = '( - )'
end
GoalsText = GoalsText .. GoalVerb .. ': ' .. self:GetGoalCount(GoalVerb) .. ' goals ' .. Goals .. ' of ' .. self:GetGoalTotal(GoalVerb) .. ' goals completed (' .. self:GetGoalPercentage(GoalVerb) .. '%); '
end
if GoalsText == "" then
GoalsText = "( - )"
end
return GoalsText
end
--- Show progress of a TASK.
-- @tparam MISSION Mission Group structure describing the Mission.
-- @tparam CLIENT Client Group structure describing the Client.
function TASK:ShowGoalProgress( Mission, Client )
trace.f(self.ClassName)
local GoalsText = ""
for GoalVerb, GoalVerbData in pairs( self.GoalTasks ) do
if Mission:IsCompleted() then
else
local Goals = self:GetGoalCompletion( GoalVerb )
if Goals and Goals ~= "" then
else
Goals = "-"
end
GoalsText = GoalsText .. self:GetGoalProgress()
end
end
Client:Message( GoalsText, 10, "/TASKPROGRESS" .. self.ClassName, "Mission Command: Task Status", 30 )
end
--- Sets a TASK to status Done.
function TASK:Done()
trace.f(self.ClassName)
self.TaskDone = true
end
--- Returns if a TASK is done.
-- @treturn bool
function TASK:IsDone()
trace.f(self.ClassName)
return self.TaskDone
end
--- Sets a TASK to status failed.
function TASK:Failed()
trace.f(self.ClassName)
self.TaskFailed = true
end
--- Returns if a TASk has failed.
-- @return bool
function TASK:IsFailed()
trace.f(self.ClassName)
return self.TaskFailed
end
function TASK:Reset( Mission, Client )
trace.f(self.ClassName)
self.ExecuteStage = _TransportExecuteStage.NONE
end
--- Returns the Goals of a TASK
-- @treturn @table Goals
function TASK:GetGoals()
return self.GoalTasks
end
--- Returns if a TASK has Goal(s).
-- @tparam ?string GoalVerb is the name of the Goal of the TASK.
-- @treturn bool
function TASK:Goal( GoalVerb )
trace.f(self.ClassName)
if not GoalVerb then
GoalVerb = self.GoalVerb
end
if self.GoalTasks[GoalVerb] and self.GoalTasks[GoalVerb].GoalTotal > 0 then
return true
else
return false
end
end
--- Sets the total Goals to be achieved of the Goal Name
-- @tparam number GoalTotal is the number of times the GoalVerb needs to be achieved.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
function TASK:SetGoalTotal( GoalTotal, GoalVerb )
trace.f(self.ClassName, { GoalTotal, GoalVerb } )
if not GoalVerb then
GoalVerb = self.GoalVerb
end
self.GoalTasks[GoalVerb] = {}
self.GoalTasks[GoalVerb].Goals = {}
self.GoalTasks[GoalVerb].GoalTotal = GoalTotal
self.GoalTasks[GoalVerb].GoalCount = 0
return self
end
--- Gets the total of Goals to be achieved within the TASK of the GoalVerb.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
function TASK:GetGoalTotal( GoalVerb )
trace.f(self.ClassName)
if not GoalVerb then
GoalVerb = self.GoalVerb
end
if self:Goal( GoalVerb ) then
return self.GoalTasks[GoalVerb].GoalTotal
else
return 0
end
end
--- Sets the total of Goals currently achieved within the TASK of the GoalVerb.
-- @tparam number GoalCount is the total number of Goals achieved within the TASK.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
-- @treturn TASK
function TASK:SetGoalCount( GoalCount, GoalVerb )
trace.f(self.ClassName)
if not GoalVerb then
GoalVerb = self.GoalVerb
end
if self:Goal( GoalVerb) then
self.GoalTasks[GoalVerb].GoalCount = GoalCount
end
return self
end
--- Increments the total of Goals currently achieved within the TASK of the GoalVerb, with the given GoalCountIncrease.
-- @tparam number GoalCountIncrease is the number of new Goals achieved within the TASK.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
-- @treturn TASK
function TASK:IncreaseGoalCount( GoalCountIncrease, GoalVerb )
trace.f(self.ClassName)
if not GoalVerb then
GoalVerb = self.GoalVerb
end
if self:Goal( GoalVerb) then
self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalCountIncrease
end
return self
end
--- Gets the total of Goals currently achieved within the TASK of the GoalVerb.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
-- @treturn TASK
function TASK:GetGoalCount( GoalVerb )
trace.f(self.ClassName)
if not GoalVerb then
GoalVerb = self.GoalVerb
end
if self:Goal( GoalVerb ) then
return self.GoalTasks[GoalVerb].GoalCount
else
return 0
end
end
--- Gets the percentage of Goals currently achieved within the TASK of the GoalVerb.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
-- @treturn TASK
function TASK:GetGoalPercentage( GoalVerb )
trace.f(self.ClassName)
if not GoalVerb then
GoalVerb = self.GoalVerb
end
if self:Goal( GoalVerb ) then
return math.floor( self:GetGoalCount( GoalVerb ) / self:GetGoalTotal( GoalVerb ) * 100 + .5 )
else
return 100
end
end
--- Returns if all the Goals of the TASK were achieved.
-- @treturn bool
function TASK:IsGoalReached( )
trace.f(self.ClassName)
local GoalReached = true
for GoalVerb, Goals in pairs( self.GoalTasks ) do
trace.i( self.ClassName, { "GoalVerb", GoalVerb } )
if self:Goal( GoalVerb ) then
local GoalToDo = self:GetGoalTotal( GoalVerb ) - self:GetGoalCount( GoalVerb )
trace.i( self.ClassName, "GoalToDo = " .. GoalToDo )
if GoalToDo <= 0 then
else
GoalReached = false
break
end
else
break
end
end
return GoalReached
end
--- Adds an Additional Goal for the TASK to be achieved.
-- @tparam string GoalVerb is the name of the Goal of the TASK.
-- @tparam string GoalTask is a text describing the Goal of the TASK to be achieved.
-- @tparam number GoalIncrease is a number by which the Goal achievement is increasing.
function TASK:AddGoalCompletion( GoalVerb, GoalTask, GoalIncrease )
trace.f( self.ClassName, { GoalVerb, GoalTask, GoalIncrease } )
if self:Goal( GoalVerb ) then
self.GoalTasks[GoalVerb].Goals[#self.GoalTasks[GoalVerb].Goals+1] = GoalTask
self.GoalTasks[GoalVerb].GoalCount = self.GoalTasks[GoalVerb].GoalCount + GoalIncrease
end
return self
end
--- Returns if the additional Goal for the TASK was completed.
-- @tparam ?string GoalVerb is the name of the Goal of the TASK. If the GoalVerb is not given, then the default TASK Goals will be used.
-- @treturn string Goals
function TASK:GetGoalCompletion( GoalVerb )
trace.f( self.ClassName, { GoalVerb } )
if self:Goal( GoalVerb ) then
local Goals = ""
for GoalID, GoalName in pairs( self.GoalTasks[GoalVerb].Goals ) do Goals = Goals .. GoalName .. " + " end
return Goals:gsub(" + $", ""), self.GoalTasks[GoalVerb].GoalCount
end
end
function TASK.MenuAction( Parameter )
trace.menu("TASK","MenuAction")
trace.l( "TASK", "MenuAction", { Parameter } )
Parameter.ReferenceTask.ExecuteStage = _TransportExecuteStage.EXECUTING
Parameter.ReferenceTask.CargoName = Parameter.CargoName
end
function TASK:StageExecute()
trace.f(self.ClassName)
local Execute = false
if self.Frequency == STAGE.FREQUENCY.REPEAT then
Execute = true
elseif self.Frequency == STAGE.FREQUENCY.NONE then
Execute = false
elseif self.Frequency >= 0 then
Execute = true
self.Frequency = self.Frequency - 1
end
return Execute
end
--- Work function to set signal events within a TASK.
function TASK:AddSignal( SignalUnitNames, SignalType, SignalColor, SignalHeight )
trace.f(self.ClassName)
local Valid = true
if Valid then
if type( SignalUnitNames ) == "table" then
self.LandingZoneSignalUnitNames = SignalUnitNames
else
self.LandingZoneSignalUnitNames = { SignalUnitNames }
end
self.LandingZoneSignalType = SignalType
self.LandingZoneSignalColor = SignalColor
self.Signalled = false
if SignalHeight ~= nil then
self.LandingZoneSignalHeight = SignalHeight
else
self.LandingZoneSignalHeight = 0
end
if self.TaskBriefing then
self.TaskBriefing = self.TaskBriefing .. " " .. SignalColor.TEXT .. " " .. SignalType.TEXT .. " will be fired when entering the landing zone."
end
end
return Valid
end
--- When the CLIENT is approaching the landing zone, a RED SMOKE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddSmokeRed( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.RED, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a GREEN SMOKE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddSmokeGreen( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.GREEN, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a BLUE SMOKE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddSmokeBlue( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.BLUE, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a WHITE SMOKE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddSmokeWhite( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.WHITE, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, an ORANGE SMOKE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddSmokeOrange( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.SMOKE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a RED FLARE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddFlareRed( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.RED, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a GREEN FLARE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddFlareGreen( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.GREEN, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a BLUE FLARE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddFlareBlue( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.BLUE, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, a WHITE FLARE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddFlareWhite( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.WHITE, SignalHeight )
end
--- When the CLIENT is approaching the landing zone, an ORANGE FLARE will be fired by an optional SignalUnitNames.
-- @tparam table|string SignalUnitNames Name of the Group that will fire the signal. If this parameter is NIL, the signal will be fired from the center of the landing zone.
-- @tparam number SignalHeight Altitude that the Signal should be fired...
function TASK:AddFlareOrange( SignalUnitNames, SignalHeight )
trace.f(self.ClassName)
self:AddSignal( SignalUnitNames, TASK.SIGNAL.TYPE.FLARE, TASK.SIGNAL.COLOR.ORANGE, SignalHeight )
end

261
Trace.lua Normal file
View File

@ -0,0 +1,261 @@
--- Tracing functions...
-- @module trace
-- @author Flightcontrol
trace = {}
trace.names = {}
trace.scheduledfunction = ""
trace.names.all = false
trace.names.New = false
trace.names.Inherit = false
trace.names.do_scheduled_functions = false
trace.names.main = false
trace.names.Meta = false
trace.names.mistdisplayV3 = false
trace.names.f = false
trace.names.Spawn = false
trace.names.SpawnTrack = false
trace.names.SpawnGroupAdd = false
trace.names.SpawnInfantry = false
trace.names.SpawnPrepare = false
trace.names.SpawnInit = false
trace.names.SpawnInitSchedule = false
trace.names.SpawnFromCarrier = false
trace.names.SpawnGroup = false
trace.names.SpawnMissionGroup = false
trace.names.SpawnScheduled = false
trace.names.SpawnInZone = false
trace.names.Spawn = false
trace.names.ShowCargo = false
trace.names.AddCargo = true
trace.names.RemoveCargo = false
trace.names.MenuAction = false
trace.names.DeploySA6TroopsGoal = false
trace.names.AddEvent = false
trace.names.onEvent = false
trace.names.EventShot = false
trace.names.EventDead = false
trace.names.EventFunction = false
trace.names.ShowGoalProgress = false
trace.names.ReportGoalProgress = false
trace.names.ProgressTrigger = false
trace.names.IsGoalReached = false
trace.names.Validate = false
trace.names.Execute = false
trace.names.EnableEvents = false
trace.names.DisableEvents = false
trace.names.IsCompleted = false
trace.names.GetGoalCount = false
trace.names.GetGoalTotal = false
trace.names.SetGoalTotal = false
trace.names.GetGoalPercentage = false
trace.names.deepCopy = false
trace.names._Scheduler = false
trace.names._GetTemplate = false
trace.names.FollowPlayers = false
trace.names.AddPlayerFromUnit = false
trace.names.FromCarrier = false
trace.cache = {}
trace.tracefunction = function( functionname )
if functionname then
if trace.names[functionname] then
return true
else
return false
end
else
return false
end
end
trace.f = function(object, parameters)
local info = debug.getinfo( 2, "nl" )
if trace.names.all or trace.tracefunction( info.name ) then
local objecttrace = ""
if object then
objecttrace = object
end
trace.nametrace = ""
if info.name then
trace.nametrace = info.name
end
local parameterstrace = "()"
if parameters then
parameterstrace = "( " .. routines.utils.oneLineSerialize( parameters ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "F", objecttrace, "function " .. trace.nametrace .. parameterstrace ) )
end
end
trace.scheduled = function(object, func, parameters)
local info = debug.getinfo( 2, "l" )
if trace.names.all or trace.tracefunction( func ) then
local objecttrace = ""
if object then
objecttrace = object
end
trace.nametrace = ""
if func then
trace.nametrace = func
end
trace.scheduledfunction = trace.nametrace
local parameterstrace = "()"
if parameters then
parameterstrace = "( " .. routines.utils.oneLineSerialize( parameters ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "S", objecttrace, "function " .. trace.nametrace .. parameterstrace ) )
end
end
trace.s = function(object, parameters)
local info = debug.getinfo( 3, "nl" )
if trace.names.all or trace.tracefunction( info.name ) then
local objecttrace = ""
if object then
objecttrace = object
end
trace.nametrace = ""
if info.name then
trace.nametrace = info.name
end
trace.scheduledfunction = trace.nametrace
local parameterstrace = "()"
if parameters then
parameterstrace = "( " .. routines.utils.oneLineSerialize( parameters ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "S", objecttrace, "scheduled " .. trace.nametrace .. parameterstrace ) )
end
end
trace.si = function(object, variable)
local info = debug.getinfo( 3, "nl" )
if info.name ~= trace.nametrace then
trace.nametrace = info.name
end
if trace.names.all or trace.tracefunction( trace.nametrace ) then
local objecttrace = ""
if object then
objecttrace = object
end
local variabletrace = ""
if variable then
variabletrace = "( " .. routines.utils.oneLineSerialize( variable ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "S", objecttrace, trace.nametrace .. variabletrace) )
end
end
trace.l = function(object, func, variable)
local info = debug.getinfo( 2, "l" )
if trace.names.all or trace.tracefunction( func ) then
local objecttrace = ""
if object then
objecttrace = object
end
trace.nametrace = ""
if func then
trace.nametrace = func
end
local variabletrace = ""
if variable then
variabletrace = "( " .. routines.utils.oneLineSerialize( variable ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "L", objecttrace, trace.nametrace .. variabletrace) )
end
end
trace.menu = function(object, func)
if trace.names.all then
local objecttrace = ""
if object then
objecttrace = object
end
trace.nametrace = ""
if func then
trace.nametrace = func
end
env.info( string.format( "%6d/%1s:%20s.%s" , 0, "M", objecttrace, trace.nametrace .. "()" ) )
end
end
trace.r = function(object, step, variable)
local info = debug.getinfo( 2, "nl" )
if info.name ~= trace.nametrace then
trace.nametrace = info.name
end
if trace.names.all or trace.tracefunction( trace.nametrace ) then
local objecttrace = ""
if object then
objecttrace = object
end
local steptrace = ""
if step then
steptrace = "< " .. step .. " >"
end
local variabletrace = ""
if variable then
variabletrace = "( " .. routines.utils.oneLineSerialize( variable ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "R", objecttrace, "return " .. trace.nametrace .. variabletrace ) )
end
end
trace.e = function(object)
local info = debug.getinfo( 2, "nl" )
if info.name ~= trace.nametrace then
trace.nametrace = info.name
end
if trace.names.all or trace.tracefunction( trace.nametrace ) then
local objecttrace = ""
if object then
objecttrace = object
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "E", objecttrace, "end " .. trace.nametrace ) )
end
end
trace.i = function(object, variable)
local info = debug.getinfo( 2, "nl" )
if info.name ~= trace.nametrace then
trace.nametrace = info.name
end
if trace.names.all or trace.tracefunction( trace.nametrace ) then
local objecttrace = ""
if object then
objecttrace = object
end
local variabletrace = ""
if variable then
variabletrace = "( " .. routines.utils.oneLineSerialize( variable ) .. " )"
end
env.info( string.format( "%6d/%1s:%20s.%s" , info.currentline, "I", objecttrace, trace.nametrace .. variabletrace) )
end
end

10
config.ld Normal file
View File

@ -0,0 +1,10 @@
project = 'MOOSE'
title = "MOOSE"
description = 'Mission Object Oriented Scripting Environment'
full_description = [[
A Scripting Framework for DCS World, to script quickly missions.]]
no_summary = false
no_return_or_parms = false
format = 'markdown'
colon = true
topics = "manual.md"

2
ldoc_errors.txt Normal file
View File

@ -0,0 +1,2 @@
d:\my dcs missions\scripts\moose\pickuptask.lua:5: ?: unknown tag: 'parent' nil
d:\my dcs missions\scripts\moose\message.lua:98: MESSAGE:ToCoalition: module not found: coalition coalition.side