-- Copyright (C) 2025 snoutie -- Authors: snoutie (copyright@achtarmig.org) -- This program is free software: you can redistribute it and/or modify -- it under the terms of the GNU Affero General Public License as published -- by the Free Software Foundation, either version 3 of the License, or -- (at your option) any later version. -- This program is distributed in the hope that it will be useful, -- but WITHOUT ANY WARRANTY; without even the implied warranty of -- MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -- GNU Affero General Public License for more details. -- You should have received a copy of the GNU Affero General Public License -- along with this program. If not, see . local M = {} M.dependencies = { 'career_career', 'career_saveSystem', 'career_modules_inventory' } local extensionName = "career_vehicleSaveSystem" local vehicleSaves_temp = "/temp/career/vehicle_saves" local function CreateDirectories(dir) local p = path.dirname(dir) if not FS:directoryExists(p) then CreateDirectories(string.sub(p, 1, #p - 1)) end FS:directoryCreate(dir) end local function ClearDirectory(dir) local files = FS:findFiles(dir .. "/", '*', 0, false, true) for i = 1, tableSize(files) do FS:remove(files[i]) end end local function CopySaves(from, to) local save_folders = FS:directoryList(from, false, true) for i = 1, tableSize(save_folders) do local folder_dir, folder_name, _ = path.split(save_folders[i]) FS:directoryCreate(to .. "/" .. folder_name) local save_files = FS:findFiles(folder_dir .. folder_name .. "/", '*.json', 0, false, false) for n = 1, tableSize(save_files) do local file_dir, file_name, file_ext = path.split(save_files[n]) FS:remove(to .. "/" .. folder_name .. "/" .. file_name) FS:copyFile(file_dir .. file_name, to .. "/" .. folder_name .. "/" .. file_name) end end end local function LoadFiles(from) ClearDirectory(vehicleSaves_temp) CopySaves(from, vehicleSaves_temp) end local function SaveFiles(to) CopySaves(vehicleSaves_temp, to) end local function GetVehicleSaveFile(root, inventoryId) local path = root .. "/" .. inventoryId CreateDirectories(path) return path .. "/save.json" end local function QueueVehicleToSave(inventoryId) M.queuedVehicleSaves[inventoryId] = true end local function QueuedVehicleSaved(inventoryId) log("I", "saving", "saved vehicle " .. inventoryId) table.remove(M.queuedVehicleSaves, inventoryId) end local function VehiclesSaved() if next(M.queuedVehicleSaves) == nil then log('I', 'saving', 'all vehicles saved') return true else log('I', 'saving', 'still saving vehicles') end return false end local function CheckSavedAsync(callback) extensions.core_jobsystem.create( function(job) while true do if VehiclesSaved() then break end job.sleep(0.1) end if callback then callback() end end) end local function SaveVehicle(inventoryId) local saveFile = GetVehicleSaveFile(vehicleSaves_temp, inventoryId) local vehicleId = career_modules_inventory.getVehicleIdFromInventoryId(inventoryId) if vehicleId then log('I', 'saving', 'saving vehicle ' .. inventoryId .. " to " .. saveFile) local object = be:getObjectByID(vehicleId) object:queueLuaCommand("beamstate.save(\"" .. saveFile .. "\"); obj:queueGameEngineLua('career_vehicleSaveSystem.QueuedVehicleSaved(\"" .. inventoryId .. "\")');") return true else return false end end local function LoadVehicle(inventoryId) local saveFile = GetVehicleSaveFile(vehicleSaves_temp, inventoryId) local vehicleId = career_modules_inventory.getVehicleIdFromInventoryId(inventoryId) if vehicleId then log("I", "loading", "loading vehicle from " .. saveFile) local object = be:getObjectByID(vehicleId) object:queueLuaCommand("beamstate.load(\"" .. saveFile .. "\");" .. "for key, value in pairs(v.data.controller) do if value['fileName'] == 'advancedCouplerControl' then local c = controller.getController(value['name']) if c['reset'] then c['reset']() end end end" ) end end local function onSaveCurrentSaveSlotAsyncStart() career_saveSystem.registerAsyncSaveExtension(extensionName) end local function onSaveCurrentSaveSlot(currentSavePath, oldSaveDate, vehiclesThumbnailUpdate) local vehicles = career_modules_inventory.getVehicles() for id, _ in pairs(vehicles) do QueueVehicleToSave(id) end for id, _ in pairs(M.queuedVehicleSaves) do if not SaveVehicle(id) then QueuedVehicleSaved(id) end end CheckSavedAsync(function() SaveFiles(currentSavePath .. "/career/vehicle_saves") career_saveSystem.asyncSaveExtensionFinished(extensionName) end) end local function onCareerActive() local _, saveSlot = career_saveSystem.getCurrentSaveSlot() local vehicleSaves_saveSlot = saveSlot .. "/career/vehicle_saves" CreateDirectories(vehicleSaves_saveSlot) CreateDirectories(vehicleSaves_temp) LoadFiles(vehicleSaves_saveSlot) end M.queuedVehicleSaves = {} M.SaveVehicle = SaveVehicle M.LoadVehicle = LoadVehicle M.CheckSavedAsync = CheckSavedAsync M.QueueVehicleToSave = QueueVehicleToSave M.QueuedVehicleSaved = QueuedVehicleSaved M.onSaveCurrentSaveSlotAsyncStart = onSaveCurrentSaveSlotAsyncStart M.onSaveCurrentSaveSlot = onSaveCurrentSaveSlot M.onCareerActive = onCareerActive return M