From 2538d583ad8895c17de86f04d5115274658b3cf7 Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 19 Nov 2022 19:36:55 +0100 Subject: [PATCH 01/66] Update Suppression.lua - Fixed routing of group (passing waypoint function was triggered too early due to changed DCS behaviour) --- .../Moose/Functional/Suppression.lua | 95 ++++++------------- 1 file changed, 29 insertions(+), 66 deletions(-) diff --git a/Moose Development/Moose/Functional/Suppression.lua b/Moose Development/Moose/Functional/Suppression.lua index 1698f0c0a..8c6b88a93 100644 --- a/Moose Development/Moose/Functional/Suppression.lua +++ b/Moose Development/Moose/Functional/Suppression.lua @@ -85,6 +85,7 @@ -- @field #boolean eventmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. Default true. -- @field Core.Zone#ZONE BattleZone -- @field #boolean AutoEngage +-- @field #table waypoints Waypoints of the group as defined in the ME. -- @extends Core.Fsm#FSM_CONTROLLABLE -- @@ -265,6 +266,7 @@ SUPPRESSION={ DefaultAlarmState = "Auto", DefaultROE = "Weapon Free", eventmoose = true, + waypoints = {}, } --- Enumerator of possible rules of engagement. @@ -295,7 +297,7 @@ SUPPRESSION.MenuF10=nil --- PSEUDOATC version. -- @field #number version -SUPPRESSION.version="0.9.3" +SUPPRESSION.version="0.9.4" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -309,7 +311,7 @@ SUPPRESSION.version="0.9.3" --- Creates a new AI_suppression object. -- @param #SUPPRESSION self -- @param Wrapper.Group#GROUP group The GROUP object for which suppression should be applied. --- @return #SUPPRESSION SUPPRESSION object or *nil* if group does not exist or is not a ground group. +-- @return #SUPPRESSION self function SUPPRESSION:New(group) -- Inherits from FSM_CONTROLLABLE @@ -320,7 +322,7 @@ function SUPPRESSION:New(group) self.lid=string.format("SUPPRESSION %s | ", tostring(group:GetName())) self:T(self.lid..string.format("SUPPRESSION version %s. Activating suppressive fire for group %s", SUPPRESSION.version, group:GetName())) else - self:E(self.lid.."SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group.)") + self:E("SUPPRESSION | Requested group does not exist! (Has to be a MOOSE group)") return nil end @@ -1186,6 +1188,16 @@ function SUPPRESSION:onafterFightBack(Controllable, From, Event, To) -- Set ROE and alarm state back to default. self:_SetROE() self:_SetAlarmState() + + local group=Controllable --Wrapper.Group#GROUP + + local Waypoints = group:GetTemplateRoutePoints() + +-- env.info("FF waypoints",showMessageBox) +-- self:I(Waypoints) + + group:Route(Waypoints, 5) + end ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -1251,7 +1263,7 @@ function SUPPRESSION:onafterFallBack(Controllable, From, Event, To, AttackUnit) self:_SetROE(SUPPRESSION.ROE.Hold) -- Set alarm state to GREEN and let the unit run away. - self:_SetAlarmState(SUPPRESSION.AlarmState.Green) + self:_SetAlarmState(SUPPRESSION.AlarmState.Auto) -- Make the group run away. self:_Run(Coord, self.Speed, self.Formation, self.FallbackWait) @@ -1537,7 +1549,7 @@ end -- @param #SUPPRESSION self -- @param Core.Event#EVENTDATA EventData function SUPPRESSION:_OnEventHit(EventData) - self:F(EventData) + self:F3(EventData) local GroupNameSelf=self.Controllable:GetName() local GroupNameTgt=EventData.TgtGroupName @@ -1676,15 +1688,15 @@ end function SUPPRESSION:_Run(fin, speed, formation, wait) speed=speed or 20 - formation=formation or "Off road" + formation=formation or ENUMS.Formation.Vehicle.OffRoad wait=wait or 30 - local group=self.Controllable -- Wrapper.Controllable#CONTROLLABLE + local group=self.Controllable -- Wrapper.Group#GROUP if group and group:IsAlive() then -- Clear all tasks. - group:ClearTasks() + --group:ClearTasks() -- Current coordinates of group. local ini=group:GetCoordinate() @@ -1694,57 +1706,18 @@ function SUPPRESSION:_Run(fin, speed, formation, wait) -- Heading from ini to fin. local heading=self:_Heading(ini, fin) - - -- Number of waypoints. - local nx - if dist <= 50 then - nx=2 - elseif dist <= 100 then - nx=3 - elseif dist <= 500 then - nx=4 - else - nx=5 - end - - -- Number of intermediate waypoints. - local dx=dist/(nx-1) - + -- Waypoint and task arrays. local wp={} local tasks={} -- First waypoint is the current position of the group. wp[1]=ini:WaypointGround(speed, formation) - tasks[1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, 1, false) if self.Debug then local MarkerID=ini:MarkToAll(string.format("Waypoing %d of group %s (initial)", #wp, self.Controllable:GetName())) end - self:T2(self.lid..string.format("Number of waypoints %d", nx)) - for i=1,nx-2 do - - local x=dx*i - local coord=ini:Translate(x, heading) - - wp[#wp+1]=coord:WaypointGround(speed, formation) - tasks[#tasks+1]=group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, false) - - self:T2(self.lid..string.format("%d x = %4.1f", i, x)) - if self.Debug then - local MarkerID=coord:MarkToAll(string.format("Waypoing %d of group %s", #wp, self.Controllable:GetName())) - end - - end - self:T2(self.lid..string.format("Total distance: %4.1f", dist)) - - -- Final waypoint. - wp[#wp+1]=fin:WaypointGround(speed, formation) - if self.Debug then - local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)", #wp, self.Controllable:GetName())) - end - -- Task to hold. local ConditionWait=group:TaskCondition(nil, nil, nil, nil, wait, nil) local TaskHold = group:TaskHold() @@ -1753,25 +1726,15 @@ function SUPPRESSION:_Run(fin, speed, formation, wait) local TaskComboFin = {} TaskComboFin[#TaskComboFin+1] = group:TaskFunction("SUPPRESSION._Passing_Waypoint", self, #wp, true) TaskComboFin[#TaskComboFin+1] = group:TaskControlled(TaskHold, ConditionWait) - - -- Add final task. - tasks[#tasks+1]=group:TaskCombo(TaskComboFin) - - -- Original waypoints of the group. - local Waypoints = group:GetTemplateRoutePoints() - -- New points are added to the default route. - for i,p in ipairs(wp) do - table.insert(Waypoints, i, wp[i]) - end - - -- Set task for all waypoints. - for i,wp in ipairs(Waypoints) do - group:SetTaskWaypoint(Waypoints[i], tasks[i]) - end + -- Final waypoint. + wp[#wp+1]=fin:WaypointGround(speed, formation, TaskComboFin) + if self.Debug then + local MarkerID=fin:MarkToAll(string.format("Waypoing %d of group %s (final)", #wp, self.Controllable:GetName())) + end -- Submit task and route group along waypoints. - group:Route(Waypoints) + group:Route(wp) else self:E(self.lid..string.format("ERROR: Group is not alive!")) @@ -1790,7 +1753,7 @@ function SUPPRESSION._Passing_Waypoint(group, Fsm, i, final) local text=string.format("Group %s passing waypoint %d (final=%s)", group:GetName(), i, tostring(final)) MESSAGE:New(text,10):ToAllIf(Fsm.Debug) if Fsm.Debug then - env.info(self.lid..text) + env.info(Fsm.lid..text) end if final then @@ -1891,7 +1854,7 @@ function SUPPRESSION:_GetLife() local groupstrength=#units/self.IniGroupStrength*100 - self.T2(self.lid..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units)) + self:T2(self.lid..string.format("Group %s _GetLife nunits = %d", self.Controllable:GetName(), #units)) for _,unit in pairs(units) do From 6834a2e083a723bd49d68d3cfdcf4d97e04a472a Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 20 Nov 2022 20:55:34 +0100 Subject: [PATCH 02/66] Spawning FARPS **SPAWNSTATIC** - Added event birth when static FARPS are spawned **EVENT** - Unknown airbases as inititiator are registered --- Moose Development/Moose/Core/Database.lua | 30 ++++++++--- Moose Development/Moose/Core/Event.lua | 11 ++-- Moose Development/Moose/Core/SpawnStatic.lua | 13 ++++- Moose Development/Moose/DCS.lua | 56 ++++++++++++++++++-- Moose Development/Moose/Wrapper/Airbase.lua | 3 ++ 5 files changed, 100 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index 1b5fba242..afa1d6a64 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -1027,11 +1027,26 @@ function DATABASE:_RegisterAirbases() for DCSAirbaseId, DCSAirbase in pairs(world.getAirbases()) do + self:_RegisterAirbase(DCSAirbase) + + end + + return self +end + +--- Register a DCS airbase. +-- @param #DATABASE self +-- @param DCS#Airbase airbase Airbase. +-- @return #DATABASE self +function DATABASE:_RegisterAirbase(airbase) + + if airbase then + -- Get the airbase name. - local DCSAirbaseName = DCSAirbase:getName() + local DCSAirbaseName = airbase:getName() -- This gave the incorrect value to be inserted into the airdromeID for DCS 2.5.6. Is fixed now. - local airbaseID=DCSAirbase:getID() + local airbaseID=airbase:getID() -- Add and register airbase. local airbase=self:AddAirbase( DCSAirbaseName ) @@ -1065,20 +1080,23 @@ function DATABASE:_EventOnBirth( Event ) if Event.IniDCSUnit then - if Event.IniObjectCategory == 3 then + if Event.IniObjectCategory == Object.Category.STATIC then + -- Add static object to DB. self:AddStatic( Event.IniDCSUnitName ) else - if Event.IniObjectCategory == 1 then + if Event.IniObjectCategory == Object.Category.UNIT then + -- Add unit and group to DB. self:AddUnit( Event.IniDCSUnitName ) self:AddGroup( Event.IniDCSGroupName ) - -- Add airbase if it was spawned later in the mission. + -- A unit can also be an airbase (e.g. ships). local DCSAirbase = Airbase.getByName(Event.IniDCSUnitName) if DCSAirbase then + -- Add airbase if it was spawned later in the mission. self:I(string.format("Adding airbase %s", tostring(Event.IniDCSUnitName))) self:AddAirbase(Event.IniDCSUnitName) end @@ -1086,7 +1104,7 @@ function DATABASE:_EventOnBirth( Event ) end end - if Event.IniObjectCategory == 1 then + if Event.IniObjectCategory == Object.Category.UNIT then Event.IniUnit = self:FindUnit( Event.IniDCSUnitName ) Event.IniGroup = self:FindGroup( Event.IniDCSGroupName ) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index d59f1c244..9b334bf2a 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1130,7 +1130,7 @@ function EVENT:onEvent( Event ) Event.IniUnitName = Event.IniDCSUnitName Event.IniDCSGroup = Event.IniDCSUnit:getGroup() Event.IniUnit = UNIT:FindByName( Event.IniDCSUnitName ) - + if not Event.IniUnit then -- Unit can be a CLIENT. Most likely this will be the case ... Event.IniUnit = CLIENT:FindByName( Event.IniDCSUnitName, '', true ) @@ -1165,8 +1165,7 @@ function EVENT:onEvent( Event ) if Event.IniObjectCategory == Object.Category.SCENERY then --- -- Scenery - --- - + --- Event.IniDCSUnit = Event.initiator Event.IniDCSUnitName = Event.IniDCSUnit:getName() Event.IniUnitName = Event.IniDCSUnitName @@ -1186,6 +1185,12 @@ function EVENT:onEvent( Event ) Event.IniCoalition = Event.IniDCSUnit:getCoalition() Event.IniCategory = Event.IniDCSUnit:getDesc().category Event.IniTypeName = Event.IniDCSUnit:getTypeName() + + -- If the airbase does not exist in the DB, we add it (e.g. when FARPS are spawned). + if not Event.IniUnit then + _DATABASE:_RegisterAirbase(Event.initiator) + Event.IniUnit = AIRBASE:FindByName(Event.IniDCSUnitName) + end end end diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index 8508c760f..d73336464 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -467,7 +467,7 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) self:T(Template) -- Add static to the game. - local Static=nil + local Static=nil --DCS#StaticObject if self.InitFarp then @@ -487,6 +487,17 @@ function SPAWNSTATIC:_SpawnStatic(Template, CountryID) -- ED's dirty way to spawn FARPS. Static=coalition.addGroup(CountryID, -1, TemplateGroup) + + -- Currently DCS 2.8 does not trigger birth events if FAPRS are spawned! + -- We create such an event. The airbase is registered in Core.Event + local Event = { + id = EVENTS.Birth, + time = timer.getTime(), + initiator = Static + } + -- Create BIRTH event. + world.onEvent(Event) + else self:T("Spawning Static") self:T2({Template=Template}) diff --git a/Moose Development/Moose/DCS.lua b/Moose Development/Moose/DCS.lua index bc5a06de1..4db719cdb 100644 --- a/Moose Development/Moose/DCS.lua +++ b/Moose Development/Moose/DCS.lua @@ -341,9 +341,23 @@ do -- coalition -- @field RED -- @field BLUE - --- @function [parent=#coalition] getCountryCoalition - -- @param #number countryId - -- @return #number coalitionId + --- Get country coalition. + -- @function [parent=#coalition] getCountryCoalition + -- @param #number countryId Country ID. + -- @return #number coalitionId Coalition ID. + + --- Dynamically spawns a group. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup) + -- @function [parent=#coalition] addGroup + -- @param #number countryId Id of the country. + -- @param #number groupCategory Group category. Set -1 for spawning FARPS. + -- @param #table groupData Group data table. + -- @return DCS#Group The spawned Group object. + + --- Dynamically spawns a static object. See [hoggit](https://wiki.hoggitworld.com/view/DCS_func_addGroup) + -- @function [parent=#coalition] addStaticObject + -- @param #number countryId Id of the country. + -- @param #table groupData Group data table. + -- @return DCS#Static The spawned static object. coalition = {} -- #coalition @@ -1294,6 +1308,42 @@ do -- Group end -- Group +do -- StaticObject + + --- Represents a static object. + -- @type StaticObject + -- @extends DCS#Object + + --- Returns the static object. + -- @function [parent=#StaticObject] getByName + -- @param #string name Name of the static object. + -- @return #StaticObject + + StaticObject = {} --#StaticObject + +end + +do --Event + + --- Event structure. Note that present fields depend on type of event. + -- @type Event + -- @field #number id Event ID. + -- @field #number time Mission time in seconds. + -- @field DCS#Unit initiator Unit initiating the event. + -- @field DCS#Unit target Target unit. + -- @field DCS#Airbase place Airbase. + -- @field number subPlace Subplace. Unknown and often just 0. + -- @field #string weapon_name Weapoin name. + -- @field #number idx Mark ID. + -- @field #number coalition Coalition ID. + -- @field #number groupID Group ID, *e.g.* of group that added mark point. + -- @field #string text Text, *e.g.* of mark point. + -- @field DCS#Vec3 pos Position vector, *e.g.* of mark point. + -- @field #string comment Comment, *e.g.* LSO score. + + Event={} --#Event + +end do -- AI diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index f4f658477..f6e4c440f 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -685,6 +685,9 @@ function AIRBASE:Register(AirbaseName) else self:E(string.format("ERROR: Cound not get position Vec2 of airbase %s", AirbaseName)) end + + -- Debug info. + self:T2(string.format("Registered airbase %s", tostring(self.AirbaseName))) return self end From eacfbad729f5e5948cb3f71c349cb782a0209664 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Nov 2022 09:48:06 +0100 Subject: [PATCH 03/66] #SPAWN * Fix for unit callsign number duplication since 2.8 release (ED saving callsign.name now as "Texaco11" instead of "Texaco" for the F10 Map overview --- Moose Development/Moose/Core/Spawn.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 9c5225a0b..00265187d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3038,6 +3038,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if type( Callsign ) ~= "number" then -- blue callsign 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 local CallsignLen = CallsignName:len() SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] else From 1e139a6005116777a39b2b190acfc749731531b1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 23 Nov 2022 09:55:47 +0100 Subject: [PATCH 04/66] #SPAWN * Fix callsign dupplication of numbers introduced with 2.8 --- Moose Development/Moose/Core/Spawn.lua | 1 + 1 file changed, 1 insertion(+) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 9c5225a0b..00265187d 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -3038,6 +3038,7 @@ function SPAWN:_Prepare( SpawnTemplatePrefix, SpawnIndex ) -- R2.2 if type( Callsign ) ~= "number" then -- blue callsign 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 local CallsignLen = CallsignName:len() SpawnTemplate.units[UnitID].callsign["name"] = CallsignName:sub( 1, CallsignLen ) .. SpawnTemplate.units[UnitID].callsign[2] .. SpawnTemplate.units[UnitID].callsign[3] else From cec045045e3fe1db1171a6c8d1a89873ef612b48 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 27 Nov 2022 17:29:53 +0100 Subject: [PATCH 05/66] * Less noise --- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index f6e4c440f..a2fd0577a 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -1368,7 +1368,7 @@ function AIRBASE:FindFreeParkingSpotForAircraft(group, terminaltype, scanradius, local _nspots=nspots or group:GetSize() -- Debug info. - self:E(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) + self:T(string.format("%s: Looking for %d parking spot(s) for aircraft of size %.1f m (x=%.1f,y=%.1f,z=%.1f) at terminal type %s.", airport, _nspots, _aircraftsize, ax, ay, az, tostring(terminaltype))) -- Table of valid spots. local validspots={} From eb5a72fc27ab1916ba5ae4d1ea68fb22e40ca711 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 27 Nov 2022 17:35:09 +0100 Subject: [PATCH 06/66] * less noise --- Moose Development/Moose/Ops/ATIS.lua | 2 ++ Moose Development/Moose/Tasking/CommandCenter.lua | 2 +- Moose Development/Moose/Tasking/Mission.lua | 8 ++++---- Moose Development/Moose/Utilities/Utils.lua | 2 ++ Moose Development/Moose/Wrapper/Controllable.lua | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 083aa86a4..9cdf60ec3 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -268,6 +268,8 @@ -- Unfortunately, it is not possible to determine the duration of the complete transmission. So once the transmission is finished, there might be some radio silence before -- the next iteration begins. You can fine tune the time interval between transmissions with the @{#ATIS.SetQueueUpdateTime}() function. The default interval is 90 seconds. -- +-- An SRS Setup-Guide can be found here: [Moose TTS Setup Guide](https://github.com/FlightControl-Master/MOOSE_GUIDES/blob/master/documents/Moose%20TTS%20Setup%20Guide.pdf) +-- -- # Examples -- -- ## Caucasus: Batumi diff --git a/Moose Development/Moose/Tasking/CommandCenter.lua b/Moose Development/Moose/Tasking/CommandCenter.lua index 6562fcb90..8af4786bc 100644 --- a/Moose Development/Moose/Tasking/CommandCenter.lua +++ b/Moose Development/Moose/Tasking/CommandCenter.lua @@ -512,7 +512,7 @@ function COMMANDCENTER:AssignTask( TaskGroup ) if Task then - self:I( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority ) + self:T( "Assigning task " .. Task:GetName() .. " using auto assign method " .. self.AutoAssignMethod .. " to " .. TaskGroup:GetName() .. " with task priority " .. AssignPriority ) if not self.AutoAcceptTasks == true then Task:SetAutoAssignMethod( ACT_ASSIGN_MENU_ACCEPT:New( Task.TaskBriefing ) ) diff --git a/Moose Development/Moose/Tasking/Mission.lua b/Moose Development/Moose/Tasking/Mission.lua index ba84fe162..b0f39072f 100644 --- a/Moose Development/Moose/Tasking/Mission.lua +++ b/Moose Development/Moose/Tasking/Mission.lua @@ -413,7 +413,7 @@ end -- @param Wrapper.Group#GROUP PlayerGroup The GROUP of the player joining the Mission. -- @return #boolean true if Unit is part of a Task in the Mission. function MISSION:JoinUnit( PlayerUnit, PlayerGroup ) - self:I( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) + self:T( { Mission = self:GetName(), PlayerUnit = PlayerUnit, PlayerGroup = PlayerGroup } ) local PlayerUnitAdded = false @@ -571,7 +571,7 @@ do -- Group Assignment local MissionGroupName = MissionGroup:GetName() self.AssignedGroups[MissionGroupName] = MissionGroup - self:I( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) ) + self:T( string.format( "Mission %s is assigned to %s", MissionName, MissionGroupName ) ) return self end @@ -698,7 +698,7 @@ end function MISSION:AddTask( Task ) local TaskName = Task:GetTaskName() - self:I( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } ) + self:T( { "==> Adding TASK ", MissionName = self:GetName(), TaskName = TaskName } ) self.Tasks[TaskName] = Task @@ -717,7 +717,7 @@ end function MISSION:RemoveTask( Task ) local TaskName = Task:GetTaskName() - self:I( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } ) + self:T( { "<== Removing TASK ", MissionName = self:GetName(), TaskName = TaskName } ) self:F( TaskName ) self.Tasks[TaskName] = self.Tasks[TaskName] or { n = 0 } diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index df656431b..dd073f753 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1576,6 +1576,8 @@ function UTILS.GMTToLocalTimeDifference() return 3 -- Damascus is UTC+3 hours elseif theatre==DCSMAP.MarianaIslands then return 10 -- Guam is UTC+10 hours. + elseif theatre==DCSMAP.Falklands then + return -3 -- Fireland is UTC-3 hours. else BASE:E(string.format("ERROR: Unknown Map %s in UTILS.GMTToLocal function. Returning 0", tostring(theatre))) return 0 diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 95c8c4ade..5cf5b4ac2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -2395,7 +2395,7 @@ do -- Route methods -- @return DCS#Task Task. -- @return #boolean If true, path on road is possible. If false, task will route the group directly to its destination. function CONTROLLABLE:TaskGroundOnRoad( ToCoordinate, Speed, OffRoadFormation, Shortcut, FromCoordinate, WaypointFunction, WaypointFunctionArguments ) - self:I( { ToCoordinate = ToCoordinate, Speed = Speed, OffRoadFormation = OffRoadFormation, WaypointFunction = WaypointFunction, Args = WaypointFunctionArguments } ) + self:T( { ToCoordinate = ToCoordinate, Speed = Speed, OffRoadFormation = OffRoadFormation, WaypointFunction = WaypointFunction, Args = WaypointFunctionArguments } ) -- Defaults. Speed = Speed or 20 From 53d71d976678af6dbc7cac5846c721bb0f8ff624 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 28 Nov 2022 17:42:09 +0100 Subject: [PATCH 07/66] Update RAT.lua Fix #1848 --- Moose Development/Moose/Functional/RAT.lua | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index 0643cf01b..a54533eeb 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -5671,6 +5671,9 @@ function RAT:_ATCClearForLanding(airport, flight) -- Debug message. local text1=string.format("ATC %s: Flight %s cleared for landing (flag=%d).", airport, flight, flagvalue) + if string.find(flight,"#") then + flight = string.match(flight,"^(.+)#") + end local text2=string.format("ATC %s: Flight %s you are cleared for landing.", airport, flight) BASE:T( RAT.id..text1) MESSAGE:New(text2, 10):ToAllIf(RAT.ATC.messages) @@ -5713,6 +5716,9 @@ function RAT:_ATCFlightLanded(name) local text1=string.format("ATC %s: Flight %s landed. Tholding = %i:%02d, Tfinal = %i:%02d.", dest, name, Thold/60, Thold%60, Tfinal/60, Tfinal%60) local text2=string.format("ATC %s: Number of flights still on final %d.", dest, RAT.ATC.airport[dest].Nonfinal) local text3=string.format("ATC %s: Traffic report: Number of planes landed in total %d. Flights/hour = %3.2f.", dest, RAT.ATC.airport[dest].traffic, TrafficPerHour) + if string.find(name,"#") then + name = string.match(name,"^(.+)#") + end local text4=string.format("ATC %s: Flight %s landed. Welcome to %s.", dest, name, dest) BASE:T(RAT.id..text1) BASE:T(RAT.id..text2) From df0c0ec21e7a58afa830d748fb5ef8fe25e9dd11 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Nov 2022 15:39:58 +0100 Subject: [PATCH 08/66] #CTLD * Small fix for BEACON Zones --- Moose Development/Moose/Ops/CTLD.lua | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 0a7c38665..e370b430e 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update October 2022 +-- Last Update December 2022 do @@ -1078,7 +1078,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.19" +CTLD.version="1.0.20" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3611,7 +3611,8 @@ function CTLD:CheckDroppedBeacons() for _,_beacon in pairs (self.droppedBeacons) do local beacon = _beacon -- #CTLD.CargoZone - local T0 = beacon.timestamp + if not beacon.timestamp then beacon.timestamp = timer.getTime() end + local T0 = beacon.timestamp if timer.getTime() - T0 > timeout then local name = beacon.name self.droppedbeaconref[name] = nil From df2a6a6902eb790516cb0fe8e2b395e1c61f82e8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 29 Nov 2022 17:53:41 +0100 Subject: [PATCH 09/66] #ATIS * Added `ATIS:GetSRSText()` --- Moose Development/Moose/Ops/ATIS.lua | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 9cdf60ec3..d4c20f948 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -93,6 +93,7 @@ -- @field #number dTQueueCheck Time interval to check the radio queue. Default 5 sec or 90 sec if SRS is used. -- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights -- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players. +-- @field #string SRSText Text of the complete SRS message (if done at least once, else nil) -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -592,7 +593,7 @@ _ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version = "0.9.11" +ATIS.version = "0.9.12" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -880,6 +881,13 @@ function ATIS:SetMapMarks( switch ) return self end +--- Return the complete SRS Text block, if at least generated once. Else nil. +-- @param #ATIS self +-- @return #string SRSText +function ATIS:GetSRSText() + return self.SRSText +end + --- Set magnetic runway headings as depicted on the runway, *e.g.* "13" for 130° or "25L" for the left runway with magnetic heading 250°. -- @param #ATIS self -- @param #table headings Magnetic headings. Inverse (-180°) headings are added automatically. You only need to specify one heading per runway direction. "L"eft and "R" right can also be appended. @@ -2402,7 +2410,8 @@ function ATIS:onafterReport( From, Event, To, Text ) local duration = STTS.getSpeechTime(text,0.95) self.msrsQ:NewTransmission(text,duration,self.msrs,nil,2) --self.msrs:PlayText( text ) - + self.SRSText = text + end end From f17f688a2035a0b439a06b8bbd4222ed72be935e Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 30 Nov 2022 18:37:14 +0100 Subject: [PATCH 10/66] Condition and Message --- Moose Development/Moose/Core/Condition.lua | 72 +++++++++++++++++++++- Moose Development/Moose/Core/Message.lua | 10 +++ 2 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Condition.lua b/Moose Development/Moose/Core/Condition.lua index 8d9824a72..80f80cf52 100644 --- a/Moose Development/Moose/Core/Condition.lua +++ b/Moose Development/Moose/Core/Condition.lua @@ -23,6 +23,7 @@ -- @type CONDITION -- @field #string ClassName Name of the class. -- @field #string lid Class id string for output to DCS log file. +-- @field #string name Name of the condition. -- @field #boolean isAny General functions are evaluated as any condition. -- @field #boolean negateResult Negeate result of evaluation. -- @field #table functionsGen General condition functions. @@ -55,7 +56,7 @@ CONDITION = { --- CONDITION class version. -- @field #string version -CONDITION.version="0.1.0" +CONDITION.version="0.2.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -206,6 +207,10 @@ function CONDITION:Evaluate(AnyTrue) return result end +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Private Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + --- Check if all given condition are true. -- @param #CONDITION self -- @param #table functions Functions to evaluate. @@ -290,6 +295,71 @@ function CONDITION:_CreateCondition(Function, ...) return condition end + +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- +-- Global Condition Functions +------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- + +--- Condition to check if time is greater than a given threshold time. +-- @param #number Time Time in seconds. +-- @param #boolean Absolute If `true`, abs. mission time from `timer.getAbsTime()` is checked. Default is relative mission time from `timer.getTime()`. +-- @return #boolean Returns `true` if time is greater than give the time. +function CONDITION.IsTimeGreater(Time, Absolute) + + local Tnow=nil + + if Absolute then + Tnow=timer.getAbsTime() + else + Tnow=timer.getTime() + end + + if Tnow>Time then + return true + else + return false + end + + return nil +end + +--- Function that returns `true` (success) with a certain probability. For example, if you specify `Probability=80` there is an 80% chance that `true` is returned. +-- Technically, a random number between 0 and 100 is created. If the given success probability is less then this number, `true` is returned. +-- @param #number Probability Success probability in percent. Default 50 %. +-- @return #boolean Returns `true` for success and `false` otherwise. +function CONDITION.IsRandomSuccess(Probability) + + Probability=Probability or 50 + + -- Create some randomness. + math.random() + math.random() + math.random() + + -- Number between 0 and 100. + local N=math.random()*100 + + if N Date: Thu, 1 Dec 2022 13:27:58 +0100 Subject: [PATCH 11/66] * Minor fixes --- Moose Development/Moose/Core/Set.lua | 2 +- Moose Development/Moose/Functional/RAT.lua | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index d70bc20e5..f3b227e24 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -179,7 +179,7 @@ do -- SET_BASE return Names end - --- Gets a list of the Objects in the Set. + --- Return a table of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:GetSetObjects() -- R2.3 diff --git a/Moose Development/Moose/Functional/RAT.lua b/Moose Development/Moose/Functional/RAT.lua index a54533eeb..081132413 100644 --- a/Moose Development/Moose/Functional/RAT.lua +++ b/Moose Development/Moose/Functional/RAT.lua @@ -3492,7 +3492,7 @@ function RAT:Status(message, forID) local fuel=group:GetFuel()*100.0 local airborne=group:InAir() local coords=group:GetCoordinate() - local alt=coords.y + local alt=coords.y or 1000 --local vel=group:GetVelocityKMH() local departure=ratcraft.departure:GetName() local destination=ratcraft.destination:GetName() From b29b9f1b2c71caef118ded5eaf9e56828379a7a3 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Thu, 1 Dec 2022 21:32:19 +0100 Subject: [PATCH 12/66] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 69ab8403a..aec3cbd3a 100644 --- a/README.md +++ b/README.md @@ -68,7 +68,7 @@ Pene has kindly created a [tutorial series for MOOSE](https://youtube.com/playli -## [MOOSE on Discord](https://discord.gg/yBPfxC6) +## [MOOSE on Discord](https://discord.gg/aQtjcR94Qf) MOOSE has a living (chat and video) community of users, beta testers and contributors. The gathering point is a service provided by discord.com. If you want to join this community, just click Discord and you'll be on board in no time. From 37b1e7366c1f1a7e4003e2bedcc4e59ba91d1255 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 2 Dec 2022 16:39:05 +0100 Subject: [PATCH 13/66] *smaller fixes --- Moose Development/Moose/Core/Message.lua | 29 ++++++++++++++------ Moose Development/Moose/Core/SpawnStatic.lua | 2 +- Moose Development/Moose/Ops/CTLD.lua | 2 +- 3 files changed, 22 insertions(+), 11 deletions(-) diff --git a/Moose Development/Moose/Core/Message.lua b/Moose Development/Moose/Core/Message.lua index 780446109..45c9cd968 100644 --- a/Moose Development/Moose/Core/Message.lua +++ b/Moose Development/Moose/Core/Message.lua @@ -98,7 +98,7 @@ function MESSAGE:New( MessageText, MessageDuration, MessageCategory, ClearScreen self.MessageType = nil - -- When no MessageCategory is given, we don't show it as a title... + -- When no MessageCategory is given, we don't show it as a title... if MessageCategory and MessageCategory ~= "" then if MessageCategory:sub( -1 ) ~= "\n" then self.MessageCategory = MessageCategory .. ": " @@ -204,14 +204,14 @@ function MESSAGE:ToClient( Client, Settings ) local Unit = Client:GetClient() if self.MessageDuration ~= 0 then - local ClientGroupID = Client:GetClientGroupID() - self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) - --trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) - trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) - end - end - - return self + local ClientGroupID = Client:GetClientGroupID() + self:T( self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$","") .. " / " .. self.MessageDuration ) + --trigger.action.outTextForGroup( ClientGroupID, self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) + trigger.action.outTextForUnit( Unit:GetID(), self.MessageCategory .. self.MessageText:gsub("\n$",""):gsub("\n$",""), self.MessageDuration , self.ClearScreen) + end + end + + return self end --- Sends a MESSAGE to a Group. @@ -443,3 +443,14 @@ function MESSAGE:ToLog() return self end + +--- Sends a MESSAGE to DCS log file if the given Condition is true. +-- @param #MESSAGE self +-- @return #MESSAGE self +function MESSAGE:ToLogIf( Condition ) + + if Condition and Condition == true then + env.info(self.MessageCategory .. self.MessageText:gsub( "\n$", "" ):gsub( "\n$", "" )) + end + return self +end diff --git a/Moose Development/Moose/Core/SpawnStatic.lua b/Moose Development/Moose/Core/SpawnStatic.lua index d73336464..4420f70ed 100644 --- a/Moose Development/Moose/Core/SpawnStatic.lua +++ b/Moose Development/Moose/Core/SpawnStatic.lua @@ -275,7 +275,7 @@ end --- Initialize as dead. -- @param #SPAWNSTATIC self --- @param #boolean IsCargo If true, this static is dead. +-- @param #boolean IsDead If true, this static is dead. -- @return #SPAWNSTATIC self function SPAWNSTATIC:InitDead(IsDead) self.InitStaticDead=IsDead diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index e370b430e..145c2c9e0 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3690,7 +3690,7 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) local Sound = "l10n/DEFAULT/"..Sound trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight elseif Zone then - local ZoneCoord = Zone:GetCoordinate() + local ZoneCoord = Zone:GetCoordinate(2) local ZoneVec3 = ZoneCoord:GetVec3() local Frequency = Mhz * 1000000 -- Freq in Hertz local Sound = "l10n/DEFAULT/"..Sound From 508e36d3274a8478aa6a72321777d155ff3eb8ea Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 3 Dec 2022 14:37:01 +0100 Subject: [PATCH 14/66] #CTLD Fix for Beacon Zone disappearing too fast --- Moose Development/Moose/Ops/CTLD.lua | 17 +++++++++++++---- 1 file changed, 13 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 145c2c9e0..e36752b3f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3522,7 +3522,12 @@ function CTLD:AddCTLDZone(Name, Type, Color, Active, HasBeacon, Shiplength, Ship ctldzone.name = Name or "NONE" ctldzone.type = Type or CTLD.CargoZoneType.MOVE -- #CTLD.CargoZoneType ctldzone.hasbeacon = HasBeacon or false - + + if Type == CTLD.CargoZoneType.BEACON then + self.droppedbeaconref[ctldzone.name] = zone:GetCoordinate() + ctldzone.timestamp = timer.getTime() + end + if HasBeacon then ctldzone.fmbeacon = self:_GetFMBeacon(Name) ctldzone.uhfbeacon = self:_GetUHFBeacon(Name) @@ -3611,7 +3616,7 @@ function CTLD:CheckDroppedBeacons() for _,_beacon in pairs (self.droppedBeacons) do local beacon = _beacon -- #CTLD.CargoZone - if not beacon.timestamp then beacon.timestamp = timer.getTime() end + if not beacon.timestamp then beacon.timestamp = timer.getTime() + timeout end local T0 = beacon.timestamp if timer.getTime() - T0 > timeout then local name = beacon.name @@ -3686,15 +3691,19 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) if IsDropped and Zone then local ZoneCoord = Zone local ZoneVec3 = ZoneCoord:GetVec3() - local Frequency = Mhz * 1000000 -- Freq in Hertz + local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz local Sound = "l10n/DEFAULT/"..Sound trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + --local status = string.format("***** Beacon added Freq %s Mod %s", Mhz, UTILS.GetModulationName(Modulation)) + --MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug) elseif Zone then local ZoneCoord = Zone:GetCoordinate(2) local ZoneVec3 = ZoneCoord:GetVec3() - local Frequency = Mhz * 1000000 -- Freq in Hertz + local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz local Sound = "l10n/DEFAULT/"..Sound trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight + --local status = string.format("***** Beacon added Freq %s Mod %s", Mhz, UTILS.GetModulationName(Modulation)) + --MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug) end return self end From 9ea4a5dbd4c22b8c01ac9b7b1b7581c07654448a Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 6 Dec 2022 12:49:27 +0100 Subject: [PATCH 15/66] #CTLD less log noise --- Moose Development/Moose/Ops/CTLD.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index e36752b3f..61ff3edbf 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -4621,7 +4621,7 @@ end -- @param Wrapper.Group#GROUP Vehicle The #GROUP object of the vehicle or FOB build. -- @return #CTLD self function CTLD:onbeforeCratesBuild(From, Event, To, Group, Unit, Vehicle) - self:I({From, Event, To}) + self:T({From, Event, To}) if Unit and Unit:IsPlayer() and self.PlayerTaskQueue then local playername = Unit:GetPlayerName() local dropcoord = Vehicle:GetCoordinate() or COORDINATE:New(0,0,0) From 813d4edc970915de04591762e02b249d59f88fec Mon Sep 17 00:00:00 2001 From: Frank Date: Wed, 7 Dec 2022 18:35:12 +0100 Subject: [PATCH 16/66] CONDITION v0.3.0 - Added methods to remove condition functions - Added option to define output if no condition functions at all were given - --- Moose Development/Moose/Core/Condition.lua | 145 +++++++++++++++++---- 1 file changed, 122 insertions(+), 23 deletions(-) diff --git a/Moose Development/Moose/Core/Condition.lua b/Moose Development/Moose/Core/Condition.lua index 80f80cf52..7e9842a7b 100644 --- a/Moose Development/Moose/Core/Condition.lua +++ b/Moose Development/Moose/Core/Condition.lua @@ -25,10 +25,13 @@ -- @field #string lid Class id string for output to DCS log file. -- @field #string name Name of the condition. -- @field #boolean isAny General functions are evaluated as any condition. --- @field #boolean negateResult Negeate result of evaluation. +-- @field #boolean negateResult Negate result of evaluation. +-- @field #boolean noneResult Boolean that is returned if no condition functions at all were specified. -- @field #table functionsGen General condition functions. -- @field #table functionsAny Any condition functions. -- @field #table functionsAll All condition functions. +-- @field #number functionCounter Running number to determine the unique ID of condition functions. +-- @field #boolean defaultPersist Default persistence of condition functions. -- -- @extends Core.Base#BASE @@ -42,27 +45,34 @@ -- -- @field #CONDITION CONDITION = { - ClassName = "CONDITION", - lid = nil, - functionsGen = {}, - functionsAny = {}, - functionsAll = {}, + ClassName = "CONDITION", + lid = nil, + functionsGen = {}, + functionsAny = {}, + functionsAll = {}, + functionCounter = 0, + defaultPersist = false, } --- Condition function. -- @type CONDITION.Function --- @field #function func Callback function to check for a condition. Should return a `#boolean`. +-- @field #number uid Unique ID of the condition function. +-- @field #string type Type of the condition function: "gen", "any", "all". +-- @field #boolean persistence If `true`, this is persistent. +-- @field #function func Callback function to check for a condition. Must return a `#boolean`. -- @field #table arg (Optional) Arguments passed to the condition callback function if any. --- CONDITION class version. -- @field #string version -CONDITION.version="0.2.0" +CONDITION.version="0.3.0" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- --- TODO: Make FSM. +-- TODO: Make FSM. No sure if really necessary. +-- DONE: Option to remove condition functions. +-- DONE: Persistence option for condition functions. ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- Constructor @@ -79,6 +89,8 @@ function CONDITION:New(Name) self.name=Name or "Condition X" + self:SetNoneResult(false) + self.lid=string.format("%s | ", self.name) return self @@ -102,6 +114,28 @@ function CONDITION:SetNegateResult(Negate) return self end +--- Set whether `true` or `false` is returned, if no conditions at all were specified. By default `false` is returned. +-- @param #CONDITION self +-- @param #boolean ReturnValue Returns this boolean. +-- @return #CONDITION self +function CONDITION:SetNoneResult(ReturnValue) + if not ReturnValue then + self.noneResult=false + else + self.noneResult=true + end + return self +end + +--- Set whether condition functions are persistent, *i.e.* are removed. +-- @param #CONDITION self +-- @param #boolean IsPersistent If `true`, condition functions are persistent. +-- @return #CONDITION self +function CONDITION:SetDefaultPersistence(IsPersistent) + self.defaultPersist=IsPersistent + return self +end + --- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`). -- @param #CONDITION self -- @param #function Function The function to call. @@ -114,47 +148,109 @@ end -- -- myCondition:AddFunction(isAequalB, a, b) -- --- @return #CONDITION self +-- @return #CONDITION.Function Condition function table. function CONDITION:AddFunction(Function, ...) -- Condition function. - local condition=self:_CreateCondition(Function, ...) + local condition=self:_CreateCondition(0, Function, ...) -- Add to table. table.insert(self.functionsGen, condition) - return self + return condition end --- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`). -- @param #CONDITION self -- @param #function Function The function to call. -- @param ... (Optional) Parameters passed to the function (if any). --- @return #CONDITION self +-- @return #CONDITION.Function Condition function table. function CONDITION:AddFunctionAny(Function, ...) -- Condition function. - local condition=self:_CreateCondition(Function, ...) + local condition=self:_CreateCondition(1, Function, ...) -- Add to table. table.insert(self.functionsAny, condition) - return self + return condition end --- Add a function that is evaluated. It must return a `#boolean` value, *i.e.* either `true` or `false` (or `nil`). -- @param #CONDITION self -- @param #function Function The function to call. -- @param ... (Optional) Parameters passed to the function (if any). --- @return #CONDITION self +-- @return #CONDITION.Function Condition function table. function CONDITION:AddFunctionAll(Function, ...) -- Condition function. - local condition=self:_CreateCondition(Function, ...) + local condition=self:_CreateCondition(2, Function, ...) -- Add to table. table.insert(self.functionsAll, condition) + return condition +end + +--- Remove a condition function. +-- @param #CONDITION self +-- @param #CONDITION.Function ConditionFunction The condition function to be removed. +-- @return #CONDITION self +function CONDITION:RemoveFunction(ConditionFunction) + + if ConditionFunction then + + local data=nil + if ConditionFunction.type==0 then + data=self.functionsGen + elseif ConditionFunction.type==1 then + data=self.functionsAny + elseif ConditionFunction.type==2 then + data=self.functionsAll + end + + if data then + for i=#data,1,-1 do + local cf=data[i] --#CONDITION.Function + if cf.uid==ConditionFunction.uid then + self:T(self.lid..string.format("Removed ConditionFunction UID=%d", cf.uid)) + table.remove(data, i) + return self + end + end + end + + end + + return self +end + +--- Remove all non-persistant condition functions. +-- @param #CONDITION self +-- @return #CONDITION self +function CONDITION:RemoveNonPersistant() + + for i=#self.functionsGen,1,-1 do + local cf=self.functionsGen[i] --#CONDITION.Function + if not cf.persistence then + table.remove(self.functionsGen, i) + end + end + + for i=#self.functionsAll,1,-1 do + local cf=self.functionsAll[i] --#CONDITION.Function + if not cf.persistence then + table.remove(self.functionsAll, i) + end + end + + for i=#self.functionsAny,1,-1 do + local cf=self.functionsAny[i] --#CONDITION.Function + if not cf.persistence then + table.remove(self.functionsAny, i) + end + end + return self end @@ -167,11 +263,7 @@ function CONDITION:Evaluate(AnyTrue) -- Check if at least one function was given. if #self.functionsAll + #self.functionsAny + #self.functionsAll == 0 then - if self.negateResult then - return true - else - return false - end + return self.noneResult end -- Any condition for gen. @@ -280,13 +372,20 @@ end --- Create conditon function object. -- @param #CONDITION self +-- @param #number Ftype Function type: 0=Gen, 1=All, 2=Any. -- @param #function Function The function to call. -- @param ... (Optional) Parameters passed to the function (if any). -- @return #CONDITION.Function Condition function. -function CONDITION:_CreateCondition(Function, ...) +function CONDITION:_CreateCondition(Ftype, Function, ...) + + -- Increase counter. + self.functionCounter=self.functionCounter+1 local condition={} --#CONDITION.Function + condition.uid=self.functionCounter + condition.type=Ftype or 0 + condition.persistence=self.defaultPersist condition.func=Function condition.arg={} if arg then From dcfce8b619fe582254d88ce30d35d1868ba433fa Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 7 Dec 2022 18:50:22 +0100 Subject: [PATCH 17/66] #CTLD - enforce modulation on beacons --- Moose Development/Moose/Ops/CTLD.lua | 47 +++++++++++++++++++--------- 1 file changed, 33 insertions(+), 14 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 61ff3edbf..4dabfddd4 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1015,7 +1015,16 @@ CTLD = { -- @type CTLD.ZoneBeacon -- @field #string name -- Name of zone for the coordinate -- @field #number frequency -- in mHz --- @field #number modulation -- i.e.radio.modulation.FM or radio.modulation.AM +-- @field #number modulation -- i.e.CTLD.RadioModulation.FM or CTLD.RadioModulation.AM + +--- Radio Modulation +-- @type CTLD.RadioModulation +-- @field #number AM +-- @field #number FM +CTLD.RadioModulation = { + AM = 0, + FM = 1, +} --- Zone Info. -- @type CTLD.CargoZone @@ -3440,7 +3449,7 @@ function CTLD:_GetFMBeacon(Name) table.insert(self.UsedFMFrequencies, FM) beacon.name = Name beacon.frequency = FM / 1000000 - beacon.modulation = radio.modulation.FM + beacon.modulation = CTLD.RadioModulation.FM return beacon end @@ -3460,7 +3469,7 @@ function CTLD:_GetUHFBeacon(Name) table.insert(self.UsedUHFFrequencies, UHF) beacon.name = Name beacon.frequency = UHF / 1000000 - beacon.modulation = radio.modulation.AM + beacon.modulation = CTLD.RadioModulation.AM return beacon end @@ -3481,7 +3490,7 @@ function CTLD:_GetVHFBeacon(Name) table.insert(self.UsedVHFFrequencies, VHF) beacon.name = Name beacon.frequency = VHF / 1000000 - beacon.modulation = radio.modulation.FM + beacon.modulation = CTLD.RadioModulation.FM return beacon end @@ -3690,19 +3699,25 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) local Sound = Sound or "beacon.ogg" if IsDropped and Zone then local ZoneCoord = Zone - local ZoneVec3 = ZoneCoord:GetVec3() + local ZoneVec3 = ZoneCoord:GetVec3(1) local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz + --local Frequency = Mhz*1000000 local Sound = "l10n/DEFAULT/"..Sound - trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight - --local status = string.format("***** Beacon added Freq %s Mod %s", Mhz, UTILS.GetModulationName(Modulation)) + --local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation)) + --trigger.action.stopRadioTransmission(name) + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight + --local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation)) --MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug) elseif Zone then - local ZoneCoord = Zone:GetCoordinate(2) + local ZoneCoord = Zone:GetCoordinate(1) local ZoneVec3 = ZoneCoord:GetVec3() local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz + --local Frequency = Mhz*1000000 local Sound = "l10n/DEFAULT/"..Sound - trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000) -- Beacon in MP only runs for 30secs straight - --local status = string.format("***** Beacon added Freq %s Mod %s", Mhz, UTILS.GetModulationName(Modulation)) + --local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation)) + --trigger.action.stopRadioTransmission(name) + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight + --local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation)) --MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug) end return self @@ -3730,10 +3745,14 @@ function CTLD:_RefreshRadioBeacons() local Name = czone.name local FM = FMbeacon.frequency -- MHz local VHF = VHFbeacon.frequency -- KHz - local UHF = UHFbeacon.frequency -- MHz - self:_AddRadioBeacon(Name,Sound,FM,radio.modulation.FM, IsShip, IsDropped) - self:_AddRadioBeacon(Name,Sound,VHF,radio.modulation.FM, IsShip, IsDropped) - self:_AddRadioBeacon(Name,Sound,UHF,radio.modulation.AM, IsShip, IsDropped) + local UHF = UHFbeacon.frequency -- MHz + -- local co = coroutine.create(self._AddRadioBeacon) + --coroutine.resume(co, self, Name,Sound,FM,CTLD.RadioModulation.FM, IsShip, IsDropped) + --coroutine.resume(co, self, Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped) + --coroutine.resume(co, self, Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,FM, CTLD.RadioModulation.FM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped) + self:_AddRadioBeacon(Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped) end end end From 84d301c67662965fc05b6a4ad278166b8c04d816 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 9 Dec 2022 12:37:39 +0100 Subject: [PATCH 18/66] #CTLD * Added disallow building in loadzones: my_ctld.nobuildinloadzones = true --- Moose Development/Moose/Ops/CTLD.lua | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4dabfddd4..62c0a445b 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -711,6 +711,7 @@ do -- my_ctld.droppedbeacontimeout = 600 -- dropped beacon lasts 10 minutes -- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types. -- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted. +-- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true` -- -- ## 2.1 User functions -- @@ -1087,7 +1088,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.20" +CTLD.version="1.0.21" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1252,6 +1253,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self.usesubcats = false self.subcats = {} + -- disallow building in loadzones + self.nobuildinloadzones = true + local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -2102,10 +2106,10 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self.CargoCounter = self.CargoCounter + 1 local realcargo = nil if drop then - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) table.insert(droppedcargo,realcargo) else - realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,subcat) + realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,false,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],false,cargotype.PerCrateMass,nil,subcat) Cargo:RemoveStock() end table.insert(self.Spawned_Cargo, realcargo) @@ -2833,6 +2837,14 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) return self end end + if not Engineering and self.nobuildinloadzones then + -- are we in a load zone? + local inloadzone = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) + if inloadzone then + self:_SendMessage("You cannot build in a loading area, Pilot!", 10, false, Group) + return self + end + end -- get nearby crates local finddist = self.CrateDistance or 35 local crates,number = self:_FindCratesNearby(Group,Unit, finddist,true) -- #table From 324739aeb9b7f61c346cabc6b0e102d257a43433 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 11 Dec 2022 15:49:50 +0100 Subject: [PATCH 19/66] #SET * Improve GetRandom() a bit --- Moose Development/Moose/Core/Set.lua | 18 ++++++++---------- 1 file changed, 8 insertions(+), 10 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index f3b227e24..41c0b1501 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -179,7 +179,7 @@ do -- SET_BASE return Names end - --- Return a table of the Objects in the Set. + --- Returns a table of the Objects in the Set. -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:GetSetObjects() -- R2.3 @@ -376,7 +376,6 @@ do -- SET_BASE -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetFirst() - local ObjectName = self.Index[1] local FirstObject = self.Set[ObjectName] self:T3( { FirstObject } ) @@ -387,8 +386,8 @@ do -- SET_BASE -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetLast() - - local ObjectName = self.Index[#self.Index] + local tablemax = table.maxn(self.Index) + local ObjectName = self.Index[tablemax] local LastObject = self.Set[ObjectName] self:T3( { LastObject } ) return LastObject @@ -398,8 +397,8 @@ do -- SET_BASE -- @param #SET_BASE self -- @return Core.Base#BASE function SET_BASE:GetRandom() - - local RandomItem = self.Set[self.Index[math.random( #self.Index )]] + local tablemax = table.maxn(self.Index) + local RandomItem = self.Set[self.Index[math.random(1,tablemax)]] self:T3( { RandomItem } ) return RandomItem end @@ -408,8 +407,7 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #number Count function SET_BASE:Count() - - return self.Index and #self.Index or 0 + return self.Index and table.maxn(self.Index) or 0 end --- Copies the Filter criteria from a given Set (for rebuilding a new Set based on an existing Set). @@ -6800,7 +6798,7 @@ do -- SET_SCENERY if ZoneSet then for _,_zone in pairs(ZoneSet.Set) do - --self:I("Zone type handed: "..tostring(_zone.ClassName)) + self:T("Zone type handed: "..tostring(_zone.ClassName)) table.insert(zonenames,_zone:GetName()) end self:AddSceneryByName(zonenames) @@ -6814,7 +6812,7 @@ do -- SET_SCENERY -- @param Core.Zone#ZONE Zone The zone to be scanned. Can be a ZONE_RADIUS (round) or a ZONE_POLYGON (e.g. Quad-Point) -- @return #SET_SCENERY function SET_SCENERY:NewFromZone(Zone) - local zone = Zone -- Core.Zone#ZONE_POLYGON + local zone = Zone -- Core.Zone#ZONE_RADIUS if type(Zone) == "string" then zone = ZONE:FindByName(Zone) end From 77e7f767d82d1da286ce7cff0937db1af7f8778b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 12 Dec 2022 16:23:47 +0100 Subject: [PATCH 20/66] #AIRBASE * docu correction --- Moose Development/Moose/Wrapper/Airbase.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index a2fd0577a..cb8e35175 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -839,7 +839,7 @@ end -- Black listed spots overrule white listed spots. -- **NOTE** that terminal IDs are not necessarily the same as those displayed in the mission editor! -- @param #AIRBASE self --- @param #table TerminalIdBlacklist Table of white listed terminal IDs. +-- @param #table TerminalIdWhitelist Table of white listed terminal IDs. -- @return #AIRBASE self -- @usage AIRBASE:FindByName("Batumi"):SetParkingSpotWhitelist({2, 3, 4}) --Only allow terminal IDs 2, 3, 4 function AIRBASE:SetParkingSpotWhitelist(TerminalIdWhitelist) From 089467c15a72a818990a70e798efcbb442be8d1e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Dec 2022 09:41:35 +0100 Subject: [PATCH 21/66] #AI\_A2A\_GCICAP * Fix demo mission link --- Moose Development/Moose/AI/AI_A2A_Dispatcher.lua | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua index 889ebe631..d0d1a940a 100644 --- a/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua +++ b/Moose Development/Moose/AI/AI_A2A_Dispatcher.lua @@ -3940,11 +3940,7 @@ do -- -- # Demo Missions -- - -- ### [AI\_A2A\_GCICAP for Caucasus](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-200%20-%20AI_A2A%20-%20GCICAP%20Demonstration) - -- ### [AI\_A2A\_GCICAP for NTTR](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-210%20-%20NTTR%20AI_A2A_GCICAP%20Demonstration) - -- ### [AI\_A2A\_GCICAP for Normandy](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/release-2-2-pre/AID%20-%20AI%20Dispatching/AID-220%20-%20NORMANDY%20AI_A2A_GCICAP%20Demonstration) - -- - -- ### [AI\_A2A\_GCICAP for beta testers](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching) + -- ### [Demo Missions](https://github.com/FlightControl-Master/MOOSE_MISSIONS/tree/master/AID%20-%20AI%20Dispatching/AID-A2A%20-%20AI%20A2A%20Dispatching) -- -- === -- From 1fac6b7c937b205365f630fd65250e27a3c452ee Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 14 Dec 2022 14:48:20 +0100 Subject: [PATCH 22/66] #SET_CLIENT * Added `FilterCallsigns()` and `FilterPlayernames()` --- Moose Development/Moose/Core/Set.lua | 72 +++++++++++++++++++++++++--- 1 file changed, 66 insertions(+), 6 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 41c0b1501..8785850f3 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -3808,6 +3808,8 @@ do -- SET_CLIENT Countries = nil, ClientPrefixes = nil, Zones = nil, + Playernames = nil, + Callsigns = nil, }, FilterMeta = { Coalitions = { @@ -3880,6 +3882,40 @@ do -- SET_CLIENT return ClientFound end + --- Builds a set of clients of certain callsigns. + -- @param #SET_CLIENT self + -- @param #string Callsigns Can be a single string e.g. "Ford", or a table of strings e.g. {"Uzi","Enfield","Chevy"}. Refers to the callsigns as they can be set in the mission editor. + -- @return #SET_CLIENT self + function SET_CLIENT:FilterCallsigns( Callsigns ) + if not self.Filter.Callsigns then + self.Filter.Callsigns = {} + end + if type( Callsigns ) ~= "table" then + Callsigns = { Callsigns } + end + for callsignID, callsign in pairs( Callsigns ) do + self.Filter.Callsigns[callsign] = callsign + end + return self + end + + --- Builds a set of clients of certain playernames. + -- @param #SET_CLIENT self + -- @param #string Playernames Can be a single string e.g. "Apple", or a table of strings e.g. {"Walter","Hermann","Gonzo"}. Useful if you have e.g. a common squadron prefix. + -- @return #SET_CLIENT self + function SET_CLIENT:FilterPlayernames( Playernames ) + if not self.Filter.Playernames then + self.Filter.Playernames = {} + end + if type( Playernames ) ~= "table" then + Playernames = { Playernames } + end + for PlayernameID, playername in pairs( Playernames ) do + self.Filter.Playernames[playername] = playername + end + return self + end + --- Builds a set of clients of coalitions. -- Possible current coalitions are red, blue and neutral. -- @param #SET_CLIENT self @@ -4235,20 +4271,44 @@ do -- SET_CLIENT self:T( { "Evaluated Prefix", MClientPrefix } ) MClientInclude = MClientInclude and MClientPrefix end - end if self.Filter.Zones then local MClientZone = false for ZoneName, Zone in pairs( self.Filter.Zones ) do - self:T3( "Zone:", ZoneName ) - local unit = MClient:GetClientGroupUnit() - if unit and unit:IsInZone(Zone) then - MClientZone = true - end + self:T3( "Zone:", ZoneName ) + local unit = MClient:GetClientGroupUnit() + if unit and unit:IsInZone(Zone) then + MClientZone = true + end end MClientInclude = MClientInclude and MClientZone end + if self.Filter.Playernames then + local MClientPlayername = false + local playername = MClient:GetPlayerName() or "Unknown" + for _,_Playername in pairs(self.Filter.Playernames) do + if playername and string.find(playername,_Playername) then + MClientPlayername = true + end + end + self:T( { "Evaluated Playername", MClientPlayername } ) + MClientInclude = MClientInclude and MClientPlayername + end + + if self.Filter.Callsigns then + local MClientCallsigns = false + local callsign = MClient:GetCallsign() + for _,_Callsign in pairs(self.Filter.Callsigns) do + if callsign and string.find(callsign,_Callsign) then + MClientCallsigns = true + end + end + self:T( { "Evaluated Callsign", MClientCallsigns } ) + MClientInclude = MClientInclude and MClientCallsigns + end + + end self:T2( MClientInclude ) return MClientInclude end From d5028d86dffa671804e7aa77800d23a8ebbd2fae Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 Dec 2022 11:49:28 +0100 Subject: [PATCH 23/66] #SET_CLIENT * small addition --- Moose Development/Moose/Core/Set.lua | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 8785850f3..a3c404bbe 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -4203,9 +4203,10 @@ do -- SET_CLIENT if self.Filter.Active ~= nil then local MClientActive = false - if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true) then + if self.Filter.Active == false or (self.Filter.Active == true and MClient:IsActive() == true and MClient:IsAlive() == true) then MClientActive = true end + --self:I( { "Evaluated Active", MClientActive } ) MClientInclude = MClientInclude and MClientActive end @@ -4287,6 +4288,7 @@ do -- SET_CLIENT if self.Filter.Playernames then local MClientPlayername = false local playername = MClient:GetPlayerName() or "Unknown" + --self:I(playername) for _,_Playername in pairs(self.Filter.Playernames) do if playername and string.find(playername,_Playername) then MClientPlayername = true @@ -4299,6 +4301,7 @@ do -- SET_CLIENT if self.Filter.Callsigns then local MClientCallsigns = false local callsign = MClient:GetCallsign() + --self:I(callsign) for _,_Callsign in pairs(self.Filter.Callsigns) do if callsign and string.find(callsign,_Callsign) then MClientCallsigns = true From 80e3b157caf51f99e161e9e632ac81a308ede594 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 15 Dec 2022 18:28:06 +0100 Subject: [PATCH 24/66] #UTILS * UTILS.LoadSetOfStatics(Path,Filename) ignore statics which do not exist --- Moose Development/Moose/Utilities/Utils.lua | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index dd073f753..0e3b3d37d 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2414,11 +2414,14 @@ function UTILS.LoadSetOfStatics(Path,Filename) local dataset = UTILS.Split(_entry,",") -- staticname,position.x,position.y,position.z local staticname = dataset[1] - local posx = tonumber(dataset[2]) - local posy = tonumber(dataset[3]) - local posz = tonumber(dataset[4]) - local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) - datatable:AddObject(STATIC:FindByName(staticname,false)) + --local posx = tonumber(dataset[2]) + --local posy = tonumber(dataset[3]) + --local posz = tonumber(dataset[4]) + --local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) + local StaticObject = STATIC:FindByName(staticname,false) + if StaticObject then + datatable:AddObject(StaticObject) + end end else return nil From fea1839c06a7608182a465a1852800a07e3b43bb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 16 Dec 2022 18:43:50 +0100 Subject: [PATCH 25/66] #AIRBASE * Added Rio Chico Airfield --- Moose Development/Moose/Wrapper/Airbase.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index cb8e35175..bb355c1a6 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -510,6 +510,7 @@ AIRBASE.MarianaIslands = { -- * AIRBASE.SouthAtlantic.Porvenir_Airfield -- * AIRBASE.SouthAtlantic.Almirante_Schroeders -- * AIRBASE.SouthAtlantic.Rio_Turbio +-- * AIRBASE.SouthAtlantic.Rio_Chico_Airfield -- --@field MarianaIslands AIRBASE.SouthAtlantic={ @@ -532,6 +533,7 @@ AIRBASE.SouthAtlantic={ ["Porvenir_Airfield"]="Porvenir Airfield", ["Almirante_Schroeders"]="Almirante Schroeders", ["Rio_Turbio"]="Rio Turbio", + ["Rio_Chico"] = "Rio Chico", } --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". From 5010cdff71b1467642719406104384e7db1dd69d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 19 Dec 2022 14:04:30 +0100 Subject: [PATCH 26/66] #CTLD * Fix to also save crates which have not been moved --- Moose Development/Moose/Ops/CTLD.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 62c0a445b..17149318d 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1088,7 +1088,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.21" +CTLD.version="1.0.22" --- Instantiate a new CTLD. -- @param #CTLD self @@ -2106,6 +2106,7 @@ function CTLD:_GetCrates(Group, Unit, Cargo, number, drop) self.CargoCounter = self.CargoCounter + 1 local realcargo = nil if drop then + --CTLD_CARGO:New(ID, Name, Templates, Sorte, HasBeenMoved, LoadDirectly, CratesNeeded, Positionable, Dropped, PerCrateMass, Stock, Subcategory) realcargo = CTLD_CARGO:New(self.CargoCounter,cratename,templ,sorte,true,false,cratesneeded,self.Spawned_Crates[self.CrateCounter],true,cargotype.PerCrateMass,nil,subcat) table.insert(droppedcargo,realcargo) else @@ -4778,7 +4779,7 @@ end for _,_cargo in pairs (stcstable) do local cargo = _cargo -- #CTLD_CARGO local object = cargo:GetPositionable() -- Wrapper.Static#STATIC - if object and object:IsAlive() and cargo:WasDropped() then + if object and object:IsAlive() and (cargo:WasDropped() or not cargo:HasMoved()) then statics[#statics+1] = cargo end end From 8df6e2dd57400d3d252d1da6eb3c7c1dc7cd44c2 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 19 Dec 2022 16:12:04 +0100 Subject: [PATCH 27/66] #UNIT * Improve GetGroup() as after Dec/22 patch geGroup() might be nil # SET_UNIT * Improved Docu --- Moose Development/Moose/Core/Set.lua | 7 +------ Moose Development/Moose/Wrapper/Unit.lua | 25 ++++++++++++++---------- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index a3c404bbe..cf370d4ce 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -1952,12 +1952,7 @@ do -- SET_UNIT -- * @{#SET_UNIT.ForEachUnit}: Calls a function for each alive unit it finds within the SET_UNIT. -- * @{#SET_UNIT.ForEachUnitInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence completely in a @{Core.Zone}, providing the UNIT object and optional parameters to the called function. -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate the SET_UNIT and call an iterator function for each **alive** UNIT object presence not in a @{Core.Zone}, providing the UNIT object and optional parameters to the called function. - -- - -- Planned iterators methods in development are (so these are not yet available): - -- - -- * @{#SET_UNIT.ForEachUnitInUnit}: Calls a function for each unit contained within the SET_UNIT. - -- * @{#SET_UNIT.ForEachUnitCompletelyInZone}: Iterate and call an iterator function for each **alive** UNIT presence completely in a @{Core.Zone}, providing the UNIT and optional parameters to the called function. - -- * @{#SET_UNIT.ForEachUnitNotInZone}: Iterate and call an iterator function for each **alive** UNIT presence not in a @{Core.Zone}, providing the UNIT and optional parameters to the called function. + -- * @{#SET_UNIT:ForEachUnitPerThreatLevel}: Iterate the SET_UNIT **sorted *per Threat Level** and call an iterator function for each **alive** UNIT, providing the UNIT and optional parameters -- -- ## 5) SET_UNIT atomic methods -- diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 59474de9c..11326a4a6 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -629,22 +629,27 @@ end --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self -- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. +--- Returns the unit's group if it exists and nil otherwise. +-- @param Wrapper.Unit#UNIT self +-- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. function UNIT:GetGroup() - self:F2( self.UnitName ) - - local DCSUnit = self:GetDCSObject() - - if DCSUnit then - local UnitGroup = GROUP:FindByName( DCSUnit:getGroup():getName() ) + self:F2( self.UnitName ) + local UnitGroup = GROUP:FindByName(self.GroupName) + if UnitGroup then return UnitGroup + else + local DCSUnit = self:GetDCSObject() + if DCSUnit then + local grp = DCSUnit:getGroup() + if grp then + local UnitGroup = GROUP:FindByName( grp:getName() ) + return UnitGroup + end + end end - return nil end - --- Need to add here functions to check if radar is on and which object etc. - --- Returns the prefix name of the DCS Unit. A prefix name is a part of the name before a '#'-sign. -- DCS Units spawned with the @{Core.Spawn#SPAWN} class contain a '#'-sign to indicate the end of the (base) DCS Unit name. -- The spawn sequence number and unit number are contained within the name after the '#' sign. From 6f0ba337c400f1ce1644bad501cb59f401ddee0e Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Dec 2022 12:55:01 +0100 Subject: [PATCH 28/66] #SET_SCENERY * Added GetAliveSet() --- Moose Development/Moose/Core/Set.lua | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index cf370d4ce..82a7d65a5 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -6990,7 +6990,29 @@ do -- SET_SCENERY return CountU end + + --- Get a table of alive objects. + -- @param #SET_GROUP self + -- @return #table Table of alive objects + -- @return Core.Set#SET_SCENERY SET of alive objects + function SET_SCENERY:GetAliveSet() + self:F2() + local AliveSet = SET_SCENERY:New() + + -- Clean the Set before returning with only the alive Groups. + for GroupName, GroupObject in pairs( self.Set ) do + local GroupObject = GroupObject -- Wrapper.Group#GROUP + if GroupObject then + if GroupObject:IsAlive() then + AliveSet:Add( GroupName, GroupObject ) + end + end + end + + return AliveSet.Set or {}, AliveSet + end + --- Iterate the SET_SCENERY and call an iterator function for each **alive** SCENERY, providing the SCENERY and optional parameters. -- @param #SET_SCENERY self -- @param #function IteratorFunction The function that will be called when there is an alive SCENERY in the SET_SCENERY. The function needs to accept a SCENERY parameter. From 4a299ea53ff7fbf5991cef6bc777d4856d0956f5 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Dec 2022 14:05:58 +0100 Subject: [PATCH 29/66] #CTLD * Added option to inject troops into helos --- Moose Development/Moose/Ops/CTLD.lua | 69 ++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 17149318d..4b90a90f5 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1088,7 +1088,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.22" +CTLD.version="1.0.23" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1648,12 +1648,54 @@ function CTLD:_SendMessage(Text, Time, Clearscreen, Group) return self end +--- (Internal) Find a troops CTLD_CARGO object in stock +-- @param #CTLD self +-- @param #string Name of the object +-- @return #CTLD_CARGO Cargo object, nil if it cannot be found +function CTLD:_FindTroopsCargoObject(Name) + self:T(self.lid .. " _FindTroopsCargoObject") + local cargo = nil + for _,_cargo in pairs(self.Cargo_Troops)do + local cargo = _cargo -- #CTLD_CARGO + if cargo.Name == Name then + return cargo + end + end + return nil +end + +--- (User) Pre-load troops into a helo, e.g. for airstart. Unit **must** be alive in-game, i.e. player has taken the slot! +-- @param #CTLD self +-- @param Wrapper.Unit#UNIT Unit The unit to load into, can be handed as Wrapper.Client#CLIENT object +-- @param #string Troopname The name of the Troops to be loaded. Must be created prior in the CTLD setup! +-- @return #CTLD self +-- @usage +-- local client = UNIT:FindByName("Helo-1-1") +-- if client and client:IsAlive() then +-- myctld:PreloadTroops(client,"Infantry") +-- end +function CTLD:PreloadTroops(Unit,Troopname) + self:T(self.lid .. " PreloadTroops") + local name = Troopname or "Unknown" + if Unit and Unit:IsAlive() then + local cargo = self:_FindTroopsCargoObject(name) + local group = Unit:GetGroup() + if cargo then + self:_LoadTroops(group,Unit,cargo,true) + else + self:E(self.lid.." Troops preload - Cargo Object "..name.." not found!") + end + end + return self +end + --- (Internal) Function to load troops into a heli. -- @param #CTLD self -- @param Wrapper.Group#GROUP Group -- @param Wrapper.Unit#UNIT Unit -- @param #CTLD_CARGO Cargotype -function CTLD:_LoadTroops(Group, Unit, Cargotype) +-- @param #boolean Inject +function CTLD:_LoadTroops(Group, Unit, Cargotype, Inject) self:T(self.lid .. " _LoadTroops") -- check if we have stock local instock = Cargotype:GetStock() @@ -1661,7 +1703,7 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) local cgotype = Cargotype:GetType() local cgonetmass = Cargotype:GetNetMass() local maxloadable = self:_GetMaxLoadableMass(Unit) - if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 then + if type(instock) == "number" and tonumber(instock) <= 0 and tonumber(instock) ~= -1 and not Inject then -- nothing left over self:_SendMessage(string.format("Sorry, all %s are gone!", cgoname), 10, false, Group) return self @@ -1669,21 +1711,22 @@ function CTLD:_LoadTroops(Group, Unit, Cargotype) -- landed or hovering over load zone? local grounded = not self:IsUnitInAir(Unit) local hoverload = self:CanHoverLoad(Unit) - --local dooropen = UTILS.IsLoadingDoorOpen(Unit:GetName()) and self.pilotmustopendoors -- check if we are in LOAD zone local inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.LOAD) if not inzone then inzone, zonename, zone, distance = self:IsUnitInZone(Unit,CTLD.CargoZoneType.SHIP) end - if not inzone then - self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) - if not self.debug then return self end - elseif not grounded and not hoverload then - self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) - if not self.debug then return self end - elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then - self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) - if not self.debug then return self end + if not Inject then + if not inzone then + self:_SendMessage("You are not close enough to a logistics zone!", 10, false, Group) + if not self.debug then return self end + elseif not grounded and not hoverload then + self:_SendMessage("You need to land or hover in position to load!", 10, false, Group) + if not self.debug then return self end + elseif self.pilotmustopendoors and not UTILS.IsLoadingDoorOpen(Unit:GetName()) then + self:_SendMessage("You need to open the door(s) to load troops!", 10, false, Group) + if not self.debug then return self end + end end -- load troops into heli local group = Group -- Wrapper.Group#GROUP From f26ff52712f8d98af471733b4f872520b45a258c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 21 Dec 2022 14:09:47 +0100 Subject: [PATCH 30/66] #UNIT * Docu fix --- Moose Development/Moose/Wrapper/Unit.lua | 3 --- 1 file changed, 3 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 11326a4a6..8cf7c9226 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -629,9 +629,6 @@ end --- Returns the unit's group if it exist and nil otherwise. -- @param Wrapper.Unit#UNIT self -- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. ---- Returns the unit's group if it exists and nil otherwise. --- @param Wrapper.Unit#UNIT self --- @return Wrapper.Group#GROUP The Group of the Unit or `nil` if the unit does not exist. function UNIT:GetGroup() self:F2( self.UnitName ) local UnitGroup = GROUP:FindByName(self.GroupName) From 535060153a3bf227ccf541e24448c20f612f47d8 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 23 Dec 2022 13:39:00 +0100 Subject: [PATCH 31/66] CTLD #1851 Add variable for a sound path (#1852) CTLD #1851 Add variable for a sound path - defaults to `self.RadioPath = "l10n/DEFAULT/"` --- Moose Development/Moose/Ops/CTLD.lua | 17 ++++------------- 1 file changed, 4 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 4b90a90f5..d70a0a17f 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1088,7 +1088,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.23" +CTLD.version="1.0.24" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1171,6 +1171,7 @@ function CTLD:New(Coalition, Prefixes, Alias) -- radio beacons self.RadioSound = "beacon.ogg" + self.RadioPath = "l10n/DEFAULT/" -- zones stuff self.pickupZones = {} @@ -3757,24 +3758,14 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) local ZoneCoord = Zone local ZoneVec3 = ZoneCoord:GetVec3(1) local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz - --local Frequency = Mhz*1000000 - local Sound = "l10n/DEFAULT/"..Sound - --local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation)) - --trigger.action.stopRadioTransmission(name) + local Sound = self.RadioPath..Sound trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight - --local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation)) - --MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug) elseif Zone then local ZoneCoord = Zone:GetCoordinate(1) local ZoneVec3 = ZoneCoord:GetVec3() local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz - --local Frequency = Mhz*1000000 - local Sound = "l10n/DEFAULT/"..Sound - --local name = string.format("%s-%f-%s",Zone:GetName(),Mhz,tostring(Modulation)) - --trigger.action.stopRadioTransmission(name) + local Sound = self.RadioPath..Sound trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight - --local status = string.format("***** Beacon added Freq %s Mod %s", Frequency, UTILS.GetModulationName(Modulation)) - --MESSAGE:New(status,10,"Debug"):ToLogIf(self.debug) end return self end From 9619b11c58a9ef67982daa312bd30199924d008d Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Fri, 23 Dec 2022 13:42:08 +0100 Subject: [PATCH 32/66] #CTLD * add sound folder path option --- Moose Development/Moose/Ops/CTLD.lua | 25 +++++++++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d70a0a17f..677c810e8 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -3770,6 +3770,31 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) return self end +--- Set folder path where the CTLD sound files are located **within you mission (miz) file**. +-- The default path is "l10n/DEFAULT/" but sound files simply copied there will be removed by DCS the next time you save the mission. +-- However, if you create a new folder inside the miz file, which contains the sounds, it will not be deleted and can be used. +-- @param #CTLD self +-- @param #string FolderPath The path to the sound files, e.g. "CTLD_Soundfiles/". +-- @return #CTLD self +function CTLD:SetSoundfilesFolder( FolderPath ) + self:T(self.lid .. " SetSoundfilesFolder") + -- Check that it ends with / + if FolderPath then + local lastchar = string.sub( FolderPath, -1 ) + if lastchar ~= "/" then + FolderPath = FolderPath .. "/" + end + end + + -- Folderpath. + self.RadioPath = FolderPath + + -- Info message. + self:I( self.lid .. string.format( "Setting sound files folder to: %s", self.RadioPath ) ) + + return self +end + --- (Internal) Function to refresh radio beacons -- @param #CTLD self function CTLD:_RefreshRadioBeacons() From cd4844d26c58c0566712d9f6343b8442b855e0cd Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sat, 24 Dec 2022 12:03:04 +0100 Subject: [PATCH 33/66] #EVENT * Small fix for hit event on coordinates --- Moose Development/Moose/Core/Event.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 9b334bf2a..595e094f4 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1224,7 +1224,7 @@ function EVENT:onEvent( Event ) if Event.TgtObjectCategory == Object.Category.STATIC then -- get base data Event.TgtDCSUnit = Event.target - if Event.target:isExist() and Event.id ~= 33 then -- leave out ejected seat object + if Event.target:isExist() and Event.id ~= 33 and not Event.TgtObjectCategory == Object.Category.COORDINATE then -- leave out ejected seat object Event.TgtDCSUnitName = Event.TgtDCSUnit:getName() Event.TgtUnitName = Event.TgtDCSUnitName Event.TgtUnit = STATIC:FindByName( Event.TgtDCSUnitName, false ) From 9facf07955e4a28b0570d7d49af79bb9c511918e Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Sun, 25 Dec 2022 14:18:53 +0100 Subject: [PATCH 34/66] ATIS - added basic FARP support (#1854) ATIS - added basic FARP support, only works with SRS --- Moose Development/Moose/Ops/ATIS.lua | 169 +++++++++++++++------------ 1 file changed, 93 insertions(+), 76 deletions(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index d4c20f948..e65a2ac71 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -94,6 +94,7 @@ -- @field #boolean ReportmBar Report mBar/hpa even if not metric, i.e. for Mirage flights -- @field #boolean TransmitOnlyWithPlayers For SRS - If true, only transmit if there are alive Players. -- @field #string SRSText Text of the complete SRS message (if done at least once, else nil) +-- @field #boolean ATISforFARPs Will be set to true if the base given is a FARP/Helipad -- @extends Core.Fsm#FSM --- *It is a very sad thing that nowadays there is so little useless information.* - Oscar Wilde @@ -309,6 +310,19 @@ -- atis:Start() -- -- This uses a male voice with US accent. It requires SRS to be installed in the `D:\DCS\_SRS\` directory. Not that backslashes need to be escaped or simply use slashes (as in linux). +-- +-- ## FARPS +-- +-- ATIS is working with FARPS, but this requires the usage of SRS. The airbase name for the `New()-method` is the UNIT name of the FARP: +-- +-- atis = ATIS:New("FARP Gold",119,radio.modulation.AM) +-- atis:SetMetricUnits() +-- atis:SetTransmitOnlyWithPlayers(true) +-- atis:SetReportmBar(true) +-- atis:SetTowerFrequencies(127.50) +-- atis:SetSRS("D:\\DCS\\_SRS\\", "male", "en-US",nil,5002) +-- atis:SetAdditionalInformation("Welcome to the Jungle!") +-- atis:__Start(3) -- -- @field #ATIS ATIS = { @@ -351,6 +365,7 @@ ATIS = { relHumidity = nil, ReportmBar = false, TransmitOnlyWithPlayers = false, + ATISforFARPs = false, } --- NATO alphabet. @@ -593,7 +608,7 @@ _ATIS = {} --- ATIS class version. -- @field #string version -ATIS.version = "0.9.12" +ATIS.version = "0.9.14" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- TODO list @@ -1049,7 +1064,7 @@ end -- -- * 186° on the Caucaus map -- * 192° on the Nevada map --- * 170° on the Normany map +-- * 170° on the Normandy map -- * 182° on the Persian Gulf map -- -- Likewise, to convert *true* into *magnetic* heading, one has to substract easterly and add westerly variation. @@ -1257,11 +1272,18 @@ end function ATIS:onafterStart( From, Event, To ) -- Check that this is an airdrome. - if self.airbase:GetAirbaseCategory() ~= Airbase.Category.AIRDROME then - self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT FARPS or SHIPS.", self.airbasename ) ) + if self.airbase:GetAirbaseCategory() == Airbase.Category.SHIP then + self:E( self.lid .. string.format( "ERROR: Cannot start ATIS for airbase %s! Only AIRDROMES are supported but NOT SHIPS.", self.airbasename ) ) return end - + + -- Check that if is a Helipad. + if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then + self:E( self.lid .. string.format( "EXPERIMENTAL: Starting ATIS for Helipad %s! SRS must be ON", self.airbasename ) ) + self.ATISforFARPs = true + self.useSRS = true + 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 ) ) @@ -1473,10 +1495,19 @@ function ATIS:onafterBroadcast( From, Event, To ) -------------- --- Runway --- -------------- - - local runwayLanding, rwyLandingLeft=self:GetActiveRunway() - local runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true) - + + + local runwayLanding, rwyLandingLeft + local runwayTakeoff, rwyTakeoffLeft + + if self.airbase:GetAirbaseCategory() == Airbase.Category.HELIPAD then + runwayLanding, rwyLandingLeft="PAD 01",false + runwayTakeoff, rwyTakeoffLeft="PAD 02",false + else + runwayLanding, rwyLandingLeft=self:GetActiveRunway() + runwayTakeoff, rwyTakeoffLeft=self:GetActiveRunway(true) + end + ------------ --- Time --- ------------ @@ -1790,7 +1821,7 @@ function ATIS:onafterBroadcast( From, Event, To ) -- Airbase name subtitle = string.format( "%s", self.airbasename ) - if self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then + if (not self.ATISforFARPs) and self.airbasename:find( "AFB" ) == nil and self.airbasename:find( "Airport" ) == nil and self.airbasename:find( "Airstrip" ) == nil and self.airbasename:find( "airfield" ) == nil and self.airbasename:find( "AB" ) == nil then subtitle = subtitle .. " Airport" end if not self.useSRS then @@ -1865,8 +1896,6 @@ function ATIS:onafterBroadcast( From, Event, To ) end end alltext = alltext .. ";\n" .. subtitle - --self:I("Line 1811") - --self:I(alltext) -- Visibility if self.metric then @@ -1884,8 +1913,6 @@ function ATIS:onafterBroadcast( From, Event, To ) end end alltext = alltext .. ";\n" .. subtitle - --self:I("Line 1830") - --self:I(alltext) subtitle = "" -- Weather phenomena @@ -1987,10 +2014,8 @@ function ATIS:onafterBroadcast( From, Event, To ) end end end - --self:I("Line 1932") alltext = alltext .. ";\n" .. subtitle - --self:I(alltext) subtitle = "" -- Temperature if self.TDegF then @@ -2019,9 +2044,7 @@ function ATIS:onafterBroadcast( From, Event, To ) self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end - --self:I("Line 1962") alltext = alltext .. ";\n" .. subtitle - --self:I(alltext) -- Dew point if self.TDegF then @@ -2050,8 +2073,6 @@ function ATIS:onafterBroadcast( From, Event, To ) self:Transmission( ATIS.Sound.DegreesCelsius, 0.2 ) end end - --self:I("Line 1992") - --self:I(alltext) alltext = alltext .. ";\n" .. subtitle -- Altimeter QNH/QFE. @@ -2117,69 +2138,68 @@ function ATIS:onafterBroadcast( From, Event, To ) end end end - --self:I("Line 2049") - --self:I(alltext) alltext = alltext .. ";\n" .. subtitle - -- Active runway. - local subtitle=string.format("Active runway %s", runwayLanding) - if rwyLandingLeft==true then - subtitle=subtitle.." Left" - elseif rwyLandingLeft==false then - subtitle=subtitle.." Right" - end - local _RUNACT = subtitle - if not self.useSRS then - self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) - self.radioqueue:Number2Transmission(runwayLanding) + if not self.ATISforFARPs then + -- Active runway. + local subtitle=string.format("Active runway %s", runwayLanding) if rwyLandingLeft==true then - self:Transmission(ATIS.Sound.Left, 0.2) + subtitle=subtitle.." Left" elseif rwyLandingLeft==false then - self:Transmission(ATIS.Sound.Right, 0.2) + subtitle=subtitle.." Right" end - end - alltext = alltext .. ";\n" .. subtitle - - -- Runway length. - if self.rwylength then - - local runact = self.airbase:GetActiveRunway( self.runwaym2t ) - local length = runact.length - if not self.metric then - length = UTILS.MetersToFeet( length ) - end - - -- Length in thousands and hundrets of ft/meters. - local L1000, L0100 = self:_GetThousandsAndHundreds( length ) - - -- Subtitle. - local subtitle = string.format( "Runway length %d", length ) - if self.metric then - subtitle = subtitle .. " meters" - else - subtitle = subtitle .. " feet" - end - - -- Transmit. + local _RUNACT = subtitle if not self.useSRS then - self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle ) - if tonumber( L1000 ) > 0 then - self.radioqueue:Number2Transmission( L1000 ) - self:Transmission( ATIS.Sound.Thousand, 0.1 ) - end - if tonumber( L0100 ) > 0 then - self.radioqueue:Number2Transmission( L0100 ) - self:Transmission( ATIS.Sound.Hundred, 0.1 ) - end - if self.metric then - self:Transmission( ATIS.Sound.Meters, 0.1 ) - else - self:Transmission( ATIS.Sound.Feet, 0.1 ) + self:Transmission(ATIS.Sound.ActiveRunway, 1.0, subtitle) + self.radioqueue:Number2Transmission(runwayLanding) + if rwyLandingLeft==true then + self:Transmission(ATIS.Sound.Left, 0.2) + elseif rwyLandingLeft==false then + self:Transmission(ATIS.Sound.Right, 0.2) end end alltext = alltext .. ";\n" .. subtitle + + -- Runway length. + if self.rwylength then + + local runact = self.airbase:GetActiveRunway( self.runwaym2t ) + local length = runact.length + if not self.metric then + length = UTILS.MetersToFeet( length ) + end + + -- Length in thousands and hundrets of ft/meters. + local L1000, L0100 = self:_GetThousandsAndHundreds( length ) + + -- Subtitle. + local subtitle = string.format( "Runway length %d", length ) + if self.metric then + subtitle = subtitle .. " meters" + else + subtitle = subtitle .. " feet" + end + + -- Transmit. + if not self.useSRS then + self:Transmission( ATIS.Sound.RunwayLength, 1.0, subtitle ) + if tonumber( L1000 ) > 0 then + self.radioqueue:Number2Transmission( L1000 ) + self:Transmission( ATIS.Sound.Thousand, 0.1 ) + end + if tonumber( L0100 ) > 0 then + self.radioqueue:Number2Transmission( L0100 ) + self:Transmission( ATIS.Sound.Hundred, 0.1 ) + end + if self.metric then + self:Transmission( ATIS.Sound.Meters, 0.1 ) + else + self:Transmission( ATIS.Sound.Feet, 0.1 ) + end + end + alltext = alltext .. ";\n" .. subtitle + end end - -- Airfield elevation if self.elevation then @@ -2246,9 +2266,7 @@ function ATIS:onafterBroadcast( From, Event, To ) end -- ILS - --self:I({ils=self.ils}) local ils=self:GetNavPoint(self.ils, runwayLanding, rwyLandingLeft) - --self:I({ils=ils,runwayLanding=runwayLanding, rwyLandingLeft=rwyLandingLeft}) if ils then subtitle = string.format( "ILS frequency %.2f MHz", ils.frequency ) if not self.useSRS then @@ -2263,7 +2281,6 @@ function ATIS:onafterBroadcast( From, Event, To ) self:Transmission( ATIS.Sound.MegaHertz, 0.2 ) end alltext = alltext .. ";\n" .. subtitle - --self:I(alltext) end -- Outer NDB From 41eec658e06bffceb022c238484931b0bcff4052 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 28 Dec 2022 15:40:40 +0100 Subject: [PATCH 35/66] UTILS - Update Load/Save Groups and Statics (#1857) Addresses #1855 and #1856 Added options to save groups in a structrued manner, allowing to despawn specific unit-types on a reload. Optionally, cinematic effects like smoke and fires can be put in place of despawned units. For statics, an option to replace them with dead statics has been added, and also cinematic effects. --- Moose Development/Moose/Utilities/Utils.lua | 243 +++++++++++++++++--- 1 file changed, 212 insertions(+), 31 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 0e3b3d37d..5889b8dd9 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2177,10 +2177,29 @@ function UTILS.CheckFileExists(Path,Filename) end end +--- Function to obtain a table of typenames from the group given with the number of units of the same type in the group. +-- @param Wrapper.Group#GROUP Group The group to list +-- @return #table Table of typnames and typename counts, e.g. `{["KAMAZ Truck"]=3,["ATZ-5"]=1}` +function UTILS.GetCountPerTypeName(Group) + local units = Group:GetUnits() + local TypeNameTable = {} + for _,_unt in pairs (units) do + local unit = _unt -- Wrapper.Unit#UNIT + local typen = unit:GetTypeName() + if not TypeNameTable[typen] then + TypeNameTable[typen] = 1 + else + TypeNameTable[typen] = TypeNameTable[typen] + 1 + end + end + return TypeNameTable +end + --- Function to save the state of a list of groups found by name -- @param #table List Table of strings with groupnames -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. +-- @param #boolean Structured Append the data with a list of typenames in the group plus their count. -- @return #boolean outcome True if saving is successful, else false. -- @usage -- We will go through the list and find the corresponding group and save the current group size (0 when dead). @@ -2188,7 +2207,7 @@ end -- Position is still saved for your usage. -- The idea is to reduce the number of units when reloading the data again to restart the saved mission. -- The data will be a simple comma separated list of groupname and size, with one header line. -function UTILS.SaveStationaryListOfGroups(List,Path,Filename) +function UTILS.SaveStationaryListOfGroups(List,Path,Filename,Structured) local filename = Filename or "StateListofGroups" local data = "--Save Stationary List of Groups: "..Filename .."\n" for _,_group in pairs (List) do @@ -2196,7 +2215,16 @@ function UTILS.SaveStationaryListOfGroups(List,Path,Filename) if group and group:IsAlive() then local units = group:CountAliveUnits() local position = group:GetVec3() - data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) + if Structured then + local structure = UTILS.GetCountPerTypeName(group) + local strucdata = "" + for typen,anzahl in pairs (structure) do + strucdata = strucdata .. typen .. "=="..anzahl..";" + end + data = string.format("%s%s,%d,%d,%d,%d,%s\n",data,_group,units,position.x,position.y,position.z,strucdata) + else + data = string.format("%s%s,%d,%d,%d,%d\n",data,_group,units,position.x,position.y,position.z) + end else data = string.format("%s%s,0,0,0,0\n",data,_group) end @@ -2210,6 +2238,7 @@ end -- @param Core.Set#SET_BASE Set of objects to save -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. +-- @param #boolean Structured Append the data with a list of typenames in the group plus their count. -- @return #boolean outcome True if saving is successful, else false. -- @usage -- We will go through the set and find the corresponding group and save the current group size and current position. @@ -2219,7 +2248,7 @@ end -- **Note** Do NOT use dashes or hashes in group template names (-,#)! -- The data will be a simple comma separated list of groupname and size, with one header line. -- The current task/waypoint/etc cannot be restored. -function UTILS.SaveSetOfGroups(Set,Path,Filename) +function UTILS.SaveSetOfGroups(Set,Path,Filename,Structured) local filename = Filename or "SetOfGroups" local data = "--Save SET of groups: "..Filename .."\n" local List = Set:GetSetObjects() @@ -2233,7 +2262,16 @@ function UTILS.SaveSetOfGroups(Set,Path,Filename) end local units = group:CountAliveUnits() local position = group:GetVec3() - data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) + if Structured then + local structure = UTILS.GetCountPerTypeName(group) + local strucdata = "" + for typen,anzahl in pairs (structure) do + strucdata = strucdata .. typen .. "=="..anzahl..";" + end + data = string.format("%s%s,%s,%d,%d,%d,%d,%s\n",data,name,template,units,position.x,position.y,position.z,strucdata) + else + data = string.format("%s%s,%s,%d,%d,%d,%d\n",data,name,template,units,position.x,position.y,position.z) + end end end -- save the data @@ -2297,8 +2335,41 @@ end -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @param #boolean Reduce If false, existing loaded groups will not be reduced to fit the saved number. +-- @param #boolean Structured (Optional, needs Reduce = true) If true, and the data has been saved as structure before, remove the correct unit types as per the saved list. +-- @param #boolean Cinematic (Optional, needs Structured = true) If true, place a fire/smoke effect on the dead static position. +-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. +-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. -- @return #table Table of data objects (tables) containing groupname, coordinate and group object. Returns nil when file cannot be read. -function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) +-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` +function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce,Structured,Cinematic,Effect,Density) + + local fires = {} + + local function Smokers(name,coord,effect,density) + local eff = math.random(8) + if type(effect) == "number" then eff = effect end + coord:BigSmokeAndFire(eff,density,name) + table.insert(fires,name) + end + + local function Cruncher(group,typename,anzahl) + local units = group:GetUnits() + local reduced = 0 + for _,_unit in pairs (units) do + local typo = _unit:GetTypeName() + if typename == typo then + if Cinematic then + local coordinate = _unit:GetCoordinate() + local name = _unit:GetName() + Smokers(name,coordinate,Effect,Density) + end + _unit:Destroy(false) + reduced = reduced + 1 + if reduced == anzahl then break end + end + end + end + local reduce = true if Reduce == false then reduce = false end local filename = Filename or "StateListofGroups" @@ -2315,18 +2386,48 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) local posx = tonumber(dataset[3]) local posy = tonumber(dataset[4]) local posz = tonumber(dataset[5]) + local structure = dataset[6] + --BASE:I({structure}) local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) local data = { groupname=groupname, size=size, coordinate=coordinate, group=GROUP:FindByName(groupname) } if reduce then local actualgroup = GROUP:FindByName(groupname) if actualgroup and actualgroup:IsAlive() and actualgroup:CountAliveUnits() > size then - local reduction = actualgroup:CountAliveUnits() - size - BASE:I("Reducing groupsize by ".. reduction .. " units!") - -- reduce existing group - local units = actualgroup:GetUnits() - local units2 = UTILS.ShuffleTable(units) -- randomize table - for i=1,reduction do - units2[i]:Destroy(false) + if Structured and structure then + --BASE:I("Reducing group structure!") + local loadedstructure = {} + local strcset = UTILS.Split(structure,";") + for _,_data in pairs(strcset) do + local datasplit = UTILS.Split(_data,"==") + loadedstructure[datasplit[1]] = tonumber(datasplit[2]) + end + --BASE:I({loadedstructure}) + local originalstructure = UTILS.GetCountPerTypeName(actualgroup) + --BASE:I({originalstructure}) + for _name,_number in pairs(originalstructure) do + local loadednumber = 0 + if loadedstructure[_name] then + loadednumber = loadedstructure[_name] + end + local reduce = false + if loadednumber < _number then reduce = true end + + --BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce))) + + if reduce then + Cruncher(actualgroup,_name,_number-loadednumber) + end + + end + else + local reduction = actualgroup:CountAliveUnits() - size + --BASE:I("Reducing groupsize by ".. reduction .. " units!") + -- reduce existing group + local units = actualgroup:GetUnits() + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) + end end end end @@ -2335,19 +2436,52 @@ function UTILS.LoadStationaryListOfGroups(Path,Filename,Reduce) else return nil end - return datatable + return datatable,fires end --- Load back a SET of groups from file. -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @param #boolean Spawn If set to false, do not re-spawn the groups loaded in location and reduce to size. +-- @param #boolean Structured (Optional, needs Spawn=true)If true, and the data has been saved as structure before, remove the correct unit types as per the saved list. +-- @param #boolean Cinematic (Optional, needs Structured=true) If true, place a fire/smoke effect on the dead static position. +-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. +-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. -- @return Core.Set#SET_GROUP Set of GROUP objects. -- Returns nil when file cannot be read. Returns a table of data entries if Spawn is false: `{ groupname=groupname, size=size, coordinate=coordinate, template=template }` -function UTILS.LoadSetOfGroups(Path,Filename,Spawn) +-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` +function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density) + + local fires = {} + + local function Smokers(name,coord,effect,density) + local eff = math.random(8) + if type(effect) == "number" then eff = effect end + coord:BigSmokeAndFire(eff,density,name) + table.insert(fires,name) + end + + local function Cruncher(group,typename,anzahl) + local units = group:GetUnits() + local reduced = 0 + for _,_unit in pairs (units) do + local typo = _unit:GetTypeName() + if typename == typo then + if Cinematic then + local coordinate = _unit:GetCoordinate() + local name = _unit:GetName() + Smokers(name,coordinate,Effect,Density) + end + _unit:Destroy(false) + reduced = reduced + 1 + if reduced == anzahl then break end + end + end + end + local spawn = true if Spawn == false then spawn = false end - BASE:I("Spawn = "..tostring(spawn)) + --BASE:I("Spawn = "..tostring(spawn)) local filename = Filename or "SetOfGroups" local setdata = SET_GROUP:New() local datatable = {} @@ -2364,6 +2498,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn) local posx = tonumber(dataset[4]) local posy = tonumber(dataset[5]) local posz = tonumber(dataset[6]) + local structure = dataset[7] local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) local group=nil local data = { groupname=groupname, size=size, coordinate=coordinate, template=template } @@ -2376,12 +2511,40 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn) setdata:AddObject(spwndgrp) local actualsize = spwndgrp:CountAliveUnits() if actualsize > size then - local reduction = actualsize-size - -- reduce existing group - local units = spwndgrp:GetUnits() - local units2 = UTILS.ShuffleTable(units) -- randomize table - for i=1,reduction do - units2[i]:Destroy(false) + if Structured and structure then + --BASE:I("Reducing group structure!") + local loadedstructure = {} + local strcset = UTILS.Split(structure,";") + for _,_data in pairs(strcset) do + local datasplit = UTILS.Split(_data,"==") + loadedstructure[datasplit[1]] = tonumber(datasplit[2]) + end + --BASE:I({loadedstructure}) + local originalstructure = UTILS.GetCountPerTypeName(spwndgrp) + --BASE:I({originalstructure}) + for _name,_number in pairs(originalstructure) do + local loadednumber = 0 + if loadedstructure[_name] then + loadednumber = loadedstructure[_name] + end + local reduce = false + if loadednumber < _number then reduce = true end + + --BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce))) + + if reduce then + Cruncher(spwndgrp,_name,_number-loadednumber) + end + + end + else + local reduction = actualsize-size + -- reduce existing group + local units = spwndgrp:GetUnits() + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) + end end end end @@ -2393,7 +2556,7 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn) return nil end if spawn then - return setdata + return setdata,fires else return datatable end @@ -2412,12 +2575,7 @@ function UTILS.LoadSetOfStatics(Path,Filename) table.remove(loadeddata, 1) for _id,_entry in pairs (loadeddata) do local dataset = UTILS.Split(_entry,",") - -- staticname,position.x,position.y,position.z local staticname = dataset[1] - --local posx = tonumber(dataset[2]) - --local posy = tonumber(dataset[3]) - --local posz = tonumber(dataset[4]) - --local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) local StaticObject = STATIC:FindByName(staticname,false) if StaticObject then datatable:AddObject(StaticObject) @@ -2433,9 +2591,15 @@ end -- @param #string Path The path to use. Use double backslashes \\\\ on Windows filesystems. -- @param #string Filename The name of the file. -- @param #boolean Reduce If false, do not destroy the units with size=0. --- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. +-- @param #boolean Dead (Optional, needs Reduce = true) If Dead is true, re-spawn the dead object as dead and do not just delete it. +-- @param #boolean Cinematic (Optional, needs Dead = true) If true, place a fire/smoke effect on the dead static position. +-- @param #number Effect (Optional for Cinematic) What effect to use. Defaults to a random effect. Smoke presets are: 1=small smoke and fire, 2=medium smoke and fire, 3=large smoke and fire, 4=huge smoke and fire, 5=small smoke, 6=medium smoke, 7=large smoke, 8=huge smoke. +-- @param #number Density (Optional for Cinematic) What smoke density to use, can be 0 to 1. Defaults to 0.5. +-- @return #table Table of data objects (tables) containing staticname, size (0=dead else 1), coordinate and the static object. Dead objects will have coordinate points `{x=0,y=0,z=0}` +-- @return #table When using Cinematic: table of names of smoke and fire objects, so they can be extinguished with `COORDINATE.StopBigSmokeAndFire( name )` -- Returns nil when file cannot be read. -function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) +function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce,Dead,Cinematic,Effect,Density) + local fires = {} local reduce = true if Reduce == false then reduce = false end local filename = Filename or "StateListofStatics" @@ -2458,14 +2622,31 @@ function UTILS.LoadStationaryListOfStatics(Path,Filename,Reduce) if size==0 and reduce then local static = STATIC:FindByName(staticname,false) if static then - static:Destroy(false) + if Dead then + local deadobject = SPAWNSTATIC:NewFromStatic(staticname,static:GetCountry()) + deadobject:InitDead(true) + local heading = static:GetHeading() + local coord = static:GetCoordinate() + static:Destroy(false) + deadobject:SpawnFromCoordinate(coord,heading,staticname) + if Cinematic then + local effect = math.random(8) + if type(Effect) == "number" then + effect = Effect + end + coord:BigSmokeAndFire(effect,Density,staticname) + table.insert(fires,staticname) + end + else + static:Destroy(false) + end end end end else return nil end - return datatable + return datatable,fires end --- Heading Degrees (0-360) to Cardinal From 8661d07e1e2cdd7f0e3ee47ba501bb3771b55a18 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 29 Dec 2022 16:33:13 +0100 Subject: [PATCH 36/66] #ATIS * Make SRS say TACAN and FARP and not spell the single characters --- Moose Development/Moose/Ops/ATIS.lua | 2 ++ 1 file changed, 2 insertions(+) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index e65a2ac71..96794d939 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -2416,6 +2416,8 @@ function ATIS:onafterReport( From, Event, To, Text ) local text = string.gsub( text, "mmHg", "millimeters of Mercury" ) local text = string.gsub( text, "hPa", "hectopascals" ) local text = string.gsub( text, "m/s", "meters per second" ) + local text = string.gsub( text, "TACAN", "tackan" ) + local text = string.gsub( text, "FARP", "farp" ) -- Replace ";" by "." local text = string.gsub( text, ";", " . " ) From 5d802f0e16081f92dad6bd7b61013ae9fefffbe9 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 1 Jan 2023 12:34:02 +0100 Subject: [PATCH 37/66] #TIMER * Added `StartIf()` --- Moose Development/Moose/Core/Timer.lua | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Core/Timer.lua b/Moose Development/Moose/Core/Timer.lua index 88e1e77b3..02e4cee63 100644 --- a/Moose Development/Moose/Core/Timer.lua +++ b/Moose Development/Moose/Core/Timer.lua @@ -155,7 +155,7 @@ function TIMER:New(Function, ...) return self end ---- Create a new TIMER object. +--- Start TIMER object. -- @param #TIMER self -- @param #number Tstart Relative start time in seconds. -- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once. @@ -192,6 +192,20 @@ function TIMER:Start(Tstart, dT, Duration) return self end +--- Start TIMER object if a condition is met. Useful for e.g. debugging. +-- @param #TIMER self +-- @param #boolean Condition Must be true for the TIMER to start +-- @param #number Tstart Relative start time in seconds. +-- @param #number dT Interval between function calls in seconds. If not specified `nil`, the function is called only once. +-- @param #number Duration Time in seconds for how long the timer is running. If not specified `nil`, the timer runs forever or until stopped manually by the `TIMER:Stop()` function. +-- @return #TIMER self +function TIMER:StartIf(Condition,Tstart, dT, Duration) + if Condition then + self:Start(Tstart, dT, Duration) + end + return self +end + --- Stop the timer by removing the timer function. -- @param #TIMER self -- @param #number Delay (Optional) Delay in seconds, before the timer is stopped. From cdd240abb7b03cdd2ce407853e2a25fabc1696b0 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Mon, 2 Jan 2023 14:28:35 +0100 Subject: [PATCH 38/66] PseudoATC - Option to display playername (#1870) Use `myatc:SetReportPlayername()` to switch this on #1864 --- .../Moose/Functional/PseudoATC.lua | 21 ++++++++++++++++--- 1 file changed, 18 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 0cf76281f..67b86c5fe 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -45,6 +45,7 @@ -- @field #number talt Interval in seconds between reporting altitude until touchdown. Default 3 sec. -- @field #boolean chatty Display some messages on events like take-off and touchdown. -- @field #boolean eventsmoose If true, events are handled by MOOSE. If false, events are handled directly by DCS eventhandler. +-- @field #boolean reportplayername If true, use playername not callsign on callouts -- @extends Core.Base#BASE --- Adds some rudimentary ATC functionality via the radio menu. @@ -88,6 +89,7 @@ PSEUDOATC={ talt=3, chatty=true, eventsmoose=true, + reportplayername = false, } ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- @@ -98,7 +100,7 @@ PSEUDOATC.id="PseudoATC | " --- PSEUDOATC version. -- @field #number version -PSEUDOATC.version="0.9.2" +PSEUDOATC.version="0.9.3" ----------------------------------------------------------------------------------------------------------------------------------------- @@ -183,6 +185,13 @@ function PSEUDOATC:SetMessageDuration(duration) self.mdur=duration or 30 end +--- Use player name, not call sign, in callouts +-- @param #PSEUDOATC self +function PSEUDOATC:SetReportPlayername() + self.reportplayername = true + return self +end + --- Set time interval after which the F10 radio menu is refreshed. -- @param #PSEUDOATC self -- @param #number interval Interval in seconds. Default is every 120 sec. @@ -485,6 +494,9 @@ function PSEUDOATC:PlayerTakeOff(unit, place) -- Bye-Bye message. if place and self.chatty then local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign) + if self.reportplayername then + text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName) + end MESSAGE:New(text, self.mdur):ToGroup(group) end @@ -844,7 +856,8 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear) local position=unit:GetCoordinate() local height=get_AGL(position) local callsign=unit:GetCallsign() - + local PlayerName=self.group[GID].player[UID].playername + -- Settings. local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS @@ -856,7 +869,9 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear) -- Message text. local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs) - + if self.reportplayername then + _text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs) + end -- Append flight level. if _clear==false then _text=_text..string.format(" FL%03d.", position.y/30.48) From 3fbfb8b5284820187dad7f473dfe049c773bd200 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 2 Jan 2023 17:27:01 +0100 Subject: [PATCH 39/66] #UNIT * Fix #1865 --- Moose Development/Moose/Wrapper/Unit.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Unit.lua b/Moose Development/Moose/Wrapper/Unit.lua index 8cf7c9226..596f670e4 100644 --- a/Moose Development/Moose/Wrapper/Unit.lua +++ b/Moose Development/Moose/Wrapper/Unit.lua @@ -229,7 +229,7 @@ function UNIT:ReSpawnAt( Coordinate, Heading ) SpawnGroupTemplate.y = Coordinate.z self:F( #SpawnGroupTemplate.units ) - for UnitID, UnitData in pairs( SpawnGroup:GetUnits() ) do + for UnitID, UnitData in pairs( SpawnGroup:GetUnits() or {} ) do local GroupUnit = UnitData -- #UNIT self:F( GroupUnit:GetName() ) if GroupUnit:IsAlive() then From b0eef34146edb5d76020698389a21a4e97fcd59c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 3 Jan 2023 10:14:34 +0100 Subject: [PATCH 40/66] #CONTROLLABLE * Docu fix #WAREHOUSE * Changes from dev #ZONE * Additions to ZONE_POLYGON --- Moose Development/Moose/Core/Zone.lua | 72 ++++++ .../Moose/Functional/Warehouse.lua | 236 +++++++++++------- .../Moose/Wrapper/Controllable.lua | 2 +- 3 files changed, 215 insertions(+), 95 deletions(-) diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 5f89fdaed..70495842d 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -2084,6 +2084,52 @@ function ZONE_POLYGON_BASE:DrawZone(Coalition, Color, Alpha, FillColor, FillAlph return self end + +--- Get the smallest circular 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_RADIUS The circular zone. +function ZONE_POLYGON_BASE:GetZoneRadius(ZoneName, DoNotRegisterZone) + + 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 + + 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. +-- @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 +end + --- Smokes the zone boundaries in a color. -- @param #ZONE_POLYGON_BASE self -- @param Utilities.Utils#SMOKECOLOR SmokeColor The smoke color. @@ -2286,6 +2332,32 @@ function ZONE_POLYGON_BASE:GetBoundingSquare() return { x1 = x1, y1 = y1, x2 = x2, y2 = y2 } end +--- Get the bounding 2D vectors of the polygon. +-- @param #ZONE_POLYGON_BASE self +-- @return DCS#Vec2 Coordinates of western-southern-lower vertex of the box. +-- @return DCS#Vec2 Coordinates of eastern-northern-upper vertex of the box. +function ZONE_POLYGON_BASE:GetBoundingVec2() + + local x1 = self._.Polygon[1].x + local y1 = self._.Polygon[1].y + local x2 = self._.Polygon[1].x + local y2 = self._.Polygon[1].y + + for i = 2, #self._.Polygon do + self:T2( { self._.Polygon[i], x1, y1, x2, y2 } ) + x1 = ( x1 > self._.Polygon[i].x ) and self._.Polygon[i].x or x1 + x2 = ( x2 < self._.Polygon[i].x ) and self._.Polygon[i].x or x2 + y1 = ( y1 > self._.Polygon[i].y ) and self._.Polygon[i].y or y1 + 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} + + return vec1, vec2 +end + --- Draw a frontier on the F10 map with small filled circles. -- @param #ZONE_POLYGON_BASE self -- @param #number Coalition (Optional) Coalition: All=-1, Neutral=0, Red=1, Blue=2. Default -1= All. diff --git a/Moose Development/Moose/Functional/Warehouse.lua b/Moose Development/Moose/Functional/Warehouse.lua index 26cef4db9..8a1dfcd95 100644 --- a/Moose Development/Moose/Functional/Warehouse.lua +++ b/Moose Development/Moose/Functional/Warehouse.lua @@ -302,8 +302,8 @@ -- -- Initial Spawn states is as follows: -- GROUND: ROE, "Return Fire" Alarm, "Green" --- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense" --- NAVAL ROE, "Return Fire" Alarm,"N/A" +-- AIR: ROE, "Return Fire" Reaction to Threat, "Passive Defense" +-- NAVAL ROE, "Return Fire" Alarm,"N/A" -- -- A request can be added by the @{#WAREHOUSE.AddRequest}(*warehouse*, *AssetDescriptor*, *AssetDescriptorValue*, *nAsset*, *TransportType*, *nTransport*, *Prio*, *Assignment*) function. -- The parameters are @@ -2647,6 +2647,13 @@ function WAREHOUSE:SetWarehouseZone(zone) return self end +--- Get the warehouse zone. +-- @param #WAREHOUSE self +-- @return Core.Zone#ZONE The warehouse zone. +function WAREHOUSE:GetWarehouseZone() + return self.zone +end + --- Set auto defence on. When the warehouse is under attack, all ground assets are spawned automatically and will defend the warehouse zone. -- @param #WAREHOUSE self -- @return #WAREHOUSE self @@ -5810,6 +5817,7 @@ function WAREHOUSE:_SpawnAssetRequest(Request) -- Now we try to find all parking spots for all cargo groups in advance. Due to the for loop, the parking spots do not get updated while spawning. local Parking={} if Request.cargocategory==Group.Category.AIRPLANE or Request.cargocategory==Group.Category.HELICOPTER then + --TODO: Check for airstart. Should be a request property. Parking=self:_FindParkingForAssets(self.airbase, cargoassets) or {} end @@ -6069,7 +6077,9 @@ function WAREHOUSE:_SpawnAssetAircraft(alias, asset, request, parking, uncontrol end if self.Debug then - coord:MarkToAll(string.format("Spawnplace unit %s terminal %d.", unit.name, terminal)) + local text=string.format("Spawnplace unit %s terminal %d.", unit.name, terminal) + coord:MarkToAll(text) + env.info(text) end unit.x=coord.x @@ -7374,6 +7384,7 @@ function WAREHOUSE:_CheckRequestNow(request) local _transports local _assetattribute local _assetcategory + local _assetairstart=false -- Check if at least one (cargo) asset is available. if _nassets>0 then @@ -7381,21 +7392,28 @@ function WAREHOUSE:_CheckRequestNow(request) -- Get the attibute of the requested asset. _assetattribute=_assets[1].attribute _assetcategory=_assets[1].category + _assetairstart=_assets[1].takeoffType and _assets[1].takeoffType==COORDINATE.WaypointType.TurningPoint or false -- Check available parking for air asset units. if _assetcategory==Group.Category.AIRPLANE or _assetcategory==Group.Category.HELICOPTER then if self.airbase and self.airbase:GetCoalition()==self:GetCoalition() then - if self:IsRunwayOperational() then + if self:IsRunwayOperational() or _assetairstart then - local Parking=self:_FindParkingForAssets(self.airbase,_assets) - - --if Parking==nil and not (self.category==Airbase.Category.HELIPAD) then - if Parking==nil then - local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias) - self:_InfoMessage(text, 5) - return false + if _assetairstart then + -- Airstart no need to check parking + else + + -- Check parking. + local Parking=self:_FindParkingForAssets(self.airbase,_assets) + + -- No parking? + if Parking==nil then + local text=string.format("Warehouse %s: Request denied! Not enough free parking spots for all requested assets at the moment.", self.alias) + self:_InfoMessage(text, 5) + return false + end end else @@ -7969,93 +7987,123 @@ function WAREHOUSE:_FindParkingForAssets(airbase, assets) -- Loop over all assets that need a parking psot. for _,asset in pairs(assets) do local _asset=asset --#WAREHOUSE.Assetitem - - -- Get terminal type of this asset - local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory()) - - -- Asset specific parking. - parking[_asset.uid]={} - - -- Loop over all units - each one needs a spot. - for i=1,_asset.nunits do - -- Asset name - local assetname=_asset.spawngroupname.."-"..tostring(i) - - -- Loop over all parking spots. - local gotit=false - for _,_parkingspot in pairs(parkingdata) do - local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot - - -- Check correct terminal type for asset. We don't want helos in shelters etc. - if AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) and self:_CheckParkingValid(parkingspot) and self:_CheckParkingAsset(parkingspot, asset) and airbase:_CheckParkingLists(parkingspot.TerminalID) then - - -- Coordinate of the parking spot. - local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE - local _termid=parkingspot.TerminalID - local free=true - local problem=nil - - -- Loop over all obstacles. - for _,obstacle in pairs(obstacles) do - - -- Check if aircraft overlaps with any obstacle. - local dist=_spot:Get2DDistance(obstacle.coord) - local safe=_overlap(_asset.size, obstacle.size, dist) - - -- Spot is blocked. - if not safe then - self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist)) - free=false - problem=obstacle - problem.dist=dist - break - else - --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist)) - end - - end - - -- Check if spot is free - if free then - - -- Add parkingspot for this asset unit. - table.insert(parking[_asset.uid], parkingspot) - - -- Debug - self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid)) - - -- Add the unit as obstacle so that this spot will not be available for the next unit. - table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"}) - - gotit=true - break + if not _asset.spawned then + -- Get terminal type of this asset + local terminaltype=asset.terminalType or self:_GetTerminal(asset.attribute, self:GetAirbaseCategory()) + + -- Asset specific parking. + parking[_asset.uid]={} + + -- Loop over all units - each one needs a spot. + for i=1,_asset.nunits do + + -- Asset name + local assetname=_asset.spawngroupname.."-"..tostring(i) + + -- Loop over all parking spots. + local gotit=false + for _,_parkingspot in pairs(parkingdata) do + local parkingspot=_parkingspot --Wrapper.Airbase#AIRBASE.ParkingSpot + + -- Parking valid? + local valid=true + + if asset.parkingIDs then + -- If asset has assigned parking spots, we take these no matter what. + valid=self:_CheckParkingAsset(parkingspot, asset) else - - -- Debug output for occupied spots. - if self.Debug then - local coord=problem.coord --Core.Point#COORDINATE - local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist) - self:I(self.lid..text) - coord:MarkToAll(string.format(text)) - else - self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) - end - + + -- Valid terminal type depending on attribute. + local validTerminal=AIRBASE._CheckTerminalType(parkingspot.TerminalType, terminaltype) + + -- Valid parking list. + local validParking=self:_CheckParkingValid(parkingspot) + + -- Black and white list. + local validBWlist=airbase:_CheckParkingLists(parkingspot.TerminalID) + + -- Debug info. + --env.info(string.format("FF validTerminal = %s", tostring(validTerminal))) + --env.info(string.format("FF validParking = %s", tostring(validParking))) + --env.info(string.format("FF validBWlist = %s", tostring(validBWlist))) + + -- Check if all are true + valid=validTerminal and validParking and validBWlist end - - else - self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType)) - end -- check terminal type - end -- loop over parking spots - - -- No parking spot for at least one asset :( - if not gotit then - self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid)) - return nil - end - end -- loop over asset units + + + -- Check correct terminal type for asset. We don't want helos in shelters etc. + if valid then + + -- Coordinate of the parking spot. + local _spot=parkingspot.Coordinate -- Core.Point#COORDINATE + local _termid=parkingspot.TerminalID + local free=true + local problem=nil + + -- Loop over all obstacles. + for _,obstacle in pairs(obstacles) do + + -- Check if aircraft overlaps with any obstacle. + local dist=_spot:Get2DDistance(obstacle.coord) + local safe=_overlap(_asset.size, obstacle.size, dist) + + -- Spot is blocked. + if not safe then + self:T3(self.lid..string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is NOT SAFE", assetname, _asset.uid, _termid, dist)) + free=false + problem=obstacle + problem.dist=dist + break + else + --env.info(string.format("FF asset=%s (id=%d): spot id=%d dist=%.1fm is SAFE", assetname, _asset.uid, _termid, dist)) + end + + end + + -- Check if spot is free + if free then + + -- Add parkingspot for this asset unit. + table.insert(parking[_asset.uid], parkingspot) + + -- Debug + self:T(self.lid..string.format("Parking spot %d is free for asset %s [id=%d]!", _termid, assetname, _asset.uid)) + + -- Add the unit as obstacle so that this spot will not be available for the next unit. + table.insert(obstacles, {coord=_spot, size=_asset.size, name=assetname, type="asset"}) + + gotit=true + break + + else + + -- Debug output for occupied spots. + if self.Debug then + local coord=problem.coord --Core.Point#COORDINATE + local text=string.format("Obstacle %s [type=%s] blocking spot=%d! Size=%.1f m and distance=%.1f m.", problem.name, problem.type, _termid, problem.size, problem.dist) + self:I(self.lid..text) + coord:MarkToAll(string.format(text)) + else + self:T(self.lid..string.format("Parking spot %d is occupied or not big enough!", _termid)) + end + + end + + else + self:T2(self.lid..string.format("Terminal ID=%d: type=%s not supported", parkingspot.TerminalID, parkingspot.TerminalType)) + end -- check terminal type + end -- loop over parking spots + + -- No parking spot for at least one asset :( + if not gotit then + self:I(self.lid..string.format("WARNING: No free parking spot for asset %s [id=%d]", assetname, _asset.uid)) + return nil + end + end -- loop over asset units + end -- Asset spawned check end -- loop over asset groups return parking diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 5cf5b4ac2..44e95efe2 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1416,7 +1416,7 @@ end -- @param #CONTROLLABLE self -- @param Core.Zone#ZONE Zone The zone where to land. -- @param #number Duration The duration in seconds to stay on the ground. --- @return #CONTROLLABLE self +-- @return DCS#Task The DCS task structure. function CONTROLLABLE:TaskLandAtZone( Zone, Duration, RandomPoint ) -- Get landing point From 793c0d988eb91fb3125de814f208411fe8ae895b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 3 Jan 2023 10:22:10 +0100 Subject: [PATCH 41/66] Changes from dev --- Moose Development/Moose/Core/Set.lua | 644 ++++++++++++++++++-- Moose Development/Moose/Utilities/Utils.lua | 76 ++- 2 files changed, 663 insertions(+), 57 deletions(-) diff --git a/Moose Development/Moose/Core/Set.lua b/Moose Development/Moose/Core/Set.lua index 82a7d65a5..b973172c1 100644 --- a/Moose Development/Moose/Core/Set.lua +++ b/Moose Development/Moose/Core/Set.lua @@ -53,6 +53,7 @@ do -- SET_BASE -- @field #table Index Table of indices. -- @field #table List Unused table. -- @field Core.Scheduler#SCHEDULER CallScheduler + -- @field #SET_BASE.Filters Filter Filters -- @extends Core.Base#BASE --- The @{Core.Set#SET_BASE} class defines the core functions that define a collection of objects. @@ -83,6 +84,11 @@ do -- SET_BASE YieldInterval = nil, } + --- Filters + -- @type SET_BASE.Filters + -- @field #table Coalition Coalitions + -- @field #table Prefix Prefixes. + --- Creates a new SET_BASE object, building a set of units belonging to a coalitions, categories, countries, types or with defined prefix names. -- @param #SET_BASE self -- @return #SET_BASE @@ -135,11 +141,12 @@ do -- SET_BASE --- Clear the Objects in the Set. -- @param #SET_BASE self + -- @param #boolean TriggerEvent If `true`, an event remove is triggered for each group that is removed from the set. -- @return #SET_BASE self - function SET_BASE:Clear() + function SET_BASE:Clear(TriggerEvent) for Name, Object in pairs( self.Set ) do - self:Remove( Name ) + self:Remove( Name, not TriggerEvent ) end return self @@ -166,7 +173,7 @@ do -- SET_BASE --- Gets a list of the Names of the Objects in the Set. -- @param #SET_BASE self - -- @return #SET_BASE self + -- @return #table Table of names. function SET_BASE:GetSetNames() -- R2.3 self:F2() @@ -181,7 +188,7 @@ do -- SET_BASE --- Returns a table of the Objects in the Set. -- @param #SET_BASE self - -- @return #SET_BASE self + -- @return #table Table of objects. function SET_BASE:GetSetObjects() -- R2.3 self:F2() @@ -197,16 +204,21 @@ do -- SET_BASE --- Removes a @{Core.Base#BASE} object from the @{Core.Set#SET_BASE} and derived classes, based on the Object Name. -- @param #SET_BASE self -- @param #string ObjectName - -- @param NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. + -- @param #boolean NoTriggerEvent (Optional) When `true`, the :Remove() method will not trigger a **Removed** event. function SET_BASE:Remove( ObjectName, NoTriggerEvent ) self:F2( { ObjectName = ObjectName } ) local TriggerEvent = true - if NoTriggerEvent then TriggerEvent = false end + if NoTriggerEvent then + TriggerEvent = false + else + TriggerEvent = true + end local Object = self.Set[ObjectName] if Object then + for Index, Key in ipairs( self.Index ) do if Key == ObjectName then table.remove( self.Index, Index ) @@ -214,6 +226,7 @@ do -- SET_BASE break end end + -- When NoTriggerEvent is true, then no Removed event will be triggered. if TriggerEvent then self:Removed( ObjectName, Object ) @@ -311,7 +324,6 @@ do -- SET_BASE -- @param #SET_BASE self -- @param Core.Set#SET_BASE SetB Set other set, called *B*. -- @return Core.Set#SET_BASE A set of objects that is included in set *A* **and** in set *B*. - function SET_BASE:GetSetIntersection(SetB) local intersection=SET_BASE:New() @@ -461,16 +473,32 @@ do -- SET_BASE -- @param #SET_BASE self -- @return #SET_BASE self function SET_BASE:FilterOnce() + + --self:Clear() for ObjectName, Object in pairs( self.Database ) do if self:IsIncludeObject( Object ) then self:Add( ObjectName, Object ) + else + self:Remove(ObjectName, true) end end return self end + + --- Clear all filters. You still need to apply :FilterOnce() + -- @param #SET_BASE self + -- @return #SET_BASE self + function SET_BASE:FilterClear() + + for key,value in pairs(self.Filter) do + self.Filter[key]={} + end + + return self + end --- Starts the filtering for the defined collection. -- @param #SET_BASE self @@ -817,7 +845,7 @@ do -- SET_BASE --- Decides whether an object is in the SET -- @param #SET_BASE self -- @param #table Object - -- @return #SET_BASE self + -- @return #boolean `true` if object is in set and `false` otherwise. function SET_BASE:IsInSet( Object ) self:F3( Object ) local outcome = false @@ -1021,9 +1049,9 @@ do -- SET_GROUP return self end - --- Gets the Set. + --- Get a *new* set that only contains alive groups. -- @param #SET_GROUP self - -- @return #table Table of objects + -- @return #SET_GROUP Set of alive groups. function SET_GROUP:GetAliveSet() self:F2() @@ -1169,11 +1197,14 @@ do -- SET_GROUP --- Builds a set of groups in zones. -- @param #SET_GROUP self -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE + -- @param #boolean Clear If `true`, clear any previously defined filters. -- @return #SET_GROUP self - function SET_GROUP:FilterZones( Zones ) - if not self.Filter.Zones then + function SET_GROUP:FilterZones( Zones, Clear ) + + if Clear or not self.Filter.Zones then self.Filter.Zones = {} end + local zones = {} if Zones.ClassName and Zones.ClassName == "SET_ZONE" then zones = Zones.Set @@ -1183,34 +1214,12 @@ do -- SET_GROUP else zones = Zones end + for _, Zone in pairs( zones ) do local zonename = Zone:GetName() self.Filter.Zones[zonename] = Zone end - return self - end - - --- Builds a set of groups in zones. - -- @param #SET_GROUP self - -- @param #table Zones Table of Core.Zone#ZONE Zone objects, or a Core.Set#SET_ZONE - -- @return #SET_GROUP self - function SET_GROUP:FilterZones( Zones ) - if not self.Filter.Zones then - self.Filter.Zones = {} - end - local zones = {} - if Zones.ClassName and Zones.ClassName == "SET_ZONE" then - zones = Zones.Set - elseif type( Zones ) ~= "table" or (type( Zones ) == "table" and Zones.ClassName ) then - self:E("***** FilterZones needs either a table of ZONE Objects or a SET_ZONE as parameter!") - return self - else - zones = Zones - end - for _,Zone in pairs( zones ) do - local zonename = Zone:GetName() - self.Filter.Zones[zonename] = Zone - end + return self end @@ -1218,17 +1227,21 @@ do -- SET_GROUP -- Possible current coalitions are red, blue and neutral. -- @param #SET_GROUP self -- @param #string Coalitions Can take the following values: "red", "blue", "neutral". + -- @param #boolean Clear If `true`, clear any previously defined filters. -- @return #SET_GROUP self - function SET_GROUP:FilterCoalitions( Coalitions ) - if not self.Filter.Coalitions then + function SET_GROUP:FilterCoalitions( Coalitions, Clear ) + + if Clear or (not self.Filter.Coalitions) then self.Filter.Coalitions = {} end - if type( Coalitions ) ~= "table" then - Coalitions = { Coalitions } - end + + -- Ensure table. + Coalitions = UTILS.EnsureTable(Coalitions, false) + for CoalitionID, Coalition in pairs( Coalitions ) do self.Filter.Coalitions[Coalition] = Coalition end + return self end @@ -1236,17 +1249,22 @@ do -- SET_GROUP -- Possible current categories are plane, helicopter, ground, ship. -- @param #SET_GROUP self -- @param #string Categories Can take the following values: "plane", "helicopter", "ground", "ship". + -- @param #boolean Clear If `true`, clear any previously defined filters. -- @return #SET_GROUP self - function SET_GROUP:FilterCategories( Categories ) - if not self.Filter.Categories then + function SET_GROUP:FilterCategories( Categories, Clear ) + + if Clear or not self.Filter.Categories then self.Filter.Categories = {} end + if type( Categories ) ~= "table" then Categories = { Categories } end + for CategoryID, Category in pairs( Categories ) do self.Filter.Categories[Category] = Category end + return self end @@ -1899,6 +1917,41 @@ do -- SET_GROUP end end + + --- 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. + -- @return Wrapper.Group#GROUP The closest group (if any). + -- @return #number Distance in meters to the closest group. + function SET_GROUP:GetClosestGroup(Coordinate, Coalitions) + + local Set = self:GetSet() + + local dmin=math.huge + local gmin=nil + + for GroupID, GroupData in pairs( Set ) do -- For each GROUP in SET_GROUP + local group=GroupData --Wrapper.Group#GROUP + + if group and group:IsAlive() and (Coalitions==nil or UTILS.IsAnyInTable(Coalitions, group:GetCoalition())) then + + local coord=group:GetCoord() + + -- Distance between ref. coordinate and group coordinate. + local d=UTILS.VecDist3D(Coordinate, coord) + + if d Date: Thu, 5 Jan 2023 10:45:37 +0100 Subject: [PATCH 42/66] #CTLD/CSAR * Small fix for --- Moose Development/Moose/Ops/CSAR.lua | 5 +++-- Moose Development/Moose/Ops/CTLD.lua | 32 +++++++++++++++------------- 2 files changed, 20 insertions(+), 17 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index da3853223..b7cbbeda6 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -272,7 +272,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.16" +CSAR.version="1.0.17" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -2108,9 +2108,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local _radioUnit = _group:GetUnit(1) if _radioUnit then local Frequency = _freq -- Freq in Hertz + local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight + trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight end end return self diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 677c810e8..d15f32f59 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1088,7 +1088,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.24" +CTLD.version="1.0.25" --- Instantiate a new CTLD. -- @param #CTLD self @@ -3754,18 +3754,24 @@ function CTLD:_AddRadioBeacon(Name, Sound, Mhz, Modulation, IsShip, IsDropped) end end local Sound = Sound or "beacon.ogg" - if IsDropped and Zone then + if Zone then + if IsDropped then local ZoneCoord = Zone - local ZoneVec3 = ZoneCoord:GetVec3(1) - local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz + local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0} + local Frequency = Mhz * 1000000 -- Freq in Hertz local Sound = self.RadioPath..Sound - trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight - elseif Zone then - local ZoneCoord = Zone:GetCoordinate(1) - local ZoneVec3 = ZoneCoord:GetVec3() - local Frequency = string.format("%09d",Mhz * 1000000) -- Freq in Hertz + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straight + self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = %d %d %d | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) + else + local ZoneCoord = Zone:GetCoordinate() + local ZoneVec3 = ZoneCoord:GetVec3() or {x=0,y=0,z=0} + local Frequency = Mhz * 1000000 -- Freq in Hert local Sound = self.RadioPath..Sound - trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, tonumber(Frequency), 1000) -- Beacon in MP only runs for 30secs straight + trigger.action.radioTransmission(Sound, ZoneVec3, Modulation, false, Frequency, 1000, Name..math.random(1,10000)) -- Beacon in MP only runs for 30secs straightt + self:T2(string.format("Beacon added | Name = %s | Sound = %s | Vec3 = {x=%d, y=%d, z=%d} | Freq = %f | Modulation = %d (0=AM/1=FM)",Name,Sound,ZoneVec3.x,ZoneVec3.y,ZoneVec3.z,Mhz,Modulation)) + end + else + self:E(self.lid.."***** _AddRadioBeacon: Zone does not exist: "..Name) end return self end @@ -3817,11 +3823,7 @@ function CTLD:_RefreshRadioBeacons() local Name = czone.name local FM = FMbeacon.frequency -- MHz local VHF = VHFbeacon.frequency -- KHz - local UHF = UHFbeacon.frequency -- MHz - -- local co = coroutine.create(self._AddRadioBeacon) - --coroutine.resume(co, self, Name,Sound,FM,CTLD.RadioModulation.FM, IsShip, IsDropped) - --coroutine.resume(co, self, Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped) - --coroutine.resume(co, self, Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped) + local UHF = UHFbeacon.frequency -- MHz self:_AddRadioBeacon(Name,Sound,FM, CTLD.RadioModulation.FM, IsShip, IsDropped) self:_AddRadioBeacon(Name,Sound,VHF,CTLD.RadioModulation.FM, IsShip, IsDropped) self:_AddRadioBeacon(Name,Sound,UHF,CTLD.RadioModulation.AM, IsShip, IsDropped) From 59e8aed4451ffb217d224bd60d6e52d3fb4da7a6 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Jan 2023 10:48:50 +0100 Subject: [PATCH 43/66] #CSAR fix for beacons --- Moose Development/Moose/Ops/CSAR.lua | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index da3853223..b7cbbeda6 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -272,7 +272,7 @@ CSAR.AircraftType["Bronco-OV-10A"] = 2 --- CSAR class version. -- @field #string version -CSAR.version="1.0.16" +CSAR.version="1.0.17" ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- ToDo list @@ -2108,9 +2108,10 @@ function CSAR:_AddBeaconToGroup(_group, _freq) local _radioUnit = _group:GetUnit(1) if _radioUnit then local Frequency = _freq -- Freq in Hertz + local name = _radioUnit:GetName() local Sound = "l10n/DEFAULT/"..self.radioSound local vec3 = _radioUnit:GetVec3() or _radioUnit:GetPositionVec3() or {x=0,y=0,z=0} - trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000) -- Beacon in MP only runs for exactly 30secs straight + trigger.action.radioTransmission(Sound, vec3, 0, false, Frequency, self.ADFRadioPwr or 1000,name..math.random(1,10000)) -- Beacon in MP only runs for exactly 30secs straight end end return self From 8cedd88ce259d407be844eadf5ef9d4eda54a2f0 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 5 Jan 2023 10:58:36 +0100 Subject: [PATCH 44/66] #SCENERY - explain destroy doesn't work --- Moose Development/Moose/Wrapper/Scenery.lua | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Scenery.lua b/Moose Development/Moose/Wrapper/Scenery.lua index 4cd321063..9f40bc230 100644 --- a/Moose Development/Moose/Wrapper/Scenery.lua +++ b/Moose Development/Moose/Wrapper/Scenery.lua @@ -225,3 +225,10 @@ function SCENERY:FindAllByZoneName( ZoneName ) end end end + +--- SCENERY objects cannot be destroyed via the API (at the punishment of game crash). +--@param #SCENERY self +--@return #SCENERY self +function SCENERY:Destroy() + return self +end \ No newline at end of file From dd2a4ee7ff9b3f4dd2d215675c288aa51d3cbc26 Mon Sep 17 00:00:00 2001 From: Frank Date: Sun, 8 Jan 2023 19:32:38 +0100 Subject: [PATCH 45/66] COORDINATE - Added `GetMagneticDeclination` function --- Moose Development/Moose/Core/Point.lua | 36 ++++++++++++++++++++++++++ 1 file changed, 36 insertions(+) diff --git a/Moose Development/Moose/Core/Point.lua b/Moose Development/Moose/Core/Point.lua index fe4863f54..a52f4a9da 100644 --- a/Moose Development/Moose/Core/Point.lua +++ b/Moose Development/Moose/Core/Point.lua @@ -406,6 +406,42 @@ do -- COORDINATE return self end + --- Returns the magnetic declination at the given coordinate. + -- NOTE that this needs `require` to be available so you need to desanitize the `MissionScripting.lua` file in your DCS/Scrips folder. + -- If `require` is not available, a constant value for the whole map. + -- @param #COORDINATE self + -- @param #number Month (Optional) The month at which the declination is calculated. Default is the mission month. + -- @param #number Year (Optional) The year at which the declination is calculated. Default is the mission year. + -- @return #number Magnetic declination in degrees. + function COORDINATE:GetMagneticDeclination(Month, Year) + + local decl=UTILS.GetMagneticDeclination() + + if require then + + local magvar = require('magvar') + + if magvar then + + local date, year, month, day=UTILS.GetDCSMissionDate() + + magvar.init(Month or month, Year or year) + + local lat, lon=self:GetLLDDM() + + decl=magvar.get_mag_decl(lat, lon) + + if decl then + decl=math.deg(decl) + end + + end + else + self:T("The require package is not available. Using constant value for magnetic declination") + end + + return decl + end --- Returns the coordinate from the latitude and longitude given in decimal degrees. -- @param #COORDINATE self From 91801d441f00991933a2d7e1a7b01f2cb84cf8eb Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 9 Jan 2023 17:07:17 +0100 Subject: [PATCH 46/66] #GROUP * Improve functionality of GetUnit(x) * Added GetFirstUnit() --- Moose Development/Moose/Wrapper/Group.lua | 57 +++++++++++++---------- 1 file changed, 32 insertions(+), 25 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Group.lua b/Moose Development/Moose/Wrapper/Group.lua index 08853948a..19c2b9fd6 100644 --- a/Moose Development/Moose/Wrapper/Group.lua +++ b/Moose Development/Moose/Wrapper/Group.lua @@ -624,10 +624,9 @@ function GROUP:GetRange() return nil end - --- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group}. -- @param #GROUP self --- @return #list The list of @{Wrapper.Unit} objects of the @{Wrapper.Group}. +-- @return #table of Wrapper.Unit#UNIT objects, indexed by number. function GROUP:GetUnits() self:F2( { self.GroupName } ) local DCSGroup = self:GetDCSObject() @@ -645,7 +644,6 @@ function GROUP:GetUnits() return nil end - --- Returns a list of @{Wrapper.Unit} objects of the @{Wrapper.Group} that are occupied by a player. -- @param #GROUP self -- @return #list The list of player occupied @{Wrapper.Unit} objects of the @{Wrapper.Group}. @@ -676,41 +674,38 @@ function GROUP:IsPlayer() return self:GetUnit(1):IsPlayer() end ---- Returns the UNIT wrapper class with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . +--- Returns the UNIT wrapper object with number UnitNumber. If it doesn't exist, tries to return the next available unit. +-- If no underlying DCS Units exist, the method will return nil. -- @param #GROUP self -- @param #number UnitNumber The number of the UNIT wrapper class to be returned. --- @return Wrapper.Unit#UNIT The UNIT wrapper class. +-- @return Wrapper.Unit#UNIT The UNIT object or nil function GROUP:GetUnit( UnitNumber ) - local DCSGroup = self:GetDCSObject() - - if DCSGroup then - + if DCSGroup then local UnitFound = nil -- 2.7.1 dead event bug, return the first alive unit instead - local units = DCSGroup:getUnits() or {} - - for _,_unit in pairs(units) do - - local UnitFound = UNIT:Find(_unit) - + -- Maybe fixed with 2.8? + local units = DCSGroup:getUnits() or {} + if units[UnitNumber] then + local UnitFound = UNIT:Find(units[UnitNumber]) if UnitFound then - return UnitFound - + end + else + for _,_unit in pairs(units) do + local UnitFound = UNIT:Find(_unit) + if UnitFound then + return UnitFound + end end end - end - - return nil - + return nil end --- Returns the DCS Unit with number UnitNumber. --- If the underlying DCS Unit does not exist, the method will return nil. . +-- If the underlying DCS Unit does not exist, the method will return try to find the next unit. Returns nil if no units are found. -- @param #GROUP self -- @param #number UnitNumber The number of the DCS Unit to be returned. -- @return DCS#Unit The DCS Unit. @@ -723,8 +718,7 @@ function GROUP:GetDCSUnit( UnitNumber ) if DCSGroup.getUnit and DCSGroup:getUnit( UnitNumber ) then return DCSGroup:getUnit( UnitNumber ) else - - local UnitFound = nil + -- 2.7.1 dead event bug, return the first alive unit instead local units = DCSGroup:getUnits() or {} @@ -803,7 +797,20 @@ function GROUP:GetFirstUnitAlive() return nil end +--- Get the first unit of the group. Might be nil! +-- @param #GROUP self +-- @return Wrapper.Unit#UNIT First unit or nil if it does not exist. +function GROUP:GetFirstUnit() + self:F3({self.GroupName}) + local DCSGroup = self:GetDCSObject() + if DCSGroup then + local units=self:GetUnits() + return units[1] + end + + return nil +end --- Returns the average velocity Vec3 vector. -- @param Wrapper.Group#GROUP self From 6501e89fa29877bebe8675fb965ae3fe9b2dfce8 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 Jan 2023 07:47:56 +0100 Subject: [PATCH 47/66] #PSEUDOATC * Fix for debug messages --- .../Moose/Functional/PseudoATC.lua | 62 +++++++++++-------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 67b86c5fe..1930394b6 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | " --- PSEUDOATC version. -- @field #number version -PSEUDOATC.version="0.9.3" +PSEUDOATC.version="0.9.4" ----------------------------------------------------------------------------------------------------------------------------------------- @@ -450,14 +450,18 @@ function PSEUDOATC:PlayerLanded(unit, place) local group=unit:GetGroup() local GID=group:GetID() local UID=unit:GetDCSObject():getID() - local PlayerName=self.group[GID].player[UID].playername - local UnitName=self.group[GID].player[UID].unitname - local GroupName=self.group[GID].player[UID].groupname - - -- Debug message. - local text=string.format("Player %s in unit %s of group %s (id=%d) landed at %s.", PlayerName, UnitName, GroupName, GID, place) - self:T(PSEUDOATC.id..text) - MESSAGE:New(text, 30):ToAllIf(self.Debug) + --local PlayerName=self.group[GID].player[UID].playername + --local UnitName=self.group[GID].player[UID].unitname + --local GroupName=self.group[GID].player[UID].groupname + local PlayerName = unit:GetPlayerName() or "Ghost" + local UnitName = unit:GetName() or "Ghostplane" + local GroupName = group:GetName() or "Ghostgroup" + if self.Debug then + -- Debug message. + local text=string.format("Player %s in unit %s of group %s landed at %s.", PlayerName, UnitName, GroupName, place) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text, 30):ToAllIf(self.Debug) + end -- Stop altitude reporting timer if its activated. self:AltitudeTimerStop(GID,UID) @@ -479,24 +483,28 @@ function PSEUDOATC:PlayerTakeOff(unit, place) -- Gather some information. local group=unit:GetGroup() - local GID=group:GetID() - local UID=unit:GetDCSObject():getID() - local PlayerName=self.group[GID].player[UID].playername - local CallSign=self.group[GID].player[UID].callsign - local UnitName=self.group[GID].player[UID].unitname - local GroupName=self.group[GID].player[UID].groupname - - -- Debug message. - local text=string.format("Player %s in unit %s of group %s (id=%d) took off at %s.", PlayerName, UnitName, GroupName, GID, place) - self:T(PSEUDOATC.id..text) - MESSAGE:New(text, 30):ToAllIf(self.Debug) - + --local GID=group:GetID() + --local UID=unit:GetDCSObject():getID() + --local PlayerName=self.group[GID].player[UID].playername + --local CallSign=self.group[GID].player[UID].callsign + --local UnitName=self.group[GID].player[UID].unitname + --local GroupName=self.group[GID].player[UID].groupname + local PlayerName = unit:GetPlayerName() or "Ghost" + local UnitName = unit:GetName() or "Ghostplane" + local GroupName = group:GetName() or "Ghostgroup" + local CallSign = unit:GetCallsign() or "Ghost11" + if self.Debug then + -- Debug message. + local text=string.format("Player %s in unit %s of group %s took off at %s.", PlayerName, UnitName, GroupName, place) + self:T(PSEUDOATC.id..text) + MESSAGE:New(text, 30):ToAllIf(self.Debug) + end -- Bye-Bye message. if place and self.chatty then local text=string.format("%s, %s, you are airborne. Have a safe trip!", place, CallSign) - if self.reportplayername then - text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName) - end + if self.reportplayername then + text=string.format("%s, %s, you are airborne. Have a safe trip!", place, PlayerName) + end MESSAGE:New(text, self.mdur):ToGroup(group) end @@ -857,7 +865,7 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear) local height=get_AGL(position) local callsign=unit:GetCallsign() local PlayerName=self.group[GID].player[UID].playername - + -- Settings. local settings=_DATABASE:GetPlayerSettings(self.group[GID].player[UID].playername) or _SETTINGS --Core.Settings#SETTINGS @@ -870,8 +878,8 @@ function PSEUDOATC:ReportHeight(GID, UID, dt, _clear) -- Message text. local _text=string.format("%s, your altitude is %s AGL.", callsign, Hs) if self.reportplayername then - _text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs) - end + _text=string.format("%s, your altitude is %s AGL.", PlayerName, Hs) + end -- Append flight level. if _clear==false then _text=_text..string.format(" FL%03d.", position.y/30.48) From e83df502edaf66f16cf8f91acee2731bb9a4f4ab Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 10 Jan 2023 13:08:05 +0100 Subject: [PATCH 48/66] #AIRBASE - docu fixes --- Moose Development/Moose/Wrapper/Airbase.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index bb355c1a6..1be9c1513 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -246,7 +246,7 @@ AIRBASE.Normandy = { -- -- * AIRBASE.PersianGulf.Abu_Dhabi_International_Airport -- * AIRBASE.PersianGulf.Abu_Musa_Island_Airport --- * AIRBASE.PersianGulf.Al-Bateen_Airport +-- * AIRBASE.PersianGulf.Al_Bateen_Airport -- * AIRBASE.PersianGulf.Al_Ain_International_Airport -- * AIRBASE.PersianGulf.Al_Dhafra_AB -- * AIRBASE.PersianGulf.Al_Maktoum_Intl @@ -265,7 +265,7 @@ AIRBASE.Normandy = { -- * AIRBASE.PersianGulf.Lavan_Island_Airport -- * AIRBASE.PersianGulf.Liwa_Airbase -- * AIRBASE.PersianGulf.Qeshm_Island --- * AIRBASE.PersianGulf.Ras_Al_Khaimah_International_Airport +-- * AIRBASE.PersianGulf.Ras_Al_Khaimah -- * AIRBASE.PersianGulf.Sas_Al_Nakheel_Airport -- * AIRBASE.PersianGulf.Sharjah_Intl -- * AIRBASE.PersianGulf.Shiraz_International_Airport From 55fcaf1c054cf2ae9575b20549d5e70db97687f4 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 12 Jan 2023 13:20:12 +0100 Subject: [PATCH 49/66] #CTLD - Adde Shark III typename --- Moose Development/Moose/Ops/CTLD.lua | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index d15f32f59..23d0252a9 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -1077,6 +1077,7 @@ CTLD.UnitTypes = { ["Mi-8MTV2"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, ["Mi-8MT"] = {type="Mi-8MTV2", crates=true, troops=true, cratelimit = 2, trooplimit = 12, length = 15, cargoweightlimit = 3000}, ["Ka-50"] = {type="Ka-50", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, + ["Ka-50_3"] = {type="Ka-50_3", crates=false, troops=false, cratelimit = 0, trooplimit = 0, length = 15, cargoweightlimit = 0}, ["Mi-24P"] = {type="Mi-24P", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, ["Mi-24V"] = {type="Mi-24V", crates=true, troops=true, cratelimit = 2, trooplimit = 8, length = 18, cargoweightlimit = 700}, ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64, length = 25, cargoweightlimit = 19000}, -- 19t cargo, 64 paratroopers. @@ -1088,7 +1089,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.25" +CTLD.version="1.0.26" --- Instantiate a new CTLD. -- @param #CTLD self From d82bce79c90e840a305f6067a35724e923164b15 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 15 Jan 2023 17:38:54 +0100 Subject: [PATCH 50/66] #CSAR * Docu corrections --- Moose Development/Moose/Ops/CSAR.lua | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index b7cbbeda6..2b7037e71 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -850,7 +850,7 @@ end --- (Internal) Function to add a CSAR object into the scene at a Point coordinate (VEC_2). For mission designers wanting to add e.g. casualties to the scene, that don't use beacons. -- @param #CSAR self --- @param #string _Point a POINT_VEC2. +-- @param Core.Point#COORDINATE _Point -- @param #number _coalition Coalition. -- @param #string _description (optional) Description. -- @param #boolean _nomessage (optional) If true, don\'t send a message to SAR. @@ -883,7 +883,7 @@ end --- Function to add a CSAR object into the scene at a zone coordinate. For mission designers wanting to add e.g. PoWs to the scene. -- @param #CSAR self --- @param #string Point a POINT_VEC2. +-- @param Core.Point#COORDINATE Point -- @param #number Coalition Coalition. -- @param #string Description (optional) Description. -- @param #boolean addBeacon (optional) yes or no. @@ -893,8 +893,8 @@ end -- @param #boolean Forcedesc (optional) Force to use the **description passed only** for the pilot track entry. Use to have fully custom names. -- @usage If missions designers want to spawn downed pilots into the field, e.g. at mission begin, to give the helicopter guys work, they can do this like so: -- --- -- Create casualty "CASEVAC" at Point #POINT_VEC2 for the blue coalition. --- my_csar:SpawnCASEVAC( POINT_VEC2, coalition.side.BLUE ) +-- -- Create casualty "CASEVAC" at coordinate Core.Point#COORDINATE for the blue coalition. +-- my_csar:SpawnCASEVAC( coordinate, coalition.side.BLUE ) function CSAR:SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) self:_SpawnCASEVAC(Point, Coalition, Description, Nomessage, Unitname, Typename, Forcedesc) return self From 4ae0089e4f53341f6407ff0ac3c0d7a7565de294 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 15 Jan 2023 17:40:03 +0100 Subject: [PATCH 51/66] #CSAR * Docu corrections --- Moose Development/Moose/Core/Spawn.lua | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Spawn.lua b/Moose Development/Moose/Core/Spawn.lua index 00265187d..491bac853 100644 --- a/Moose Development/Moose/Core/Spawn.lua +++ b/Moose Development/Moose/Core/Spawn.lua @@ -389,7 +389,7 @@ end -- @param #SPAWN self -- @param #table SpawnTemplate is the Template of the Group. This must be a valid Group Template structure! -- @param #string SpawnTemplatePrefix is the name of the Group that will be given at each spawn. --- @param #string SpawnAliasPrefix (optional) is the name that will be given to the Group at runtime. +-- @param #string SpawnAliasPrefix is the name that will be given to the Group at runtime. -- @return #SPAWN -- @usage -- -- Create a new SPAWN object based on a Group Template defined from scratch. @@ -403,7 +403,7 @@ function SPAWN:NewFromTemplate( SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPr local self = BASE:Inherit( self, BASE:New() ) self:F( { SpawnTemplate, SpawnTemplatePrefix, SpawnAliasPrefix } ) if SpawnAliasPrefix == nil or SpawnAliasPrefix == "" then - BASE:I( "ERROR: in function NewFromTemplate, required paramter SpawnAliasPrefix is not set" ) + BASE:I( "ERROR: in function NewFromTemplate, required parameter SpawnAliasPrefix is not set" ) return nil end From fabab9bfbb04f455b7be57705c484ca429064d3b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 17 Jan 2023 09:25:01 +0100 Subject: [PATCH 52/66] #ATIS docu fix --- Moose Development/Moose/Ops/ATIS.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Ops/ATIS.lua b/Moose Development/Moose/Ops/ATIS.lua index 96794d939..3780ba573 100644 --- a/Moose Development/Moose/Ops/ATIS.lua +++ b/Moose Development/Moose/Ops/ATIS.lua @@ -634,7 +634,7 @@ ATIS.version = "0.9.14" -- Constructor ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Create a new ATIS class object for a specific aircraft carrier unit. +--- 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. From 80798f278c63d0fe1de1bd6cd47248b89c8c2da7 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 17 Jan 2023 12:09:09 +0100 Subject: [PATCH 53/66] #Controllable - docu changes --- Moose Development/Moose/Wrapper/Controllable.lua | 1 - 1 file changed, 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index 44e95efe2..c5456448b 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -67,7 +67,6 @@ -- * @{#CONTROLLABLE.TaskRouteToVec2}: (AIR + GROUND) Make the Controllable move to a given point. -- * @{#CONTROLLABLE.TaskRouteToVec3}: (AIR + GROUND) Make the Controllable move to a given point. -- * @{#CONTROLLABLE.TaskRouteToZone}: (AIR + GROUND) Route the controllable to a given zone. --- * @{#CONTROLLABLE.TaskReturnToBase}: (AIR) Route the controllable to an airbase. -- -- ## 2.2) EnRoute assignment -- From 6ec867196c3100495da5ff8b6a078c5424230c70 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Thu, 19 Jan 2023 14:59:54 +0100 Subject: [PATCH 54/66] #UTILS - make LoadSetOfGroups save(r) for groups spawned with SpawnScheduled --- Moose Development/Moose/Utilities/Utils.lua | 146 ++++++++++++-------- 1 file changed, 92 insertions(+), 54 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 280fdeaad..5896f21c6 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2477,6 +2477,12 @@ end function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,Density) local fires = {} + local usedtemplates = {} + local spawn = true + if Spawn == false then spawn = false end + local filename = Filename or "SetOfGroups" + local setdata = SET_GROUP:New() + local datatable = {} local function Smokers(name,coord,effect,density) local eff = math.random(8) @@ -2502,13 +2508,73 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D end end end + + local function PostSpawn(args) + local spwndgrp = args[1] + local size = args[2] + local structure = args[3] - local spawn = true - if Spawn == false then spawn = false end + setdata:AddObject(spwndgrp) + local actualsize = spwndgrp:CountAliveUnits() + if actualsize > size then + if Structured and structure then + + local loadedstructure = {} + local strcset = UTILS.Split(structure,";") + for _,_data in pairs(strcset) do + local datasplit = UTILS.Split(_data,"==") + loadedstructure[datasplit[1]] = tonumber(datasplit[2]) + end + + local originalstructure = UTILS.GetCountPerTypeName(spwndgrp) + + for _name,_number in pairs(originalstructure) do + local loadednumber = 0 + if loadedstructure[_name] then + loadednumber = loadedstructure[_name] + end + local reduce = false + if loadednumber < _number then reduce = true end + + if reduce then + Cruncher(spwndgrp,_name,_number-loadednumber) + end + + end + else + local reduction = actualsize-size + -- reduce existing group + local units = spwndgrp:GetUnits() + local units2 = UTILS.ShuffleTable(units) -- randomize table + for i=1,reduction do + units2[i]:Destroy(false) + end + end + end + end + + local function MultiUse(Data) + local template = Data.template + if template and usedtemplates[template] and usedtemplates[template].used and usedtemplates[template].used > 1 then + -- multispawn + if not usedtemplates[template].done then + local spwnd = 0 + local spawngrp = SPAWN:New(template) + spawngrp:InitLimit(0,usedtemplates[template].used) + for _,_entry in pairs(usedtemplates[template].data) do + spwnd = spwnd + 1 + local sgrp=spawngrp:SpawnFromCoordinate(_entry.coordinate,spwnd) + BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) + end + usedtemplates[template].done = true + end + return true + else + return false + end + end + --BASE:I("Spawn = "..tostring(spawn)) - local filename = Filename or "SetOfGroups" - local setdata = SET_GROUP:New() - local datatable = {} if UTILS.CheckFileExists(Path,filename) then local outcome,loadeddata = UTILS.LoadFromFile(Path,Filename) -- remove header @@ -2525,55 +2591,27 @@ function UTILS.LoadSetOfGroups(Path,Filename,Spawn,Structured,Cinematic,Effect,D local structure = dataset[7] local coordinate = COORDINATE:NewFromVec3({x=posx, y=posy, z=posz}) local group=nil - local data = { groupname=groupname, size=size, coordinate=coordinate, template=template } - table.insert(datatable,data) - if spawn then - local group = SPAWN:New(template) - :InitDelayOff() - :OnSpawnGroup( - function(spwndgrp) - setdata:AddObject(spwndgrp) - local actualsize = spwndgrp:CountAliveUnits() - if actualsize > size then - if Structured and structure then - --BASE:I("Reducing group structure!") - local loadedstructure = {} - local strcset = UTILS.Split(structure,";") - for _,_data in pairs(strcset) do - local datasplit = UTILS.Split(_data,"==") - loadedstructure[datasplit[1]] = tonumber(datasplit[2]) - end - --BASE:I({loadedstructure}) - local originalstructure = UTILS.GetCountPerTypeName(spwndgrp) - --BASE:I({originalstructure}) - for _name,_number in pairs(originalstructure) do - local loadednumber = 0 - if loadedstructure[_name] then - loadednumber = loadedstructure[_name] - end - local reduce = false - if loadednumber < _number then reduce = true end - - --BASE:I(string.format("Looking at: %s | Original number: %d | Loaded number: %d | Reduce: %s",_name,_number,loadednumber,tostring(reduce))) - - if reduce then - Cruncher(spwndgrp,_name,_number-loadednumber) - end - - end - else - local reduction = actualsize-size - -- reduce existing group - local units = spwndgrp:GetUnits() - local units2 = UTILS.ShuffleTable(units) -- randomize table - for i=1,reduction do - units2[i]:Destroy(false) - end - end - end - end - ) - :SpawnFromCoordinate(coordinate) + if size > 0 then + local data = { groupname=groupname, size=size, coordinate=coordinate, template=template, structure=structure } + table.insert(datatable,data) + if usedtemplates[template] then + usedtemplates[template].used = usedtemplates[template].used + 1 + table.insert(usedtemplates[template].data,data) + else + usedtemplates[template] = { + data = {}, + used = 1, + done = false, + } + table.insert(usedtemplates[template].data,data) + end + end + end + for _id,_entry in pairs (datatable) do + if spawn and not MultiUse(_entry) and _entry.size > 0 then + local group = SPAWN:New(_entry.template) + local sgrp=group:SpawnFromCoordinate(_entry.coordinate) + BASE:ScheduleOnce(0.5,PostSpawn,{sgrp,_entry.size,_entry.structure}) end end else From 1d52e27668afdcbdf7af84eee202ced7dfd6e08e Mon Sep 17 00:00:00 2001 From: Frank Date: Thu, 19 Jan 2023 19:10:56 +0100 Subject: [PATCH 55/66] Update Controllable.lua - Added enroute task SEAD --- .../Moose/Wrapper/Controllable.lua | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index c5456448b..a7c29b626 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -1664,6 +1664,26 @@ function CONTROLLABLE:EnRouteTaskAntiShip(TargetTypes, Priority) return DCSTask end +--- (AIR) Enroute SEAD task. +-- @param #CONTROLLABLE self +-- @param DCS#AttributeNameArray TargetTypes Array of target categories allowed to engage. Default `{"Air Defence"}`. +-- @param #number Priority (Optional) All en-route tasks have the priority parameter. This is a number (less value - higher priority) that determines actions related to what task will be performed first. Default 0. +-- @return DCS#Task The DCS task structure. +function CONTROLLABLE:EnRouteTaskSEAD(TargetTypes, Priority) + + local DCSTask = { + id = 'EngageTargets', + key = "SEAD", + --auto = false, + --enabled = true, + params = { + targetTypes = TargetTypes or {"Air Defence"}, + priority = Priority or 0 + } + } + + return DCSTask +end --- (AIR) Engaging a controllable. The task does not assign the target controllable to the unit/controllable to attack now; it just allows the unit/controllable to engage the target controllable as well as other assigned targets. -- @param #CONTROLLABLE self From 53cff8229b786a262d5fcc4f7deb749360d2a79b Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Sun, 22 Jan 2023 13:10:09 +0100 Subject: [PATCH 56/66] #CTLD * Added option movecratesbeforebuild * Added option surfacetypes for build-in reloads * Inject function can optionally take surfacetypes * Inject function can use the center of the zone instead of a random position #EVENT * Handle landing event if the place is a SCENERY object (helopad on a map, but not an airbase) #ZONE * docu corrections --- Moose Development/Moose/Core/Event.lua | 8 ++++--- Moose Development/Moose/Core/Zone.lua | 2 +- Moose Development/Moose/Ops/CTLD.lua | 31 ++++++++++++++++++-------- 3 files changed, 28 insertions(+), 13 deletions(-) diff --git a/Moose Development/Moose/Core/Event.lua b/Moose Development/Moose/Core/Event.lua index 595e094f4..1ce9b2cae 100644 --- a/Moose Development/Moose/Core/Event.lua +++ b/Moose Development/Moose/Core/Event.lua @@ -1280,9 +1280,11 @@ function EVENT:onEvent( Event ) --local name=Event.place:getName() -- This returns a DCS error "Airbase doesn't exit" :( -- However, this is not a big thing, as the aircraft the pilot ejected from is usually long crashed before the ejected pilot touches the ground. --Event.Place=UNIT:Find(Event.place) - else - Event.Place=AIRBASE:Find(Event.place) - Event.PlaceName=Event.Place:GetName() + else + if Event.place:isExist() and Event.place:getCategory() ~= Object.Category.SCENERY then + Event.Place=AIRBASE:Find(Event.place) + Event.PlaceName=Event.Place:GetName() + end end end diff --git a/Moose Development/Moose/Core/Zone.lua b/Moose Development/Moose/Core/Zone.lua index 70495842d..a42a6a40e 100644 --- a/Moose Development/Moose/Core/Zone.lua +++ b/Moose Development/Moose/Core/Zone.lua @@ -1387,7 +1387,7 @@ end -- @param #ZONE_RADIUS self -- @param #number inner (Optional) Minimal distance from the center of the zone in meters. Default is 0 m. -- @param #number outer (Optional) Maximal distance from the outer edge of the zone in meters. Default is the radius of the zone. --- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! +-- @param #table surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 100 times to find the right type! -- @return Core.Point#COORDINATE The random coordinate. function ZONE_RADIUS:GetRandomCoordinate(inner, outer, surfacetypes) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 23d0252a9..3becb3d03 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -22,7 +22,7 @@ -- @module Ops.CTLD -- @image OPS_CTLD.jpg --- Last Update December 2022 +-- Last Update Jan 2023 do @@ -712,6 +712,8 @@ do -- my_ctld.usesubcats = false -- use sub-category names for crates, adds an extra menu layer in "Get Crates", useful if you have > 10 crate types. -- my_ctld.placeCratesAhead = false -- place crates straight ahead of the helicopter, in a random way. If true, crates are more neatly sorted. -- my_ctld.nobuildinloadzones = true -- forbid players to build stuff in LOAD zones if set to `true` +-- my_ctld.movecratesbeforebuild = true -- crates must be moved once before they can be build. Set to false for direct builds. +-- my_ctld.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} -- surfaces for loading back objects -- -- ## 2.1 User functions -- @@ -1089,7 +1091,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="1.0.26" +CTLD.version="1.0.27" --- Instantiate a new CTLD. -- @param #CTLD self @@ -1257,6 +1259,8 @@ function CTLD:New(Coalition, Prefixes, Alias) -- disallow building in loadzones self.nobuildinloadzones = true + self.movecratesbeforebuild = true + self.surfacetypes = {land.SurfaceType.LAND,land.SurfaceType.ROAD,land.SurfaceType.RUNWAY,land.SurfaceType.SHALLOW_WATER} local AliaS = string.gsub(self.alias," ","_") self.filename = string.format("CTLD_%s_Persist.csv",AliaS) @@ -2901,7 +2905,7 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) -- get dropped crates for _,_crate in pairs(crates) do local Crate = _crate -- #CTLD_CARGO - if Crate:WasDropped() and not Crate:IsRepair() and not Crate:IsStatic() then + if (Crate:WasDropped() or not self.movecratesbeforebuild) and not Crate:IsRepair() and not Crate:IsStatic() then -- we can build these - maybe local name = Crate:GetName() local required = Crate:GetCratesNeeded() @@ -2946,7 +2950,12 @@ function CTLD:_BuildCrates(Group, Unit,Engineering) local text = string.format("Type: %s | Required %d | Found %d | Can Build %s", name, needed, found, txtok) report:Add(text) end -- end list buildables - if not foundbuilds then report:Add(" --- None Found ---") end + if not foundbuilds then + report:Add(" --- None found! ---") + if self.movecratesbeforebuild then + report:Add("*** Crates need to be moved before building!") + end + end report:Add("------------------------------------------------------------") local text = report:Text() if not Engineering then @@ -4339,6 +4348,8 @@ end -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. -- @param Ops.CTLD#CTLD_CARGO Cargo The #CTLD_CARGO object to spawn. + -- @param #table Surfacetypes (Optional) Table of surface types. Can also be a single surface type. We will try max 1000 times to find the right type! + -- @param #boolean PreciseLocation (Optional) Don't try to get a random position in the zone but use the dead center. Caution not to stack up stuff on another! -- @return #CTLD self -- @usage Use this function to pre-populate the field with Troops or Engineers at a random coordinate in a zone: -- -- create a matching #CTLD_CARGO type @@ -4346,8 +4357,8 @@ end -- -- get a #ZONE object -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: - -- my_ctld:InjectTroops(dropzone,InjectTroopsType) - function CTLD:InjectTroops(Zone,Cargo) + -- my_ctld:InjectTroops(dropzone,InjectTroopsType,{land.SurfaceType.LAND}) + function CTLD:InjectTroops(Zone,Cargo,Surfacetypes,PreciseLocation) self:T(self.lid.." InjectTroops") local cargo = Cargo -- #CTLD_CARGO @@ -4379,8 +4390,10 @@ end local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone - - local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + local randomcoord = zone:GetRandomCoordinate(10,30*factor,Surfacetypes):GetVec2() + if PreciseLocation then + randomcoord = zone:GetCoordinate():GetVec2() + end for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 local alias = string.format("%s-%d", _template, math.random(1,100000)) @@ -5082,7 +5095,7 @@ end self:InjectVehicles(dropzone,injectvehicle) elseif cargotype == CTLD_CARGO.Enum.TROOPS or cargotype == CTLD_CARGO.Enum.ENGINEERS then local injecttroops = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,true,mass) - self:InjectTroops(dropzone,injecttroops) + self:InjectTroops(dropzone,injecttroops,self.surfacetypes) end elseif (type(groupname) == "string" and groupname == "STATIC") or cargotype == CTLD_CARGO.Enum.REPAIR then local cargotemplates = dataset[6] From be68314c3a1c7e0f5d797539305d791641e3fb94 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Mon, 23 Jan 2023 17:11:18 +0100 Subject: [PATCH 57/66] #CONTROLLABLE * Added CommandActivateACLS() * Added CommandDeactivateACLS() --- .../Moose/Wrapper/Controllable.lua | 127 +++++++++--------- 1 file changed, 67 insertions(+), 60 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Controllable.lua b/Moose Development/Moose/Wrapper/Controllable.lua index a7c29b626..0c743259b 100644 --- a/Moose Development/Moose/Wrapper/Controllable.lua +++ b/Moose Development/Moose/Wrapper/Controllable.lua @@ -668,12 +668,61 @@ function CONTROLLABLE:CommandActivateBeacon( Type, System, Frequency, UnitID, Ch return self end +--- Activate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier! Also needs Link4 to work. +-- @param #CONTROLLABLE self +-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the ACLS system is attached to. Defaults to the UNIT itself. +-- @param #string Name (Optional) Name of the ACLS Beacon +-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandActivateACLS( UnitID, Name, Delay ) + + -- Command to activate ACLS system. + local CommandActivateACLS= { + id = 'ActivateACLS', + params = { + unitId = UnitID or self:GetID(), + name = Name or "ACL", + } +} + + self:T({CommandActivateACLS}) + + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandActivateACLS, { self, UnitID, Name }, Delay ) + else + self:SetCommand( CommandActivateACLS ) + end + + return self +end + +--- Deactivate ACLS system of the CONTROLLABLE. The controllable should be an aircraft carrier! +-- @param #CONTROLLABLE self +-- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. +-- @return #CONTROLLABLE self +function CONTROLLABLE:CommandDeactivateACLS( Delay ) + + -- Command to activate ACLS system. + local CommandDeactivateACLS= { + id = 'DeactivateACLS', + params = { } +} + + if Delay and Delay > 0 then + SCHEDULER:New( nil, self.CommandDeactivateACLS, { self }, Delay ) + else + self:SetCommand( CommandDeactivateACLS ) + end + + return self +end + --- Activate ICLS system of the CONTROLLABLE. The controllable should be an aircraft carrier! -- @param #CONTROLLABLE self -- @param #number Channel ICLS channel. -- @param #number UnitID The DCS UNIT ID of the unit the ICLS system is attached to. Useful if more units are in one group. -- @param #string Callsign Morse code identification callsign. --- @param #number Delay (Optional) Delay in seconds before the ICLS is deactivated. +-- @param #number Delay (Optional) Delay in seconds before the ICLS is activated. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay ) @@ -683,13 +732,13 @@ function CONTROLLABLE:CommandActivateICLS( Channel, UnitID, Callsign, Delay ) params = { ["type"] = BEACON.Type.ICLS, ["channel"] = Channel, - ["unitId"] = UnitID, + ["unitId"] = UnitID or self:GetID(), ["callsign"] = Callsign, }, } if Delay and Delay > 0 then - SCHEDULER:New( nil, self.CommandActivateICLS, { self }, Delay ) + SCHEDULER:New( nil, self.CommandActivateICLS, { self, Channel, UnitID, Callsign }, Delay ) else self:SetCommand( CommandActivateICLS ) end @@ -699,53 +748,29 @@ end --- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier! -- @param #CONTROLLABLE self --- @param #number Frequency Link4 Frequency in MHz, e.g. 336 --- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group. --- @param #string Callsign Morse code identification callsign. --- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated. +-- @param #number Frequency Link4 Frequency in MHz, e.g. 336 (defaults to 336 MHz) +-- @param #number UnitID (Optional) The DCS UNIT ID of the unit the LINK4 system is attached to. Defaults to the UNIT itself. +-- @param #string Callsign (Optional) Morse code identification callsign. +-- @param #number Delay (Optional) Delay in seconds before the LINK4 is activated. -- @return #CONTROLLABLE self function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) - + + local freq = Frequency or 336 + -- Command to activate Link4 system. local CommandActivateLink4= { id = "ActivateLink4", params= { - ["frequency "] = Frequency*1000, - ["unitId"] = UnitID, - ["name"] = Callsign, + ["frequency "] = freq*1000000, + ["unitId"] = UnitID or self:GetID(), + ["name"] = Callsign or "LNK", } } - + + self:T({CommandActivateLink4}) + if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay) - else - self:SetCommand(CommandActivateLink4) - end - - return self -end - ---- Activate LINK4 system of the CONTROLLABLE. The controllable should be an aircraft carrier! --- @param #CONTROLLABLE self --- @param #number Frequency Link4 Frequency in MHz, e.g. 336 --- @param #number UnitID The DCS UNIT ID of the unit the LINK4 system is attached to. Useful if more units are in one group. --- @param #string Callsign Morse code identification callsign. --- @param #number Delay (Optional) Delay in seconds before the LINK4 is deactivated. --- @return #CONTROLLABLE self -function CONTROLLABLE:CommandActivateLink4(Frequency, UnitID, Callsign, Delay) - - -- Command to activate Link4 system. - local CommandActivateLink4= { - id = "ActivateLink4", - params= { - ["frequency "] = Frequency*1000, - ["unitId"] = UnitID, - ["name"] = Callsign, - } - } - - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandActivateLink4, {self}, Delay) + SCHEDULER:New(nil, self.CommandActivateLink4, {self, Frequency, UnitID, Callsign}, Delay) else self:SetCommand(CommandActivateLink4) end @@ -809,24 +834,6 @@ function CONTROLLABLE:CommandDeactivateICLS( Delay ) return self end ---- Deactivate the active Link4 of the CONTROLLABLE. --- @param #CONTROLLABLE self --- @param #number Delay (Optional) Delay in seconds before the Link4 is deactivated. --- @return #CONTROLLABLE self -function CONTROLLABLE:CommandDeactivateLink4(Delay) - - -- Command to deactivate - local CommandDeactivateLink4={id='DeactivateLink4', params={}} - - if Delay and Delay>0 then - SCHEDULER:New(nil, self.CommandDeactivateLink4, {self}, Delay) - else - self:SetCommand(CommandDeactivateLink4) - end - - return self -end - --- Set callsign of the CONTROLLABLE. See [DCS command setCallsign](https://wiki.hoggitworld.com/view/DCS_command_setCallsign) -- @param #CONTROLLABLE self -- @param DCS#CALLSIGN CallName Number corresponding the the callsign identifier you wish this group to be called. @@ -3973,4 +3980,4 @@ function CONTROLLABLE:SetAltitude(Altitude, Keep, AltType) end end return self -end \ No newline at end of file +end From f34408479127d2d787b206001264d7b906026951 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 24 Jan 2023 10:06:05 +0100 Subject: [PATCH 58/66] #1885 #PSEUDOATC - Menu shows Waypoint name if it has been set --- Moose Development/Moose/Functional/PseudoATC.lua | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Moose Development/Moose/Functional/PseudoATC.lua b/Moose Development/Moose/Functional/PseudoATC.lua index 1930394b6..5ee714705 100644 --- a/Moose Development/Moose/Functional/PseudoATC.lua +++ b/Moose Development/Moose/Functional/PseudoATC.lua @@ -100,7 +100,7 @@ PSEUDOATC.id="PseudoATC | " --- PSEUDOATC version. -- @field #number version -PSEUDOATC.version="0.9.4" +PSEUDOATC.version="0.9.5" ----------------------------------------------------------------------------------------------------------------------------------------- @@ -521,7 +521,7 @@ function PSEUDOATC:PlayerLeft(unit) local GID=group:GetID() local UID=unit:GetDCSObject():getID() - if self.group[GID].player[UID] then + if self.group[GID] and self.group[GID].player and self.group[GID].player[UID] then local PlayerName=self.group[GID].player[UID].playername local CallSign=self.group[GID].player[UID].callsign local UnitName=self.group[GID].player[UID].unitname @@ -707,7 +707,9 @@ function PSEUDOATC:MenuWaypoints(GID, UID) -- Position of Waypoint local pos=COORDINATE:New(wp.x, wp.alt, wp.y) local name=string.format("Waypoint %d", i-1) - + if wp.name and wp.name ~= "" then + name = string.format("Waypoint %s",wp.name) + end -- "F10/PseudoATC/Waypoints/Waypoint X" local submenu=missionCommands.addSubMenuForGroup(GID, name, self.group[GID].player[UID].menu_waypoints) From 57a30621e1662d163372305be207ba500ed5bf80 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Tue, 24 Jan 2023 15:26:32 +0100 Subject: [PATCH 59/66] #CTLD added documentation --- Moose Development/Moose/Ops/CTLD.lua | 103 +++++++++++++++++++++++++++ 1 file changed, 103 insertions(+) diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 3becb3d03..e4e2fd1ed 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -969,7 +969,110 @@ do -- -- **Caveat:** -- If you use units build by multiple templates, they will effectively double on loading. Dropped crates are not saved. Current stock is not saved. +-- +-- ## 7. Complex example - Build a complete FARP from a CTLD crate drop +-- +-- Prerequisites - you need to add a cargo of type FOB to your CTLD instance, for simplification reasons we call it FOB: +-- +-- my_ctld:AddCratesCargo("FARP",{"FOB"},CTLD_CARGO.Enum.FOB,2) +-- +-- Also, you need to have **all statics with the fitting names** as per the script in your mission already, as we're going to copy them, and a template +-- for FARP vehicles, so -- services are goin to work (e.g. for the blue side: an unarmed humvee, two trucks and a fuel truck. Optionally add a fire fighter). +-- +-- The following code will build a FARP at the coordinate the FOB was dropped and built: +-- +-- -- FARP Radio. First one has 130AM, next 131 and for forth +-- local FARPFreq = 130 +-- local FARPName = 1 -- numbers 1..10 -- +-- local FARPClearnames = { +-- [1]="London", +-- [2]="Dallas", +-- [3]="Paris", +-- [4]="Moscow", +-- [5]="Berlin", +-- [6]="Rome", +-- [7]="Madrid", +-- [8]="Warsaw", +-- [9]="Dublin", +-- [10]="Perth", +-- } +-- +-- function BuildAFARP(Coordinate) +-- local coord = Coordinate -- Core.Point#COORDINATE +-- +-- local FarpName = ((FARPName-1)%10)+1 +-- local FName = FARPClearnames[FarpName] +-- +-- FARPFreq = FARPFreq + 1 +-- FARPName = FARPName + 1 +-- +-- -- Create a SPAWNSTATIC object from a template static FARP object. +-- local SpawnStaticFarp=SPAWNSTATIC:NewFromStatic("Static Invisible FARP-1", country.id.USA) +-- +-- -- Spawning FARPs is special in DCS. Therefore, we need to specify that this is a FARP. We also set the callsign and the frequency. +-- SpawnStaticFarp:InitFARP(FARPName, FARPFreq, 0) +-- SpawnStaticFarp:InitDead(false) +-- +-- -- Spawn FARP +-- local ZoneSpawn = ZONE_RADIUS:New("FARP "..FName,Coordinate:GetVec2(),160,false) +-- local Heading = 0 +-- local FarpBerlin=SpawnStaticFarp:SpawnFromZone(ZoneSpawn, Heading, "FARP "..FName) +-- +-- -- ATC and services - put them 125m from the center of the zone towards North +-- local FarpVehicles = SPAWN:NewWithAlias("FARP Vehicles Template","FARP "..FName.." Technicals") +-- FarpVehicles:InitHeading(180) +-- local FarpVCoord = coord:Translate(125,0) +-- FarpVehicles:SpawnFromCoordinate(FarpVCoord) +-- +-- -- We will put the rest of the statics in a nice circle around the center +-- local base = 330 +-- local delta = 30 +-- +-- local windsock = SPAWNSTATIC:NewFromStatic("Static Windsock-1",country.id.USA) +-- local sockcoord = coord:Translate(125,base) +-- windsock:SpawnFromCoordinate(sockcoord,Heading,"Windsock "..FName) +-- base=base-delta +-- +-- local fueldepot = SPAWNSTATIC:NewFromStatic("Static FARP Fuel Depot-1",country.id.USA) +-- local fuelcoord = coord:Translate(125,base) +-- fueldepot:SpawnFromCoordinate(fuelcoord,Heading,"Fueldepot "..FName) +-- base=base-delta +-- +-- local ammodepot = SPAWNSTATIC:NewFromStatic("Static FARP Ammo Storage-2-1",country.id.USA) +-- local ammocoord = coord:Translate(125,base) +-- ammodepot:SpawnFromCoordinate(ammocoord,Heading,"Ammodepot "..FName) +-- base=base-delta +-- +-- local CommandPost = SPAWNSTATIC:NewFromStatic("Static FARP Command Post-1",country.id.USA) +-- local CommandCoord = coord:Translate(125,base) +-- CommandPost:SpawnFromCoordinate(CommandCoord,Heading,"Command Post "..FName) +-- base=base-delta +-- +-- local Tent1 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA) +-- local Tent1Coord = coord:Translate(125,base) +-- Tent1:SpawnFromCoordinate(Tent1Coord,Heading,"Command Tent "..FName) +-- base=base-delta +-- +-- local Tent2 = SPAWNSTATIC:NewFromStatic("Static FARP Tent-11",country.id.USA) +-- local Tent2Coord = coord:Translate(125,base) +-- Tent2:SpawnFromCoordinate(Tent2Coord,Heading,"Command Tent2 "..FName) +-- +-- -- add a loadzone to CTLD +-- my_ctld:AddCTLDZone("FARP "..FName,CTLD.CargoZoneType.LOAD,SMOKECOLOR.Blue,true,true) +-- local m = MESSAGE:New(string.format("FARP %s in operation!",FName),15,"CTLD"):ToBlue() +-- end +-- +-- function my_ctld:OnAfterCratesBuild(From,Event,To,Group,Unit,Vehicle) +-- local name = Vehicle:GetName() +-- if string.match(name,"FOB",1,true) then +-- local Coord = Vehicle:GetCoordinate() +-- Vehicle:Destroy(false) +-- BuildAFARP(Coord) +-- end +-- end +-- +-- -- @field #CTLD CTLD = { ClassName = "CTLD", From d763e924a9cc3b386e0c457f9a764bbc6ea34658 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Tue, 24 Jan 2023 20:16:46 +0100 Subject: [PATCH 60/66] Update Utils.lua (#1886) --- Moose Development/Moose/Utilities/Utils.lua | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 5896f21c6..0ed43f055 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -2733,7 +2733,7 @@ end -- @return #string Formatted BRAA NATO call function UTILS.ToStringBRAANATO(FromGrp,ToGrp) local BRAANATO = "Merged." - local GroupNumber = FromGrp:GetSize() + local GroupNumber = ToGrp:GetSize() local GroupWords = "Singleton" if GroupNumber == 2 then GroupWords = "Two-Ship" elseif GroupNumber >= 3 then GroupWords = "Heavy" From cec865b9fb4490056207db3d1179cee5bab67424 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Wed, 25 Jan 2023 13:37:56 +0100 Subject: [PATCH 61/66] UTILS - exclude 243 MHz and 121.5 MHz --- Moose Development/Moose/Utilities/Utils.lua | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Utilities/Utils.lua b/Moose Development/Moose/Utilities/Utils.lua index 0ed43f055..d81856f3b 100644 --- a/Moose Development/Moose/Utilities/Utils.lua +++ b/Moose Development/Moose/Utilities/Utils.lua @@ -1933,7 +1933,7 @@ function UTILS.GenerateVHFrequencies() 705,720,722,730,735,740,745,750,770,795, 822,830,862,866, 905,907,920,935,942,950,995, - 1000,1025,1030,1050,1065,1116,1175,1182,1210 + 1000,1025,1030,1050,1065,1116,1175,1182,1210,1215 } local FreeVHFFrequencies = {} @@ -2001,7 +2001,9 @@ function UTILS.GenerateUHFrequencies() local _start = 220000000 while _start < 399000000 do - table.insert(FreeUHFFrequencies, _start) + if _start ~= 243000000 then + table.insert(FreeUHFFrequencies, _start) + end _start = _start + 500000 end From 6ab1632b4bd23c5d70e280dbb7da5fcaf1d35f6c Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 25 Jan 2023 17:53:29 +0100 Subject: [PATCH 62/66] #RANGE * Set Google Key if given #AIRBASE * Added 3 Falklands AB to the enumerator --- Moose Development/Moose/Functional/Range.lua | 75 ++++++++++++-------- 1 file changed, 46 insertions(+), 29 deletions(-) diff --git a/Moose Development/Moose/Functional/Range.lua b/Moose Development/Moose/Functional/Range.lua index 20d879ee0..c3425f26b 100644 --- a/Moose Development/Moose/Functional/Range.lua +++ b/Moose Development/Moose/Functional/Range.lua @@ -578,7 +578,7 @@ RANGE.MenuF10Root = nil --- Range script version. -- @field #string version -RANGE.version = "2.5.0" +RANGE.version = "2.5.1" -- TODO list: -- TODO: Verbosity level for messages. @@ -1207,13 +1207,18 @@ function RANGE:SetSRS(PathToSRS, Port, Coalition, Frequency, Modulation, Volume, self.controlmsrs:SetCoalition(Coalition or coalition.side.BLUE) self.controlmsrs:SetLabel("RANGEC") self.controlsrsQ = MSRSQUEUE:New("CONTROL") - + self.instructmsrs=MSRS:New(PathToSRS, Frequency or 305, Modulation or radio.modulation.AM, Volume or 1.0) self.instructmsrs:SetPort(Port) self.instructmsrs:SetCoalition(Coalition or coalition.side.BLUE) self.instructmsrs:SetLabel("RANGEI") self.instructsrsQ = MSRSQUEUE:New("INSTRUCT") + if PathToGoogleKey then + self.instructmsrs:SetGoogle(PathToGoogleKey) + self.instructmsrs:SetGoogle(PathToGoogleKey) + end + else self:E(self.lid..string.format("ERROR: No SRS path specified!")) end @@ -2570,7 +2575,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) self:F( _unitName ) -- Get player unit and name - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then @@ -2622,7 +2627,7 @@ function RANGE:_DisplayMyStrafePitResults( _unitName ) end -- Send message to group. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer ) end end @@ -2633,7 +2638,7 @@ function RANGE:_DisplayStrafePitResults( _unitName ) self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit which is a player. if _unit and _playername then @@ -2680,7 +2685,7 @@ function RANGE:_DisplayStrafePitResults( _unitName ) end -- Send message. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer ) end end @@ -2691,7 +2696,7 @@ function RANGE:_DisplayMyBombingResults( _unitName ) self:F( _unitName ) -- Get player unit and name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitName ) if _unit and _playername then @@ -2737,7 +2742,7 @@ function RANGE:_DisplayMyBombingResults( _unitName ) end -- Send message. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer ) end end @@ -2751,7 +2756,7 @@ function RANGE:_DisplayBombingResults( _unitName ) local _playerResults = {} -- Get player unit and name. - local _unit, _player = self:_GetPlayerUnitAndName( _unitName ) + local _unit, _player, _multiplayer = self:_GetPlayerUnitAndName( _unitName ) -- Check if we have a unit with a player. if _unit and _player then @@ -2795,7 +2800,7 @@ function RANGE:_DisplayBombingResults( _unitName ) end -- Send message. - self:_DisplayMessageToGroup( _unit, _message, nil, true, true ) + self:_DisplayMessageToGroup( _unit, _message, nil, true, true, _multiplayer ) end end @@ -2806,7 +2811,7 @@ function RANGE:_DisplayRangeInfo( _unitname ) self:F( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName( _unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then @@ -2901,7 +2906,7 @@ function RANGE:_DisplayRangeInfo( _unitname ) text = text .. textdelay -- Send message to player group. - self:_DisplayMessageToGroup( unit, text, nil, true, true ) + self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer ) -- Debug output. self:T2( self.id .. text ) @@ -2916,7 +2921,7 @@ function RANGE:_DisplayBombTargets( _unitname ) self:F( _unitname ) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) + local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if _unit and _playername then @@ -2948,7 +2953,7 @@ function RANGE:_DisplayBombTargets( _unitname ) end end - self:_DisplayMessageToGroup( _unit, _text, 120, true, true ) + self:_DisplayMessageToGroup( _unit, _text, 120, true, true, _multiplayer ) end end @@ -2959,7 +2964,7 @@ function RANGE:_DisplayStrafePits( _unitname ) self:F( _unitname ) -- Get player unit and player name. - local _unit, _playername = self:_GetPlayerUnitAndName( _unitname ) + local _unit, _playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if _unit and _playername then @@ -2988,7 +2993,7 @@ function RANGE:_DisplayStrafePits( _unitname ) _text = _text .. string.format( "\n- %s: heading %03d°\n%s", _strafepit.name, heading, mycoord ) end - self:_DisplayMessageToGroup( _unit, _text, nil, true, true ) + self:_DisplayMessageToGroup( _unit, _text, nil, true, true, _multiplayer ) end end @@ -2999,7 +3004,7 @@ function RANGE:_DisplayRangeWeather( _unitname ) self:F( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName( _unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then @@ -3048,7 +3053,7 @@ function RANGE:_DisplayRangeWeather( _unitname ) end -- Send message to player group. - self:_DisplayMessageToGroup( unit, text, nil, true, true ) + self:_DisplayMessageToGroup( unit, text, nil, true, true, _multiplayer ) -- Debug output. self:T2( self.id .. text ) @@ -3666,7 +3671,8 @@ end -- @param #number _time Duration how long the message is displayed. -- @param #boolean _clear Clear up old messages. -- @param #boolean display If true, display message regardless of player setting "Messages Off". -function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display ) +-- @param #boolean _togroup If true, display the message to the group in any case +function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display, _togroup ) self:F( { unit = _unit, text = _text, time = _time, clear = _clear } ) -- Defaults @@ -3694,8 +3700,13 @@ function RANGE:_DisplayMessageToGroup( _unit, _text, _time, _clear, display ) local playermessage = self.PlayerSettings[playername].messages -- Send message to player if messages enabled and not only for the examiner. + if _gid and (playermessage == true or display) and (not self.examinerexclusive) then - local m = MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit) + if _togroup and _grp then + local m = MESSAGE:New(_text,_time,nil,_clear):ToGroup(_grp) + else + local m = MESSAGE:New(_text,_time,nil,_clear):ToUnit(_unit) + end end -- Send message to examiner. @@ -3715,7 +3726,7 @@ end function RANGE:_SmokeBombImpactOnOff( unitname ) self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName( unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname ) if unit and playername then local text if self.PlayerSettings[playername].smokebombimpact == true then @@ -3736,7 +3747,7 @@ end function RANGE:_SmokeBombDelayOnOff( unitname ) self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName( unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname ) if unit and playername then local text if self.PlayerSettings[playername].delaysmoke == true then @@ -3757,7 +3768,7 @@ end function RANGE:_MessagesToPlayerOnOff( unitname ) self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName( unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname ) if unit and playername then local text if self.PlayerSettings[playername].messages == true then @@ -3778,7 +3789,7 @@ function RANGE:_TargetsheetOnOff( _unitname ) self:F2( _unitname ) -- Get player unit and player name. - local unit, playername = self:_GetPlayerUnitAndName( _unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( _unitname ) -- Check if we have a player. if unit and playername then @@ -3820,7 +3831,7 @@ end function RANGE:_FlareDirectHitsOnOff( unitname ) self:F( unitname ) - local unit, playername = self:_GetPlayerUnitAndName( unitname ) + local unit, playername, _multiplayer = self:_GetPlayerUnitAndName( unitname ) if unit and playername then local text if self.PlayerSettings[playername].flaredirecthits == true then @@ -4039,12 +4050,14 @@ end -- @param #string _unitName Name of the player unit. -- @return Wrapper.Unit#UNIT Unit of player. -- @return #string Name of the player. --- @return nil If player does not exist. +-- @return #boolean If true, group has > 1 player in it function RANGE:_GetPlayerUnitAndName( _unitName ) self:F2( _unitName ) if _unitName ~= nil then - + + local multiplayer = false + -- Get DCS unit from its name. local DCSunit = Unit.getByName( _unitName ) @@ -4056,7 +4069,11 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) self:T2( { DCSunit = DCSunit, unit = unit, playername = playername } ) if DCSunit and unit and playername then self:F2(playername) - return unit, playername + local grp = unit:GetGroup() + if grp and grp:CountAliveUnits() > 1 then + multiplayer = true + end + return unit, playername, multiplayer end end @@ -4064,7 +4081,7 @@ function RANGE:_GetPlayerUnitAndName( _unitName ) end -- Return nil if we could not find a player. - return nil, nil + return nil, nil, nil end --- Returns a string which consists of the player name. From bdc08a530db439cd0959ed546da453cae3d08de1 Mon Sep 17 00:00:00 2001 From: Applevangelist Date: Wed, 25 Jan 2023 17:54:13 +0100 Subject: [PATCH 63/66] #AB --- Moose Development/Moose/Wrapper/Airbase.lua | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/Moose Development/Moose/Wrapper/Airbase.lua b/Moose Development/Moose/Wrapper/Airbase.lua index 1be9c1513..6c283d002 100644 --- a/Moose Development/Moose/Wrapper/Airbase.lua +++ b/Moose Development/Moose/Wrapper/Airbase.lua @@ -510,7 +510,11 @@ AIRBASE.MarianaIslands = { -- * AIRBASE.SouthAtlantic.Porvenir_Airfield -- * AIRBASE.SouthAtlantic.Almirante_Schroeders -- * AIRBASE.SouthAtlantic.Rio_Turbio --- * AIRBASE.SouthAtlantic.Rio_Chico_Airfield +-- * AIRBASE.SouthAtlantic.Rio_Chico +-- * AIRBASE.SouthAtlantic.Franco_Bianco +-- * AIRBASE.SouthAtlantic.Goose_Green +-- * AIRBASE.SouthAtlantic.Hipico +-- * AIRBASE.SouthAtlantic.CaletaTortel -- --@field MarianaIslands AIRBASE.SouthAtlantic={ @@ -534,6 +538,10 @@ AIRBASE.SouthAtlantic={ ["Almirante_Schroeders"]="Almirante Schroeders", ["Rio_Turbio"]="Rio Turbio", ["Rio_Chico"] = "Rio Chico", + ["Franco_Bianco"] = "Franco Bianco", + ["Goose_Green"] = "Goose Green", + ["Hipico"] = "Hipico", + ["CaletaTortel"] = "CaletaTortel", } --- AIRBASE.ParkingSpot ".Coordinate, ".TerminalID", ".TerminalType", ".TOAC", ".Free", ".TerminalID0", ".DistToRwy". From 0d5b6d4c3e43802e83d7b25bda4626410e7012a9 Mon Sep 17 00:00:00 2001 From: Jason du Plessis <33880363+TheChosenOn3@users.noreply.github.com> Date: Thu, 26 Jan 2023 22:11:15 +0200 Subject: [PATCH 64/66] #CSAR - Add Persistence (#1889) * Adds a modified version of ops.CTLD's Persistence to ops.CSAR --- Moose Development/Moose/Ops/CSAR.lua | 315 ++++++++++++++++++++++++++- 1 file changed, 313 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Ops/CSAR.lua b/Moose Development/Moose/Ops/CSAR.lua index 2b7037e71..0f64b56cc 100644 --- a/Moose Development/Moose/Ops/CSAR.lua +++ b/Moose Development/Moose/Ops/CSAR.lua @@ -26,11 +26,11 @@ -- -- === -- --- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing) +-- ### Author: **Applevangelist** (Moose Version), ***Ciribob*** (original), Thanks to: Shadowze, Cammel (testing), The Chosen One (Persistence) -- @module Ops.CSAR -- @image OPS_CSAR.jpg --- Date: November 2022 +-- Date: January 2023 ------------------------------------------------------------------------- --- **CSAR** class, extends Core.Base#BASE, Core.Fsm#FSM @@ -197,6 +197,26 @@ -- -- --Create a casualty and CASEVAC request from a "Point" (VEC2) for the blue coalition --shagrat -- my_csar:SpawnCASEVAC(Point, coalition.side.BLUE) +-- +-- ## 6. Save and load downed pilots - Persistance +-- +-- You can save and later load back downed pilots to make your mission persistent. +-- For this to work, you need to de-sanitize **io** and **lfs** in your MissionScripting.lua, which is located in your DCS installtion folder under Scripts. +-- There is a risk involved in doing that; if you do not know what that means, this is possibly not for you. +-- +-- Use the following options to manage your saves: +-- +-- mycsar.enableLoadSave = true -- allow auto-saving and loading of files +-- mycsar.saveinterval = 600 -- save every 10 minutes +-- mycsar.filename = "missionsave.csv" -- example filename +-- mycsar.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path +-- +-- Then use an initial load at the beginning of your mission: +-- +-- mycsar:__Load(10) +-- +-- **Caveat:** +-- Dropped troop noMessage and forcedesc parameters aren't saved. -- -- @field #CSAR CSAR = { @@ -296,6 +316,8 @@ function CSAR:New(Coalition, Template, Alias) -- Inherit everything from FSM class. local self=BASE:Inherit(self, FSM:New()) -- #CSAR + BASE:T({Coalition, Prefixes, Alias}) + --set Coalition if Coalition and type(Coalition)=="string" then if Coalition=="blue" then @@ -346,6 +368,8 @@ function CSAR:New(Coalition, Template, Alias) self:AddTransition("*", "Returning", "*") -- CSAR able to return to base. self:AddTransition("*", "Rescued", "*") -- Pilot at MASH. self:AddTransition("*", "KIA", "*") -- Pilot killed in action. + self:AddTransition("*", "Load", "*") -- CSAR load event. + self:AddTransition("*", "Save", "*") -- CSAR save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables, mainly for tracking actions @@ -442,6 +466,14 @@ function CSAR:New(Coalition, Template, Alias) self.SRSVolume = 1.0 -- volume 0.0 to 1.0 self.SRSGender = "male" -- male or female + local AliaS = string.gsub(self.alias," ","_") + self.filename = string.format("CSAR_%s_Persist.csv",AliaS) + + -- load and save downed pilots + self.enableLoadSave = false + self.filepath = nil + self.saveinterval = 600 + ------------------------ --- Pseudo Functions --- ------------------------ @@ -471,6 +503,24 @@ function CSAR:New(Coalition, Template, Alias) -- @function [parent=#CSAR] __Status -- @param #CSAR self -- @param #number delay Delay in seconds. + -- + -- --- Triggers the FSM event "Load". + -- @function [parent=#CSAR] Load + -- @param #CSAR self + + --- Triggers the FSM event "Load" after a delay. + -- @function [parent=#CSAR] __Load + -- @param #CSAR self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Save". + -- @function [parent=#CSAR] Load + -- @param #CSAR self + + --- Triggers the FSM event "Save" after a delay. + -- @function [parent=#CSAR] __Save + -- @param #CSAR self + -- @param #number delay Delay in seconds. --- On After "PilotDown" event. Downed Pilot detected. -- @function [parent=#CSAR] OnAfterPilotDown @@ -538,6 +588,24 @@ function CSAR:New(Coalition, Template, Alias) -- @param #string To To state. -- @param #string Pilotname Name of the pilot KIA. + --- FSM Function OnAfterLoad. + -- @function [parent=#CSAR] OnAfterLoad + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for loading. Default is "CSAR__Persist.csv". + + --- FSM Function OnAfterSave. + -- @function [parent=#CSAR] OnAfterSave + -- @param #CSAR self + -- @param #string From From state. + -- @param #string Event Event. + -- @param #string To To state. + -- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. + -- @param #string filename (Optional) File name for saving. Default is "CSAR__Persist.csv". + return self end @@ -2219,7 +2287,16 @@ function CSAR:onafterStart(From, Event, To) self.msrs:SetLabel("CSAR") self.SRSQueue = MSRSQUEUE:New("CSAR") end + self:__Status(-10) + + if self.enableLoadSave then + local interval = self.saveinterval + local filename = self.filename + local filepath = self.filepath + self:__Save(interval,filepath,filename) + end + return self end @@ -2452,6 +2529,240 @@ function CSAR:onbeforeLanded(From, Event, To, HeliName, Airbase) self:T({From, Event, To, HeliName, Airbase}) return self end + +--- On before "Save" event. Checks if io and lfs are available. +-- @param #CSAR self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string path (Optional) Path where the file is saved. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. +-- @param #string filename (Optional) File name for saving. Default is "CSAR__Persist.csv". +function CSAR:onbeforeSave(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + if not self.enableLoadSave then + return self + end + -- Thanks to @FunkyFranky + -- Check io module is available. + if not io then + self:E(self.lid.."ERROR: io not desanitized. Can't save current state.") + return false + end + + -- Check default path. + if path==nil and not lfs then + self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + end + + return true +end + +--- On after "Save" event. Player data is saved to file. +-- @param #CSAR self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string path Path where the file is saved. If nil, file is saved in the DCS root installtion directory or your "Saved Games" folder if lfs was desanitized. +-- @param #string filename (Optional) File name for saving. Default is Default is "CSAR__Persist.csv". +function CSAR:onafterSave(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + -- Thanks to @FunkyFranky + if not self.enableLoadSave then + return self + end + --- Function that saves data to file + local function _savefile(filename, data) + local f = assert(io.open(filename, "wb")) + f:write(data) + f:close() + end + + -- Set path or default. + if lfs then + path=self.filepath or lfs.writedir() + end + + -- Set file name. + filename=filename or self.filename + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + local pilots = self.downedPilots + + --local data = "LoadedData = {\n" + local data = "playerName,x,y,z,coalition,country,description,typeName,unitName,freq\n" + local n = 0 + for _,_grp in pairs(pilots) do + local DownedPilot = _grp -- Wrapper.Group#GROUP + if DownedPilot and DownedPilot.alive then + -- get downed pilot data for saving + local playerName = DownedPilot.player + local group = DownedPilot.group + local coalition = group:GetCoalition() + local country = group:GetCountry() + local description = DownedPilot.desc + local typeName = DownedPilot.typename + local freq = DownedPilot.frequency + local location = group:GetVec3() + local unitName = DownedPilot.originalUnit + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%s,%s,%d\n",playerName,location.x,location.y,location.z,coalition,country,description,typeName,unitName,freq) + + self:I(self.lid.."Saving to CSAR File: " .. txt) + + data = data .. txt + end + end + + _savefile(filename, data) + + -- AutoSave + if self.enableLoadSave then + local interval = self.saveinterval + local filename = self.filename + local filepath = self.filepath + self:__Save(interval,filepath,filename) + end + return self +end + +--- On before "Load" event. Checks if io and lfs and the file are available. +-- @param #CSAR self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. +-- @param #string filename (Optional) File name for loading. Default is "CSAR__Persist.csv". +function CSAR:onbeforeLoad(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + if not self.enableLoadSave then + return self + end + --- Function that check if a file exists. + local function _fileexists(name) + local f=io.open(name,"r") + if f~=nil then + io.close(f) + return true + else + return false + end + end + + -- Set file name and path + filename=filename or self.filename + path = path or self.filepath + + -- Check io module is available. + if not io then + self:E(self.lid.."WARNING: io not desanitized. Cannot load file.") + return false + end + + -- Check default path. + if path==nil and not lfs then + self:E(self.lid.."WARNING: lfs not desanitized. State will be saved in DCS installation root directory rather than your \"Saved Games\\DCS\" folder.") + end + + -- Set path or default. + if lfs then + path=path or lfs.writedir() + end + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Check if file exists. + local exists=_fileexists(filename) + + if exists then + return true + else + self:E(self.lid..string.format("WARNING: State file %s might not exist.", filename)) + return false + --return self + end + +end + +--- On after "Load" event. Loads dropped units from file. +-- @param #CSAR self +-- @param #string From From state. +-- @param #string Event Event. +-- @param #string To To state. +-- @param #string path (Optional) Path where the file is located. Default is the DCS root installation folder or your "Saved Games\\DCS" folder if the lfs module is desanitized. +-- @param #string filename (Optional) File name for loading. Default is "CSAR__Persist.csv". +function CSAR:onafterLoad(From, Event, To, path, filename) + self:T({From, Event, To, path, filename}) + if not self.enableLoadSave then + return self + end + --- Function that loads data from a file. + local function _loadfile(filename) + local f=assert(io.open(filename, "rb")) + local data=f:read("*all") + f:close() + return data + end + + -- Set file name and path + filename=filename or self.filename + path = path or self.filepath + + -- Set path or default. + if lfs then + path=path or lfs.writedir() + end + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Info message. + local text=string.format("Loading CSAR state from file %s", filename) + MESSAGE:New(text,10):ToAllIf(self.Debug) + self:I(self.lid..text) + + local file=assert(io.open(filename, "rb")) + + local loadeddata = {} + for line in file:lines() do + loadeddata[#loadeddata+1] = line + end + file:close() + + -- remove header + table.remove(loadeddata, 1) + + for _id,_entry in pairs (loadeddata) do + local dataset = UTILS.Split(_entry,",") + -- 1=playerName,2=x,3=y,4=z,5=coalition,6=country,7=description,8=typeName,9=unitName,10=freq\n + local playerName = dataset[1] + + local vec3 = {} + vec3.x = tonumber(dataset[2]) + vec3.y = tonumber(dataset[3]) + vec3.z = tonumber(dataset[4]) + local point = COORDINATE:NewFromVec3(vec3) + + local coalition = dataset[5] + local country = dataset[6] + local description = dataset[7] + local typeName = dataset[8] + local unitName = dataset[9] + local freq = dataset[10] + + self:_AddCsar(coalition, country, point, typeName, unitName, playerName, freq, nil, description, nil) + end + + return self +end + -------------------------------------------------------------------------------------------------------------------------------------------------------------------- -- End Ops.CSAR -------------------------------------------------------------------------------------------------------------------------------------------------------------------- From c20927b6d7b6b389b9b687778d67f2c571eb8923 Mon Sep 17 00:00:00 2001 From: Thomas <72444570+Applevangelist@users.noreply.github.com> Date: Fri, 27 Jan 2023 18:34:53 +0100 Subject: [PATCH 65/66] Update Marker.lua --- Moose Development/Moose/Wrapper/Marker.lua | 2 -- 1 file changed, 2 deletions(-) diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 21936ae46..4457c3c35 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -175,8 +175,6 @@ function MARKER:New( Coordinate, Text ) -- Inherit everything from FSM class. local self = BASE:Inherit( self, FSM:New() ) -- #MARKER - local self=BASE:Inherit(self, FSM:New()) -- #MARKER - self.coordinate=UTILS.DeepCopy(Coordinate) self.text = Text From c0442fca682907667a6602a5aefc92a8660c05ca Mon Sep 17 00:00:00 2001 From: grandpaSam Date: Sat, 28 Jan 2023 00:02:15 -0800 Subject: [PATCH 66/66] Changed documentation for Marker.lua and MarkerOps_Base.lua (#1891) --- Moose Development/Moose/Core/MarkerOps_Base.lua | 6 ++++++ Moose Development/Moose/Wrapper/Marker.lua | 14 ++++++-------- 2 files changed, 12 insertions(+), 8 deletions(-) diff --git a/Moose Development/Moose/Core/MarkerOps_Base.lua b/Moose Development/Moose/Core/MarkerOps_Base.lua index 5e5aa273f..2cdb2afb6 100644 --- a/Moose Development/Moose/Core/MarkerOps_Base.lua +++ b/Moose Development/Moose/Core/MarkerOps_Base.lua @@ -5,6 +5,12 @@ -- * Create an easy way to tap into markers added to the F10 map by users. -- * Recognize own tag and list of keywords. -- * Matched keywords are handed down to functions. +-- ##Listen for your tag +-- myMarker = MARKEROPS_BASE:New("tag", {}, false) +-- function myMarker:OnAfterMarkChanged(From, Event, To, Text, Keywords, Coord, idx) +-- +-- end +-- Make sure to use the "MarkChanged" event as "MarkAdded" comes in right after the user places a blank marker and your callback will never be called. -- -- === -- diff --git a/Moose Development/Moose/Wrapper/Marker.lua b/Moose Development/Moose/Wrapper/Marker.lua index 4457c3c35..4bb7f9c7b 100644 --- a/Moose Development/Moose/Wrapper/Marker.lua +++ b/Moose Development/Moose/Wrapper/Marker.lua @@ -58,18 +58,16 @@ -- If the maker should be visible to a specific coalition, you can use the :ToCoalition() function. -- -- mymarker = MARKER:New( Coordinate , "I am Batumi Airfield" ):ToCoalition( coalition.side.BLUE ) --- --- ### To Blue Coalition --- --- ### To Red Coalition --- +-- -- This would show the marker only to the Blue coalition. -- -- ## For a Group -- +-- mymarker = MARKER:New( Coordinate , "Target Location" ):ToGroup( tankGroup ) -- -- # Removing a Marker --- +-- mymarker:Remove(60) +-- This removes the marker after 60 seconds -- -- # Updating a Marker -- @@ -305,7 +303,7 @@ end -- User API Functions ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- ---- Marker is readonly. Text cannot be changed and marker cannot be removed. +--- Marker is readonly. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state. -- @param #MARKER self -- @return #MARKER self function MARKER:ReadOnly() @@ -315,7 +313,7 @@ function MARKER:ReadOnly() return self end ---- Marker is readonly. Text cannot be changed and marker cannot be removed. +--- Marker is read and write. Text cannot be changed and marker cannot be removed. The will not update the marker in the game, Call MARKER:Refresh to update state. -- @param #MARKER self -- @return #MARKER self function MARKER:ReadWrite()