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 1/4] #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 2/4] 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 3/4] 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() From 3f97ba3bd7e6368b17c19141b767581a2f2675ab Mon Sep 17 00:00:00 2001 From: Frank Date: Sat, 28 Jan 2023 18:42:29 +0100 Subject: [PATCH 4/4] Database - Polygon drawings are registered as polygon zones --- Moose Development/Moose/Core/Database.lua | 53 ++++++++++++++++++++++- 1 file changed, 51 insertions(+), 2 deletions(-) diff --git a/Moose Development/Moose/Core/Database.lua b/Moose Development/Moose/Core/Database.lua index afa1d6a64..0ac54972a 100644 --- a/Moose Development/Moose/Core/Database.lua +++ b/Moose Development/Moose/Core/Database.lua @@ -372,9 +372,58 @@ do -- Zones end end + -- Drawings as zones + if env.mission.drawings and env.mission.drawings.layers then + + -- Loop over layers. + for layerID, layerData in pairs(env.mission.drawings.layers or {}) do + + -- Loop over objects in layers. + for objectID, objectData in pairs(layerData.objects or {}) do + + -- Check for polygon which has at least 4 points (we would need 3 but the origin seems to be there twice) + if objectData.polygonMode=="free" and objectData.points and #objectData.points>=4 then + + -- Name of the zone. + local ZoneName=objectData.name or "Unknown Drawing Zone" + + -- Reference point. All other points need to be translated by this. + local vec2={x=objectData.mapX, y=objectData.mapY} + + -- Copy points array. + local points=UTILS.DeepCopy(objectData.points) + + -- Translate points. + for i,_point in pairs(points) do + local point=_point --DCS#Vec2 + points[i]=UTILS.Vec2Add(point, vec2) + end + + -- Remove last point. + table.remove(points, #points) + + -- Debug output + self:I(string.format("Register ZONE: %s (Polygon drawing with %d verticies)", ZoneName, #points)) + + -- Create new polygon zone. + local Zone=ZONE_POLYGON:NewFromPointsArray(ZoneName, points) + + -- Set color. + Zone:SetColor({1, 0, 0}, 0.15) + + -- Store in DB. + self.ZONENAMES[ZoneName] = ZoneName + + -- Add zone. + self:AddZone(ZoneName, Zone) + + end + end + end + + end + end - - end -- zone do -- Zone_Goal