diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index a52f4a9da..0a9d28e25 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -540,7 +540,7 @@ do -- COORDINATE local gotscenery=false local function EvaluateZone(ZoneObject) - + BASE:T({ZoneObject}) if ZoneObject then -- Get category of scanned object. @@ -1321,7 +1321,15 @@ do -- COORDINATE self.y=alt return self end - + + --- Set altitude to be at land height (i.e. on the ground!) + -- @param #COORDINATE self + function COORDINATE:SetAtLandheight() + local alt=self:GetLandHeight() + self.y=alt + return self + end + --- Build an air type route point. -- @param #COORDINATE self -- @param #COORDINATE.WaypointAltType AltType The altitude type. @@ -1947,7 +1955,6 @@ do -- COORDINATE -- @param #COORDINATE self -- @param #string name (Optional) Name of the fire to stop it, if not using the same COORDINATE object. function COORDINATE:StopBigSmokeAndFire( name ) - self:F2( { name = name } ) name = name or self.firename trigger.action.effectSmokeStop( name ) end diff --git a/Moose Development/Moose/Functional/Scoring.lua b/Moose Development/Moose/Functional/Scoring.lua index 24085edf3..92fe5f16a 100644 --- a/Moose Development/Moose/Functional/Scoring.lua +++ b/Moose Development/Moose/Functional/Scoring.lua @@ -873,8 +873,10 @@ end function SCORING:OnEventBirth( Event ) if Event.IniUnit then + Event.IniUnit.ThreatLevel, Event.IniUnit.ThreatType = Event.IniUnit:GetThreatLevel() if Event.IniObjectCategory == 1 then local PlayerName = Event.IniUnit:GetPlayerName() + Event.IniUnit.BirthTime = timer.getTime() if PlayerName then self:_AddPlayerFromUnit( Event.IniUnit ) self:SetScoringMenu( Event.IniGroup ) @@ -1005,7 +1007,18 @@ function SCORING:_EventOnHit( Event ) PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT + -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth + if PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() + -- if this fails for some reason, set a good default value + if PlayerHit.ThreatType == nil then + PlayerHit.ThreatLevel = 1 + PlayerHit.ThreatType = "Unknown" + end + else + PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel + PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType + end -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then @@ -1021,19 +1034,20 @@ function SCORING:_EventOnHit( Event ) if InitCoalition then -- A coalition object was hit. if InitCoalition == TargetCoalition then - Player.Penalty = Player.Penalty + 10 - PlayerHit.Penalty = PlayerHit.Penalty + 10 + local Penalty = 10 + Player.Penalty = Player.Penalty + Penalty + PlayerHit.Penalty = PlayerHit.Penalty + Penalty PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 if TargetPlayerName ~= nil then -- It is a player hitting another player ... MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, + "Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) else MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. InitPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. PlayerHit.PenaltyHit .. " times. " .. - "Penalty: -" .. PlayerHit.Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, + "Penalty: -" .. Penalty .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) @@ -1104,7 +1118,18 @@ function SCORING:_EventOnHit( Event ) PlayerHit.PenaltyHit = PlayerHit.PenaltyHit or 0 PlayerHit.TimeStamp = PlayerHit.TimeStamp or 0 PlayerHit.UNIT = PlayerHit.UNIT or TargetUNIT + -- After an instant kill we can't compute the thread level anymore. To fix this we compute at OnEventBirth + if PlayerHit.UNIT.ThreatType == nil then PlayerHit.ThreatLevel, PlayerHit.ThreatType = PlayerHit.UNIT:GetThreatLevel() + -- if this fails for some reason, set a good default value + if PlayerHit.ThreatType == nil then + PlayerHit.ThreatLevel = 1 + PlayerHit.ThreatType = "Unknown" + end + else + PlayerHit.ThreatLevel = PlayerHit.UNIT.ThreatLevel + PlayerHit.ThreatType = PlayerHit.UNIT.ThreatType + end -- Only grant hit scores if there was more than one second between the last hit. if timer.getTime() - PlayerHit.TimeStamp > 1 then @@ -1115,14 +1140,15 @@ function SCORING:_EventOnHit( Event ) if InitCoalition then -- A coalition object was hit, probably a static. if InitCoalition == TargetCoalition then -- TODO: Penalty according scale - Player.Penalty = Player.Penalty + 10 --* self.ScaleDestroyPenalty - PlayerHit.Penalty = PlayerHit.Penalty + 10 --* self.ScaleDestroyPenalty + local Penalty = 10 + Player.Penalty = Player.Penalty + Penalty --* self.ScaleDestroyPenalty + PlayerHit.Penalty = PlayerHit.Penalty + Penalty --* self.ScaleDestroyPenalty PlayerHit.PenaltyHit = PlayerHit.PenaltyHit + 1 * self.ScaleDestroyPenalty MESSAGE :NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit friendly target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - "Penalty: -" .. PlayerHit.Penalty .. " = " .. Player.Score - Player.Penalty, + "Penalty: -" .. Penalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) @@ -1133,7 +1159,7 @@ function SCORING:_EventOnHit( Event ) PlayerHit.Score = PlayerHit.Score + 1 PlayerHit.ScoreHit = PlayerHit.ScoreHit + 1 MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. Event.WeaponPlayerName .. "' hit enemy target " .. TargetUnitCategory .. " ( " .. TargetType .. " ) " .. - "Score: +" .. PlayerHit.Score .. " = " .. Player.Score - Player.Penalty, + "Score: " .. PlayerHit.Score .. ". Score Total:" .. Player.Score - Player.Penalty, MESSAGE.Type.Update ) :ToAllIf( self:IfMessagesHit() and self:IfMessagesToAll() ) :ToCoalitionIf( Event.WeaponCoalition, self:IfMessagesHit() and self:IfMessagesToCoalition() ) @@ -1211,7 +1237,7 @@ function SCORING:_EventOnDeadOrCrash( Event ) local Destroyed = false -- What is the player destroying? - if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 then -- Was there a hit for this unit for this player before registered??? + if Player and Player.Hit and Player.Hit[TargetCategory] and Player.Hit[TargetCategory][TargetUnitName] and Player.Hit[TargetCategory][TargetUnitName].TimeStamp ~= 0 and (TargetUnit.BirthTime == nil or Player.Hit[TargetCategory][TargetUnitName].TimeStamp > TargetUnit.BirthTime) then -- Was there a hit for this unit for this player before registered??? local TargetThreatLevel = Player.Hit[TargetCategory][TargetUnitName].ThreatLevel local TargetThreatType = Player.Hit[TargetCategory][TargetUnitName].ThreatType @@ -1240,13 +1266,13 @@ function SCORING:_EventOnDeadOrCrash( Event ) if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty, + "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) else MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed friendly target " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Penalty: -" .. TargetDestroy.Penalty .. " = " .. Player.Score - Player.Penalty, + "Penalty: -" .. ThreatPenalty .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) @@ -1268,13 +1294,13 @@ function SCORING:_EventOnDeadOrCrash( Event ) TargetDestroy.ScoreDestroy = TargetDestroy.ScoreDestroy + 1 if Player.HitPlayers[TargetPlayerName] then -- A player destroyed another player MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy player '" .. TargetPlayerName .. "' " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty, + "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) else MESSAGE:NewType( self.DisplayMessagePrefix .. "Player '" .. PlayerName .. "' destroyed enemy " .. TargetUnitCategory .. " ( " .. ThreatTypeTarget .. " ) " .. - "Score: +" .. TargetDestroy.Score .. " = " .. Player.Score - Player.Penalty, + "Score: +" .. ThreatScore .. " = " .. Player.Score - Player.Penalty, MESSAGE.Type.Information ) :ToAllIf( self:IfMessagesDestroy() and self:IfMessagesToAll() ) :ToCoalitionIf( InitCoalition, self:IfMessagesDestroy() and self:IfMessagesToCoalition() ) diff --git a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua index 2439029f9..3215b9a12 100644 --- a/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua +++ b/Moose Development/Moose/Functional/ZoneCaptureCoalition.lua @@ -715,7 +715,7 @@ do -- ZONE_CAPTURE_COALITION local UnitHit = EventData.TgtUnit - if UnitHit.ClassName ~= "SCENERY" then + if UnitHit and UnitHit.ClassName ~= "SCENERY" then -- Check if unit is inside the capture zone and that it is of the defending coalition. if UnitHit and UnitHit:IsInZone(self) and UnitHit:GetCoalition()==self.Coalition then diff --git a/Moose Development/Moose/Modules.lua b/Moose Development/Moose/Modules.lua index ce170be0f..46458f046 100644 --- a/Moose Development/Moose/Modules.lua +++ b/Moose Development/Moose/Modules.lua @@ -46,6 +46,7 @@ __Moose.Include( 'Scripts/Moose/Wrapper/Positionable.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Scenery.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Static.lua' ) __Moose.Include( 'Scripts/Moose/Wrapper/Unit.lua' ) +__Moose.Include( 'Scripts/Moose/Wrapper/Weapon.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/Cargo.lua' ) __Moose.Include( 'Scripts/Moose/Cargo/CargoUnit.lua' ) diff --git a/Moose Development/Moose/Ops/AirWing.lua b/Moose Development/Moose/Ops/AirWing.lua index fe0d22de5..4cfbe36a7 100644 --- a/Moose Development/Moose/Ops/AirWing.lua +++ b/Moose Development/Moose/Ops/AirWing.lua @@ -524,7 +524,7 @@ function AIRWING:FetchPayloadFromStock(UnitType, MissionType, Payloads) if a and b then -- I had the case that a or b were nil even though the self.payloads table was looking okay. Very strange! Seems to be solved by pre-selecting valid payloads. local performanceA=self:GetPayloadPeformance(a, MissionType) local performanceB=self:GetPayloadPeformance(b, MissionType) - return (performanceA>performanceB) or (performanceA==performanceB and a.unlimited==true) or (performanceA==performanceB and a.unlimited==true and b.unlimited==true and a.navail>b.navail) + return (performanceA>performanceB) or (performanceA==performanceB and a.unlimited==true and b.unlimited~=true) or (performanceA==performanceB and a.unlimited==true and b.unlimited==true and a.navail>b.navail) elseif not a then self:I(self.lid..string.format("FF ERROR in sortpayloads: a is nil")) return false diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index e4e2fd1ed..db2afa9c0 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -713,8 +713,9 @@ do -- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted. -- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true` -- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds. --- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects --- +-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects. +-- my_ctld.nobuildmenu = false -- if set to true effectively enforces to have engineers build/repair stuff for you. +-- -- ## 2.1 User functions -- -- ### 2.1.1 Adjust or add chopper unit-type capabilities @@ -732,6 +733,7 @@ do -- ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, -- ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, -- ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, +-- ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, -- ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, -- ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, -- ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, @@ -1180,7 +1182,7 @@ CTLD.UnitTypes = { ["SA342Minigun"] = {type="SA342Minigun", crates=false, troops=true, cratelimit = 0, trooplimit = 2, length = 12, cargoweightlimit = 400}, ["UH-1H"] = {type="UH-1H", crates=true, troops=true, cratelimit = 1, trooplimit = 8, length = 15, cargoweightlimit = 700}, ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, - ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, + ["Mi-8MT"] = {type="Mi-8MT", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, ["Ka-50_3"] = {type="Ka-50_3", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, @@ -1194,7 +1196,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.27" +CTLD.version="1.0.30" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1306,6 +1308,7 @@ function CTLD:New(Coalition, Prefixes, Alias) self.Engineers = 0 -- #number use as counter self.EngineersInField = {} -- #table holds #CTLD_ENGINEERING objects self.EngineerSearch = 2000 -- #number search distance for crates to build or repair + self.nobuildmenu = false -- enfore engineer build only? -- setup self.CrateDistance = 35 -- list/load crates in this radius @@ -1773,6 +1776,22 @@ function CTLD:_FindTroopsCargoObject(Name) return nil end +--- (Internal) Find a crates CTLD_CARGO object in stock +-- @param #CTLD self +-- @param #string Name of the object +-- @return #CTLD_CARGO Cargo object, nil if it cannot be found +function CTLD:_FindCratesCargoObject(Name) + self:T(self.lid .. " _FindCratesCargoObject") + local cargo = nil + for _,_cargo in pairs(self.Cargo_Crates)do + local cargo = _cargo -- #CTLD_CARGO + if cargo.Name == Name then + return cargo + end + end + return nil +end + --- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! -- @param #CTLD self -- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object @@ -1798,6 +1817,84 @@ function CTLD:PreloadTroops(Unit,Troopname) return self end +--- (Internal) Pre-load crates into a helo. Do not use standalone! +-- @param #CTLD self +-- @param Wrapper.Group#GROUP Group The group to load into, can be handed as Wrapper.Client#CLIENT object +-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object +-- @param #CTLD_CARGO Cargo The Cargo crate object to load +-- @param #number NumberOfCrates (Optional) Number of crates to be loaded. Default - all necessary to build this object. Might overload the helo! +-- @return #CTLD self +function CTLD:_PreloadCrates(Group, Unit, Cargo, NumberOfCrates) + -- load crate into heli + local group = Group -- Wrapper.Group#GROUP + local unit = Unit -- Wrapper.Unit#UNIT + local unitname = unit:GetName() + -- see if this heli can load crates + local unittype = unit:GetTypeName() + local capabilities = self:_GetUnitCapabilities(Unit) -- #CTLD.UnitCapabilities + local cancrates = capabilities.crates -- #boolean + local cratelimit = capabilities.cratelimit -- #number + if not cancrates then + self:_SendMessage("Sorry this chopper cannot carry crates!", 10, false, Group) + return self + else + -- have we loaded stuff already? + local numberonboard = 0 + local massonboard = 0 + local loaded = {} + if self.Loaded_Cargo[unitname] then + loaded = self.Loaded_Cargo[unitname] -- #CTLD.LoadedCargo + numberonboard = loaded.Cratesloaded or 0 + massonboard = self:_GetUnitCargoMass(Unit) + else + loaded = {} -- #CTLD.LoadedCargo + loaded.Troopsloaded = 0 + loaded.Cratesloaded = 0 + loaded.Cargo = {} + end + local crate = Cargo -- #CTLD_CARGO + local numbercrates = NumberOfCrates or crate:GetCratesNeeded() + for i=1,numbercrates do + loaded.Cratesloaded = loaded.Cratesloaded + 1 + crate:SetHasMoved(true) + crate:SetWasDropped(false) + table.insert(loaded.Cargo, crate) + crate.Positionable = nil + self:_SendMessage(string.format("Crate ID %d for %s loaded!",crate:GetID(),crate:GetName()), 10, false, Group) + --self:__CratesPickedUp(1, Group, Unit, crate) + self.Loaded_Cargo[unitname] = loaded + self:_UpdateUnitCargoMass(Unit) + end + end + return self +end + +--- (User) Pre-load crates into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object +-- @param #string Cratesname The name of the cargo to be loaded. Must be created prior in the CTLD setup! +-- @param #number NumberOfCrates (Optional) Number of crates to be loaded. Default - all necessary to build this object. Might overload the helo! +-- @return #CTLD self +-- @usage +-- local client = UNIT:FindByName("Helo-1-1") +-- if client and client:IsAlive() then +-- myctld:PreloadCrates(client,"Humvee") +-- end +function CTLD:PreloadCrates(Unit,Cratesname,NumberOfCrates) + self:T(self.lid .. " PreloadCrates") + local name = Cratesname or "Unknown" + if Unit and Unit:IsAlive() then + local cargo = self:_FindCratesCargoObject(name) + local group = Unit:GetGroup() + if cargo then + self:_PreloadCrates(group,Unit,cargo,NumberOfCrates) + else + self:E(self.lid.." Crates preload - Cargo Object "..name.." not found!") + end + end + return self +end + --- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group @@ -3323,6 +3420,12 @@ function CTLD:_RefreshF10Menus() self.subcats[entry.Subcategory] = entry.Subcategory end end + for _id,_cargo in pairs(self.Cargo_Statics) do + local entry = _cargo -- #CTLD_CARGO + if not self.subcats[entry.Subcategory] then + self.subcats[entry.Subcategory] = entry.Subcategory + end + end end -- build unit menus @@ -3386,6 +3489,13 @@ function CTLD:_RefreshF10Menus() local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) end + for _,_entry in pairs(self.Cargo_Statics) do + local entry = _entry -- #CTLD_CARGO + local subcat = entry.Subcategory + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,subcatmenus[subcat],self._GetCrates, self, _group, _unit, entry) + end else for _,_entry in pairs(self.Cargo_Crates) do local entry = _entry -- #CTLD_CARGO @@ -3393,17 +3503,21 @@ function CTLD:_RefreshF10Menus() local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) end - end - for _,_entry in pairs(self.Cargo_Statics) do - local entry = _entry -- #CTLD_CARGO - menucount = menucount + 1 - local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) - menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + for _,_entry in pairs(self.Cargo_Statics) do + local entry = _entry -- #CTLD_CARGO + menucount = menucount + 1 + local menutext = string.format("Crate %s (%dkg)",entry.Name,entry.PerCrateMass or 0) + menus[menucount] = MENU_GROUP_COMMAND:New(_group,menutext,cratesmenu,self._GetCrates, self, _group, _unit, entry) + end end listmenu = MENU_GROUP_COMMAND:New(_group,"List crates nearby",topcrates, self._ListCratesNearby, self, _group, _unit) local unloadmenu = MENU_GROUP_COMMAND:New(_group,"Drop crates",topcrates, self._UnloadCrates, self, _group, _unit) - local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) - local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() + if not self.nobuildmenu then + local buildmenu = MENU_GROUP_COMMAND:New(_group,"Build crates",topcrates, self._BuildCrates, self, _group, _unit) + local repairmenu = MENU_GROUP_COMMAND:New(_group,"Repair",topcrates, self._RepairCrates, self, _group, _unit):Refresh() + else + unloadmenu:Refresh() + end end if self:IsHercules(_unit) then local hoverpars = MENU_GROUP_COMMAND:New(_group,"Show flight parameters",topmenu, self._ShowFlightParams, self, _group, _unit):Refresh() @@ -3488,13 +3602,14 @@ end -- @param #string Name Unique name of this type of cargo as set in the mission editor (note: UNIT name!), e.g. "Ammunition-1". -- @param #number Mass Mass in kg of each static in kg, e.g. 100. -- @param #number Stock Number of groups in stock. Nil for unlimited. -function CTLD:AddStaticsCargo(Name,Mass,Stock) +-- @param #string SubCategory Name of sub-category (optional). +function CTLD:AddStaticsCargo(Name,Mass,Stock,SubCategory) self:T(self.lid .. " AddStaticsCargo") self.CargoCounter = self.CargoCounter + 1 local type = CTLD_CARGO.Enum.STATIC local template = STATIC:FindByName(Name,true):GetTypeName() -- Crates are not directly loadable - local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock) + local cargo = CTLD_CARGO:New(self.CargoCounter,Name,template,type,false,false,1,nil,nil,Mass,Stock,SubCategory) table.insert(self.Cargo_Statics,cargo) return self end @@ -5252,7 +5367,7 @@ CTLD_HERCULES = { ClassName = "CTLD_HERCULES", lid = "", Name = "", - Version = "0.0.2", + Version = "0.0.3", } --- Define cargo types. @@ -5570,8 +5685,8 @@ function CTLD_HERCULES:Cargo_SpawnObjects(Cargo_Drop_initiator,Cargo_Drop_Direct if offload_cargo == true or ParatrooperGroupSpawn == true then if ParatrooperGroupSpawn == true then - self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) - self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) + --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 0) + --self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 5) self:Soldier_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country, 10) else self:Cargo_SpawnGroup(Cargo_Drop_initiator,Cargo_Content_position, Cargo_Type_name, CargoHeading, Cargo_Country) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 746fff5e3..df55a88d4 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -104,7 +104,7 @@ PLAYERRECCE = { ClassName = "PLAYERRECCE", verbose = true, lid = nil, - version = "0.0.16", + version = "0.0.17", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -149,7 +149,8 @@ PLAYERRECCE.LaserRelativePos = { ["SA342Mistral"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342Minigun"] = { x = 1.7, y = 1.2, z = 0 }, ["SA342L"] = { x = 1.7, y = 1.2, z = 0 }, - ["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 } + ["Ka-50"] = { x = 6.1, y = -0.85 , z = 0 }, + ["Ka-50_3"] = { x = 6.1, y = -0.85 , z = 0 } } --- @@ -161,6 +162,7 @@ PLAYERRECCE.MaxViewDistance = { ["SA342Minigun"] = 8000, ["SA342L"] = 8000, ["Ka-50"] = 8000, + ["Ka-50_3"] = 8000, } --- @@ -172,6 +174,7 @@ PLAYERRECCE.Cameraheight = { ["SA342Minigun"] = 2.85, ["SA342L"] = 2.85, ["Ka-50"] = 0.5, + ["Ka-50_3"] = 0.5, } --- @@ -182,7 +185,8 @@ PLAYERRECCE.CanLase = { ["SA342Mistral"] = true, ["SA342Minigun"] = false, -- no optics ["SA342L"] = true, - ["Ka-50"] = true, + ["Ka-50"] = true, + ["Ka-50_3"] = true, } --- @@ -337,11 +341,7 @@ function PLAYERRECCE:_GetClockDirection(unit, target) clock = 12+hours clock = UTILS.Round(clock,0) if clock > 12 then clock = clock-12 end - end - --if self.debug then - --local text = string.format("Heading = %d, Angle = %d, Hours= %d, Clock = %d",_heading,Angle,hours,clock) - --self:I(self.lid .. text) - --end + end return clock end @@ -411,7 +411,7 @@ function PLAYERRECCE:_CameraOn(client,playername) if vivihorizontal < -0.7 or vivihorizontal > 0.7 then camera = false end - elseif typename == "Ka-50" then + elseif string.find(typename,"Ka-50") then camera = true end end @@ -653,7 +653,7 @@ function PLAYERRECCE:_GetTargetSet(unit,camera,laser) angle=10 -- Model nod and actual TV view don't compute maxview = self.MaxViewDistance[typename] or 5000 - elseif typename == "Ka-50" and camera then + elseif string.find(typename,"Ka-50") and camera then heading = unit:GetHeading() nod,maxview,camon = 10,1000,true angle = 10 diff --git a/Moose Development/Moose/Wrapper/Weapon.lua b/Moose Development/Moose/Wrapper/Weapon.lua new file mode 100644 index 000000000..b8437d7ca --- /dev/null +++ b/Moose Development/Moose/Wrapper/Weapon.lua @@ -0,0 +1,839 @@ +--- **Wrapper** - Weapon functions. +-- +-- ## Main Features: +-- +-- * Convenient access to DCS API functions +-- * Track weapon and get impact position +-- * Get launcher and target of weapon +-- * Define callback function when weapon impacts +-- * Define callback function when tracking weapon +-- * Mark impact points on F10 map +-- * Put coloured smoke on impact points +-- +-- === +-- +-- ## Example Missions: +-- +-- Demo missions can be found on [github](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/develop/Wrapper%20-%20Weapon). +-- +-- === +-- +-- ### Author: **funkyfranky** +-- +-- === +-- @module Wrapper.Weapon +-- @image Wrapper_Weapon.png + + +--- WEAPON class. +-- @type WEAPON +-- @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 DCS#Weapon weapon The DCS weapon object. +-- @field #string name Name of the weapon object. +-- @field #string typeName Type name of the weapon. +-- @field #number category Weapon category 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB, 4=TORPEDO (Weapon.Category.X). +-- @field #number categoryMissile Missile category 0=AAM, 1=SAM, 2=BM, 3=ANTI_SHIP, 4=CRUISE, 5=OTHER (Weapon.MissileCategory.X). +-- @field #number coalition Coalition ID. +-- @field #number country Country ID. +-- @field DCS#Desc desc Descriptor table. +-- @field DCS#Unit launcher Launcher DCS unit. +-- @field Wrapper.Unit#UNIT launcherUnit Launcher Unit. +-- @field #string launcherName Name of launcher unit. +-- @field #number dtTrack Time step in seconds for tracking scheduler. +-- @field #function impactFunc Callback function for weapon impact. +-- @field #table impactArg Optional arguments for the impact callback function. +-- @field #function trackFunc Callback function when weapon is tracked and alive. +-- @field #table trackArg Optional arguments for the track callback function. +-- @field DCS#Vec3 vec3 Last known 3D position vector of the tracked weapon. +-- @field DCS#Position3 pos3 Last known 3D position and direction vector of the tracked weapon. +-- @field Core.Point#COORDINATE coordinate Coordinate object of the weapon. Can be used in other classes. +-- @field DCS#Vec3 impactVec3 Impact 3D vector. +-- @field Core.Point#COORDINATE impactCoord Impact coordinate. +-- @field #number trackScheduleID Tracking scheduler ID. Can be used to remove/destroy the scheduler function. +-- @field #boolean tracking If `true`, scheduler will keep tracking. Otherwise, function will return nil and stop tracking. +-- @field #boolean impactMark If `true`, the impact point is marked on the F10 map. Requires tracking to be started. +-- @field #boolean impactSmoke If `true`, the impact point is marked by smoke. Requires tracking to be started. +-- @field #number impactSmokeColor Colour of impact point smoke. +-- @field #boolean impactDestroy If `true`, destroy weapon before impact. Requires tracking to be started and sufficiently small time step. +-- @field #number impactDestroyDist Distance in meters to the estimated impact point. If smaller, then weapon is destroyed. +-- @field #number distIP Distance in meters for the intercept point estimation. +-- @field Wrapper.Unit#UNIT target Last known target. +-- @extends Wrapper.Positionable#POSITIONABLE + +--- *In the long run, the sharpest weapon of all is a kind and gentle spirit.* -- Anne Frank +-- +-- === +-- +-- # The WEAPON Concept +-- +-- The WEAPON class offers an easy-to-use wrapper interface to all DCS API functions. +-- +-- Probably, the most striking highlight is that the position of the weapon can be tracked and its impact position can be determined, which is not +-- possible with the native DCS scripting engine functions. +-- +-- **Note** that this wrapper class is different from most others as weapon objects cannot be found with a DCS API function like `getByName()`. +-- They can only be found in DCS events like the "Shot" event, where the weapon object is contained in the event data. +-- +-- # Tracking +-- +-- The status of the weapon can be tracked with the @{#WEAPON.StartTrack} function. This function will try to determin the position of the weapon in (normally) relatively +-- small time steps. The time step can be set via the @{#WEAPON.SetTimeStepTrack} function and is by default set to 0.01 seconds. +-- +-- Once the position cannot be retrieved any more, the weapon has impacted (or was destroyed otherwise) and the last known position is safed as the impact point. +-- The impact point can be accessed with the @{#WEAPON.GetImpactVec3} or @{#WEAPON.GetImpactCoordinate} functions. +-- +-- ## Impact Point Marking +-- +-- You can mark the impact point on the F10 map with @{#WEAPON.SetMarkImpact}. +-- +-- You can also trigger coloured smoke at the impact point via @{#WEAPON.SetSmokeImpact}. +-- +-- ## Callback functions +-- +-- It is possible to define functions that are called during the tracking of the weapon and upon impact, which help you to customize further actions. +-- +-- ### Callback on Impact +-- +-- The function called on impact can be set with @{#WEAPON.SetFuncImpact} +-- +-- ### Callback when Tracking +-- +-- The function called each time the weapon status is tracked can be set with @{#WEAPON.SetFuncTrack} +-- +-- # Target +-- +-- If the weapon has a specific target, you can get it with the @{#WEAPON.GetTarget} function. Note that the object, which is returned can vary. Normally, it is a UNIT +-- but it could also be a STATIC object. +-- +-- Also note that the weapon does not always have a target, it can loose a target and re-aquire it and the target might change to another unit. +-- +-- You can get the target name with the @{#WEAPON.GetTargetName} function. +-- +-- The distance to the target is returned by the @{#WEAPON.GetTargetDistance} function. +-- +-- # Category +-- +-- The category (bomb, rocket, missile, shell, torpedo) of the weapon can be retrieved with the @{#WEAPON.GetCategory} function. +-- +-- You can check if the weapon is a +-- +-- * bomb with @{#WEAPON.IsBomb} +-- * rocket with @{#WEAPON.IsRocket} +-- * missile with @{#WEAPON.IsMissile} +-- * shell with @{#WEAPON.IsShell} +-- * torpedo with @{#WEAPON.IsTorpedo} +-- +-- # Parameters +-- +-- You can get various parameters of the weapon, *e.g.* +-- +-- * position: @{#WEAPON.GetVec3}, @{#WEAPON.GetVec2 }, @{#WEAPON.GetCoordinate} +-- * speed: @{#WEAPON.GetSpeed} +-- * coalition: @{#WEAPON.GetCoalition} +-- * country: @{#WEAPON.GetCountry} +-- +-- # Dependencies +-- +-- This class is used (at least) in the MOOSE classes: +-- +-- * RANGE (to determine the impact points of bombs and missiles) +-- * ARTY (to destroy and replace shells with smoke or illumination) +-- * FOX (to destroy the missile before it hits the target) +-- +-- @field #WEAPON +WEAPON = { + ClassName = "WEAPON", + verbose = 0, +} + + +--- WEAPON class version. +-- @field #string version +WEAPON.version="0.1.0" + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- TODO list +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +-- TODO: A lot... +-- TODO: Destroy before impact. +-- TODO: Monitor target. + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Constructor +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Create a new WEAPON object from the DCS weapon object. +-- @param #WEAPON self +-- @param DCS#Weapon WeaponObject The DCS weapon object. +-- @return #WEAPON self +function WEAPON:New(WeaponObject) + + -- Nil check on object. + if WeaponObject==nil then + env.error("ERROR: Weapon object does NOT exist") + return nil + end + + -- Inherit everything from FSM class. + local self=BASE:Inherit(self, POSITIONABLE:New("Weapon")) -- #WEAPON + + -- Set DCS weapon object. + self.weapon=WeaponObject + + -- Descriptors containing a lot of info. + self.desc=WeaponObject:getDesc() + + -- This gives the object category which is always Object.Category.WEAPON! + --self.category=WeaponObject:getCategory() + + -- Weapon category: 0=SHELL, 1=MISSILE, 2=ROCKET, 3=BOMB (Weapon.Category.X) + self.category = self.desc.category + + if self:IsMissile() and self.desc.missileCategory then + self.categoryMissile=self.desc.missileCategory + end + + -- Get type name. + self.typeName=WeaponObject:getTypeName() or "Unknown Type" + + -- Get name of object. Usually a number like "1234567". + self.name=WeaponObject:getName() + + -- Get coaliton of weapon. + self.coalition=WeaponObject:getCoalition() + + -- Get country of weapon. + self.country=WeaponObject:getCountry() + + -- Get DCS unit of the launcher. + self.launcher=WeaponObject:getLauncher() + + -- Get launcher of weapon. + self.launcherName="Unknown Launcher" + if self.launcher then + self.launcherName=self.launcher:getName() + self.launcherUnit=UNIT:Find(self.launcher) + end + + -- Init the coordinate of the weapon from that of the launcher. + self.coordinate=COORDINATE:NewFromVec3(self.launcher:getPoint()) + + -- Set log ID. + self.lid=string.format("[%s] %s | ", self.typeName, self.name) + + -- Set default parameters + self:SetTimeStepTrack() + self:SetDistanceInterceptPoint() + + -- Debug info. + local text=string.format("Weapon v%s\nName=%s, TypeName=%s, Category=%s, Coalition=%d, Country=%d, Launcher=%s", + self.version, self.name, self.typeName, self.category, self.coalition, self.country, self.launcherName) + self:T(self.lid..text) + + -- Descriptors. + self:T2(self.desc) + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- User API Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Set verbosity level. +-- @param #WEAPON self +-- @param #number VerbosityLevel Level of output (higher=more). Default 0. +-- @return #WEAPON self +function WEAPON:SetVerbosity(VerbosityLevel) + self.verbose=VerbosityLevel or 0 + return self +end + +--- Set track position time step. +-- @param #WEAPON self +-- @param #number TimeStep Time step in seconds when the position is updated. Default 0.01 sec ==> 100 evaluations per second. +-- @return #WEAPON self +function WEAPON:SetTimeStepTrack(TimeStep) + self.dtTrack=TimeStep or 0.01 + return self +end + +--- Set distance of intercept point for estimated impact point. +-- If the weapon cannot be tracked any more, the intercept point from its last known position and direction is used to get +-- a better approximation of the impact point. Can be useful when using longer time steps in the tracking and still achieve +-- a good result on the impact point. +-- It uses the DCS function [getIP](https://wiki.hoggitworld.com/view/DCS_func_getIP). +-- @param #WEAPON self +-- @param #number Distance Distance in meters. Default is 50 m. Set to 0 to deactivate. +-- @return #WEAPON self +function WEAPON:SetDistanceInterceptPoint(Distance) + self.distIP=Distance or 50 + return self +end + +--- Mark impact point on the F10 map. This requires that the tracking has been started. +-- @param #WEAPON self +-- @param #boolean Switch If `true` or nil, impact is marked. +-- @return #WEAPON self +function WEAPON:SetMarkImpact(Switch) + + if Switch==false then + self.impactMark=false + else + self.impactMark=true + end + + return self +end + + +--- Put smoke on impact point. This requires that the tracking has been started. +-- @param #WEAPON self +-- @param #boolean Switch If `true` or nil, impact is smoked. +-- @param #number SmokeColor Color of smoke. Default is `SMOKECOLOR.Red`. +-- @return #WEAPON self +function WEAPON:SetSmokeImpact(Switch, SmokeColor) + + if Switch==false then + self.impactSmoke=false + else + self.impactSmoke=true + end + + self.impactSmokeColor=SmokeColor or SMOKECOLOR.Red + + return self +end + +--- Set callback function when weapon is tracked and still alive. The first argument will be the WEAPON object. +-- Note that this can be called many times per second. So be careful for performance reasons. +-- @param #WEAPON self +-- @param #function FuncTrack Function called during tracking. +-- @param ... Optional function arguments. +-- @return #WEAPON self +function WEAPON:SetFuncTrack(FuncTrack, ...) + self.trackFunc=FuncTrack + self.trackArg=arg or {} + return self +end + +--- Set callback function when weapon impacted or was destroyed otherwise, *i.e.* cannot be tracked any more. +-- @param #WEAPON self +-- @param #function FuncImpact Function called once the weapon impacted. +-- @param ... Optional function arguments. +-- @return #WEAPON self +-- +-- @usage +-- -- Function called on impact. +-- local function OnImpact(Weapon) +-- Weapon:GetImpactCoordinate():MarkToAll("Impact Coordinate of weapon") +-- end +-- +-- -- Set which function to call. +-- myweapon:SetFuncImpact(OnImpact) +-- +-- -- Start tracking. +-- myweapon:Track() +-- +function WEAPON:SetFuncImpact(FuncImpact, ...) + self.impactFunc=FuncImpact + self.impactArg=arg or {} + return self +end + + +--- Get the unit that launched the weapon. +-- @param #WEAPON self +-- @return Wrapper.Unit#UNIT Laucher unit. +function WEAPON:GetLauncher() + return self.launcherUnit +end + +--- Get the target, which the weapon is guiding to. +-- @param #WEAPON self +-- @return Wrapper.Object#OBJECT The target object, which can be a UNIT or STATIC object. +function WEAPON:GetTarget() + + local target=nil --Wrapper.Object#OBJECT + + if self.weapon then + + -- Get the DCS target object, which can be a Unit, Weapon, Static, Scenery, Airbase. + local object=self.weapon:getTarget() + + if object then + + -- Get object category. + local category=object:getCategory() + + --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)) + end + + end + end + + return target +end + +--- Get the distance to the current target the weapon is guiding to. +-- @param #WEAPON self +-- @param #function ConversionFunction (Optional) Conversion function from meters to desired unit, *e.g.* `UTILS.MpsToKmph`. +-- @return #number Distance from weapon to target in meters. +function WEAPON:GetTargetDistance(ConversionFunction) + + -- Get the target of the weapon. + local target=self:GetTarget() --Wrapper.Unit#UNIT + + local distance=nil + if target then + + -- Current position of target. + local tv3=target:GetVec3() + + -- Current position of weapon. + local wv3=self:GetVec3() + + if tv3 and wv3 then + distance=UTILS.VecDist3D(tv3, wv3) + + if ConversionFunction then + distance=ConversionFunction(distance) + end + + end + + end + + return distance +end + + +--- Get name the current target the weapon is guiding to. +-- @param #WEAPON self +-- @return #string Name of the target or "None" if no target. +function WEAPON:GetTargetName() + + -- Get the target of the weapon. + local target=self:GetTarget() --Wrapper.Unit#UNIT + + local name="None" + if target then + name=target:GetName() + end + + return name +end + +--- Get velocity vector of weapon. +-- @param #WEAPON self +-- @return DCS#Vec3 Velocity vector with x, y and z components in meters/second. +function WEAPON:GetVelocityVec3() + local Vvec3=nil + if self.weapon then + Vvec3=self.weapon:getVelocity() + end + return Vvec3 +end + +--- Get speed of weapon. +-- @param #WEAPON self +-- @param #function ConversionFunction (Optional) Conversion function from m/s to desired unit, *e.g.* `UTILS.MpsToKmph`. +-- @return #number Speed in meters per second. +function WEAPON:GetSpeed(ConversionFunction) + + local speed=nil + + if self.weapon then + + local v=self:GetVelocityVec3() + + speed=UTILS.VecNorm(v) + + if ConversionFunction then + speed=ConversionFunction(speed) + end + + end + + return speed +end + +--- Get the current 3D position vector. +-- @param #WEAPON self +-- @return DCS#Vec3 Current position vector in 3D. +function WEAPON:GetVec3() + + local vec3=nil + if self.weapon then + vec3=self.weapon:getPoint() + end + + return vec3 +end + + +--- Get the current 2D position vector. +-- @param #WEAPON self +-- @return DCS#Vec2 Current position vector in 2D. +function WEAPON:GetVec2() + + local vec3=self:GetVec3() + + if vec3 then + + local vec2={x=vec3.x, y=vec3.z} + + return vec2 + end + + return nil +end + +--- Get type name. +-- @param #WEAPON self +-- @return #string The type name. +function WEAPON:GetTypeName() + return self.typeName +end + +--- Get coalition. +-- @param #WEAPON self +-- @return #number Coalition ID. +function WEAPON:GetCoalition() + return self.coalition +end + +--- Get country. +-- @param #WEAPON self +-- @return #number Country ID. +function WEAPON:GetCountry() + return self.country +end + +--- Get DCS object. +-- @param #WEAPON self +-- @return DCS#Weapon The weapon object. +function WEAPON:GetDCSObject() + -- This polymorphic function is used in Wrapper.Identifiable#IDENTIFIABLE + return self.weapon +end + +--- Get the impact position vector. Note that this might not exist if the weapon has not impacted yet! +-- @param #WEAPON self +-- @return DCS#Vec3 Impact position vector (if any). +function WEAPON:GetImpactVec3() + return self.impactVec3 +end + +--- Get the impact coordinate. Note that this might not exist if the weapon has not impacted yet! +-- @param #WEAPON self +-- @return Core.Point#COORDINATE Impact coordinate (if any). +function WEAPON:GetImpactCoordinate() + return self.impactCoord +end + +--- Check if weapon is in the air. Obviously not really useful for torpedos. Well, then again, this is DCS... +-- @param #WEAPON self +-- @return #boolean If `true`, weapon is in the air and `false` if not. Returns `nil` if weapon object itself is `nil`. +function WEAPON:InAir() + local inAir=nil + if self.weapon then + inAir=self.weapon:inAir() + end + return inAir +end + + +--- Check if weapon object (still) exists. +-- @param #WEAPON self +-- @return #boolean If `true`, the weapon object still exists and `false` otherwise. Returns `nil` if weapon object itself is `nil`. +function WEAPON:IsExist() + local isExist=nil + if self.weapon then + isExist=self.weapon:isExist() + end + return isExist +end + + +--- Check if weapon is a bomb. +-- @param #WEAPON self +-- @return #boolean If `true`, is a bomb. +function WEAPON:IsBomb() + return self.category==Weapon.Category.BOMB +end + +--- Check if weapon is a missile. +-- @param #WEAPON self +-- @return #boolean If `true`, is a missile. +function WEAPON:IsMissile() + return self.category==Weapon.Category.MISSILE +end + +--- Check if weapon is a rocket. +-- @param #WEAPON self +-- @return #boolean If `true`, is a missile. +function WEAPON:IsRocket() + return self.category==Weapon.Category.ROCKET +end + +--- Check if weapon is a shell. +-- @param #WEAPON self +-- @return #boolean If `true`, is a shell. +function WEAPON:IsShell() + return self.category==Weapon.Category.SHELL +end + +--- Check if weapon is a torpedo. +-- @param #WEAPON self +-- @return #boolean If `true`, is a torpedo. +function WEAPON:IsTorpedo() + return self.category==Weapon.Category.TORPEDO +end + + +--- Destroy the weapon object. +-- @param #WEAPON self +-- @param #number Delay Delay before destroy in seconds. +-- @return #WEAPON self +function WEAPON:Destroy(Delay) + + if Delay and Delay>0 then + self:ScheduleOnce(Delay, WEAPON.Destroy, self, 0) + else + if self.weapon then + self:T(self.lid.."Destroying Weapon NOW!") + self:StopTrack() + self.weapon:destroy() + end + end + + return self +end + +--- Start tracking the weapon until it impacts or is destroyed otherwise. +-- The position of the weapon is monitored in small time steps. Once the position cannot be determined anymore, the monitoring is stopped and the last known position is +-- the (approximate) impact point. Of course, the smaller the time step, the better the position can be determined. However, this can hit the performance as many +-- calculations per second need to be carried out. +-- @param #WEAPON self +-- @param #number Delay Delay in seconds before the tracking starts. Default 0.001 sec. +-- @return #WEAPON self +function WEAPON:StartTrack(Delay) + + -- Set delay before start. + Delay=math.max(Delay or 0.001, 0.001) + + -- Debug info. + self:T(self.lid..string.format("Start tracking weapon in %.4f sec", Delay)) + + -- Weapon is not yet "alife" just yet. Start timer in 0.001 seconds. + self.trackScheduleID=timer.scheduleFunction(WEAPON._TrackWeapon, self, timer.getTime() + Delay) + + return self +end + + +--- Stop tracking the weapon by removing the scheduler function. +-- @param #WEAPON self +-- @param #number Delay (Optional) Delay in seconds before the tracking is stopped. +-- @return #WEAPON self +function WEAPON:StopTrack(Delay) + + if Delay and Delay>0 then + -- Delayed call. + self:ScheduleOnce(Delay, WEAPON.StopTrack, self, 0) + else + + if self.trackScheduleID then + + timer.removeFunction(self.trackScheduleID) + + end + + end + + return self +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Track weapon until impact. +-- @param #WEAPON self +-- @param DCS#Time time Time in seconds. +-- @return #number Time when called next or nil if not called again. +function WEAPON:_TrackWeapon(time) + + -- Debug info. + if self.verbose>=20 then + self:I(self.lid..string.format("Tracking at T=%.5f", time)) + end + + -- Protected call to get the weapon position. If the position cannot be determined any more, the weapon has impacted and status is nil. + local status, pos3= pcall( + function() + local point=self.weapon:getPosition() + return point + end + ) + + if status then + + ------------------------------- + -- Weapon is still in exists -- + ------------------------------- + + -- Update last known position. + self.pos3 = pos3 + + -- Update last known vec3. + self.vec3 = UTILS.DeepCopy(self.pos3.p) + + -- Update coordinate. + self.coordinate:UpdateFromVec3(self.vec3) + + -- Keep on tracking by returning the next time below. + self.tracking=true + + -- Callback function. + if self.trackFunc then + self.trackFunc(self, unpack(self.trackArg)) + end + + -- Verbose output. + if self.verbose>=5 then + + -- Get vec2 of current position. + local vec2={x=self.vec3.x, y=self.vec3.z} + + -- Land hight. + local height=land.getHeight(vec2) + + -- Current height above ground level. + local agl=self.vec3.y-height + + -- Estimated IP (if any) + local ip=self:_GetIP(self.distIP) + + -- Distance between positon and estimated impact. + local d=0 + if ip then + d=UTILS.VecDist3D(self.vec3, ip) + end + + -- Output. + self:I(self.lid..string.format("T=%.3f: Height=%.3f m AGL=%.3f m, dIP=%.3f", time, height, agl, d)) + + end + + else + + --------------------------- + -- Weapon does NOT exist -- + --------------------------- + + -- Get intercept point from position (p) and direction (x) in 50 meters. + local ip = self:_GetIP(self.distIP) + + if self.verbose>=10 and ip then + + -- Output. + self:I(self.lid.."Got intercept point!") + + -- Coordinate of the impact point. + local coord=COORDINATE:NewFromVec3(ip) + + -- Mark coordinate. + coord:MarkToAll("Intercept point") + coord:SmokeBlue() + + -- Distance to last known pos. + local d=UTILS.VecDist3D(ip, self.vec3) + + -- Output. + self:I(self.lid..string.format("FF d(ip, vec3)=%.3f meters", d)) + + end + + -- Safe impact vec3. + self.impactVec3=ip or self.vec3 + + -- Safe impact coordinate. + self.impactCoord=COORDINATE:NewFromVec3(self.vec3) + + -- Mark impact point on F10 map. + if self.impactMark then + self.impactCoord:MarkToAll(string.format("Impact point of weapon %s\ntype=%s\nlauncher=%s", self.name, self.typeName, self.launcherName)) + end + + -- Smoke on impact point. + if self.impactSmoke then + self.impactCoord:Smoke(self.impactSmokeColor) + end + + -- Call callback function. + if self.impactFunc then + self.impactFunc(self, unpack(self.impactArg or {})) + end + + -- Stop tracking by returning nil below. + self.tracking=false + + end + + -- Return next time the function is called or nil to stop the scheduler. + if self.tracking then + if self.dtTrack and self.dtTrack>0.001 then + return time+self.dtTrack + else + return nil + end + end + + return nil +end + +--- Compute estimated intercept/impact point (IP) based on last known position and direction. +-- @param #WEAPON self +-- @param #number Distance Distance in meters. Default 50 m. +-- @return DCS#Vec3 Estimated intercept/impact point. Can also return `nil`, if no IP can be determined. +function WEAPON:_GetIP(Distance) + + Distance=Distance or 50 + + local ip=nil --DCS#Vec3 + + if Distance>0 and self.pos3 then + + -- Get intercept point from position (p) and direction (x) in 20 meters. + ip = land.getIP(self.pos3.p, self.pos3.x, Distance or 20) --DCS#Vec3 + + end + + return ip +end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- diff --git a/Moose Setup/Moose.files b/Moose Setup/Moose.files index ae26fe81f..6cd862e94 100644 --- a/Moose Setup/Moose.files +++ b/Moose Setup/Moose.files @@ -43,6 +43,7 @@ Wrapper/Static.lua Wrapper/Airbase.lua Wrapper/Scenery.lua Wrapper/Marker.lua +Wrapper/Weapon.lua Cargo/Cargo.lua Cargo/CargoUnit.lua