diff --git a/Moose Development/Moose/Ops/CTLD.lua b/Moose Development/Moose/Ops/CTLD.lua index 779e21e7b..35a22e8cf 100644 --- a/Moose Development/Moose/Ops/CTLD.lua +++ b/Moose Development/Moose/Ops/CTLD.lua @@ -304,7 +304,7 @@ CTLD_ENGINEERING = { -- @field #number ID ID of this cargo. -- @field #string Name Name for menu. -- @field #table Templates Table of #POSITIONABLE objects. --- @field #CTLD_CARGO.Enum Type Enumerator of Type. +-- @field #CTLD_CARGO.Enum CargoType Enumerator of Type. -- @field #boolean HasBeenMoved Flag for moving. -- @field #boolean LoadDirectly Flag for direct loading. -- @field #number CratesNeeded Crates needed to build. @@ -763,16 +763,20 @@ do -- -- function CTLD_Cargotransport:OnAfterCratesDropped(From, Event, To, Group, Unit, Cargotable) -- local points = 10 --- local PlayerName = Unit:GetPlayerName() --- my_scoring:_AddPlayerFromUnit( Unit ) --- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- if Unit then +-- local PlayerName = Unit:GetPlayerName() +-- my_scoring:_AddPlayerFromUnit( Unit ) +-- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for transporting cargo crates!", PlayerName, points), points) +-- end -- end -- -- function CTLD_Cargotransport:OnAfterCratesBuild(From, Event, To, Group, Unit, Vehicle) -- local points = 5 --- local PlayerName = Unit:GetPlayerName() --- my_scoring:_AddPlayerFromUnit( Unit ) --- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- if Unit then + -- local PlayerName = Unit:GetPlayerName() + -- my_scoring:_AddPlayerFromUnit( Unit ) + -- my_scoring:AddGoalScore(Unit, "CTLD", string.format("Pilot %s has been awarded %d points for the construction of Units!", PlayerName, points), points) +-- end -- end -- -- ## 4. F10 Menu structure @@ -803,7 +807,11 @@ do -- ## 4.6 Show hover parameters -- -- Lists hover parameters and indicates if these are curently fulfilled. Also @see options on hover heights. --- +-- +-- ## 4.7 List Inventory +-- +-- Lists invetory of available units to drop or build. +-- -- ## 5. Support for Hercules mod by Anubis -- -- Basic support for the Hercules mod By Anubis has been build into CTLD. Currently this does **not** cover objects and troops which can @@ -826,6 +834,27 @@ do -- -- ["Hercules"] = {type="Hercules", crates=true, troops=true, cratelimit = 7, trooplimit = 64}, -- 19t cargo, 64 paratroopers -- +-- ## 6. Save and load back units - persistance +-- +-- You can save and later load back units dropped or build 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: +-- +-- my_ctld.enableLoadSave = true -- allow auto-saving and loading of files +-- my_ctld.saveinterval = 600 -- save every 10 minutes +-- my_ctld.filename = "missionsave.csv" -- example filename +-- my_ctld.filepath = "C:\\Users\\myname\\Saved Games\\DCS\Missions\\MyMission" -- example path +-- my_ctld.eventoninject = true -- fire OnAfterCratesBuild and OnAfterTroopsDeployed events when loading (uses Inject functions) +-- +-- Then use an initial load at the beginning of your mission: +-- +-- my_ctld:__Load(10) +-- +-- **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. +-- -- @field #CTLD CTLD = { ClassName = "CTLD", @@ -869,6 +898,7 @@ CTLD = { -- TODO: Add statics as cargo -- DONE: List cargo in stock -- DONE: Limit of troops, crates buildable? +-- DONE: Allow saving of Troops & Vehicles ------------------------------ --- Radio Beacons @@ -932,7 +962,7 @@ CTLD.UnitTypes = { --- CTLD class version. -- @field #string version -CTLD.version="0.1.8a2" +CTLD.version="0.2.1a2" --- Instantiate a new CTLD. -- @param #CTLD self @@ -996,7 +1026,9 @@ function CTLD:New(Coalition, Prefixes, Alias) self:AddTransition("*", "TroopsRTB", "*") -- CTLD deploy event. self:AddTransition("*", "CratesDropped", "*") -- CTLD deploy event. self:AddTransition("*", "CratesBuild", "*") -- CTLD build event. - self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. + self:AddTransition("*", "CratesRepaired", "*") -- CTLD repair event. + self:AddTransition("*", "Load", "*") -- CTLD load event. + self:AddTransition("*", "Save", "*") -- CTLD save event. self:AddTransition("*", "Stop", "Stopped") -- Stop FSM. -- tables @@ -1077,6 +1109,15 @@ function CTLD:New(Coalition, Prefixes, Alias) self.cratecountry = country.id.RUSSIA end + -- load and save dropped TROOPS + self.enableLoadSave = false + self.filepath = nil + self.saveinterval = 600 + self.eventoninject = true + + local AliaS = string.gsub(self.alias," ","_") + self.filename = string.format("CTLD_%s_Persist.csv",AliaS) + for i=1,100 do math.random() end @@ -1115,6 +1156,24 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param #CTLD self -- @param #number delay Delay in seconds. + --- Triggers the FSM event "Load". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Load" after a delay. + -- @function [parent=#CTLD] __Load + -- @param #CTLD self + -- @param #number delay Delay in seconds. + + --- Triggers the FSM event "Save". + -- @function [parent=#CTLD] Load + -- @param #CTLD self + + --- Triggers the FSM event "Save" after a delay. + -- @function [parent=#CTLD] __Save + -- @param #CTLD self + -- @param #number delay Delay in seconds. + --- FSM Function OnAfterTroopsPickedUp. -- @function [parent=#CTLD] OnAfterTroopsPickedUp -- @param #CTLD self @@ -1201,6 +1260,24 @@ function CTLD:New(Coalition, Prefixes, Alias) -- @param Wrapper.Group#GROUP Group Group Object. -- @param Wrapper.Unit#UNIT Unit Unit Object. + --- FSM Function OnAfterLoad. + -- @function [parent=#CTLD] OnAfterLoad + -- @param #CTLD 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 "CTLD__Persist.csv". + + --- FSM Function OnAfterSave. + -- @function [parent=#CTLD] OnAfterSave + -- @param #CTLD 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 "CTLD__Persist.csv". + return self end @@ -3483,7 +3560,6 @@ end return self end - -- DONE: Make inject respect existing cargo types --- (User) Pre-populate troops in the field. -- @param #CTLD self -- @param Core.Zone#ZONE Zone The zone where to drop the troops. @@ -3491,7 +3567,7 @@ end -- @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 - -- local InjectTroopsType = CTLD_CARGO:New(nil,"Injected Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80) + -- local InjectTroopsType = CTLD_CARGO:New(nil,"Infantry",{"Inf12"},CTLD_CARGO.Enum.TROOPS,true,true,12,nil,false,80) -- -- get a #ZONE object -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE -- -- and go: @@ -3517,16 +3593,18 @@ end if not IsTroopsMatch(cargo) then self.CargoCounter = self.CargoCounter + 1 cargo.ID = self.CargoCounter + cargo.Stock = 1 table.insert(self.Cargo_Troops,cargo) end local type = cargo:GetType() -- #CTLD_CARGO.Enum - if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) and not cargo:WasDropped() then - -- unload troops + if (type == CTLD_CARGO.Enum.TROOPS or type == CTLD_CARGO.Enum.ENGINEERS) then + -- unload local name = cargo:GetName() or "none" local temptable = cargo:GetTemplates() or {} local factor = 1.5 local zone = Zone + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() for _,_template in pairs(temptable) do self.TroopCounter = self.TroopCounter + 1 @@ -3545,10 +3623,85 @@ end self.Engineers = self.Engineers + 1 local grpname = self.DroppedTroops[self.TroopCounter]:GetName() self.EngineersInField[self.Engineers] = CTLD_ENGINEERING:New(name, grpname) - self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) + --self:I(string.format("%s Injected Engineers %s into action!",self.lid, name)) else - self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) + --self:I(string.format("%s Injected Troops %s into action!",self.lid, name)) end + if self.eventoninject then + self:__TroopsDeployed(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + end + end -- if type end + return self + end + + --- (User) Pre-populate vehicles in the field. + -- @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. + -- @return #CTLD self + -- @usage Use this function to pre-populate the field with Vehicles or FOB at a random coordinate in a zone: + -- -- create a matching #CTLD_CARGO type + -- local InjectVehicleType = CTLD_CARGO:New(nil,"Humvee",{"Humvee"},CTLD_CARGO.Enum.VEHICLE,true,true,1,nil,false,1000) + -- -- get a #ZONE object + -- local dropzone = ZONE:New("InjectZone") -- Core.Zone#ZONE + -- -- and go: + -- my_ctld:InjectVehicles(dropzone,InjectVehicleType) + function CTLD:InjectVehicles(Zone,Cargo) + self:T(self.lid.." InjectVehicles") + local cargo = Cargo -- #CTLD_CARGO + + local function IsVehicMatch(cargo) + local match = false + local cgotbl = self.Cargo_Crates + local name = cargo:GetName() + for _,_cgo in pairs (cgotbl) do + local cname = _cgo:GetName() + if name == cname then + match = true + break + end + end + return match + end + + if not IsVehicMatch(cargo) then + self.CargoCounter = self.CargoCounter + 1 + cargo.ID = self.CargoCounter + cargo.Stock = 1 + table.insert(self.Cargo_Crates,cargo) + end + + local type = cargo:GetType() -- #CTLD_CARGO.Enum + if (type == CTLD_CARGO.Enum.VEHICLE or type == CTLD_CARGO.Enum.FOB) then + -- unload + local name = cargo:GetName() or "none" + local temptable = cargo:GetTemplates() or {} + local factor = 1.5 + local zone = Zone + local randomcoord = zone:GetRandomCoordinate(10,30*factor):GetVec2() + cargo:SetWasDropped(true) + local canmove = false + if type == CTLD_CARGO.Enum.VEHICLE then canmove = true end + for _,_template in pairs(temptable) do + self.TroopCounter = self.TroopCounter + 1 + local alias = string.format("%s-%d", _template, math.random(1,100000)) + if canmove then + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitRandomizeUnits(true,20,2) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + else -- don't random position of e.g. SAM units build as FOB + self.DroppedTroops[self.TroopCounter] = SPAWN:NewWithAlias(_template,alias) + :InitDelayOff() + :SpawnFromVec2(randomcoord) + end + if self.movetroopstowpzone and canmove then + self:_MoveGroupToZone(self.DroppedTroops[self.TroopCounter]) + end + if self.eventoninject then + self:__CratesBuild(1,nil,nil,self.DroppedTroops[self.TroopCounter]) + end + end -- end loop end -- if type end return self end @@ -3581,6 +3734,14 @@ end self:HandleEvent(EVENTS.PlayerEnterUnit, self._EventHandler) self:HandleEvent(EVENTS.PlayerLeaveUnit, self._EventHandler) self:__Status(-5) + + -- 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 @@ -3757,6 +3918,289 @@ end return self end + --- On before "Save" event. Checks if io and lfs are available. + -- @param #CTLD 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 "CTLD__Persist.csv". + function CTLD: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 #CTLD 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 "CTLD__Persist.csv". + function CTLD: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 grouptable = self.DroppedTroops -- #table + local cgovehic = self.Cargo_Crates + local cgotable = self.Cargo_Troops + + -- find matching cargo + local function FindCargoType(name,table) + -- name matching a template in the table + local match = false + local cargo = nil + for _ind,_cargo in pairs (table) do + local thiscargo = _cargo -- #CTLD_CARGO + local template = thiscargo:GetTemplates() + if type(template) == "string" then + template = { template } + end + for _,_name in pairs (template) do + --self:I(string.format("*** Saving CTLD: Matching %s with %s",name,_name)) + if string.find(name,_name) then + match = true + cargo = thiscargo + end + end + if match then break end + end + return match, cargo + end + + + --local data = "LoadedData = {\n" + local data = "Group,x,y,z,CargoName,CargoTemplates,CargoType,CratesNeeded,CrateMass\n" + local n = 0 + for _,_grp in pairs(grouptable) do + local group = _grp -- Wrapper.Group#GROUP + if group and group:IsAlive() then + -- get template name + local name = group:GetName() + local template = string.gsub(name,"-(.+)$","") + if string.find(template,"#") then + template = string.gsub(name,"#(%d+)$","") + end + + local match, cargo = FindCargoType(template,cgotable) + if not match then + match, cargo = FindCargoType(template,cgovehic) + end + if match then + n = n + 1 + local cargo = cargo -- #CTLD_CARGO + local cgoname = cargo.Name + local cgotemp = cargo.Templates + local cgotype = cargo.CargoType + local cgoneed = cargo.CratesNeeded + local cgomass = cargo.PerCrateMass + + local templates = "{" + for _,_tmpl in pairs(cgotemp) do + templates = templates .. _tmpl .. ";" + end + templates = templates .. "}" + --self:I({cgoname,templates,cgotype,cgoneed,cgomass}) + --CTLD01238.function({[1]=Infantry Squad,[2]={'Inf12',},[4]=12,[5]=80,}) + local location = group:GetVec3() + local txt = string.format("%s,%d,%d,%d,%s,%s,%s,%d,%d\n" + ,template,location.x,location.y,location.z,cgoname,templates,cgotype,cgoneed,cgomass) + --BASE:I(txt) + data = data .. txt + end + end + end + --data = data .. "\n" + + _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 #CTLD 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 "CTLD__Persist.csv". + function CTLD: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 + + -- 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 file name. + filename=filename or self.filename + + -- 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 does not exist.", filename)) + return false + end + + end + + --- On after "Load" event. Loads dropped units from file. + -- @param #CTLD 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 "CTLD__Persist.csv". + function CTLD: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 path or default. + if lfs then + path=path or lfs.writedir() + end + + -- Set file name. + filename=filename or self.filename + + -- Set path. + if path~=nil then + filename=path.."\\"..filename + end + + -- Info message. + local text=string.format("Loading CTLD 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 + --self:I({line=type(line)}) + 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=Group,2=x,3=y,4=z,5=CargoName,6=CargoTemplates,7=CargoType,8=CratesNeeded,9=CrateMass + local groupname = dataset[1] + local vec2 = {} + vec2.x = dataset[2] + vec2.y = dataset[4] + local cargoname = dataset[5] + if type(cargoname) == "string" then + local cargotemplates = dataset[6] + cargotemplates = string.gsub(cargotemplates,"{","") + cargotemplates = string.gsub(cargotemplates,"}","") + cargotemplates = UTILS.Split(cargotemplates,";") + local cargotype = dataset[7] + local size = dataset[8] + local mass = dataset[9] + --self:I({groupname,vec3,cargoname,cargotemplates,cargotype,size,mass}) + -- inject at Vec2 + local dropzone = ZONE_RADIUS:New("DropZone",vec2,100) + if cargotype == CTLD_CARGO.Enum.VEHICLE or cargotype == CTLD_CARGO.Enum.FOB then + local injectvehicle = CTLD_CARGO:New(nil,cargoname,cargotemplates,cargotype,true,true,size,nil,false,mass) + 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,false,mass) + self:InjectTroops(dropzone,injecttroops) + end + end + end + + return self + end end -- end do ------------------------------------------------------------------- -- End Ops.CTLD.lua