--- **Core** - Manages several databases containing templates, mission objects, and mission information. -- -- === -- -- ## Features: -- -- * During mission startup, scan the mission environment, and create / instantiate intelligently the different objects as defined within the mission. -- * Manage database of DCS Group templates (as modelled using the mission editor). -- - Group templates. -- - Unit templates. -- - Statics templates. -- * Manage database of @{Wrapper.Group#GROUP} objects alive in the mission. -- * Manage database of @{Wrapper.Unit#UNIT} objects alive in the mission. -- * Manage database of @{Wrapper.Static#STATIC} objects alive in the mission. -- * Manage database of players. -- * Manage database of client slots defined using the mission editor. -- * Manage database of airbases on the map, and from FARPs and ships as defined using the mission editor. -- * Manage database of countries. -- * Manage database of zone names. -- * Manage database of hits to units and statics. -- * Manage database of destroys of units and statics. -- * Manage database of @{Core.Zone#ZONE_BASE} objects. -- -- === -- -- ### Author: **FlightControl** -- ### Contributions: **funkyfranky** -- -- === -- -- @module Core.Database -- @image Core_Database.JPG --- -- @type DATABASE -- @field #string ClassName Name of the class. -- @field #table Templates Templates: Units, Groups, Statics, ClientsByName, ClientsByID. -- @field #table CLIENTS Clients. -- @field #table STORAGES DCS warehouse storages. -- @field #table STNS Used Link16 octal numbers for F16/15/18/AWACS planes. -- @field #table SADL Used Link16 octal numbers for A10/C-II planes. -- @extends Core.Base#BASE --- Contains collections of wrapper objects defined within MOOSE that reflect objects within the simulator. -- -- Mission designers can use the DATABASE class to refer to: -- -- * STATICS -- * UNITS -- * GROUPS -- * CLIENTS -- * AIRBASES -- * PLAYERSJOINED -- * PLAYERS -- * CARGOS -- * STORAGES (DCS warehouses) -- -- On top, for internal MOOSE administration purposes, the DATABASE administers the Unit and Group TEMPLATES as defined within the Mission Editor. -- -- The singleton object **_DATABASE** is automatically created by MOOSE, that administers all objects within the mission. -- Moose refers to **_DATABASE** within the framework extensively, but you can also refer to the _DATABASE object within your missions if required. -- -- @field #DATABASE DATABASE = { ClassName = "DATABASE", Templates = { Units = {}, Groups = {}, Statics = {}, ClientsByName = {}, ClientsByID = {}, }, UNITS = {}, UNITS_Index = {}, STATICS = {}, GROUPS = {}, PLAYERS = {}, PLAYERSJOINED = {}, PLAYERUNITS = {}, CLIENTS = {}, CARGOS = {}, AIRBASES = {}, COUNTRY_ID = {}, COUNTRY_NAME = {}, NavPoints = {}, PLAYERSETTINGS = {}, ZONENAMES = {}, HITS = {}, DESTROYS = {}, ZONES = {}, ZONES_GOAL = {}, WAREHOUSES = {}, FLIGHTGROUPS = {}, FLIGHTCONTROLS = {}, OPSZONES = {}, PATHLINES = {}, STORAGES = {}, STNS={}, SADL={}, } local _DATABASECoalition = { [1] = "Red", [2] = "Blue", [3] = "Neutral", } local _DATABASECategory = { ["plane"] = Unit.Category.AIRPLANE, ["helicopter"] = Unit.Category.HELICOPTER, ["vehicle"] = Unit.Category.GROUND_UNIT, ["ship"] = Unit.Category.SHIP, ["static"] = Unit.Category.STRUCTURE, } --- Creates a new DATABASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #DATABASE self -- @return #DATABASE -- @usage -- -- Define a new DATABASE Object. This DBObject will contain a reference to all Group and Unit Templates defined within the ME and the DCSRTE. -- DBObject = DATABASE:New() function DATABASE:New() -- Inherits from BASE local self = BASE:Inherit( self, BASE:New() ) -- #DATABASE self:SetEventPriority( 1 ) self:HandleEvent( EVENTS.Birth, self._EventOnBirth ) -- DCS 2.9 fixed CA event for players -- TODO: reset unit when leaving self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) self:HandleEvent( EVENTS.Dead, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.Crash, self._EventOnDeadOrCrash ) self:HandleEvent( EVENTS.RemoveUnit, self._EventOnDeadOrCrash ) --self:HandleEvent( EVENTS.UnitLost, self._EventOnDeadOrCrash ) -- DCS 2.7.1 for Aerial units no dead event ATM self:HandleEvent( EVENTS.Hit, self.AccountHits ) self:HandleEvent( EVENTS.NewCargo ) self:HandleEvent( EVENTS.DeleteCargo ) self:HandleEvent( EVENTS.NewZone ) self:HandleEvent( EVENTS.DeleteZone ) --self:HandleEvent( EVENTS.PlayerEnterUnit, self._EventOnPlayerEnterUnit ) -- This is not working anymore!, handling this through the birth event. self:HandleEvent( EVENTS.PlayerLeaveUnit, self._EventOnPlayerLeaveUnit ) self:_RegisterTemplates() self:_RegisterGroupsAndUnits() self:_RegisterClients() self:_RegisterStatics() --self:_RegisterPlayers() --self:_RegisterAirbases() self.UNITS_Position = 0 return self end --- Finds a Unit based on the Unit Name. -- @param #DATABASE self -- @param #string UnitName -- @return Wrapper.Unit#UNIT The found Unit. function DATABASE:FindUnit( UnitName ) local UnitFound = self.UNITS[UnitName] return UnitFound end --- Adds a Unit based on the Unit Name in the DATABASE. -- @param #DATABASE self -- @param #string DCSUnitName Unit name. -- @return Wrapper.Unit#UNIT The added unit. function DATABASE:AddUnit( DCSUnitName ) if not self.UNITS[DCSUnitName] then -- Debug info. self:T( { "Add UNIT:", DCSUnitName } ) -- Register unit self.UNITS[DCSUnitName]=UNIT:Register(DCSUnitName) end return self.UNITS[DCSUnitName] end --- Deletes a Unit from the DATABASE based on the Unit Name. -- @param #DATABASE self function DATABASE:DeleteUnit( DCSUnitName ) self.UNITS[DCSUnitName] = nil end --- Adds a Static based on the Static Name in the DATABASE. -- @param #DATABASE self -- @param #string DCSStaticName Name of the static. -- @return Wrapper.Static#STATIC The static object. function DATABASE:AddStatic( DCSStaticName ) if not self.STATICS[DCSStaticName] then self.STATICS[DCSStaticName] = STATIC:Register( DCSStaticName ) return self.STATICS[DCSStaticName] end return nil end --- Deletes a Static from the DATABASE based on the Static Name. -- @param #DATABASE self function DATABASE:DeleteStatic( DCSStaticName ) self.STATICS[DCSStaticName] = nil end --- Finds a STATIC based on the StaticName. -- @param #DATABASE self -- @param #string StaticName -- @return Wrapper.Static#STATIC The found STATIC. function DATABASE:FindStatic( StaticName ) local StaticFound = self.STATICS[StaticName] return StaticFound end --- Adds a Airbase based on the Airbase Name in the DATABASE. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase. -- @return Wrapper.Airbase#AIRBASE Airbase object. function DATABASE:AddAirbase( AirbaseName ) if not self.AIRBASES[AirbaseName] then self.AIRBASES[AirbaseName] = AIRBASE:Register( AirbaseName ) end return self.AIRBASES[AirbaseName] end --- Deletes a Airbase from the DATABASE based on the Airbase Name. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase function DATABASE:DeleteAirbase( AirbaseName ) self.AIRBASES[AirbaseName] = nil end --- Finds an AIRBASE based on the AirbaseName. -- @param #DATABASE self -- @param #string AirbaseName -- @return Wrapper.Airbase#AIRBASE The found AIRBASE. function DATABASE:FindAirbase( AirbaseName ) local AirbaseFound = self.AIRBASES[AirbaseName] return AirbaseFound end --- Adds a STORAGE (DCS warehouse wrapper) based on the Airbase Name to the DATABASE. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase. -- @return Wrapper.Storage#STORAGE Storage object. function DATABASE:AddStorage( AirbaseName ) if not self.STORAGES[AirbaseName] then self.STORAGES[AirbaseName] = STORAGE:New( AirbaseName ) end return self.STORAGES[AirbaseName] end --- Deletes a STORAGE from the DATABASE based on the name of the associated airbase. -- @param #DATABASE self -- @param #string AirbaseName The name of the airbase. function DATABASE:DeleteStorage( AirbaseName ) self.STORAGES[AirbaseName] = nil end --- Finds an STORAGE based on the name of the associated airbase. -- @param #DATABASE self -- @param #string AirbaseName Name of the airbase. -- @return Wrapper.Storage#STORAGE The found STORAGE. function DATABASE:FindStorage( AirbaseName ) local storage = self.STORAGES[AirbaseName] return storage end do -- Zones and Pathlines --- Finds a @{Core.Zone} based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZone( ZoneName ) local ZoneFound = self.ZONES[ZoneName] return ZoneFound end --- Adds a @{Core.Zone} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @param Core.Zone#ZONE_BASE Zone The zone. function DATABASE:AddZone( ZoneName, Zone ) if not self.ZONES[ZoneName] then self.ZONES[ZoneName] = Zone end end --- Deletes a @{Core.Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteZone( ZoneName ) self.ZONES[ZoneName] = nil end --- Adds a @{Core.Pathline} based on its name in the DATABASE. -- @param #DATABASE self -- @param #string PathlineName The name of the pathline -- @param Core.Pathline#PATHLINE Pathline The pathline. function DATABASE:AddPathline( PathlineName, Pathline ) if not self.PATHLINES[PathlineName] then self.PATHLINES[PathlineName]=Pathline end end --- Finds a @{Core.Pathline} by its name. -- @param #DATABASE self -- @param #string PathlineName The name of the Pathline. -- @return Core.Pathline#PATHLINE The found PATHLINE. function DATABASE:FindPathline( PathlineName ) local pathline = self.PATHLINES[PathlineName] return pathline end --- Deletes a @{Core.Pathline} from the DATABASE based on its name. -- @param #DATABASE self -- @param #string PathlineName The name of the PATHLINE. function DATABASE:DeletePathline( PathlineName ) self.PATHLINES[PathlineName]=nil return self end --- Private method that registers new ZONE_BASE derived objects within the DATABASE Object. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterZones() for ZoneID, ZoneData in pairs(env.mission.triggers.zones) do local ZoneName = ZoneData.name -- Color local color=ZoneData.color or {1, 0, 0, 0.15} -- Create new Zone local Zone=nil --Core.Zone#ZONE_BASE if ZoneData.type==0 then --- -- Circular zone --- self:I(string.format("Register ZONE: %s (Circular)", ZoneName)) Zone=ZONE:New(ZoneName) else --- -- Quad-point zone --- self:I(string.format("Register ZONE: %s (Polygon, Quad)", ZoneName)) Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, ZoneData.verticies) --for i,vec2 in pairs(ZoneData.verticies) do -- local coord=COORDINATE:NewFromVec2(vec2) -- coord:MarkToAll(string.format("%s Point %d", ZoneName, i)) --end end if Zone then -- Store color of zone. Zone.Color=color -- Store zone ID. Zone.ZoneID=ZoneData.zoneId -- Store zone properties (if any) local ZoneProperties = ZoneData.properties or nil Zone.Properties = {} if ZoneName and ZoneProperties then for _,ZoneProp in ipairs(ZoneProperties) do if ZoneProp.key then Zone.Properties[ZoneProp.key] = ZoneProp.value end end end -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName -- Add zone. self:AddZone(ZoneName, Zone) end end -- Polygon zones defined by late activated groups. for ZoneGroupName, ZoneGroup in pairs( self.GROUPS ) do if ZoneGroupName:match("#ZONE_POLYGON") then local ZoneName1 = ZoneGroupName:match("(.*)#ZONE_POLYGON") local ZoneName2 = ZoneGroupName:match(".*#ZONE_POLYGON(.*)") local ZoneName = ZoneName1 .. ( ZoneName2 or "" ) -- Debug output self:I(string.format("Register ZONE: %s (Polygon)", ZoneName)) -- Create a new polygon zone. local Zone_Polygon = ZONE_POLYGON:New( ZoneName, ZoneGroup ) -- Set color. Zone_Polygon:SetColor({1, 0, 0}, 0.15) -- Store name in DB. self.ZONENAMES[ZoneName] = ZoneName -- Add zone to DB. self:AddZone( ZoneName, Zone_Polygon ) end end -- Drawings as zones if env.mission.drawings and env.mission.drawings.layers then -- Loop over layers. for layerID, layerData in pairs(env.mission.drawings.layers or {}) do -- Loop over objects in layers. for objectID, objectData in pairs(layerData.objects or {}) do -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) if objectData.polygonMode and (objectData.polygonMode=="free") and objectData.points and #objectData.points>=4 then --- -- Drawing: Polygon free --- -- Name of the zone. local ZoneName=objectData.name or "Unknown free Polygon Drawing" -- Reference point. All other points need to be translated by this. local vec2={x=objectData.mapX, y=objectData.mapY} -- Debug stuff. --local vec3={x=objectData.mapX, y=0, z=objectData.mapY} --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") --trigger.action.markToAll(id, "mapXY", vec3) -- Copy points array. local points=UTILS.DeepCopy(objectData.points) -- Translate points. for i,_point in pairs(points) do local point=_point --DCS#Vec2 points[i]=UTILS.Vec2Add(point, vec2) end -- Remove last point. table.remove(points, #points) -- Debug output self:I(string.format("Register ZONE: %s (Polygon (free) drawing with %d vertices)", ZoneName, #points)) -- Create new polygon zone. local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) --Zone.DrawID = objectID -- Set color. Zone:SetColor({1, 0, 0}, 0.15) Zone:SetFillColor({1, 0, 0}, 0.15) if objectData.colorString then -- eg colorString = 0xff0000ff local color = string.gsub(objectData.colorString,"^0x","") local r = tonumber(string.sub(color,1,2),16)/255 local g = tonumber(string.sub(color,3,4),16)/255 local b = tonumber(string.sub(color,5,6),16)/255 local a = tonumber(string.sub(color,7,8),16)/255 Zone:SetColor({r, g, b}, a) end if objectData.fillColorString then -- eg fillColorString = 0xff00004b local color = string.gsub(objectData.colorString,"^0x","") local r = tonumber(string.sub(color,1,2),16)/255 local g = tonumber(string.sub(color,3,4),16)/255 local b = tonumber(string.sub(color,5,6),16)/255 local a = tonumber(string.sub(color,7,8),16)/255 Zone:SetFillColor({r, g, b}, a) end -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName -- Add zone. self:AddZone(ZoneName, Zone) -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) elseif objectData.polygonMode and objectData.polygonMode=="rect" then --- -- Drawing: Polygon rect --- -- Name of the zone. local ZoneName=objectData.name or "Unknown rect Polygon Drawing" -- Reference point (center of the rectangle). local vec2={x=objectData.mapX, y=objectData.mapY} -- For a rectangular polygon drawing, we have the width (y) and height (x). local w=objectData.width local h=objectData.height -- Create points from center using with and height (width for y and height for x is a bit confusing, but this is how ED implemented it). local points={} points[1]={x=vec2.x-h/2, y=vec2.y+w/2} --Upper left points[2]={x=vec2.x+h/2, y=vec2.y+w/2} --Upper right points[3]={x=vec2.x+h/2, y=vec2.y-w/2} --Lower right points[4]={x=vec2.x-h/2, y=vec2.y-w/2} --Lower left --local coord=COORDINATE:NewFromVec2(vec2):MarkToAll("MapX, MapY") -- Debug output self:I(string.format("Register ZONE: %s (Polygon (rect) drawing with %d vertices)", ZoneName, #points)) -- Create new polygon zone. local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) -- Set color. Zone:SetColor({1, 0, 0}, 0.15) if objectData.colorString then -- eg colorString = 0xff0000ff local color = string.gsub(objectData.colorString,"^0x","") local r = tonumber(string.sub(color,1,2),16)/255 local g = tonumber(string.sub(color,3,4),16)/255 local b = tonumber(string.sub(color,5,6),16)/255 local a = tonumber(string.sub(color,7,8),16)/255 Zone:SetColor({r, g, b}, a) end if objectData.fillColorString then -- eg fillColorString = 0xff00004b local color = string.gsub(objectData.colorString,"^0x","") local r = tonumber(string.sub(color,1,2),16)/255 local g = tonumber(string.sub(color,3,4),16)/255 local b = tonumber(string.sub(color,5,6),16)/255 local a = tonumber(string.sub(color,7,8),16)/255 Zone:SetFillColor({r, g, b}, a) end -- Store in DB. self.ZONENAMES[ZoneName] = ZoneName -- Add zone. self:AddZone(ZoneName, Zone) elseif objectData.lineMode and (objectData.lineMode=="segments" or objectData.lineMode=="segment" or objectData.lineMode=="free") and objectData.points and #objectData.points>=2 then --- -- Drawing: Line (segments, segment or free) --- -- Name of the zone. local Name=objectData.name or "Unknown Line Drawing" -- Reference point. All other points need to be translated by this. local vec2={x=objectData.mapX, y=objectData.mapY} -- Copy points array. local points=UTILS.DeepCopy(objectData.points) -- Translate points. for i,_point in pairs(points) do local point=_point --DCS#Vec2 points[i]=UTILS.Vec2Add(point, vec2) end -- Debug output self:I(string.format("Register PATHLINE: %s (Line drawing with %d points)", Name, #points)) -- Create new polygon zone. local Pathline=PATHLINE:NewFromVec2Array(Name, points) -- Set color. --Zone:SetColor({1, 0, 0}, 0.15) -- Add zone. self:AddPathline(Name,Pathline) end end end end end end -- zone do -- Zone_Goal --- Finds a @{Core.Zone} based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @return Core.Zone#ZONE_BASE The found ZONE. function DATABASE:FindZoneGoal( ZoneName ) local ZoneFound = self.ZONES_GOAL[ZoneName] return ZoneFound end --- Adds a @{Core.Zone} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @param Core.Zone#ZONE_BASE Zone The zone. function DATABASE:AddZoneGoal( ZoneName, Zone ) if not self.ZONES_GOAL[ZoneName] then self.ZONES_GOAL[ZoneName] = Zone end end --- Deletes a @{Core.Zone} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteZoneGoal( ZoneName ) self.ZONES_GOAL[ZoneName] = nil end end -- Zone_Goal do -- OpsZone --- Finds a @{Ops.OpsZone#OPSZONE} based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. -- @return Ops.OpsZone#OPSZONE The found OPSZONE. function DATABASE:FindOpsZone( ZoneName ) local ZoneFound = self.OPSZONES[ZoneName] return ZoneFound end --- Adds a @{Ops.OpsZone#OPSZONE} based on the zone name in the DATABASE. -- @param #DATABASE self -- @param Ops.OpsZone#OPSZONE OpsZone The zone. function DATABASE:AddOpsZone( OpsZone ) if OpsZone then local ZoneName=OpsZone:GetName() if not self.OPSZONES[ZoneName] then self.OPSZONES[ZoneName] = OpsZone end end end --- Deletes a @{Ops.OpsZone#OPSZONE} from the DATABASE based on the zone name. -- @param #DATABASE self -- @param #string ZoneName The name of the zone. function DATABASE:DeleteOpsZone( ZoneName ) self.OPSZONES[ZoneName] = nil end end -- OpsZone do -- cargo --- Adds a Cargo based on the Cargo Name in the DATABASE. -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:AddCargo( Cargo ) if not self.CARGOS[Cargo.Name] then self.CARGOS[Cargo.Name] = Cargo end end --- Deletes a Cargo from the DATABASE based on the Cargo Name. -- @param #DATABASE self -- @param #string CargoName The name of the airbase function DATABASE:DeleteCargo( CargoName ) self.CARGOS[CargoName] = nil end --- Finds an CARGO based on the CargoName. -- @param #DATABASE self -- @param #string CargoName -- @return Cargo.Cargo#CARGO The found CARGO. function DATABASE:FindCargo( CargoName ) local CargoFound = self.CARGOS[CargoName] return CargoFound end --- Checks if the Template name has a #CARGO tag. -- If yes, the group is a cargo. -- @param #DATABASE self -- @param #string TemplateName -- @return #boolean function DATABASE:IsCargo( TemplateName ) TemplateName = env.getValueDictByKey( TemplateName ) local Cargo = TemplateName:match( "#(CARGO)" ) return Cargo and Cargo == "CARGO" end --- Private method that registers new Static Templates within the DATABASE Object. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterCargos() local Groups = UTILS.DeepCopy( self.GROUPS ) -- This is a very important statement. CARGO_GROUP:New creates a new _DATABASE.GROUP entry, which will confuse the loop. I searched 4 hours on this to find the bug! for CargoGroupName, CargoGroup in pairs( Groups ) do if self:IsCargo( CargoGroupName ) then local CargoInfo = CargoGroupName:match("#CARGO(.*)") local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") local CargoName1 = CargoGroupName:match("(.*)#CARGO%(.*%)") local CargoName2 = CargoGroupName:match(".*#CARGO%(.*%)(.*)") local CargoName = CargoName1 .. ( CargoName2 or "" ) local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) self:I({"Register CargoGroup:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_GROUP:New( CargoGroup, Type, Name, LoadRadius, NearRadius ) end end for CargoStaticName, CargoStatic in pairs( self.STATICS ) do if self:IsCargo( CargoStaticName ) then local CargoInfo = CargoStaticName:match("#CARGO(.*)") local CargoParam = CargoInfo and CargoInfo:match( "%((.*)%)") local CargoName = CargoStaticName:match("(.*)#CARGO") local Type = CargoParam and CargoParam:match( "T=([%a%d ]+),?") local Category = CargoParam and CargoParam:match( "C=([%a%d ]+),?") local Name = CargoParam and CargoParam:match( "N=([%a%d]+),?") or CargoName local LoadRadius = CargoParam and tonumber( CargoParam:match( "RR=([%a%d]+),?") ) local NearRadius = CargoParam and tonumber( CargoParam:match( "NR=([%a%d]+),?") ) if Category == "SLING" then self:I({"Register CargoSlingload:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_SLINGLOAD:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) else if Category == "CRATE" then self:I({"Register CargoCrate:",Type=Type,Name=Name,LoadRadius=LoadRadius,NearRadius=NearRadius}) CARGO_CRATE:New( CargoStatic, Type, Name, LoadRadius, NearRadius ) end end end end end end -- cargo --- Finds a CLIENT based on the ClientName. -- @param #DATABASE self -- @param #string ClientName - Note this is the UNIT name of the client! -- @return Wrapper.Client#CLIENT The found CLIENT. function DATABASE:FindClient( ClientName ) local ClientFound = self.CLIENTS[ClientName] return ClientFound end --- Adds a CLIENT based on the ClientName in the DATABASE. -- @param #DATABASE self -- @param #string ClientName Name of the Client unit. -- @return Wrapper.Client#CLIENT The client object. function DATABASE:AddClient( ClientName ) if not self.CLIENTS[ClientName] then self.CLIENTS[ClientName] = CLIENT:Register( ClientName ) end return self.CLIENTS[ClientName] end --- Finds a GROUP based on the GroupName. -- @param #DATABASE self -- @param #string GroupName -- @return Wrapper.Group#GROUP The found GROUP. function DATABASE:FindGroup( GroupName ) local GroupFound = self.GROUPS[GroupName] return GroupFound end --- Adds a GROUP based on the GroupName in the DATABASE. -- @param #DATABASE self function DATABASE:AddGroup( GroupName ) if not self.GROUPS[GroupName] then self:T( { "Add GROUP:", GroupName } ) self.GROUPS[GroupName] = GROUP:Register( GroupName ) end return self.GROUPS[GroupName] end --- Adds a player based on the Player Name in the DATABASE. -- @param #DATABASE self function DATABASE:AddPlayer( UnitName, PlayerName ) if PlayerName then self:T( { "Add player for unit:", UnitName, PlayerName } ) self.PLAYERS[PlayerName] = UnitName self.PLAYERUNITS[PlayerName] = self:FindUnit( UnitName ) self.PLAYERSJOINED[PlayerName] = PlayerName end end --- Deletes a player from the DATABASE based on the Player Name. -- @param #DATABASE self function DATABASE:DeletePlayer( UnitName, PlayerName ) if PlayerName then self:T( { "Clean player:", PlayerName } ) self.PLAYERS[PlayerName] = nil self.PLAYERUNITS[PlayerName] = nil end end --- Get the player table from the DATABASE. -- The player table contains all unit names with the key the name of the player (PlayerName). -- @param #DATABASE self -- @usage -- local Players = _DATABASE:GetPlayers() -- for PlayerName, UnitName in pairs( Players ) do -- .. -- end function DATABASE:GetPlayers() return self.PLAYERS end --- Get the player table from the DATABASE, which contains all UNIT objects. -- The player table contains all UNIT objects of the player with the key the name of the player (PlayerName). -- @param #DATABASE self -- @usage -- local PlayerUnits = _DATABASE:GetPlayerUnits() -- for PlayerName, PlayerUnit in pairs( PlayerUnits ) do -- .. -- end function DATABASE:GetPlayerUnits() return self.PLAYERUNITS end --- Get the player table from the DATABASE which have joined in the mission historically. -- The player table contains all UNIT objects with the key the name of the player (PlayerName). -- @param #DATABASE self -- @usage -- local PlayersJoined = _DATABASE:GetPlayersJoined() -- for PlayerName, PlayerUnit in pairs( PlayersJoined ) do -- .. -- end function DATABASE:GetPlayersJoined() return self.PLAYERSJOINED end --- Instantiate new Groups within the DCSRTE. -- This method expects EXACTLY the same structure as a structure within the ME, and needs 2 additional fields defined: -- SpawnCountryID, SpawnCategoryID -- This method is used by the SPAWN class. -- @param #DATABASE self -- @param #table SpawnTemplate Template of the group to spawn. -- @return Wrapper.Group#GROUP Spawned group. function DATABASE:Spawn( SpawnTemplate ) self:F( SpawnTemplate.name ) self:T( { SpawnTemplate.SpawnCountryID, SpawnTemplate.SpawnCategoryID } ) -- Copy the spawn variables of the template in temporary storage, nullify, and restore the spawn variables. local SpawnCoalitionID = SpawnTemplate.CoalitionID local SpawnCountryID = SpawnTemplate.CountryID local SpawnCategoryID = SpawnTemplate.CategoryID -- Nullify SpawnTemplate.CoalitionID = nil SpawnTemplate.CountryID = nil SpawnTemplate.CategoryID = nil self:_RegisterGroupTemplate( SpawnTemplate, SpawnCoalitionID, SpawnCategoryID, SpawnCountryID, SpawnTemplate.name ) self:T3( SpawnTemplate ) coalition.addGroup( SpawnCountryID, SpawnCategoryID, SpawnTemplate ) -- Restore SpawnTemplate.CoalitionID = SpawnCoalitionID SpawnTemplate.CountryID = SpawnCountryID SpawnTemplate.CategoryID = SpawnCategoryID -- Ensure that for the spawned group and its units, there are GROUP and UNIT objects created in the DATABASE. local SpawnGroup = self:AddGroup( SpawnTemplate.name ) for UnitID, UnitData in pairs( SpawnTemplate.units ) do self:AddUnit( UnitData.name ) end return SpawnGroup end --- Set a status to a Group within the Database, this to check crossing events for example. -- @param #DATABASE self -- @param #string GroupName Group name. -- @param #string Status Status. function DATABASE:SetStatusGroup( GroupName, Status ) self:F2( Status ) self.Templates.Groups[GroupName].Status = Status end --- Get a status to a Group within the Database, this to check crossing events for example. -- @param #DATABASE self -- @param #string GroupName Group name. -- @return #string Status or an empty string "". function DATABASE:GetStatusGroup( GroupName ) self:F2( GroupName ) if self.Templates.Groups[GroupName] then return self.Templates.Groups[GroupName].Status else return "" end end --- Private method that registers new Group Templates within the DATABASE Object. -- @param #DATABASE self -- @param #table GroupTemplate -- @param DCS#coalition.side CoalitionSide The coalition.side of the object. -- @param DCS#Object.Category CategoryID The Object.category of the object. -- @param DCS#country.id CountryID the country ID of the object. -- @param #string GroupName (Optional) The name of the group. Default is `GroupTemplate.name`. -- @return #DATABASE self function DATABASE:_RegisterGroupTemplate( GroupTemplate, CoalitionSide, CategoryID, CountryID, GroupName ) local GroupTemplateName = GroupName or env.getValueDictByKey( GroupTemplate.name ) 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. if GroupTemplate.route and GroupTemplate.route.spans then GroupTemplate.route.spans = nil end GroupTemplate.CategoryID = CategoryID GroupTemplate.CoalitionID = CoalitionSide GroupTemplate.CountryID = CountryID 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.Templates.Groups[GroupTemplateName].CategoryID = CategoryID self.Templates.Groups[GroupTemplateName].CoalitionID = CoalitionSide self.Templates.Groups[GroupTemplateName].CountryID = CountryID local UnitNames = {} for unit_num, UnitTemplate in pairs( GroupTemplate.units ) do UnitTemplate.name = env.getValueDictByKey(UnitTemplate.name) self.Templates.Units[UnitTemplate.name] = {} self.Templates.Units[UnitTemplate.name].UnitName = UnitTemplate.name self.Templates.Units[UnitTemplate.name].Template = UnitTemplate self.Templates.Units[UnitTemplate.name].GroupName = GroupTemplateName self.Templates.Units[UnitTemplate.name].GroupTemplate = GroupTemplate self.Templates.Units[UnitTemplate.name].GroupId = GroupTemplate.groupId self.Templates.Units[UnitTemplate.name].CategoryID = CategoryID self.Templates.Units[UnitTemplate.name].CoalitionID = CoalitionSide self.Templates.Units[UnitTemplate.name].CountryID = CountryID if UnitTemplate.skill and (UnitTemplate.skill == "Client" or UnitTemplate.skill == "Player") then self.Templates.ClientsByName[UnitTemplate.name] = UnitTemplate self.Templates.ClientsByName[UnitTemplate.name].CategoryID = CategoryID self.Templates.ClientsByName[UnitTemplate.name].CoalitionID = CoalitionSide self.Templates.ClientsByName[UnitTemplate.name].CountryID = CountryID self.Templates.ClientsByID[UnitTemplate.unitId] = UnitTemplate end if UnitTemplate.AddPropAircraft then if UnitTemplate.AddPropAircraft.STN_L16 then local stn = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.STN_L16) if stn == nil or stn < 1 then self:E("WARNING: Invalid STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) else self.STNS[stn] = UnitTemplate.name self:I("Register STN "..tostring(UnitTemplate.AddPropAircraft.STN_L16).." for ".. UnitTemplate.name) end end if UnitTemplate.AddPropAircraft.SADL_TN then local sadl = UTILS.OctalToDecimal(UnitTemplate.AddPropAircraft.SADL_TN) if sadl == nil or sadl < 1 then self:E("WARNING: Invalid SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) else self.SADL[sadl] = UnitTemplate.name self:I("Register SADL "..tostring(UnitTemplate.AddPropAircraft.SADL_TN).." for ".. UnitTemplate.name) end end end UnitNames[#UnitNames+1] = self.Templates.Units[UnitTemplate.name].UnitName end -- Debug info. self:T( { Group = self.Templates.Groups[GroupTemplateName].GroupName, Coalition = self.Templates.Groups[GroupTemplateName].CoalitionID, Category = self.Templates.Groups[GroupTemplateName].CategoryID, Country = self.Templates.Groups[GroupTemplateName].CountryID, Units = UnitNames } ) end --- Get next (consecutive) free STN as octal number. -- @param #DATABASE self -- @param #number octal Starting octal. -- @param #string unitname Name of the associated unit. -- @return #number Octal function DATABASE:GetNextSTN(octal,unitname) local first = UTILS.OctalToDecimal(octal) if self.STNS[first] == unitname then return octal end local nextoctal = 77777 local found = false if 32767-first < 10 then first = 0 end for i=first+1,32767 do if self.STNS[i] == nil then found = true nextoctal = UTILS.DecimalToOctal(i) self.STNS[i] = unitname self:T("Register STN "..tostring(nextoctal).." for ".. unitname) break end end if not found then self:E(string.format("WARNING: No next free STN past %05d found!",octal)) -- cleanup local NewSTNS = {} for _id,_name in pairs(self.STNS) do if self.UNITS[_name] ~= nil then NewSTNS[_id] = _name end end self.STNS = nil self.STNS = NewSTNS end return nextoctal end --- Get next (consecutive) free SADL as octal number. -- @param #DATABASE self -- @param #number octal Starting octal. -- @param #string unitname Name of the associated unit. -- @return #number Octal function DATABASE:GetNextSADL(octal,unitname) local first = UTILS.OctalToDecimal(octal) if self.SADL[first] == unitname then return octal end local nextoctal = 7777 local found = false if 4095-first < 10 then first = 0 end for i=first+1,4095 do if self.STNS[i] == nil then found = true nextoctal = UTILS.DecimalToOctal(i) self.SADL[i] = unitname self:T("Register SADL "..tostring(nextoctal).." for ".. unitname) break end end if not found then self:E(string.format("WARNING: No next free SADL past %04d found!",octal)) -- cleanup local NewSTNS = {} for _id,_name in pairs(self.SADL) do if self.UNITS[_name] ~= nil then NewSTNS[_id] = _name end end self.SADL = nil self.SADL = NewSTNS end return nextoctal end --- Get group template. -- @param #DATABASE self -- @param #string GroupName Group name. -- @return #table Group template table. function DATABASE:GetGroupTemplate( GroupName ) local GroupTemplate = self.Templates.Groups[GroupName].Template GroupTemplate.SpawnCoalitionID = self.Templates.Groups[GroupName].CoalitionID GroupTemplate.SpawnCategoryID = self.Templates.Groups[GroupName].CategoryID GroupTemplate.SpawnCountryID = self.Templates.Groups[GroupName].CountryID return GroupTemplate end --- Private method that registers new Static Templates within the DATABASE Object. -- @param #DATABASE self -- @param #table StaticTemplate Template table. -- @param #number CoalitionID Coalition ID. -- @param #number CategoryID Category ID. -- @param #number CountryID Country ID. -- @return #DATABASE self function DATABASE:_RegisterStaticTemplate( StaticTemplate, CoalitionID, CategoryID, CountryID ) local StaticTemplate = UTILS.DeepCopy( StaticTemplate ) local StaticTemplateGroupName = env.getValueDictByKey(StaticTemplate.name) local StaticTemplateName=StaticTemplate.units[1].name self.Templates.Statics[StaticTemplateName] = self.Templates.Statics[StaticTemplateName] or {} StaticTemplate.CategoryID = CategoryID StaticTemplate.CoalitionID = CoalitionID StaticTemplate.CountryID = CountryID self.Templates.Statics[StaticTemplateName].StaticName = StaticTemplateGroupName self.Templates.Statics[StaticTemplateName].GroupTemplate = StaticTemplate self.Templates.Statics[StaticTemplateName].UnitTemplate = StaticTemplate.units[1] self.Templates.Statics[StaticTemplateName].CategoryID = CategoryID self.Templates.Statics[StaticTemplateName].CoalitionID = CoalitionID self.Templates.Statics[StaticTemplateName].CountryID = CountryID -- Debug info. self:T( { Static = self.Templates.Statics[StaticTemplateName].StaticName, Coalition = self.Templates.Statics[StaticTemplateName].CoalitionID, Category = self.Templates.Statics[StaticTemplateName].CategoryID, Country = self.Templates.Statics[StaticTemplateName].CountryID } ) self:AddStatic( StaticTemplateName ) return self end --- Get static group template. -- @param #DATABASE self -- @param #string StaticName Name of the static. -- @return #table Static template table. function DATABASE:GetStaticGroupTemplate( StaticName ) if self.Templates.Statics[StaticName] then local StaticTemplate = self.Templates.Statics[StaticName].GroupTemplate return StaticTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID else self:E("ERROR: Static group template does NOT exist for static "..tostring(StaticName)) return nil end end --- Get static unit template. -- @param #DATABASE self -- @param #string StaticName Name of the static. -- @return #table Static template table. function DATABASE:GetStaticUnitTemplate( StaticName ) if self.Templates.Statics[StaticName] then local UnitTemplate = self.Templates.Statics[StaticName].UnitTemplate return UnitTemplate, self.Templates.Statics[StaticName].CoalitionID, self.Templates.Statics[StaticName].CategoryID, self.Templates.Statics[StaticName].CountryID else self:E("ERROR: Static unit template does NOT exist for static "..tostring(StaticName)) return nil end end --- Get group name from unit name. -- @param #DATABASE self -- @param #string UnitName Name of the unit. -- @return #string Group name. function DATABASE:GetGroupNameFromUnitName( UnitName ) if self.Templates.Units[UnitName] then return self.Templates.Units[UnitName].GroupName else self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) return nil end end --- Get group template from unit name. -- @param #DATABASE self -- @param #string UnitName Name of the unit. -- @return #table Group template. function DATABASE:GetGroupTemplateFromUnitName( UnitName ) if self.Templates.Units[UnitName] then return self.Templates.Units[UnitName].GroupTemplate else self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) return nil end end --- Get group template from unit name. -- @param #DATABASE self -- @param #string UnitName Name of the unit. -- @return #table Group template. function DATABASE:GetUnitTemplateFromUnitName( UnitName ) if self.Templates.Units[UnitName] then return self.Templates.Units[UnitName] else self:E("ERROR: Unit template does not exist for unit "..tostring(UnitName)) return nil end end --- Get coalition ID from client name. -- @param #DATABASE self -- @param #string ClientName Name of the Client. -- @return #number Coalition ID. function DATABASE:GetCoalitionFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CoalitionID end --- Get category ID from client name. -- @param #DATABASE self -- @param #string ClientName Name of the Client. -- @return #number Category ID. function DATABASE:GetCategoryFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CategoryID end --- Get country ID from client name. -- @param #DATABASE self -- @param #string ClientName Name of the Client. -- @return #number Country ID. function DATABASE:GetCountryFromClientTemplate( ClientName ) return self.Templates.ClientsByName[ClientName].CountryID end --- Airbase --- Get coalition ID from airbase name. -- @param #DATABASE self -- @param #string AirbaseName Name of the airbase. -- @return #number Coalition ID. function DATABASE:GetCoalitionFromAirbase( AirbaseName ) return self.AIRBASES[AirbaseName]:GetCoalition() end --- Get category from airbase name. -- @param #DATABASE self -- @param #string AirbaseName Name of the airbase. -- @return #number Category. function DATABASE:GetCategoryFromAirbase( AirbaseName ) return self.AIRBASES[AirbaseName]:GetAirbaseCategory() 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 ), AlivePlayersNeutral = coalition.getPlayers( coalition.side.NEUTRAL ) } 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() local PlayerName = UnitData:getPlayerName() if not self.PLAYERS[PlayerName] then self:I( { "Add player for unit:", UnitName, PlayerName } ) self:AddPlayer( UnitName, PlayerName ) end end end end return self end --- Private method that registers all Groups and Units within in the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterGroupsAndUnits() local CoalitionsData = { GroupsRed = coalition.getGroups( coalition.side.RED ), GroupsBlue = coalition.getGroups( coalition.side.BLUE ), GroupsNeutral = coalition.getGroups( coalition.side.NEUTRAL ) } for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSGroupId, DCSGroup in pairs( CoalitionData ) do if DCSGroup:isExist() then -- Group name. local DCSGroupName = DCSGroup:getName() -- Add group. self:I(string.format("Register Group: %s", tostring(DCSGroupName))) self:AddGroup( DCSGroupName ) -- Loop over units in group. for DCSUnitId, DCSUnit in pairs( DCSGroup:getUnits() ) do -- Get unit name. local DCSUnitName = DCSUnit:getName() -- Add unit. self:I(string.format("Register Unit: %s", tostring(DCSUnitName))) self:AddUnit( DCSUnitName ) end else self:E({"Group does not exist: ", DCSGroup}) end end end return self end --- Private method that registers all Units of skill Client or Player within in the mission. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterClients() for ClientName, ClientTemplate in pairs( self.Templates.ClientsByName ) do self:I(string.format("Register Client: %s", tostring(ClientName))) local client=self:AddClient( ClientName ) client.SpawnCoord=COORDINATE:New(ClientTemplate.x, ClientTemplate.alt, ClientTemplate.y) end return self end --- Private method that registeres all static objects. -- @param #DATABASE self function DATABASE:_RegisterStatics() local CoalitionsData={GroupsRed=coalition.getStaticObjects(coalition.side.RED), GroupsBlue=coalition.getStaticObjects(coalition.side.BLUE), GroupsNeutral=coalition.getStaticObjects(coalition.side.NEUTRAL)} for CoalitionId, CoalitionData in pairs( CoalitionsData ) do for DCSStaticId, DCSStatic in pairs( CoalitionData ) do if DCSStatic:isExist() then local DCSStaticName = DCSStatic:getName() self:I(string.format("Register Static: %s", tostring(DCSStaticName))) self:AddStatic( DCSStaticName ) else self:E( { "Static does not exist: ", DCSStatic } ) end end end return self end --- Register all world airbases. -- @param #DATABASE self -- @return #DATABASE self function DATABASE:_RegisterAirbases() for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do self:_RegisterAirbase(DCSAirbase) end return self end --- Register a DCS airbase. -- @param #DATABASE self -- @param DCS#Airbase airbase Airbase. -- @return #DATABASE self function DATABASE:_RegisterAirbase(airbase) if airbase then -- Get the airbase name. local DCSAirbaseName = airbase:getName() -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. local airbaseID=airbase:getID() -- Add and register airbase. local airbase=self:AddAirbase( DCSAirbaseName ) -- Unique ID. local airbaseUID=airbase:GetID(true) local typename = airbase:GetTypeName() local category = airbase.category if category == Airbase.Category.SHIP and typename == "FARP_SINGLE_01" then category = Airbase.Category.HELIPAD end -- Debug output. local text=string.format("Register %s: %s (UID=%d), Runways=%d, Parking=%d [", AIRBASE.CategoryName[category], tostring(DCSAirbaseName), airbaseUID, #airbase.runways, airbase.NparkingTotal) for _,terminalType in pairs(AIRBASE.TerminalType) do if airbase.NparkingTerminal and airbase.NparkingTerminal[terminalType] then text=text..string.format("%d=%d ", terminalType, airbase.NparkingTerminal[terminalType]) end end text=text.."]" self:I(text) end return self end --- Events --- Handles the OnBirth event for the alive units set. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnBirth( Event ) self:F( { Event } ) if Event.IniDCSUnit then if Event.IniObjectCategory == Object.Category.STATIC then -- Add static object to DB. self:AddStatic( Event.IniDCSUnitName ) else if Event.IniObjectCategory == Object.Category.UNIT then -- Add unit and group to DB. self:AddUnit( Event.IniDCSUnitName ) self:AddGroup( Event.IniDCSGroupName ) -- A unit can also be an airbase (e.g. ships). local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName) if DCSAirbase then -- Add airbase if it was spawned later in the mission. self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName))) self:AddAirbase(Event.IniDCSUnitName) end end end if Event.IniObjectCategory == Object.Category.UNIT then Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) -- Client local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT if client then -- TODO: create event ClientAlive end -- Get player name. local PlayerName = Event.IniUnit:GetPlayerName() if PlayerName then -- Debug info. self:I(string.format("Player '%s' joined unit '%s' of group '%s'", tostring(PlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) -- Add client in case it does not exist already. if not client then client=self:AddClient(Event.IniDCSUnitName) end -- Add player. client:AddPlayer(PlayerName) -- Add player. if not self.PLAYERS[PlayerName] then self:AddPlayer( Event.IniUnitName, PlayerName ) end -- Player settings. local Settings = SETTINGS:Set( PlayerName ) Settings:SetPlayerMenu(Event.IniUnit) -- Create an event. self:CreateEventPlayerEnterAircraft(Event.IniUnit) end end end end --- Handles the OnDead or OnCrash event for alive units set. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnDeadOrCrash( Event ) if Event.IniDCSUnit then local name=Event.IniDCSUnitName if Event.IniObjectCategory == 3 then --- -- STATICS --- if self.STATICS[Event.IniDCSUnitName] then self:DeleteStatic( Event.IniDCSUnitName ) end --- -- Maybe a UNIT? --- -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then self:T("STATIC Event for UNIT "..tostring(Event.IniDCSUnitName)) local DCSUnit = _DATABASE:FindUnit( Event.IniDCSUnitName ) self:T({DCSUnit}) if DCSUnit then --self:I("Creating DEAD Event for UNIT "..tostring(Event.IniDCSUnitName)) --DCSUnit:Destroy(true) return end end else if Event.IniObjectCategory == 1 then --- -- UNITS --- -- Delete unit. if self.UNITS[Event.IniDCSUnitName] then self:DeleteUnit(Event.IniDCSUnitName) end -- Remove client players. local client=self.CLIENTS[name] --Wrapper.Client#CLIENT if client then client:RemovePlayers() end end end -- Add airbase if it was spawned later in the mission. local airbase=self.AIRBASES[Event.IniDCSUnitName] --Wrapper.Airbase#AIRBASE if airbase and (airbase:IsHelipad() or airbase:IsShip()) then self:DeleteAirbase(Event.IniDCSUnitName) end end -- Account destroys. self:AccountDestroys( Event ) end --- Handles the OnPlayerEnterUnit event to fill the active players table for CA units (with the unit filter applied). -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerEnterUnit( Event ) self:F2( { Event } ) if Event.IniDCSUnit then -- Player entering a CA slot if Event.IniObjectCategory == 1 and Event.IniGroup and Event.IniGroup:IsGround() then local IsPlayer = Event.IniDCSUnit:getPlayerName() if IsPlayer then -- Debug info. self:I(string.format("Player '%s' joined GROUND unit '%s' of group '%s'", tostring(Event.IniPlayerName), tostring(Event.IniDCSUnitName), tostring(Event.IniDCSGroupName))) local client= self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT -- Add client in case it does not exist already. if not client then client=self:AddClient(Event.IniDCSUnitName) end -- Add player. client:AddPlayer(Event.IniPlayerName) -- Add player. if not self.PLAYERS[Event.IniPlayerName] then self:AddPlayer( Event.IniUnitName, Event.IniPlayerName ) end -- Player settings. local Settings = SETTINGS:Set( Event.IniPlayerName ) Settings:SetPlayerMenu(Event.IniUnit) end end end end --- Handles the OnPlayerLeaveUnit event to clean the active players table. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:_EventOnPlayerLeaveUnit( Event ) self:F2( { Event } ) local function FindPlayerName(UnitName) local playername = nil for _name,_unitname in pairs(self.PLAYERS) do if _unitname == UnitName then playername = _name break end end return playername end if Event.IniUnit then if Event.IniObjectCategory == 1 then -- Try to get the player name. This can be buggy for multicrew aircraft! local PlayerName = Event.IniUnit:GetPlayerName() or FindPlayerName(Event.IniUnitName) if PlayerName then -- Debug info. self:I(string.format("Player '%s' left unit %s", tostring(PlayerName), tostring(Event.IniUnitName))) -- Remove player menu. local Settings = SETTINGS:Set( PlayerName ) Settings:RemovePlayerMenu(Event.IniUnit) -- Delete player. self:DeletePlayer(Event.IniUnit, PlayerName) -- Client stuff. local client=self.CLIENTS[Event.IniDCSUnitName] --Wrapper.Client#CLIENT if client then client:RemovePlayer(PlayerName) end end end end end --- Iterators --- Iterate the DATABASE and call an iterator 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:ForEach( IteratorFunction, FinalizeFunction, arg, Set ) self:F2( arg ) local function CoRoutine() local Count = 0 for ObjectID, Object in pairs( Set ) do self:T2( Object ) IteratorFunction( Object, unpack( arg ) ) Count = Count + 1 -- if Count % 100 == 0 then -- coroutine.yield( false ) -- end end return true end -- local co = coroutine.create( CoRoutine ) local co = CoRoutine local function Schedule() -- local status, res = coroutine.resume( co ) local status, res = co() self:T3( { status, res } ) if status == false then error( res ) end if res == false then return true -- resume next time the loop end if FinalizeFunction then FinalizeFunction( unpack( arg ) ) end return false end --local Scheduler = SCHEDULER:New( self, Schedule, {}, 0.001, 0.001, 0 ) Schedule() return self end --- Iterate the DATABASE and call an iterator function for each **alive** STATIC, providing the STATIC and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a STATIC parameter. -- @return #DATABASE self function DATABASE:ForEachStatic( IteratorFunction, FinalizeFunction, ... ) --R2.1 self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.STATICS ) return self end --- Iterate the DATABASE and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. -- @return #DATABASE self function DATABASE:ForEachUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.UNITS ) return self end --- Iterate the DATABASE and call an iterator function for each **alive** GROUP, providing the GROUP and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a GROUP parameter. -- @return #DATABASE self function DATABASE:ForEachGroup( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.GROUPS ) return self end --- Iterate the DATABASE and call an iterator function for each **ALIVE** player, providing the player name and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept the player name. -- @return #DATABASE self function DATABASE:ForEachPlayer( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERS ) return self end --- Iterate the DATABASE and call an iterator function for each player who has joined the mission, providing the Unit of the player and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a UNIT parameter. -- @return #DATABASE self function DATABASE:ForEachPlayerJoined( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERSJOINED ) return self end --- Iterate the DATABASE and call an iterator function for each **ALIVE** player UNIT, providing the player UNIT and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept the player name. -- @return #DATABASE self function DATABASE:ForEachPlayerUnit( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.PLAYERUNITS ) return self end --- Iterate the DATABASE and call an iterator 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 object in the database. The function needs to accept a CLIENT parameter. -- @return #DATABASE self function DATABASE:ForEachClient( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.CLIENTS ) return self end --- Iterate the DATABASE and call an iterator function for each CARGO, providing the CARGO object to the function and optional parameters. -- @param #DATABASE self -- @param #function IteratorFunction The function that will be called for each object in the database. The function needs to accept a CLIENT parameter. -- @return #DATABASE self function DATABASE:ForEachCargo( IteratorFunction, FinalizeFunction, ... ) self:F2( arg ) self:ForEach( IteratorFunction, FinalizeFunction, arg, self.CARGOS ) return self end --- Handles the OnEventNewCargo event. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData function DATABASE:OnEventNewCargo( EventData ) self:F2( { EventData } ) if EventData.Cargo then self:AddCargo( EventData.Cargo ) end end --- Handles the OnEventDeleteCargo. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData function DATABASE:OnEventDeleteCargo( EventData ) self:F2( { EventData } ) if EventData.Cargo then self:DeleteCargo( EventData.Cargo.Name ) end end --- Handles the OnEventNewZone event. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData function DATABASE:OnEventNewZone( EventData ) self:F2( { EventData } ) if EventData.Zone then self:AddZone( EventData.Zone.ZoneName, EventData.Zone ) end end --- Handles the OnEventDeleteZone. -- @param #DATABASE self -- @param Core.Event#EVENTDATA EventData function DATABASE:OnEventDeleteZone( EventData ) self:F2( { EventData } ) if EventData.Zone then self:DeleteZone( EventData.Zone.ZoneName ) end end --- Gets the player settings -- @param #DATABASE self -- @param #string PlayerName -- @return Core.Settings#SETTINGS function DATABASE:GetPlayerSettings( PlayerName ) self:F2( { PlayerName } ) return self.PLAYERSETTINGS[PlayerName] end --- Sets the player settings -- @param #DATABASE self -- @param #string PlayerName -- @param Core.Settings#SETTINGS Settings -- @return Core.Settings#SETTINGS function DATABASE:SetPlayerSettings( PlayerName, Settings ) self:F2( { PlayerName, Settings } ) self.PLAYERSETTINGS[PlayerName] = Settings end --- Add an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) to the data base. -- @param #DATABASE self -- @param Ops.OpsGroup#OPSGROUP opsgroup The OPS group added to the DB. function DATABASE:AddOpsGroup(opsgroup) --env.info("Adding OPSGROUP "..tostring(opsgroup.groupname)) self.FLIGHTGROUPS[opsgroup.groupname]=opsgroup end --- Get an OPS group (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) from the data base. -- @param #DATABASE self -- @param #string groupname Group name of the group. Can also be passed as GROUP object. -- @return Ops.OpsGroup#OPSGROUP OPS group object. function DATABASE:GetOpsGroup(groupname) -- Get group and group name. if type(groupname)=="string" then else groupname=groupname:GetName() end --env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end --- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base. -- @param #DATABASE self -- @param #string groupname Group name of the group. Can also be passed as GROUP object. -- @return Ops.OpsGroup#OPSGROUP OPS group object. function DATABASE:FindOpsGroup(groupname) -- Get group and group name. if type(groupname)=="string" then else groupname=groupname:GetName() end --env.info("Getting OPSGROUP "..tostring(groupname)) return self.FLIGHTGROUPS[groupname] end --- Find an OPSGROUP (FLIGHTGROUP, ARMYGROUP, NAVYGROUP) in the data base for a given unit. -- @param #DATABASE self -- @param #string unitname Unit name. Can also be passed as UNIT object. -- @return Ops.OpsGroup#OPSGROUP OPS group object. function DATABASE:FindOpsGroupFromUnit(unitname) local unit=nil --Wrapper.Unit#UNIT local groupname -- Get group and group name. if type(unitname)=="string" then unit=UNIT:FindByName(unitname) else unit=unitname end if unit then groupname=unit:GetGroup():GetName() end if groupname then return self.FLIGHTGROUPS[groupname] else return nil end end --- Add a flight control to the data base. -- @param #DATABASE self -- @param OPS.FlightControl#FLIGHTCONTROL flightcontrol function DATABASE:AddFlightControl(flightcontrol) self:F2( { flightcontrol } ) self.FLIGHTCONTROLS[flightcontrol.airbasename]=flightcontrol end --- Get a flight control object from the data base. -- @param #DATABASE self -- @param #string airbasename Name of the associated airbase. -- @return OPS.FlightControl#FLIGHTCONTROL The FLIGHTCONTROL object.s function DATABASE:GetFlightControl(airbasename) return self.FLIGHTCONTROLS[airbasename] end -- @param #DATABASE self function DATABASE:_RegisterTemplates() self:F2() self.Navpoints = {} self.UNITS = {} --Build self.Navpoints for CoalitionName, coa_data in pairs(env.mission.coalition) do self:T({CoalitionName=CoalitionName}) if (CoalitionName == 'red' or CoalitionName == 'blue' or CoalitionName == 'neutrals') and type(coa_data) == 'table' then --self.Units[coa_name] = {} local CoalitionSide = coalition.side[string.upper(CoalitionName)] if CoalitionName=="red" then CoalitionSide=coalition.side.RED elseif CoalitionName=="blue" then CoalitionSide=coalition.side.BLUE else CoalitionSide=coalition.side.NEUTRAL end -- build nav points DB self.Navpoints[CoalitionName] = {} if coa_data.nav_points then --navpoints for nav_ind, nav_data in pairs(coa_data.nav_points) do if type(nav_data) == 'table' then self.Navpoints[CoalitionName][nav_ind] = UTILS.DeepCopy(nav_data) self.Navpoints[CoalitionName][nav_ind]['name'] = nav_data.callsignStr -- name is a little bit more self-explanatory. self.Navpoints[CoalitionName][nav_ind]['point'] = {} -- point is used by SSE, support it. self.Navpoints[CoalitionName][nav_ind]['point']['x'] = nav_data.x self.Navpoints[CoalitionName][nav_ind]['point']['y'] = 0 self.Navpoints[CoalitionName][nav_ind]['point']['z'] = nav_data.y end end end ------------------------------------------------- if coa_data.country then --there is a country table for cntry_id, cntry_data in pairs(coa_data.country) do local CountryName = string.upper(cntry_data.name) local CountryID = cntry_data.id self.COUNTRY_ID[CountryName] = CountryID self.COUNTRY_NAME[CountryID] = CountryName --self.Units[coa_name][countryName] = {} --self.Units[coa_name][countryName]["countryId"] = cntry_data.id if type(cntry_data) == 'table' then --just making sure for obj_type_name, obj_type_data in pairs(cntry_data) do if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then --should be an unncessary check local CategoryName = obj_type_name if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then --there's a group! --self.Units[coa_name][countryName][category] = {} for group_num, Template in pairs(obj_type_data.group) do if obj_type_name ~= "static" and Template and Template.units and type(Template.units) == 'table' then --making sure again- this is a valid group self:_RegisterGroupTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) else self:_RegisterStaticTemplate(Template, CoalitionSide, _DATABASECategory[string.lower(CategoryName)], CountryID) end --if GroupTemplate and GroupTemplate.units then end --for group_num, GroupTemplate in pairs(obj_type_data.group) do end --if ((type(obj_type_data) == 'table') and obj_type_data.group and (type(obj_type_data.group) == 'table') and (#obj_type_data.group > 0)) then end --if obj_type_name == "helicopter" or obj_type_name == "ship" or obj_type_name == "plane" or obj_type_name == "vehicle" or obj_type_name == "static" then end --for obj_type_name, obj_type_data in pairs(cntry_data) do end --if type(cntry_data) == 'table' then end --for cntry_id, cntry_data in pairs(coa_data.country) do end --if coa_data.country then --there is a country table end --if coa_name == 'red' or coa_name == 'blue' and type(coa_data) == 'table' then end --for coa_name, coa_data in pairs(mission.coalition) do return self end --- Account the Hits of the Players. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:AccountHits( Event ) self:F( { Event } ) if Event.IniPlayerName ~= nil then -- It is a player that is hitting something self:T( "Hitting Something" ) -- What is he hitting? if Event.TgtCategory then -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] Hit.Players = Hit.Players or {} Hit.Players[Event.IniPlayerName] = true end end -- It is a weapon initiated by a player, that is hitting something -- This seems to occur only with scenery and static objects. if Event.WeaponPlayerName ~= nil then self:T( "Hitting Scenery" ) -- What is he hitting? if Event.TgtCategory then if Event.WeaponCoalition then -- A coalition object was hit, probably a static. -- A target got hit self.HITS[Event.TgtUnitName] = self.HITS[Event.TgtUnitName] or {} local Hit = self.HITS[Event.TgtUnitName] Hit.Players = Hit.Players or {} Hit.Players[Event.WeaponPlayerName] = true else -- A scenery object was hit. end end end end --- Account the destroys. -- @param #DATABASE self -- @param Core.Event#EVENTDATA Event function DATABASE:AccountDestroys( Event ) self:F( { Event } ) local TargetUnit = nil local TargetGroup = nil local TargetUnitName = "" local TargetGroupName = "" local TargetPlayerName = "" local TargetCoalition = nil local TargetCategory = nil local TargetType = nil local TargetUnitCoalition = nil local TargetUnitCategory = nil local TargetUnitType = nil if Event.IniDCSUnit then TargetUnit = Event.IniUnit TargetUnitName = Event.IniDCSUnitName TargetGroup = Event.IniDCSGroup TargetGroupName = Event.IniDCSGroupName TargetPlayerName = Event.IniPlayerName TargetCoalition = Event.IniCoalition TargetCategory = Event.IniCategory TargetType = Event.IniTypeName TargetUnitType = TargetType self:T( { TargetUnitName, TargetGroupName, TargetPlayerName, TargetCoalition, TargetCategory, TargetType } ) end local Destroyed = false -- What is the player destroying? if self.HITS[Event.IniUnitName] then -- Was there a hit for this unit for this player before registered??? self.DESTROYS[Event.IniUnitName] = self.DESTROYS[Event.IniUnitName] or {} self.DESTROYS[Event.IniUnitName] = true end end