From 7c8f212b03f828df2f095283fb0cfd5ec6028b01 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 25 Nov 2023 18:44:21 +0100 Subject: [PATCH 01/21] -- noise --- Moose Development/Moose/Core/Spawn.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 6ae5e1bc1..e562bfbf5 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3321,7 +3321,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 SpawnTemplate.units[UnitID].callsign[2] = UnitID SpawnTemplate.units[UnitID].callsign[3] = "1" SpawnTemplate.units[UnitID].callsign["name"] = tostring(callsignname)..tostring(UnitID).."1" - UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].callsign,1) + -- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].callsign,1) end else -- Ruskis @@ -3335,7 +3335,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 local Callsign = SpawnTemplate.units[UnitID].callsign if Callsign then if type( Callsign ) ~= "number" then -- blue callsign - UTILS.PrintTableToLog(Callsign,1) + --UTILS.PrintTableToLog(Callsign,1) Callsign[2] = ((SpawnIndex - 1) % 10) + 1 local CallsignName = SpawnTemplate.units[UnitID].callsign["name"] -- #string CallsignName = string.match(CallsignName,"^(%a+)") -- 2.8 - only the part w/o numbers @@ -3390,7 +3390,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 end SpawnTemplate.units[UnitID].AddPropAircraft.VoiceCallsignLabel = label end - UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].AddPropAircraft,1) + -- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].AddPropAircraft,1) -- FlightLead if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.settings then SpawnTemplate.units[UnitID].datalinks.Link16.settings.flightLead = UnitID == 1 and true or false @@ -3399,7 +3399,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.SADL and SpawnTemplate.units[UnitID].datalinks.SADL.settings then SpawnTemplate.units[UnitID].datalinks.SADL.settings.flightLead = UnitID == 1 and true or false end - UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].datalinks,1) + --UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].datalinks,1) end end From 641707f37b670ed53a20f02ea227d8f2c9f80fae Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 26 Nov 2023 16:59:44 +0100 Subject: [PATCH 02/21] #UNIT * Added `GetSTN()` to obtain Link16 info from a unit --- Moose Development/Moose/Wrapper/Unit.lua | 33 ++++++++++++++++++++++++ 1 file changed, 33 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 7ad0998ac..0b3e3300f 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -1659,3 +1659,36 @@ function UNIT:GetSkill() local skill = _DATABASE.Templates.Units[name].Template.skill or "Random" return skill end + +--- Get Link16 STN or SADL TN and other datalink info from Unit, if any. +-- @param #UNIT self +-- @return #string STN STN or TN Octal as string, or nil if not set/capable. +-- @return #string VCL Voice Callsign Label or nil if not set/capable. +-- @return #string VCN Voice Callsign Number or nil if not set/capable. +-- @return #string Lead If true, unit is Flight Lead, else false or nil. +function UNIT:GetSTN() + self:F2(self.UnitName) + local STN = nil -- STN/TN + local VCL = nil -- VoiceCallsignLabel + local VCN = nil -- VoiceCallsignNumber + local FGL = false -- FlightGroupLeader + local template = self:GetTemplate() + if template.AddPropAircraft then + if template.AddPropAircraft.STN_L16 then + STN = template.AddPropAircraft.STN_L16 + elseif template.AddPropAircraft.SADL_TN then + STN = template.AddPropAircraft.SADL_TN + end + VCN = template.AddPropAircraft.VoiceCallsignNumber + VCL = template.AddPropAircraft.VoiceCallsignLabel + end + if template.datalinks and template.datalinks.Link16 and template.datalinks.Link16.settings then + FGL = template.datalinks.Link16.settings.flightLead + end + -- A10CII + if template.datalinks and template.datalinks.SADL and template.datalinks.SADL.settings then + FGL = template.datalinks.SADL.settings.flightLead + end + + return STN, VCL, VCN, FGL +end From c489a881061eef407eec48643f691aefa8a4beb5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 27 Nov 2023 16:49:06 +0100 Subject: [PATCH 03/21] #GROUP * Get Link16 S/TN data from a group --- Moose Development/Moose/Wrapper/Group.lua | 36 ++++++++++++++++++++++- 1 file changed, 35 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index f3d8ccc53..9ee2d2c21 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -2922,7 +2922,7 @@ function GROUP:GetCustomCallSign(ShortCallsign,Keepnumber,CallsignTranslations) return callsign end ---- +--- Set a GROUP to act as recovery tanker -- @param #GROUP self -- @param Wrapper.Group#GROUP CarrierGroup. -- @param #number Speed Speed in knots. @@ -2948,3 +2948,37 @@ function GROUP:SetAsRecoveryTanker(CarrierGroup,Speed,ToKIAS,Altitude,Delay,Last return self end + +--- Get a list of Link16 S/TN data from a GROUP. Can (as of Nov 2023) be obtained from F-18, F-16, F-15E (not the user flyable one) and A-10C-II groups. +-- @param #GROUP self +-- @return #table Table of data entries, indexed by unit name, each entry is a table containing STN, VCL (voice call label), VCN (voice call number), and Lead (#boolean, if true it's the flight lead) +-- @return #string Report Formatted report of all data +function GROUP:GetGroupSTN() + local tSTN = {} -- table + local units = self:GetUnits() + local gname = self:GetName() + gname = string.gsub(gname,"(#%d+)$","") + local report = REPORT:New() + report:Add("Link16 S/TN Report") + report:Add("Group: "..gname) + report:Add("==================") + for _,_unit in pairs(units) do + local unit = _unit -- Wrapper.Unit#UNIT + if unit and unit:IsAlive() then + local STN, VCL, VCN, Lead = unit:GetSTN() + local name = unit:GetName() + tSTN[name] = { + STN=STN, + VCL=VCL, + VCN=VCN, + Lead=Lead, + } + local lead = Lead == true and "(*)" or "" + report:Add(string.format("| %s%s %s %s",tostring(VCL),tostring(VCN),tostring(STN),lead)) + end + end + report:Add("==================") + local text = report:Text() + return tSTN,text +end + From 3c0e977584dd2d37a15539b8f7c3a90a1f68f7e2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 28 Nov 2023 10:37:45 +0100 Subject: [PATCH 04/21] #PLAYERRECCE * Bug fix for clock view calculation * Ensure lasing is switched off when using the menu * Create SPOT more often --- Moose Development/Moose/Ops/PlayerRecce.lua | 49 +++++++++++++++------ 1 file changed, 35 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index c8cad0bdd..70853e14b 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.21", + version = "0.0.22", ViewZone = {}, ViewZoneVisual = {}, ViewZoneLaser = {}, @@ -469,8 +469,10 @@ function PLAYERRECCE:_GetClockDirection(unit, target) local _playerPosition = unit:GetCoordinate() -- get position of helicopter local _targetpostions = target:GetCoordinate() -- get position of downed pilot local _heading = unit:GetHeading() -- heading + --self:I("Heading = ".._heading) local DirectionVec3 = _playerPosition:GetDirectionVec3( _targetpostions ) local Angle = _playerPosition:GetAngleDegrees( DirectionVec3 ) + --self:I("Angle = "..Angle) local clock = 12 local hours = 0 if _heading and Angle then @@ -478,10 +480,13 @@ function PLAYERRECCE:_GetClockDirection(unit, target) --if angle == 0 then angle = 360 end clock = _heading-Angle hours = (clock/30)*-1 + --self:I("hours = "..hours) clock = 12+hours clock = UTILS.Round(clock,0) if clock > 12 then clock = clock-12 end - end + if clock == 0 then clock = 12 end + end + --self:I("Clock ="..clock) return clock end @@ -912,32 +917,41 @@ function PLAYERRECCE:_LaseTarget(client,targetset) else laser = self.LaserSpots[playername] end + -- old target if self.LaserTarget[playername] then -- still looking at target? local target=self.LaserTarget[playername] -- Ops.Target#TARGET local oldtarget = target:GetObject() --or laser.Target - --self:I("Targetstate: "..target:GetState()) - --self:I("Laser State: "..tostring(laser:IsLasing())) - if not oldtarget or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then + self:T("Targetstate: "..target:GetState()) + self:T("Laser State: "..tostring(laser:IsLasing())) + if (not oldtarget) or targetset:IsNotInSet(oldtarget) or target:IsDead() or target:IsDestroyed() then -- lost LOS or dead laser:LaseOff() if target:IsDead() or target:IsDestroyed() or target:GetLife() < 2 then self:__Shack(-1,client,oldtarget) - self.LaserTarget[playername] = nil + --self.LaserTarget[playername] = nil else self:__TargetLOSLost(-1,client,oldtarget) - self.LaserTarget[playername] = nil + --self.LaserTarget[playername] = nil end - end - if oldtarget and (not laser:IsLasing()) then - --self:I("Switching laser back on ..") + self.LaserTarget[playername] = nil + oldtarget = nil + self.LaserSpots[playername] = nil + elseif oldtarget and laser and (not laser:IsLasing()) then + --laser:LaseOff() + self:T("Switching laser back on ..") local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688 local lasingtime = self.lasingtime or 60 --local targettype = target:GetTypeName() laser:LaseOn(oldtarget,lasercode,lasingtime) --self:__TargetLasing(-1,client,oldtarget,lasercode,lasingtime) + else + -- we should not be here... + self:T("Target alive and laser is on!") + --self.LaserSpots[playername] = nil end - elseif not laser:IsLasing() and target then + -- new target + elseif (not laser:IsLasing()) and target then local relativecam = self.LaserRelativePos[client:GetTypeName()] laser:SetRelativeStartPosition(relativecam) local lasercode = self.UnitLaserCodes[playername] or laser.LaserCode or 1688 @@ -945,7 +959,7 @@ function PLAYERRECCE:_LaseTarget(client,targetset) --local targettype = target:GetTypeName() laser:LaseOn(target,lasercode,lasingtime) self.LaserTarget[playername] = TARGET:New(target) - self.LaserTarget[playername].TStatus = 9 + --self.LaserTarget[playername].TStatus = 9 self:__TargetLasing(-1,client,target,lasercode,lasingtime) end return self @@ -1027,6 +1041,13 @@ function PLAYERRECCE:_SwitchLasing(client,group,playername) MESSAGE:New("Lasing is now ON",10,self.Name or "FACA"):ToClient(client) else self.AutoLase[playername] = false + if self.LaserSpots[playername] then + local laser = self.LaserSpots[playername] -- Core.Spot#SPOT + if laser:IsLasing() then + laser:LaseOff() + end + self.LaserSpots[playername] = nil + end MESSAGE:New("Lasing is now OFF",10,self.Name or "FACA"):ToClient(client) end if self.ClientMenus[playername] then @@ -1681,7 +1702,7 @@ function PLAYERRECCE:onafterRecceOnStation(From, Event, To, Client, Playername) local text2tts = string.format("All stations, FACA %s on station at %s!",callsign, coordtext) text2tts = self:_GetTextForSpeech(text2tts) if self.debug then - self:I(text2.."\n"..text2tts) + self:T(text2.."\n"..text2tts) end if self.UseSRS then local grp = Client:GetGroup() @@ -1720,7 +1741,7 @@ function PLAYERRECCE:onafterRecceOffStation(From, Event, To, Client, Playername) local texttts = string.format("All stations, FACA %s leaving station at %s, good bye!",callsign, coordtext) texttts = self:_GetTextForSpeech(texttts) if self.debug then - self:I(text.."\n"..texttts) + self:T(text.."\n"..texttts) end local text1 = "Going home!" if self.UseSRS then From 68b97773fe8e489adb79d72214d54dd2ca6cb245 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 29 Nov 2023 16:42:30 +0100 Subject: [PATCH 05/21] #OPSZONE * Added option to set own pikey-ish zone colors --- Moose Development/Moose/Ops/OpsZone.lua | 21 ++++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/OpsZone.lua b/Moose Development/Moose/Ops/OpsZone.lua index 01b7a3b19..780b389c0 100644 --- a/Moose Development/Moose/Ops/OpsZone.lua +++ b/Moose Development/Moose/Ops/OpsZone.lua @@ -1483,11 +1483,11 @@ function OPSZONE:_GetZoneColor() local color={0,0,0} if self.ownerCurrent==coalition.side.NEUTRAL then - color={1, 1, 1} + color=self.ZoneOwnerNeutral or {1, 1, 1} elseif self.ownerCurrent==coalition.side.BLUE then - color={0, 0, 1} + color=self.ZoneOwnerBlue or {0, 0, 1} elseif self.ownerCurrent==coalition.side.RED then - color={1, 0, 0} + color=self.ZoneOwnerRed or {1, 0, 0} else end @@ -1495,6 +1495,21 @@ function OPSZONE:_GetZoneColor() return color end +--- Set custom RGB color of zone depending on current owner. +-- @param #OPSZONE self +-- @param #table Neutral Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {1,0,0} for red. +-- @param #table Blue Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {0,1,0} for green. +-- @param #table Red Color is a table of RGB values 0..1 for Red, Green, and Blue respectively, e.g. {0,0,1} for blue. +-- @return #OPSZONE self +function OPSZONE:SetZoneColor(Neutral, Blue, Red) + + self.ZoneOwnerNeutral = Neutral or {1, 1, 1} + self.ZoneOwnerBlue = Blue or {0, 0, 1} + self.ZoneOwnerRed = Red or {1, 0, 0} + + return self +end + --- Update marker on the F10 map. -- @param #OPSZONE self function OPSZONE:_UpdateMarker() From 1dc31cc85283a4f685c36acd939ef305cfc60232 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 29 Nov 2023 18:01:37 +0100 Subject: [PATCH 06/21] spawn --- Moose Development/Moose/Core/Spawn.lua | 19 ++++++++++++++++++- 1 file changed, 18 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 3e33aea63..f80dbd41b 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3324,7 +3324,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 -- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].callsign,1) end else - -- Ruskis + -- Russkis for UnitID = 1, #SpawnTemplate.units do SpawnTemplate.units[UnitID].callsign = math.random(1,999) end @@ -3402,8 +3402,25 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 -- UTILS.PrintTableToLog(SpawnTemplate.units[UnitID].datalinks,1) end end + -- Link16 team members + for UnitID = 1, #SpawnTemplate.units do + if SpawnTemplate.units[UnitID].datalinks and SpawnTemplate.units[UnitID].datalinks.Link16 and SpawnTemplate.units[UnitID].datalinks.Link16.network then + local team = {} + local isF16 = string.find(SpawnTemplate.units[UnitID].type,"F-16",1,true) and true or false + for ID = 1, #SpawnTemplate.units do + local member = {} + member.missionUnitId = ID + if isF16 then + member.TDOA = true + end + table.insert(team,member) + end + SpawnTemplate.units[UnitID].datalinks.Link16.network.teamMembers = team + end + end self:T3( { "Template:", SpawnTemplate } ) + --UTILS.PrintTableToLog(SpawnTemplate,1) return SpawnTemplate end From 4b8d120f20b4b695a32595b6aa26f03753a3f1e1 Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 30 Nov 2023 23:29:29 +0100 Subject: [PATCH 07/21] Update Warehouse.lua - Added check that DCS warehouse has enough air assets for selfpropelled assets --- .../Moose/Functional/Warehouse.lua | 20 ++++++++++++++++++- 1 file changed, 19 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index beb60200f..7ac6e2839 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -7404,6 +7404,8 @@ function WAREHOUSE:_CheckRequestNow(request) -- Check if at least one (cargo) asset is available. if _nassets>0 then + + local asset=_assets[1] --#WAREHOUSE.Assetitem -- Get the attibute of the requested asset. _assetattribute=_assets[1].attribute @@ -7414,11 +7416,24 @@ function WAREHOUSE:_CheckRequestNow(request) if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then + + -- Check if DCS warehouse of airbase has enough assets + if self.airbase.storage then + local nS=self.airbase.storage:GetAmount(asset.unittype) + local nA=asset.nunits*request.nasset -- Number of units requested + if nS NOT enough to spawn the requested %d asset units (%d groups)", + self.alias, nS, asset.unittype, nA, request.nasset) + self:_InfoMessage(text, 5) + return false + end + end + if self:IsRunwayOperational() or _assetairstart then if _assetairstart then - -- Airstart no need to check parking + -- Airstart no need to check parking else -- Check parking. @@ -7530,6 +7545,9 @@ function WAREHOUSE:_CheckRequestNow(request) self:_InfoMessage(text, 5) return false end + + elseif _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then + end From c3bba7d1fc98dbea2d18ca48adb208fc5c18b7e4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 1 Dec 2023 16:29:06 +0100 Subject: [PATCH 08/21] #PLAYERRECCE * Make the laser corridor a bit wider #FLIGHtCONTROL * Also create non-info when ATIS isn't set --- Moose Development/Moose/Ops/FlightControl.lua | 6 +++++- Moose Development/Moose/Ops/PlayerRecce.lua | 4 ++-- 2 files changed, 7 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/FlightControl.lua b/Moose Development/Moose/Ops/FlightControl.lua index bab56059a..8403e0c24 100644 --- a/Moose Development/Moose/Ops/FlightControl.lua +++ b/Moose Development/Moose/Ops/FlightControl.lua @@ -2811,7 +2811,11 @@ function FLIGHTCONTROL:_PlayerInfoATIS(groupname) -- Radio message. self:TransmissionPilot(rtext, flight) - self:TransmissionTower(srstxt,flight,10) + if self.atis then + self:TransmissionTower(srstxt,flight,10) + else + self:TransmissionTower(text,flight,10) + end else self:E(self.lid..string.format("Cannot find flight group %s.", tostring(groupname))) diff --git a/Moose Development/Moose/Ops/PlayerRecce.lua b/Moose Development/Moose/Ops/PlayerRecce.lua index 70853e14b..ec3a57954 100644 --- a/Moose Development/Moose/Ops/PlayerRecce.lua +++ b/Moose Development/Moose/Ops/PlayerRecce.lua @@ -714,8 +714,8 @@ function PLAYERRECCE:_GetViewZone(unit, vheading, minview, maxview, angle, camon local heading2 = (vheading-90)%360 self:T({heading1,heading2}) local startpos = startp:Translate(minview,vheading) - local pos1 = startpos:Translate(10,heading1) - local pos2 = startpos:Translate(10,heading2) + local pos1 = startpos:Translate(12.5,heading1) + local pos2 = startpos:Translate(12.5,heading2) local pos3 = pos1:Translate(maxview,vheading) local pos4 = pos2:Translate(maxview,vheading) local array = {} From ae604fd847439b32de420cb26b8fa434f5c04823 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 2 Dec 2023 14:45:42 +0100 Subject: [PATCH 09/21] #AIRBASE * Add'l Normandy Airfields --- Moose Development/Moose/Wrapper/Airbase.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index b877fa004..76da83c94 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -239,6 +239,13 @@ AIRBASE.Nevada = { -- * AIRBASE.Normandy.Broglie -- * AIRBASE.Normandy.Bernay_Saint_Martin -- * AIRBASE.Normandy.Saint_Andre_de_lEure +-- * AIRBASE.Normandy.Biggin_Hill +-- * AIRBASE.Normandy.Manston +-- * AIRBASE.Normandy.Detling +-- * AIRBASE.Normandy.Lympne +-- * AIRBASE.Normandy.Abbeville_Drucat +-- * AIRBASE.Normandy.Merville_Calonne +-- * AIRBASE.Normandy.Saint_Omer_Wizernes -- -- @field Normandy AIRBASE.Normandy = { @@ -311,7 +318,14 @@ AIRBASE.Normandy = { ["Beaumont_le_Roger"] = "Beaumont-le-Roger", ["Broglie"] = "Broglie", ["Bernay_Saint_Martin"] = "Bernay Saint Martin", - ["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure", + ["Saint_Andre_de_lEure"] = "Saint-Andre-de-lEure", + ["Biggin_Hill"] = "Biggin Hill", + ["Manston"] = "Manston", + ["Detling"] = "Detling", + ["Lympne"] = "Lympne", + ["Abbeville_Drucat"] = "Abbeville Drucat", + ["Merville_Calonne"] = "Merville Calonne", + ["Saint_Omer_Wizernes"] = "Saint-Omer Wizernes", } --- Airbases of the Persion Gulf Map: From 89a902fd57dcabc7f0d7d21c1a9558cd8858cec4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 2 Dec 2023 15:11:14 +0100 Subject: [PATCH 10/21] #ATIS * make info multi-frequency safe --- Moose Development/Moose/Ops/ATIS.lua | 37 ++++++++++++++++++++++++---- 1 file changed, 32 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 143364103..3e0c258f2 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -1594,8 +1594,16 @@ function ATIS:onafterStart( From, Event, To ) end -- Info. - self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) ) - + if type(self.frequency) == "table" then + local frequency = table.concat(self.frequency,"/") + local modulation = self.modulation + if type(self.modulation) == "table" then + modulation = table.concat(self.modulation,"/") + end + self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %s MHz Modulation=%s", ATIS.version, self.airbasename, frequency, modulation ) ) + else + self:I( self.lid .. string.format( "Starting ATIS v%s for airbase %s on %.3f MHz Modulation=%d", ATIS.version, self.airbasename, self.frequency, self.modulation ) ) + end -- Start radio queue. if not self.useSRS then self.radioqueue = RADIOQUEUE:New( self.frequency, self.modulation, string.format( "ATIS %s", self.airbasename ) ) @@ -1653,7 +1661,17 @@ function ATIS:onafterStatus( From, Event, To ) end -- Info text. - local text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) ) + local text = "" + if type(self.frequency) == "table" then + local frequency = table.concat(self.frequency,"/") + local modulation = self.modulation + if type(self.modulation) == "table" then + modulation = table.concat(self.modulation,"/") + end + text = string.format( "State %s: Freq=%s MHz %s", fsmstate, frequency, modulation ) + else + text = string.format( "State %s: Freq=%.3f MHz %s", fsmstate, self.frequency, UTILS.GetModulationName( self.modulation ) ) + end if self.useSRS then text = text .. string.format( ", SRS path=%s (%s), gender=%s, culture=%s, voice=%s", tostring( self.msrs.path ), tostring( self.msrs.port ), tostring( self.msrs.gender ), tostring( self.msrs.culture ), tostring( self.msrs.voice ) ) else @@ -2919,8 +2937,17 @@ function ATIS:UpdateMarker( information, runact, wind, altimeter, temperature ) if self.markerid then self.airbase:GetCoordinate():RemoveMark( self.markerid ) end - - local text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) ) + local text = "" + if type(self.frequency) == "table" then + local frequency = table.concat(self.frequency,"/") + local modulation = self.modulation + if type(modulation) == "table" then + modulation = table.concat(self.modulation,"/") + end + text = string.format( "ATIS on %s %s, %s:\n", tostring(frequency), tostring(modulation), tostring( information ) ) + else + text = string.format( "ATIS on %.3f %s, %s:\n", self.frequency, UTILS.GetModulationName( self.modulation ), tostring( information ) ) + end text = text .. string.format( "%s\n", tostring( runact ) ) text = text .. string.format( "%s\n", tostring( wind ) ) text = text .. string.format( "%s\n", tostring( altimeter ) ) From 29c0d81c279282dc01c868edf6885cb40b8900e3 Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Sat, 2 Dec 2023 20:33:30 +0100 Subject: [PATCH 11/21] Update on the ZONE_POLYGON class, so we can finally use it with drawings made in the Mission Editor as well. Supports closed line segments, rects and freely drawn polygons. Added the correct way of generating a point within a polygon with trial and error Added a way to get the surface area. Added a helper class (_ZONE_TRIANGLE), which shouldn't ever be used on its own. It's there to support the update ZONE_POLYGON. Some test code, assuming there's a poygon drawn in the mission editor called "poly": ```Lua -- make a new zone from a drawing poly = ZONE_POLYGON:NewFromDrawing("poly") -- draw the zone for everyone, include the individual triangles that make up the polygon poly:DrawZone(-1, {1,0,0}, 1, {1,0,0}, 255, 4, false, true) -- generate 500 random points, evenly distributed in the polygon for i=1, 500 do COORDINATE:NewFromVec2(poly:GetRandomVec2()):CircleToAll(1000) end -- remove the drawing from the game poly:UndrawZone() ``` --- Moose Development/Moose/Core/Zone.lua | 404 +++++++++++++++++++++----- 1 file changed, 336 insertions(+), 68 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index aaff86ce7..8d5ca1509 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -46,7 +46,7 @@ -- === -- -- ### Author: **FlightControl** --- ### Contributions: **Applevangelist**, **FunkyFranky** +-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit** -- -- === -- @@ -479,8 +479,12 @@ function ZONE_BASE:UndrawZone(Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) else - if self.DrawID then + if self.DrawID and type(self.DrawID) ~= "table" then UTILS.RemoveMark(self.DrawID) + else -- DrawID is a table with a collections of mark ids, as used in ZONE_POLYGON + for _, mark_id in pairs(self.DrawID) do + UTILS.RemoveMark(mark_id) + end end end return self @@ -1994,6 +1998,97 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) end + +--- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua +--- This triangle "zone" is not really to be used on its own, it only serves as building blocks for +--- ZONE_POLYGON to accurately find a point inside a polygon; as well as getting the correct surface area of +--- a polygon. +-- @type _ZONE_TRIANGLE +-- @extends #BASE + +_ZONE_TRIANGLE = { + ClassName="ZONE_TRIANGLE", + Points={}, + Coords={}, + CenterVec2={x=0, y=0}, + SurfaceArea=0, + DrawIDs={} +} + +function _ZONE_TRIANGLE:New(p1, p2, p3) + local self = BASE:Inherit(self, BASE:New()) + self.Points = {p1, p2, p3} + + local center_x = (p1.x + p2.x + p3.x) / 3 + local center_y = (p1.y + p2.y + p3.y) / 3 + self.CenterVec2 = {x=center_x, y=center_y} + + for _, pt in pairs({p1, p2, p3}) do + table.add(self.Coords, COORDINATE:NewFromVec2(pt)) + end + + self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5 + + return self +end + +--- Checks if a point is contained within the triangle. +-- @param #table pt The point to check +-- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it +-- @return #bool True if the point is contained, false otherwise +function _ZONE_TRIANGLE:ContainsPoint(pt, points) + points = points or self.Points + + local function sign(p1, p2, p3) + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y) + end + + local d1 = sign(pt, self.Points[1], self.Points[2]) + local d2 = sign(pt, self.Points[2], self.Points[3]) + local d3 = sign(pt, self.Points[3], self.Points[1]) + + local has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) + local has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) + + return not (has_neg and has_pos) +end + +--- Returns a random Vec2 within the triangle. +-- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it +-- @return #table The random Vec2 +function _ZONE_TRIANGLE:GetRandomVec2(points) + points = points or self.Points + local pt = {math.random(), math.random()} + table.sort(pt) + local s = pt[1] + local t = pt[2] - pt[1] + local u = 1 - pt[2] + + return {x = s * points[1].x + t * points[2].x + u * points[3].x, + y = s * points[1].y + t * points[2].y + u * points[3].y} +end + +--- Draw the triangle +function _ZONE_TRIANGLE:Draw(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + Coalition=Coalition or -1 + + Color=Color or {1, 0, 0 } + Alpha=Alpha or 1 + + FillColor=FillColor or Color + if not FillColor then UTILS.DeepCopy(Color) end + FillAlpha=FillAlpha or Alpha + if not FillAlpha then FillAlpha=1 end + + for i=1, #self.Coords do + local c1 = self.Coords[i] + local c2 = self.Coords[i % #self.Coords + 1] + table.add(self.DrawIDs, c1:LineToAll(c2, Coalition, Color, Alpha, LineType, ReadOnly)) + end + return self.DrawIDs +end + + --- -- @type ZONE_POLYGON_BASE -- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. @@ -2021,7 +2116,10 @@ end -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", - } + _Triangles={}, -- _ZONE_TRIANGLES + SurfaceArea=0, + DrawID={} -- making a table out of the MarkID so its easier to draw an n-sided polygon, see ZONE_POLYGON_BASE:Draw() +} --- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 @@ -2055,9 +2153,101 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) end + -- triangulate the polygon so we can work with it + self._Triangles = self:_Triangulate() + -- set the polygon's surface area + self.SurfaceArea = self:_CalculateSurfaceArea() + return self end +--- Triangulates the polygon. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua +-- @return #table The #_TRIANGLE list that make up +function ZONE_POLYGON_BASE:_Triangulate() + local points = self._.Polygon + local triangles = {} + + local function get_orientation(shape_points) + local sum = 0 + for i = 1, #shape_points do + local j = i % #shape_points + 1 + sum = sum + (shape_points[j].x - shape_points[i].x) * (shape_points[j].y + shape_points[i].y) + end + return sum >= 0 and "clockwise" or "counter-clockwise" -- sum >= 0, return "clockwise", else return "counter-clockwise" + end + + local function ensure_clockwise(shape_points) + local orientation = get_orientation(shape_points) + if orientation == "counter-clockwise" then + -- Reverse the order of shape_points so they're clockwise + local reversed = {} + for i = #shape_points, 1, -1 do + table.insert(reversed, shape_points[i]) + end + return reversed + end + return shape_points + end + + local function is_clockwise(p1, p2, p3) + local cross_product = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) + return cross_product < 0 + end + + local function divide_recursively(shape_points) + if #shape_points == 3 then + table.insert(triangles, _ZONE_TRIANGLE:New(shape_points[1], shape_points[2], shape_points[3])) + elseif #shape_points > 3 then -- find an ear -> a triangle with no other points inside it + for i, p1 in ipairs(shape_points) do + local p2 = shape_points[(i % #shape_points) + 1] + local p3 = shape_points[(i + 1) % #shape_points + 1] + local triangle = _ZONE_TRIANGLE:New(p1, p2, p3) + local is_ear = true + + if not is_clockwise(p1, p2, p3) then + is_ear = false + else + for _, point in ipairs(shape_points) do + if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then + is_ear = false + break + end + end + end + + if is_ear then + -- Check if any point in the original polygon is inside the ear triangle + local is_valid_triangle = true + for _, point in ipairs(points) do + if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then + is_valid_triangle = false + break + end + end + if is_valid_triangle then + table.insert(triangles, triangle) + local remaining_points = {} + for j, point in ipairs(shape_points) do + if point ~= p2 then + table.insert(remaining_points, point) + end + end + divide_recursively(remaining_points) + break + end + else + + end + end + end + end + + points = ensure_clockwise(points) + divide_recursively(points) + return triangles +end + --- Update polygon points with an array of @{DCS#Vec2}. -- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. @@ -2072,6 +2262,10 @@ function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) self._.Polygon[i].y=Vec2Array[i].y end + -- triangulate the polygon so we can work with it + self._Triangles = self:_Triangulate() + -- set the polygon's surface area + self.SurfaceArea = self:_CalculateSurfaceArea() return self end @@ -2089,9 +2283,24 @@ function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) self._.Polygon[i].y=Vec3Array[i].z end + -- triangulate the polygon so we can work with it + self._Triangles = self:_Triangulate() + -- set the polygon's surface area + self.SurfaceArea = self:_CalculateSurfaceArea() return self end +--- Calculates the surface area of the polygon. The surface area is the sum of the areas of the triangles that make up the polygon. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua +-- @return #number The surface area of the polygon +function ZONE_POLYGON_BASE:_CalculateSurfaceArea() + local area = 0 + for _, triangle in pairs(self._Triangles) do + area = area + triangle.SurfaceArea + end + return area +end + --- Returns the center location of the polygon. -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. @@ -2233,63 +2442,77 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) return self end ---- Draw the zone on the F10 map. **NOTE** Currently, only polygons **up to ten points** are supported! +--- Draw the zone on the F10 map. Infinite number of points supported +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. -- @param #number Alpha Transparency [0,1]. Default 1. --- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. --- @param #number FillAlpha Transparency [0,1]. Default 0.15. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. -- doesn't seem to work +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. -- doesn't seem to work -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) +function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, IncludeTriangles) + if self._.Polygon and #self._.Polygon >= 3 then + Coalition = Coalition or self:GetDrawCoalition() - if self._.Polygon and #self._.Polygon>=3 then + -- Set draw coalition. + self:SetDrawCoalition(Coalition) - local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + Color = Color or self:GetColorRGB() + Alpha = Alpha or 1 - Coalition=Coalition or self:GetDrawCoalition() + -- Set color. + self:SetColor(Color, Alpha) - -- Set draw coalition. - self:SetDrawCoalition(Coalition) + FillColor = FillColor or self:GetFillColorRGB() + if not FillColor then + UTILS.DeepCopy(Color) + end + FillAlpha = FillAlpha or self:GetFillColorAlpha() + if not FillAlpha then + FillAlpha = 0.15 + end - Color=Color or self:GetColorRGB() - Alpha=Alpha or 1 + -- Set fill color -----------> has fill color worked in recent versions of DCS? + -- doing something like + -- + -- trigger.action.markupToAll(7, -1, 501, p.Coords[1]:GetVec3(), p.Coords[2]:GetVec3(),p.Coords[3]:GetVec3(),p.Coords[4]:GetVec3(),{1,0,0, 1}, {1,0,0, 1}, 4, false, Text or "") + -- + -- doesn't seem to fill in the shape for an n-sided polygon + self:SetFillColor(FillColor, FillAlpha) - -- Set color. - self:SetColor(Color, Alpha) - - FillColor=FillColor or self:GetFillColorRGB() - if not FillColor then UTILS.DeepCopy(Color) end - FillAlpha=FillAlpha or self:GetFillColorAlpha() - if not FillAlpha then FillAlpha=0.15 end - - -- Set fill color. - self:SetFillColor(FillColor, FillAlpha) - - if #self._.Polygon==4 then - - local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) - local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) - local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - - self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - - else - - local Coordinates=self:GetVerticiesCoordinates() - table.remove(Coordinates, 1) - - self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + IncludeTriangles = IncludeTriangles or false + -- just draw the triangles, we get the outline for free + if IncludeTriangles then + for _, triangle in pairs(self._Triangles) do + local draw_ids = triangle:Draw() + table.combine(self.DrawID, draw_ids) + end + -- draw outline only + else + local coords = self:GetVerticiesCoordinates() + for i = 1, #coords do + local c1 = coords[i] + local c2 = coords[i % #coords + 1] + table.add(self.DrawID, c1:LineToAll(c2, Coalition, Color, Alpha, LineType, ReadOnly)) + end + end end - - end - - return self + return self end +--- Get the surface area of this polygon +-- @param #ZONE_POLYGON_BASE self +-- @return #number Surface area +function ZONE_POLYGON_BASE:GetSurfaceArea() + return self.SurfaceArea +end + + + --- Get the smallest radius encompassing all points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @return #number Radius of the zone in meters. @@ -2449,7 +2672,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end + if not Vec2 then return false end local Next local Prev local InPolygon = false @@ -2479,40 +2702,34 @@ end -- @return #boolean true if the point is within the zone. function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - - if not Vec3 then return false end - + + if not Vec3 then return false end + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end --- Define a random @{DCS#Vec2} within the zone. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() - - -- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - - -- Get the bounding square. - local BS = self:GetBoundingSquare() - - local Nmax=1000 ; local n=0 - while n= random_weight then + return triangle:GetRandomVec2() + end + end end --- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. @@ -2649,7 +2866,8 @@ end -- @extends #ZONE_POLYGON_BASE ---- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon, OR by drawings made with the Draw tool +--- in the Mission Editor -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. -- -- ## Declare a ZONE_POLYGON directly in the DCS mission editor! @@ -2672,6 +2890,13 @@ end -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, -- without much scripting overhead! -- +-- This class now also supports drawings made with the Draw tool in the Mission Editor. Any drawing made with Line > Segments > Closed, Polygon > Rect or Polygon > Free can be +-- made into a ZONE_POLYGON. +-- +-- This class has been updated to use a accurate way of generating random points inside the polygon without having to use trial and error guesses. +-- You can also get the surface area of the polygon now, handy if you want measure which coalition has the largest captured area, for example. + + -- @field #ZONE_POLYGON ZONE_POLYGON = { ClassName="ZONE_POLYGON", @@ -2732,6 +2957,49 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) return self end +--- Constructor to create a ZONE_POLYGON instance, taking the name of a drawing made with the draw tool in the Mission Editor. +-- @param #ZONE_POLYGON self +-- @param #string DrawingName The name of the drawing in the Mission Editor +-- @return #ZONE_POLYGON self +function ZONE_POLYGON:NewFromDrawing(DrawingName) + local points = {} + for _, layer in pairs(env.mission.drawings.layers) do + for _, object in pairs(layer["objects"]) do + if object["name"] == DrawingName then + if (object["primitiveType"] == "Line" and object["closed"] == true) or (object["polygonMode"] == "free") then + -- points for the drawings are saved in local space, so add the object's map x and y coordinates to get + -- world space points we can use + for _, point in UTILS.spairs(object["points"]) do + local p = {x = object["mapX"] + point["x"], + y = object["mapY"] + point["y"] } + table.add(points, p) + end + elseif object["polygonMode"] == "rect" then + -- the points for a rect are saved as local coordinates with an angle. To get the world space points from this + -- we need to rotate the points around the center of the rects by an angle. UTILS.RotatePointAroundPivot was + -- committed in an earlier commit + local angle = object["angle"] + local half_width = object["width"] / 2 + local half_height = object["height"] / 2 + + local center = { x = object["mapX"], y = object["mapY"] } + local p1 = UTILS.RotatePointAroundPivot({ x = center.x - half_height, y = center.y + half_width }, center, angle) + local p2 = UTILS.RotatePointAroundPivot({ x = center.x + half_height, y = center.y + half_width }, center, angle) + local p3 = UTILS.RotatePointAroundPivot({ x = center.x + half_height, y = center.y - half_width }, center, angle) + local p4 = UTILS.RotatePointAroundPivot({ x = center.x - half_height, y = center.y - half_width }, center, angle) + + points = {p1, p2, p3, p4} + else + -- something else that might be added in the future + end + end + end + end + local self = BASE:Inherit(self, ZONE_POLYGON_BASE:New(DrawingName, points)) + _EVENTDISPATCHER:CreateEventNewZone(self) + return self +end + --- Find a polygon zone in the _DATABASE using the name of the polygon zone. -- @param #ZONE_POLYGON self From afe542cc637c5b8eec85765b486fd9dbe7bab8cf Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 3 Dec 2023 09:23:42 +0100 Subject: [PATCH 12/21] Update Event.lua Fix for playername in weapon target --- Moose Development/Moose/Core/Event.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index b5122c04e..d3a105631 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1348,7 +1348,8 @@ function EVENT:onEvent( Event ) Event.Weapon = Event.weapon Event.WeaponName = Event.Weapon:getTypeName() Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit! - Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName() + Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName() + --Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName() Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition() Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() From 31bdde130ab12b232b893d038da6fe6ff8a63c9e Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 3 Dec 2023 09:26:09 +0100 Subject: [PATCH 13/21] Update Event.lua (#2056) Fix for playername in weapon target --- Moose Development/Moose/Core/Event.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index b5122c04e..d3a105631 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1348,7 +1348,8 @@ function EVENT:onEvent( Event ) Event.Weapon = Event.weapon Event.WeaponName = Event.Weapon:getTypeName() Event.WeaponUNIT = CLIENT:Find( Event.Weapon, '', true ) -- Sometimes, the weapon is a player unit! - Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName() + Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon.getPlayerName and Event.Weapon:getPlayerName() + --Event.WeaponPlayerName = Event.WeaponUNIT and Event.Weapon:getPlayerName() Event.WeaponCoalition = Event.WeaponUNIT and Event.Weapon:getCoalition() Event.WeaponCategory = Event.WeaponUNIT and Event.Weapon:getDesc().category Event.WeaponTypeName = Event.WeaponUNIT and Event.Weapon:getTypeName() From fd191be27491bcc9fc552596a6971bb8886d3c10 Mon Sep 17 00:00:00 2001 From: Niels Vaes Date: Sun, 3 Dec 2023 11:34:52 +0100 Subject: [PATCH 14/21] Added ZONE_OVAL. (#2057) * Added ZONE_OVAL. This can be useful for simulating the area of a view zone from an object looking down at the ground for example. Can be constructed from a Mission Editor drawing. * add to DB --- Moose Development/Moose/Core/Zone.lua | 310 ++++++++++++++++++++++---- 1 file changed, 262 insertions(+), 48 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 8d5ca1509..6014c7f92 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -111,7 +111,7 @@ -- ## A zone might have additional Properties created in the DCS Mission Editor, which can be accessed: -- -- *@{#ZONE_BASE.GetProperty}(): Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. --- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table. +-- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table. -- -- @field #ZONE_BASE ZONE_BASE = { @@ -313,7 +313,7 @@ function ZONE_BASE:Get2DDistance(Coordinate) else b.x=Coordinate.x b.y=Coordinate.y - end + end local dist=UTILS.VecDist2D(a,b) return dist end @@ -579,17 +579,17 @@ end -- @usage -- -- Create a new zone and start watching it every 5 secs for a defined GROUP entering or leaving -- local triggerzone = ZONE:New("ZonetoWatch"):Trigger(GROUP:FindByName("Aerial-1")) --- +-- -- -- This FSM function will be called when the group enters the zone -- function triggerzone:OnAfterEnteredZone(From,Event,To,Group) -- MESSAGE:New("Group has entered zone!",15):ToAll() -- end --- +-- -- -- This FSM function will be called when the group leaves the zone -- function triggerzone:OnAfterLeftZone(From,Event,To,Group) -- MESSAGE:New("Group has left zone!",15):ToAll() -- end --- +-- -- -- Stop watching the zone after 1 hour -- triggerzone:__TriggerStop(3600) function ZONE_BASE:Trigger(Objects) @@ -610,20 +610,20 @@ function ZONE_BASE:Trigger(Objects) self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self - + ------------------------ --- Pseudo Functions --- ------------------------ - + --- Triggers the FSM event "TriggerStop". Stops the ZONE_BASE Trigger. -- @function [parent=#ZONE_BASE] TriggerStop -- @param #ZONE_BASE self - --- Triggers the FSM event "TriggerStop" after a delay. + --- Triggers the FSM event "TriggerStop" after a delay. -- @function [parent=#ZONE_BASE] __TriggerStop -- @param #ZONE_BASE self -- @param #number delay Delay in seconds. - + --- On After "EnteredZone" event. An observed object has entered the zone. -- @function [parent=#ZONE_BASE] OnAfterEnteredZone -- @param #ZONE_BASE self @@ -666,12 +666,12 @@ function ZONE_BASE:_TriggerCheck(fromstart) local obj = _object -- Wrapper.Controllable#CONTROLLABLE if obj and obj:IsAlive() then if not obj.TriggerInZone then - -- has not been tagged previously - wasn't in set! + -- has not been tagged previously - wasn't in set! obj.TriggerInZone = {} end if not obj.TriggerInZone[self.ZoneName] then - -- has not been tagged previously - wasn't in set! - obj.TriggerInZone[self.ZoneName] = false + -- has not been tagged previously - wasn't in set! + obj.TriggerInZone[self.ZoneName] = false end -- is obj in zone? local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) @@ -691,7 +691,7 @@ function ZONE_BASE:_TriggerCheck(fromstart) end end end - end + end return self end @@ -716,7 +716,7 @@ end -- @param #string PropertyName The name of a the TriggerZone Property to be retrieved. -- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent. -- @usage --- +-- -- local PropertiesZone = ZONE:FindByName("Properties Zone") -- local Property = "ExampleProperty" -- local PropertyValue = PropertiesZone:GetProperty(Property) @@ -792,7 +792,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone ) if not DoNotRegisterZone then _EVENTDISPATCHER:CreateEventNewZone(self) end - + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) return self @@ -1430,7 +1430,7 @@ end function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end + if not Vec2 then return false end local ZoneVec2 = self:GetVec2() @@ -1449,7 +1449,7 @@ end -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - if not Vec3 then return false end + if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -1569,7 +1569,7 @@ function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) return Coordinate end ---- Returns a @{Core.Point#COORDINATE} object reflecting a random location within the zone where there are no **map objects** of type "Building". +--- Returns a @{Core.Point#COORDINATE} object reflecting a random location within the zone where there are no **map objects** of type "Building". -- Does not find statics you might have placed there. **Note** This might be quite CPU intensive, use with care. -- @param #ZONE_RADIUS self -- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0m. @@ -1596,7 +1596,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma local buildings = {} local buildingzones = {} - + if self.ScanData and self.ScanData.BuildingCoordinates then buildings = self.ScanData.BuildingCoordinates buildingzones = self.ScanData.BuildingZones @@ -1623,7 +1623,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma end -- max 1000 tries - local rcoord = nil + local rcoord = nil local found = true local iterations = 0 @@ -1639,21 +1639,21 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma break end end - if found then + if found then -- we have a winner! if markfinal then MARKER:New(rcoord,"FREE"):ToAll() end - break + break end end - + if not found then -- max 1000 tries - local rcoord = nil + local rcoord = nil local found = true local iterations = 0 - + for i=1,1000 do iterations = iterations + 1 rcoord = self:GetRandomCoordinate(inner,outer) @@ -1665,22 +1665,22 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma found = false end end - if found then + if found then -- we have a winner! if markfinal then MARKER:New(rcoord,"FREE"):ToAll() end - break + break end end end - + T1=timer.getTime() - + self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) - + if found then return rcoord else return nil end - + end --- @@ -1998,6 +1998,220 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) end +--- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua +--- This triangle "zone" is not really to be used on its own, it only serves as building blocks for +--- ZONE_POLYGON to accurately find a point inside a polygon; as well as getting the correct surface area of +--- a polygon. +-- @type _ZONE_TRIANGLE +-- @extends #BASE + +ZONE_OVAL = { + ClassName = "OVAL", + ZoneName="", + MajorAxis = nil, + MinorAxis = nil, + Angle = 0, + DrawPoly = nil -- let's just use a ZONE_POLYGON to draw the ZONE_OVAL on the map +} + +--- Creates a new ZONE_OVAL from a center point, major axis, minor axis, and angle. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @param #table vec2 The center point of the oval +-- @param #number major_axis The major axis of the oval +-- @param #number minor_axis The minor axis of the oval +-- @param #number angle The angle of the oval +-- @return #ZONE_OVAL The new oval +function ZONE_OVAL:New(name, vec2, major_axis, minor_axis, angle) + self = BASE:Inherit(self, ZONE_BASE:New()) + self.ZoneName = name + self.CenterVec2 = vec2 + self.MajorAxis = major_axis + self.MinorAxis = minor_axis + self.Angle = angle or 0 + + _DATABASE:AddZone(name, self) + + return self +end + +--- Constructor to create a ZONE_OVAL instance, taking the name of a drawing made with the draw tool in the Mission Editor. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @param #ZONE_OVAL self +-- @param #string DrawingName The name of the drawing in the Mission Editor +-- @return #ZONE_OVAL self +function ZONE_OVAL:NewFromDrawing(DrawingName) + self = BASE:Inherit(self, ZONE_BASE:New(DrawingName)) + for _, layer in pairs(env.mission.drawings.layers) do + for _, object in pairs(layer["objects"]) do + if string.find(object["name"], DrawingName, 1, true) then + if object["polygonMode"] == "oval" then + self.CenterVec2 = { x = object["mapX"], y = object["mapY"] } + self.MajorAxis = object["r1"] + self.MinorAxis = object["r2"] + self.Angle = object["angle"] + + end + end + end + end + + _DATABASE:AddZone(DrawingName, self) + + return self +end + +--- Gets the major axis of the oval. +-- @return #number The major axis of the oval +function ZONE_OVAL:GetMajorAxis() + return self.MajorAxis +end + +--- Gets the minor axis of the oval. +-- @return #number The minor axis of the oval +function ZONE_OVAL:GetMinorAxis() + return self.MinorAxis +end + +--- Gets the angle of the oval. +-- @return #number The angle of the oval +function ZONE_OVAL:GetAngle() + return self.Angle +end + +--- Returns a the center point of the oval +-- @return #table The center Vec2 +function ZONE_OVAL:GetVec2() + return self.CenterVec2 +end + +--- Checks if a point is contained within the oval. +-- @param #table point The point to check +-- @return #bool True if the point is contained, false otherwise +function ZONE_OVAL:IsVec2InZone(vec2) + local cos, sin = math.cos, math.sin + local dx = vec2.x - self.CenterVec2.x + local dy = vec2.y - self.CenterVec2.y + local rx = dx * cos(self.Angle) + dy * sin(self.Angle) + local ry = -dx * sin(self.Angle) + dy * cos(self.Angle) + return rx * rx / (self.MajorAxis * self.MajorAxis) + ry * ry / (self.MinorAxis * self.MinorAxis) <= 1 +end + +--- Calculates the bounding box of the oval. The bounding box is the smallest rectangle that contains the oval. +-- @return #table The bounding box of the oval +function ZONE_OVAL:GetBoundingSquare() + local min_x = self.CenterVec2.x - self.MajorAxis + local min_y = self.CenterVec2.y - self.MinorAxis + local max_x = self.CenterVec2.x + self.MajorAxis + local max_y = self.CenterVec2.y + self.MinorAxis + + return { + {x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y} + } +end + +--- Find points on the edge of the oval +-- @param #number num_points How many points should be found. More = smoother shape +-- @return #table Points on he edge +function ZONE_OVAL:PointsOnEdge(num_points) + num_points = num_points or 40 + local points = {} + local dtheta = 2 * math.pi / num_points + + for i = 0, num_points - 1 do + local theta = i * dtheta + local x = self.CenterVec2.x + self.MajorAxis * math.cos(theta) * math.cos(self.Angle) - self.MinorAxis * math.sin(theta) * math.sin(self.Angle) + local y = self.CenterVec2.y + self.MajorAxis * math.cos(theta) * math.sin(self.Angle) + self.MinorAxis * math.sin(theta) * math.cos(self.Angle) + table.insert(points, {x = x, y = y}) + end + + return points +end + +--- Returns a random Vec2 within the oval. +-- @return #table The random Vec2 +function ZONE_OVAL:GetRandomVec2() + local theta = math.rad(self.Angle) + + local random_point = math.sqrt(math.random()) --> uniformly + --local random_point = math.random() --> more clumped around center + local phi = math.random() * 2 * math.pi + local x_c = random_point * math.cos(phi) + local y_c = random_point * math.sin(phi) + local x_e = x_c * self.MajorAxis + local y_e = y_c * self.MinorAxis + local rx = (x_e * math.cos(theta) - y_e * math.sin(theta)) + self.CenterVec2.x + local ry = (x_e * math.sin(theta) + y_e * math.cos(theta)) + self.CenterVec2.y + + return {x=rx, y=ry} +end + +--- Define a random @{Core.Point#POINT_VEC2} within the zone. +-- @param #ZONE_OVAL self +-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +function ZONE_OVAL:GetRandomPointVec2() + return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) +end + +--- Define a random @{Core.Point#POINT_VEC2} within the zone. +-- @param #ZONE_OVAL self +-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +function ZONE_OVAL:GetRandomPointVec3() + return POINT_VEC2:NewFromVec3(self:GetRandomVec2()) +end + +--- Draw the zone on the F10 map. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @param #ZONE_OVAL self +-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. +-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. +-- @param #number Alpha Transparency [0,1]. Default 1. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. -- doesn't seem to work +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. -- doesn't seem to work +-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. +-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. +-- @return #ZONE_OVAL self +function ZONE_OVAL:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType) + Coalition = Coalition or self:GetDrawCoalition() + + -- Set draw coalition. + self:SetDrawCoalition(Coalition) + + Color = Color or self:GetColorRGB() + Alpha = Alpha or 1 + + -- Set color. + self:SetColor(Color, Alpha) + + FillColor = FillColor or self:GetFillColorRGB() + if not FillColor then + UTILS.DeepCopy(Color) + end + FillAlpha = FillAlpha or self:GetFillColorAlpha() + if not FillAlpha then + FillAlpha = 0.15 + end + + LineType = LineType or 1 + + -- Set fill color -----------> has fill color worked in recent versions of DCS? + -- doing something like + -- + -- trigger.action.markupToAll(7, -1, 501, p.Coords[1]:GetVec3(), p.Coords[2]:GetVec3(),p.Coords[3]:GetVec3(),p.Coords[4]:GetVec3(),{1,0,0, 1}, {1,0,0, 1}, 4, false, Text or "") + -- + -- doesn't seem to fill in the shape for an n-sided polygon + self:SetFillColor(FillColor, FillAlpha) + + self.DrawPoly = ZONE_POLYGON:NewFromPointsArray(self.ZoneName, self:PointsOnEdge(80)) + self.DrawPoly:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType) +end + +--- Remove drawing from F10 map +function ZONE_OVAL:UndrawZone() + if self.DrawPoly then + self.DrawPoly:UndrawZone() + end +end + --- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua --- This triangle "zone" is not really to be used on its own, it only serves as building blocks for @@ -2016,7 +2230,7 @@ _ZONE_TRIANGLE = { } function _ZONE_TRIANGLE:New(p1, p2, p3) - local self = BASE:Inherit(self, BASE:New()) + local self = BASE:Inherit(self, ZONE_BASE:New()) self.Points = {p1, p2, p3} local center_x = (p1.x + p2.x + p3.x) / 3 @@ -2513,7 +2727,7 @@ end ---- Get the smallest radius encompassing all points of the polygon zone. +--- Get the smallest radius encompassing all points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @return #number Radius of the zone in meters. function ZONE_POLYGON_BASE:GetRadius() @@ -2521,22 +2735,22 @@ function ZONE_POLYGON_BASE:GetRadius() local center=self:GetVec2() local radius=0 - + for _,_vec2 in pairs(self._.Polygon) do local vec2=_vec2 --DCS#Vec2 - + local r=UTILS.VecDist2D(center, vec2) - + if r>radius then radius=r end - + end return radius end ---- Get the smallest circular zone encompassing all points of the polygon zone. +--- Get the smallest circular zone encompassing all points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. -- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. @@ -2546,25 +2760,25 @@ function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone) local center=self:GetVec2() local radius=self:GetRadius() - + local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone) return zone end ---- Get the smallest rectangular zone encompassing all points points of the polygon zone. +--- Get the smallest rectangular zone encompassing all points points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. -- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. -- @return #ZONE_POLYGON The rectangular zone. function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName, DoNotRegisterZone) - + local vec1, vec3=self:GetBoundingVec2() - + local vec2={x=vec1.x, y=vec3.y} local vec4={x=vec3.x, y=vec1.y} - + local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName, {vec1, vec2, vec3, vec4}) return zone @@ -2577,15 +2791,15 @@ end function ZONE_POLYGON_BASE:RemoveJunk(Height) Height=Height or 1000 - + local vec2SW, vec2NE=self:GetBoundingVec2() local vec3SW={x=vec2SW.x, y=-Height, z=vec2SW.y} --DCS#Vec3 local vec3NE={x=vec2NE.x, y= Height, z=vec2NE.y} --DCS#Vec3 - + --local coord1=COORDINATE:NewFromVec3(vec3SW):MarkToAll("SW") --local coord1=COORDINATE:NewFromVec3(vec3NE):MarkToAll("NE") - + local volume = { id = world.VolumeType.BOX, params = { @@ -2594,7 +2808,7 @@ function ZONE_POLYGON_BASE:RemoveJunk(Height) } } - local n=world.removeJunk(volume) + local n=world.removeJunk(volume) return n end @@ -2814,7 +3028,7 @@ function ZONE_POLYGON_BASE:GetBoundingVec2() y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 end - + local vec1={x=x1, y=y1} local vec2={x=x2, y=y2} From cf7d41cd7fe4ac0eaa401b194a673f6ca065f705 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 3 Dec 2023 11:42:53 +0100 Subject: [PATCH 15/21] #ZONE_POLYGON improvements #ZONE_OVAL NEW --- Moose Development/Moose/Core/Zone.lua | 712 +++++++++++++++++++++----- 1 file changed, 597 insertions(+), 115 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index aaff86ce7..6014c7f92 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -46,7 +46,7 @@ -- === -- -- ### Author: **FlightControl** --- ### Contributions: **Applevangelist**, **FunkyFranky** +-- ### Contributions: **Applevangelist**, **FunkyFranky**, **coconutcockpit** -- -- === -- @@ -111,7 +111,7 @@ -- ## A zone might have additional Properties created in the DCS Mission Editor, which can be accessed: -- -- *@{#ZONE_BASE.GetProperty}(): Returns the Value of the zone with the given PropertyName, or nil if no matching property exists. --- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table. +-- *@{#ZONE_BASE.GetAllProperties}(): Returns the zone Properties table. -- -- @field #ZONE_BASE ZONE_BASE = { @@ -313,7 +313,7 @@ function ZONE_BASE:Get2DDistance(Coordinate) else b.x=Coordinate.x b.y=Coordinate.y - end + end local dist=UTILS.VecDist2D(a,b) return dist end @@ -479,8 +479,12 @@ function ZONE_BASE:UndrawZone(Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) else - if self.DrawID then + if self.DrawID and type(self.DrawID) ~= "table" then UTILS.RemoveMark(self.DrawID) + else -- DrawID is a table with a collections of mark ids, as used in ZONE_POLYGON + for _, mark_id in pairs(self.DrawID) do + UTILS.RemoveMark(mark_id) + end end end return self @@ -575,17 +579,17 @@ end -- @usage -- -- Create a new zone and start watching it every 5 secs for a defined GROUP entering or leaving -- local triggerzone = ZONE:New("ZonetoWatch"):Trigger(GROUP:FindByName("Aerial-1")) --- +-- -- -- This FSM function will be called when the group enters the zone -- function triggerzone:OnAfterEnteredZone(From,Event,To,Group) -- MESSAGE:New("Group has entered zone!",15):ToAll() -- end --- +-- -- -- This FSM function will be called when the group leaves the zone -- function triggerzone:OnAfterLeftZone(From,Event,To,Group) -- MESSAGE:New("Group has left zone!",15):ToAll() -- end --- +-- -- -- Stop watching the zone after 1 hour -- triggerzone:__TriggerStop(3600) function ZONE_BASE:Trigger(Objects) @@ -606,20 +610,20 @@ function ZONE_BASE:Trigger(Objects) self:_TriggerCheck(true) self:__TriggerRunCheck(self.Checktime) return self - + ------------------------ --- Pseudo Functions --- ------------------------ - + --- Triggers the FSM event "TriggerStop". Stops the ZONE_BASE Trigger. -- @function [parent=#ZONE_BASE] TriggerStop -- @param #ZONE_BASE self - --- Triggers the FSM event "TriggerStop" after a delay. + --- Triggers the FSM event "TriggerStop" after a delay. -- @function [parent=#ZONE_BASE] __TriggerStop -- @param #ZONE_BASE self -- @param #number delay Delay in seconds. - + --- On After "EnteredZone" event. An observed object has entered the zone. -- @function [parent=#ZONE_BASE] OnAfterEnteredZone -- @param #ZONE_BASE self @@ -662,12 +666,12 @@ function ZONE_BASE:_TriggerCheck(fromstart) local obj = _object -- Wrapper.Controllable#CONTROLLABLE if obj and obj:IsAlive() then if not obj.TriggerInZone then - -- has not been tagged previously - wasn't in set! + -- has not been tagged previously - wasn't in set! obj.TriggerInZone = {} end if not obj.TriggerInZone[self.ZoneName] then - -- has not been tagged previously - wasn't in set! - obj.TriggerInZone[self.ZoneName] = false + -- has not been tagged previously - wasn't in set! + obj.TriggerInZone[self.ZoneName] = false end -- is obj in zone? local inzone = self:IsCoordinateInZone(obj:GetCoordinate()) @@ -687,7 +691,7 @@ function ZONE_BASE:_TriggerCheck(fromstart) end end end - end + end return self end @@ -712,7 +716,7 @@ end -- @param #string PropertyName The name of a the TriggerZone Property to be retrieved. -- @return #string The Value of the TriggerZone Property with the given PropertyName, or nil if absent. -- @usage --- +-- -- local PropertiesZone = ZONE:FindByName("Properties Zone") -- local Property = "ExampleProperty" -- local PropertyValue = PropertiesZone:GetProperty(Property) @@ -788,7 +792,7 @@ function ZONE_RADIUS:New( ZoneName, Vec2, Radius, DoNotRegisterZone ) if not DoNotRegisterZone then _EVENTDISPATCHER:CreateEventNewZone(self) end - + --self.Coordinate=COORDINATE:NewFromVec2(Vec2) return self @@ -1426,7 +1430,7 @@ end function ZONE_RADIUS:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end + if not Vec2 then return false end local ZoneVec2 = self:GetVec2() @@ -1445,7 +1449,7 @@ end -- @return #boolean true if the point is within the zone. function ZONE_RADIUS:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - if not Vec3 then return false end + if not Vec3 then return false end local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone @@ -1565,7 +1569,7 @@ function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) return Coordinate end ---- Returns a @{Core.Point#COORDINATE} object reflecting a random location within the zone where there are no **map objects** of type "Building". +--- Returns a @{Core.Point#COORDINATE} object reflecting a random location within the zone where there are no **map objects** of type "Building". -- Does not find statics you might have placed there. **Note** This might be quite CPU intensive, use with care. -- @param #ZONE_RADIUS self -- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0m. @@ -1592,7 +1596,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma local buildings = {} local buildingzones = {} - + if self.ScanData and self.ScanData.BuildingCoordinates then buildings = self.ScanData.BuildingCoordinates buildingzones = self.ScanData.BuildingZones @@ -1619,7 +1623,7 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma end -- max 1000 tries - local rcoord = nil + local rcoord = nil local found = true local iterations = 0 @@ -1635,21 +1639,21 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma break end end - if found then + if found then -- we have a winner! if markfinal then MARKER:New(rcoord,"FREE"):ToAll() end - break + break end end - + if not found then -- max 1000 tries - local rcoord = nil + local rcoord = nil local found = true local iterations = 0 - + for i=1,1000 do iterations = iterations + 1 rcoord = self:GetRandomCoordinate(inner,outer) @@ -1661,22 +1665,22 @@ function ZONE_RADIUS:GetRandomCoordinateWithoutBuildings(inner,outer,distance,ma found = false end end - if found then + if found then -- we have a winner! if markfinal then MARKER:New(rcoord,"FREE"):ToAll() end - break + break end end end - + T1=timer.getTime() - + self:T(string.format("Found a coordinate: %s | Iterations: %d | Time: %.3f",tostring(found),iterations,T1-T0)) - + if found then return rcoord else return nil end - + end --- @@ -1994,6 +1998,311 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) end +--- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua +--- This triangle "zone" is not really to be used on its own, it only serves as building blocks for +--- ZONE_POLYGON to accurately find a point inside a polygon; as well as getting the correct surface area of +--- a polygon. +-- @type _ZONE_TRIANGLE +-- @extends #BASE + +ZONE_OVAL = { + ClassName = "OVAL", + ZoneName="", + MajorAxis = nil, + MinorAxis = nil, + Angle = 0, + DrawPoly = nil -- let's just use a ZONE_POLYGON to draw the ZONE_OVAL on the map +} + +--- Creates a new ZONE_OVAL from a center point, major axis, minor axis, and angle. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @param #table vec2 The center point of the oval +-- @param #number major_axis The major axis of the oval +-- @param #number minor_axis The minor axis of the oval +-- @param #number angle The angle of the oval +-- @return #ZONE_OVAL The new oval +function ZONE_OVAL:New(name, vec2, major_axis, minor_axis, angle) + self = BASE:Inherit(self, ZONE_BASE:New()) + self.ZoneName = name + self.CenterVec2 = vec2 + self.MajorAxis = major_axis + self.MinorAxis = minor_axis + self.Angle = angle or 0 + + _DATABASE:AddZone(name, self) + + return self +end + +--- Constructor to create a ZONE_OVAL instance, taking the name of a drawing made with the draw tool in the Mission Editor. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @param #ZONE_OVAL self +-- @param #string DrawingName The name of the drawing in the Mission Editor +-- @return #ZONE_OVAL self +function ZONE_OVAL:NewFromDrawing(DrawingName) + self = BASE:Inherit(self, ZONE_BASE:New(DrawingName)) + for _, layer in pairs(env.mission.drawings.layers) do + for _, object in pairs(layer["objects"]) do + if string.find(object["name"], DrawingName, 1, true) then + if object["polygonMode"] == "oval" then + self.CenterVec2 = { x = object["mapX"], y = object["mapY"] } + self.MajorAxis = object["r1"] + self.MinorAxis = object["r2"] + self.Angle = object["angle"] + + end + end + end + end + + _DATABASE:AddZone(DrawingName, self) + + return self +end + +--- Gets the major axis of the oval. +-- @return #number The major axis of the oval +function ZONE_OVAL:GetMajorAxis() + return self.MajorAxis +end + +--- Gets the minor axis of the oval. +-- @return #number The minor axis of the oval +function ZONE_OVAL:GetMinorAxis() + return self.MinorAxis +end + +--- Gets the angle of the oval. +-- @return #number The angle of the oval +function ZONE_OVAL:GetAngle() + return self.Angle +end + +--- Returns a the center point of the oval +-- @return #table The center Vec2 +function ZONE_OVAL:GetVec2() + return self.CenterVec2 +end + +--- Checks if a point is contained within the oval. +-- @param #table point The point to check +-- @return #bool True if the point is contained, false otherwise +function ZONE_OVAL:IsVec2InZone(vec2) + local cos, sin = math.cos, math.sin + local dx = vec2.x - self.CenterVec2.x + local dy = vec2.y - self.CenterVec2.y + local rx = dx * cos(self.Angle) + dy * sin(self.Angle) + local ry = -dx * sin(self.Angle) + dy * cos(self.Angle) + return rx * rx / (self.MajorAxis * self.MajorAxis) + ry * ry / (self.MinorAxis * self.MinorAxis) <= 1 +end + +--- Calculates the bounding box of the oval. The bounding box is the smallest rectangle that contains the oval. +-- @return #table The bounding box of the oval +function ZONE_OVAL:GetBoundingSquare() + local min_x = self.CenterVec2.x - self.MajorAxis + local min_y = self.CenterVec2.y - self.MinorAxis + local max_x = self.CenterVec2.x + self.MajorAxis + local max_y = self.CenterVec2.y + self.MinorAxis + + return { + {x=min_x, y=min_x}, {x=max_x, y=min_y}, {x=max_x, y=max_y}, {x=min_x, y=max_y} + } +end + +--- Find points on the edge of the oval +-- @param #number num_points How many points should be found. More = smoother shape +-- @return #table Points on he edge +function ZONE_OVAL:PointsOnEdge(num_points) + num_points = num_points or 40 + local points = {} + local dtheta = 2 * math.pi / num_points + + for i = 0, num_points - 1 do + local theta = i * dtheta + local x = self.CenterVec2.x + self.MajorAxis * math.cos(theta) * math.cos(self.Angle) - self.MinorAxis * math.sin(theta) * math.sin(self.Angle) + local y = self.CenterVec2.y + self.MajorAxis * math.cos(theta) * math.sin(self.Angle) + self.MinorAxis * math.sin(theta) * math.cos(self.Angle) + table.insert(points, {x = x, y = y}) + end + + return points +end + +--- Returns a random Vec2 within the oval. +-- @return #table The random Vec2 +function ZONE_OVAL:GetRandomVec2() + local theta = math.rad(self.Angle) + + local random_point = math.sqrt(math.random()) --> uniformly + --local random_point = math.random() --> more clumped around center + local phi = math.random() * 2 * math.pi + local x_c = random_point * math.cos(phi) + local y_c = random_point * math.sin(phi) + local x_e = x_c * self.MajorAxis + local y_e = y_c * self.MinorAxis + local rx = (x_e * math.cos(theta) - y_e * math.sin(theta)) + self.CenterVec2.x + local ry = (x_e * math.sin(theta) + y_e * math.cos(theta)) + self.CenterVec2.y + + return {x=rx, y=ry} +end + +--- Define a random @{Core.Point#POINT_VEC2} within the zone. +-- @param #ZONE_OVAL self +-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +function ZONE_OVAL:GetRandomPointVec2() + return POINT_VEC2:NewFromVec2(self:GetRandomVec2()) +end + +--- Define a random @{Core.Point#POINT_VEC2} within the zone. +-- @param #ZONE_OVAL self +-- @return Core.Point#POINT_VEC2 The PointVec2 coordinates. +function ZONE_OVAL:GetRandomPointVec3() + return POINT_VEC2:NewFromVec3(self:GetRandomVec2()) +end + +--- Draw the zone on the F10 map. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @param #ZONE_OVAL self +-- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. +-- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. +-- @param #number Alpha Transparency [0,1]. Default 1. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. -- doesn't seem to work +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. -- doesn't seem to work +-- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. +-- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. +-- @return #ZONE_OVAL self +function ZONE_OVAL:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType) + Coalition = Coalition or self:GetDrawCoalition() + + -- Set draw coalition. + self:SetDrawCoalition(Coalition) + + Color = Color or self:GetColorRGB() + Alpha = Alpha or 1 + + -- Set color. + self:SetColor(Color, Alpha) + + FillColor = FillColor or self:GetFillColorRGB() + if not FillColor then + UTILS.DeepCopy(Color) + end + FillAlpha = FillAlpha or self:GetFillColorAlpha() + if not FillAlpha then + FillAlpha = 0.15 + end + + LineType = LineType or 1 + + -- Set fill color -----------> has fill color worked in recent versions of DCS? + -- doing something like + -- + -- trigger.action.markupToAll(7, -1, 501, p.Coords[1]:GetVec3(), p.Coords[2]:GetVec3(),p.Coords[3]:GetVec3(),p.Coords[4]:GetVec3(),{1,0,0, 1}, {1,0,0, 1}, 4, false, Text or "") + -- + -- doesn't seem to fill in the shape for an n-sided polygon + self:SetFillColor(FillColor, FillAlpha) + + self.DrawPoly = ZONE_POLYGON:NewFromPointsArray(self.ZoneName, self:PointsOnEdge(80)) + self.DrawPoly:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType) +end + +--- Remove drawing from F10 map +function ZONE_OVAL:UndrawZone() + if self.DrawPoly then + self.DrawPoly:UndrawZone() + end +end + + +--- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua +--- This triangle "zone" is not really to be used on its own, it only serves as building blocks for +--- ZONE_POLYGON to accurately find a point inside a polygon; as well as getting the correct surface area of +--- a polygon. +-- @type _ZONE_TRIANGLE +-- @extends #BASE + +_ZONE_TRIANGLE = { + ClassName="ZONE_TRIANGLE", + Points={}, + Coords={}, + CenterVec2={x=0, y=0}, + SurfaceArea=0, + DrawIDs={} +} + +function _ZONE_TRIANGLE:New(p1, p2, p3) + local self = BASE:Inherit(self, ZONE_BASE:New()) + self.Points = {p1, p2, p3} + + local center_x = (p1.x + p2.x + p3.x) / 3 + local center_y = (p1.y + p2.y + p3.y) / 3 + self.CenterVec2 = {x=center_x, y=center_y} + + for _, pt in pairs({p1, p2, p3}) do + table.add(self.Coords, COORDINATE:NewFromVec2(pt)) + end + + self.SurfaceArea = math.abs((p2.x - p1.x) * (p3.y - p1.y) - (p3.x - p1.x) * (p2.y - p1.y)) * 0.5 + + return self +end + +--- Checks if a point is contained within the triangle. +-- @param #table pt The point to check +-- @param #table points (optional) The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it +-- @return #bool True if the point is contained, false otherwise +function _ZONE_TRIANGLE:ContainsPoint(pt, points) + points = points or self.Points + + local function sign(p1, p2, p3) + return (p1.x - p3.x) * (p2.y - p3.y) - (p2.x - p3.x) * (p1.y - p3.y) + end + + local d1 = sign(pt, self.Points[1], self.Points[2]) + local d2 = sign(pt, self.Points[2], self.Points[3]) + local d3 = sign(pt, self.Points[3], self.Points[1]) + + local has_neg = (d1 < 0) or (d2 < 0) or (d3 < 0) + local has_pos = (d1 > 0) or (d2 > 0) or (d3 > 0) + + return not (has_neg and has_pos) +end + +--- Returns a random Vec2 within the triangle. +-- @param #table points The points of the triangle, or 3 other points if you're just using the TRIANGLE class without an object of it +-- @return #table The random Vec2 +function _ZONE_TRIANGLE:GetRandomVec2(points) + points = points or self.Points + local pt = {math.random(), math.random()} + table.sort(pt) + local s = pt[1] + local t = pt[2] - pt[1] + local u = 1 - pt[2] + + return {x = s * points[1].x + t * points[2].x + u * points[3].x, + y = s * points[1].y + t * points[2].y + u * points[3].y} +end + +--- Draw the triangle +function _ZONE_TRIANGLE:Draw(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + Coalition=Coalition or -1 + + Color=Color or {1, 0, 0 } + Alpha=Alpha or 1 + + FillColor=FillColor or Color + if not FillColor then UTILS.DeepCopy(Color) end + FillAlpha=FillAlpha or Alpha + if not FillAlpha then FillAlpha=1 end + + for i=1, #self.Coords do + local c1 = self.Coords[i] + local c2 = self.Coords[i % #self.Coords + 1] + table.add(self.DrawIDs, c1:LineToAll(c2, Coalition, Color, Alpha, LineType, ReadOnly)) + end + return self.DrawIDs +end + + --- -- @type ZONE_POLYGON_BASE -- @field #ZONE_POLYGON_BASE.ListVec2 Polygon The polygon defined by an array of @{DCS#Vec2}. @@ -2021,7 +2330,10 @@ end -- @field #ZONE_POLYGON_BASE ZONE_POLYGON_BASE = { ClassName="ZONE_POLYGON_BASE", - } + _Triangles={}, -- _ZONE_TRIANGLES + SurfaceArea=0, + DrawID={} -- making a table out of the MarkID so its easier to draw an n-sided polygon, see ZONE_POLYGON_BASE:Draw() +} --- A 2D points array. -- @type ZONE_POLYGON_BASE.ListVec2 @@ -2055,9 +2367,101 @@ function ZONE_POLYGON_BASE:New( ZoneName, PointsArray ) end + -- triangulate the polygon so we can work with it + self._Triangles = self:_Triangulate() + -- set the polygon's surface area + self.SurfaceArea = self:_CalculateSurfaceArea() + return self end +--- Triangulates the polygon. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua +-- @return #table The #_TRIANGLE list that make up +function ZONE_POLYGON_BASE:_Triangulate() + local points = self._.Polygon + local triangles = {} + + local function get_orientation(shape_points) + local sum = 0 + for i = 1, #shape_points do + local j = i % #shape_points + 1 + sum = sum + (shape_points[j].x - shape_points[i].x) * (shape_points[j].y + shape_points[i].y) + end + return sum >= 0 and "clockwise" or "counter-clockwise" -- sum >= 0, return "clockwise", else return "counter-clockwise" + end + + local function ensure_clockwise(shape_points) + local orientation = get_orientation(shape_points) + if orientation == "counter-clockwise" then + -- Reverse the order of shape_points so they're clockwise + local reversed = {} + for i = #shape_points, 1, -1 do + table.insert(reversed, shape_points[i]) + end + return reversed + end + return shape_points + end + + local function is_clockwise(p1, p2, p3) + local cross_product = (p2.x - p1.x) * (p3.y - p1.y) - (p2.y - p1.y) * (p3.x - p1.x) + return cross_product < 0 + end + + local function divide_recursively(shape_points) + if #shape_points == 3 then + table.insert(triangles, _ZONE_TRIANGLE:New(shape_points[1], shape_points[2], shape_points[3])) + elseif #shape_points > 3 then -- find an ear -> a triangle with no other points inside it + for i, p1 in ipairs(shape_points) do + local p2 = shape_points[(i % #shape_points) + 1] + local p3 = shape_points[(i + 1) % #shape_points + 1] + local triangle = _ZONE_TRIANGLE:New(p1, p2, p3) + local is_ear = true + + if not is_clockwise(p1, p2, p3) then + is_ear = false + else + for _, point in ipairs(shape_points) do + if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then + is_ear = false + break + end + end + end + + if is_ear then + -- Check if any point in the original polygon is inside the ear triangle + local is_valid_triangle = true + for _, point in ipairs(points) do + if point ~= p1 and point ~= p2 and point ~= p3 and triangle:ContainsPoint(point) then + is_valid_triangle = false + break + end + end + if is_valid_triangle then + table.insert(triangles, triangle) + local remaining_points = {} + for j, point in ipairs(shape_points) do + if point ~= p2 then + table.insert(remaining_points, point) + end + end + divide_recursively(remaining_points) + break + end + else + + end + end + end + end + + points = ensure_clockwise(points) + divide_recursively(points) + return triangles +end + --- Update polygon points with an array of @{DCS#Vec2}. -- @param #ZONE_POLYGON_BASE self -- @param #ZONE_POLYGON_BASE.ListVec2 Vec2Array An array of @{DCS#Vec2}, forming a polygon. @@ -2072,6 +2476,10 @@ function ZONE_POLYGON_BASE:UpdateFromVec2(Vec2Array) self._.Polygon[i].y=Vec2Array[i].y end + -- triangulate the polygon so we can work with it + self._Triangles = self:_Triangulate() + -- set the polygon's surface area + self.SurfaceArea = self:_CalculateSurfaceArea() return self end @@ -2089,9 +2497,24 @@ function ZONE_POLYGON_BASE:UpdateFromVec3(Vec3Array) self._.Polygon[i].y=Vec3Array[i].z end + -- triangulate the polygon so we can work with it + self._Triangles = self:_Triangulate() + -- set the polygon's surface area + self.SurfaceArea = self:_CalculateSurfaceArea() return self end +--- Calculates the surface area of the polygon. The surface area is the sum of the areas of the triangles that make up the polygon. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua +-- @return #number The surface area of the polygon +function ZONE_POLYGON_BASE:_CalculateSurfaceArea() + local area = 0 + for _, triangle in pairs(self._Triangles) do + area = area + triangle.SurfaceArea + end + return area +end + --- Returns the center location of the polygon. -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The location of the zone based on the @{Wrapper.Group} location. @@ -2233,64 +2656,78 @@ function ZONE_POLYGON_BASE:BoundZone( UnBound ) return self end ---- Draw the zone on the F10 map. **NOTE** Currently, only polygons **up to ten points** are supported! +--- Draw the zone on the F10 map. Infinite number of points supported +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1=All. -- @param #table Color RGB color table {r, g, b}, e.g. {1,0,0} for red. -- @param #number Alpha Transparency [0,1]. Default 1. --- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. --- @param #number FillAlpha Transparency [0,1]. Default 0.15. +-- @param #table FillColor RGB color table {r, g, b}, e.g. {1,0,0} for red. Default is same as `Color` value. -- doesn't seem to work +-- @param #number FillAlpha Transparency [0,1]. Default 0.15. -- doesn't seem to work -- @param #number LineType Line type: 0=No line, 1=Solid, 2=Dashed, 3=Dotted, 4=Dot dash, 5=Long dash, 6=Two dash. Default 1=Solid. -- @param #boolean ReadOnly (Optional) Mark is readonly and cannot be removed by users. Default false. -- @return #ZONE_POLYGON_BASE self -function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) +function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly, IncludeTriangles) + if self._.Polygon and #self._.Polygon >= 3 then + Coalition = Coalition or self:GetDrawCoalition() - if self._.Polygon and #self._.Polygon>=3 then + -- Set draw coalition. + self:SetDrawCoalition(Coalition) - local coordinate=COORDINATE:NewFromVec2(self._.Polygon[1]) + Color = Color or self:GetColorRGB() + Alpha = Alpha or 1 - Coalition=Coalition or self:GetDrawCoalition() + -- Set color. + self:SetColor(Color, Alpha) - -- Set draw coalition. - self:SetDrawCoalition(Coalition) + FillColor = FillColor or self:GetFillColorRGB() + if not FillColor then + UTILS.DeepCopy(Color) + end + FillAlpha = FillAlpha or self:GetFillColorAlpha() + if not FillAlpha then + FillAlpha = 0.15 + end - Color=Color or self:GetColorRGB() - Alpha=Alpha or 1 + -- Set fill color -----------> has fill color worked in recent versions of DCS? + -- doing something like + -- + -- trigger.action.markupToAll(7, -1, 501, p.Coords[1]:GetVec3(), p.Coords[2]:GetVec3(),p.Coords[3]:GetVec3(),p.Coords[4]:GetVec3(),{1,0,0, 1}, {1,0,0, 1}, 4, false, Text or "") + -- + -- doesn't seem to fill in the shape for an n-sided polygon + self:SetFillColor(FillColor, FillAlpha) - -- Set color. - self:SetColor(Color, Alpha) - - FillColor=FillColor or self:GetFillColorRGB() - if not FillColor then UTILS.DeepCopy(Color) end - FillAlpha=FillAlpha or self:GetFillColorAlpha() - if not FillAlpha then FillAlpha=0.15 end - - -- Set fill color. - self:SetFillColor(FillColor, FillAlpha) - - if #self._.Polygon==4 then - - local Coord2=COORDINATE:NewFromVec2(self._.Polygon[2]) - local Coord3=COORDINATE:NewFromVec2(self._.Polygon[3]) - local Coord4=COORDINATE:NewFromVec2(self._.Polygon[4]) - - self.DrawID=coordinate:QuadToAll(Coord2, Coord3, Coord4, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) - - else - - local Coordinates=self:GetVerticiesCoordinates() - table.remove(Coordinates, 1) - - self.DrawID=coordinate:MarkupToAllFreeForm(Coordinates, Coalition, Color, Alpha, FillColor, FillAlpha, LineType, ReadOnly) + IncludeTriangles = IncludeTriangles or false + -- just draw the triangles, we get the outline for free + if IncludeTriangles then + for _, triangle in pairs(self._Triangles) do + local draw_ids = triangle:Draw() + table.combine(self.DrawID, draw_ids) + end + -- draw outline only + else + local coords = self:GetVerticiesCoordinates() + for i = 1, #coords do + local c1 = coords[i] + local c2 = coords[i % #coords + 1] + table.add(self.DrawID, c1:LineToAll(c2, Coalition, Color, Alpha, LineType, ReadOnly)) + end + end end - - end - - return self + return self end ---- Get the smallest radius encompassing all points of the polygon zone. +--- Get the surface area of this polygon +-- @param #ZONE_POLYGON_BASE self +-- @return #number Surface area +function ZONE_POLYGON_BASE:GetSurfaceArea() + return self.SurfaceArea +end + + + +--- Get the smallest radius encompassing all points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @return #number Radius of the zone in meters. function ZONE_POLYGON_BASE:GetRadius() @@ -2298,22 +2735,22 @@ function ZONE_POLYGON_BASE:GetRadius() local center=self:GetVec2() local radius=0 - + for _,_vec2 in pairs(self._.Polygon) do local vec2=_vec2 --DCS#Vec2 - + local r=UTILS.VecDist2D(center, vec2) - + if r>radius then radius=r end - + end return radius end ---- Get the smallest circular zone encompassing all points of the polygon zone. +--- Get the smallest circular zone encompassing all points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. -- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. @@ -2323,25 +2760,25 @@ function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone) local center=self:GetVec2() local radius=self:GetRadius() - + local zone=ZONE_RADIUS:New(ZoneName or self.ZoneName, center, radius, DoNotRegisterZone) return zone end ---- Get the smallest rectangular zone encompassing all points points of the polygon zone. +--- Get the smallest rectangular zone encompassing all points points of the polygon zone. -- @param #ZONE_POLYGON_BASE self -- @param #string ZoneName (Optional) Name of the zone. Default is the name of the polygon zone. -- @param #boolean DoNotRegisterZone (Optional) If `true`, zone is not registered. -- @return #ZONE_POLYGON The rectangular zone. function ZONE_POLYGON_BASE:GetZoneQuad(ZoneName, DoNotRegisterZone) - + local vec1, vec3=self:GetBoundingVec2() - + local vec2={x=vec1.x, y=vec3.y} local vec4={x=vec3.x, y=vec1.y} - + local zone=ZONE_POLYGON_BASE:New(ZoneName or self.ZoneName, {vec1, vec2, vec3, vec4}) return zone @@ -2354,15 +2791,15 @@ end function ZONE_POLYGON_BASE:RemoveJunk(Height) Height=Height or 1000 - + local vec2SW, vec2NE=self:GetBoundingVec2() local vec3SW={x=vec2SW.x, y=-Height, z=vec2SW.y} --DCS#Vec3 local vec3NE={x=vec2NE.x, y= Height, z=vec2NE.y} --DCS#Vec3 - + --local coord1=COORDINATE:NewFromVec3(vec3SW):MarkToAll("SW") --local coord1=COORDINATE:NewFromVec3(vec3NE):MarkToAll("NE") - + local volume = { id = world.VolumeType.BOX, params = { @@ -2371,7 +2808,7 @@ function ZONE_POLYGON_BASE:RemoveJunk(Height) } } - local n=world.removeJunk(volume) + local n=world.removeJunk(volume) return n end @@ -2449,7 +2886,7 @@ end -- @return #boolean true if the location is within the zone. function ZONE_POLYGON_BASE:IsVec2InZone( Vec2 ) self:F2( Vec2 ) - if not Vec2 then return false end + if not Vec2 then return false end local Next local Prev local InPolygon = false @@ -2479,40 +2916,34 @@ end -- @return #boolean true if the point is within the zone. function ZONE_POLYGON_BASE:IsVec3InZone( Vec3 ) self:F2( Vec3 ) - - if not Vec3 then return false end - + + if not Vec3 then return false end + local InZone = self:IsVec2InZone( { x = Vec3.x, y = Vec3.z } ) return InZone end --- Define a random @{DCS#Vec2} within the zone. +--- ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Polygon.lua -- @param #ZONE_POLYGON_BASE self -- @return DCS#Vec2 The Vec2 coordinate. function ZONE_POLYGON_BASE:GetRandomVec2() - - -- It is a bit tricky to find a random point within a polygon. Right now i am doing it the dirty and inefficient way... - - -- Get the bounding square. - local BS = self:GetBoundingSquare() - - local Nmax=1000 ; local n=0 - while n= random_weight then + return triangle:GetRandomVec2() + end + end end --- Return a @{Core.Point#POINT_VEC2} object representing a random 2D point at landheight within the zone. @@ -2597,7 +3028,7 @@ function ZONE_POLYGON_BASE:GetBoundingVec2() y2 = ( y2 < self._.Polygon[i].y ) and self._.Polygon[i].y or y2 end - + local vec1={x=x1, y=y1} local vec2={x=x2, y=y2} @@ -2649,7 +3080,8 @@ end -- @extends #ZONE_POLYGON_BASE ---- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +--- The ZONE_POLYGON class defined by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon, OR by drawings made with the Draw tool +--- in the Mission Editor -- This class implements the inherited functions from @{#ZONE_RADIUS} taking into account the own zone format and properties. -- -- ## Declare a ZONE_POLYGON directly in the DCS mission editor! @@ -2672,6 +3104,13 @@ end -- then SetZone would contain the ZONE_POLYGON object `DefenseZone` as part of the zone collection, -- without much scripting overhead! -- +-- This class now also supports drawings made with the Draw tool in the Mission Editor. Any drawing made with Line > Segments > Closed, Polygon > Rect or Polygon > Free can be +-- made into a ZONE_POLYGON. +-- +-- This class has been updated to use a accurate way of generating random points inside the polygon without having to use trial and error guesses. +-- You can also get the surface area of the polygon now, handy if you want measure which coalition has the largest captured area, for example. + + -- @field #ZONE_POLYGON ZONE_POLYGON = { ClassName="ZONE_POLYGON", @@ -2732,6 +3171,49 @@ function ZONE_POLYGON:NewFromGroupName( GroupName ) return self end +--- Constructor to create a ZONE_POLYGON instance, taking the name of a drawing made with the draw tool in the Mission Editor. +-- @param #ZONE_POLYGON self +-- @param #string DrawingName The name of the drawing in the Mission Editor +-- @return #ZONE_POLYGON self +function ZONE_POLYGON:NewFromDrawing(DrawingName) + local points = {} + for _, layer in pairs(env.mission.drawings.layers) do + for _, object in pairs(layer["objects"]) do + if object["name"] == DrawingName then + if (object["primitiveType"] == "Line" and object["closed"] == true) or (object["polygonMode"] == "free") then + -- points for the drawings are saved in local space, so add the object's map x and y coordinates to get + -- world space points we can use + for _, point in UTILS.spairs(object["points"]) do + local p = {x = object["mapX"] + point["x"], + y = object["mapY"] + point["y"] } + table.add(points, p) + end + elseif object["polygonMode"] == "rect" then + -- the points for a rect are saved as local coordinates with an angle. To get the world space points from this + -- we need to rotate the points around the center of the rects by an angle. UTILS.RotatePointAroundPivot was + -- committed in an earlier commit + local angle = object["angle"] + local half_width = object["width"] / 2 + local half_height = object["height"] / 2 + + local center = { x = object["mapX"], y = object["mapY"] } + local p1 = UTILS.RotatePointAroundPivot({ x = center.x - half_height, y = center.y + half_width }, center, angle) + local p2 = UTILS.RotatePointAroundPivot({ x = center.x + half_height, y = center.y + half_width }, center, angle) + local p3 = UTILS.RotatePointAroundPivot({ x = center.x + half_height, y = center.y - half_width }, center, angle) + local p4 = UTILS.RotatePointAroundPivot({ x = center.x - half_height, y = center.y - half_width }, center, angle) + + points = {p1, p2, p3, p4} + else + -- something else that might be added in the future + end + end + end + end + local self = BASE:Inherit(self, ZONE_POLYGON_BASE:New(DrawingName, points)) + _EVENTDISPATCHER:CreateEventNewZone(self) + return self +end + --- Find a polygon zone in the _DATABASE using the name of the polygon zone. -- @param #ZONE_POLYGON self From 89a9d1d0a40c1ce37b17929e9d41b04f0f91395f Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 3 Dec 2023 12:01:39 +0100 Subject: [PATCH 16/21] #CONTROLLABLE * Fixed ID issue with AA Missile Attack Range option #POINT * Added methdo to get the BULLSEYE as coordinate --- Moose Development/Moose/Core/Point.lua | 16 ++++++++++++++-- Moose Development/Moose/Wrapper/Controllable.lua | 2 +- 2 files changed, 15 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index 8a811e19f..d120bc35a 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -920,7 +920,7 @@ do -- COORDINATE end - --- Return an angle in radians from the COORDINATE using a direction vector in Vec3 format. + --- Return an angle in radians from the COORDINATE using a **direction vector in Vec3 format**. -- @param #COORDINATE self -- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The angle in radians. @@ -933,10 +933,12 @@ do -- COORDINATE return DirectionRadians end - --- Return an angle in degrees from the COORDINATE using a direction vector in Vec3 format. + --- Return an angle in degrees from the COORDINATE using a **direction vector in Vec3 format**. -- @param #COORDINATE self -- @param DCS#Vec3 DirectionVec3 The direction vector in Vec3 format. -- @return #number DirectionRadians The angle in degrees. + -- @usage + -- local directionAngle = currentCoordinate:GetAngleDegrees(currentCoordinate:GetDirectionVec3(sourceCoordinate:GetVec3())) function COORDINATE:GetAngleDegrees( DirectionVec3 ) local AngleRadians = self:GetAngleRadians( DirectionVec3 ) local Angle = UTILS.ToDegree( AngleRadians ) @@ -3021,6 +3023,16 @@ do -- COORDINATE return BRAANATO end + --- Return the BULLSEYE as COORDINATE Object + -- @param #number Coalition Coalition of the bulls eye to return, e.g. coalition.side.BLUE + -- @return #COORDINATE self + -- @usage + -- -- note the dot (.) here,not using the colon (:) + -- local redbulls = COORDINATE.GetBullseyeCoordinate(coalition.side.RED) + function COORDINATE.GetBullseyeCoordinate(Coalition) + return COORDINATE:NewFromVec3( coalition.getMainRefPoint( Coalition ) ) + end + --- Return a BULLS string out of the BULLS of the coalition to the COORDINATE. -- @param #COORDINATE self -- @param DCS#coalition.side Coalition The coalition. diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index ded42ebfd..e7d4ca3c9 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -3974,7 +3974,7 @@ function CONTROLLABLE:OptionAAAttackRange( range ) local Controller = self:_GetController() if Controller then if self:IsAir() then - self:SetOption( AI.Option.Air.val.MISSILE_ATTACK, range ) + self:SetOption( AI.Option.Air.id.MISSILE_ATTACK, range ) end end return self From c97d2ecabaffdb58f2fcaba6b4acec07a9957d0e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 3 Dec 2023 12:11:22 +0100 Subject: [PATCH 17/21] #ATIS - multi freq example added --- Moose Development/Moose/Ops/ATIS.lua | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 3e0c258f2..6819ee36c 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -312,10 +312,16 @@ -- -- atis=ATIS:New("Batumi", 305, radio.modulation.AM) -- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US") --- atis:Start() +-- atis:Start() -- -- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Note that backslashes need to be escaped or simply use slashes (as in linux). -- +-- ### SRS can use multiple frequencies: +-- +-- atis=ATIS:New("Batumi", {305,103.85}, {radio.modulation.AM,radio.modulation.FM}) +-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US") +-- atis:Start() +-- -- ### SRS Localization -- -- You can localize the SRS output, all you need is to provide a table of translations and set the `locale` of your instance. You need to provide the translations in your script **before you instantiate your ATIS**. @@ -884,13 +890,14 @@ _ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version = "0.10.3" +ATIS.version = "0.10.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO: Correct fog for elevation. +-- DONE: Option to add multiple frequencies for SRS -- DONE: Zulu time --> Zulu in output. -- DONE: Fix for AB not having a runway - Helopost like Naqoura -- DONE: Add new Normandy airfields. @@ -899,7 +906,7 @@ ATIS.version = "0.10.3" -- DONE: Visibility reported twice over SRS -- DONE: Add text report for output. -- DONE: Add stop FMS functions. --- NOGO: Use local time. Not realisitc! +-- NOGO: Use local time. Not realistic! -- DONE: Dew point. Approx. done. -- DONE: Metric units. -- DONE: Set UTC correction. @@ -915,8 +922,8 @@ ATIS.version = "0.10.3" --- Create a new ATIS class object for a specific airbase. -- @param #ATIS self -- @param #string AirbaseName Name of the airbase. --- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. --- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. +-- @param #number Frequency Radio frequency in MHz. Default 143.00 MHz. When using **SRS** this can be passed as a table of multiple frequencies. +-- @param #number Modulation Radio modulation: 0=AM, 1=FM. Default 0=AM. See `radio.modulation.AM` and `radio.modulation.FM` enumerators. When using **SRS** this can be passed as a table of multiple modulations. -- @return #ATIS self function ATIS:New(AirbaseName, Frequency, Modulation) From c22304f2b0b4e6585b0cdbcbc5feb688abc29fff Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 3 Dec 2023 12:25:25 +0100 Subject: [PATCH 18/21] Remove demo links which were empty --- Moose Development/Moose/Core/Point.lua | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index d120bc35a..3911a52f3 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -8,22 +8,6 @@ -- -- === -- --- # Demo Missions --- --- ### [POINT_VEC Demo Missions source code]() --- --- ### [POINT_VEC Demo Missions, only for beta testers]() --- --- ### [ALL Demo Missions pack of the last release](https://github.com/FlightControl-Master/MOOSE_MISSIONS/releases) --- --- === --- --- # YouTube Channel --- --- ### [POINT_VEC YouTube Channel]() --- --- === --- -- ### Authors: -- -- * FlightControl (Design & Programming) From f739062463177899f91ff8f6ae90f62b5dec98c3 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 3 Dec 2023 12:39:08 +0100 Subject: [PATCH 19/21] #ZONE_OVAL - fix documentation and intellisense --- Moose Development/Moose/Core/Zone.lua | 29 +++++++++++++++++++-------- 1 file changed, 21 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 6014c7f92..cb2648d0d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -42,6 +42,7 @@ -- * @{#ZONE_UNIT}: The ZONE_UNIT class defines by a zone around a @{Wrapper.Unit#UNIT} with a radius. -- * @{#ZONE_GROUP}: The ZONE_GROUP class defines by a zone around a @{Wrapper.Group#GROUP} with a radius. -- * @{#ZONE_POLYGON}: The ZONE_POLYGON class defines by a sequence of @{Wrapper.Group#GROUP} waypoints within the Mission Editor, forming a polygon. +-- * @{#ZONE_OVAL}: The ZONE_OVAL class isdefined by a center point, major axis, minor axis, and angle. -- -- === -- @@ -1998,13 +1999,17 @@ function ZONE_GROUP:GetRandomPointVec2( inner, outer ) end ---- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Triangle.lua ---- This triangle "zone" is not really to be used on its own, it only serves as building blocks for ---- ZONE_POLYGON to accurately find a point inside a polygon; as well as getting the correct surface area of ---- a polygon. --- @type _ZONE_TRIANGLE --- @extends #BASE +--- ZONE_OVAL created from a center point, major axis, minor axis, and angle. +-- Ported from https://github.com/nielsvaes/CCMOOSE/blob/master/Moose%20Development/Moose/Shapes/Oval.lua +-- @type ZONE_OVAL +-- @extends Core.Zone#ZONE_BASE +--- ## ZONE_OVAL class, extends @{#ZONE_BASE} +-- +-- The ZONE_OVAL class is defined by a center point, major axis, minor axis, and angle. +-- This class implements the inherited functions from @{#ZONE_BASE} taking into account the own zone format and properties. +-- +-- @field #ZONE_OVAL ZONE_OVAL = { ClassName = "OVAL", ZoneName="", @@ -2060,31 +2065,36 @@ function ZONE_OVAL:NewFromDrawing(DrawingName) return self end ---- Gets the major axis of the oval. +--- Gets the major axis of the oval. +-- @param #ZONE_OVAL self -- @return #number The major axis of the oval function ZONE_OVAL:GetMajorAxis() return self.MajorAxis end --- Gets the minor axis of the oval. +-- @param #ZONE_OVAL self -- @return #number The minor axis of the oval function ZONE_OVAL:GetMinorAxis() return self.MinorAxis end --- Gets the angle of the oval. +-- @param #ZONE_OVAL self -- @return #number The angle of the oval function ZONE_OVAL:GetAngle() return self.Angle end --- Returns a the center point of the oval +-- @param #ZONE_OVAL self -- @return #table The center Vec2 function ZONE_OVAL:GetVec2() return self.CenterVec2 end --- Checks if a point is contained within the oval. +-- @param #ZONE_OVAL self -- @param #table point The point to check -- @return #bool True if the point is contained, false otherwise function ZONE_OVAL:IsVec2InZone(vec2) @@ -2097,6 +2107,7 @@ function ZONE_OVAL:IsVec2InZone(vec2) end --- Calculates the bounding box of the oval. The bounding box is the smallest rectangle that contains the oval. +-- @param #ZONE_OVAL self -- @return #table The bounding box of the oval function ZONE_OVAL:GetBoundingSquare() local min_x = self.CenterVec2.x - self.MajorAxis @@ -2110,6 +2121,7 @@ function ZONE_OVAL:GetBoundingSquare() end --- Find points on the edge of the oval +-- @param #ZONE_OVAL self -- @param #number num_points How many points should be found. More = smoother shape -- @return #table Points on he edge function ZONE_OVAL:PointsOnEdge(num_points) @@ -2128,6 +2140,7 @@ function ZONE_OVAL:PointsOnEdge(num_points) end --- Returns a random Vec2 within the oval. +-- @param #ZONE_OVAL self -- @return #table The random Vec2 function ZONE_OVAL:GetRandomVec2() local theta = math.rad(self.Angle) @@ -2206,6 +2219,7 @@ function ZONE_OVAL:DrawZone(Coalition, Color, Alpha, FillColor, FillAlpha, LineT end --- Remove drawing from F10 map +-- @param #ZONE_OVAL self function ZONE_OVAL:UndrawZone() if self.DrawPoly then self.DrawPoly:UndrawZone() @@ -2219,7 +2233,6 @@ end --- a polygon. -- @type _ZONE_TRIANGLE -- @extends #BASE - _ZONE_TRIANGLE = { ClassName="ZONE_TRIANGLE", Points={}, From 49191fb144330e7d072941c5f4184eac1d9e7b5d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 3 Dec 2023 15:34:55 +0100 Subject: [PATCH 20/21] clarifications --- Moose Development/Moose/Core/Set.lua | 10 +++++++++- Moose Development/Moose/Functional/Range.lua | 12 ++++++++++-- 2 files changed, 19 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index ee0917406..fec517906 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1065,8 +1065,15 @@ do self:FilterActive( false ) return self + + --- Filter the set once + -- @function [parent=#SET_GROUP] FilterOnce + -- @param #SET_GROUP self + -- @return #SET_GROUP self + + end - + --- Get a *new* set that only contains alive groups. -- @param #SET_GROUP self -- @return #SET_GROUP Set of alive groups. @@ -1976,6 +1983,7 @@ do --- Get the closest group of the set with respect to a given reference coordinate. Optionally, only groups of given coalitions are considered in the search. -- @param #SET_GROUP self -- @param Core.Point#COORDINATE Coordinate Reference Coordinate from which the closest group is determined. + -- @param #table Coalitions (Optional) Table of coalition #number entries to filter for. -- @return Wrapper.Group#GROUP The closest group (if any). -- @return #number Distance in meters to the closest group. function SET_GROUP:GetClosestGroup(Coordinate, Coalitions) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index e9005ace0..e238914a9 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -1234,7 +1234,7 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, return self end ---- (SRS) Set range control frequency and voice. +--- (SRS) Set range control frequency and voice. Use `RANGE:SetSRS()` once first before using this function. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 256 MHz. -- @param #number modulation Modulation, defaults to radio.modulation.AM. @@ -1244,6 +1244,10 @@ end -- @param #string relayunitname Name of the unit used for transmission location. -- @return #RANGE self function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender, relayunitname ) + if not self.instructmsrs then + self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeControl!") + return self + end self.rangecontrolfreq = frequency or 256 self.controlmsrs:SetFrequencies(self.rangecontrolfreq) self.controlmsrs:SetModulations(modulation or radio.modulation.AM) @@ -1259,7 +1263,7 @@ function RANGE:SetSRSRangeControl( frequency, modulation, voice, culture, gender return self end ---- (SRS) Set range instructor frequency and voice. +--- (SRS) Set range instructor frequency and voice. Use `RANGE:SetSRS()` once first before using this function. -- @param #RANGE self -- @param #number frequency Frequency in MHz. Default 305 MHz. -- @param #number modulation Modulation, defaults to radio.modulation.AM. @@ -1269,6 +1273,10 @@ end -- @param #string relayunitname Name of the unit used for transmission location. -- @return #RANGE self function RANGE:SetSRSRangeInstructor( frequency, modulation, voice, culture, gender, relayunitname ) + if not self.instructmsrs then + self:E(self.lid.."Use myrange:SetSRS() once first before using myrange:SetSRSRangeInstructor!") + return self + end self.instructorfreq = frequency or 305 self.instructmsrs:SetFrequencies(self.instructorfreq) self.instructmsrs:SetModulations(modulation or radio.modulation.AM) From cca5a5d55d8a4429f8a79843c79120681b9ecc29 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 3 Dec 2023 20:51:19 +0100 Subject: [PATCH 21/21] Fixes - Fixed A/C starting on ALERT5 - Fixed nil check for DrawID in UndrawZone --- Moose Development/Moose/Core/Zone.lua | 10 ++++++---- Moose Development/Moose/Ops/FlightGroup.lua | 3 +++ 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index cb2648d0d..229356455 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -480,12 +480,14 @@ function ZONE_BASE:UndrawZone(Delay) if Delay and Delay>0 then self:ScheduleOnce(Delay, ZONE_BASE.UndrawZone, self) else - if self.DrawID and type(self.DrawID) ~= "table" then - UTILS.RemoveMark(self.DrawID) - else -- DrawID is a table with a collections of mark ids, as used in ZONE_POLYGON + if self.DrawID then + if type(self.DrawID) ~= "table" then + UTILS.RemoveMark(self.DrawID) + else -- DrawID is a table with a collections of mark ids, as used in ZONE_POLYGON for _, mark_id in pairs(self.DrawID) do - UTILS.RemoveMark(mark_id) + UTILS.RemoveMark(mark_id) end + end end end return self diff --git a/Moose Development/Moose/Ops/FlightGroup.lua b/Moose Development/Moose/Ops/FlightGroup.lua index ad115cdc2..25a6b8ef2 100644 --- a/Moose Development/Moose/Ops/FlightGroup.lua +++ b/Moose Development/Moose/Ops/FlightGroup.lua @@ -2863,8 +2863,11 @@ function FLIGHTGROUP:_CheckGroupDone(delay, waittime) end else + -- Check if not parking (could be on ALERT5 and just spawned (current mission=nil) + if not self:IsParking() then self:T(self.lid..string.format("Passed Final WP but Tasks=%d or Missions=%d left in the queue. Wait!", nTasks, nMissions)) self:__Wait(-1) + end end else self:T(self.lid..string.format("Passed Final WP but still have current Task (#%s) or Mission (#%s) left to do", tostring(self.taskcurrent), tostring(self.currentmission)))