From b9832a8af81de0a12a1a43aa92003ff7279fc11a Mon Sep 17 00:00:00 2001 From: masterdraco Date: Tue, 22 Sep 2026 22:21:30 +0200 Subject: [PATCH] feat: runs gemmes i savegameN/ADSmartPickup_runs.xml --- FS25_ADSmartPickup/adRunsStorage.lua | 96 ++++++++++++++++++++++++++++ tests/test_adRuns.lua | 67 +++++++++++++++++++ 2 files changed, 163 insertions(+) create mode 100644 FS25_ADSmartPickup/adRunsStorage.lua diff --git a/FS25_ADSmartPickup/adRunsStorage.lua b/FS25_ADSmartPickup/adRunsStorage.lua new file mode 100644 index 0000000..012a6a2 --- /dev/null +++ b/FS25_ADSmartPickup/adRunsStorage.lua @@ -0,0 +1,96 @@ +-- AD Runs Storage +-- Runs gemmes i savegame-mappen ved siden af AutoDrive_config.xml. Kun dette lag +-- kender XMLFile; modellen (adRuns) ser kun flade poster. + +ADRunsStorage = {} + +ADRunsStorage.FILE_NAME = "ADSmartPickup_runs.xml" +ADRunsStorage.ROOT = "ADSmartPickupRuns" +ADRunsStorage.LOG_PREFIX = "ADSmartPickup: " + +-- Samme opslag som AutoDrive.getXMLFile_new: mappen kendes efter første gem, ellers via indeks. +function ADRunsStorage.getPath() + local info = g_currentMission ~= nil and g_currentMission.missionInfo or nil + if info == nil then + return nil + end + if info.savegameDirectory ~= nil then + return info.savegameDirectory .. "/" .. ADRunsStorage.FILE_NAME + end + if info.savegameIndex ~= nil and getUserProfileAppPath ~= nil then + return getUserProfileAppPath() .. "savegame" .. tostring(info.savegameIndex) .. "/" .. ADRunsStorage.FILE_NAME + end + return nil +end + +function ADRunsStorage.writeRecords(xmlFile, records) + xmlFile:setInt(ADRunsStorage.ROOT .. "#version", ADRuns.XML_VERSION) + for index, record in ipairs(records) do + local key = string.format("%s.run(%d)", ADRunsStorage.ROOT, index - 1) + xmlFile:setInt(key .. "#id", record.id) + xmlFile:setString(key .. "#name", record.name) + xmlFile:setInt(key .. "#target", record.target) + xmlFile:setString(key .. "#ingredientMode", record.ingredientMode) + xmlFile:setString(key .. "#ingredients", record.ingredients) + xmlFile:setString(key .. "#waitPool", record.waitPool) + xmlFile:setInt(key .. "#loops", record.loops) + for vehicleIndex, vehicleId in ipairs(record.vehicleIds) do + xmlFile:setString(string.format("%s.vehicle(%d)#id", key, vehicleIndex - 1), vehicleId) + end + end +end + +function ADRunsStorage.readRecords(xmlFile) + local records = {} + xmlFile:iterate(ADRunsStorage.ROOT .. ".run", function(_, key) + local record = { + id = xmlFile:getInt(key .. "#id"), + name = xmlFile:getString(key .. "#name"), + target = xmlFile:getInt(key .. "#target"), + ingredientMode = xmlFile:getString(key .. "#ingredientMode"), + ingredients = xmlFile:getString(key .. "#ingredients"), + waitPool = xmlFile:getString(key .. "#waitPool"), + loops = xmlFile:getInt(key .. "#loops"), + vehicleIds = {}, + } + xmlFile:iterate(key .. ".vehicle", function(_, vehicleKey) + local id = xmlFile:getString(vehicleKey .. "#id") + if id ~= nil and id ~= "" then + table.insert(record.vehicleIds, id) + end + end) + table.insert(records, record) + end) + return records +end + +function ADRunsStorage.save(store) + local path = ADRunsStorage.getPath() + if path == nil then + return false + end + local xmlFile = XMLFile.create("ADSmartPickupRuns", path, ADRunsStorage.ROOT) + if xmlFile == nil then + Logging.warning(ADRunsStorage.LOG_PREFIX .. "kunne ikke oprette %s", path) + return false + end + ADRunsStorage.writeRecords(xmlFile, ADRuns.toRecords(store)) + xmlFile:save() + xmlFile:delete() + return true +end + +function ADRunsStorage.load() + local path = ADRunsStorage.getPath() + if path == nil or not fileExists(path) then + return ADRuns.newStore() + end + local xmlFile = XMLFile.load("ADSmartPickupRuns", path) + if xmlFile == nil then + Logging.warning(ADRunsStorage.LOG_PREFIX .. "kunne ikke læse %s; starter uden runs", path) + return ADRuns.newStore() + end + local records = ADRunsStorage.readRecords(xmlFile) + xmlFile:delete() + return ADRuns.fromRecords(records) +end diff --git a/tests/test_adRuns.lua b/tests/test_adRuns.lua index 62fa254..cf57836 100644 --- a/tests/test_adRuns.lua +++ b/tests/test_adRuns.lua @@ -96,5 +96,72 @@ check("R6 nextId", dirty.nextId, 8) check("R6 tom liste", #ADRuns.fromRecords(nil).runs, 0) check("R6 parseIngredients trimmer", #ADRuns.parseIngredients(" STRAW FORAGE "), 2) +-- ==== XML-laget mod en flad XMLFile-mock (nøgle -> værdi) ==== +dofile("FS25_ADSmartPickup/adRunsStorage.lua") + +local function makeXmlMock(values) + local xml = {values = values or {}, saved = false, deleted = false} + xml.setInt = function(self, key, value) self.values[key] = value end + xml.setString = function(self, key, value) self.values[key] = value end + xml.getInt = function(self, key) return tonumber(self.values[key]) end + xml.getString = function(self, key) local v = self.values[key]; return v ~= nil and tostring(v) or nil end + -- iterate: kalder callback for "prefix(0)", "prefix(1)", ... så længe en nøgle med den stamme findes + xml.iterate = function(self, prefix, callback) + local index = 0 + while true do + local key = string.format("%s(%d)", prefix, index) + local found = false + for k in pairs(self.values) do + if k:sub(1, #key) == key then found = true break end + end + if not found then return end + callback(index + 1, key) + index = index + 1 + end + end + xml.save = function(self) self.saved = true end + xml.delete = function(self) self.deleted = true end + return xml +end + +-- X1: records skrives som attributter + vehicle-underelementer +local out = makeXmlMock() +ADRunsStorage.writeRecords(out, ADRuns.toRecords(s5)) +check("X1 version", out.values["ADSmartPickupRuns#version"], 1) +check("X1 run 0 navn", out.values["ADSmartPickupRuns.run(0)#name"], "Cow 1") +check("X1 run 0 target", out.values["ADSmartPickupRuns.run(0)#target"], 94) +check("X1 run 0 varer", out.values["ADSmartPickupRuns.run(0)#ingredients"], "STRAW FORAGE") +check("X1 run 0 pulje", out.values["ADSmartPickupRuns.run(0)#waitPool"], "Cow Wait") +check("X1 run 1 køretøj", out.values["ADSmartPickupRuns.run(1).vehicle(0)#id"], "vehicleA") + +-- X2: samme data læses tilbage +local records2 = ADRunsStorage.readRecords(makeXmlMock(out.values)) +check("X2 to poster", #records2, 2) +check("X2 id", records2[1].id, 1) +check("X2 køretøj", records2[2].vehicleIds[1], "vehicleA") +check("X2 loops", records2[1].loops, 99) +check("X2 round-trip", ADRuns.findRunByVehicle(ADRuns.fromRecords(records2), "vehicleA").id, 2) + +-- X3: save/load via XMLFile-globalen; ingen savegame-mappe -> tom store, intet skrives +Logging = Logging or {warning = function() end, info = function() end} +local created, loaded = nil, nil +XMLFile = { + create = function(_, path, root) created = makeXmlMock(); created.path = path; created.root = root; return created end, + load = function(_, path) loaded = makeXmlMock(created and created.values or {}); loaded.path = path; return loaded end, +} +fileExists = function(path) return created ~= nil and created.path == path end +g_currentMission = {missionInfo = {savegameDirectory = "/tmp/sg1"}} +check("X3 gemt", ADRunsStorage.save(s5), true) +check("X3 sti", created.path, "/tmp/sg1/ADSmartPickup_runs.xml") +check("X3 rodelement", created.root, "ADSmartPickupRuns") +check("X3 save+delete kaldt", created.saved and created.deleted, true) +check("X3 indlæst", #ADRunsStorage.load().runs, 2) +check("X3 læse-handle lukket", loaded.deleted, true) +g_currentMission = {missionInfo = {}} +check("X3 uden savegame -> false", ADRunsStorage.save(s5), false) +check("X3 uden savegame -> tom store", #ADRunsStorage.load().runs, 0) +g_currentMission = {missionInfo = {savegameDirectory = "/tmp/sg-ny"}} +check("X3 fil mangler -> tom store", #ADRunsStorage.load().runs, 0) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)