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] 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 zcmZ9RRZyH=u&%LzAOQw<2n-1lJa}-o;1C>w2Y1L2KHS|M1_A_k5AN;+cXxN$`OmpH zr*?JK>YKOMx_henS$EIKmCwlDHSpu9sNjQfq$?yixGzcbsqj>Qp4|oy-p^gluSZ?3 z8sy#P7L346Z zTmsa$PO+TREA$pg2;k~FyF#BYH{exwI*N0tUwlG`8)4IM_oEfzaQD*?XSix2gO!^| zkxQl~<)8aGKcNGrqA-Jn<49twx40!<*{~Mey?QDUOepFkS+SGeAe4BGTR*D-50+!H z%-t;n89yb0!-56-`ZtaSB@ZEDEXsJh_oH_`;dx3I{N?<`5z6f$;Z;}87KfPhNplOEkJiXDhz7hw9UFi#9ii$`X~l|#RF**cG{ z0CuLR2?bD1kRIW1mB$RpBi^ftC0mYDrr&bkr+takJX1b6 zGU*`pdn0On7I-{Cx+8l@7ZQq)t$fm0jSeaRjP)e=NQ0z_RZHjFV{iojOsbu%Zj0In zb+b5E^6(D}e0}ICeNXK}5lRmU_m*6WXBF6LSpY0y;0yopW~SuPu9>fB_zau7e8zLq z)lAMPlFH2bNkb`GNhpO{e>rDaE&ghEW!nvZ!+fb0GeC+*eaK)%6KBNH=&n-PAM=^| zpXT)@rtWPp_{1wf>t? z#sbh)ZaD6DIGjxgG2}_WAiSY0s6;L9Py&wry=Aq)=)1f4w*3 zrUW6^%1T$Sffh}4hrX(1h(y!`)~f%dKLt*;wques#~>^6%lV_5aC@n60U0M>12S%Y zBL(zz7h?0rLoKA*nWr{;jK0cJ8zqVAfh$zy-SxdTe7ow`1}2cDpAg|Fe#H@+AKk=d z&0!HWhP*bCGJW877?=7@;OfJ-r@G(MlKF^mvhViN@^0h24M_u36a&O9J?e z=ppb$BW%K2P-{*B(V7#AFnqNBu15`RhauOYmzz?&97btY1*YFgv~|Quln=dqpq;el8tQwC(MV| z6|FyyQ$9#L>7MNEzl*bMjLa`nhx!7#DQB_ zvAI|L4>X0Nkq=#b1dp63X4lNKYzv$&V)suPrm$B2pFX`8*O%!>=n}e_Pc>78a_vj3 zB)em#Tu*aXydGxYOl1j=p-D!D!VP4>kIU^r_-t|tIug@g9uf7`$8X5Pq^*G#bFxwm zetz714Co$-*HwTd!HF|Q6YkI92k~S7G_r_l8-x~#u}jilk4arj8n?ox{m!Z1q!$p? zmw8KKqpnB3mpvYhbGS%*^dvV5majEaoLqtk!g(pd(&xZd6fSh6gHC#A2ml`-Y6gW6I8%rlAOFP?R z0fneVzS!F*w2lI#U~*N;#E2kXXSdiSs(t7D=GCDS6J%tjsk%DT0Wq13U5uTI+h-K? zBVZ0hFgKKWgJtqE5u0Nzhesb%HRW(oqFUd>FpErwmpCz3vj8_j`DURkdIkD-;5{eBbdk@;lukvK*`R?99ikYaxu3r6`g z?V)|Lw8c+3_5^1`-yrD_{|(FLHx=-D8cf9R@%j+&hoK^zPyc>uA!t}chdl#~RXA13 zxH*AyX;Dv(Vn}s3KybTNb38zNfGRGTqzXGcRgAFxE4)4(Y$nl6KZnyKWkk(Mi`kLy z0KX!)3DIWgv?C#0qu)jgJQ^FsfGYD`bJ$Az^(ls%_;fmkrEA@!DpA!QUTu>X=caHl5g2n}O3W z*KV0R;oTcNt7zgzHQ(ZY@p#HZ-KiC*B&Ggj*DBXtnL!IuK;7_;a_tclmU#)H1ogZL=xY9c+VTdW8gs&;u#B?Jmk=~T|>DTCX+K1Z5i4(2ONpaX{z z)4WpIl)WYhXR&+m`wTY&F}Mof{7)%Wq?;-3CrH;7u@(EU6jf@?3q>lxbnK|MCA$RC zy%s-+6{aTJnId1SnfCw#BFg@8EpaE~DlYwY5AX2ZLu}@sQSb?VxltKIW%H-FbdwJc zM~b`VNxczmc-%ihLd(SU0``)muMZnXpCH z6$>4V^=fQ)Pk-*t)Q!j8a;Y6vvSyzb?HiDi%`aoTraL|84F={Rf!q~`R}q(RKiqmp z5H|QFMg_%rl2wk0$_=K(;*+)=YtvLR>wE3_D!<=+E0sJ#ixN2NEGa!5daB0)gfyV>c6Bnufsw zREnLS=*CpYR~z$TWi!K+*YicOG`Y_ExFpO70GHJ)A2(1KpC9EZ3zIA0?To$Oa>5%l zhG0ThH0lJ6Gqzg1zTMED#8>$C@{H)awm$UoBY06vyuf*5k^&&q`#f%fF)3Yb3qQK#~6`A0+@i%8R?=56LlR>6g9g-bGxh+Dg zAUG6+#{_sGk5&za#kC?b@GA@GpLF}ncv@K~sXX?Qmc0aldu9D;- zpp)|v|C}IC(}UNCEqAO4;ej#yk2;RVbZ-kCZS;=xyp8>ZfrlO0qfDe1 zHu9S4U?DpKtKuNe`P9Lx#B<2%fhG?V8{RU!D48D)m(Z@eGm4ez*Z>Xtw@JGyGyggU7Hx_DlDydH!;s{ zdYt>>hMfE4;`Sr${`Gp(rcJ1m#7Rbkfj%tdKv;q}<-od?JD-mVKiN5~aqq@akw@Z4 zTu`XkJg$8QBMZulyr^X)p$$a8Imn~Tb~7n!wM`}|8Yy1R!X<49IM0apSMaN>rOiFN zAyKe`UEUh2_I5Q=VD0Ah&?KHn`YuTO_~CMI&gdi?xg}Rk2YCB)%OgA)Sp*?rs;>IU z2#)QBO)!$6t9N?T;^M(WF9JoW(TLxXf%&6BRx^-_{MM!dA zhLSseymqn+(;a9tFjjWcQB29E{Xtn{jLXg^vvCMXbz+@4>IelV|2~NiH$H=4l7rz} zpgen$rY1K_cs)3sB#BN-`~oA^cZVmU_JWYVl3e3*ao>2?Y)>thM`^H-N`w~NPRl5CpkWPg#4wauHM%s-}ucniHs*Aki)L2iSRJc-8!r6=is{9wT+ zaS_})PQED3cMQimEX$14zwDyZuo{3~m^I6Ze9sg}!OpD33-~)nF9wL5c*gB9N`3HQS+6!W^oP zEZT-So5v!X_%uk$&Mq76qady=w3o@=8t-zu*4ai1XW6w=mfJpVzd>7ncE`U@Kp19_ zc}g*Wukdw>1H@uc{oU;K;{#Fk0T9Y=HLldlw1N1%7ReNi9Oc0m6k=+DuF-*wDNJ#b zj9kN$f+5MA8-vg-@waJ~P2TU)xrPRCqD|^mvd|QGd_#OMtl^cvFy1>cJ zcE*XX>+Y`MMOZ*CMNXWNUv$D_e%ERR`A$VOXd@Os1;|gyVHCqFB|tQVCo2-t@tp5C zLeF%K4BvvpBNnjp#BudI?U$veq=j+il*SX|eQ)&ph;p*$f8I!_)m|R@rmvu>qhh4e zg2ca&*IXW$aVVc!6eb|3Q?`k5nPd})x#K=Ko@FNlX(qea zs(J?DA-DGT_-P30V}H05f4<01p^AZ`ls+|23oKO^t!h|Rm7F-zqLiYHDFq8u@F7S_ zCHiJ?bf_~A`RpN_A%ptmF7P@IZrQrM{r7)K0){`jIpWXJLT|Kh#L{541or&hOwosK z(Jry&LdQNfsa=_yRa?hq66)Y>bahsuCg#0LGJC0_C+j@ z_YZLipZS(|aV-$Fc!3B;Nlsx2nac#;;WE5Zr8>lS(8wMPe%4ptFp~iWlqCY+310SJ zKyB=4A;$N*`EGaprA$)`2uze|yZL*~=>7b4g6nE^aEV;irJQyZzI8oqEqL3E1L3Ej zatpDmF}>K(aeZz4q$uy|!b^p$193SLvO(BSeezrX_kK zX1y**rYMY#kZWjPRY3&eh+lrm_s@e>K+mH47=E{X;rf@*D8ZQ$MTXo&y}HxH)e5su z7P%Xjdrs%aY>Up2v?muu!Gp!L(RVvc>xp#;x)iAlNR?W9nZPIMDjHqnaU!je+~3>5 z_j6izQ}k%p2+#3eJX_i4O%K*(U~0Vgn@6~WAxA`72oW;Bt3&M(KDZ1uJ_noV0Wq2R z6Bl)pupCJ)#qIQBe?>o+BExIgAHT5EC#rs|ZzteDr}>FIqCa|nDu5EIP~5#fhp*X& zmgmA7d7Pbute%n2(CN-aaGbckEVWeK$T+F*#5mVuE|{p0c~YBKJ|b_Lf{Q52S}7*e z_wDzOoKYRf$6brrB`>1k`*+q#fI|W^?;*YbbZ%`>CnH=bBGVWA-P(%W4~&Ur=U+vp z$dg z5h4tmil1&!TB+Yoj1`y9RjsHgfr<047#d2r^f<-Jqb+BQ44eS&6^Q{~h60MO_f(qp zKgVRLJzk6#)0|z5HdVZyN3@)L1-NAyvP4GY#3^M+_6$sB^Dkx{eTIPbv=(zjbhx1U zn9bkKrCtK-@PoGZcK4xg_|uk{)^>wRnzq|$Kjs{efv^ikdN@#uo3@2aKT;p~&OIY) zEMKfD6U1Dl4qpRUnvO$2mzMYh=^gP3(_RETmZt3xAfYY(mmdeaP@|H(WJ?34d&C!E z47;ujgru(7k=vYP`B2D7OvbL*!PU!*4OXAjk5zO-Fv4svn(J#q3mtG7 zFw4`-yK&7#ZcqlfAx23BrO`kS6dJowRH7WIcnP2*;pBXwO*1(Xc;MKc$Kts#XgOuA-&Kd@ z^*ezrpG!8z{9o+`%giGVvVsI1r>ue~0Wz(B9CD$FSde*0rw&rxvJWL|_1H3O%FW!y z_z3j5J}?6x963^Lw&1Lr|C&eTi@L>hS(r5XQ7oQsv&7r>s^IeO0pAbsc>hiB?BenE z%P4)kdCAWTQ8{ZBx6vnj?>VN)`RgX_6Ei3dUzoTs22+ z{xSZ@fB)^PN1Us|u11)zXi=R8c!J?Z3%Hu#D4l3`Joe$Il5;K1=@wA5x)mxcRs1_k z`i8LqbB~ay?EuF_;$i&$w?ng+dsMPpQ8Tl&vMo^}f9&$LJ|cziuo|Mt-@tyK7Et=g z0>YT;j+IZORMBwuy~Q|@NRRdNiJ+^5#(R;YF>YN8VsX4L)El5aqb8HV!+$c#5;(m( z)%%#e&$GZE{!j$3DIt1<#@Lx#FK48tP-UfR$J(`5PLI~&VvGD%9Un`{Jh~uTBh@R9 z-^k<#liri}+19&qe|Ux@Ws|=qx;<Svv!s-dK!k>cUy|0D+UD0-FOG^$72~_n=2eK59U~)N`QhcFu|k`_Dmth| zs@-*F^oVYaRw?9#xFfHa5DmZH27+M{}qd0$_=AWp?TP zNi!DX{QmU%&tG;vzhDaB@6d68c6`~;b_ETv9Ux@3AyZ~feHuQRscLl>essLDW^U7# zPCAAqdv23~jvaQ}E|cHa^{dBZ3QYBSSCrYM_9y>Rn&!8Yy*Q_z#82jp==RFTG#cOV zG}QKWP)Aj#lNX?>G}Z791Ib`yiN&+siB)u##3RuSsaLH3{2bmt6ZZxV{@>YAY|}A| z2nXkl@qhiCoxSrvJ?9jmq2RR1h0(IB>F%lTwo$XPpBDuA;)*qD+vj>axo=^JQ^g9C zek*k3!vDC6u2n@NtERpB(VObb`&L-hgMC54D{bk?iDMmD%~F-|_(Dwwq`s&Fp_vAj*MBVj{Ffc*WXL?J2t`cw3ZJ~eU6`xa_NV%WrQ!a-%#~*dpt@+~NTSCeu#5n+(Hw-pi@QCsl<>BQ0iX<^=P<(f8 z#Ga<0G!!xb#k0}Uq33};BP21jt~pS<_mUsGkouoiDB*w3v;5}d^f7wHQOd6eJ^2?6 zt88n6zWXfk$lHf;j2>s^J2P~)+>Z>1vG_isxHev-4paD4BMw$zDe1fcVmpNyx(OEW zSv#C{~!x z%Kbg)ACCdGMGa%^k`7%MVK=zyUH>KV&;-!Ptp2`u+AN<)K`7_rY10$W$NzpiJ-|_> z4;n+fLRd{2<`_o^5(N1IN)yr7?svTfjObmNS8v+5j9lyITIat(H!trV2U%@2)77R3 zLU>uX`Ih&$?L!tCEgnDX3@4o$AdR}9M}Ew|dtvwph?XZR?4=DH4rqW$pJuyaGr23Dvgthv3hD(zwZEHidp-_J%%_t%u{g8 zaki{v*^WMIxB2KP=O}dJGp1u&2UxsmN6NSa;^ku4tF3l=?mFO|RT91;)rlc`cKGFz z|I?7qv`ZL<9EXNsBF!Lq@Lvpo_i^ z-3~a5|7NC9l3P7~Y`Z7?4*^LP{~Ue2djq@z=>Pk?9BqNuKsd}wdfy7qK>EMk{Xbm% zA7;S8WhGhtn