8 Commits
1.0 ... 1.4

5 changed files with 288 additions and 99 deletions

1
.gitignore vendored Normal file
View File

@@ -0,0 +1 @@
_*

View File

@@ -1,97 +0,0 @@
-- 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 <https://www.gnu.org/licenses/>.
local M = {}
M.dependencies = { 'career_career', 'career_modules_computer', 'career_modules_inventory', 'career_modules_payment',
'career_modules_playerAttributes' }
local function tprint(tbl, indent)
if not indent then indent = 0 end
local toprint = string.rep(" ", indent) .. "{\r\n"
indent = indent + 2
for k, v in pairs(tbl) do
toprint = toprint .. string.rep(" ", indent)
if (type(k) == "number") then
toprint = toprint .. "[" .. k .. "] = "
elseif (type(k) == "string") then
toprint = toprint .. k .. "= "
end
if (type(v) == "number") then
toprint = toprint .. v .. ",\r\n"
elseif (type(v) == "string") then
toprint = toprint .. "\"" .. v .. "\",\r\n"
elseif (type(v) == "table") then
toprint = toprint .. tprint(v, indent + 2) .. ",\r\n"
else
toprint = toprint .. "\"" .. tostring(v) .. "\",\r\n"
end
end
toprint = toprint .. string.rep(" ", indent - 2) .. "}"
return toprint
end
local function retrieve_favourite()
local fav_veh_id = career_modules_inventory.getFavoriteVehicle()
local vid = career_modules_inventory.getVehicleIdFromInventoryId(fav_veh_id)
if vid then
local old_veh = be:getObjectByID(vid)
old_veh:queueLuaCommand("beamstate.save()")
end
extensions.core_jobsystem.create(
function(job)
job.sleep(0.2)
local veh = career_modules_inventory.spawnVehicle(fav_veh_id, 2)
local vehh = be:getObjectByID(career_modules_inventory.getVehicleIdFromInventoryId(fav_veh_id))
local location = { pos = vehh:getPosition(), rot = quat(0, 0, 1, 0) * quat(vehh:getRefNodeRotation()) }
local garage = career_modules_inventory.getClosestGarage(location.pos)
freeroam_facilities.teleportToGarage(garage.id, vehh, false)
veh:queueLuaCommand("beamstate.load()")
end
)
end
local function onComputerAddFunctions(menuData, computerFunctions)
if menuData.computerFacility.functions["vehicleInventory"] then
local computerFunctionData = {
id = "retrieve_damaged",
label = "Retrieve Favourite Damaged",
callback = retrieve_favourite,
order = 1
}
if menuData.tutorialPartShoppingActive or menuData.tutorialTuningActive then
computerFunctionData.disabled = true
computerFunctionData.reason = career_modules_computer.reasons.tutorialActive
end
computerFunctions.general[computerFunctionData.id] = computerFunctionData
end
end
local function onSaveCurrentSaveSlot(currentSavePath, oldSaveDate, vehiclesThumbnailUpdate)
local vs = career_modules_inventory.getVehicles()
for id, _ in pairs(vs) do
local vid = career_modules_inventory.getVehicleIdFromInventoryId(id)
local veh = be:getObjectByID(vid)
veh:queueLuaCommand("beamstate.save()")
end
end
M.onSaveCurrentSaveSlot = onSaveCurrentSaveSlot
M.onComputerAddFunctions = onComputerAddFunctions
return M

View File

@@ -0,0 +1,105 @@
-- 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 <https://www.gnu.org/licenses/>.
local M = {}
M.dependencies = { 'career_modules_inventory', 'freeroam_facilities', "career_vehicleSaveSystem",
"career_modules_permissions", 'career_modules_valueCalculator' }
local career_modules_inventory_removeVehicleObject
local vehicleObjectsToRemove = {}
local function spawnVehicle(inventoryId, callback)
if career_modules_inventory.getVehicleIdFromInventoryId(inventoryId) then
callback()
else
career_modules_inventory.spawnVehicle(inventoryId, nil, callback)
end
end
local function Retrieve(inventoryId)
career_vehicleSaveSystem.QueueVehicleToSave(inventoryId)
if not career_vehicleSaveSystem.SaveVehicle(inventoryId) then
career_vehicleSaveSystem.QueuedVehicleSaved(inventoryId)
end
local vehicleValue = career_modules_valueCalculator.getInventoryVehicleValue(inventoryId, true)
local currentVehicleValue = career_modules_valueCalculator.getInventoryVehicleValue(inventoryId)
career_vehicleSaveSystem.CheckSavedAsync(function()
extensions.core_jobsystem.create(
function(job)
spawnVehicle(inventoryId, function()
local veh = be:getObjectByID(career_modules_inventory.getVehicleIdFromInventoryId(inventoryId))
local location = { pos = veh:getPosition(), rot = quat(0, 0, 1, 0) * quat(veh:getRefNodeRotation()) }
local garage = career_modules_inventory.getClosestGarage(location.pos)
job.sleep(1)
if currentVehicleValue < vehicleValue then
career_vehicleSaveSystem.LoadVehicle(inventoryId)
else
log('I', 'retrieval', 'retrieving vehicle ' .. inventoryId .. " in an undamaged state")
end
freeroam_facilities.teleportToGarage(garage.id, veh, false)
end)
end
)
end)
end
local function onComputerAddFunctions(menuData, computerFunctions)
if menuData.computerFacility.functions["vehicleInventory"] then
local favouriteVehicleId = career_modules_inventory.getFavoriteVehicle()
local computerFunctionData = {
id = "retrieve_damaged",
label = "Retrieve Favourite Damaged",
callback = function() Retrieve(favouriteVehicleId) end,
order = 1
}
local repairPermission = career_modules_permissions.getStatusForTag("vehicleRepair",
{ inventoryId = favouriteVehicleId })
if not repairPermission["allow"] then
computerFunctionData.disabled = true
computerFunctionData.reason = { type = "text", label = "Vehicle is being repaired." }
end
if menuData.tutorialPartShoppingActive or menuData.tutorialTuningActive then
computerFunctionData.disabled = true
computerFunctionData.reason = career_modules_computer.reasons.tutorialActive
end
computerFunctions.general[computerFunctionData.id] = computerFunctionData
end
end
local function RemoveVehicleObject(inventoryId)
table.insert(vehicleObjectsToRemove, inventoryId)
career_vehicleSaveSystem.QueueVehicleToSave(inventoryId)
career_vehicleSaveSystem.SaveVehicle(inventoryId)
career_vehicleSaveSystem.CheckSavedAsync(function()
career_modules_inventory_removeVehicleObject(inventoryId)
end)
end
local function onCareerActive()
career_modules_inventory_removeVehicleObject = career_modules_inventory.removeVehicleObject
career_modules_inventory.removeVehicleObject = RemoveVehicleObject
end
M.onComputerAddFunctions = onComputerAddFunctions
M.onCareerActive = onCareerActive
return M

View File

@@ -0,0 +1,178 @@
-- 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 <https://www.gnu.org/licenses/>.
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

View File

@@ -15,12 +15,14 @@
local M = {} local M = {}
M.dependencies = { 'career_career', 'career_modules_payment', 'career_modules_playerAttributes' } M.dependencies = { 'career_career' }
extensions.load("career_retrievedamaged") extensions.load("career_vehicleSaveSystem")
extensions.load("career_vehicleRetrieval")
M.onInit = function() M.onInit = function()
setExtensionUnloadMode(M, "manual") setExtensionUnloadMode(M, "manual")
end end
return M return M