From db2cc49b85afe965c001856476f11f30bd03913e Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Tue, 10 May 2016 20:01:45 +0200 Subject: [PATCH 01/11] Work in progress --- Moose/Client.lua | 4 ++-- Moose/Database.lua | 52 +++++++++++++++++++++------------------- Moose/Group.lua | 6 ++--- Moose/MissileTrainer.lua | 2 +- Moose/Routines.lua | 2 +- Moose/Sead.lua | 2 +- Moose/Spawn.lua | 2 +- 7 files changed, 36 insertions(+), 34 deletions(-) diff --git a/Moose/Client.lua b/Moose/Client.lua index bc444e5c5..ede7507ea 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -196,12 +196,12 @@ function CLIENT:GetDCSGroup() -- Now we need to resolve the bugs in DCS 1.5 ... -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) self:T3( "Bug 1.5 logic" ) - local ClientUnits = _DATABASE.Groups[self.ClientName].Units + local ClientUnits = _DATABASE.Templates.Groups[self.ClientName].Units self:T3( { ClientUnits[1].name, env.getValueDictByKey(ClientUnits[1].name) } ) for ClientUnitID, ClientUnitData in pairs( ClientUnits ) do self:T3( { tonumber(UnitData:getID()), ClientUnitData.unitId } ) if tonumber(UnitData:getID()) == ClientUnitData.unitId then - local ClientGroupTemplate = _DATABASE.Groups[self.ClientName].Template + local ClientGroupTemplate = _DATABASE.Templates.Groups[self.ClientName].Template self.ClientID = ClientGroupTemplate.groupId self.ClientGroupUnit = UnitData self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) diff --git a/Moose/Database.lua b/Moose/Database.lua index 631bd665b..02d745131 100644 --- a/Moose/Database.lua +++ b/Moose/Database.lua @@ -66,6 +66,7 @@ Include.File( "Routines" ) Include.File( "Base" ) Include.File( "Menu" ) Include.File( "Group" ) +Include.File( "Unit" ) Include.File( "Event" ) --- DATABASE class @@ -73,6 +74,7 @@ Include.File( "Event" ) -- @extends Base#BASE DATABASE = { ClassName = "DATABASE", + Templates = {}, DCSUnits = {}, DCSUnitsAlive = {}, Units = {}, @@ -260,7 +262,7 @@ function DATABASE:FilterStart() -- OK, we have a _DATABASE -- Now use the different filters to build the set. -- We first take ALL of the Units of the _DATABASE. - for UnitRegistrationID, UnitRegistration in pairs( _DATABASE.Units ) do + for UnitRegistrationID, UnitRegistration in pairs( _DATABASE.Templates.Units ) do self:T( UnitRegistration ) local DCSUnit = Unit.getByName( UnitRegistration.UnitName ) if self:_IsIncludeDCSUnit( DCSUnit ) then @@ -318,7 +320,7 @@ end function DATABASE:SetStatusGroup( GroupName, Status ) self:F( Status ) - self.Groups[GroupName].Status = Status + self.Templates.Groups[GroupName].Status = Status end @@ -326,8 +328,8 @@ end function DATABASE:GetStatusGroup( GroupName ) self:F( Status ) - if self.Groups[GroupName] then - return self.Groups[GroupName].Status + if self.Templates.Groups[GroupName] then + return self.Templates.Groups[GroupName].Status else return "" end @@ -341,9 +343,9 @@ function DATABASE:_RegisterGroup( GroupTemplate ) local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name) - if not self.Groups[GroupTemplateName] then - self.Groups[GroupTemplateName] = {} - self.Groups[GroupTemplateName].Status = nil + if not self.Templates.Groups[GroupTemplateName] then + self.Templates.Groups[GroupTemplateName] = {} + self.Templates.Groups[GroupTemplateName].Status = nil end -- Delete the spans from the route, it is not needed and takes memory. @@ -351,28 +353,28 @@ function DATABASE:_RegisterGroup( GroupTemplate ) GroupTemplate.route.spans = nil end - self.Groups[GroupTemplateName].GroupName = GroupTemplateName - self.Groups[GroupTemplateName].Template = GroupTemplate - self.Groups[GroupTemplateName].groupId = GroupTemplate.groupId - self.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units - self.Groups[GroupTemplateName].Units = GroupTemplate.units + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName + self.Templates.Groups[GroupTemplateName].Template = GroupTemplate + self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId + self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units + self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units - self:T( { "Group", self.Groups[GroupTemplateName].GroupName, self.Groups[GroupTemplateName].UnitCount } ) + self:T( { "Group", self.Templates.Groups[GroupTemplateName].GroupName, self.Templates.Groups[GroupTemplateName].UnitCount } ) - for unit_num, UnitTemplate in pairs(GroupTemplate.units) do + 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 + self.Templates.Units[UnitTemplateName] = {} + self.Templates.Units[UnitTemplateName].UnitName = UnitTemplateName + self.Templates.Units[UnitTemplateName].Template = UnitTemplate + self.Templates.Units[UnitTemplateName].GroupName = GroupTemplateName + self.Templates.Units[UnitTemplateName].GroupTemplate = GroupTemplate + self.Templates.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 + self.Templates.ClientsByName[UnitTemplateName] = UnitTemplate + self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - self:E( { "Unit", self.Units[UnitTemplateName].UnitName } ) + self:E( { "Unit", self.Templates.Units[UnitTemplateName].UnitName } ) end end @@ -385,7 +387,7 @@ function DATABASE:_EventOnBirth( Event ) if Event.IniDCSUnit then if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then self.DCSUnits[Event.IniDCSUnitName] = Event.IniDCSUnit - self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit + self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit end end end @@ -439,7 +441,7 @@ function DATABASE:ForEachAliveUnit( IteratorFunction, ... ) end end - timer.scheduleFunction( Schedule, {}, timer.getTime() + 1 ) + timer.scheduleFunction( Schedule, {}, timer.getTime() + 0.001 ) return self end diff --git a/Moose/Group.lua b/Moose/Group.lua index e93f50b69..a886d75b3 100644 --- a/Moose/Group.lua +++ b/Moose/Group.lua @@ -1084,7 +1084,7 @@ end function GROUP:GetTaskMission() self:F( self.GroupName ) - return routines.utils.deepCopy( _DATABASE.Groups[self.GroupName].Template ) + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) end --- Return the mission route of the group. @@ -1093,7 +1093,7 @@ end function GROUP:GetTaskRoute() self:F( self.GroupName ) - return routines.utils.deepCopy( _DATABASE.Groups[self.GroupName].Template.route.points ) + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) end --- Return the route of a group by using the @{Database#DATABASE} class. @@ -1117,7 +1117,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius ) self:T( { GroupName } ) - local Template = _DATABASE.Groups[GroupName].Template + local Template = _DATABASE.Templates.Groups[GroupName].Template if Template then if not Begin then diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index e805103b4..624503021 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -47,7 +47,7 @@ function MISSILETRAINER:_EventShot( Event ) local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) local TrainerTargetDCSGroup = TrainerTargetDCSUnit:getGroup() local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName() - local TrainerTargetSkill = _DATABASE.Units[TrainerTargetDCSUnitName].Template.skill + local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill self:T( TrainerTargetSkill ) diff --git a/Moose/Routines.lua b/Moose/Routines.lua index 059f60544..834d2d5b3 100644 --- a/Moose/Routines.lua +++ b/Moose/Routines.lua @@ -1737,7 +1737,7 @@ function routines.getGroupRoute(groupIdent, task) -- same as getGroupPoints bu -- refactor to search by groupId and allow groupId and groupName as inputs local gpId = groupIdent if type(groupIdent) == 'string' and not tonumber(groupIdent) then - gpId = _DATABASE.Groups[groupIdent].groupId + gpId = _DATABASE.Templates.Groups[groupIdent].groupId end for coa_name, coa_data in pairs(env.mission.coalition) do diff --git a/Moose/Sead.lua b/Moose/Sead.lua index d89c47cf0..dd47ff2e5 100644 --- a/Moose/Sead.lua +++ b/Moose/Sead.lua @@ -67,7 +67,7 @@ function SEAD:EventShot( Event ) local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimgroupName = _targetMimgroup:getName() local _targetMimcont= _targetMimgroup:getController() - local _targetskill = _DATABASE.Units[_targetMimname].Template.skill + local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill self:T( self.SEADGroupPrefixes ) self:T( _targetMimgroupName ) local SEADGroupFound = false diff --git a/Moose/Spawn.lua b/Moose/Spawn.lua index 364fad3f9..afe13cae3 100644 --- a/Moose/Spawn.lua +++ b/Moose/Spawn.lua @@ -954,7 +954,7 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) local SpawnTemplate = nil - SpawnTemplate = routines.utils.deepCopy( _DATABASE.Groups[SpawnTemplatePrefix].Template ) + SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) if SpawnTemplate == nil then error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) From 4d40f28cf82b29c30770249f496cca3401203c7b Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Wed, 11 May 2016 14:19:46 +0200 Subject: [PATCH 02/11] Revised client and database logic. Still work in progress. --- Moose/Client.lua | 122 ++++----- Moose/Database.lua | 259 ++++++++++++++++-- Moose/Event.lua | 27 ++ Moose/MissileTrainer.lua | 55 +++- .../Moose_Test_MISSILETRAINER.miz | Bin 114353 -> 114424 bytes 5 files changed, 368 insertions(+), 95 deletions(-) diff --git a/Moose/Client.lua b/Moose/Client.lua index ede7507ea..00315964c 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -51,7 +51,7 @@ function CLIENT:New( ClientName, ClientBriefing ) local self = BASE:Inherit( self, BASE:New() ) self:F( ClientName, ClientBriefing ) - self.ClientName = ClientName + self.ClientName = ClientName self:AddBriefing( ClientBriefing ) self.MessageSwitch = true @@ -132,9 +132,9 @@ end function CLIENT:IsAlive() self:F( self.ClientName ) - local ClientDCSGroup = self:GetDCSGroup() + local ClientUnit = Unit.getByName( self.ClientName ) - if ClientDCSGroup then + if ClientUnit and ClientUnit:isExist() then self:T("true") return true end @@ -176,6 +176,8 @@ function CLIENT:GetDCSGroup() -- return nil -- end + local ClientUnit = Unit.getByName( self.ClientName ) + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } for CoalitionId, CoalitionData in pairs( CoalitionsData ) do self:T3( { "CoalitionData:", CoalitionData } ) @@ -183,49 +185,49 @@ function CLIENT:GetDCSGroup() self:T3( { "UnitData:", UnitData } ) if UnitData and UnitData:isExist() then - local ClientGroup = Group.getByName( self.ClientName ) - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientUnits = _DATABASE.Templates.Groups[self.ClientName].Units - self:T3( { ClientUnits[1].name, env.getValueDictByKey(ClientUnits[1].name) } ) - for ClientUnitID, ClientUnitData in pairs( ClientUnits ) do - self:T3( { tonumber(UnitData:getID()), ClientUnitData.unitId } ) - if tonumber(UnitData:getID()) == ClientUnitData.unitId then - local ClientGroupTemplate = _DATABASE.Templates.Groups[self.ClientName].Template - self.ClientID = ClientGroupTemplate.groupId - self.ClientGroupUnit = UnitData - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - end - end --- else --- error( "Client " .. self.ClientName .. " not found!" ) - end + --self:E(self.ClientName) + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() then + if ClientGroup:getID() == UnitData:getGroup():getID() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + self.ClientGroupID = ClientGroup:getID() + self.ClientGroupName = ClientGroup:getName() + return ClientGroup + end + else + -- Now we need to resolve the bugs in DCS 1.5 ... + -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) + self:T3( "Bug 1.5 logic" ) + local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate + self.ClientGroupID = ClientGroupTemplate.groupId + self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName + self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) + return ClientGroup + end + -- else + -- error( "Client " .. self.ClientName .. " not found!" ) + end + end end end end -- For non player clients - local ClientGroup = Group.getByName( self.ClientName ) - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + return ClientGroup + end + end + end self.ClientGroupID = nil self.ClientGroupUnit = nil @@ -242,14 +244,9 @@ function CLIENT:GetClientGroupID() if not self.ClientGroupID then local ClientGroup = self:GetDCSGroup() - if ClientGroup and ClientGroup:isExist() then - self.ClientGroupID = ClientGroup:getID() - else - self.ClientGroupID = self.ClientID - end end - self:T( self.ClientGroupID ) + self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() return self.ClientGroupID end @@ -261,14 +258,9 @@ function CLIENT:GetClientGroupName() if not self.ClientGroupName then local ClientGroup = self:GetDCSGroup() - if ClientGroup and ClientGroup:isExist() then - self.ClientGroupName = ClientGroup:getName() - else - self.ClientGroupName = self.ClientName - end end - self:T( self.ClientGroupName ) + self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() return self.ClientGroupName end @@ -276,14 +268,15 @@ end -- @param #CLIENT self -- @return Unit#UNIT function CLIENT:GetClientGroupUnit() - self:F() + self:F2() - local ClientGroup = self:GetDCSGroup() - - if ClientGroup and ClientGroup:isExist() then - return UNIT:New( ClientGroup:getUnit(1) ) - else - return UNIT:New( self.ClientGroupUnit ) + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + self:T( self.ClientDCSUnit ) + if ClientDCSUnit and ClientDCSUnit:isExist() then + local ClientUnit = _DATABASE.Units[ self.ClientName ] + self:T2( ClientUnit ) + return ClientUnit end end @@ -293,12 +286,11 @@ end function CLIENT:GetClientGroupDCSUnit() self:F2() - local ClientGroup = self:GetDCSGroup() + local ClientDCSUnit = Unit.getByName( self.ClientName ) - if ClientGroup and ClientGroup:isExist() then - return ClientGroup:getUnit(1) - else - return self.ClientGroupUnit + if ClientDCSUnit and ClientDCSUnit:isExist() then + self:T2( ClientDCSUnit ) + return ClientDCSUnit end end diff --git a/Moose/Database.lua b/Moose/Database.lua index 02d745131..7b1f216df 100644 --- a/Moose/Database.lua +++ b/Moose/Database.lua @@ -68,25 +68,33 @@ Include.File( "Menu" ) Include.File( "Group" ) Include.File( "Unit" ) Include.File( "Event" ) +Include.File( "Client" ) --- DATABASE class -- @type DATABASE -- @extends Base#BASE DATABASE = { ClassName = "DATABASE", - Templates = {}, + Templates = { + Units = {}, + Groups = {}, + ClientsByName = {}, + ClientsByID = {}, + }, DCSUnits = {}, DCSUnitsAlive = {}, - Units = {}, - Groups = {}, DCSGroups = {}, DCSGroupsAlive = {}, + Units = {}, + UnitsAlive = {}, + Groups = {}, + GroupsAlive = {}, NavPoints = {}, Statics = {}, Players = {}, - AlivePlayers = {}, - ClientsByName = {}, - ClientsByID = {}, + PlayersAlive = {}, + Clients = {}, + ClientsAlive = {}, Filter = { Coalitions = nil, Categories = nil, @@ -142,6 +150,14 @@ function DATABASE:New() _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) + + -- Add database with registered clients and already alive players + + -- Follow alive players and clients + _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) + + return self end @@ -262,16 +278,43 @@ function DATABASE:FilterStart() -- OK, we have a _DATABASE -- Now use the different filters to build the set. -- We first take ALL of the Units of the _DATABASE. - for UnitRegistrationID, UnitRegistration in pairs( _DATABASE.Templates.Units ) do - self:T( UnitRegistration ) - local DCSUnit = Unit.getByName( UnitRegistration.UnitName ) + + self:E( { "Adding Database Datapoints with filters" } ) + for DCSUnitName, DCSUnit in pairs( _DATABASE.DCSUnits ) do + if self:_IsIncludeDCSUnit( DCSUnit ) then - self.DCSUnits[DCSUnit:getName()] = DCSUnit - end - if self:_IsAliveDCSUnit( DCSUnit ) then - self.DCSUnitsAlive[DCSUnit:getName()] = DCSUnit + + self:E( { "Adding Unit:", DCSUnitName } ) + self.DCSUnits[DCSUnitName] = _DATABASE.DCSUnits[DCSUnitName] + self.Units[DCSUnitName] = _DATABASE.Units[DCSUnitName] + + if _DATABASE.DCSUnitsAlive[DCSUnitName] then + self.DCSUnitsAlive[DCSUnitName] = _DATABASE.DCSUnitsAlive[DCSUnitName] + self.UnitsAlive[DCSUnitName] = _DATABASE.UnitsAlive[DCSUnitName] + end + end end + + for DCSGroupName, DCSGroup in pairs( _DATABASE.DCSGroups ) do + + --if self:_IsIncludeDCSGroup( DCSGroup ) then + self:E( { "Adding Group:", DCSGroupName } ) + self.DCSGroups[DCSGroupName] = _DATABASE.DCSGroups[DCSGroupName] + self.Groups[DCSGroupName] = _DATABASE.Groups[DCSGroupName] + --end + + if _DATABASE.DCSGroupsAlive[DCSGroupName] then + self.DCSGroupsAlive[DCSGroupName] = _DATABASE.DCSGroupsAlive[DCSGroupName] + self.GroupsAlive[DCSGroupName] = _DATABASE.GroupsAlive[DCSGroupName] + end + end + + for DCSUnitName, Client in pairs( _DATABASE.Clients ) do + self:E( { "Adding Client for Unit:", DCSUnitName } ) + self.Clients[DCSUnitName] = _DATABASE.Clients[DCSUnitName] + end + else self:E( "There is a structural error in MOOSE. No _DATABASE has been defined! Cannot build this custom DATABASE." ) end @@ -378,6 +421,77 @@ function DATABASE:_RegisterGroup( GroupTemplate ) end end +--- Private method that registers all alive players in the mission. +-- @param #DATABASE self +-- @return #DATABASE self +function DATABASE:_RegisterPlayers() + + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + for UnitId, UnitData in pairs( CoalitionData ) do + self:T3( { "UnitData:", UnitData } ) + if UnitData and UnitData:isExist() then + local UnitName = UnitData:getName() + if not self.PlayersAlive[UnitName] then + self:E( { "Add player for unit:", UnitName, UnitData:getPlayerName() } ) + self.PlayersAlive[UnitName] = UnitData:getPlayerName() + end + end + end + end + + return self +end + +--- Private method that registers all datapoints within in the mission. +-- @param #DATABASE self +-- @return #DATABASE self +function DATABASE:_RegisterDatabase() + + local CoalitionsData = { AlivePlayersRed = coalition.getGroups( coalition.side.RED ), AlivePlayersBlue = coalition.getGroups( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + for DCSGroupId, DCSGroup in pairs( CoalitionData ) do + + local DCSGroupName = DCSGroup:getName() + + self:E( { "Register Group:", DCSGroup, DCSGroupName } ) + self.DCSGroups[DCSGroupName] = DCSGroup + self.Groups[DCSGroupName] = GROUP:New( DCSGroup ) + + if self:_IsAliveDCSGroup(DCSGroup) then + self:E( { "Register Alive Group:", DCSGroup, DCSGroupName } ) + self.DCSGroupsAlive[DCSGroupName] = DCSGroup + self.GroupsAlive[DCSGroupName] = self.Groups[DCSGroupName] + end + + for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do + + local DCSUnitName = DCSUnit:getName() + self:E( { "Register Unit:", DCSUnit, DCSUnitName } ) + + self.DCSUnits[DCSUnitName] = DCSUnit + self.Units[DCSUnitName] = UNIT:New( DCSUnit ) + + if self:_IsAliveDCSUnit(DCSUnit) then + self:E( { "Register Alive Unit:", DCSUnit, DCSUnitName } ) + self.DCSUnitsAlive[DCSUnitName] = DCSUnit + self.UnitsAlive[DCSUnitName] = self.Units[DCSUnitName] + end + + if self.Templates.ClientsByName[DCSUnitName] then + self.Clients[DCSUnitName] = CLIENT:New( DCSUnitName ) + end + + end + end + end + + return self +end + + +--- Events + --- Handles the OnBirth event for the alive units set. -- @param #DATABASE self -- @param Event#EVENTDATA Event @@ -388,6 +502,13 @@ function DATABASE:_EventOnBirth( Event ) if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then self.DCSUnits[Event.IniDCSUnitName] = Event.IniDCSUnit self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit + self.Units[Event.IniDCSUnitName] = UNIT:New( Event.IniDCSUnit ) + + if not self.DCSGroups[Event.IniDCSGroupName] then + self.DCSGroups[Event.IniDCSGroupName] = Event.IniDCSGroupName + self.DCSGroupsAlive[Event.IniDCSGroupName] = Event.IniDCSGroupName + self.Groups[Event.IniDCSGroupName] = GROUP:New( Event.IniDCSGroup ) + end end end end @@ -406,18 +527,54 @@ function DATABASE:_EventOnDeadOrCrash( Event ) end end ---- Interate the DATABASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. +--- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive unit in the database. The function needs to accept a UNIT parameter. +-- @param Event#EVENTDATA Event +function DATABASE:_EventOnPlayerEnterUnit( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then + if not self.PlayersAlive[Event.IniDCSUnitName] then + self:E( { "Add player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) + self.PlayersAlive[Event.IniDCSUnitName] = Event.IniDCSUnit:getPlayerName() + self.ClientsAlive[Event.IniDCSUnitName] = _DATABASE.Clients[ Event.IniDCSUnitName ] + end + end + end +end + +--- Handles the OnPlayerLeaveUnit event to clean the active players table. +-- @param #DATABASE self +-- @param Event#EVENTDATA Event +function DATABASE:_EventOnPlayerLeaveUnit( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then + if self.PlayersAlive[Event.IniDCSUnitName] then + self:E( { "Cleaning player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) + self.PlayersAlive[Event.IniDCSUnitName] = nil + self.ClientsAlive[Event.IniDCSUnitName] = nil + end + end + end +end + +--- Iterators + +--- Interate the DATABASE and call an interator function for the given set, providing the Object for each element within the set and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. -- @return #DATABASE self -function DATABASE:ForEachAliveUnit( IteratorFunction, ... ) +function DATABASE:ForEach( IteratorFunction, arg, Set ) self:F( arg ) local function CoRoutine() local Count = 0 - for DCSUnitID, DCSUnit in pairs( self.DCSUnitsAlive ) do - self:T2( DCSUnit ) - IteratorFunction( DCSUnit, unpack( arg ) ) + for ObjectID, Object in pairs( Set ) do + self:T2( Object ) + IteratorFunction( Object, unpack( arg ) ) Count = Count + 1 if Count % 10 == 0 then coroutine.yield( false ) @@ -437,15 +594,56 @@ function DATABASE:ForEachAliveUnit( IteratorFunction, ... ) error( res ) end if res == false then - timer.scheduleFunction( Schedule, {}, timer.getTime() + 0.001 ) + return true -- resume next time the loop end + + return false end - timer.scheduleFunction( Schedule, {}, timer.getTime() + 0.001 ) + local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) return self end + +--- Interate the DATABASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive unit in the database. The function needs to accept a UNIT parameter. +-- @return #DATABASE self +function DATABASE:ForEachDCSUnitAlive( IteratorFunction, ... ) + self:F( arg ) + + self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) + + return self +end + +--- Interate the DATABASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a UNIT parameter. +-- @return #DATABASE self +function DATABASE:ForEachPlayer( IteratorFunction, ... ) + self:F( arg ) + + self:ForEach( IteratorFunction, arg, self.PlayersAlive ) + + return self +end + + +--- Interate the DATABASE and call an interator function for each client, providing the Client to the function and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter. +-- @return #DATABASE self +function DATABASE:ForEachClient( IteratorFunction, ... ) + self:F( arg ) + + self:ForEach( IteratorFunction, arg, self.Clients ) + + return self +end + + function DATABASE:ScanEnvironment() self:F() @@ -509,6 +707,9 @@ function DATABASE:ScanEnvironment() 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:_RegisterDatabase() + self:_RegisterPlayers() + return self end @@ -596,6 +797,22 @@ function DATABASE:_IsAliveDCSUnit( DCSUnit ) return DCSUnitAlive end +--- +-- @param #DATABASE self +-- @param DCSGroup#Group DCSGroup +-- @return #DATABASE self +function DATABASE:_IsAliveDCSGroup( DCSGroup ) + self:F( DCSGroup ) + local DCSGroupAlive = false + if DCSGroup and DCSGroup:isExist() then + if self.DCSGroups[DCSGroup:getName()] then + DCSGroupAlive = true + end + end + self:T( DCSGroupAlive ) + return DCSGroupAlive +end + --- Traces the current database contents in the log ... (for debug reasons). -- @param #DATABASE self diff --git a/Moose/Event.lua b/Moose/Event.lua index a18d4d22b..f7c2a0649 100644 --- a/Moose/Event.lua +++ b/Moose/Event.lua @@ -437,6 +437,32 @@ function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) return self end +--- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. +-- @param #EVENT self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @return #EVENT +function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + self:F() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self +end + +--- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. +-- @param #EVENT self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @return #EVENT +function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + self:F() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self +end + function EVENT:onEvent( Event ) @@ -468,6 +494,7 @@ function EVENT:onEvent( Event ) Event.WeaponName = Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end + self:E( { _EVENTCODES[Event.id], Event } ) for ClassName, EventData in pairs( self.Events[Event.id] ) do if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then self:T2( { "Calling event function for class ", ClassName, " unit ", Event.IniDCSUnitName } ) diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index 624503021..f5080dbfc 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -28,9 +28,34 @@ function MISSILETRAINER:New( Distance ) _EVENTDISPATCHER:OnShot( self._EventShot, self ) + self.DB = DATABASE:New():FilterStart() + self.DBClients = self.DB.Clients + self.DBUnits = self.DB.Units + + self.DB:ForEachClient( + --- @param Client#CLIENT Client + function( Client ) + Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) + --Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) + --Client.MenuOnOff = MENU_CLIENT:New( Client, "On/Off", Client.MainMenu ) + --Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuOnOff, MISSILETRAINER._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) + --Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuOnOff, MISSILETRAINER._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) + end + ) + + self.DisplayMessages = true + return self end +function MISSILETRAINER._MenuMessages( MenuParameters ) + + local self = MenuParameters.MenuSelf + local MessagesOnOff = MenuParameters.MessagesOnOff + + self.DisplayMessages = MessagesOnOff +end + --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. -- @see MISSILETRAINER function MISSILETRAINER:_EventShot( Event ) @@ -51,17 +76,23 @@ function MISSILETRAINER:_EventShot( Event ) self:T( TrainerTargetSkill ) - if TrainerTargetSkill == "Client" or TrainerTargetSkill == "Player" then - self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit }, 0.5, 0.05, 0 ) + local Client = self.DBClients[TrainerTargetDCSUnitName] + if Client then + local TrainerSourceUnit = UNIT:New(TrainerSourceDCSUnit) + local TrainerTargetUnit = UNIT:New(TrainerTargetDCSUnit) + self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client }, 0.5, 0.05, 0 ) end end -function MISSILETRAINER:_FollowMissile( TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit ) - self:F( { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit } ) +--- +-- @param #MISSILETRAINER self +-- @param DCSUnit#Unit TrainerSourceDCSUnit +-- @param DCSWeapon#Weapon TrainerWeapon +-- @param DCSUnit#Unit TrainerTargetDCSUnit +-- @param Client#CLIENT Client +function MISSILETRAINER:_FollowMissile( TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client ) + self:F( { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client } ) - local TrainerSourceUnit = UNIT:New( TrainerSourceDCSUnit ) - local TrainerTargetUnit = UNIT:New( TrainerTargetDCSUnit ) - local PositionMissile = TrainerWeapon:getPoint() local PositionTarget = TrainerTargetUnit:GetPositionVec3() @@ -70,11 +101,17 @@ function MISSILETRAINER:_FollowMissile( TrainerSourceDCSUnit, TrainerWeapon, Tra ( PositionMissile.z - PositionTarget.z )^2 ) ^ 0.5 - MESSAGE:New( "Distance Missle = " .. Distance, nil, 0.2, "/Missile" ):ToAll() + if self.DisplayMessages then + self:T( Distance ) + MESSAGE:New( "Distance Missile = " .. Distance, "TEST", 0.2, "ID" ):ToAll() + end if Distance <= self.Distance then TrainerWeapon:destroy() - MESSAGE:New( "Missle Destroyed", nil, 5, "/Missile" ):ToAll() + if self.DisplayMessages then + self:T( "Destroyed" ) + MESSAGE:New( "Missile Destroyed", "TEST", 5, "ID" ):ToAll() + end return false end diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz index b1b93909dd65307d26c67b366ed2d06833c65375..53610c076c73c8595b0efac05f984f2d68f4f879 100644 GIT binary patch delta 7544 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 delta 7495 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 Date: Wed, 11 May 2016 17:18:50 +0200 Subject: [PATCH 03/11] Progress --- Moose/Client.lua | 8 +- Moose/MissileTrainer.lua | 85 +++++++++++++----- .../Moose_Test_MISSILETRAINER.miz | Bin 114424 -> 114675 bytes 3 files changed, 67 insertions(+), 26 deletions(-) diff --git a/Moose/Client.lua b/Moose/Client.lua index 00315964c..12d3b9888 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -242,9 +242,7 @@ end -- @return DCSTypes#Group.ID function CLIENT:GetClientGroupID() - if not self.ClientGroupID then - local ClientGroup = self:GetDCSGroup() - end + local ClientGroup = self:GetDCSGroup() self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() return self.ClientGroupID @@ -256,9 +254,7 @@ end -- @return #string function CLIENT:GetClientGroupName() - if not self.ClientGroupName then - local ClientGroup = self:GetDCSGroup() - end + local ClientGroup = self:GetDCSGroup() self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() return self.ClientGroupName diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index f5080dbfc..852a7fe89 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -24,6 +24,9 @@ function MISSILETRAINER:New( Distance ) self.Schedulers = {} self.SchedulerID = 0 + self.MessageInterval = 2 + self.MessageLastTime = timer.getTime() + self.Distance = Distance _EVENTDISPATCHER:OnShot( self._EventShot, self ) @@ -32,28 +35,47 @@ function MISSILETRAINER:New( Distance ) self.DBClients = self.DB.Clients self.DBUnits = self.DB.Units - self.DB:ForEachClient( - --- @param Client#CLIENT Client - function( Client ) - Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) - --Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) - --Client.MenuOnOff = MENU_CLIENT:New( Client, "On/Off", Client.MainMenu ) - --Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuOnOff, MISSILETRAINER._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) - --Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuOnOff, MISSILETRAINER._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - end - ) + for ClientID, Client in pairs( self.DBClients ) do + Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) + Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) + Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) + Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) + Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) + Client.MenuToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesToAll = true } ) + Client.MenuToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesToAll = false } ) + Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesTrack = true } ) + Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesTrack = false } ) + end +-- self.DB:ForEachClient( +-- --- @param Client#CLIENT Client +-- function( Client ) +-- +-- end +-- ) - self.DisplayMessages = true + self.MessagesOnOff = true + self.MessagesToAll = false + self.MessagesTrack = true return self end -function MISSILETRAINER._MenuMessages( MenuParameters ) +function MISSILETRAINER:_MenuMessages( MenuParameters ) local self = MenuParameters.MenuSelf - local MessagesOnOff = MenuParameters.MessagesOnOff - self.DisplayMessages = MessagesOnOff + if MenuParameters.MessagesOnOff then + self.MessagesOnOff = MenuParameters.MessagesOnOff + end + + if MenuParameters.MessagesToAll then + self.MessagesToAll = MenuParameters.MessagesToAll + end + + if MenuParameters.MessagesTrack then + self.MessagesTrack = MenuParameters.MessagesTrack + end + 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. @@ -86,9 +108,9 @@ end --- -- @param #MISSILETRAINER self --- @param DCSUnit#Unit TrainerSourceDCSUnit +-- @param Unit#UNIT TrainerSourceDCSUnit -- @param DCSWeapon#Weapon TrainerWeapon --- @param DCSUnit#Unit TrainerTargetDCSUnit +-- @param Unit#UNIT TrainerTargetDCSUnit -- @param Client#CLIENT Client function MISSILETRAINER:_FollowMissile( TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client ) self:F( { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client } ) @@ -101,16 +123,39 @@ function MISSILETRAINER:_FollowMissile( TrainerSourceUnit, TrainerWeapon, Traine ( PositionMissile.z - PositionTarget.z )^2 ) ^ 0.5 - if self.DisplayMessages then + + if self.MessagesOnOff and self.MessagesTrack and self.MessageLastTime + 2 <= timer.getTime() then self:T( Distance ) - MESSAGE:New( "Distance Missile = " .. Distance, "TEST", 0.2, "ID" ):ToAll() + self.MessageLastTime = timer.getTime() + local Message = MESSAGE:New( + string.format( "%s launched by %s: %4.2f km", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetName(), + Distance + ),"Tracking", 2, "ID" ) + + if self.MessagesToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end end if Distance <= self.Distance then TrainerWeapon:destroy() - if self.DisplayMessages then + if self.MessagesOnOff then self:T( "Destroyed" ) - MESSAGE:New( "Missile Destroyed", "TEST", 5, "ID" ):ToAll() + local Message = MESSAGE:New( + string.format( "%s launched by %s destroyed", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetName(), + Distance + ),"Tracking", 2, "ID" ) + if self.MessagesToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end end return false end diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz index 53610c076c73c8595b0efac05f984f2d68f4f879..e971eab9dd73dc800c111328ee56f68ae5b16fe2 100644 GIT binary patch delta 7823 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 O8;k$&gWmZc82vv+CIG?! delta 7560 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 From e486c8035083e554cdab558dbda32855b7f681ac Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 12 May 2016 12:38:35 +0200 Subject: [PATCH 04/11] Got something working now --- Moose/Client.lua | 4 +- Moose/MissileTrainer.lua | 268 ++++++++++++++---- .../Moose_Test_MISSILETRAINER.miz | Bin 114675 -> 114890 bytes 3 files changed, 217 insertions(+), 55 deletions(-) diff --git a/Moose/Client.lua b/Moose/Client.lua index 12d3b9888..be60463fb 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -12,7 +12,7 @@ Include.File( "Message" ) --- The CLIENT class -- @type CLIENT --- @extends Base#BASE +-- @extends Unit#UNIT CLIENT = { ONBOARDSIDE = { NONE = 0, @@ -48,7 +48,7 @@ CLIENT = { -- 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 ) - local self = BASE:Inherit( self, BASE:New() ) + local self = BASE:Inherit( self, UNIT:New( Unit.getByName( ClientName ) ) ) self:F( ClientName, ClientBriefing ) self.ClientName = ClientName diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index 852a7fe89..eaed63155 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -37,14 +37,34 @@ function MISSILETRAINER:New( Distance ) for ClientID, Client in pairs( self.DBClients ) do Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) + Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) + Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - Client.MenuToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesToAll = true } ) - Client.MenuToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesToAll = false } ) - Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesTrack = true } ) - Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesTrack = false } ) + + Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) + Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) + Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) + Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = true } ) + Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = false } ) + + Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) + Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) + Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) + Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = true } ) + Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = false } ) + Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = true } ) + Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = false } ) + + Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) + Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = true } ) + Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = false } ) + Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = true } ) + Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = false } ) + + end -- self.DB:ForEachClient( -- --- @param Client#CLIENT Client @@ -54,9 +74,21 @@ function MISSILETRAINER:New( Distance ) -- ) self.MessagesOnOff = true - self.MessagesToAll = false - self.MessagesTrack = true - + + self.TrackingToAll = false + self.Tracking = true + + self.AlertsToAll = true + self.AlertsHits = true + self.AlertsLaunches = true + + self.DetailsRange = true + self.DetailsBearing = true + + self.TrackingMissiles = {} + + self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 ) + return self end @@ -68,18 +100,39 @@ function MISSILETRAINER:_MenuMessages( MenuParameters ) self.MessagesOnOff = MenuParameters.MessagesOnOff end - if MenuParameters.MessagesToAll then - self.MessagesToAll = MenuParameters.MessagesToAll + if MenuParameters.TrackingToAll then + self.TrackingToAll = MenuParameters.TrackingToAll end - if MenuParameters.MessagesTrack then - self.MessagesTrack = MenuParameters.MessagesTrack + if MenuParameters.Tracking then + self.Tracking = MenuParameters.Tracking + end + + if MenuParameters.AlertsToAll then + self.AlertsToAll = MenuParameters.AlertsToAll + end + + if MenuParameters.AlertsHits then + self.AlertsHits = MenuParameters.AlertsHits + end + + if MenuParameters.AlertsLaunches then + self.AlertsLaunches = MenuParameters.AlertsLaunches + end + + if MenuParameters.DetailsRange then + self.DetailsRange = MenuParameters.DetailsRange + end + + if MenuParameters.DetailsBearing then + self.DetailsBearing = MenuParameters.DetailsBearing end end --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see MISSILETRAINER +-- @param #MISSILETRAINER self +-- @param Event#EVENTDATA Event function MISSILETRAINER:_EventShot( Event ) self:F( { Event } ) @@ -96,68 +149,177 @@ function MISSILETRAINER:_EventShot( Event ) local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName() local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - self:T( TrainerTargetSkill ) local Client = self.DBClients[TrainerTargetDCSUnitName] if Client then + local TrainerSourceUnit = UNIT:New(TrainerSourceDCSUnit) local TrainerTargetUnit = UNIT:New(TrainerTargetDCSUnit) - self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client }, 0.5, 0.05, 0 ) + + if self.MessagesOnOff and self.AlertsLaunches then + + local Message = MESSAGE:New( + string.format( "%s launched a %s", + TrainerSourceUnit:GetTypeName(), + TrainerWeaponName + ) .. self:AddRange( Client, TrainerWeapon ) .. self:AddBearing( Client, TrainerWeapon ),"Launch", 5, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + end + + local ClientID = Client:GetID() + self:T( ClientID ) + if not self.TrackingMissiles[ClientID] then + self.TrackingMissiles[ClientID] = {} + end + self.TrackingMissiles[ClientID].Client = Client + if not self.TrackingMissiles[ClientID].MissileData then + self.TrackingMissiles[ClientID].MissileData = {} + end + local MissileData = {} + MissileData.TrainerSourceUnit = TrainerSourceUnit + MissileData.TrainerWeapon = TrainerWeapon + MissileData.TrainerTargetUnit = TrainerTargetUnit + table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) + --self:T( self.TrackingMissiles ) end end +function MISSILETRAINER:AddRange( Client, TrainerWeapon ) + + local RangeText = "" + + if self.DetailsRange then + + local PositionMissile = TrainerWeapon:getPoint() + local PositionTarget = Client:GetPositionVec3() + + local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 + + RangeText = string.format( ", at %4.2fkm", Range ) + end + + return RangeText +end + +function MISSILETRAINER:AddBearing( Client, TrainerWeapon ) + + local BearingText = "" + + if self.DetailsBearing then + + local PositionMissile = TrainerWeapon:getPoint() + local PositionTarget = Client:GetPositionVec3() + + self:T( { PositionTarget, PositionMissile }) + + local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } + local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) + --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) + if DirectionRadians < 0 then + DirectionRadians = DirectionRadians + 2 * math.pi + end + local DirectionDegrees = DirectionRadians * 180 / math.pi + + BearingText = string.format( ", %d degrees", DirectionDegrees ) + end + + return BearingText +end + + --- -- @param #MISSILETRAINER self -- @param Unit#UNIT TrainerSourceDCSUnit -- @param DCSWeapon#Weapon TrainerWeapon -- @param Unit#UNIT TrainerTargetDCSUnit -- @param Client#CLIENT Client -function MISSILETRAINER:_FollowMissile( TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client ) - self:F( { TrainerSourceUnit, TrainerWeapon, TrainerTargetUnit, Client } ) - - local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = TrainerTargetUnit:GetPositionVec3() - - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 +function MISSILETRAINER:_TrackMissiles() + self:F2() - if self.MessagesOnOff and self.MessagesTrack and self.MessageLastTime + 2 <= timer.getTime() then - self:T( Distance ) + local ShowMessages = false + if self.MessagesOnOff and self.MessageLastTime + 3 <= timer.getTime() then self.MessageLastTime = timer.getTime() - local Message = MESSAGE:New( - string.format( "%s launched by %s: %4.2f km", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetName(), - Distance - ),"Tracking", 2, "ID" ) - - if self.MessagesToAll then - Message:ToAll() - else - Message:ToClient( Client ) - end + ShowMessages = true end - if Distance <= self.Distance then - TrainerWeapon:destroy() - if self.MessagesOnOff then - self:T( "Destroyed" ) - local Message = MESSAGE:New( - string.format( "%s launched by %s destroyed", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetName(), - Distance - ),"Tracking", 2, "ID" ) - if self.MessagesToAll then - Message:ToAll() - else - Message:ToClient( Client ) + for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do + + local Client = ClientData.Client + self:T2( { Client:GetName() } ) + + + ClientData.MessageToAll = "Missiles to Other Players:\n" + ClientData.MessageToClient = "Missiles to You:\n" + + for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do + + self:T( #TrackingData.MissileData ) + + for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do + self:T( MissileDataID ) + + local TrainerSourceUnit = MissileData.TrainerSourceUnit + local TrainerWeapon = MissileData.TrainerWeapon + local TrainerTargetUnit = MissileData.TrainerTargetUnit + + local PositionMissile = TrainerWeapon:getPosition().p + local PositionTarget = Client:GetPositionVec3() + + local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 + + if Distance <= self.Distance then + -- Hit alert + TrainerWeapon:destroy() + if self.MessagesOnOff and self.AlertsHits then + + self:T( "Destroyed" ) + + local Message = MESSAGE:New( + string.format( "%s launched by %s destroyed", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetTypeName() + ),"Tracking", 2, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + + MissileData = nil + table.remove( TrackingData.MissileData, MissileDataID ) + end + else + local TrackingTo = string.format( " -> %s launched by %s", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetName() + ) + + if ClientDataID == TrackingDataID then + ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" + else + ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( TrackingData.Client, TrainerWeapon ) .. self:AddBearing( TrackingData.Client, TrainerWeapon ) .. "\n" + end + end end end - return false + + if self.MessagesOnOff and self.Tracking and ShowMessages then + local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, "Tracking", 1, "ID" ):ToClient( Client ) + end + end return true diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz index e971eab9dd73dc800c111328ee56f68ae5b16fe2..aa99ee89791f756cfd0943a160c18a849a8f0294 100644 GIT binary patch delta 8723 zcmZ{q1#BNp(x?AsrWj(1nVDi{W@g9C6f?uvjv;1dW`>xVW5>+Q%*=dwckjF1JL#mE z9+mpn&y+OPRZ{ibd_WKT!}!&MO{T$}QPYg_L4iOs$xCTq3;^VoTOt1>)_tC>C6u#9 z@d#XMKcsag_HQ$-bLWqj$oQB*KjyQ$T&wtrozw}|jffp34P}CDi;#20y=?e^Y=QFV z%Ugt{u?=CGK~5s4k+OFvzhlxQcYi1$_I4pw5;D(pls93W?m^svb#;FKM`0BiEZ0u&$-7w0ij(>XT@Vlnr+6ET00n4Cb{s6hXO zM@YiVF)7Cpp4`rYPw?X*i-SJ$0LuCel7(XBZJo;3>)9LHA#e1L0@p=>zxQ)saPFcg z^%9hPs zneERsTC14IKCo}c@@TPw4?`Ge;yKOj2}}*Us=)c|B4cI&yar&^3bi9DuzzVc|d+}`ezRgxMyKy zv2{Dr#-N|PU!7cQrijm3hJPbUuC0&Giuo5n-5%BCKkW49K~+Lg-jT(D>d+_RUHpO5sov9eUG~ zBc}J=x&4`{sF}y;gNje!t8|CMELs{fW`In-E!9jFb2#3fb2C-?HLLY;Adw3E44KIh z$fNTP`KHo^o!V1Nc`4U79oAHbaz1t@iX?auZ^&a zK6?21a|Kb5qCeg6%BL#bvNpHayyC>HrHYYZbXEN06x&=Gy6&Fs4zGY;UgK6X1*r4> zI%RTHv<<#@MU7Xyk+eSJ=8~=6lGr{XU-hF9$~I5~z#&Y$0Z_Dw&)=8*p*Ebs~e;&)F&0y=KtAKxg++`I!u*~! z=X4@lWtMt$WiiWApPzT>7v&W)CG;{Kn)8fB)?nrXp;MKY?oPe?w4e~y2(Y4WL4099 zmdBg%1U^*;Nc!ElR_g;&O-=6~my7g1B1c(3AEH*pObYecGtcn>xr+j^Dh9AVjL z$69OA*!N0=zO#`^7z|^G;_QWBV6n94)`;2X*(mCzn-Lx#w zt=Z2e?&(8P${9R{tZ$lbFw=g3B@^a=^#VrbpTGEGfIy%2Kp+?p2xM#Nl9U|3HM!GU}#(8SUWpO<)p?W6S9(5DFBZJNewJa%o8t7xU(+}v!px3>q;y>i~?E520T?@lPxS9l+j1B=-Y z?|5Jv0R1+NZf*4Z#pRY@iNPka=| ztuLd~_Ho3QE0ZW??r(c7^B2}s8CFvjG-H@>5=~UH&EWE2ni^I0*#!=t=pk3CX*HG) zQaH*!6PF9De7PipFZN|ltLU9E*LIBTUBlfo7@EL=_O3<6DC0D3U97r>1LVX0RppDs*j|Gunfv;tl(d;xs~H8zcKJgu{( z4Dl@=r>e)_y0PbL`c@i#GoX(5tsJL{yx@0RMjlO!R%gaFbJz@#p~CZ&4b~&E)h?~Z z#<7c7e|)!zCtfA!L_JfmTxVWgF@e0mcWtszxSUMdK=C$DpxT?jd`4YD;8;UA0X~r_ z%~dqdm)@dRd81(<3BKL0Yrkq1PgrQdL(eyRhYG2fQYmGV%HX7l5coKSQBt0Ng3^77 z2cZREsHm&O)WMf{h@Y8K45EMsYUE4RYj)d*+2e^x_C>}WPT+J^1^sxA-A5>Qf)^5q zsDpEj6Mgup(VzgIX@O7b?UgOMd@m_u7bqaY`(;f)`Yx*lUWhEF@KZuXGH%R(g(sHW z%$J_Z;g9mpx9i^{oj_@%#xgU!5fGb`uD7=LI?Ho{*IJdmi^&+}3wS#h={|?2Xx)8% z_0HEt>Q>TwN=}wI@R(!s(=k?oIRBW1xnGQK$mj6LPqrr@I&;SC4H7Wq;0ndsv4Kvr zl5j^uX!MwK%hXsV$2Xqk-_*bnr;IEhYHn28p+8+|b;a)<0wP^Kkgg($Y6(g4WgDVV zt?kx#b^n$*QRf^kJ|z3o&vzTdDO@U?qFmtg5hco!DVQ3m82&0}bT=`EoS$u+Q}2;p zq}_Ra9!`%xi+G(eLUBI)Lg9UzoslO+B%j2wss_hiC!!NFC26Rn@^N2-scu-H)^1qf zJA(LPA<^V}3+Pi>Dlzqv5`V#K=L}^Ut`K?!e|8V3yPFBG+qmO91Me}lyhxGdj>F;( zNQ_wRI7we03f~BKRZ}%bM=3(bF2+w+DzsTwdq1FVHC5-&vJ`x%Z}O2rEzQQP-=yk0 zB-6Ow<&Lu?l`fIZ`Rp4!Cbcwu_#IX1(&>{rdT!?yA|hQ*30`r6D`7J8#c?o=!2qAXl;hhezuzMvM<*!4|$R zYwI^e0)!=};^!*WYR}_0$(}3oju-G{)K_TIP76i)ZDa3f{FsWv`hdr+&qzL=<59>1 z-t5>W*P)IoQ5v+u>;qf_dBvHcdiVVw6Q%tIz6yog82RC_2jW8$?tD$5p=X7UF7rev z`}0QK&cKhH`2|{5JzlUk(58IGB<)jzS2Y^JATZ=aj{>vD`T~`fZyzFSZMZc}HG~z! zp+(7@p5Nqq8vWL-pdKj6u4@?9>;85ULs%EK-=Gvj?0wq@xpko3$P$!`wNc?Z$tcc4 zigEs>P^unw%E=a|fPcDni9Vi0nvTl5r7!FGNmSiRnBm;}Tti4b6-V@_-t>37%r}wh zSfEL|V>#Lw8~t_EQv?W|GD!{m+Bn6cYBZD^;%VjQpj$j8|l(cEV?>}1?5(3 zw&V$x>|7of!G&-IUz!&IH%LOXRb zDpF0QiZbQw$f;Fm_j!snujE8Zqg6oP0t+Owr?uPt&~FWk@J#r}!f2yuTQ~HWwHcECz<3#Wk=Dn0obiY z86R!t7xg2?E12`Kv&2X>u3H9tdk=7V!qBuE7{ZBhzY_KB4;hb?!$YhH5C1*{Rq6-2 zv9F_!pL|Yt%9;p!D=js5iA#VB8^BJ{^0zL0M9|lvS^m-nH!+TqM|E%UHJ>7xJ z;prrIMI)|bN9_&Rx7S(puTqg&+@hAao?5Ya(XyUkDaY2LntVsf1$8hkjo=5D3iwNL zbtQ*S8j=1|*RVecWajr3qrgg3MTHNwjW3``;wcqdb=4Yc?~!cayOjWjv~yC_7L8Xw z1?~69BjuMucPM`|RxWSXL7SzG@eN$6lY$+J$8irVy|T}ozxxt9{b(Eyea#McQ&}pD z0H-@XKEbGObwR>8H?HXT<*MnH);s^P(1fAUgokc}qi%41c$YOYC*{;^(OzfIfwc%~ zL?*u(kWgot2qT461sDF(NU$g~OAxrOg*g;15S{3AG|mKP5~ z0ff=A5MYx=1cz0Gq+u!B{kF<5DWm<-rIccd5FEF_M7eE&#;Xp_`nq|GwNAB_y}oG`kV!7QQG>H3fp=y)>i2YQcLq~op6 zYy)GvXGZ~+3&EjIrw3@76#87k#+`na6Tz`61g5KFT+0ikEXp)i*6$M0rknn8L7!7x zs+7HdKtg7lZu`eYnm-P)DHEr-}{U-tQ?$+?GT@3jQ^uNqb<0=7_HW!sHpGIz$ENTj!6w zc*i$Ot|Y_eh?C zxZ5^1oQ%~`f0LhnF52ChQ2qMV)bAGOjXElw6bzn}C(B9$^yCC+)#FGQx;5Ox0{c92 zZzAhV2f4u;XwfHk1RVZ^;kXt=N8lTUhw#;(U#cmr@OeX#v%F0RMqWu%1dE9zmWcDr zDlGLkOR{Z0iR=i2Q3|-iMrmn^>YcNJuyU+C5~pv!qZo*T5#aii>8~~8`JHg7T}#*L zzqC?tO8&@mI+nRUW4^fM+&02=24y?0=QTF9NknKEwJnU}^Mobm77a5a%de6^6T zuw_eoObBM-x6rY(j6SfaQ#xch&@#+A$wGM%ziqZ8<$4iZiZ0knC#e{@G=#7WWU|0- zt0;+w&K+zeOx64v?NY~@;b9-%q^=<#scj7EKz@JuUeL)f7!d`g4oGg+>i!WPev3nE%g z!3sld^CN-{@%~17Jj*pQQ4KL4uc^r?1nY~b9a7ye!7%3&B9DR^Dv%wYZ0u#uk+Kh0lnMs%r2DGAfo&IN}Lo5!>M4(k+_qE?V{ z!;gy^krARCW?iSiDu?<}!oP|8iGPWftx0h-wh2>CboLe;F8>meDf|_-EBsq$Qm#aD za%8`3akB5rj6cJS6jab@X6t<#EP1ipc~sYX%# zG=}nf%XJE|OFBS3`Ws|*M|9jthRhfc8Pw#1!}vkRMVE7Nq<7H9N_#mGG3q~Q&g2Ta@>jC#86>qT9 z7jPV4ttE=)PaFS&gSX3+n@2SuZIA2rOFa_s!ED>gZ6uz1g)-I8W$ve;W`{xk1^*08 zQ#fAal#xXjc+B9-e7XK!xDIt?{9BoI3W4y6#5=Z25tgH>lBuurjW>)nGm>Fsp{{Tn zrqJBmnVM|ck~6dyQbI`aSvi_T{pyeuB6vLPlB0=F)h4I)eg%CR0v-#XbEp^Pywdut z+=Q7(eq;+fM0*;iAs;h;1qt07FWtd~c<{p3wN~eok#iknGUR1-sHPTV{(!l;%t@4V zZOgO5*krwqh^nCusbDnjz!HKD&>7 zv074~fMt*pIxQbf=L89d2$cy&teSk_LcNn|Q@~!(BI9+7yTm@`Y6=pzEpdL2wu zqa$rYrCD7ybn!wyE?*b*)ueKJ8bge3^!#a7_USAuH<|=tUO9eP*Sx z+W?=yb6;2VbUVwfo@I!V-*A@s+B3hVd?qOK6+#n20;cGEnVYHLLX)g600~jzh>aJl zR%#3?in!z0TC;j+bxG83ZO&89aVDhu5XXGrzrr8*!h2dCl$A z98;USiW8R0@7@QXpt#mWZMs41AKBvjF-nN7S!A60G`E-J97cG1g&~1(Tl6FfBJ6&K zcI{)z^auMkzD1Bvt-r52wxM&YI%HvN;&8ysWX7tn(_F3T#^CK0k-r~Jn9Te2) z!$W3qabsy`@xe{$=f@`4YH@382y<;;5)1e%)p#l74Jj{x2hN)ebDisFZ1r<)vgJli zbpcb!?u*nHS-k=NRMtCxzIvC>*aMU7^y^Dp>@j=~P7fO+w)_V4lyEZ5@Yk}hJi3*1 zb^Ips&n0&v>HW1TONnA((ql{^S+W$SOT2z5HPDv?uzMU?-};th8?JcC2eAI66f9$C zOaBOjJR<{u^IY|%X^Nk3PoAjC&!|B=RCNLQOuDG+M}i418$@VoSB12pY!8PU2B*flLQ2jXrxAC~UPVf){S_UiF0n=Po$eV|z+a4`Eep5ef`73_B{N z&N$idet#|8i@z4q=)CMQi*t}e?Qa9t`LDhssDPDmepgKW7+`6I@}XKVnr=`Mn> z$O1epqcaJJ9~aZ!1R#OUKVkpj;t3Vp##D@I(- zi}^2Q4R{*AYX(>HD0`TBeLp?#DqZou${$UgWVovfwoZO-kvKL72s@q)9R$!T0wqzq+;oQB*jJ!x$tLYGq>;k+IOV^w5jKxjEBQk-5;1lr7>xjGUD2w-t zXcF_T_QSkG)@Qs?N+f=`J75h8f*N_r-F;liv~<_-@#DhMXD%hK8T_jThanZ|Mbs5k z;bbrFnpf(J=w>q5kD|rdsJq)#C}pQ<2-mjt^5`SgH@~lzQ7fvK#&4-1)KfP|1ZM4VqoTDceiS|m7vU6PU+aDPD^8@Kt3h&BK zhFgPAm)WlZiZT$8f18&eSPnL7~!H@ zlX|>2cJ8MGlm0FNd2&;n(45zkqwF~k7wr)%4=lwqG90RqZ0%2w58ND)c$y=U^8Uu+%~-~s8(MogJ{s3$xG zhUn$uJf`_R!Q&c3MzU3iLl?iV^POxVZ9P$VhW#>9@2THFU$cK#a@}SpYJDAlj#YJE zrPV&*_vN7sMNrr#XJ{lE>K7xIG)?o8AP-<67~_am|51h-^?4zYir3xv!nfqOGy)+& zq-|1>;Mlz&XJ|n~0Rgy8>bGoiFLb~c%AZPvJC8Pv`mHl1E74;#lo&nOAV&4%i9cR6 zk08(AY+Zw1IErQZ%_CB4fOC#n6H;uPu=erYl8qo*aU^!g6T?O zKAK1SWB~T1Msa_&(80*bfi6bRQS8TWzLWwU1B}x|v(=M`eQ?01Bz>C=?Q}^vSqUo< zYzW<7*!JvztmU;~2A4~+PFK4hOxx0NKu2Z1?l2kowj1msYGP$jOVNsWF)u1TV|_fj zIFJm6HVC~QVstSke=cKat(!+gzU;8T>%|?g!FC<%;!*HzJ+1e|+uy}bfd18J(3m0{ zrF%-@9}ddxW&}>+7__l?+@Nfeu*%GqRSu{sCC=+M_Y;t0(_3vxr{GNkA_@*#*m
  • q}(3 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 rfO<1L-2brPe_Q>(_rnwp4&xt)nh8; From 971f5e9ca491aceae2f517e686032a38aeeee740 Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 12 May 2016 14:53:28 +0200 Subject: [PATCH 05/11] Updates --- Moose/Client.lua | 42 ++++- Moose/Database.lua | 21 +-- Moose/MissileTrainer.lua | 158 +++++++++++------- Moose/Unit.lua | 13 +- .../Moose_Test_MISSILETRAINER.miz | Bin 114890 -> 115947 bytes 5 files changed, 154 insertions(+), 80 deletions(-) diff --git a/Moose/Client.lua b/Moose/Client.lua index be60463fb..9489d8019 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -12,7 +12,7 @@ Include.File( "Message" ) --- The CLIENT class -- @type CLIENT --- @extends Unit#UNIT +-- @extends Base#BASE CLIENT = { ONBOARDSIDE = { NONE = 0, @@ -48,7 +48,7 @@ CLIENT = { -- 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 ) - local self = BASE:Inherit( self, UNIT:New( Unit.getByName( ClientName ) ) ) + local self = BASE:Inherit( self, BASE:New() ) self:F( ClientName, ClientBriefing ) self.ClientName = ClientName @@ -319,6 +319,44 @@ function CLIENT:GetPointVec2() return nil end +function CLIENT:GetPositionVec3() + self:F( self.ClientName ) + + local DCSUnit = Unit.getByName( self.ClientName ) + local UnitPos = DCSUnit:getPosition().p + + self:T( UnitPos ) + return UnitPos +end + +function CLIENT:GetID() + self:F( self.ClientName ) + + local DCSUnit = Unit.getByName( self.ClientName ) + local UnitID = DCSUnit:getID() + + self:T( UnitID ) + return UnitID +end + +function CLIENT:GetName() + self:F( self.ClientName ) + + self:T( self.ClientName ) + return self.ClientName +end + +function CLIENT:GetTypeName() + self:F( self.ClientName ) + + local DCSUnit = Unit.getByName( self.ClientName ) + local TypeName = DCSUnit:getTypeName() + + self:T( TypeName ) + return TypeName +end + + --- Returns the position of the CLIENT in @{DCSTypes#Vec3} format. -- @param #CLIENT self diff --git a/Moose/Database.lua b/Moose/Database.lua index 7b1f216df..bca8306b1 100644 --- a/Moose/Database.lua +++ b/Moose/Database.lua @@ -413,6 +413,7 @@ function DATABASE:_RegisterGroup( GroupTemplate ) self.Templates.Units[UnitTemplateName].GroupName = GroupTemplateName self.Templates.Units[UnitTemplateName].GroupTemplate = GroupTemplate self.Templates.Units[UnitTemplateName].GroupId = GroupTemplate.groupId + self:E( {"skill",UnitTemplate.skill}) if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then self.Templates.ClientsByName[UnitTemplateName] = UnitTemplate self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate @@ -477,11 +478,10 @@ function DATABASE:_RegisterDatabase() self.DCSUnitsAlive[DCSUnitName] = DCSUnit self.UnitsAlive[DCSUnitName] = self.Units[DCSUnitName] end - - if self.Templates.ClientsByName[DCSUnitName] then - self.Clients[DCSUnitName] = CLIENT:New( DCSUnitName ) - end - + end + + for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do + self.Clients[ClientName] = CLIENT:New( ClientName ) end end end @@ -504,11 +504,12 @@ function DATABASE:_EventOnBirth( Event ) self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit self.Units[Event.IniDCSUnitName] = UNIT:New( Event.IniDCSUnit ) - if not self.DCSGroups[Event.IniDCSGroupName] then - self.DCSGroups[Event.IniDCSGroupName] = Event.IniDCSGroupName - self.DCSGroupsAlive[Event.IniDCSGroupName] = Event.IniDCSGroupName - self.Groups[Event.IniDCSGroupName] = GROUP:New( Event.IniDCSGroup ) - end + --if not self.DCSGroups[Event.IniDCSGroupName] then + -- self.DCSGroups[Event.IniDCSGroupName] = Event.IniDCSGroupName + -- self.DCSGroupsAlive[Event.IniDCSGroupName] = Event.IniDCSGroupName + -- self.Groups[Event.IniDCSGroupName] = GROUP:New( Event.IniDCSGroup ) + --end + self:_EventOnPlayerEnterUnit( Event ) end end end diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index eaed63155..e3b19f8b6 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -36,34 +36,39 @@ function MISSILETRAINER:New( Distance ) self.DBUnits = self.DB.Units for ClientID, Client in pairs( self.DBClients ) do - Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) - - Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) - - Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) - Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) - Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) - - Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) - Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) - Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) - Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = true } ) - Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = false } ) - - Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) - Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) - Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) - Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = true } ) - Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = false } ) - Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = true } ) - Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = false } ) - - Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) - Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = true } ) - Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = false } ) - Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = true } ) - Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = false } ) - + + local function _Alive( Client ) + + Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) + + Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) + + Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) + Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) + Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) + + Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) + Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) + Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) + Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = true } ) + Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = false } ) + + Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) + Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) + Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) + Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = true } ) + Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = false } ) + Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = true } ) + Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = false } ) + + Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) + Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = true } ) + Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = false } ) + Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = true } ) + Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = false } ) + end + + Client:Alive( _Alive ) end -- self.DB:ForEachClient( @@ -145,10 +150,9 @@ function MISSILETRAINER:_EventShot( Event ) local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) - local TrainerTargetDCSGroup = TrainerTargetDCSUnit:getGroup() - local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName() local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill + self:T(TrainerTargetDCSUnitName ) local Client = self.DBClients[TrainerTargetDCSUnitName] if Client then @@ -271,46 +275,70 @@ function MISSILETRAINER:_TrackMissiles() local TrainerWeapon = MissileData.TrainerWeapon local TrainerTargetUnit = MissileData.TrainerTargetUnit - local PositionMissile = TrainerWeapon:getPosition().p - local PositionTarget = Client:GetPositionVec3() + if TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then + local PositionMissile = TrainerWeapon:getPosition().p + local PositionTarget = Client:GetPositionVec3() + + local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 + + if Distance <= self.Distance then + -- Hit alert + TrainerWeapon:destroy() + if self.MessagesOnOff and self.AlertsHits then - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 - - if Distance <= self.Distance then - -- Hit alert - TrainerWeapon:destroy() - if self.MessagesOnOff and self.AlertsHits then - - self:T( "Destroyed" ) - - local Message = MESSAGE:New( - string.format( "%s launched by %s destroyed", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetTypeName() - ),"Tracking", 2, "ID" ) - - if self.AlertsToAll then - Message:ToAll() - else - Message:ToClient( Client ) + self:T( "killed" ) + + local Message = MESSAGE:New( + string.format( "%s launched by %s killed '%s'", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetTypeName(), + TrainerSourceUnit:GetPlayerName() + ),"Tracking", 15, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + + MissileData = nil + table.remove( TrackingData.MissileData, MissileDataID ) + self:T(TrackingData.MissileData) + end + else + local TrackingTo = string.format( " -> %s launched by %s", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetName() + ) + + if ClientDataID == TrackingDataID then + ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" + else + ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( TrackingData.Client, TrainerWeapon ) .. self:AddBearing( TrackingData.Client, TrainerWeapon ) .. "\n" end - - MissileData = nil - table.remove( TrackingData.MissileData, MissileDataID ) end else - local TrackingTo = string.format( " -> %s launched by %s", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetName() - ) + if not ( TrainerWeapon and TrainerWeapon:isExist() ) then + if self.MessagesOnOff and self.AlertsLaunches then + -- Weapon does not exist anymore. Delete from Table + local Message = MESSAGE:New( + string.format( "%s launched by %s is self destructed!", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetTypeName() + ),"Tracking", 5, "ID" ) - if ClientDataID == TrackingDataID then - ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" - else - ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( TrackingData.Client, TrainerWeapon ) .. self:AddBearing( TrackingData.Client, TrainerWeapon ) .. "\n" + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + end + MissileData = nil + table.remove( TrackingData.MissileData, MissileDataID ) + self:T(TrackingData.MissileData) end end end diff --git a/Moose/Unit.lua b/Moose/Unit.lua index a6091a77c..4cea12dbb 100644 --- a/Moose/Unit.lua +++ b/Moose/Unit.lua @@ -56,11 +56,13 @@ UNIT = { -- @return Unit#UNIT function UNIT:New( DCSUnit ) local self = BASE:Inherit( self, BASE:New() ) - self:F( DCSUnit:getName() ) + self:F( DCSUnit ) self.DCSUnit = DCSUnit - self.UnitName = DCSUnit:getName() - self.UnitID = DCSUnit:getID() + if DCSUnit then + self.UnitName = DCSUnit:getName() + self.UnitID = DCSUnit:getID() + end return self end @@ -91,6 +93,11 @@ function UNIT:GetName() return self.UnitName end +function UNIT:GetPlayerName() + self:F( self.UnitName ) + + return self.DCSUnit:getPlayerName() +end function UNIT:GetTypeName() self:F( self.UnitName ) diff --git a/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz b/Test Missions/Moose_Test_MISSILETRAINER/Moose_Test_MISSILETRAINER.miz index aa99ee89791f756cfd0943a160c18a849a8f0294..1bba5cd294ac83b9ebe4d696729315f8d9012721 100644 GIT binary patch delta 7890 zcmZ9RRZtv&vZfi_g9o>u!6mr6yF0<%T?V%R!QGv~-3c&AfZ#H?48h$kkehSrZtd1S zbaz$P-(CM>cYPC~NT;z#A+3Pf9MqsmJk~V?7#MBB%(WZ5^|664Jk4s=M0gwQc_E>-D1B; z@GM%D`4zwR19OA_N)dgZb*alJ?>N85HGCG!S;JpwSRZb7q0e|}t2IY?4E**OO4PYy zE;a5T+wzVrHpU4cY}K-f!e3CO+IC8%H2gvE+gx^aavo{g*}?VD?8WZ4wIy(aXv77h z(x%NHHO{-^t6&^s2c>8U#N^~=?7J;ajIo?r>mc?YYGBN+od5d(ztm7EmD?vwW-mXi zWJ;VE#HLm7-d`^sAs1m0XP6p?x;9{bW(;)-}6AR6X%5#N^N#w#YMwIm+775eKF z6_km)Kfkm=$Z`w6yU#tZa`8+hhku&J#4?W+%9q`?_iHoB`O_|U9^%tA{r1~5Au2LV z44=CsEHIH0{lfzSuZHb$r7WIv*t*@QiQv?v=TuM9tF{XpCE>c{AE9rkPuT0}CEgFa zDd(=x8-7R30S$N@ov^u@!1SD|`b=o0|JBwke=JxZ9-FyoSkYT7fK-YzF-&SeIDVMQ zm6z(nk*Hp?T!~vOmRpq-_Z2Htq&KBlsFw(h9N0+{Of?R5c5Ot2%+y;^;w*g1mKZ`V zE6zkz3cexmVVy&+ZrZS*n>(jCud7h|;8+s?lc1vaL=6 z(3<$3U$i2$KO@gyr8_JIg4UzOulGh;dF`Wd%qRKv@Z{yoK){JJbf zU`h{mwm{-59%#u22UYI^7W)b9cl3)a@K`lXFddH(V|_D4y(#p5Rst0H4K9gpww5*q zAE{OZ(_3Sh)j~Y2L6`gLhO-DpVW%wYocod=(FzBqr6{20ch6+^u=c_rr~;gKt$vj?aFOS@_31%#c{gWS*P={Mg>k_e{kPsvJ|mS)xy2!Baw@3*C{{d6AxS*~b_Np2?x)y$)oK|RKpxOOf|Y$oj?ixn(^1-QXi zsD?+e2<)3)cecG{-Hk0#f(0T0!@p2Ybys%o=l&UZ&q%I$qTnrk{{7kHj@&XWp9(EM z(_~|!Z$|8Bv~)yH1hIT!O{rhW`rGh;UD|edw<=ZpW9rDQtw64j;M8pyMjbJ0#Rs!5 zZ+DDs{*wn*JjS-BTU)9YG}oA3WW`Q+ZT$jPZJ_Exn+#*_I5FQGVMu5_9)ZFV#(FXW!L~_)1YfI`yxkIiv3JNh z@m%Zc=tWzFXjE#D3HTXx5z) zJLE5EoJ#c=(4*3u5dinF?-asREGFgri7iCBCD|wk6mh2iu8~qQ_IoB5<;6%!f^&JGNcrE&@5FEOI zey9u)b+%^U3Wbhg=lz1p3n+(4KinZ&i4L;M&V<`}w+Du2sQ%^r{5}d)R#A-0Jc2kv zkAOr$kDC-bG}5cT1tNG=gYx=c!AGms2#CgmzXg!zKpCnG3AdJUCplMtPvhvkwWEc6 zikRiOES$l>_|l2^^95QfkXJTKlh!K+pOSz3XW{i!2cvAm+*CIqR>aFEf$tSrp`tS4 zE5A=KXIfKnF{HUz`kfOGMv-M7874a6@~G8poYM*q>i=)&yq!jw!E>zoa|t8F_Z@_^}|A z$;|U=PKyPwNH1bmEvb~o*lwy*PPZ^vE0<4FiR&E}v8$t|>-qTwUDKJUCKEd#)t=zj z9K4AQgC8o-@mzQ*NL)em?Ogv%>!30e3w9)#3GX%8;Ge zFRw>Az)eqOOyipG^-IbSoO$R}K?!G)et*slZrXwQXu;jy=_z}Rn#~XQ`?`ExOJAHE zC704nvvm-zz0>5HVq9~?Vgpkbw`KYW5&ERTolU}wuJ0Q0Z`n76i1{RqFzfK7DpSSm z#>I~16@}2lE67l~zcLN7=cE2TSQ+PV?o22V*i3`Dib`Ha5zamh5)v(5HzTH|PIjNR zPqfSfc&S!@a`Xseml}8HOgQu)oKl;o$I);tr+wiXZMZ)w3N90j(&isA!z%ZU%D29T zyIHDD{Yu}jpzi)RNx}dk_hFmw8G){49Ne;4pIw7(3E%7)Mtqu%_?t1O@0-o@=zvoq z@NXvl!?#P*ysWh)4M9nB;$K{zy!Nkog|e3_=*EzlZ}EjHe*?W=RC54f*G0*sEiDA39fgdam`E-s=Z&j8SY@WBIE-4<9CFzn!n|Z-7*OwOtmgp z7NBPM`9b;F+R%K{i{24`43p;j#D!h00qGNXuZEY~TCh|uTfZ4*ZJ=q@ z$8(;EeZ07#mkhvXhWEcX#N9ca2|D-76GQPfI=4bD6-<+p%Z^_&$p{Ygf;F*g0J^uH z%>)F)dKTqxAzl9{s+Ap-qd+-cd3wF&6JH@N+Xho+)A<7K-+DyzpVsK%Kc@Ftc+Xeg z6@4-qkMHTAuK>Qk6-w89ZfS``x@A=INSv}&Xs-f2-ST(aSc~+G_D3}I@i)O|zf=_= zRA!alN{qLx>s7>868Utxl_D**dxC&%RC027B@N|X5O_Q)^P>dW zB+}NMY@59eV13JHyyF%@x9UFUzH!w#naU)zR%$ns(#9cBMW7};`m|)0Ru&Bm$MDb7 zJ^AvIH1hYc@LDf`>8%aYrH+)F z_G<03B|kD)xH}DI*OKGa3TWB!_#`=PG0`i?nD@CE#QXMNb(KEe}pmg)JPbZ%p&9vVf>Kzt8xMXn9UCYs*c)ic9Gi zK1qV9XtBP{@yTQCpaVGDh-8{)IUdCa7spcoS&QGBr#pZI_cTWXMIX6ISCU4oP%(}? zn%(!nruKKtm?3+l{yrb~4jCTqDYd>smD2z)vUA^cx@17AO$eP%Usx_nf5piNEi&U& z2D6k2sqd+V-_C057xEgBobb?Q3#=a=3_^jKL6DwgTR*irG4Oo;^VT2*ZMOYog;0pg zv7Q@eWLwhjz{g$i2->+5y3Lwk+UDq=Pj505nn5*|tP{2FpP9L3bXvyNCh)itc9PbB zQY)^C`0Lh6XgXyoOMLE3`jne;<#^8OS|Qwy7fkcN&J*%hhGFpNX@dD9yDqQ2W_HnS z+LCA5@-U`jKK}JGoW^P8$ePBV@Mq8eU?t#WI_2q|_DY~^qyN1tb?_21oI>$N4R_(& zGk(q#)^?>!ql|Y-{|O5W6$$LqgF)K{kk?$YoBmWd;bszECHWyyY_Y3!!#>{qmGV8a z%ptu5mLsoOB?6Ld@D6!V80|O=jO{`yC-nx1#fC_?BCydVt(Q~%}gCP8`n4y!7fN4w1&;M;mB%E&vJ*) zdcjb3X;ZaLHjO07{pz^oi0G0CoFCwP^$SQW;a9rw&vqwN>iVp-`X5wsGdl6rcKu;`4hn5lS5OtY>V9YdTK@2_% zH!tkN)w%~dm85~iU!0d>i zI9fC|_EV#SaWWjjCuZCaX^ui*)r#zwYh_VM=3C6PGeL;ZM(Sz5!eqzI(0-U{Ymcvd zm{aHo3YLh%nb#8X$pk0xhgy?PuNYO1HNIRUnO}r47O%i5Ru<`Pgk^U~tyA{-&y2EH zgjyNB(9S>vE83-(1$Sof6=9W&o5Yd?K3~CUn43J}Ea7DTV$bJO%TVDCpK=z2gYQg! zZs=mX#T5g!O|q6VZuoxHJ|he|YHIU$>7*sPVFo|fLwCw8)WO)m+OHjr4>sM3_4Oh6 zsg_zBDSz~ki!Brrl@CYu?c}5Om^_>N`=faT1mEt&yDv@&*_0+0rlHL`oogkjbXvy= zADB0ZPifq|6`!Q+^RJc09>=7febIGIXpvBiT=u)*Ri|mw2tXY{uP}(sXYUEB8 zf|N~5$i!)bbQN8UammZ%=dxZD0u6r&ewrQ^(*?=&b)TGlMYM2sMoX*7(*pRkdqPhK z#7h!ec7C2vNXLCTxIN;@no^@Vyg8J9O3L@YfZldTw9IA!(;4d_is?r3sMJR6_XOfnTO!`N#REicqzU3?Go&!C0^ znCpTV%+bX$>5@W-njnsqaD$THq%t$9JLHU6!nL``(~WHM-sGd?SHhFCK@?9Vr#!Ti zj7{BZ5=09?aUz6gJBHHX6AzyW+t=xgiKCN?>GWiu)NtkFr!;j}gc*J$>vEPfHx6U; zHHJ}DNfO%jUhk@eZp}+ddB?4}HHII#OLYlCvkbZO*LI)wwBq&IUW?+>+h<3UyjYji z{ncdN)vn2WfZ>eA-e9VPAD&oyMWtPpsB<$Ibkqx|eGCJ%G$Oisn|3#L0e z)u=ZMYO;)8D@z-;ur5j}rAI65oAAUQr7p`z1{#Rvie!FO4m|%RWf9@S^w~)82Nl2@ zKUH7VWEf@LWG8H4AnVKOUd*&!7iuz(O>87YAr#hqj_6oS7!&_hVUp((UOgn!wIbmh?I%9I(=;1wk zvIq?UuDj|9M7RLQ0hsHQTDeSV#*p1^vAzVLbl zg#3ZM;ofD?BlOQ#HDQk69FPyP=XVTG!(H#wC`^IRyiS5%vxj{h0~Ma=0=~>@kaW!8 zKYT|;grdr1Lhxl{H%7~7R&Vgc0=78{1{>w!y;%2-3S&j#W3aAuUal~C-)m}zW^D1Ynu*uH%}w2u!5Kz zR@G*;y@Z(HvBMj^^!9^4{ygbvflF4@sX+HmT5w*>vH{w{u5~kmsPY#f_>^g$0fpSO z0l^ija*BsD)|K)oP7bAqqNsj2q3t$LNnWGDb*8acYcf1HCTSt~INGB2 z96CAGbZC%R{aWEdRKHWO>Ly`;gyfaW4v2=CU#;jm|79D{vkl^B7$pq=RqS;|9R{qJ zFe)dH!8(xPxsyZ+N#T)A!Z9V4LWxSw&wgnOGxYX~J;AQ?lCHNlv0k9e8Z1wu%uOqV zQ+rqRF+i!Pk5<|@fN>37;h>5^*wOO|>h)ftcE7=bvu^&EEHZ{S1o(|(-U-b4D;Kn{eik#E4Sa?JS z`-Ik!#0`>4tAEL(x6!jx8<}qXs4WB!8g22rByF0k)52!j15T4^6j9(Qx7?dkpGwnO zx8><({F4s~kwy^koAof#BOIRaT$I}P;HI*mYrK^Z-8qn-?W#w zQ*!ul_z`JjLC<2cg@s}!q26LeK}_=sh=mOuE}^fIRX)N*Ka9Pb1Hgg3@+j3)8ps)J ztSg)A$t7&8w|u+apsjz8z3tP~(v{uTTP#a|^$~C0SLUr7m{KkF zy6Q{vkk*0<9O>y;uspjvxgdBa7rBC=1q%tlu4PS^^y{U4R*gqYokk%WDf8F}mc}&Ku(bLic+@}xP{dpBSU=%) z;BP+K@&!ipNL|EpD0}y6#nW5H{sMY&M7%{8)bsa*OrgAYhdb8p(kY|we#6yQIw}no z7K}}|G+1ox&wr5n>(O<>u?(ZP5h{2ebmYGVgk!CVGwe`3IRo>NQ2GsZSa`wb_0Z-U z?6|m1iKV8c3ZLiKCmuuCD z>mt{#heOAP+V_afzSly55-L|w(9)$3h3K?I%nPxJl zfWVaD44-Hr9ydTk!to#Oi=J4huy1Anx?3>a#ST$SK_b1fCBkegGlaOOVxK6nYf{)eTM#W`Wyb{(7e4vLFrr!t1KtpAZRv|#8PRA=x+ zM?XsNGYeR@jAxL57>nKNR%eaYHl&!#j^TVFBX=xGYCN)ns!|fD@}h!NVoV=I(|GR$ zE8kP>%+}q2-${-0*>&TV-+9k7!3|G(x7tXqQ)PDB9Zxwko!~CMNU~Q=lv3nX0X=4h zd<_n&3Hk!I-fh%pC`d%Uw)3m!Se8#Y8yc-Obp)O<8j;azJ@o`^U7uZG#LQgWRq;Gd zfRKjiGgm^Mv=sgghhBv|l|7SV71~?&CxX)f*Q?f+VuAB^dZ6)i(US5Uz(&(?c6)-Z zNg`t(Yqf>-v7mV1ON9oXu0*-TE*>FD6Q2=ZgJ%f+G1F z(ClS&1$N-t7;S~~ni(bA1@7f|Tm@f+(-Bp8Rf zCoFiPPejK;^uEajcD>09%;~t9Je2L^*Vyw`>`Z^RkQ#Vw6%y=xBOoT)5fWVBSgN~? z{-8PUT166HkTg#{*H4cB;K3$dRr6E^tgFpkC3Nu!v=#-=;i{!aA3HtlBPi6vLy5Rd zK0o~mPAg)Y;IyfAx{44UH$0cjDNzdfA~Wy+we!aJLyk;_8X2 zif+-e$LL)mdy1yAkhJxpYrIG3<3s`Gj_D)1xLJ9$d8QDad8*C7N-I6k)L{VRVeTO$ zO0^t=f2To>6PoE+2l8QLGrag9M7>0dr9}p?dA5vjWx60xll!%if$y>=dBRXLL_CYW zs^jYOdg>3};4CD&xp(@%A3DCZ0nkzZU&vhi1E38zR|LpZF9HxTt0@9tVPO6*i}?S+ o!$0=$Uq%4~6Iq-oG63L$t?kP6_)k^r&#W2%kRtva_z%PQAInr${r~^~ delta 6798 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 From 6dd4bc638ee08491a0bae0839d0d650c0617239d Mon Sep 17 00:00:00 2001 From: Sven Van de Velde Date: Thu, 12 May 2016 14:54:52 +0200 Subject: [PATCH 06/11] Updates --- Maths/Bearing calculation.ggb | Bin 0 -> 7349 bytes 1 file changed, 0 insertions(+), 0 deletions(-) create mode 100644 Maths/Bearing calculation.ggb diff --git a/Maths/Bearing calculation.ggb b/Maths/Bearing calculation.ggb new file mode 100644 index 0000000000000000000000000000000000000000..affc37c069298c12337c7e7842ef8ed87643c109 GIT binary patch literal 7349 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 Date: Fri, 13 May 2016 12:46:08 +0200 Subject: [PATCH 07/11] Progress --- Embedded/Moose_Create_Embedded.bat | 2 +- Embedded/Moose_Embedded.lua | 1690 +++++++++++------ Moose/Client.lua | 4 +- Moose/MissileTrainer.lua | 157 +- Moose/Unit.lua | 9 +- ...ent - Part 1 - Tools and Installation.pptx | Bin ...rt 2 - Using Eclipse and MOOSE - Copy.pptx | Bin ...d - MOOSE - Escorting - Part 2 - APIs.pptx | Bin .../DCS World - MOOSE - Missile Trainer.pptx | Bin 0 -> 800876 bytes ...ld - MOOSE - Spawning - Part 2 - APIs.pptx | Bin .../Moose_Test_MISSILETRAINER.miz | Bin 115947 -> 224315 bytes 11 files changed, 1283 insertions(+), 579 deletions(-) rename {Tools and Installation => Presentations}/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx (100%) rename {Tools and Installation => Presentations}/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx (100%) rename {Tools and Installation => Presentations}/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx (100%) create mode 100644 Presentations/DCS World - MOOSE - Missile Trainer.pptx rename {Tools and Installation => Presentations}/DCS World - MOOSE - Spawning - Part 2 - APIs.pptx (100%) diff --git a/Embedded/Moose_Create_Embedded.bat b/Embedded/Moose_Create_Embedded.bat index c4e58833c..673cf37b3 100644 --- a/Embedded/Moose_Create_Embedded.bat +++ b/Embedded/Moose_Create_Embedded.bat @@ -9,12 +9,12 @@ copy /b Moose_Embedded.lua + ..\Moose\Menu.lua Moose_Embedded.l copy /b Moose_Embedded.lua + ..\Moose\Group.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Unit.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Zone.lua Moose_Embedded.lua +copy /b Moose_Embedded.lua + ..\Moose\Client.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Database.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Moose.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Scheduler.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Scoring.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Cargo.lua Moose_Embedded.lua -copy /b Moose_Embedded.lua + ..\Moose\Client.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Message.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Stage.lua Moose_Embedded.lua copy /b Moose_Embedded.lua + ..\Moose\Task.lua Moose_Embedded.lua diff --git a/Embedded/Moose_Embedded.lua b/Embedded/Moose_Embedded.lua index 2ceb43472..55c5cce27 100644 --- a/Embedded/Moose_Embedded.lua +++ b/Embedded/Moose_Embedded.lua @@ -1738,7 +1738,7 @@ function routines.getGroupRoute(groupIdent, task) -- same as getGroupPoints bu -- refactor to search by groupId and allow groupId and groupName as inputs local gpId = groupIdent if type(groupIdent) == 'string' and not tonumber(groupIdent) then - gpId = _DATABASE.Groups[groupIdent].groupId + gpId = _DATABASE.Templates.Groups[groupIdent].groupId end for coa_name, coa_data in pairs(env.mission.coalition) do @@ -3555,6 +3555,32 @@ function EVENT:OnHitForUnit( EventDCSUnitName, EventFunction, EventSelf ) return self end +--- Set a new listener for an S_EVENT_PLAYER_ENTER_UNIT event. +-- @param #EVENT self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @return #EVENT +function EVENT:OnPlayerEnterUnit( EventFunction, EventSelf ) + self:F() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_ENTER_UNIT ) + + return self +end + +--- Set a new listener for an S_EVENT_PLAYER_LEAVE_UNIT event. +-- @param #EVENT self +-- @param #function EventFunction The function to be called when the event occurs for the unit. +-- @param Base#BASE EventSelf The self instance of the class for which the event is. +-- @return #EVENT +function EVENT:OnPlayerLeaveUnit( EventFunction, EventSelf ) + self:F() + + self:OnEventGeneric( EventFunction, EventSelf, world.event.S_EVENT_PLAYER_LEAVE_UNIT ) + + return self +end + function EVENT:onEvent( Event ) @@ -3586,6 +3612,7 @@ function EVENT:onEvent( Event ) Event.WeaponName = Event.Weapon:getTypeName() --Event.WeaponTgtDCSUnit = Event.Weapon:getTarget() end + self:E( { _EVENTCODES[Event.id], Event } ) for ClassName, EventData in pairs( self.Events[Event.id] ) do if Event.IniDCSUnitName and EventData.IniUnit and EventData.IniUnit[Event.IniDCSUnitName] then self:T2( { "Calling event function for class ", ClassName, " unit ", Event.IniDCSUnitName } ) @@ -5057,7 +5084,7 @@ end function GROUP:GetTaskMission() self:F( self.GroupName ) - return routines.utils.deepCopy( _DATABASE.Groups[self.GroupName].Template ) + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template ) end --- Return the mission route of the group. @@ -5066,7 +5093,7 @@ end function GROUP:GetTaskRoute() self:F( self.GroupName ) - return routines.utils.deepCopy( _DATABASE.Groups[self.GroupName].Template.route.points ) + return routines.utils.deepCopy( _DATABASE.Templates.Groups[self.GroupName].Template.route.points ) end --- Return the route of a group by using the @{Database#DATABASE} class. @@ -5090,7 +5117,7 @@ function GROUP:CopyRoute( Begin, End, Randomize, Radius ) self:T( { GroupName } ) - local Template = _DATABASE.Groups[GroupName].Template + local Template = _DATABASE.Templates.Groups[GroupName].Template if Template then if not Begin then @@ -5523,11 +5550,13 @@ UNIT = { -- @return Unit#UNIT function UNIT:New( DCSUnit ) local self = BASE:Inherit( self, BASE:New() ) - self:F( DCSUnit:getName() ) + self:F( DCSUnit ) self.DCSUnit = DCSUnit - self.UnitName = DCSUnit:getName() - self.UnitID = DCSUnit:getID() + if DCSUnit then + self.UnitName = DCSUnit:getName() + self.UnitID = DCSUnit:getID() + end return self end @@ -5558,6 +5587,18 @@ function UNIT:GetName() return self.UnitName end +function UNIT:GetPlayerName() + self:F( self.UnitName ) + + local DCSUnit = Unit.getByName( self.UnitName ) + + local PlayerName = DCSUnit:getPlayerName() + if PlayerName == nil then + PlayerName = "" + end + + return PlayerName +end function UNIT:GetTypeName() self:F( self.UnitName ) @@ -5800,6 +5841,487 @@ function ZONE:GetRadius() return Zone.radius end +--- The CLIENT models client units in multi player missions. +-- Clients are those groups defined within the Mission Editor that have the skillset defined as "Client" or "Player". +-- Note that clients are NOT the same as groups, they are NOT necessarily alive. +-- @module Client +-- @author FlightControl + +Include.File( "Routines" ) +Include.File( "Base" ) +Include.File( "Cargo" ) +Include.File( "Message" ) + + +--- The CLIENT class +-- @type CLIENT +-- @extends Base#BASE +CLIENT = { + ONBOARDSIDE = { + NONE = 0, + LEFT = 1, + RIGHT = 2, + BACK = 3, + FRONT = 4 + }, + ClassName = "CLIENT", + ClientName = nil, + ClientAlive = false, + ClientTransport = false, + ClientBriefingShown = false, + _Menus = {}, + _Tasks = {}, + Messages = { + } +} + + +--- Use this method to register new Clients within the MOF. +-- @param #CLIENT self +-- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. +-- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. +-- @return #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 ) + local self = BASE:Inherit( self, BASE:New() ) + self:F( ClientName, ClientBriefing ) + + self.ClientName = ClientName + self:AddBriefing( ClientBriefing ) + self.MessageSwitch = true + + return self +end + +--- Transport defines that the Client is a Transport. Transports show cargo. +-- @param #CLIENT self +-- @return #CLIENT +function CLIENT:Transport() + self:F() + + self.ClientTransport = true + return self +end + +--- AddBriefing adds a briefing to a CLIENT when a player joins a mission. +-- @param #CLIENT self +-- @param #string ClientBriefing is the text defining the Mission briefing. +-- @return #CLIENT +function CLIENT:AddBriefing( ClientBriefing ) + self:F() + self.ClientBriefing = ClientBriefing + return self +end + + +--- Resets a CLIENT. +-- @param #CLIENT self +-- @param #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 ) + self:F() + self._Menus = {} +end + +--- Checks for a client alive event and calls a function on a continuous basis. +-- @param #CLIENT self +-- @param #function CallBack Function. +-- @return #CLIENT +function CLIENT:Alive( CallBack, ... ) + self:F() + + self.ClientAlive2 = false + self.ClientCallBack = CallBack + self.ClientParameters = arg + self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) + + return self +end + +-- Is Functions + +--- Checks if the CLIENT is a multi-seated UNIT. +-- @param #CLIENT self +-- @return #boolean true if multi-seated. +function CLIENT:IsMultiSeated() + self:F( self.ClientName ) + + local ClientMultiSeatedTypes = { + ["Mi-8MT"] = "Mi-8MT", + ["UH-1H"] = "UH-1H", + ["P-51B"] = "P-51B" + } + + if self:IsAlive() then + local ClientTypeName = self:GetClientGroupUnit():GetTypeName() + if ClientMultiSeatedTypes[ClientTypeName] then + return true + end + end + + return false +end + +--- Checks if client is alive and returns true or false. +-- @param #CLIENT self +-- @returns #boolean Returns true if client is alive. +function CLIENT:IsAlive() + self:F( self.ClientName ) + + local ClientUnit = Unit.getByName( self.ClientName ) + + if ClientUnit and ClientUnit:isExist() then + self:T("true") + return true + end + + self:T( "false" ) + return false +end + + +--- @param #CLIENT self +function CLIENT:_AliveCheckScheduler() + self:F( { self.ClientName, self.ClientAlive2 } ) + + if self:IsAlive() then + if self.ClientAlive2 == false then + self:T("Calling Callback function") + self.ClientCallBack( self, unpack( self.ClientParameters ) ) + self.ClientAlive2 = true + end + else + if self.ClientAlive2 == true then + self.ClientAlive2 = false + end + end +end + +--- Return the DCSGroup of a Client. +-- This function is modified to deal with a couple of bugs in DCS 1.5.3 +-- @param #CLIENT self +-- @return DCSGroup#Group +function CLIENT:GetDCSGroup() + self:F3() + +-- local ClientData = Group.getByName( self.ClientName ) +-- if ClientData and ClientData:isExist() then +-- self:T( self.ClientName .. " : group found!" ) +-- return ClientData +-- else +-- return nil +-- end + + local ClientUnit = Unit.getByName( self.ClientName ) + + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + self:T3( { "CoalitionData:", CoalitionData } ) + for UnitId, UnitData in pairs( CoalitionData ) do + self:T3( { "UnitData:", UnitData } ) + if UnitData and UnitData:isExist() then + + --self:E(self.ClientName) + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() and UnitData:getGroup():isExist() then + if ClientGroup:getID() == UnitData:getGroup():getID() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + self.ClientGroupID = ClientGroup:getID() + self.ClientGroupName = ClientGroup:getName() + return ClientGroup + end + else + -- Now we need to resolve the bugs in DCS 1.5 ... + -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) + self:T3( "Bug 1.5 logic" ) + local ClientGroupTemplate = _DATABASE.Templates.Units[self.ClientName].GroupTemplate + self.ClientGroupID = ClientGroupTemplate.groupId + self.ClientGroupName = _DATABASE.Templates.Units[self.ClientName].GroupName + self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) + return ClientGroup + end + -- else + -- error( "Client " .. self.ClientName .. " not found!" ) + end + end + end + end + end + + -- For non player clients + if ClientUnit then + local ClientGroup = ClientUnit:getGroup() + if ClientGroup then + self:T3( "ClientGroup = " .. self.ClientName ) + if ClientGroup:isExist() then + self:T3( "Normal logic" ) + self:T3( self.ClientName .. " : group found!" ) + return ClientGroup + end + end + end + + self.ClientGroupID = nil + self.ClientGroupUnit = nil + + return nil +end + + +-- TODO: Check DCSTypes#Group.ID +--- Get the group ID of the client. +-- @param #CLIENT self +-- @return DCSTypes#Group.ID +function CLIENT:GetClientGroupID() + + local ClientGroup = self:GetDCSGroup() + + --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() + return self.ClientGroupID +end + + +--- Get the name of the group of the client. +-- @param #CLIENT self +-- @return #string +function CLIENT:GetClientGroupName() + + local ClientGroup = self:GetDCSGroup() + + self:T( self.ClientGroupName ) -- Determined in GetDCSGroup() + return self.ClientGroupName +end + +--- Returns the UNIT of the CLIENT. +-- @param #CLIENT self +-- @return Unit#UNIT +function CLIENT:GetClientGroupUnit() + self:F2() + + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + self:T( self.ClientDCSUnit ) + if ClientDCSUnit and ClientDCSUnit:isExist() then + local ClientUnit = _DATABASE.Units[ self.ClientName ] + self:T2( ClientUnit ) + return ClientUnit + end +end + +--- Returns the DCSUnit of the CLIENT. +-- @param #CLIENT self +-- @return DCSTypes#Unit +function CLIENT:GetClientGroupDCSUnit() + self:F2() + + local ClientDCSUnit = Unit.getByName( self.ClientName ) + + if ClientDCSUnit and ClientDCSUnit:isExist() then + self:T2( ClientDCSUnit ) + return ClientDCSUnit + end +end + +-- TODO what is this??? check. possible double function. +function CLIENT:GetUnit() + self:F() + + return UNIT:New( self:GetClientGroupDCSUnit() ) +end + +--- Returns the position of the CLIENT in @{DCSTypes#Vec2} format.. +-- @param #CLIENT self +-- @return DCSTypes#Vec2 +function CLIENT:GetPointVec2() + self:F() + + ClientGroupUnit = self:GetClientGroupDCSUnit() + + if ClientGroupUnit then + if ClientGroupUnit:isExist() then + local PointVec3 = ClientGroupUnit:getPoint() --DCSTypes#Vec3 + local PointVec2 = {} --DCSTypes#Vec2 + PointVec2.x = PointVec3.x + PointVec2.y = PointVec3.z + self:T( { PointVec2 } ) + return PointVec2 + end + end + + return nil +end + +function CLIENT:GetPositionVec3() + self:F( self.ClientName ) + + local DCSUnit = Unit.getByName( self.ClientName ) + local UnitPos = DCSUnit:getPosition().p + + self:T( UnitPos ) + return UnitPos +end + +function CLIENT:GetID() + self:F( self.ClientName ) + + local DCSUnit = Unit.getByName( self.ClientName ) + local UnitID = DCSUnit:getID() + + self:T( UnitID ) + return UnitID +end + +function CLIENT:GetName() + self:F( self.ClientName ) + + self:T( self.ClientName ) + return self.ClientName +end + +function CLIENT:GetTypeName() + self:F( self.ClientName ) + + local DCSUnit = Unit.getByName( self.ClientName ) + local TypeName = DCSUnit:getTypeName() + + self:T( TypeName ) + return TypeName +end + + + +--- Returns the position of the CLIENT in @{DCSTypes#Vec3} format. +-- @param #CLIENT self +-- @return DCSTypes#Vec3 +function CLIENT:ClientPosition() + self:F() + + ClientGroupUnit = self:GetClientGroupDCSUnit() + + if ClientGroupUnit then + if ClientGroupUnit:isExist() then + return ClientGroupUnit:getPosition() + end + end + + return nil +end + +--- Returns the altitude of the CLIENT. +-- @param #CLIENT self +-- @return DCSTypes#Distance +function CLIENT:GetAltitude() + self:F() + + ClientGroupUnit = self:GetClientGroupDCSUnit() + + if ClientGroupUnit then + if ClientGroupUnit:isExist() then + local PointVec3 = ClientGroupUnit:getPoint() --DCSTypes#Vec3 + return PointVec3.y + end + end + + return nil +end + + +--- Evaluates if the CLIENT is a transport. +-- @param #CLIENT self +-- @return #boolean true is a transport. +function CLIENT:IsTransport() + self:F() + return self.ClientTransport +end + +--- Shows the @{Cargo#CARGO} contained within the CLIENT to the player as a message. +-- The @{Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. +-- @param #CLIENT self +function CLIENT:ShowCargo() + self:F() + + local CargoMsg = "" + + for CargoName, Cargo in pairs( CARGOS ) do + if self == Cargo:IsLoadedInClient() then + CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" + end + end + + if CargoMsg == "" then + CargoMsg = "empty" + end + + self:Message( CargoMsg, 15, self.ClientName .. "/Cargo", "Co-Pilot: Cargo Status", 30 ) + +end + +-- TODO (1) I urgently need to revise this. +--- A local function called by the DCS World Menu system to switch off messages. +function CLIENT.SwitchMessages( PrmTable ) + PrmTable[1].MessageSwitch = PrmTable[2] +end + +--- The main message driver for the CLIENT. +-- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. +-- @param #CLIENT self +-- @param #string Message is the text describing the message. +-- @param #number MessageDuration is the duration in seconds that the Message should be displayed. +-- @param #string MessageId is a text identifying the Message in the MessageQueue. The Message system overwrites Messages with the same MessageId +-- @param #string MessageCategory is the category of the message (the title). +-- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. +function CLIENT:Message( Message, MessageDuration, MessageId, MessageCategory, MessageInterval ) + self:F() + + if not self.MenuMessages then + if self:GetClientGroupID() then + self.MenuMessages = MENU_CLIENT:New( self, 'Messages' ) + self.MenuRouteMessageOn = MENU_CLIENT_COMMAND:New( self, 'Messages On', self.MenuMessages, CLIENT.SwitchMessages, { self, true } ) + self.MenuRouteMessageOff = MENU_CLIENT_COMMAND:New( self,'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 = 600 + else + self.Messages[MessageId].MessageInterval = MessageInterval + end + MESSAGE:New( Message, MessageCategory, MessageDuration, MessageId ):ToClient( self ) + else + if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then + if timer.getTime() - self.Messages[MessageId].MessageTime >= self.Messages[MessageId].MessageDuration + 10 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 --- Manage sets of units and groups. -- -- @{#Database} class @@ -5868,25 +6390,35 @@ Include.File( "Routines" ) Include.File( "Base" ) Include.File( "Menu" ) Include.File( "Group" ) +Include.File( "Unit" ) Include.File( "Event" ) +Include.File( "Client" ) --- DATABASE class -- @type DATABASE -- @extends Base#BASE DATABASE = { ClassName = "DATABASE", + Templates = { + Units = {}, + Groups = {}, + ClientsByName = {}, + ClientsByID = {}, + }, DCSUnits = {}, DCSUnitsAlive = {}, - Units = {}, - Groups = {}, DCSGroups = {}, DCSGroupsAlive = {}, + Units = {}, + UnitsAlive = {}, + Groups = {}, + GroupsAlive = {}, NavPoints = {}, Statics = {}, Players = {}, - AlivePlayers = {}, - ClientsByName = {}, - ClientsByID = {}, + PlayersAlive = {}, + Clients = {}, + ClientsAlive = {}, Filter = { Coalitions = nil, Categories = nil, @@ -5942,6 +6474,14 @@ function DATABASE:New() _EVENTDISPATCHER:OnDead( self._EventOnDeadOrCrash, self ) _EVENTDISPATCHER:OnCrash( self._EventOnDeadOrCrash, self ) + + -- Add database with registered clients and already alive players + + -- Follow alive players and clients + _EVENTDISPATCHER:OnPlayerEnterUnit( self._EventOnPlayerEnterUnit, self ) + _EVENTDISPATCHER:OnPlayerLeaveUnit( self._EventOnPlayerLeaveUnit, self ) + + return self end @@ -6062,16 +6602,43 @@ function DATABASE:FilterStart() -- OK, we have a _DATABASE -- Now use the different filters to build the set. -- We first take ALL of the Units of the _DATABASE. - for UnitRegistrationID, UnitRegistration in pairs( _DATABASE.Units ) do - self:T( UnitRegistration ) - local DCSUnit = Unit.getByName( UnitRegistration.UnitName ) + + self:E( { "Adding Database Datapoints with filters" } ) + for DCSUnitName, DCSUnit in pairs( _DATABASE.DCSUnits ) do + if self:_IsIncludeDCSUnit( DCSUnit ) then - self.DCSUnits[DCSUnit:getName()] = DCSUnit - end - if self:_IsAliveDCSUnit( DCSUnit ) then - self.DCSUnitsAlive[DCSUnit:getName()] = DCSUnit + + self:E( { "Adding Unit:", DCSUnitName } ) + self.DCSUnits[DCSUnitName] = _DATABASE.DCSUnits[DCSUnitName] + self.Units[DCSUnitName] = _DATABASE.Units[DCSUnitName] + + if _DATABASE.DCSUnitsAlive[DCSUnitName] then + self.DCSUnitsAlive[DCSUnitName] = _DATABASE.DCSUnitsAlive[DCSUnitName] + self.UnitsAlive[DCSUnitName] = _DATABASE.UnitsAlive[DCSUnitName] + end + end end + + for DCSGroupName, DCSGroup in pairs( _DATABASE.DCSGroups ) do + + --if self:_IsIncludeDCSGroup( DCSGroup ) then + self:E( { "Adding Group:", DCSGroupName } ) + self.DCSGroups[DCSGroupName] = _DATABASE.DCSGroups[DCSGroupName] + self.Groups[DCSGroupName] = _DATABASE.Groups[DCSGroupName] + --end + + if _DATABASE.DCSGroupsAlive[DCSGroupName] then + self.DCSGroupsAlive[DCSGroupName] = _DATABASE.DCSGroupsAlive[DCSGroupName] + self.GroupsAlive[DCSGroupName] = _DATABASE.GroupsAlive[DCSGroupName] + end + end + + for DCSUnitName, Client in pairs( _DATABASE.Clients ) do + self:E( { "Adding Client for Unit:", DCSUnitName } ) + self.Clients[DCSUnitName] = _DATABASE.Clients[DCSUnitName] + end + else self:E( "There is a structural error in MOOSE. No _DATABASE has been defined! Cannot build this custom DATABASE." ) end @@ -6120,7 +6687,7 @@ end function DATABASE:SetStatusGroup( GroupName, Status ) self:F( Status ) - self.Groups[GroupName].Status = Status + self.Templates.Groups[GroupName].Status = Status end @@ -6128,8 +6695,8 @@ end function DATABASE:GetStatusGroup( GroupName ) self:F( Status ) - if self.Groups[GroupName] then - return self.Groups[GroupName].Status + if self.Templates.Groups[GroupName] then + return self.Templates.Groups[GroupName].Status else return "" end @@ -6143,9 +6710,9 @@ function DATABASE:_RegisterGroup( GroupTemplate ) local GroupTemplateName = env.getValueDictByKey(GroupTemplate.name) - if not self.Groups[GroupTemplateName] then - self.Groups[GroupTemplateName] = {} - self.Groups[GroupTemplateName].Status = nil + if not self.Templates.Groups[GroupTemplateName] then + self.Templates.Groups[GroupTemplateName] = {} + self.Templates.Groups[GroupTemplateName].Status = nil end -- Delete the spans from the route, it is not needed and takes memory. @@ -6153,31 +6720,102 @@ function DATABASE:_RegisterGroup( GroupTemplate ) GroupTemplate.route.spans = nil end - self.Groups[GroupTemplateName].GroupName = GroupTemplateName - self.Groups[GroupTemplateName].Template = GroupTemplate - self.Groups[GroupTemplateName].groupId = GroupTemplate.groupId - self.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units - self.Groups[GroupTemplateName].Units = GroupTemplate.units + self.Templates.Groups[GroupTemplateName].GroupName = GroupTemplateName + self.Templates.Groups[GroupTemplateName].Template = GroupTemplate + self.Templates.Groups[GroupTemplateName].groupId = GroupTemplate.groupId + self.Templates.Groups[GroupTemplateName].UnitCount = #GroupTemplate.units + self.Templates.Groups[GroupTemplateName].Units = GroupTemplate.units - self:T( { "Group", self.Groups[GroupTemplateName].GroupName, self.Groups[GroupTemplateName].UnitCount } ) + self:T( { "Group", self.Templates.Groups[GroupTemplateName].GroupName, self.Templates.Groups[GroupTemplateName].UnitCount } ) - for unit_num, UnitTemplate in pairs(GroupTemplate.units) do + 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 + self.Templates.Units[UnitTemplateName] = {} + self.Templates.Units[UnitTemplateName].UnitName = UnitTemplateName + self.Templates.Units[UnitTemplateName].Template = UnitTemplate + self.Templates.Units[UnitTemplateName].GroupName = GroupTemplateName + self.Templates.Units[UnitTemplateName].GroupTemplate = GroupTemplate + self.Templates.Units[UnitTemplateName].GroupId = GroupTemplate.groupId + self:E( {"skill",UnitTemplate.skill}) if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then - self.ClientsByName[UnitTemplateName] = UnitTemplate - self.ClientsByID[UnitTemplate.unitId] = UnitTemplate + self.Templates.ClientsByName[UnitTemplateName] = UnitTemplate + self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end - self:E( { "Unit", self.Units[UnitTemplateName].UnitName } ) + self:E( { "Unit", self.Templates.Units[UnitTemplateName].UnitName } ) end end +--- Private method that registers all alive players in the mission. +-- @param #DATABASE self +-- @return #DATABASE self +function DATABASE:_RegisterPlayers() + + local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + for UnitId, UnitData in pairs( CoalitionData ) do + self:T3( { "UnitData:", UnitData } ) + if UnitData and UnitData:isExist() then + local UnitName = UnitData:getName() + if not self.PlayersAlive[UnitName] then + self:E( { "Add player for unit:", UnitName, UnitData:getPlayerName() } ) + self.PlayersAlive[UnitName] = UnitData:getPlayerName() + end + end + end + end + + return self +end + +--- Private method that registers all datapoints within in the mission. +-- @param #DATABASE self +-- @return #DATABASE self +function DATABASE:_RegisterDatabase() + + local CoalitionsData = { AlivePlayersRed = coalition.getGroups( coalition.side.RED ), AlivePlayersBlue = coalition.getGroups( coalition.side.BLUE ) } + for CoalitionId, CoalitionData in pairs( CoalitionsData ) do + for DCSGroupId, DCSGroup in pairs( CoalitionData ) do + + local DCSGroupName = DCSGroup:getName() + + self:E( { "Register Group:", DCSGroup, DCSGroupName } ) + self.DCSGroups[DCSGroupName] = DCSGroup + self.Groups[DCSGroupName] = GROUP:New( DCSGroup ) + + if self:_IsAliveDCSGroup(DCSGroup) then + self:E( { "Register Alive Group:", DCSGroup, DCSGroupName } ) + self.DCSGroupsAlive[DCSGroupName] = DCSGroup + self.GroupsAlive[DCSGroupName] = self.Groups[DCSGroupName] + end + + for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do + + local DCSUnitName = DCSUnit:getName() + self:E( { "Register Unit:", DCSUnit, DCSUnitName } ) + + self.DCSUnits[DCSUnitName] = DCSUnit + self.Units[DCSUnitName] = UNIT:New( DCSUnit ) + + if self:_IsAliveDCSUnit(DCSUnit) then + self:E( { "Register Alive Unit:", DCSUnit, DCSUnitName } ) + self.DCSUnitsAlive[DCSUnitName] = DCSUnit + self.UnitsAlive[DCSUnitName] = self.Units[DCSUnitName] + end + end + + for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do + self.Clients[ClientName] = CLIENT:New( ClientName ) + end + end + end + + return self +end + + +--- Events + --- Handles the OnBirth event for the alive units set. -- @param #DATABASE self -- @param Event#EVENTDATA Event @@ -6187,7 +6825,15 @@ function DATABASE:_EventOnBirth( Event ) if Event.IniDCSUnit then if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then self.DCSUnits[Event.IniDCSUnitName] = Event.IniDCSUnit - self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit + self.DCSUnitsAlive[Event.IniDCSUnitName] = Event.IniDCSUnit + self.Units[Event.IniDCSUnitName] = UNIT:New( Event.IniDCSUnit ) + + --if not self.DCSGroups[Event.IniDCSGroupName] then + -- self.DCSGroups[Event.IniDCSGroupName] = Event.IniDCSGroupName + -- self.DCSGroupsAlive[Event.IniDCSGroupName] = Event.IniDCSGroupName + -- self.Groups[Event.IniDCSGroupName] = GROUP:New( Event.IniDCSGroup ) + --end + self:_EventOnPlayerEnterUnit( Event ) end end end @@ -6206,18 +6852,54 @@ function DATABASE:_EventOnDeadOrCrash( Event ) end end ---- Interate the DATABASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. +--- Handles the OnPlayerEnterUnit event to fill the active players table (with the unit filter applied). -- @param #DATABASE self --- @param #function IteratorFunction The function that will be called when there is an alive unit in the database. The function needs to accept a UNIT parameter. +-- @param Event#EVENTDATA Event +function DATABASE:_EventOnPlayerEnterUnit( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then + if not self.PlayersAlive[Event.IniDCSUnitName] then + self:E( { "Add player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) + self.PlayersAlive[Event.IniDCSUnitName] = Event.IniDCSUnit:getPlayerName() + self.ClientsAlive[Event.IniDCSUnitName] = _DATABASE.Clients[ Event.IniDCSUnitName ] + end + end + end +end + +--- Handles the OnPlayerLeaveUnit event to clean the active players table. +-- @param #DATABASE self +-- @param Event#EVENTDATA Event +function DATABASE:_EventOnPlayerLeaveUnit( Event ) + self:F( { Event } ) + + if Event.IniDCSUnit then + if self:_IsIncludeDCSUnit( Event.IniDCSUnit ) then + if self.PlayersAlive[Event.IniDCSUnitName] then + self:E( { "Cleaning player for unit:", Event.IniDCSUnitName, Event.IniDCSUnit:getPlayerName() } ) + self.PlayersAlive[Event.IniDCSUnitName] = nil + self.ClientsAlive[Event.IniDCSUnitName] = nil + end + end + end +end + +--- Iterators + +--- Interate the DATABASE and call an interator function for the given set, providing the Object for each element within the set and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. -- @return #DATABASE self -function DATABASE:ForEachAliveUnit( IteratorFunction, ... ) +function DATABASE:ForEach( IteratorFunction, arg, Set ) self:F( arg ) local function CoRoutine() local Count = 0 - for DCSUnitID, DCSUnit in pairs( self.DCSUnitsAlive ) do - self:T2( DCSUnit ) - IteratorFunction( DCSUnit, unpack( arg ) ) + for ObjectID, Object in pairs( Set ) do + self:T2( Object ) + IteratorFunction( Object, unpack( arg ) ) Count = Count + 1 if Count % 10 == 0 then coroutine.yield( false ) @@ -6237,15 +6919,56 @@ function DATABASE:ForEachAliveUnit( IteratorFunction, ... ) error( res ) end if res == false then - timer.scheduleFunction( Schedule, {}, timer.getTime() + 0.001 ) + return true -- resume next time the loop end + + return false end - timer.scheduleFunction( Schedule, {}, timer.getTime() + 1 ) + local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) return self end + +--- Interate the DATABASE and call an interator function for each **alive** unit, providing the Unit and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive unit in the database. The function needs to accept a UNIT parameter. +-- @return #DATABASE self +function DATABASE:ForEachDCSUnitAlive( IteratorFunction, ... ) + self:F( arg ) + + self:ForEach( IteratorFunction, arg, self.DCSUnitsAlive ) + + return self +end + +--- Interate the DATABASE and call an interator function for each **alive** player, providing the Unit of the player and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a UNIT parameter. +-- @return #DATABASE self +function DATABASE:ForEachPlayer( IteratorFunction, ... ) + self:F( arg ) + + self:ForEach( IteratorFunction, arg, self.PlayersAlive ) + + return self +end + + +--- Interate the DATABASE and call an interator function for each client, providing the Client to the function and optional parameters. +-- @param #DATABASE self +-- @param #function IteratorFunction The function that will be called when there is an alive player in the database. The function needs to accept a CLIENT parameter. +-- @return #DATABASE self +function DATABASE:ForEachClient( IteratorFunction, ... ) + self:F( arg ) + + self:ForEach( IteratorFunction, arg, self.Clients ) + + return self +end + + function DATABASE:ScanEnvironment() self:F() @@ -6309,6 +7032,9 @@ function DATABASE:ScanEnvironment() 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:_RegisterDatabase() + self:_RegisterPlayers() + return self end @@ -6396,6 +7122,22 @@ function DATABASE:_IsAliveDCSUnit( DCSUnit ) return DCSUnitAlive end +--- +-- @param #DATABASE self +-- @param DCSGroup#Group DCSGroup +-- @return #DATABASE self +function DATABASE:_IsAliveDCSGroup( DCSGroup ) + self:F( DCSGroup ) + local DCSGroupAlive = false + if DCSGroup and DCSGroup:isExist() then + if self.DCSGroups[DCSGroup:getName()] then + DCSGroupAlive = true + end + end + self:T( DCSGroupAlive ) + return DCSGroupAlive +end + --- Traces the current database contents in the log ... (for debug reasons). -- @param #DATABASE self @@ -8428,461 +9170,6 @@ function CARGO_SLINGLOAD:UnLoad( Client, TargetZoneName ) return Cargo end ---- The CLIENT models client units in multi player missions. --- Clients are those groups defined within the Mission Editor that have the skillset defined as "Client" or "Player". --- Note that clients are NOT the same as groups, they are NOT necessarily alive. --- @module Client --- @author FlightControl - -Include.File( "Routines" ) -Include.File( "Base" ) -Include.File( "Cargo" ) -Include.File( "Message" ) - - ---- The CLIENT class --- @type CLIENT --- @extends Base#BASE -CLIENT = { - ONBOARDSIDE = { - NONE = 0, - LEFT = 1, - RIGHT = 2, - BACK = 3, - FRONT = 4 - }, - ClassName = "CLIENT", - ClientName = nil, - ClientAlive = false, - ClientTransport = false, - ClientBriefingShown = false, - _Menus = {}, - _Tasks = {}, - Messages = { - } -} - - ---- Use this method to register new Clients within the MOF. --- @param #CLIENT self --- @param #string ClientName Name of the Group as defined within the Mission Editor. The Group must have a Unit with the type Client. --- @param #string ClientBriefing Text that describes the briefing of the mission when a Player logs into the Client. --- @return #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 ) - local self = BASE:Inherit( self, BASE:New() ) - self:F( ClientName, ClientBriefing ) - - self.ClientName = ClientName - self:AddBriefing( ClientBriefing ) - self.MessageSwitch = true - - return self -end - ---- Transport defines that the Client is a Transport. Transports show cargo. --- @param #CLIENT self --- @return #CLIENT -function CLIENT:Transport() - self:F() - - self.ClientTransport = true - return self -end - ---- AddBriefing adds a briefing to a CLIENT when a player joins a mission. --- @param #CLIENT self --- @param #string ClientBriefing is the text defining the Mission briefing. --- @return #CLIENT -function CLIENT:AddBriefing( ClientBriefing ) - self:F() - self.ClientBriefing = ClientBriefing - return self -end - - ---- Resets a CLIENT. --- @param #CLIENT self --- @param #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 ) - self:F() - self._Menus = {} -end - ---- Checks for a client alive event and calls a function on a continuous basis. --- @param #CLIENT self --- @param #function CallBack Function. --- @return #CLIENT -function CLIENT:Alive( CallBack, ... ) - self:F() - - self.ClientAlive2 = false - self.ClientCallBack = CallBack - self.ClientParameters = arg - self.AliveCheckScheduler = routines.scheduleFunction( self._AliveCheckScheduler, { self }, timer.getTime() + 1, 5 ) - - return self -end - --- Is Functions - ---- Checks if the CLIENT is a multi-seated UNIT. --- @param #CLIENT self --- @return #boolean true if multi-seated. -function CLIENT:IsMultiSeated() - self:F( self.ClientName ) - - local ClientMultiSeatedTypes = { - ["Mi-8MT"] = "Mi-8MT", - ["UH-1H"] = "UH-1H", - ["P-51B"] = "P-51B" - } - - if self:IsAlive() then - local ClientTypeName = self:GetClientGroupUnit():GetTypeName() - if ClientMultiSeatedTypes[ClientTypeName] then - return true - end - end - - return false -end - ---- Checks if client is alive and returns true or false. --- @param #CLIENT self --- @returns #boolean Returns true if client is alive. -function CLIENT:IsAlive() - self:F( self.ClientName ) - - local ClientDCSGroup = self:GetDCSGroup() - - if ClientDCSGroup then - self:T("true") - return true - end - - self:T( "false" ) - return false -end - - ---- @param #CLIENT self -function CLIENT:_AliveCheckScheduler() - self:F( { self.ClientName, self.ClientAlive2 } ) - - if self:IsAlive() then - if self.ClientAlive2 == false then - self:T("Calling Callback function") - self.ClientCallBack( self, unpack( self.ClientParameters ) ) - self.ClientAlive2 = true - end - else - if self.ClientAlive2 == true then - self.ClientAlive2 = false - end - end -end - ---- Return the DCSGroup of a Client. --- This function is modified to deal with a couple of bugs in DCS 1.5.3 --- @param #CLIENT self --- @return DCSGroup#Group -function CLIENT:GetDCSGroup() - self:F3() - --- local ClientData = Group.getByName( self.ClientName ) --- if ClientData and ClientData:isExist() then --- self:T( self.ClientName .. " : group found!" ) --- return ClientData --- else --- return nil --- end - - local CoalitionsData = { AlivePlayersRed = coalition.getPlayers( coalition.side.RED ), AlivePlayersBlue = coalition.getPlayers( coalition.side.BLUE ) } - for CoalitionId, CoalitionData in pairs( CoalitionsData ) do - self:T3( { "CoalitionData:", CoalitionData } ) - for UnitId, UnitData in pairs( CoalitionData ) do - self:T3( { "UnitData:", UnitData } ) - if UnitData and UnitData:isExist() then - - local ClientGroup = Group.getByName( self.ClientName ) - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - if ClientGroup:getID() == UnitData:getGroup():getID() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - else - -- Now we need to resolve the bugs in DCS 1.5 ... - -- Consult the database for the units of the Client Group. (ClientGroup:getUnits() returns nil) - self:T3( "Bug 1.5 logic" ) - local ClientUnits = _DATABASE.Groups[self.ClientName].Units - self:T3( { ClientUnits[1].name, env.getValueDictByKey(ClientUnits[1].name) } ) - for ClientUnitID, ClientUnitData in pairs( ClientUnits ) do - self:T3( { tonumber(UnitData:getID()), ClientUnitData.unitId } ) - if tonumber(UnitData:getID()) == ClientUnitData.unitId then - local ClientGroupTemplate = _DATABASE.Groups[self.ClientName].Template - self.ClientID = ClientGroupTemplate.groupId - self.ClientGroupUnit = UnitData - self:T3( self.ClientName .. " : group found in bug 1.5 resolvement logic!" ) - return ClientGroup - end - end - end --- else --- error( "Client " .. self.ClientName .. " not found!" ) - end - end - end - end - - -- For non player clients - local ClientGroup = Group.getByName( self.ClientName ) - if ClientGroup then - self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then - self:T3( "Normal logic" ) - self:T3( self.ClientName .. " : group found!" ) - return ClientGroup - end - end - - self.ClientGroupID = nil - self.ClientGroupUnit = nil - - return nil -end - - --- TODO: Check DCSTypes#Group.ID ---- Get the group ID of the client. --- @param #CLIENT self --- @return DCSTypes#Group.ID -function CLIENT:GetClientGroupID() - - if not self.ClientGroupID then - local ClientGroup = self:GetDCSGroup() - if ClientGroup and ClientGroup:isExist() then - self.ClientGroupID = ClientGroup:getID() - else - self.ClientGroupID = self.ClientID - end - end - - self:T( self.ClientGroupID ) - return self.ClientGroupID -end - - ---- Get the name of the group of the client. --- @param #CLIENT self --- @return #string -function CLIENT:GetClientGroupName() - - if not self.ClientGroupName then - local ClientGroup = self:GetDCSGroup() - if ClientGroup and ClientGroup:isExist() then - self.ClientGroupName = ClientGroup:getName() - else - self.ClientGroupName = self.ClientName - end - end - - self:T( self.ClientGroupName ) - return self.ClientGroupName -end - ---- Returns the UNIT of the CLIENT. --- @param #CLIENT self --- @return Unit#UNIT -function CLIENT:GetClientGroupUnit() - self:F() - - local ClientGroup = self:GetDCSGroup() - - if ClientGroup and ClientGroup:isExist() then - return UNIT:New( ClientGroup:getUnit(1) ) - else - return UNIT:New( self.ClientGroupUnit ) - end -end - ---- Returns the DCSUnit of the CLIENT. --- @param #CLIENT self --- @return DCSTypes#Unit -function CLIENT:GetClientGroupDCSUnit() - self:F2() - - local ClientGroup = self:GetDCSGroup() - - if ClientGroup and ClientGroup:isExist() then - return ClientGroup:getUnit(1) - else - return self.ClientGroupUnit - end -end - --- TODO what is this??? check. possible double function. -function CLIENT:GetUnit() - self:F() - - return UNIT:New( self:GetClientGroupDCSUnit() ) -end - ---- Returns the position of the CLIENT in @{DCSTypes#Vec2} format.. --- @param #CLIENT self --- @return DCSTypes#Vec2 -function CLIENT:GetPointVec2() - self:F() - - ClientGroupUnit = self:GetClientGroupDCSUnit() - - if ClientGroupUnit then - if ClientGroupUnit:isExist() then - local PointVec3 = ClientGroupUnit:getPoint() --DCSTypes#Vec3 - local PointVec2 = {} --DCSTypes#Vec2 - PointVec2.x = PointVec3.x - PointVec2.y = PointVec3.z - self:T( { PointVec2 } ) - return PointVec2 - end - end - - return nil -end - - ---- Returns the position of the CLIENT in @{DCSTypes#Vec3} format. --- @param #CLIENT self --- @return DCSTypes#Vec3 -function CLIENT:ClientPosition() - self:F() - - ClientGroupUnit = self:GetClientGroupDCSUnit() - - if ClientGroupUnit then - if ClientGroupUnit:isExist() then - return ClientGroupUnit:getPosition() - end - end - - return nil -end - ---- Returns the altitude of the CLIENT. --- @param #CLIENT self --- @return DCSTypes#Distance -function CLIENT:GetAltitude() - self:F() - - ClientGroupUnit = self:GetClientGroupDCSUnit() - - if ClientGroupUnit then - if ClientGroupUnit:isExist() then - local PointVec3 = ClientGroupUnit:getPoint() --DCSTypes#Vec3 - return PointVec3.y - end - end - - return nil -end - - ---- Evaluates if the CLIENT is a transport. --- @param #CLIENT self --- @return #boolean true is a transport. -function CLIENT:IsTransport() - self:F() - return self.ClientTransport -end - ---- Shows the @{Cargo#CARGO} contained within the CLIENT to the player as a message. --- The @{Cargo#CARGO} is shown using the @{Message#MESSAGE} distribution system. --- @param #CLIENT self -function CLIENT:ShowCargo() - self:F() - - local CargoMsg = "" - - for CargoName, Cargo in pairs( CARGOS ) do - if self == Cargo:IsLoadedInClient() then - CargoMsg = CargoMsg .. Cargo.CargoName .. " Type:" .. Cargo.CargoType .. " Weight: " .. Cargo.CargoWeight .. "\n" - end - end - - if CargoMsg == "" then - CargoMsg = "empty" - end - - self:Message( CargoMsg, 15, self.ClientName .. "/Cargo", "Co-Pilot: Cargo Status", 30 ) - -end - --- TODO (1) I urgently need to revise this. ---- A local function called by the DCS World Menu system to switch off messages. -function CLIENT.SwitchMessages( PrmTable ) - PrmTable[1].MessageSwitch = PrmTable[2] -end - ---- The main message driver for the CLIENT. --- This function displays various messages to the Player logged into the CLIENT through the DCS World Messaging system. --- @param #CLIENT self --- @param #string Message is the text describing the message. --- @param #number MessageDuration is the duration in seconds that the Message should be displayed. --- @param #string MessageId is a text identifying the Message in the MessageQueue. The Message system overwrites Messages with the same MessageId --- @param #string MessageCategory is the category of the message (the title). --- @param #number MessageInterval is the interval in seconds between the display of the @{Message#MESSAGE} when the CLIENT is in the air. -function CLIENT:Message( Message, MessageDuration, MessageId, MessageCategory, MessageInterval ) - self:F() - - if not self.MenuMessages then - if self:GetClientGroupID() then - self.MenuMessages = MENU_CLIENT:New( self, 'Messages' ) - self.MenuRouteMessageOn = MENU_CLIENT_COMMAND:New( self, 'Messages On', self.MenuMessages, CLIENT.SwitchMessages, { self, true } ) - self.MenuRouteMessageOff = MENU_CLIENT_COMMAND:New( self,'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 = 600 - else - self.Messages[MessageId].MessageInterval = MessageInterval - end - MESSAGE:New( Message, MessageCategory, MessageDuration, MessageId ):ToClient( self ) - else - if self:GetClientGroupDCSUnit() and not self:GetClientGroupDCSUnit():inAir() then - if timer.getTime() - self.Messages[MessageId].MessageTime >= self.Messages[MessageId].MessageDuration + 10 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 --- 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. @@ -13156,7 +13443,7 @@ function SPAWN:_GetTemplate( SpawnTemplatePrefix ) local SpawnTemplate = nil - SpawnTemplate = routines.utils.deepCopy( _DATABASE.Groups[SpawnTemplatePrefix].Template ) + SpawnTemplate = routines.utils.deepCopy( _DATABASE.Templates.Groups[SpawnTemplatePrefix].Template ) if SpawnTemplate == nil then error( 'No Template returned for SpawnTemplatePrefix = ' .. SpawnTemplatePrefix ) @@ -13660,7 +13947,7 @@ function SEAD:EventShot( Event ) local _targetMimgroup = Unit.getGroup(Weapon.getTarget(SEADWeapon)) local _targetMimgroupName = _targetMimgroup:getName() local _targetMimcont= _targetMimgroup:getController() - local _targetskill = _DATABASE.Units[_targetMimname].Template.skill + local _targetskill = _DATABASE.Templates.Units[_targetMimname].Template.skill self:T( self.SEADGroupPrefixes ) self:T( _targetMimgroupName ) local SEADGroupFound = false @@ -15001,15 +15288,183 @@ function MISSILETRAINER:New( Distance ) self.Schedulers = {} self.SchedulerID = 0 - self.Distance = Distance + self.MessageInterval = 2 + self.MessageLastTime = timer.getTime() + + self.Distance = Distance / 1000 _EVENTDISPATCHER:OnShot( self._EventShot, self ) + self.DB = DATABASE:New():FilterStart() + self.DBClients = self.DB.Clients + self.DBUnits = self.DB.Units + + for ClientID, Client in pairs( self.DBClients ) do + + local function _Alive( Client ) + + Client:Message( "Hello trainee, welcome to the Missile Trainer.\nUse the F10->F2 menu options in the radio menu to change the Missile Trainer settings.\nGood luck!", 10, "ID", "Trainer" ) + + Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) + + Client.MenuMessages = MENU_CLIENT:New( Client, "Messages", Client.MainMenu ) + Client.MenuOn = MENU_CLIENT_COMMAND:New( Client, "Messages On", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = true } ) + Client.MenuOff = MENU_CLIENT_COMMAND:New( Client, "Messages Off", Client.MenuMessages, self._MenuMessages, { MenuSelf = self, MessagesOnOff = false } ) + + Client.MenuTracking = MENU_CLIENT:New( Client, "Tracking", Client.MainMenu ) + Client.MenuTrackingToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = true } ) + Client.MenuTrackingToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, TrackingToAll = false } ) + Client.MenuTrackOn = MENU_CLIENT_COMMAND:New( Client, "Tracking On", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = true } ) + Client.MenuTrackOff = MENU_CLIENT_COMMAND:New( Client, "Tracking Off", Client.MenuTracking, self._MenuMessages, { MenuSelf = self, Tracking = false } ) + + Client.MenuAlerts = MENU_CLIENT:New( Client, "Alerts", Client.MainMenu ) + Client.MenuAlertsToAll = MENU_CLIENT_COMMAND:New( Client, "To All", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = true } ) + Client.MenuAlertsToTarget = MENU_CLIENT_COMMAND:New( Client, "To Target", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsToAll = false } ) + Client.MenuHitsOn = MENU_CLIENT_COMMAND:New( Client, "Hits On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = true } ) + Client.MenuHitsOff = MENU_CLIENT_COMMAND:New( Client, "Hits Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsHits = false } ) + Client.MenuLaunchesOn = MENU_CLIENT_COMMAND:New( Client, "Launches On", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = true } ) + Client.MenuLaunchesOff = MENU_CLIENT_COMMAND:New( Client, "Launches Off", Client.MenuAlerts, self._MenuMessages, { MenuSelf = self, AlertsLaunches = false } ) + + Client.MenuDetails = MENU_CLIENT:New( Client, "Details", Client.MainMenu ) + Client.MenuDetailsDistanceOn = MENU_CLIENT_COMMAND:New( Client, "Range On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = true } ) + Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = false } ) + Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = true } ) + Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = false } ) + + Client.MenuDistance = MENU_CLIENT:New( Client, "Set distance to plane", Client.MainMenu ) + Client.MenuDistance50 = MENU_CLIENT_COMMAND:New( Client, "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } ) + Client.MenuDistance100 = MENU_CLIENT_COMMAND:New( Client, "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } ) + Client.MenuDistance150 = MENU_CLIENT_COMMAND:New( Client, "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } ) + Client.MenuDistance200 = MENU_CLIENT_COMMAND:New( Client, "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } ) + + + local ClientID = Client:GetID() + self:T( ClientID ) + if not self.TrackingMissiles[ClientID] then + self.TrackingMissiles[ClientID] = {} + end + self.TrackingMissiles[ClientID].Client = Client + if not self.TrackingMissiles[ClientID].MissileData then + self.TrackingMissiles[ClientID].MissileData = {} + end + end + + Client:Alive( _Alive ) + + end +-- self.DB:ForEachClient( +-- --- @param Client#CLIENT Client +-- function( Client ) +-- +-- end +-- ) + + self.MessagesOnOff = true + + self.TrackingToAll = false + self.Tracking = true + + self.AlertsToAll = true + self.AlertsHits = true + self.AlertsLaunches = true + + self.DetailsRange = true + self.DetailsBearing = true + + self.TrackingMissiles = {} + + self.TrackingScheduler = SCHEDULER:New( self, self._TrackMissiles, {}, 0.5, 0.05, 0 ) + return self end +function MISSILETRAINER._MenuMessages( MenuParameters ) + + local self = MenuParameters.MenuSelf + + if MenuParameters.MessagesOnOff ~= nil then + self.MessagesOnOff = MenuParameters.MessagesOnOff + if self.MessagesOnOff == true then + MESSAGE:New( "Messages ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Messages OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.TrackingToAll ~= nil then + self.TrackingToAll = MenuParameters.TrackingToAll + if self.TrackingToAll == true then + MESSAGE:New( "Missile tracking to all players ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Missile tracking to all players OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.Tracking ~= nil then + self.Tracking = MenuParameters.Tracking + if self.Tracking == true then + MESSAGE:New( "Missile tracking ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Missile tracking OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.AlertsToAll ~= nil then + self.AlertsToAll = MenuParameters.AlertsToAll + if self.AlertsToAll == true then + MESSAGE:New( "Alerts to all players ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Alerts to all players OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.AlertsHits ~= nil then + self.AlertsHits = MenuParameters.AlertsHits + if self.AlertsHits == true then + MESSAGE:New( "Alerts Hits ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Alerts Hits OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.AlertsLaunches ~= nil then + self.AlertsLaunches = MenuParameters.AlertsLaunches + if self.AlertsLaunches == true then + MESSAGE:New( "Alerts Launches ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Alerts Launches OFF", "Menu", 15, "ID" ):ToAll() + end + + end + + if MenuParameters.DetailsRange ~= nil then + self.DetailsRange = MenuParameters.DetailsRange + if self.DetailsRange == true then + MESSAGE:New( "Range display ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Range display OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.DetailsBearing ~= nil then + self.DetailsBearing = MenuParameters.DetailsBearing + if self.DetailsBearing == true then + MESSAGE:New( "Bearing display OFF", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Bearing display OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.Distance ~= nil then + self.Distance = MenuParameters.Distance + MESSAGE:New( "Hit detection distance set to " .. self.Distance .. " meters", "Menu", 15, "ID" ):ToAll() + end + +end + --- Detects if an SA site was shot with an anti radiation missile. In this case, take evasive actions based on the skill level set within the ME. --- @see MISSILETRAINER +-- @param #MISSILETRAINER self +-- @param Event#EVENTDATA Event function MISSILETRAINER:_EventShot( Event ) self:F( { Event } ) @@ -15022,37 +15477,210 @@ function MISSILETRAINER:_EventShot( Event ) local TrainerTargetDCSUnit = TrainerWeapon:getTarget() -- Identify target local TrainerTargetDCSUnitName = Unit.getName( TrainerTargetDCSUnit ) - local TrainerTargetDCSGroup = TrainerTargetDCSUnit:getGroup() - local TrainerTargetDCSGroupName = TrainerTargetDCSGroup:getName() - local TrainerTargetSkill = _DATABASE.Units[TrainerTargetDCSUnitName].Template.skill + local TrainerTargetSkill = _DATABASE.Templates.Units[TrainerTargetDCSUnitName].Template.skill - self:T( TrainerTargetSkill ) + self:T(TrainerTargetDCSUnitName ) - if TrainerTargetSkill == "Client" or TrainerTargetSkill == "Player" then - self.Schedulers[#self.Schedulers+1] = SCHEDULER:New( self, self._FollowMissile, { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit }, 0.5, 0.05, 0 ) + local Client = self.DBClients[TrainerTargetDCSUnitName] + if Client then + + local TrainerSourceUnit = UNIT:New(TrainerSourceDCSUnit) + local TrainerTargetUnit = UNIT:New(TrainerTargetDCSUnit) + + if self.MessagesOnOff and self.AlertsLaunches then + + local Message = MESSAGE:New( + string.format( "%s launched a %s", + TrainerSourceUnit:GetTypeName(), + TrainerWeaponName + ) .. self:AddRange( Client, TrainerWeapon ) .. self:AddBearing( Client, TrainerWeapon ),"Launch Alert", 5, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + end + + local ClientID = Client:GetID() + local MissileData = {} + MissileData.TrainerSourceUnit = TrainerSourceUnit + MissileData.TrainerWeapon = TrainerWeapon + MissileData.TrainerTargetUnit = TrainerTargetUnit + MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName() + MissileData.TrainerWeaponLaunched = true + table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) + --self:T( self.TrackingMissiles ) end end -function MISSILETRAINER:_FollowMissile( TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit ) - self:F( { TrainerSourceDCSUnit, TrainerWeapon, TrainerTargetDCSUnit } ) - - local TrainerSourceUnit = UNIT:New( TrainerSourceDCSUnit ) - local TrainerTargetUnit = UNIT:New( TrainerTargetDCSUnit ) +function MISSILETRAINER:AddRange( Client, TrainerWeapon ) - local PositionMissile = TrainerWeapon:getPoint() - local PositionTarget = TrainerTargetUnit:GetPositionVec3() + local RangeText = "" - local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + - ( PositionMissile.y - PositionTarget.y )^2 + - ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 + if self.DetailsRange then - MESSAGE:New( "Distance Missle = " .. Distance, nil, 0.2, "/Missile" ):ToAll() + local PositionMissile = TrainerWeapon:getPoint() + local PositionTarget = Client:GetPositionVec3() + + local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 / 1000 + + RangeText = string.format( ", at %4.2fkm", Range ) + end - if Distance <= self.Distance then - TrainerWeapon:destroy() - MESSAGE:New( "Missle Destroyed", nil, 5, "/Missile" ):ToAll() - return false + return RangeText +end + +function MISSILETRAINER:AddBearing( Client, TrainerWeapon ) + + local BearingText = "" + + if self.DetailsBearing then + + local PositionMissile = TrainerWeapon:getPoint() + local PositionTarget = Client:GetPositionVec3() + + self:T2( { PositionTarget, PositionMissile }) + + local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } + local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) + --DirectionRadians = DirectionRadians + routines.getNorthCorrection( PositionTarget ) + if DirectionRadians < 0 then + DirectionRadians = DirectionRadians + 2 * math.pi + end + local DirectionDegrees = DirectionRadians * 180 / math.pi + + BearingText = string.format( ", %d degrees", DirectionDegrees ) + end + + return BearingText +end + + +--- +-- @param #MISSILETRAINER self +-- @param Unit#UNIT TrainerSourceDCSUnit +-- @param DCSWeapon#Weapon TrainerWeapon +-- @param Unit#UNIT TrainerTargetDCSUnit +-- @param Client#CLIENT Client +function MISSILETRAINER:_TrackMissiles() + self:F2() + + + local ShowMessages = false + if self.MessagesOnOff and self.MessageLastTime + 3 <= timer.getTime() then + self.MessageLastTime = timer.getTime() + ShowMessages = true + end + + for ClientDataID, ClientData in pairs( self.TrackingMissiles ) do + + local Client = ClientData.Client + self:T2( { Client:GetName() } ) + + + ClientData.MessageToClient = "" + ClientData.MessageToAll = "" + + for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do + + for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do + self:T3( MissileDataID ) + + local TrainerSourceUnit = MissileData.TrainerSourceUnit + local TrainerWeapon = MissileData.TrainerWeapon + local TrainerTargetUnit = MissileData.TrainerTargetUnit + local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName + local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched + + if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then + local PositionMissile = TrainerWeapon:getPosition().p + local PositionTarget = Client:GetPositionVec3() + + local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + + ( PositionMissile.y - PositionTarget.y )^2 + + ( PositionMissile.z - PositionTarget.z )^2 + ) ^ 0.5 / 1000 + + if Distance <= self.Distance then + -- Hit alert + TrainerWeapon:destroy() + if self.MessagesOnOff and self.AlertsHits then + + self:T( "killed" ) + + local Message = MESSAGE:New( + string.format( "%s launched by %s killed %s", + TrainerWeapon:getTypeName(), + TrainerSourceUnit:GetTypeName(), + TrainerTargetUnit:GetPlayerName() + ),"Hit Alert", 15, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + + MissileData = nil + table.remove( TrackingData.MissileData, MissileDataID ) + self:T(TrackingData.MissileData) + end + else + if ShowMessages then + local TrackingTo + TrackingTo = string.format( " -> %s", + TrainerWeaponTypeName + ) + + if ClientDataID == TrackingDataID then + if ClientData.MessageToClient == "" then + ClientData.MessageToClient = "Missiles to You:\n" + end + ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" + else + if self.TrackingToAll then + if ClientData.MessageToAll == "" then + ClientData.MessageToAll = "Missiles to other Players:\n" + end + ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n" + end + end + end + end + else + if not ( TrainerWeapon and TrainerWeapon:isExist() ) then + if self.MessagesOnOff and self.AlertsLaunches then + -- Weapon does not exist anymore. Delete from Table + local Message = MESSAGE:New( + string.format( "%s launched by %s self destructed!", + TrainerWeaponTypeName, + TrainerSourceUnit:GetTypeName() + ),"Tracking", 5, "ID" ) + + if self.AlertsToAll then + Message:ToAll() + else + Message:ToClient( Client ) + end + end + MissileData = nil + table.remove( TrackingData.MissileData, MissileDataID ) + self:T(TrackingData.MissileData) + end + end + end + end + + if self.MessagesOnOff and self.Tracking and ShowMessages then + if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then + local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, "Tracking", 1, "ID" ):ToClient( Client ) + end + end + end return true diff --git a/Moose/Client.lua b/Moose/Client.lua index 9489d8019..403ad95aa 100644 --- a/Moose/Client.lua +++ b/Moose/Client.lua @@ -190,7 +190,7 @@ function CLIENT:GetDCSGroup() local ClientGroup = ClientUnit:getGroup() if ClientGroup then self:T3( "ClientGroup = " .. self.ClientName ) - if ClientGroup:isExist() then + if ClientGroup:isExist() and UnitData:getGroup():isExist() then if ClientGroup:getID() == UnitData:getGroup():getID() then self:T3( "Normal logic" ) self:T3( self.ClientName .. " : group found!" ) @@ -244,7 +244,7 @@ function CLIENT:GetClientGroupID() local ClientGroup = self:GetDCSGroup() - self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() + --self:E( self.ClientGroupID ) -- Determined in GetDCSGroup() return self.ClientGroupID end diff --git a/Moose/MissileTrainer.lua b/Moose/MissileTrainer.lua index e3b19f8b6..3ebfb8e7d 100644 --- a/Moose/MissileTrainer.lua +++ b/Moose/MissileTrainer.lua @@ -27,7 +27,7 @@ function MISSILETRAINER:New( Distance ) self.MessageInterval = 2 self.MessageLastTime = timer.getTime() - self.Distance = Distance + self.Distance = Distance / 1000 _EVENTDISPATCHER:OnShot( self._EventShot, self ) @@ -39,7 +39,7 @@ function MISSILETRAINER:New( Distance ) local function _Alive( Client ) - Client:Message( "Welcome to the Missile Trainer", 10, "ID", "TEST" ) + Client:Message( "Hello trainee, welcome to the Missile Trainer.\nUse the F10->F2 menu options in the radio menu to change the Missile Trainer settings.\nGood luck!", 10, "ID", "Trainer" ) Client.MainMenu = MENU_CLIENT:New( Client, "Missile Trainer", nil ) @@ -66,6 +66,23 @@ function MISSILETRAINER:New( Distance ) Client.MenuDetailsDistanceOff = MENU_CLIENT_COMMAND:New( Client, "Range Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsRange = false } ) Client.MenuDetailsBearingOn = MENU_CLIENT_COMMAND:New( Client, "Bearing On", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = true } ) Client.MenuDetailsBearingOff = MENU_CLIENT_COMMAND:New( Client, "Bearing Off", Client.MenuDetails, self._MenuMessages, { MenuSelf = self, DetailsBearing = false } ) + + Client.MenuDistance = MENU_CLIENT:New( Client, "Set distance to plane", Client.MainMenu ) + Client.MenuDistance50 = MENU_CLIENT_COMMAND:New( Client, "50 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 50 / 1000 } ) + Client.MenuDistance100 = MENU_CLIENT_COMMAND:New( Client, "100 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 100 / 1000 } ) + Client.MenuDistance150 = MENU_CLIENT_COMMAND:New( Client, "150 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 150 / 1000 } ) + Client.MenuDistance200 = MENU_CLIENT_COMMAND:New( Client, "200 meter", Client.MenuDistance, self._MenuMessages, { MenuSelf = self, Distance = 200 / 1000 } ) + + + local ClientID = Client:GetID() + self:T( ClientID ) + if not self.TrackingMissiles[ClientID] then + self.TrackingMissiles[ClientID] = {} + end + self.TrackingMissiles[ClientID].Client = Client + if not self.TrackingMissiles[ClientID].MissileData then + self.TrackingMissiles[ClientID].MissileData = {} + end end Client:Alive( _Alive ) @@ -97,40 +114,86 @@ function MISSILETRAINER:New( Distance ) return self end -function MISSILETRAINER:_MenuMessages( MenuParameters ) +function MISSILETRAINER._MenuMessages( MenuParameters ) local self = MenuParameters.MenuSelf - if MenuParameters.MessagesOnOff then + if MenuParameters.MessagesOnOff ~= nil then self.MessagesOnOff = MenuParameters.MessagesOnOff + if self.MessagesOnOff == true then + MESSAGE:New( "Messages ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Messages OFF", "Menu", 15, "ID" ):ToAll() + end end - if MenuParameters.TrackingToAll then + if MenuParameters.TrackingToAll ~= nil then self.TrackingToAll = MenuParameters.TrackingToAll + if self.TrackingToAll == true then + MESSAGE:New( "Missile tracking to all players ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Missile tracking to all players OFF", "Menu", 15, "ID" ):ToAll() + end end - if MenuParameters.Tracking then + if MenuParameters.Tracking ~= nil then self.Tracking = MenuParameters.Tracking + if self.Tracking == true then + MESSAGE:New( "Missile tracking ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Missile tracking OFF", "Menu", 15, "ID" ):ToAll() + end end - if MenuParameters.AlertsToAll then + if MenuParameters.AlertsToAll ~= nil then self.AlertsToAll = MenuParameters.AlertsToAll + if self.AlertsToAll == true then + MESSAGE:New( "Alerts to all players ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Alerts to all players OFF", "Menu", 15, "ID" ):ToAll() + end end - if MenuParameters.AlertsHits then + if MenuParameters.AlertsHits ~= nil then self.AlertsHits = MenuParameters.AlertsHits + if self.AlertsHits == true then + MESSAGE:New( "Alerts Hits ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Alerts Hits OFF", "Menu", 15, "ID" ):ToAll() + end end - if MenuParameters.AlertsLaunches then + if MenuParameters.AlertsLaunches ~= nil then self.AlertsLaunches = MenuParameters.AlertsLaunches + if self.AlertsLaunches == true then + MESSAGE:New( "Alerts Launches ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Alerts Launches OFF", "Menu", 15, "ID" ):ToAll() + end + end - if MenuParameters.DetailsRange then + if MenuParameters.DetailsRange ~= nil then self.DetailsRange = MenuParameters.DetailsRange + if self.DetailsRange == true then + MESSAGE:New( "Range display ON", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Range display OFF", "Menu", 15, "ID" ):ToAll() + end end - if MenuParameters.DetailsBearing then + if MenuParameters.DetailsBearing ~= nil then self.DetailsBearing = MenuParameters.DetailsBearing + if self.DetailsBearing == true then + MESSAGE:New( "Bearing display OFF", "Menu", 15, "ID" ):ToAll() + else + MESSAGE:New( "Bearing display OFF", "Menu", 15, "ID" ):ToAll() + end + end + + if MenuParameters.Distance ~= nil then + self.Distance = MenuParameters.Distance + MESSAGE:New( "Hit detection distance set to " .. self.Distance .. " meters", "Menu", 15, "ID" ):ToAll() end end @@ -166,7 +229,7 @@ function MISSILETRAINER:_EventShot( Event ) string.format( "%s launched a %s", TrainerSourceUnit:GetTypeName(), TrainerWeaponName - ) .. self:AddRange( Client, TrainerWeapon ) .. self:AddBearing( Client, TrainerWeapon ),"Launch", 5, "ID" ) + ) .. self:AddRange( Client, TrainerWeapon ) .. self:AddBearing( Client, TrainerWeapon ),"Launch Alert", 5, "ID" ) if self.AlertsToAll then Message:ToAll() @@ -176,18 +239,12 @@ function MISSILETRAINER:_EventShot( Event ) end local ClientID = Client:GetID() - self:T( ClientID ) - if not self.TrackingMissiles[ClientID] then - self.TrackingMissiles[ClientID] = {} - end - self.TrackingMissiles[ClientID].Client = Client - if not self.TrackingMissiles[ClientID].MissileData then - self.TrackingMissiles[ClientID].MissileData = {} - end local MissileData = {} MissileData.TrainerSourceUnit = TrainerSourceUnit MissileData.TrainerWeapon = TrainerWeapon MissileData.TrainerTargetUnit = TrainerTargetUnit + MissileData.TrainerWeaponTypeName = TrainerWeapon:getTypeName() + MissileData.TrainerWeaponLaunched = true table.insert( self.TrackingMissiles[ClientID].MissileData, MissileData ) --self:T( self.TrackingMissiles ) end @@ -205,7 +262,7 @@ function MISSILETRAINER:AddRange( Client, TrainerWeapon ) local Range = ( ( PositionMissile.x - PositionTarget.x )^2 + ( PositionMissile.y - PositionTarget.y )^2 + ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 + ) ^ 0.5 / 1000 RangeText = string.format( ", at %4.2fkm", Range ) end @@ -222,7 +279,7 @@ function MISSILETRAINER:AddBearing( Client, TrainerWeapon ) local PositionMissile = TrainerWeapon:getPoint() local PositionTarget = Client:GetPositionVec3() - self:T( { PositionTarget, PositionMissile }) + self:T2( { PositionTarget, PositionMissile }) local DirectionVector = { x = PositionMissile.x - PositionTarget.x, y = PositionMissile.y - PositionTarget.y, z = PositionMissile.z - PositionTarget.z } local DirectionRadians = math.atan2( DirectionVector.z, DirectionVector.x ) @@ -261,28 +318,28 @@ function MISSILETRAINER:_TrackMissiles() self:T2( { Client:GetName() } ) - ClientData.MessageToAll = "Missiles to Other Players:\n" - ClientData.MessageToClient = "Missiles to You:\n" + ClientData.MessageToClient = "" + ClientData.MessageToAll = "" for TrackingDataID, TrackingData in pairs( self.TrackingMissiles ) do - self:T( #TrackingData.MissileData ) - for MissileDataID, MissileData in pairs( TrackingData.MissileData ) do - self:T( MissileDataID ) + self:T3( MissileDataID ) local TrainerSourceUnit = MissileData.TrainerSourceUnit local TrainerWeapon = MissileData.TrainerWeapon local TrainerTargetUnit = MissileData.TrainerTargetUnit + local TrainerWeaponTypeName = MissileData.TrainerWeaponTypeName + local TrainerWeaponLaunched = MissileData.TrainerWeaponLaunched - if TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then + if Client and Client:IsAlive() and TrainerSourceUnit and TrainerSourceUnit:IsAlive() and TrainerWeapon and TrainerWeapon:isExist() and TrainerTargetUnit and TrainerTargetUnit:IsAlive() then local PositionMissile = TrainerWeapon:getPosition().p local PositionTarget = Client:GetPositionVec3() local Distance = ( ( PositionMissile.x - PositionTarget.x )^2 + ( PositionMissile.y - PositionTarget.y )^2 + ( PositionMissile.z - PositionTarget.z )^2 - ) ^ 0.5 + ) ^ 0.5 / 1000 if Distance <= self.Distance then -- Hit alert @@ -292,11 +349,11 @@ function MISSILETRAINER:_TrackMissiles() self:T( "killed" ) local Message = MESSAGE:New( - string.format( "%s launched by %s killed '%s'", + string.format( "%s launched by %s killed %s", TrainerWeapon:getTypeName(), TrainerSourceUnit:GetTypeName(), - TrainerSourceUnit:GetPlayerName() - ),"Tracking", 15, "ID" ) + TrainerTargetUnit:GetPlayerName() + ),"Hit Alert", 15, "ID" ) if self.AlertsToAll then Message:ToAll() @@ -309,15 +366,25 @@ function MISSILETRAINER:_TrackMissiles() self:T(TrackingData.MissileData) end else - local TrackingTo = string.format( " -> %s launched by %s", - TrainerWeapon:getTypeName(), - TrainerSourceUnit:GetName() - ) - - if ClientDataID == TrackingDataID then - ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" - else - ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( TrackingData.Client, TrainerWeapon ) .. self:AddBearing( TrackingData.Client, TrainerWeapon ) .. "\n" + if ShowMessages then + local TrackingTo + TrackingTo = string.format( " -> %s", + TrainerWeaponTypeName + ) + + if ClientDataID == TrackingDataID then + if ClientData.MessageToClient == "" then + ClientData.MessageToClient = "Missiles to You:\n" + end + ClientData.MessageToClient = ClientData.MessageToClient .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. "\n" + else + if self.TrackingToAll then + if ClientData.MessageToAll == "" then + ClientData.MessageToAll = "Missiles to other Players:\n" + end + ClientData.MessageToAll = ClientData.MessageToAll .. TrackingTo .. self:AddRange( ClientData.Client, TrainerWeapon ) .. self:AddBearing( ClientData.Client, TrainerWeapon ) .. " ( " .. TrainerTargetUnit:GetPlayerName() .. " )\n" + end + end end end else @@ -325,8 +392,8 @@ function MISSILETRAINER:_TrackMissiles() if self.MessagesOnOff and self.AlertsLaunches then -- Weapon does not exist anymore. Delete from Table local Message = MESSAGE:New( - string.format( "%s launched by %s is self destructed!", - TrainerWeapon:getTypeName(), + string.format( "%s launched by %s self destructed!", + TrainerWeaponTypeName, TrainerSourceUnit:GetTypeName() ),"Tracking", 5, "ID" ) @@ -345,7 +412,9 @@ function MISSILETRAINER:_TrackMissiles() end if self.MessagesOnOff and self.Tracking and ShowMessages then - local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, "Tracking", 1, "ID" ):ToClient( Client ) + if ClientData.MessageToClient ~= "" or ClientData.MessageToAll ~= "" then + local Message = MESSAGE:New( ClientData.MessageToClient .. ClientData.MessageToAll, "Tracking", 1, "ID" ):ToClient( Client ) + end end end diff --git a/Moose/Unit.lua b/Moose/Unit.lua index 4cea12dbb..fcd99cdd4 100644 --- a/Moose/Unit.lua +++ b/Moose/Unit.lua @@ -96,7 +96,14 @@ end function UNIT:GetPlayerName() self:F( self.UnitName ) - return self.DCSUnit:getPlayerName() + local DCSUnit = Unit.getByName( self.UnitName ) + + local PlayerName = DCSUnit:getPlayerName() + if PlayerName == nil then + PlayerName = "" + end + + return PlayerName end function UNIT:GetTypeName() self:F( self.UnitName ) diff --git a/Tools and Installation/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx b/Presentations/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx similarity index 100% rename from Tools and Installation/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx rename to Presentations/DCS World - MOOSE - Development - Part 1 - Tools and Installation.pptx diff --git a/Tools and Installation/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx b/Presentations/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx similarity index 100% rename from Tools and Installation/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx rename to Presentations/DCS World - MOOSE - Development - Part 2 - Using Eclipse and MOOSE - Copy.pptx diff --git a/Tools and Installation/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx b/Presentations/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx similarity index 100% rename from Tools and Installation/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx rename to Presentations/DCS World - MOOSE - Escorting - Part 2 - APIs.pptx diff --git a/Presentations/DCS World - MOOSE - Missile Trainer.pptx b/Presentations/DCS World - MOOSE - Missile Trainer.pptx new file mode 100644 index 0000000000000000000000000000000000000000..4c6738ffcaf172e204d4ad52efa7bc0f219279fd GIT binary patch literal 800876 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