Merge branch 'develop' into FF/OpsDev

This commit is contained in:
Frank
2025-04-09 22:43:24 +02:00
144 changed files with 13755 additions and 6814 deletions

View File

@@ -64,6 +64,11 @@
-- To be able to distinguish easily in your code the difference between a AIRBASE API call and a DCS Airbase API call,
-- the first letter of the method is also capitalized. So, by example, the DCS Airbase method DCSWrapper.Airbase#Airbase.getName()
-- is implemented in the AIRBASE class as @{#AIRBASE.GetName}().
--
-- ## Note on the "H" heli pads in the Syria map:
--
-- As of the time of writing (Oct 2024, DCS DCS 2.9.8.1107), these 143 objects have the **same name and object ID**, which makes them unusable in Moose, e.g. you cannot find a specific one for spawning etc.
-- Waiting for Ugra and ED to fix this issue.
--
-- @field #AIRBASE AIRBASE
AIRBASE = {
@@ -250,6 +255,9 @@ AIRBASE.Nevada = {
-- * AIRBASE.Normandy.Villacoublay
-- * AIRBASE.Normandy.Vrigny
-- * AIRBASE.Normandy.West_Malling
-- * AIRBASE.Normandy.Eastchurch
-- * AIRBASE.Normandy.Headcorn
-- * AIRBASE.Normandy.Hawkinge
--
-- @field Normandy
AIRBASE.Normandy = {
@@ -332,6 +340,9 @@ AIRBASE.Normandy = {
["Villacoublay"] = "Villacoublay",
["Vrigny"] = "Vrigny",
["West_Malling"] = "West Malling",
["Eastchurch"] = "Eastchurch",
["Headcorn"] = "Headcorn",
["Hawkinge"] = "Hawkinge",
}
--- Airbases of the Persion Gulf Map:
@@ -452,6 +463,7 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Gaziantep
-- * AIRBASE.Syria.Gazipasa
-- * AIRBASE.Syria.Gecitkale
-- * AIRBASE.Syria.H
-- * AIRBASE.Syria.H3
-- * AIRBASE.Syria.H3_Northwest
-- * AIRBASE.Syria.H3_Southwest
@@ -499,6 +511,10 @@ AIRBASE.TheChannel = {
-- * AIRBASE.Syria.Tha_lah
-- * AIRBASE.Syria.Tiyas
-- * AIRBASE.Syria.Wujah_Al_Hajar
-- * AIRBASE.Syria.Ben_Gurion
-- * AIRBASE.Syria.Hatzor
-- * AIRBASE.Syria.Palmashim
-- * AIRBASE.Syria.Tel_Nof
--
--@field Syria
AIRBASE.Syria={
@@ -520,6 +536,7 @@ AIRBASE.Syria={
["Gaziantep"] = "Gaziantep",
["Gazipasa"] = "Gazipasa",
["Gecitkale"] = "Gecitkale",
["H"] = "H",
["H3"] = "H3",
["H3_Northwest"] = "H3 Northwest",
["H3_Southwest"] = "H3 Southwest",
@@ -567,6 +584,10 @@ AIRBASE.Syria={
["Tha_lah"] = "Tha'lah",
["Tiyas"] = "Tiyas",
["Wujah_Al_Hajar"] = "Wujah Al Hajar",
["Ben_Gurion"] = "Ben Gurion",
["Hatzor"] = "Hatzor",
["Palmashim"] = "Palmashim",
["Tel_Nof"] = "Tel Nof",
}
--- Airbases of the Mariana Islands map:
@@ -595,7 +616,6 @@ AIRBASE.MarianaIslands = {
--- Airbases of the South Atlantic map:
--
-- * AIRBASE.SouthAtlantic.Almirante_Schroeders
-- * AIRBASE.SouthAtlantic.Caleta_Tortel
-- * AIRBASE.SouthAtlantic.Comandante_Luis_Piedrabuena
-- * AIRBASE.SouthAtlantic.Cullen
-- * AIRBASE.SouthAtlantic.El_Calafate
@@ -626,7 +646,6 @@ AIRBASE.MarianaIslands = {
--@field SouthAtlantic
AIRBASE.SouthAtlantic={
["Almirante_Schroeders"] = "Almirante Schroeders",
["Caleta_Tortel"] = "Caleta Tortel",
["Comandante_Luis_Piedrabuena"] = "Comandante Luis Piedrabuena",
["Cullen"] = "Cullen",
["El_Calafate"] = "El Calafate",
@@ -659,100 +678,237 @@ AIRBASE.SouthAtlantic={
--
-- * AIRBASE.Sinai.Abu_Rudeis
-- * AIRBASE.Sinai.Abu_Suwayr
-- * AIRBASE.Sinai.Al_Bahr_al_Ahmar
-- * AIRBASE.Sinai.Al_Ismailiyah
-- * AIRBASE.Sinai.Al_Khatatbah
-- * AIRBASE.Sinai.Al_Mansurah
-- * AIRBASE.Sinai.Al_Rahmaniyah_Air_Base
-- * AIRBASE.Sinai.As_Salihiyah
-- * AIRBASE.Sinai.AzZaqaziq
-- * AIRBASE.Sinai.Baluza
-- * AIRBASE.Sinai.Ben_Gurion
-- * AIRBASE.Sinai.Beni_Suef
-- * AIRBASE.Sinai.Bilbeis_Air_Base
-- * AIRBASE.Sinai.Bir_Hasanah
-- * AIRBASE.Sinai.Birma_Air_Base
-- * AIRBASE.Sinai.Borj_El_Arab_International_Airport
-- * AIRBASE.Sinai.Cairo_International_Airport
-- * AIRBASE.Sinai.Cairo_West
-- * AIRBASE.Sinai.Difarsuwar_Airfield
-- * AIRBASE.Sinai.El_Arish
-- * AIRBASE.Sinai.El_Gora
-- * AIRBASE.Sinai.El_Minya
-- * AIRBASE.Sinai.Fayed
-- * AIRBASE.Sinai.Gebel_El_Basur_Air_Base
-- * AIRBASE.Sinai.Hatzerim
-- * AIRBASE.Sinai.Hatzor
-- * AIRBASE.Sinai.Hurghada_International_Airport
-- * AIRBASE.Sinai.Inshas_Airbase
-- * AIRBASE.Sinai.Jiyanklis_Air_Base
-- * AIRBASE.Sinai.Kedem
-- * AIRBASE.Sinai.Kibrit_Air_Base
-- * AIRBASE.Sinai.Kom_Awshim
-- * AIRBASE.Sinai.Melez
-- * AIRBASE.Sinai.Nevatim
-- * AIRBASE.Sinai.Ovda
-- * AIRBASE.Sinai.Palmahim
-- * AIRBASE.Sinai.Palmachim
-- * AIRBASE.Sinai.Quwaysina
-- * AIRBASE.Sinai.Ramon_Airbase
-- * AIRBASE.Sinai.Ramon_International_Airport
-- * AIRBASE.Sinai.Sde_Dov
-- * AIRBASE.Sinai.Sharm_El_Sheikh_International_Airport
-- * AIRBASE.Sinai.St_Catherine
-- * AIRBASE.Sinai.Tel_Nof
-- * AIRBASE.Sinai.Wadi_Abu_Rish
-- * AIRBASE.Sinai.Wadi_al_Jandali
--
-- @field Sinai
AIRBASE.Sinai = {
["Abu_Rudeis"] = "Abu Rudeis",
["Abu_Suwayr"] = "Abu Suwayr",
["Al_Bahr_al_Ahmar"] = "Al Bahr al Ahmar",
["Al_Ismailiyah"] = "Al Ismailiyah",
["Al_Khatatbah"] = "Al Khatatbah",
["Al_Mansurah"] = "Al Mansurah",
["Al_Rahmaniyah_Air_Base"] = "Al Rahmaniyah Air Base",
["As_Salihiyah"] = "As Salihiyah",
["AzZaqaziq"] = "AzZaqaziq",
["Baluza"] = "Baluza",
["Ben_Gurion"] = "Ben-Gurion",
["Beni_Suef"] = "Beni Suef",
["Bilbeis_Air_Base"] = "Bilbeis Air Base",
["Bir_Hasanah"] = "Bir Hasanah",
["Birma_Air_Base"] = "Birma Air Base",
["Borj_El_Arab_International_Airport"] = "Borj El Arab International Airport",
["Cairo_International_Airport"] = "Cairo International Airport",
["Cairo_West"] = "Cairo West",
["Difarsuwar_Airfield"] = "Difarsuwar Airfield",
["El_Arish"] = "El Arish",
["El_Gora"] = "El Gora",
["El_Minya"] = "El Minya",
["Fayed"] = "Fayed",
["Gebel_El_Basur_Air_Base"] = "Gebel El Basur Air Base",
["Hatzerim"] = "Hatzerim",
["Hatzor"] = "Hatzor",
["Hurghada_International_Airport"] = "Hurghada International Airport",
["Inshas_Airbase"] = "Inshas Airbase",
["Jiyanklis_Air_Base"] = "Jiyanklis Air Base",
["Kedem"] = "Kedem",
["Kibrit_Air_Base"] = "Kibrit Air Base",
["Kom_Awshim"] = "Kom Awshim",
["Melez"] = "Melez",
["Nevatim"] = "Nevatim",
["Ovda"] = "Ovda",
["Palmahim"] = "Palmahim",
["Palmachim"] = "Palmachim",
["Quwaysina"] = "Quwaysina",
["Ramon_Airbase"] = "Ramon Airbase",
["Ramon_International_Airport"] = "Ramon International Airport",
["Sde_Dov"] = "Sde Dov",
["Sharm_El_Sheikh_International_Airport"] = "Sharm El Sheikh International Airport",
["St_Catherine"] = "St Catherine",
["Tel_Nof"] = "Tel Nof",
["Wadi_Abu_Rish"] = "Wadi Abu Rish",
["Wadi_al_Jandali"] = "Wadi al Jandali",
}
--- Airbases of the Kola map
--
-- * AIRBASE.Kola.Banak
-- * AIRBASE.Kola.Bas_100
-- * AIRBASE.Kola.Bodo
-- * AIRBASE.Kola.Ivalo
-- * AIRBASE.Kola.Jokkmokk
-- * AIRBASE.Kola.Kalixfors
-- * AIRBASE.Kola.Kallax
-- * AIRBASE.Kola.Kemi_Tornio
-- * AIRBASE.Kola.Kirkenes
-- * AIRBASE.Kola.Kiruna
-- * AIRBASE.Kola.Kuusamo
-- * AIRBASE.Kola.Monchegorsk
-- * AIRBASE.Kola.Murmansk_International
-- * AIRBASE.Kola.Olenya
-- * AIRBASE.Kola.Rovaniemi
-- * AIRBASE.Kola.Severomorsk_1
-- * AIRBASE.Kola.Severomorsk_3
--
-- * AIRBASE.Kola.Vidsel
-- * AIRBASE.Kola.Vuojarvi
-- * AIRBASE.Kola.Andoya
-- * AIRBASE.Kola.Alakourtti
-- * AIRBASE.Kola.Kittila
-- * AIRBASE.Kola.Bardufoss
--
-- @field Kola
AIRBASE.Kola = {
["Banak"] = "Banak",
["Bas_100"] = "Bas 100",
["Bodo"] = "Bodo",
["Ivalo"] = "Ivalo",
["Jokkmokk"] = "Jokkmokk",
["Kalixfors"] = "Kalixfors",
["Kallax"] = "Kallax",
["Kemi_Tornio"] = "Kemi Tornio",
["Kirkenes"] = "Kirkenes",
["Kiruna"] = "Kiruna",
["Kuusamo"] = "Kuusamo",
["Monchegorsk"] = "Monchegorsk",
["Murmansk_International"] = "Murmansk International",
["Olenya"] = "Olenya",
["Rovaniemi"] = "Rovaniemi",
["Severomorsk_1"] = "Severomorsk-1",
["Severomorsk_3"] = "Severomorsk-3",
["Vidsel"] = "Vidsel",
["Vuojarvi"] = "Vuojarvi",
["Andoya"] = "Andoya",
["Alakourtti"] = "Alakourtti",
["Kittila"] = "Kittila",
["Bardufoss"] = "Bardufoss",
}
--- Airbases of the Afghanistan map
--
-- * AIRBASE.Afghanistan.Bost
-- * AIRBASE.Afghanistan.Bagram
-- * AIRBASE.Afghanistan.Bamyan
-- * AIRBASE.Afghanistan.Camp_Bastion
-- * AIRBASE.Afghanistan.Camp_Bastion_Heliport
-- * AIRBASE.Afghanistan.Chaghcharan
-- * AIRBASE.Afghanistan.Dwyer
-- * AIRBASE.Afghanistan.Farah
-- * AIRBASE.Afghanistan.Herat
-- * AIRBASE.Afghanistan.Gardez
-- * AIRBASE.Afghanistan.Ghazni_Heliport
-- * AIRBASE.Afghanistan.Jalalabad
-- * AIRBASE.Afghanistan.Kabul
-- * AIRBASE.Afghanistan.Kandahar
-- * AIRBASE.Afghanistan.Kandahar_Heliport
-- * AIRBASE.Afghanistan.Khost
-- * AIRBASE.Afghanistan.Khost_Heliport
-- * AIRBASE.Afghanistan.Maymana_Zahiraddin_Faryabi
-- * AIRBASE.Afghanistan.Nimroz
-- * AIRBASE.Afghanistan.Qala_i_Naw
-- * AIRBASE.Afghanistan.Shindand
-- * AIRBASE.Afghanistan.Shindand_Heliport
-- * AIRBASE.Afghanistan.Tarinkot
-- * AIRBASE.Afghanistan.Urgoon_Heliport
--
-- @field Afghanistan
AIRBASE.Afghanistan = {
["Bagram"] = "Bagram",
["Bamyan"] = "Bamyan",
["Bost"] = "Bost",
["Camp_Bastion"] = "Camp Bastion",
["Camp_Bastion_Heliport"] = "Camp Bastion Heliport",
["Chaghcharan"] = "Chaghcharan",
["Dwyer"] = "Dwyer",
["Farah"] = "Farah",
["Gardez"] = "Gardez",
["Ghazni_Heliport"] = "Ghazni Heliport",
["Herat"] = "Herat",
["Jalalabad"] = "Jalalabad",
["Kabul"] = "Kabul",
["Kandahar"] = "Kandahar",
["Kandahar_Heliport"] = "Kandahar Heliport",
["Khost"] = "Khost",
["Khost_Heliport"] = "Khost Heliport",
["Maymana_Zahiraddin_Faryabi"] = "Maymana Zahiraddin Faryabi",
["Nimroz"] = "Nimroz",
["Qala_i_Naw"] = "Qala i Naw",
["Sharana"] = "Sharana",
["Shindand"] = "Shindand",
["Shindand_Heliport"] = "Shindand Heliport",
["Tarinkot"] = "Tarinkot",
["Urgoon_Heliport"] = "Urgoon Heliport",
}
--- Airbases of the Iraq map
--
-- * AIRBASE.Iraq.Baghdad_International_Airport
-- * AIRBASE.Iraq.Sulaimaniyah_International_Airport
-- * AIRBASE.Iraq.Al_Sahra_Airport
-- * AIRBASE.Iraq.Erbil_International_Airpor
-- * AIRBASE.Iraq.Al_Taji_Airport
-- * AIRBASE.Iraq.Al_Asad_Airbase
-- * AIRBASE.Iraq.Al_Salam_Airbase
-- * AIRBASE.Iraq.Balad_Airbase
-- * AIRBASE.Iraq.Kirkuk_International_Airport
-- * AIRBASE.Iraq.Bashur_Airport
-- * AIRBASE.Iraq.Al_Taquddum_Airport
-- * AIRBASE.Iraq.Qayyarah_Airfield_West
-- * AIRBASE.Iraq.K1_Base
--
-- @field Iraq
AIRBASE.Iraq = {
["Baghdad_International_Airport"] = "Baghdad International Airport",
["Sulaimaniyah_International_Airport"] = "Sulaimaniyah International Airport",
["Al_Sahra_Airport"] = "Al-Sahra Airport",
["Erbil_International_Airport"] = "Erbil International Airport",
["Al_Taji_Airport"] = "Al-Taji Airport",
["Al_Asad_Airbase"] = "Al-Asad Airbase",
["Al_Salam_Airbase"] = "Al-Salam Airbase",
["Balad_Airbase"] = "Balad Airbase",
["Kirkuk_International_Airport"] = "Kirkuk International Airport",
["Bashur_Airport"] = "Bashur Airport",
["Al_Taquddum_Airport"] = "Al-Taquddum Airport",
["Qayyarah_Airfield_West"] = "Qayyarah Airfield West",
["K1_Base"] = "K1 Base",
}
--- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy".
@@ -791,11 +947,12 @@ AIRBASE.Kola = {
-- @field #number HelicopterOnly 40: Special spots for Helicopers.
-- @field #number Shelter 68: Hardened Air Shelter. Currently only on Caucaus map.
-- @field #number OpenMed 72: Open/Shelter air airplane only.
-- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't
-- @field #number OpenBig 104: Open air spawn points. Generally larger but does not guarantee large aircraft are capable of spawning there.
-- @field #number OpenMedOrBig 176: Combines OpenMed and OpenBig spots.
-- @field #number HelicopterUsable 216: Combines HelicopterOnly, OpenMed and OpenBig.
-- @field #number FighterAircraft 244: Combines Shelter. OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
-- @field #number SmallSizeFigher 100: Tight spots for smaller type fixed wing aircraft, like the F-16. Example of these spots: 04, 05, 06 on Muwaffaq_Salti. A Viper sized plane can spawn here, but an A-10 or Strike Eagle can't
-- @field #number FighterAircraft 244: Combines Shelter, OpenMed and OpenBig spots. So effectively all spots usable by fixed wing aircraft.
-- @field #number FighterAircraftSmall 344: Combines Shelter, SmallsizeFighter, OpenMed and OpenBig spots. So effectively all spots usable by small fixed wing aircraft.
AIRBASE.TerminalType = {
Runway=16,
HelicopterOnly=40,
@@ -806,6 +963,7 @@ AIRBASE.TerminalType = {
OpenMedOrBig=176,
HelicopterUsable=216,
FighterAircraft=244,
FighterAircraftSmall=344,
}
--- Status of a parking spot.
@@ -894,40 +1052,55 @@ function AIRBASE:Register(AirbaseName)
--end
-- Set category.
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
elseif self.category==Airbase.Category.HELIPAD then
if self.category==Airbase.Category.AIRDROME then
self.isAirdrome=true
elseif self.category==Airbase.Category.HELIPAD or self.descriptors.typeName=="FARP_SINGLE_01" then
self.isHelipad=true
self.category=Airbase.Category.HELIPAD
elseif self.category==Airbase.Category.SHIP then
self.isShip=true
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
self.isHelipad=true
elseif self.category==Airbase.Category.SHIP then
self.isShip=true
-- DCS bug: Oil rigs and gas platforms have category=2 (ship). Also they cannot be retrieved by coalition.getStaticObjects()
if self.descriptors.typeName=="Oil rig" or self.descriptors.typeName=="Ga" then
self.isHelipad=true
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
else
self:E("ERROR: Unknown airbase category!")
self.isShip=false
self.category=Airbase.Category.HELIPAD
_DATABASE:AddStatic(AirbaseName)
end
else
self:E("ERROR: Unknown airbase category!")
end
-- Init Runways.
self:_InitRunways()
-- Number of runways
local Nrunways=#self.runways
-- Set the active runways based on wind direction.
if self.isAirdrome then
if Nrunways>0 then
self:SetActiveRunway()
end
-- Init parking spots.
self:_InitParkingSpots()
-- Some heliports identify as airdromes in the airbase category. This is buggy in the descriptors category but also in the getCategory() and getCategoryEx() functions.
-- Well, thinking about it, this is actually not that "buggy" since these are really helicopter airdromes, which do not have an automatic parking spot routine.
-- I am still changing the category but marking it as airdrome and heliport at the same time via isAirdrome=true and isHelipad=true (important in SPAWN.SpawnAtAirbase).
-- The main reason for changing the category is to be able to filter airdromes from helipads, e.g. in SET_AIRBASE.
if self.category==Airbase.Category.AIRDROME and (Nrunways==0 or self.NparkingTotal==self.NparkingTerminal[AIRBASE.TerminalType.HelicopterOnly]) then
--self:E(string.format("WARNING: %s identifies as airdrome (category=0) but has no runways or just helo parking ==> will change to helipad (category=1)", self.AirbaseName))
self.category=Airbase.Category.HELIPAD
self.isAirdrome=true
self.isHelipad=true
end
-- Get 2D position vector.
local vec2=self:GetVec2()
-- Init coordinate.
self:GetCoordinate()
-- Storage.
self.storage=_DATABASE:AddStorage(AirbaseName)
@@ -950,6 +1123,46 @@ function AIRBASE:Register(AirbaseName)
return self
end
--- Get the category of this airbase. This is only a debug function because DCS 2.9 incorrectly returns heliports as airdromes.
-- @param #AIRBASE self
function AIRBASE:_GetCategory()
local name=self.AirbaseName
local static=StaticObject.getByName(name)
local airbase=Airbase.getByName(name)
local unit=Unit.getByName(name)
local text=string.format("\n=====================================================")
text=text..string.format("\nAirbase %s:", name)
if static then
local oc, uc=static:getCategory()
local ex=static:getCategoryEx()
text=text..string.format("\nSTATIC: oc=%d, uc=%d, ex=%d", oc, uc, ex)
--text=text..UTILS.PrintTableToLog(static:getDesc(), nil, true)
text=text..string.format("\n--------------------------------------------------")
end
if unit then
local oc, uc=unit:getCategory()
local ex=unit:getCategoryEx()
text=text..string.format("\nUNIT: oc=%d, uc=%d, ex=%d", oc, uc, ex)
--text=text..UTILS.PrintTableToLog(unit:getDesc(), nil, true)
text=text..string.format("\n--------------------------------------------------")
end
if airbase then
local oc, uc=airbase:getCategory()
local ex=airbase:getCategoryEx()
text=text..string.format("\nAIRBASE: oc=%d, uc=%d, ex=%d", oc, uc, ex)
text=text..string.format("\n--------------------------------------------------")
text=text..UTILS.PrintTableToLog(airbase:getDesc(), nil, true)
end
text=text..string.format("\n=====================================================")
env.info(text)
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Reference methods
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
@@ -1629,7 +1842,7 @@ function AIRBASE:_InitParkingSpots()
self.NparkingTotal=self.NparkingTotal+1
for _,terminalType in pairs(AIRBASE.TerminalType) do
if self._CheckTerminalType(terminalType, park.TerminalType) then
if self._CheckTerminalType(park.TerminalType, terminalType) then
self.NparkingTerminal[terminalType]=self.NparkingTerminal[terminalType]+1
end
end
@@ -1637,6 +1850,9 @@ function AIRBASE:_InitParkingSpots()
self.parkingByID[park.TerminalID]=park
table.insert(self.parking, park)
end
-- Runways are not included in total number of parking spots
self.NparkingTotal=self.NparkingTotal-self.NparkingTerminal[AIRBASE.TerminalType.Runway]
return self
end
@@ -2060,9 +2276,13 @@ function AIRBASE._CheckTerminalType(Term_Type, termtype)
match=true
end
elseif termtype==AIRBASE.TerminalType.FighterAircraft then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter then
match=true
end
elseif termtype==AIRBASE.TerminalType.FighterAircraftSmall then
if Term_Type==AIRBASE.TerminalType.OpenMed or Term_Type==AIRBASE.TerminalType.OpenBig or Term_Type==AIRBASE.TerminalType.Shelter or Term_Type==AIRBASE.TerminalType.SmallSizeFighter then
match=true
end
end
return match
@@ -2120,11 +2340,6 @@ function AIRBASE:_InitRunways(IncludeInverse)
-- Runway table.
local Runways={}
if self:GetAirbaseCategory()~=Airbase.Category.AIRDROME then
self.runways={}
return {}
end
--- Function to create a runway data table.
local function _createRunway(name, course, width, length, center)
@@ -2210,7 +2425,7 @@ function AIRBASE:_InitRunways(IncludeInverse)
-- Debug info.
self:T2(runways)
if runways then
if runways and #runways>0 then
-- Loop over runways.
for _,rwy in pairs(runways) do
@@ -2243,6 +2458,12 @@ function AIRBASE:_InitRunways(IncludeInverse)
end
end
else
-- No runways
self.runways={}
return {}
end

View File

@@ -201,6 +201,13 @@ function CLIENT:AddPlayer(PlayerName)
return self
end
--- Get number of associated players.
-- @param #CLIENT self
-- @return #number Count
function CLIENT:CountPlayers()
return #self.Players or 0
end
--- Get player name(s).
-- @param #CLIENT self
-- @return #table List of player names or an empty table `{}`.
@@ -306,7 +313,7 @@ function CLIENT:IsMultiSeated()
return false
end
--- Checks for a client alive event and calls a function on a continuous basis.
--- Checks for a client alive event and calls a function on a continuous basis. Does **NOT** work for dynamic spawn client slots!
-- @param #CLIENT self
-- @param #function CallBackFunction Create a function that will be called when a player joins the slot.
-- @param ... (Optional) Arguments for callback function as comma separated list.
@@ -325,7 +332,7 @@ end
-- @param #CLIENT self
function CLIENT:_AliveCheckScheduler( SchedulerName )
self:F3( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } )
self:T2( { SchedulerName, self.ClientName, self.ClientAlive2, self.ClientBriefingShown, self.ClientCallBack } )
if self:IsAlive() then
@@ -608,4 +615,3 @@ function CLIENT:GetPlayerInfo(Attribute)
return nil
end
end

View File

@@ -58,7 +58,7 @@
-- * @{#CONTROLLABLE.TaskFollow}: (AIR) Following another airborne controllable.
-- * @{#CONTROLLABLE.TaskHold}: (GROUND) Hold ground controllable from moving.
-- * @{#CONTROLLABLE.TaskHoldPosition}: (AIR) Hold position at the current position of the first unit of the controllable.
-- * @{#CONTROLLABLE.TaskLand}: (AIR HELICOPTER) Landing at the ground. For helicopters only.
-- * @{#CONTROLLABLE.TaskLandAtVec2}: (AIR HELICOPTER) Landing at the ground. For helicopters only.
-- * @{#CONTROLLABLE.TaskLandAtZone}: (AIR) Land the controllable at a @{Core.Zone#ZONE_RADIUS).
-- * @{#CONTROLLABLE.TaskOrbitCircle}: (AIR) Orbit at the current position of the first unit of the controllable at a specified altitude.
-- * @{#CONTROLLABLE.TaskOrbitCircleAtVec2}: (AIR) Orbit at a specified position at a specified altitude during a specified duration with a specified speed.
@@ -174,7 +174,10 @@
-- * @{#CONTROLLABLE.OptionKeepWeaponsOnThreat}
--
-- ## 5.5) Air-2-Air missile attack range:
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets .
-- * @{#CONTROLLABLE.OptionAAAttackRange}(): Defines the usage of A2A missiles against possible targets.
--
-- # 6) [GROUND] IR Maker Beacons for GROUPs and UNITs
-- * @{#CONTROLLABLE:NewIRMarker}(): Create a blinking IR Marker on a GROUP or UNIT.
--
-- @field #CONTROLLABLE
CONTROLLABLE = {
@@ -899,7 +902,11 @@ function CONTROLLABLE:CommandEPLRS( SwitchOnOff, Delay )
groupId = self:GetID(),
},
}
--if self:IsGround() then
--CommandEPLRS.params.groupId = self:GetID()
--end
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandEPLRS, { self, SwitchOnOff }, Delay )
else
@@ -934,7 +941,7 @@ function CONTROLLABLE:CommandSetUnlimitedFuel(OnOff, Delay)
end
--- Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequency)
--- Set radio frequency. See [DCS command SetFrequency](https://wiki.hoggitworld.com/view/DCS_command_setFrequency)
-- @param #CONTROLLABLE self
-- @param #number Frequency Radio frequency in MHz.
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
@@ -953,7 +960,7 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay )
}
if Delay and Delay > 0 then
SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power } )
SCHEDULER:New( nil, self.CommandSetFrequency, { self, Frequency, Modulation, Power },Delay )
else
self:SetCommand( CommandSetFrequency )
end
@@ -961,12 +968,12 @@ function CONTROLLABLE:CommandSetFrequency( Frequency, Modulation, Power, Delay )
return self
end
--- [AIR] Set radio frequency. See [DCS command EPLRS](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit)
--- [AIR] Set radio frequency. See [DCS command SetFrequencyForUnit](https://wiki.hoggitworld.com/view/DCS_command_setFrequencyForUnit)
-- @param #CONTROLLABLE self
-- @param #number Frequency Radio frequency in MHz.
-- @param #number Modulation Radio modulation. Default `radio.modulation.AM`.
-- @param #number Power (Optional) Power of the Radio in Watts. Defaults to 10.
-- @param #UnitID UnitID (Optional, if your object is a UNIT) The UNIT ID this is for.
-- @param #number UnitID (Optional, if your object is a UNIT) The UNIT ID this is for.
-- @param #number Delay (Optional) Delay in seconds before the frequency is set. Default is immediately.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,UnitID,Delay)
@@ -980,13 +987,72 @@ function CONTROLLABLE:CommandSetFrequencyForUnit(Frequency,Modulation,Power,Unit
},
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID})
SCHEDULER:New(nil,self.CommandSetFrequencyForUnit,{self,Frequency,Modulation,Power,UnitID},Delay)
else
self:SetCommand(CommandSetFrequencyForUnit)
end
return self
end
--- [AIR] Set smoke on or off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off)
-- @param #CONTROLLABLE self
-- @param #boolean OnOff Set to true for on and false for off. Defaults to true.
-- @param #number Delay (Optional) Delay the command by this many seconds.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSmokeOnOff(OnOff, Delay)
local switch = (OnOff == nil) and true or OnOff
local command = {
id = 'SMOKE_ON_OFF',
params = {
value = switch
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSmokeOnOff,{self,switch},Delay)
else
self:SetCommand(command)
end
return self
end
--- [AIR] Set smoke on. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off)
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay the command by this many seconds.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSmokeON(Delay)
local command = {
id = 'SMOKE_ON_OFF',
params = {
value = true
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSmokeON,{self},Delay)
else
self:SetCommand(command)
end
return self
end
--- [AIR] Set smoke off. See [DCS command smoke on off](https://wiki.hoggitworld.com/view/DCS_command_smoke_on_off)
-- @param #CONTROLLABLE self
-- @param #number Delay (Optional) Delay the command by this many seconds.
-- @return #CONTROLLABLE self
function CONTROLLABLE:CommandSmokeOFF(Delay)
local command = {
id = 'SMOKE_ON_OFF',
params = {
value = false
}
}
if Delay and Delay>0 then
SCHEDULER:New(nil,self.CommandSmokeOFF,{self},Delay)
else
self:SetCommand(command)
end
return self
end
--- Set EPLRS data link on/off.
-- @param #CONTROLLABLE self
-- @param #boolean SwitchOnOff If true (or nil) switch EPLRS on. If false switch off.
@@ -1006,13 +1072,17 @@ function CONTROLLABLE:TaskEPLRS( SwitchOnOff, idx )
groupId = self:GetID(),
},
}
--if self:IsGround() then
--CommandEPLRS.params.groupId = self:GetID()
--end
return self:TaskWrappedAction( CommandEPLRS, idx or 1 )
end
-- TASKS FOR AIR CONTROLLABLES
--- (AIR) Attack a Controllable.
--- (AIR + GROUND) Attack a Controllable.
-- @param #CONTROLLABLE self
-- @param Wrapper.Group#GROUP AttackGroup The Group to be attacked.
-- @param #number WeaponType (optional) Bitmask of weapon types those allowed to use. If parameter is not defined that means no limits on weapon usage.
@@ -1060,7 +1130,7 @@ function CONTROLLABLE:TaskAttackGroup( AttackGroup, WeaponType, WeaponExpend, At
return DCSTask
end
--- (AIR) Attack the Unit.
--- (AIR + GROUND) Attack the Unit.
-- @param #CONTROLLABLE self
-- @param Wrapper.Unit#UNIT AttackUnit The UNIT to be attacked
-- @param #boolean GroupAttack (Optional) If true, all units in the group will attack the Unit when found. Default false.
@@ -1148,10 +1218,10 @@ function CONTROLLABLE:TaskStrafing( Vec2, AttackQty, Length, WeaponType, WeaponE
id = 'Strafing',
params = {
point = Vec2, -- req
weaponType = WeaponType or 1073741822,
weaponType = WeaponType or 805337088, -- Default 805337088 corresponds to guns/cannons (805306368) + any rocket (30720). You can set other types but then the AI uses even bombs for a strafing run!
expend = WeaponExpend or "Auto",
attackQty = AttackQty or 1, -- req
attackQtyLimit = AttackQty >1 and true or false,
attackQtyLimit = AttackQty~=nil and true or false,
direction = Direction and math.rad(Direction) or 0,
directionEnabled = Direction and true or false,
groupAttack = GroupAttack or false,
@@ -1516,8 +1586,10 @@ end
-- @param #CONTROLLABLE self
-- @param DCS#Vec2 Vec2 The point where to land.
-- @param #number Duration The duration in seconds to stay on the ground.
-- @param #boolean CombatLanding (optional) If true, set the Combat Landing option.
-- @param #number DirectionAfterLand (optional) Heading after landing in degrees.
-- @return #CONTROLLABLE self
function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration )
function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration , CombatLanding, DirectionAfterLand)
local DCSTask = {
id = 'Land',
@@ -1525,9 +1597,15 @@ function CONTROLLABLE:TaskLandAtVec2( Vec2, Duration )
point = Vec2,
durationFlag = Duration and true or false,
duration = Duration,
combatLandingFlag = CombatLanding == true and true or false,
},
}
if DirectionAfterLand ~= nil and type(DirectionAfterLand) == "number" then
DCSTask.params.directionEnabled = true
DCSTask.params.direction = math.rad(DirectionAfterLand)
end
return DCSTask
end
@@ -1535,13 +1613,16 @@ end
-- @param #CONTROLLABLE self
-- @param Core.Zone#ZONE Zone The zone where to land.
-- @param #number Duration The duration in seconds to stay on the ground.
-- @param #boolean RandomPoint (optional) If true,land at a random point inside of the zone.
-- @param #boolean CombatLanding (optional) If true, set the Combat Landing option.
-- @param #number DirectionAfterLand (optional) Heading after landing in degrees.
-- @return DCS#Task The DCS task structure.
function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint )
function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint, CombatLanding, DirectionAfterLand )
-- Get landing point
local Point = RandomPoint and Zone:GetRandomVec2() or Zone:GetVec2()
local DCSTask = CONTROLLABLE.TaskLandAtVec2( self, Point, Duration )
local DCSTask = CONTROLLABLE.TaskLandAtVec2( self, Point, Duration, CombatLanding, DirectionAfterLand)
return DCSTask
end
@@ -1755,8 +1836,6 @@ function CONTROLLABLE:TaskFAC_AttackGroup( AttackGroup, WeaponType, Designation,
return DCSTask
end
-- EN-ACT_ROUTE TASKS FOR AIRBORNE CONTROLLABLES
--- (AIR) Engaging targets of defined types.
-- @param #CONTROLLABLE self
-- @param DCS#Distance Distance Maximal distance from the target to a route leg. If the target is on a greater distance it will be ignored.
@@ -2983,7 +3062,7 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
if DCSControllable then
local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTICAL or nil
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTIC or nil
local DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil
local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST or nil
local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil
@@ -3017,26 +3096,27 @@ function CONTROLLABLE:GetDetectedTargets( DetectVisual, DetectOptical, DetectRad
return nil
end
--- Check if a target is detected.
--- Check if a DCS object (unit or static) is detected by the controllable.
-- Note that after a target is detected it remains "detected" for a certain amount of time, even if the controllable cannot "see" the target any more with it's sensors.
-- The optional parametes specify the detection methods that can be applied.
--
-- If **no** detection method is given, the detection will use **all** the available methods by default.
-- If **at least one** detection method is specified, only the methods set to *true* will be used.
-- @param #CONTROLLABLE self
-- @param DCS#Object DCSObject The DCS object that is checked.
-- @param #CONTROLLABLE self
-- @param #boolean DetectVisual (Optional) If *false*, do not include visually detected targets.
-- @param #boolean DetectOptical (Optional) If *false*, do not include optically detected targets.
-- @param #boolean DetectRadar (Optional) If *false*, do not include targets detected by radar.
-- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST.
-- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR.
-- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link.
-- @return #boolean True if target is detected.
-- @return #boolean True if target is visible by line of sight.
-- @return #number Mission time when target was detected.
-- @return #boolean True if target type is known.
-- @return #boolean True if distance to target is known.
-- @return DCS#Vec3 Last known position vector of the target.
-- @return DCS#Vec3 Last known velocity vector of the target.
-- @return #boolean `true` if target is detected.
-- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`).
-- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`).
-- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`).
-- @return #number Mission time in seconds when target was last detected. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
-- @return DCS#Vec3 Last known position vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
-- @return DCS#Vec3 Last known velocity vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
self:F2( self.ControllableName )
@@ -3045,7 +3125,7 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical,
if DCSControllable then
local DetectionVisual = (DetectVisual and DetectVisual == true) and Controller.Detection.VISUAL or nil
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTICAL or nil
local DetectionOptical = (DetectOptical and DetectOptical == true) and Controller.Detection.OPTIC or nil
local DetectionRadar = (DetectRadar and DetectRadar == true) and Controller.Detection.RADAR or nil
local DetectionIRST = (DetectIRST and DetectIRST == true) and Controller.Detection.IRST or nil
local DetectionRWR = (DetectRWR and DetectRWR == true) and Controller.Detection.RWR or nil
@@ -3053,10 +3133,10 @@ function CONTROLLABLE:IsTargetDetected( DCSObject, DetectVisual, DetectOptical,
local Controller = self:_GetController()
local TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity
local TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity
= Controller:isTargetDetected( DCSObject, DetectionVisual, DetectionOptical, DetectionRadar, DetectionIRST, DetectionRWR, DetectionDLINK )
return TargetIsDetected, TargetIsVisible, TargetLastTime, TargetKnowType, TargetKnowDistance, TargetLastPos, TargetLastVelocity
return TargetIsDetected, TargetIsVisible, TargetKnowType, TargetKnowDistance, TargetLastTime, TargetLastPos, TargetLastVelocity
end
return nil
@@ -3064,6 +3144,7 @@ end
--- Check if a certain UNIT is detected by the controllable.
-- The optional parametes specify the detection methods that can be applied.
--
-- If **no** detection method is given, the detection will use **all** the available methods by default.
-- If **at least one** detection method is specified, only the methods set to *true* will be used.
-- @param #CONTROLLABLE self
@@ -3074,13 +3155,13 @@ end
-- @param #boolean DetectIRST (Optional) If *false*, do not include targets detected by IRST.
-- @param #boolean DetectRWR (Optional) If *false*, do not include targets detected by RWR.
-- @param #boolean DetectDLINK (Optional) If *false*, do not include targets detected by data link.
-- @return #boolean True if target is detected.
-- @return #boolean True if target is visible by line of sight.
-- @return #number Mission time when target was detected.
-- @return #boolean True if target type is known.
-- @return #boolean True if distance to target is known.
-- @return DCS#Vec3 Last known position vector of the target.
-- @return DCS#Vec3 Last known velocity vector of the target.
-- @return #boolean `true` if target is detected.
-- @return #boolean `true` if target is *currently* visible by line of sight. Target must be detected (first parameter returns `true`).
-- @return #boolean `true` if target type is known. Target must be detected (first parameter returns `true`).
-- @return #boolean `true` if distance to target is known. Target must be detected (first parameter returns `true`).
-- @return #number Mission time in seconds when target was last detected. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
-- @return DCS#Vec3 Last known position vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
-- @return DCS#Vec3 Last known velocity vector of the target. Only present if the target is currently not visible (second parameter returns `false`) otherwise `nil` is returned.
function CONTROLLABLE:IsUnitDetected( Unit, DetectVisual, DetectOptical, DetectRadar, DetectIRST, DetectRWR, DetectDLINK )
self:F2( self.ControllableName )
@@ -3800,6 +3881,48 @@ function CONTROLLABLE:OptionProhibitAfterburner( Prohibit )
return self
end
--- [Ground] Allows AI radar units to take defensive actions to avoid anti radiation missiles. Units are allowed to shut radar off and displace.
-- @param #CONTROLLABLE self
-- @param #number Seconds Can be - nil, 0 or false = switch off this option, any positive number = number of seconds the escape sequency runs.
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionEvasionOfARM(Seconds)
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsGround() then
if Seconds == nil then Seconds = false end
Controller:setOption( AI.Option.Ground.id.EVASION_OF_ARM, Seconds)
end
end
return self
end
--- [Ground] Option that defines the vehicle spacing when in an on road and off road formation.
-- @param #CONTROLLABLE self
-- @param #number meters Can be zero to 100 meters. Defaults to 50 meters.
-- @return #CONTROLLABLE self
function CONTROLLABLE:OptionFormationInterval(meters)
self:F2( { self.ControllableName } )
local DCSControllable = self:GetDCSObject()
if DCSControllable then
local Controller = self:_GetController()
if self:IsGround() then
if meters == nil or meters > 100 or meters < 0 then meters = 50 end
Controller:setOption( 30, meters)
end
end
return self
end
--- [Air] Defines the usage of Electronic Counter Measures by airborne forces.
-- @param #CONTROLLABLE self
-- @param #number ECMvalue Can be - 0=Never on, 1=if locked by radar, 2=if detected by radar, 3=always on, defaults to 1
@@ -4197,6 +4320,9 @@ function CONTROLLABLE:RelocateGroundRandomInRadius( speed, radius, onroad, short
self:F2( { self.ControllableName } )
local _coord = self:GetCoordinate()
if not _coord then
return self
end
local _radius = radius or 500
local _speed = speed or 20
local _tocoord = _coord:GetRandomCoordinateInRadius( _radius, 100 )
@@ -4246,7 +4372,7 @@ function CONTROLLABLE:OptionDisperseOnAttack( Seconds )
end
--- Returns if the unit is a submarine.
-- @param #POSITIONABLE self
-- @param #CONTROLLABLE self
-- @return #boolean Submarines attributes result.
function CONTROLLABLE:IsSubmarine()
self:F2()
@@ -5566,3 +5692,166 @@ function CONTROLLABLE:PatrolRaceTrack(Point1, Point2, Altitude, Speed, Formation
return self
end
--- IR Marker courtesy Florian Brinker (fbrinker)
--- [GROUND] Create and enable a new IR Marker for the given controllable UNIT or GROUP.
-- @param #CONTROLLABLE self
-- @param #boolean EnableImmediately (Optionally) If true start up the IR Marker immediately. Else you need to call `myobject:EnableIRMarker()` later on.
-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Use in conjunction with EnableImmediately. Defaults to 60 seconds.
-- @return #CONTROLLABLE self
function CONTROLLABLE:NewIRMarker(EnableImmediately, Runtime)
self:T2("NewIRMarker")
if self:IsInstanceOf("GROUP") then
if self.IRMarkerGroup == true then return end
self.IRMarkerGroup = true
self.IRMarkerUnit = false
elseif self:IsInstanceOf("UNIT") then
if self.IRMarkerUnit == true then return end
self.IRMarkerGroup = false
self.IRMarkerUnit = true
end
self.Runtime = Runtime or 60
if EnableImmediately and EnableImmediately == true then
self:EnableIRMarker(Runtime)
end
return self
end
--- [GROUND] Enable the IR marker.
-- @param #CONTROLLABLE self
-- @param #number Runtime (Optionally) Run this IR Marker for the given number of seconds, then stop. Else run until you call `myobject:DisableIRMarker()`.
-- @return #CONTROLLABLE self
function CONTROLLABLE:EnableIRMarker(Runtime)
self:T2("EnableIRMarker")
if self.IRMarkerGroup == nil then
self:NewIRMarker(true,Runtime)
return
end
if self:IsInstanceOf("GROUP") then
self:EnableIRMarkerForGroup(Runtime)
return
end
if self.timer and self.timer:IsRunning() then return self end
local Runtime = Runtime or self.Runtime
self.timer = TIMER:New(CONTROLLABLE._MarkerBlink, self)
self.timer:Start(nil, 1 - math.random(1, 5) / 10 / 2, Runtime) -- start randomized
self.IRMarkerUnit = true
return self
end
--- [GROUND] Disable the IR marker.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:DisableIRMarker()
self:T2("DisableIRMarker")
if self:IsInstanceOf("GROUP") then
self:DisableIRMarkerForGroup()
return
end
if self.spot then
self.spot = nil
end
if self.timer and self.timer:IsRunning() then
self.timer:Stop()
self.timer = nil
end
if self:IsInstanceOf("GROUP") then
self.IRMarkerGroup = nil
elseif self:IsInstanceOf("UNIT") then
self.IRMarkerUnit = nil
end
return self
end
--- [GROUND] Enable the IR markers for a whole group.
-- @param #CONTROLLABLE self
-- @param #number Runtime Runtime of the marker in seconds
-- @return #CONTROLLABLE self
function CONTROLLABLE:EnableIRMarkerForGroup(Runtime)
self:T2("EnableIRMarkerForGroup")
if self:IsInstanceOf("GROUP")
then
local units = self:GetUnits() or {}
for _,_unit in pairs(units) do
_unit:EnableIRMarker(Runtime)
end
self.IRMarkerGroup = true
end
return self
end
--- [GROUND] Disable the IR markers for a whole group.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:DisableIRMarkerForGroup()
self:T2("DisableIRMarkerForGroup")
if self:IsInstanceOf("GROUP") then
local units = self:GetUnits() or {}
for _,_unit in pairs(units) do
_unit:DisableIRMarker()
end
self.IRMarkerGroup = nil
end
return self
end
--- [GROUND] Check if an IR Spot exists.
-- @param #CONTROLLABLE self
-- @return #boolean outcome
function CONTROLLABLE:HasIRMarker()
self:T2("HasIRMarker")
if self:IsInstanceOf("GROUP") then
local units = self:GetUnits() or {}
for _,_unit in pairs(units) do
if _unit.timer and _unit.timer:IsRunning() then return true end
end
elseif self.timer and self.timer:IsRunning() then return true end
return false
end
--- [Internal] This method is called by the scheduler to blink the IR marker.
function CONTROLLABLE._StopSpot(spot)
if spot then
spot:destroy()
end
end
--- [Internal] This method is called by the scheduler after enabling the IR marker.
-- @param #CONTROLLABLE self
-- @return #CONTROLLABLE self
function CONTROLLABLE:_MarkerBlink()
self:T2("_MarkerBlink")
if self:IsAlive() ~= true then
self:DisableIRMarker()
return
end
self.timer.dT = 1 - (math.random(1, 2) / 10 / 2) -- randomize the blinking by a small amount
local _, _, unitBBHeight, _ = self:GetObjectSize()
local unitPos = self:GetPositionVec3()
if self.timer:IsRunning() then
self:T2("Create Spot")
local spot = Spot.createInfraRed(
self.DCSUnit,
{ x = 0, y = (unitBBHeight + 1), z = 0 },
{ x = unitPos.x, y = (unitPos.y + unitBBHeight), z = unitPos.z }
)
self.spot = spot
local offTimer = nil
local offTimer = TIMER:New(CONTROLLABLE._StopSpot, spot)
offTimer:Start(0.5)
end
return self
end

View File

@@ -0,0 +1,535 @@
--- **Wrapper** - Dynamic Cargo create from the F8 menu.
--
-- ## Main Features:
--
-- * Convenient access to Ground Crew created cargo items.
--
-- ===
--
-- ## Example Missions:
--
-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_Demos/tree/master/).
--
-- ===
--
-- ### Author: **Applevangelist**; additional checks **Chesster**
--
-- ===
-- @module Wrapper.DynamicCargo
-- @image Wrapper_Storage.png
--- DYNAMICCARGO class.
-- @type DYNAMICCARGO
-- @field #string ClassName Name of the class.
-- @field #number verbose Verbosity level.
-- @field #string lid Class id string for output to DCS log file.
-- @field Wrapper.Storage#STORAGE warehouse The STORAGE object.
-- @field #string version.
-- @field #string CargoState.
-- @field #table DCS#Vec3 LastPosition.
-- @field #number Interval Check Interval. 20 secs default.
-- @field #boolean testing
-- @field Core.Timer#TIMER timer Timmer to run intervals
-- @field #string Owner The playername who has created, loaded or unloaded this cargo. Depends on state.
-- @extends Wrapper.Positionable#POSITIONABLE
--- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx
--
-- ===
--
-- # The DYNAMICCARGO Concept
--
-- The DYNAMICCARGO class offers an easy-to-use wrapper interface to all DCS API functions of DCS dynamically spawned cargo crates.
-- We named the class DYNAMICCARGO, because the name WAREHOUSE is already taken by another MOOSE class..
--
-- # Constructor
--
-- @field #DYNAMICCARGO
DYNAMICCARGO = {
ClassName = "DYNAMICCARGO",
verbose = 0,
testing = false,
Interval = 10,
}
--- Liquid types.
-- @type DYNAMICCARGO.Liquid
-- @field #number JETFUEL Jet fuel (0).
-- @field #number GASOLINE Aviation gasoline (1).
-- @field #number MW50 MW50 (2).
-- @field #number DIESEL Diesel (3).
DYNAMICCARGO.Liquid = {
JETFUEL = 0,
GASOLINE = 1,
MW50 = 2,
DIESEL = 3,
}
--- Liquid Names for the static cargo resource table.
-- @type DYNAMICCARGO.LiquidName
-- @field #number JETFUEL "jet_fuel".
-- @field #number GASOLINE "gasoline".
-- @field #number MW50 "methanol_mixture".
-- @field #number DIESEL "diesel".
DYNAMICCARGO.LiquidName = {
GASOLINE = "gasoline",
DIESEL = "diesel",
MW50 = "methanol_mixture",
JETFUEL = "jet_fuel",
}
--- Storage types.
-- @type DYNAMICCARGO.Type
-- @field #number WEAPONS weapons.
-- @field #number LIQUIDS liquids. Also see #list<#DYNAMICCARGO.Liquid> for types of liquids.
-- @field #number AIRCRAFT aircraft.
DYNAMICCARGO.Type = {
WEAPONS = "weapons",
LIQUIDS = "liquids",
AIRCRAFT = "aircrafts",
}
--- State types
-- @type DYNAMICCARGO.State
-- @field #string NEW
-- @field #string LOADED
-- @field #string UNLOADED
-- @field #string REMOVED
DYNAMICCARGO.State = {
NEW = "NEW",
LOADED = "LOADED",
UNLOADED = "UNLOADED",
REMOVED = "REMOVED",
}
--- Helo types possible.
-- @type DYNAMICCARGO.AircraftTypes
DYNAMICCARGO.AircraftTypes = {
["CH-47Fbl1"] = "CH-47Fbl1",
}
--- Helo types possible.
-- @type DYNAMICCARGO.AircraftDimensions
DYNAMICCARGO.AircraftDimensions = {
-- CH-47 model start coordinate is quite exactly in the middle of the model, so half values here
["CH-47Fbl1"] = {
["width"] = 4,
["height"] = 6,
["length"] = 11,
["ropelength"] = 30,
},
}
--- DYNAMICCARGO class version.
-- @field #string version
DYNAMICCARGO.version="0.0.7"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new DYNAMICCARGO object from the DCS static cargo object.
-- @param #DYNAMICCARGO self
-- @param #string CargoName Name of the Cargo.
-- @return #DYNAMICCARGO self
function DYNAMICCARGO:Register(CargoName)
-- Inherit everything from a BASE class.
local self=BASE:Inherit(self, POSITIONABLE:New(CargoName)) -- #DYNAMICCARGO
self.StaticName = CargoName
self.LastPosition = self:GetCoordinate()
self.CargoState = DYNAMICCARGO.State.NEW
self.Interval = DYNAMICCARGO.Interval or 10
local DCSObject = self:GetDCSObject()
if DCSObject then
local warehouse = STORAGE:NewFromDynamicCargo(CargoName)
self.warehouse = warehouse
end
self.lid = string.format("DYNAMICCARGO %s", CargoName)
self.Owner = string.match(CargoName,"^(.+)|%d%d:%d%d|PKG%d+") or "None"
self.timer = TIMER:New(DYNAMICCARGO._UpdatePosition,self)
self.timer:Start(self.Interval,self.Interval)
if not _DYNAMICCARGO_HELOS then
_DYNAMICCARGO_HELOS = SET_CLIENT:New():FilterAlive():FilterFunction(DYNAMICCARGO._FilterHeloTypes):FilterStart()
end
if self.testing then
BASE:TraceOn()
BASE:TraceClass("DYNAMICCARGO")
end
return self
end
--- Get DCS object.
-- @param #DYNAMICCARGO self
-- @return DCS static object
function DYNAMICCARGO:GetDCSObject()
local DCSStatic = StaticObject.getByName( self.StaticName ) or Unit.getByName( self.StaticName )
if DCSStatic then
return DCSStatic
end
return nil
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- User API Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Get last known owner name of this DYNAMICCARGO
-- @param #DYNAMICCARGO self
-- @return #string Owner
function DYNAMICCARGO:GetLastOwner()
return self.Owner
end
--- Returns true if the cargo is new and has never been loaded into a Helo.
-- @param #DYNAMICCARGO self
-- @return #boolean Outcome
function DYNAMICCARGO:IsNew()
if self.CargoState and self.CargoState == DYNAMICCARGO.State.NEW then
return true
else
return false
end
end
--- Returns true if the cargo been loaded into a Helo.
-- @param #DYNAMICCARGO self
-- @return #boolean Outcome
function DYNAMICCARGO:IsLoaded()
if self.CargoState and self.CargoState == DYNAMICCARGO.State.LOADED then
return true
else
return false
end
end
--- Returns true if the cargo has been unloaded from a Helo.
-- @param #DYNAMICCARGO self
-- @return #boolean Outcome
function DYNAMICCARGO:IsUnloaded()
if self.CargoState and self.CargoState == DYNAMICCARGO.State.UNLOADED then
return true
else
return false
end
end
--- Returns true if the cargo has been removed.
-- @param #DYNAMICCARGO self
-- @return #boolean Outcome
function DYNAMICCARGO:IsRemoved()
if self.CargoState and self.CargoState == DYNAMICCARGO.State.REMOVED then
return true
else
return false
end
end
--- [CTLD] Get number of crates this DYNAMICCARGO consists of. Always one.
-- @param #DYNAMICCARGO self
-- @return #number crate number, always one
function DYNAMICCARGO:GetCratesNeeded()
return 1
end
--- [CTLD] Get this DYNAMICCARGO drop state. True if DYNAMICCARGO.State.UNLOADED
-- @param #DYNAMICCARGO self
-- @return #boolean Dropped
function DYNAMICCARGO:WasDropped()
return self.CargoState == DYNAMICCARGO.State.UNLOADED and true or false
end
--- [CTLD] Get CTLD_CARGO.Enum type of this DYNAMICCARGO
-- @param #DYNAMICCARGO self
-- @return #string Type, only one at the moment is CTLD_CARGO.Enum.GCLOADABLE
function DYNAMICCARGO:GetType()
return CTLD_CARGO.Enum.GCLOADABLE
end
--- Find last known position of this DYNAMICCARGO
-- @param #DYNAMICCARGO self
-- @return DCS#Vec3 Position in 3D space
function DYNAMICCARGO:GetLastPosition()
return self.LastPosition
end
--- Find current state of this DYNAMICCARGO
-- @param #DYNAMICCARGO self
-- @return string The current state
function DYNAMICCARGO:GetState()
return self.CargoState
end
--- Find a DYNAMICCARGO in the **_DATABASE** using the name associated with it.
-- @param #DYNAMICCARGO self
-- @param #string Name The dynamic cargo name
-- @return #DYNAMICCARGO self
function DYNAMICCARGO:FindByName( Name )
local storage = _DATABASE:FindDynamicCargo( Name )
return storage
end
--- Find the first(!) DYNAMICCARGO matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
-- @param #DYNAMICCARGO self
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
-- @return #DYNAMICCARGO The DYNAMICCARGO.
-- @usage
-- -- Find a dynamic cargo with a partial dynamic cargo name
-- local grp = DYNAMICCARGO:FindByMatching( "Apple" )
-- -- will return e.g. a dynamic cargo named "Apple|08:00|PKG08"
--
-- -- using a pattern
-- local grp = DYNAMICCARGO:FindByMatching( ".%d.%d$" )
-- -- will return the first dynamic cargo found ending in "-1-1" to "-9-9", but not e.g. "-10-1"
function DYNAMICCARGO:FindByMatching( Pattern )
local GroupFound = nil
for name,static in pairs(_DATABASE.DYNAMICCARGO) do
if string.match(name, Pattern ) then
GroupFound = static
break
end
end
return GroupFound
end
--- Find all DYNAMICCARGO objects matching using patterns. Note that this is **a lot** slower than `:FindByName()`!
-- @param #DYNAMICCARGO self
-- @param #string Pattern The pattern to look for. Refer to [LUA patterns](http://www.easyuo.com/openeuo/wiki/index.php/Lua_Patterns_and_Captures_\(Regular_Expressions\)) for regular expressions in LUA.
-- @return #table Groups Table of matching #DYNAMICCARGO objects found
-- @usage
-- -- Find all dynamic cargo with a partial dynamic cargo name
-- local grptable = DYNAMICCARGO:FindAllByMatching( "Apple" )
-- -- will return all dynamic cargos with "Apple" in the name
--
-- -- using a pattern
-- local grp = DYNAMICCARGO:FindAllByMatching( ".%d.%d$" )
-- -- will return the all dynamic cargos found ending in "-1-1" to "-9-9", but not e.g. "-10-1" or "-1-10"
function DYNAMICCARGO:FindAllByMatching( Pattern )
local GroupsFound = {}
for name,static in pairs(_DATABASE.DYNAMICCARGO) do
if string.match(name, Pattern ) then
GroupsFound[#GroupsFound+1] = static
end
end
return GroupsFound
end
--- Get the #STORAGE object from this dynamic cargo.
-- @param #DYNAMICCARGO self
-- @return Wrapper.Storage#STORAGE Storage The #STORAGE object
function DYNAMICCARGO:GetStorageObject()
return self.warehouse
end
--- Get the weight in kgs from this dynamic cargo.
-- @param #DYNAMICCARGO self
-- @return #number Weight in kgs.
function DYNAMICCARGO:GetCargoWeight()
local DCSObject = self:GetDCSObject()
if DCSObject then
local weight = DCSObject:getCargoWeight()
return weight
else
return 0
end
end
--- Get the cargo display name from this dynamic cargo.
-- @param #DYNAMICCARGO self
-- @return #string The display name
function DYNAMICCARGO:GetCargoDisplayName()
local DCSObject = self:GetDCSObject()
if DCSObject then
local weight = DCSObject:getCargoDisplayName()
return weight
else
return self.StaticName
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- [Internal] _Get helo hovering intel
-- @param #DYNAMICCARGO self
-- @param Wrapper.Unit#UNIT Unit The Unit to test
-- @param #number ropelength Ropelength to test
-- @return #boolean Outcome
function DYNAMICCARGO:_HeloHovering(Unit,ropelength)
local DCSUnit = Unit:GetDCSObject() --DCS#Unit
local hovering = false
local Height = 0
if DCSUnit then
local UnitInAir = DCSUnit:inAir()
local UnitCategory = DCSUnit:getDesc().category
if UnitInAir == true and UnitCategory == 1 then
local VelocityVec3 = DCSUnit:getVelocity()
local Velocity = UTILS.VecNorm(VelocityVec3)
local Coordinate = DCSUnit:getPoint()
local LandHeight = land.getHeight({ x = Coordinate.x, y = Coordinate.z })
Height = Coordinate.y - LandHeight
if Velocity < 1 and Height <= ropelength and Height > 6 then -- hover lower than ropelength but higher than the normal FARP height.
hovering = true
end
end
return hovering, Height
end
return false
end
--- [Internal] _Get Possible Player Helo Nearby
-- @param #DYNAMICCARGO self
-- @param Core.Point#COORDINATE pos
-- @param #boolean loading If true measure distance for loading else for unloading
-- @return #boolean Success
-- @return Wrapper.Client#CLIENT Helo
-- @return #string PlayerName
function DYNAMICCARGO:_GetPossibleHeloNearby(pos,loading)
local set = _DYNAMICCARGO_HELOS:GetAliveSet()
local success = false
local Helo = nil
local Playername = nil
for _,_helo in pairs (set or {}) do
local helo = _helo -- Wrapper.Client#CLIENT
local name = helo:GetPlayerName() or _DATABASE:_FindPlayerNameByUnitName(helo:GetName()) or "None"
self:T(self.lid.." Checking: "..name)
local hpos = helo:GetCoordinate()
-- TODO Check unloading via sling load?
local typename = helo:GetTypeName()
local dimensions = DYNAMICCARGO.AircraftDimensions[typename]
local hovering, height = self:_HeloHovering(helo,dimensions.ropelength)
local helolanded = not helo:InAir()
self:T(self.lid.." InAir: AGL/Hovering: "..hpos.y-hpos:GetLandHeight().."/"..tostring(hovering))
if hpos and typename and dimensions then
local delta2D = hpos:Get2DDistance(pos)
local delta3D = hpos:Get3DDistance(pos)
if self.testing then
self:T(string.format("Cargo relative position: 2D %dm | 3D %dm",delta2D,delta3D))
self:T(string.format("Helo dimension: length %dm | width %dm | rope %dm",dimensions.length,dimensions.width,dimensions.ropelength))
self:T(string.format("Helo hovering: %s at %dm",tostring(hovering),height))
end
-- unloading from ground
if loading~=true and (delta2D > dimensions.length or delta2D > dimensions.width) and helolanded then -- Theoretically the cargo could still be attached to the sling if landed next to the cargo. But once moved again it would go back into loaded state once lifted again.
success = true
Helo = helo
Playername = name
end
-- unloading from hover/rope
if loading~=true and delta3D > dimensions.ropelength then
success = true
Helo = helo
Playername = name
end
-- loading
if loading == true and ((delta2D < dimensions.length and delta2D < dimensions.width and helolanded) or (delta3D == dimensions.ropelength and helo:InAir())) then -- Loaded via ground or sling
success = true
Helo = helo
Playername = name
end
end
end
return success,Helo,Playername
end
--- [Internal] Update internal states.
-- @param #DYNAMICCARGO self
-- @return #DYNAMICCARGO self
function DYNAMICCARGO:_UpdatePosition()
self:T(self.lid.." _UpdatePositionAndState")
if self:IsAlive() then
local pos = self:GetCoordinate()
if self.testing then
self:T(string.format("Cargo position: x=%d, y=%d, z=%d",pos.x,pos.y,pos.z))
self:T(string.format("Last position: x=%d, y=%d, z=%d",self.LastPosition.x,self.LastPosition.y,self.LastPosition.z))
end
if UTILS.Round(UTILS.VecDist3D(pos,self.LastPosition),2) > 0.5 then -- This checks if the cargo has moved more than 0.5m since last check. If so then the cargo is loaded
---------------
-- LOAD Cargo
---------------
if self.CargoState == DYNAMICCARGO.State.NEW or self.CargoState == DYNAMICCARGO.State.UNLOADED then
local isloaded, client, playername = self:_GetPossibleHeloNearby(pos,true)
self:T(self.lid.." moved! NEW -> LOADED by "..tostring(playername))
self.CargoState = DYNAMICCARGO.State.LOADED
self.Owner = playername
_DATABASE:CreateEventDynamicCargoLoaded(self)
end
---------------
-- UNLOAD Cargo
---------------
-- If the cargo is stationary then we need to end this condition here to check whether it is unloaded or still onboard or still hooked if anyone can hover that precisly
elseif self.CargoState == DYNAMICCARGO.State.LOADED then
-- TODO add checker if we are in flight somehow
-- ensure not just the helo is moving
local count = _DYNAMICCARGO_HELOS:CountAlive()
-- Testing
local landheight = pos:GetLandHeight()
local agl = pos.y-landheight
agl = UTILS.Round(agl,2)
self:T(self.lid.." AGL: "..agl or -1)
local isunloaded = true
local client
local playername = self.Owner
if count > 0 then
self:T(self.lid.." Possible alive helos: "..count or -1)
isunloaded, client, playername = self:_GetPossibleHeloNearby(pos,false)
if isunloaded then
self:T(self.lid.." moved! LOADED -> UNLOADED by "..tostring(playername))
self.CargoState = DYNAMICCARGO.State.UNLOADED
self.Owner = playername
_DATABASE:CreateEventDynamicCargoUnloaded(self)
end
end
end
self.LastPosition = pos
--end
else
---------------
-- REMOVED Cargo
---------------
if self.timer and self.timer:IsRunning() then self.timer:Stop() end
self:T(self.lid.." dead! " ..self.CargoState.."-> REMOVED")
self.CargoState = DYNAMICCARGO.State.REMOVED
_DATABASE:CreateEventDynamicCargoRemoved(self)
end
return self
end
--- [Internal] Track helos for loaded/unloaded decision making.
-- @param Wrapper.Client#CLIENT client
-- @return #boolean IsIn
function DYNAMICCARGO._FilterHeloTypes(client)
if not client then return false end
local typename = client:GetTypeName()
local isinclude = DYNAMICCARGO.AircraftTypes[typename] ~= nil and true or false
return isinclude
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -11,7 +11,8 @@
-- @module Wrapper.Identifiable
-- @image MOOSE.JPG
--- @type IDENTIFIABLE
---
-- @type IDENTIFIABLE
-- @extends Wrapper.Object#OBJECT
-- @field #string IdentifiableName The name of the identifiable.
@@ -111,19 +112,28 @@ end
-- * Object.Category.SCENERY = 5
-- * Object.Category.Cargo = 6
--
-- For UNITs this returns a second value, one of
--
-- Unit.Category.AIRPLANE = 0
-- Unit.Category.HELICOPTER = 1
-- Unit.Category.GROUND_UNIT = 2
-- Unit.Category.SHIP = 3
-- Unit.Category.STRUCTURE = 4
--
-- @param #IDENTIFIABLE self
-- @return DCS#Object.Category The category ID, i.e. a number.
-- @return DCS#Unit.Category The unit category ID, i.e. a number. For units only.
function IDENTIFIABLE:GetCategory()
self:F2( self.ObjectName )
local DCSObject = self:GetDCSObject()
if DCSObject then
local ObjectCategory = DCSObject:getCategory()
local ObjectCategory, UnitCategory = DCSObject:getCategory()
self:T3( ObjectCategory )
return ObjectCategory
return ObjectCategory, UnitCategory
end
return nil
return nil,nil
end

View File

@@ -43,7 +43,7 @@ do
-- @field #NET
NET = {
ClassName = "NET",
Version = "0.1.3",
Version = "0.1.4",
BlockTime = 600,
BlockedPilots = {},
BlockedUCIDs = {},
@@ -67,6 +67,9 @@ function NET:New()
self.KnownPilots = {}
self:SetBlockMessage()
self:SetUnblockMessage()
self.BlockedSides = {}
self.BlockedSides[1] = false
self.BlockedSides[2] = false
-- Start State.
self:SetStartState("Stopped")
@@ -160,11 +163,12 @@ end
-- @param #string PlayerSlot
-- @return #boolean IsBlocked
function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
self:T({UCID,Name,PlayerID,PlayerSide,PlayerSlot})
local blocked = false
local TNow = timer.getTime()
-- UCID
if UCID and self.BlockedUCIDs[UCID] and TNow < self.BlockedUCIDs[UCID] then
return true
blocked = true
end
-- ID/Name
if PlayerID and not Name then
@@ -172,16 +176,18 @@ function NET:IsAnyBlocked(UCID,Name,PlayerID,PlayerSide,PlayerSlot)
end
-- Name
if Name and self.BlockedPilots[Name] and TNow < self.BlockedPilots[Name] then
return true
blocked = true
end
-- Side
if PlayerSide and self.BlockedSides[PlayerSide] and TNow < self.BlockedSides[PlayerSide] then
return true
self:T({time = self.BlockedSides[PlayerSide]})
if PlayerSide and type(self.BlockedSides[PlayerSide]) == "number" and TNow < self.BlockedSides[PlayerSide] then
blocked = true
end
-- Slot
if PlayerSlot and self.BlockedSlots[PlayerSlot] and TNow < self.BlockedSlots[PlayerSlot] then
return true
blocked = true
end
self:T("IsAnyBlocked: "..tostring(blocked))
return blocked
end
@@ -200,19 +206,27 @@ function NET:_EventHandler(EventData)
local ucid = self:GetPlayerUCID(nil,name) or "none"
local PlayerID = self:GetPlayerIDByName(name) or "none"
local PlayerSide, PlayerSlot = self:GetSlot(data.IniUnit)
if not PlayerSide then PlayerSide = EventData.IniCoalition end
if not PlayerSlot then PlayerSlot = EventData.IniUnit:GetID() or -1 end
local TNow = timer.getTime()
self:T(self.lid.."Event for: "..name.." | UCID: "..ucid)
--self:T(self.lid.."Event for: "..name.." | UCID: "..ucid .. " | ID/SIDE/SLOT "..PlayerID.."/"..PlayerSide.."/"..PlayerSlot)
-- Joining
if data.id == EVENTS.PlayerEnterUnit or data.id == EVENTS.PlayerEnterAircraft then
self:T(self.lid.."Pilot Joining: "..name.." | UCID: "..ucid.." | Event ID: "..data.id)
-- Check for blockages
local blocked = self:IsAnyBlocked(ucid,name,PlayerID,PlayerSide,PlayerSlot)
if blocked and PlayerID and tonumber(PlayerID) ~= 1 then
if blocked and PlayerID then -- and tonumber(PlayerID) ~= 1 then
self:T("Player blocked")
-- block pilot
local outcome = net.force_player_slot(tonumber(PlayerID), 0, '' )
local outcome = net.force_player_slot(tonumber(PlayerID), PlayerSide, data.IniUnit:GetID() )
self:T({Blocked_worked=outcome})
if outcome == false then
local unit = data.IniUnit
local sched = TIMER:New(unit.Destroy,unit,3):Start(3)
self:__PlayerBlocked(5,unit,name,1)
end
else
local client = CLIENT:FindByPlayerName(name) or data.IniUnit
if not self.KnownPilots[name] or (self.KnownPilots[name] and TNow-self.KnownPilots[name].timestamp > 3) then
@@ -225,6 +239,7 @@ function NET:_EventHandler(EventData)
slot = PlayerSlot,
timestamp = TNow,
}
--UTILS.PrintTableToLog(self.KnownPilots[name])
end
return self
end
@@ -350,11 +365,10 @@ end
--- Block a specific coalition side, does NOT automatically kick all players of that side or kick out joined players
-- @param #NET self
-- @param #number side The side to block - 1 : Red, 2 : Blue
-- @param #number Side The side to block - 1 : Red, 2 : Blue
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:BlockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then
self.BlockedSides[Side] = timer.getTime()+addon
@@ -367,10 +381,9 @@ end
-- @param #number Seconds Seconds (optional) Number of seconds the player has to wait before rejoining.
-- @return #NET self
function NET:UnblockSide(Side,Seconds)
self:T({Side,Seconds})
local addon = Seconds or self.BlockTime
if Side == 1 or Side == 2 then
self.BlockedSides[Side] = nil
self.BlockedSides[Side] = false
end
return self
end
@@ -485,8 +498,11 @@ end
-- @param Wrapper.Client#CLIENT Client The client
-- @return #number PlayerID or nil
function NET:GetPlayerIDFromClient(Client)
self:T("GetPlayerIDFromClient")
self:T({Client=Client})
if Client then
local name = Client:GetPlayerName()
self:T({name=name})
local id = self:GetPlayerIDByName(name)
return id
else
@@ -528,6 +544,7 @@ function NET:SendChatToPlayer(Message, ToPlayer, FromPlayer)
return self
end
--[[ not in 2.97 MSE any longer
--- Load a specific mission.
-- @param #NET self
-- @param #string Path and Mission
@@ -550,6 +567,7 @@ function NET:LoadNextMission()
outcome = net.load_next_mission()
return outcome
end
--]]
--- Return a table of players currently connected to the server.
-- @param #NET self
@@ -680,16 +698,19 @@ end
-- @return #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
-- @return #number SlotID
function NET:GetSlot(Client)
self:T("NET.GetSlot")
local PlayerID = self:GetPlayerIDFromClient(Client)
self:T("NET.GetSlot PlayerID = "..tostring(PlayerID))
if PlayerID then
local side,slot = net.get_slot(tonumber(PlayerID))
self:T("NET.GetSlot side, slot = "..tostring(side)..","..tostring(slot))
return side,slot
else
return nil,nil
end
end
--- Force the slot for a specific client.
--- Force the slot for a specific client. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)!
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @param #number SideID i.e. 0 : spectators, 1 : Red, 2 : Blue
@@ -697,19 +718,22 @@ end
-- @return #boolean Success
function NET:ForceSlot(Client,SideID,SlotID)
local PlayerID = self:GetPlayerIDFromClient(Client)
if PlayerID and tonumber(PlayerID) ~= 1 then
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID or '' )
local SlotID = SlotID or Client:GetID()
if PlayerID then -- and tonumber(PlayerID) ~= 1 then
return net.force_player_slot(tonumber(PlayerID), SideID, SlotID )
else
return false
end
end
--- Force a client back to spectators.
--- Force a client back to spectators. If this returns false, it didn't work via `net` (which is ALWAYS the case as of Nov 2024)!
-- @param #NET self
-- @param Wrapper.Client#CLIENT Client The client
-- @return #boolean Succes
function NET:ReturnToSpectators(Client)
local outcome = self:ForceSlot(Client,0)
-- workaround
local sched = TIMER:New(Client.Destroy,Client,1):Start(1)
return outcome
end
@@ -779,7 +803,7 @@ function NET:onafterStatus(From,Event,To)
local function HouseHold(tavolo)
local TNow = timer.getTime()
for _,entry in pairs (tavolo) do
if entry >= TNow then entry = nil end
if type(entry) == "number" and entry >= TNow then entry = false end
end
end

View File

@@ -16,7 +16,7 @@
--- @type POSITIONABLE
-- @field Core.Point#COORDINATE coordinate Coordinate object.
-- @field Core.Point#POINT_VEC3 pointvec3 Point Vec3 object.
-- @field Core.Point#COORDINATE pointvec3 Point Vec3 object.
-- @extends Wrapper.Identifiable#IDENTIFIABLE
@@ -110,14 +110,17 @@ function POSITIONABLE:Destroy( GenerateEvent )
if GenerateEvent and GenerateEvent == true then
if self:IsAir() then
--self:ScheduleOnce(1,self.CreateEventCrash,self,timer.getTime(),DCSObject)
self:CreateEventCrash( timer.getTime(), DCSObject )
else
--self:ScheduleOnce(1,self.CreateEventDead,self,timer.getTime(),DCSObject)
self:CreateEventDead( timer.getTime(), DCSObject )
end
elseif GenerateEvent == false then
-- Do nothing!
else
self:CreateEventRemoveUnit( timer.getTime(), DCSObject )
--self:ScheduleOnce(1,self.CreateEventRemoveUnit,self,timer.getTime(),DCSObject)
end
USERFLAG:New( UnitGroupName ):Set( 100 )
@@ -142,7 +145,11 @@ function POSITIONABLE:GetPosition()
self:F2( self.PositionableName )
local DCSPositionable = self:GetDCSObject()
if self:IsInstanceOf("GROUP") then
DCSPositionable = self:GetFirstUnitAlive():GetDCSObject()
end
if DCSPositionable then
local PositionablePosition = DCSPositionable:getPosition()
self:T3( PositionablePosition )
@@ -277,9 +284,9 @@ function POSITIONABLE:GetVec2()
return nil
end
--- Returns a POINT_VEC2 object indicating the point in 2D of the POSITIONABLE within the mission.
--- Returns a COORDINATE object indicating the point in 2D of the POSITIONABLE within the mission.
-- @param #POSITIONABLE self
-- @return Core.Point#POINT_VEC2 The 2D point vector of the POSITIONABLE.
-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetPointVec2()
self:F2( self.PositionableName )
@@ -289,20 +296,20 @@ function POSITIONABLE:GetPointVec2()
if DCSPositionable then
local PositionableVec3 = DCSPositionable:getPosition().p
local PositionablePointVec2 = POINT_VEC2:NewFromVec3( PositionableVec3 )
local PositionablePointVec2 = COORDINATE:NewFromVec3( PositionableVec3 )
-- self:F( PositionablePointVec2 )
return PositionablePointVec2
end
self:E( { "Cannot GetPointVec2", Positionable = self, Alive = self:IsAlive() } )
self:E( { "Cannot Coordinate", Positionable = self, Alive = self:IsAlive() } )
return nil
end
--- Returns a POINT_VEC3 object indicating the point in 3D of the POSITIONABLE within the mission.
--- Returns a COORDINATE object indicating the point in 3D of the POSITIONABLE within the mission.
-- @param #POSITIONABLE self
-- @return Core.Point#POINT_VEC3 The 3D point vector of the POSITIONABLE.
-- @return Core.Point#COORDINATE The 3D point vector of the POSITIONABLE.
-- @return #nil The POSITIONABLE is not existing or alive.
function POSITIONABLE:GetPointVec3()
@@ -322,8 +329,8 @@ function POSITIONABLE:GetPointVec3()
else
-- Create a new POINT_VEC3 object.
self.pointvec3 = POINT_VEC3:NewFromVec3( PositionableVec3 )
-- Create a new COORDINATE object.
self.pointvec3 = COORDINATE:NewFromVec3( PositionableVec3 )
end
@@ -671,7 +678,7 @@ function POSITIONABLE:GetBoundingRadius( MinDist )
return math.max( math.max( CX, CZ ), boxmin )
end
BASE:E( { "Cannot GetBoundingRadius", Positionable = self, Alive = self:IsAlive() } )
BASE:T( { "Cannot GetBoundingRadius", Positionable = self, Alive = self:IsAlive() } )
return nil
end
@@ -1853,6 +1860,7 @@ do -- Cargo
["HL_KORD"] = 6*POSITIONABLE.DefaultInfantryWeight,
["HL_DSHK"] = 6*POSITIONABLE.DefaultInfantryWeight,
["CCKW_353"] = 16*POSITIONABLE.DefaultInfantryWeight, --GMC CCKW 2½-ton 6×6 truck, estimating 16 soldiers,
["MaxxPro_MRAP"] = 7*POSITIONABLE.DefaultInfantryWeight,
}
}

View File

@@ -1,13 +1,13 @@
--- **Wrapper** - SCENERY models scenery within the DCS simulator.
--
--
-- ===
--
--
-- ### Author: **FlightControl**
--
--
-- ### Contributions: **Applevangelist**, **funkyfranky**
--
--
-- ===
--
--
-- @module Wrapper.Scenery
-- @image Wrapper_Scenery.JPG
@@ -23,13 +23,13 @@
--- Wrapper class to handle Scenery objects that are defined on the map.
--
--
-- The @{Wrapper.Scenery#SCENERY} class is a wrapper class to handle the DCS Scenery objects:
--
--
-- * Wraps the DCS Scenery objects.
-- * Support all DCS Scenery APIs.
-- * Enhance with Scenery specific APIs not in the DCS API set.
--
--
-- @field #SCENERY
SCENERY = {
ClassName = "SCENERY",
@@ -43,23 +43,23 @@ SCENERY = {
function SCENERY:Register( SceneryName, SceneryObject )
local self = BASE:Inherit( self, POSITIONABLE:New( SceneryName ) )
self.SceneryName = tostring(SceneryName)
self.SceneryObject = SceneryObject
if self.SceneryObject then
self.Life0 = self.SceneryObject:getLife()
if self.SceneryObject and self.SceneryObject.getLife then -- fix some objects do not have all functions
self.Life0 = self.SceneryObject:getLife() or 0
else
self.Life0 = 0
end
self.Properties = {}
return self
end
--- Returns the Value of the zone with the given PropertyName, or nil if no matching property exists.
--- Returns the value of the scenery with the given PropertyName, or nil if no matching property exists.
-- @param #SCENERY self
-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved.
-- @return #string The Value of the QuadZone Property from the scenery assignment with the given PropertyName, or nil if absent.
@@ -67,6 +67,14 @@ function SCENERY:GetProperty(PropertyName)
return self.Properties[PropertyName]
end
--- Checks if the value of the scenery with the given PropertyName exists.
-- @param #SCENERY self
-- @param #string PropertyName The name of a the QuadZone Property from the scenery assignment to be retrieved.
-- @return #boolean Outcome True if it exists, else false.
function SCENERY:HasProperty(PropertyName)
return self.Properties[PropertyName] ~= nil and true or false
end
--- Returns the scenery Properties table.
-- @param #SCENERY self
-- @return #table The Key:Value table of QuadZone properties of the zone from the scenery assignment .
@@ -83,6 +91,7 @@ function SCENERY:SetProperty(PropertyName, PropertyValue)
self.Properties[PropertyName] = PropertyValue
return self
end
--- Obtain object name.
--@param #SCENERY self
--@return #string Name
@@ -97,15 +106,15 @@ function SCENERY:GetDCSObject()
return self.SceneryObject
end
--- Get current life points from the SCENERY Object.
-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120%
-- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success
--- Get current life points from the SCENERY Object. Note - Some scenery objects always have 0 life points.
-- **CAVEAT**: Some objects change their life value or "hitpoints" **after** the first hit. Hence we will adjust the life0 value to 120%
-- of the last life value if life exceeds life0 (initial life) at any point. Thus will will get a smooth percentage decrease, if you use this e.g. as success
-- criteria for a bombing task.
--@param #SCENERY self
--@return #number life
function SCENERY:GetLife()
local life = 0
if self.SceneryObject then
if self.SceneryObject and self.SceneryObject.getLife then
life = self.SceneryObject:getLife()
if life > self.Life0 then
self.Life0 = math.floor(life * 1.2)
@@ -121,7 +130,7 @@ function SCENERY:GetLife0()
return self.Life0 or 0
end
--- Check if SCENERY Object is alive.
--- Check if SCENERY Object is alive. Note - Some scenery objects always have 0 life points.
--@param #SCENERY self
--@param #number Threshold (Optional) If given, SCENERY counts as alive above this relative life in percent (1..100).
--@return #number life
@@ -133,7 +142,7 @@ function SCENERY:IsAlive(Threshold)
end
end
--- Check if SCENERY Object is dead.
--- Check if SCENERY Object is dead. Note - Some scenery objects always have 0 life points.
--@param #SCENERY self
--@param #number Threshold (Optional) If given, SCENERY counts as dead below this relative life in percent (1..100).
--@return #number life
@@ -145,12 +154,13 @@ function SCENERY:IsDead(Threshold)
end
end
--- Get SCENERY relative life in percent, e.g. 75.
--- Get SCENERY relative life in percent, e.g. 75. Note - Some scenery objects always have 0 life points.
--@param #SCENERY self
--@return #number rlife
function SCENERY:GetRelativeLife()
local life = self:GetLife()
local life0 = self:GetLife0()
if life == 0 or life0 == 0 then return 0 end
local rlife = math.floor((life/life0)*100)
return rlife
end
@@ -175,7 +185,7 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role)
local radius = Radius or 100
local name = Name or "unknown"
local scenery = nil
---
-- @param Core.Point#COORDINATE coordinate
-- @param #number radius
@@ -199,13 +209,13 @@ function SCENERY:FindByName(Name, Coordinate, Radius, Role)
end
return nil
end
if Coordinate then
--BASE:I("Coordinate Scenery Scan")
scenery = SceneryScan(Coordinate, radius, name)
end
return scenery
return scenery
end
--- Find a SCENERY object from its name or id. Since SCENERY isn't registered in the Moose database (just too many objects per map), we need to do a scan first
@@ -215,7 +225,7 @@ end
--@param Core.Zone#ZONE_BASE Zone Where to find the scenery object. Can be handed as zone name.
--@param #number Radius (optional) Search radius around coordinate, defaults to 100
--@return #SCENERY Scenery Object or `nil` if it cannot be found
function SCENERY:FindByNameInZone(Name, Zone, Radius)
function SCENERY:FindByNameInZone(Name, Zone, Radius)
local radius = Radius or 100
local name = Name or "unknown"
if type(Zone) == "string" then
@@ -270,7 +280,7 @@ end
function SCENERY:FindAllByZoneName( ZoneName )
local zone = ZoneName -- Core.Zone#ZONE_RADIUS
if type(ZoneName) == "string" then
zone = ZONE:FindByName(ZoneName)
zone = ZONE:FindByName(ZoneName)
end
local _id = zone:GetProperty('OBJECT ID')
--local properties = zone:GetAllProperties() or {}
@@ -290,7 +300,7 @@ function SCENERY:FindAllByZoneName( ZoneName )
return {obj}
else
return nil
end
end
end
end

View File

@@ -61,6 +61,8 @@ function STATIC:Register( StaticName )
if DCSStatic then
local Life0 = DCSStatic:getLife() or 1
self.Life0 = Life0
else
self:E(string.format("Static object %s does not exist!", tostring(self.StaticName)))
end
return self
@@ -178,7 +180,7 @@ end
-- @param #STATIC self
-- @return DCS static object
function STATIC:GetDCSObject()
local DCSStatic = StaticObject.getByName( self.StaticName )
local DCSStatic = StaticObject.getByName( self.StaticName )
if DCSStatic then
return DCSStatic
@@ -330,3 +332,26 @@ function STATIC:FindAllByMatching( Pattern )
return GroupsFound
end
--- Get the Wrapper.Storage#STORAGE object of an static if it is used as cargo and has been set up as storage object.
-- @param #STATIC self
-- @return Wrapper.Storage#STORAGE Storage or `nil` if not fund or set up.
function STATIC:GetStaticStorage()
local name = self:GetName()
local storage = STORAGE:NewFromStaticCargo(name)
return storage
end
--- Get the Cargo Weight of a static object in kgs. Returns -1 if not found.
-- @param #STATIC self
-- @return #number Mass Weight in kgs.
function STATIC:GetCargoWeight()
local DCSObject = StaticObject.getByName(self.StaticName )
local mass = -1
if DCSObject then
mass = DCSObject:getCargoWeight() or 0
local masstxt = DCSObject:getCargoDisplayName() or "none"
--BASE:I("GetCargoWeight "..tostring(mass).." MassText "..masstxt)
end
return mass
end

View File

@@ -26,6 +26,7 @@
-- @field #string lid Class id string for output to DCS log file.
-- @field DCS#Warehouse warehouse The DCS warehouse object.
-- @field DCS#Airbase airbase The DCS airbase object.
-- @field Core.Timer#TIMER SaverTimer The TIMER for autosave.
-- @extends Core.Base#BASE
--- *The capitalist cannot store labour-power in warehouses after he has bought it, as he may do with the raw material.* -- Karl Marx
@@ -124,6 +125,39 @@
-- UTILS.PrintTableToLog(liquids)
-- UTILS.PrintTableToLog(weapons)
--
-- # Weapons Helper Enumerater
--
-- The currently available weapon items are available in the `ENUMS.Storage.weapons`, e.g. `ENUMS.Storage.weapons.bombs.Mk_82Y`.
--
-- # Persistence
--
-- The contents of the storage can be saved to and read from disk. For this to function, `io` and `lfs` need to be desanitized in `MissionScripting.lua`.
--
-- ## Save once
--
-- ### To save once, e.g. this is sufficient:
--
-- -- Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Only Storage NOT set to unlimited will be saved.
-- local Path = "C:\\Users\\UserName\\Saved Games\\DCS\\Missions\\"
-- local Filename = "Batumi"
-- storage:SaveToFile(Path,Filename)
--
-- ### Autosave
--
-- storage:StartAutoSave(Path,Filename,300,true) -- save every 300 secs/5 mins starting in 5 mins, load the existing storage - if any - first if the last parameter is **not** `false`.
--
-- ### Stop Autosave
--
-- storage:StopAutoSave() -- stop the scheduler.
--
-- ### Load back with e.g.
--
-- -- Filenames searched for the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Only Storage NOT set to unlimited will be loaded.
-- local Path = "C:\\Users\\UserName\\Saved Games\\DCS\\Missions\\"
-- local Filename = "Batumi"
-- storage:LoadFromFile(Path,Filename)
--
--
-- @field #STORAGE
STORAGE = {
ClassName = "STORAGE",
@@ -143,22 +177,46 @@ STORAGE.Liquid = {
DIESEL = 3,
}
--- Liquid Names for the static cargo resource table.
-- @type STORAGE.LiquidName
-- @field #number JETFUEL "jet_fuel".
-- @field #number GASOLINE "gasoline".
-- @field #number MW50 "methanol_mixture".
-- @field #number DIESEL "diesel".
STORAGE.LiquidName = {
GASOLINE = "gasoline",
DIESEL = "diesel",
MW50 = "methanol_mixture",
JETFUEL = "jet_fuel",
}
--- Storage types.
-- @type STORAGE.Type
-- @field #number WEAPONS weapons.
-- @field #number LIQUIDS liquids. Also see #list<#STORAGE.Liquid> for types of liquids.
-- @field #number AIRCRAFT aircraft.
STORAGE.Type = {
WEAPONS = "weapons",
LIQUIDS = "liquids",
AIRCRAFT = "aircrafts",
}
--- STORAGE class version.
-- @field #string version
STORAGE.version="0.0.1"
STORAGE.version="0.1.5"
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO list
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- TODO: A lot...
-- TODO: Persistence
-- DONE: Persistence
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Constructor
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
--- Create a new STORAGE object from the DCS weapon object.
--- Create a new STORAGE object from the DCS airbase object.
-- @param #STORAGE self
-- @param #string AirbaseName Name of the airbase.
-- @return #STORAGE self
@@ -169,17 +227,57 @@ function STORAGE:New(AirbaseName)
self.airbase=Airbase.getByName(AirbaseName)
if Airbase.getWarehouse then
if Airbase.getWarehouse and self.airbase then
self.warehouse=self.airbase:getWarehouse()
end
self.lid = string.format("STORAGE %s", AirbaseName)
self.lid = string.format("STORAGE %s | ", AirbaseName)
return self
end
--- Create a new STORAGE object from an DCS static cargo object.
-- @param #STORAGE self
-- @param #string StaticCargoName Unit name of the static.
-- @return #STORAGE self
function STORAGE:NewFromStaticCargo(StaticCargoName)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #STORAGE
self.airbase=StaticObject.getByName(StaticCargoName)
if Airbase.getWarehouse then
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
end
self.lid = string.format("STORAGE %s | ", StaticCargoName)
return self
end
--- Create a new STORAGE object from a Wrapper.DynamicCargo#DYNAMICCARGO object.
-- @param #STORAGE self
-- @param #string DynamicCargoName Unit name of the dynamic cargo.
-- @return #STORAGE self
function STORAGE:NewFromDynamicCargo(DynamicCargoName)
-- Inherit everything from BASE class.
local self=BASE:Inherit(self, BASE:New()) -- #STORAGE
self.airbase=Unit.getByName(DynamicCargoName) or StaticObject.getByName(DynamicCargoName)
if Airbase.getWarehouse then
self.warehouse=Warehouse.getCargoAsWarehouse(self.airbase)
end
self.lid = string.format("STORAGE %s | ", DynamicCargoName)
return self
end
--- Find a STORAGE in the **_DATABASE** using the name associated airbase.
--- Airbases only - Find a STORAGE in the **_DATABASE** using the name associated airbase.
-- @param #STORAGE self
-- @param #string AirbaseName The Airbase Name.
-- @return #STORAGE self
@@ -198,6 +296,10 @@ end
-- @return #STORAGE self
function STORAGE:SetVerbosity(VerbosityLevel)
self.verbose=VerbosityLevel or 0
if self.verbose > 1 then
BASE:TraceOn()
BASE:TraceClass("STORAGE")
end
return self
end
@@ -406,7 +508,7 @@ end
--- Returns whether a given type of aircraft, liquid, weapon is set to be unlimited.
-- @param #STORAGE self
-- @param #string Type Name of aircraft, weapon or equipment or type of liquid (as `#number`).
-- @return #boolen If `true` the given type is unlimited or `false` otherwise.
-- @return #boolean If `true` the given type is unlimited or `false` otherwise.
function STORAGE:IsUnlimited(Type)
-- Get current amount of type.
@@ -423,7 +525,7 @@ function STORAGE:IsUnlimited(Type)
local n=self:GetAmount(Type)
-- If amount did not change, it is unlimited.
unlimited=n==N
unlimited=unlimited or n > 2^29 or n==N
-- Add item back.
if not unlimited then
@@ -431,7 +533,7 @@ function STORAGE:IsUnlimited(Type)
end
-- Debug info.
self:I(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n))
self:T(self.lid..string.format("Type=%s: unlimited=%s (N=%d n=%d)", tostring(Type), tostring(unlimited), N, n))
end
return unlimited
@@ -440,7 +542,7 @@ end
--- Returns whether a given type of aircraft, liquid, weapon is set to be limited.
-- @param #STORAGE self
-- @param #number Type Type of liquid or name of aircraft, weapon or equipment.
-- @return #boolen If `true` the given type is limited or `false` otherwise.
-- @return #boolean If `true` the given type is limited or `false` otherwise.
function STORAGE:IsLimited(Type)
local limited=not self:IsUnlimited(Type)
@@ -450,7 +552,7 @@ end
--- Returns whether aircraft are unlimited.
-- @param #STORAGE self
-- @return #boolen If `true` aircraft are unlimited or `false` otherwise.
-- @return #boolean If `true` aircraft are unlimited or `false` otherwise.
function STORAGE:IsUnlimitedAircraft()
-- We test with a specific type but if it is unlimited, than all aircraft are.
@@ -461,7 +563,7 @@ end
--- Returns whether liquids are unlimited.
-- @param #STORAGE self
-- @return #boolen If `true` liquids are unlimited or `false` otherwise.
-- @return #boolean If `true` liquids are unlimited or `false` otherwise.
function STORAGE:IsUnlimitedLiquids()
-- We test with a specific type but if it is unlimited, than all are.
@@ -472,7 +574,7 @@ end
--- Returns whether weapons and equipment are unlimited.
-- @param #STORAGE self
-- @return #boolen If `true` weapons and equipment are unlimited or `false` otherwise.
-- @return #boolean If `true` weapons and equipment are unlimited or `false` otherwise.
function STORAGE:IsUnlimitedWeapons()
-- We test with a specific type but if it is unlimited, than all are.
@@ -483,7 +585,7 @@ end
--- Returns whether aircraft are limited.
-- @param #STORAGE self
-- @return #boolen If `true` aircraft are limited or `false` otherwise.
-- @return #boolean If `true` aircraft are limited or `false` otherwise.
function STORAGE:IsLimitedAircraft()
-- We test with a specific type but if it is limited, than all are.
@@ -494,7 +596,7 @@ end
--- Returns whether liquids are limited.
-- @param #STORAGE self
-- @return #boolen If `true` liquids are limited or `false` otherwise.
-- @return #boolean If `true` liquids are limited or `false` otherwise.
function STORAGE:IsLimitedLiquids()
-- We test with a specific type but if it is limited, than all are.
@@ -505,7 +607,7 @@ end
--- Returns whether weapons and equipment are limited.
-- @param #STORAGE self
-- @return #boolen If `true` liquids are limited or `false` otherwise.
-- @return #boolean If `true` liquids are limited or `false` otherwise.
function STORAGE:IsLimitedWeapons()
-- We test with a specific type but if it is limited, than all are.
@@ -527,6 +629,247 @@ function STORAGE:GetInventory(Item)
return inventory.aircraft, inventory.liquids, inventory.weapon
end
--- Save the contents of a STORAGE to files in CSV format. Filenames created are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working.
-- @param #STORAGE self
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The base name of the files. Existing files will be overwritten.
-- @return #STORAGE self
function STORAGE:SaveToFile(Path,Filename)
if not io then
BASE:E("ERROR: io not desanitized. Can't save the files.")
return false
end
-- Check default path.
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. File will be saved in DCS installation root directory rather than your given path.")
end
local ac, lq, wp = self:GetInventory()
local DataAircraft = ""
local DataLiquids = ""
local DataWeapons = ""
if #lq > 0 then
DataLiquids = DataLiquids .."Liquids in Storage:\n"
for key,amount in pairs(lq) do
DataLiquids = DataLiquids..tostring(key).."="..tostring(amount).."\n"
end
UTILS.SaveToFile(Path,Filename.."_Liquids.csv",DataLiquids)
if self.verbose and self.verbose > 0 then
self:I(self.lid.."Saving Liquids to "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
end
end
if UTILS.TableLength(ac) > 0 then
DataAircraft = DataAircraft .."Aircraft in Storage:\n"
for key,amount in pairs(ac) do
DataAircraft = DataAircraft..tostring(key).."="..tostring(amount).."\n"
end
UTILS.SaveToFile(Path,Filename.."_Aircraft.csv",DataAircraft)
if self.verbose and self.verbose > 0 then
self:I(self.lid.."Saving Aircraft to "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
end
end
if UTILS.TableLength(wp) > 0 then
DataWeapons = DataWeapons .."Weapons and Materiel in Storage:\n"
for _,_category in pairs(ENUMS.Storage.weapons) do
for _,_key in pairs(_category) do
local amount = self:GetAmount(_key)
if type(_key) == "table" then
_key = "{"..table.concat(_key,",").."}"
end
DataWeapons = DataWeapons..tostring(_key).."="..tostring(amount).."\n"
end
end
-- Gazelle table keys
for key,amount in pairs(ENUMS.Storage.weapons.Gazelle) do
amount = self:GetItemAmount(ENUMS.Storage.weapons.Gazelle[key])
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.Gazelle."..tostring(key).."="..tostring(amount).."\n"
end
-- CH47
for key,amount in pairs(ENUMS.Storage.weapons.CH47) do
amount = self:GetItemAmount(ENUMS.Storage.weapons.CH47[key])
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.CH47."..tostring(key).."="..tostring(amount).."\n"
end
-- UH1H
for key,amount in pairs(ENUMS.Storage.weapons.UH1H) do
amount = self:GetItemAmount(ENUMS.Storage.weapons.UH1H[key])
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.UH1H."..tostring(key).."="..tostring(amount).."\n"
end
-- OH58D
for key,amount in pairs(ENUMS.Storage.weapons.OH58) do
amount = self:GetItemAmount(ENUMS.Storage.weapons.OH58[key])
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.OH58."..tostring(key).."="..tostring(amount).."\n"
end
-- AH64D
for key,amount in pairs(ENUMS.Storage.weapons.AH64D) do
amount = self:GetItemAmount(ENUMS.Storage.weapons.AH64D[key])
DataWeapons = DataWeapons.."ENUMS.Storage.weapons.AH64D."..tostring(key).."="..tostring(amount).."\n"
end
UTILS.SaveToFile(Path,Filename.."_Weapons.csv",DataWeapons)
if self.verbose and self.verbose > 0 then
self:I(self.lid.."Saving Weapons to "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
end
end
return self
end
--- Load the contents of a STORAGE from files. Filenames searched for are the Filename given amended by "_Liquids", "_Aircraft" and "_Weapons" followed by a ".csv". Requires io and lfs to be desanitized to be working.
-- @param #STORAGE self
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @return #STORAGE self
function STORAGE:LoadFromFile(Path,Filename)
if not io then
BASE:E("ERROR: io not desanitized. Can't read the files.")
return false
end
-- Check default path.
if Path==nil and not lfs then
BASE:E("WARNING: lfs not desanitized. File will be read from DCS installation root directory rather than your give path.")
end
--Liquids
if self:IsLimitedLiquids() then
local Ok,Liquids = UTILS.LoadFromFile(Path,Filename.."_Liquids.csv")
if Ok then
if self.verbose and self.verbose > 0 then
self:I(self.lid.."Loading Liquids from "..tostring(Path).."\\"..tostring(Filename).."_Liquids.csv")
end
for _id,_line in pairs(Liquids) do
if string.find(_line,"Storage") == nil then
local tbl=UTILS.Split(_line,"=")
local lqno = tonumber(tbl[1])
local lqam = tonumber(tbl[2])
self:SetLiquid(lqno,lqam)
end
end
else
self:E("File for Liquids could not be found: "..tostring(Path).."\\"..tostring(Filename"_Liquids.csv"))
end
end
--Aircraft
if self:IsLimitedAircraft() then
local Ok,Aircraft = UTILS.LoadFromFile(Path,Filename.."_Aircraft.csv")
if Ok then
if self.verbose and self.verbose > 0 then
self:I(self.lid.."Loading Aircraft from "..tostring(Path).."\\"..tostring(Filename).."_Aircraft.csv")
end
for _id,_line in pairs(Aircraft) do
if string.find(_line,"Storage") == nil then
local tbl=UTILS.Split(_line,"=")
local acname = tbl[1]
local acnumber = tonumber(tbl[2])
self:SetAmount(acname,acnumber)
end
end
else
self:E("File for Aircraft could not be found: "..tostring(Path).."\\"..tostring(Filename"_Aircraft.csv"))
end
end
--Weapons
if self:IsLimitedWeapons() then
local Ok,Weapons = UTILS.LoadFromFile(Path,Filename.."_Weapons.csv")
if Ok then
if self.verbose and self.verbose > 0 then
self:I(self.lid.."Loading Weapons from "..tostring(Path).."\\"..tostring(Filename).."_Weapons.csv")
end
for _id,_line in pairs(Weapons) do
if string.find(_line,"Storage") == nil then
local tbl=UTILS.Split(_line,"=")
local wpname = tbl[1]
local wpnumber = tonumber(tbl[2])
if string.find(wpname,"{") == 1 then
--self:I("Found a table: "..wpname)
wpname = string.gsub(wpname,"{","")
wpname = string.gsub(wpname,"}","")
local tbl = UTILS.Split(wpname,",")
local wptbl = {}
for _id,_key in ipairs(tbl) do
table.insert(wptbl,_id,_key)
end
self:SetAmount(wptbl,wpnumber)
else
self:SetAmount(wpname,wpnumber)
end
end
end
else
self:E("File for Weapons could not be found: "..tostring(Path).."\\"..tostring(Filename"_Weapons.csv"))
end
end
return self
end
--- Start a STORAGE autosave process.
-- @param #STORAGE self
-- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems.
-- @param #string Filename The name of the file.
-- @param #number Interval The interval, start after this many seconds and repeat every interval seconds. Defaults to 300.
-- @param #boolean LoadOnce If LoadOnce is true or nil, we try to load saved storage first.
-- @return #STORAGE self
function STORAGE:StartAutoSave(Path,Filename,Interval,LoadOnce)
if LoadOnce ~= false then
self:LoadFromFile(Path,Filename)
end
local interval = Interval or 300
self.SaverTimer = TIMER:New(STORAGE.SaveToFile,self,Path,Filename)
self.SaverTimer:Start(interval,interval)
return self
end
--- Stop a running STORAGE autosave process.
-- @param #STORAGE self
-- @return #STORAGE self
function STORAGE:StopAutoSave()
if self.SaverTimer and self.SaverTimer:IsRunning() then
self.SaverTimer:Stop()
self.SaverTimer = nil
end
return self
end
--- Try to find the #STORAGE object of one of the many "H"-Helipads in Syria. You need to put a (small, round) zone on top of it, because the name is not unique(!).
-- @param #STORAGE self
-- @param #string ZoneName The name of the zone where to find the helipad.
-- @return #STORAGE self or nil if not found.
function STORAGE:FindSyriaHHelipadWarehouse(ZoneName)
local findzone = ZONE:New(ZoneName)
local base = world.getAirbases()
for i = 1, #base do
local info = {}
--info.desc = Airbase.getDesc(base[i])
info.callsign = Airbase.getCallsign(base[i])
info.id = Airbase.getID(base[i])
--info.cat = Airbase.getCategory(base[i])
info.point = Airbase.getPoint(base[i])
info.coordinate = COORDINATE:NewFromVec3(info.point)
info.DCSObject = base[i]
--if Airbase.getUnit(base[i]) then
--info.unitId = Airbase.getUnit(base[i]):getID()
--end
if info.callsign == "H" and findzone:IsCoordinateInZone(info.coordinate) then
info.warehouse = info.DCSObject:getWarehouse()
info.Storage = STORAGE:New(info.callsign..info.id)
info.Storage.airbase = info.DCSObject
info.Storage.warehouse = info.warehouse
return info.Storage
end
end
end
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------
-- Private Functions
-------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

File diff suppressed because it is too large Load Diff

View File

@@ -385,24 +385,26 @@ function WEAPON:GetTarget()
--Target name
local name=object:getName()
-- Debug info.
self:T(self.lid..string.format("Got Target Object %s, category=%d", object:getName(), category))
if category==Object.Category.UNIT then
target=UNIT:FindByName(name)
elseif category==Object.Category.STATIC then
target=STATIC:FindByName(name, false)
elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
if name then
-- Debug info.
self:T(self.lid..string.format("Got Target Object %s, category=%d", name, category))
if category==Object.Category.UNIT then
target=UNIT:FindByName(name)
elseif category==Object.Category.STATIC then
target=STATIC:FindByName(name, false)
elseif category==Object.Category.SCENERY then
self:E(self.lid..string.format("ERROR: Scenery target not implemented yet!"))
else
self:E(self.lid..string.format("ERROR: Object category=%d is not implemented yet!", category))
end
end
end
end