diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua new file mode 100644 index 0000000..d52f9ab --- /dev/null +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -0,0 +1,364 @@ +-- AD Runs Controller +-- Oversætter et run til AutoDrive-tilstand pr. traktor: validering, Start/Stop, status, +-- Auto-vareliste, og "hvilket run hører traktoren til". GUI'et kalder kun herind; +-- de eksisterende hooks (choosePickup, Wait-punkter) spørger getRunForVehicle/getRunIngredients. + +ADRunsController = {} + +ADRunsController.LOG_PREFIX = "ADSmartPickup: " +-- Længere væk fra vejnettet end dette kan AutoDrive ikke starte (samme grænse som AD's HUD). +ADRunsController.MAX_NETWORK_DISTANCE = 30 + +ADRunsController.store = ADRuns.newStore() + +local function copyList(list) + local copy = {} + for index, value in ipairs(list or {}) do + copy[index] = value + end + return copy +end + +-- Stabil nøgle: FS25's uniqueId ("vehicle…"), ellers savegame-id. Navne bruges aldrig. +function ADRunsController.getVehicleId(vehicle) + if vehicle.uniqueId ~= nil then + return tostring(vehicle.uniqueId) + end + if vehicle.getUniqueId ~= nil then + local id = vehicle:getUniqueId() + if id ~= nil then + return tostring(id) + end + end + if vehicle.currentSavegameId ~= nil then + return tostring(vehicle.currentSavegameId) + end + return nil +end + +function ADRunsController.getFarmVehicles(adEnv) + local farmId = g_currentMission:getFarmId() + local result = {} + for _, vehicle in pairs(adEnv.AutoDrive.getAllVehicles()) do + if vehicle.ad ~= nil and vehicle.getOwnerFarmId ~= nil and vehicle:getOwnerFarmId() == farmId then + table.insert(result, vehicle) + end + end + return result +end + +function ADRunsController.findVehicleById(adEnv, vehicleId) + for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do + if ADRunsController.getVehicleId(vehicle) == vehicleId then + return vehicle + end + end + return nil +end + +function ADRunsController.getRunForVehicle(vehicle) + local id = ADRunsController.getVehicleId(vehicle) + return id ~= nil and ADRuns.findRunByVehicle(ADRunsController.store, id) or nil +end + +-- ---- store-wrappers (erstatter altid hele store'en; modellen muterer aldrig) ---- + +function ADRunsController.createRun(name) + local store, run = ADRuns.createRun(ADRunsController.store, name) + ADRunsController.store = store + return run +end + +function ADRunsController.deleteRun(runId) + ADRunsController.store = ADRuns.deleteRun(ADRunsController.store, runId) +end + +function ADRunsController.updateRun(runId, changes) + ADRunsController.store = ADRuns.updateRun(ADRunsController.store, runId, changes) +end + +function ADRunsController.assignVehicle(runId, vehicle) + local id = ADRunsController.getVehicleId(vehicle) + if id == nil then + return nil + end + local store, previousRunId = ADRuns.assignVehicle(ADRunsController.store, runId, id) + ADRunsController.store = store + return previousRunId +end + +function ADRunsController.unassignVehicle(vehicle) + local id = ADRunsController.getVehicleId(vehicle) + if id ~= nil then + ADRunsController.store = ADRuns.unassignVehicle(ADRunsController.store, id) + end +end + +-- ---- varer ---- + +function ADRunsController.getCarriableFillTypes(adEnv, vehicle) + return adEnv.AutoDrive.getSupportedFillTypesOfAllUnitsAlphabetically(vehicle) or {} +end + +-- Auto-listen: det vognen kan bære, som målet også tager. Er målet en fabrik, indsnævrer +-- findSupplyPickup selv til de ønskede produktioners inputs bagefter. +function ADRunsController.getTargetFillTypes(adEnv, run, vehicle) + local farmId = ADSmartPickup.getFarmId(vehicle) + local target = run.targetWayPointId ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, run.targetWayPointId, farmId) or nil + if target == nil then + return {} + end + local result = {} + for _, fillType in ipairs(ADRunsController.getCarriableFillTypes(adEnv, vehicle)) do + if ADSmartPickup.getStationLevelAndCapacity(target, fillType, farmId) ~= nil then + table.insert(result, fillType) + end + end + table.sort(result) + return result +end + +function ADRunsController.getRunIngredients(adEnv, run, vehicle) + if run.ingredientMode == "manual" then + local result = {} + for _, name in ipairs(run.ingredients) do + local index = g_fillTypeManager:getFillTypeIndexByName(name) + if index ~= nil then + table.insert(result, index) + end + end + return result + end + return ADRunsController.getTargetFillTypes(adEnv, run, vehicle) +end + +-- ---- validering ---- + +local function getLoadCapacity(adEnv, vehicle) + local trailers = adEnv.AutoDrive.getAllUnits(vehicle) + if trailers == nil then + return nil + end + local _, capacity = adEnv.AutoDrive.getAllFillLevels(trailers) + return capacity ~= nil and capacity > 0 and capacity or nil +end + +local function intersects(listA, listB) + for _, a in ipairs(listA) do + for _, b in ipairs(listB) do + if a == b then + return true + end + end + end + return false +end + +-- reason: "noTarget" | "noTrailer" | "cannotCarry" | "farFromNetwork" (l10n-nøgler i GUI'et). +function ADRunsController.validateVehicle(adEnv, run, vehicle) + local carriable = ADRunsController.getCarriableFillTypes(adEnv, vehicle) + local result = {ok = false, carriable = carriable, capacity = getLoadCapacity(adEnv, vehicle)} + if run.targetWayPointId == nil or adEnv.ADGraphManager:getMapMarkerByWayPointId(run.targetWayPointId) == nil then + result.reason = "noTarget" + return result + end + if result.capacity == nil then + result.reason = "noTrailer" + return result + end + if not intersects(ADRunsController.getRunIngredients(adEnv, run, vehicle), carriable) then + result.reason = "cannotCarry" + return result + end + local distance = adEnv.ADGraphManager.getDistanceFromNetwork ~= nil and adEnv.ADGraphManager:getDistanceFromNetwork(vehicle) or 0 + if distance == nil or distance > ADRunsController.MAX_NETWORK_DISTANCE then + result.reason = "farFromNetwork" + return result + end + result.ok = true + return result +end + +-- ---- Start / Stop ---- + +-- AD gemmer indstillinger som INDEKS i values; useFolders er global, rotateTargets pr. køretøj. +local function setSetting(adEnv, vehicle, settingName, value) + local global = adEnv.AutoDrive.settings[settingName] + if global == nil then + return + end + local setting = global + if global.isVehicleSpecific and vehicle.ad.settings ~= nil and vehicle.ad.settings[settingName] ~= nil then + setting = vehicle.ad.settings[settingName] + end + for index, candidate in ipairs(setting.values) do + if candidate == value then + setting.current = index + setting.new = index + return + end + end +end + +-- Nærmeste markør med en læssestation: kun en gyldig start — moden vælger den reelle kilde pr. tur. +local function findNearestLoadMarker(adEnv, vehicle, farmId) + local x, _, z = getWorldTranslation(vehicle.rootNode) + local best, bestDistance = nil, math.huge + for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do + if ADSmartPickup.getLoadStationAtWayPointId(adEnv, marker.id, farmId) ~= nil then + local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id) + local dx, dz = wayPoint.x - x, wayPoint.z - z + local distance = dx * dx + dz * dz + if distance < bestDistance then + best, bestDistance = marker, distance + end + end + end + return best +end + +local function applyRunToVehicle(adEnv, run, vehicle, ingredients) + local stateModule = vehicle.ad.stateModule + local farmId = ADSmartPickup.getFarmId(vehicle) + stateModule:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER) + stateModule:setSecondMarkerByWayPointId(run.targetWayPointId) + local loadMarker = findNearestLoadMarker(adEnv, vehicle, farmId) + if loadMarker ~= nil then + stateModule:setFirstMarkerByWayPointId(loadMarker.id) + end + stateModule.selectedFillTypes = copyList(ingredients) + stateModule:setFillType(ingredients[1]) + stateModule.loopCounter = run.loops + stateModule:setLoopsDone(0) + setSetting(adEnv, vehicle, "useFolders", true) + setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_ONLYPICKUP) + stateModule:raiseDirtyFlag() +end + +local function isVehicleActive(vehicle) + local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil + return stateModule ~= nil and stateModule.isActive ~= nil and stateModule:isActive() +end + +-- Pr. tildelt traktor: validér, skriv AD-tilstand, start. ✖-traktorer springes over. +function ADRunsController.startRun(adEnv, runId) + local run = ADRuns.getRun(ADRunsController.store, runId) + local results = {} + if run == nil then + return results + end + for _, vehicleId in ipairs(run.vehicleIds) do + local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) + if vehicle ~= nil then + local validation = ADRunsController.validateVehicle(adEnv, run, vehicle) + local result = {vehicle = vehicle, ok = validation.ok, reason = validation.reason} + if validation.ok then + local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle) + local ok, err = pcall(applyRunToVehicle, adEnv, run, vehicle, ingredients) + if ok then + if not isVehicleActive(vehicle) then + vehicle:startAutoDrive() + end + Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' startet mod markør %s", tostring(run.name), tostring(vehicle:getName()), tostring(run.targetWayPointId)) + else + result.ok, result.reason = false, "error" + Logging.warning(ADRunsController.LOG_PREFIX .. "run '%s': kunne ikke starte '%s': %s", tostring(run.name), tostring(vehicle:getName()), tostring(err)) + end + end + table.insert(results, result) + end + end + return results +end + +function ADRunsController.stopRun(adEnv, runId) + local run = ADRuns.getRun(ADRunsController.store, runId) + local stopped = 0 + if run == nil then + return stopped + end + for _, vehicleId in ipairs(run.vehicleIds) do + local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) + if vehicle ~= nil and isVehicleActive(vehicle) then + vehicle:stopAutoDrive() + stopped = stopped + 1 + end + end + return stopped +end + +function ADRunsController.getRunStatus(adEnv, run) + local running = 0 + for _, vehicleId in ipairs(run.vehicleIds) do + local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) + if vehicle ~= nil and isVehicleActive(vehicle) then + running = running + 1 + end + end + return {running = running, total = #run.vehicleIds} +end + +-- ---- opslag til dropdowns ---- + +function ADRunsController.getTargetMarkers(adEnv, farmId) + local result = {} + for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do + if ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId) ~= nil then + table.insert(result, marker) + end + end + table.sort(result, function(a, b) return tostring(a.name) < tostring(b.name) end) + return result +end + +function ADRunsController.getWaitPools(adEnv) + local counts = {} + for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do + if ADSupplyPlanner.isWaitMarker(marker) then + counts[marker.group] = (counts[marker.group] or 0) + 1 + end + end + local result = {} + for group, count in pairs(counts) do + table.insert(result, {group = group, count = count}) + end + table.sort(result, function(a, b) return a.group < b.group end) + return result +end + +-- ---- gem / indlæs ---- + +function ADRunsController.save() + local ok, saved = pcall(ADRunsStorage.save, ADRunsController.store) + if not ok then + Logging.warning(ADRunsController.LOG_PREFIX .. "kunne ikke gemme runs: %s", tostring(saved)) + return false + end + return saved +end + +-- Køretøjs-referencer der ikke findes (solgt, andet savegame) logges; run'et beholdes. +function ADRunsController.load(adEnv) + local ok, store = pcall(ADRunsStorage.load) + if not ok then + Logging.warning(ADRunsController.LOG_PREFIX .. "kunne ikke indlæse runs: %s", tostring(store)) + return + end + ADRunsController.store = store + for _, run in ipairs(store.runs) do + for _, vehicleId in ipairs(run.vehicleIds) do + if ADRunsController.findVehicleById(adEnv, vehicleId) == nil then + Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': køretøj %s findes ikke (endnu) i dette savegame", tostring(run.name), tostring(vehicleId)) + end + end + end +end + +function ADRunsController.installSaveHook() + if ItemSystem == nil or ItemSystem.save == nil or Utils == nil then + return + end + ItemSystem.save = Utils.prependedFunction(ItemSystem.save, function() + ADRunsController.save() + end) +end diff --git a/tests/test_adRunsController.lua b/tests/test_adRunsController.lua new file mode 100644 index 0000000..b57497c --- /dev/null +++ b/tests/test_adRunsController.lua @@ -0,0 +1,235 @@ +-- Kør: luajit tests/test_adRunsController.lua (fra repo-roden) +-- Controlleren oversætter et run til AutoDrive-tilstand pr. traktor. Mock'er AD + de +-- få ADSmartPickup-opslag den bruger. + +local STRAW, TMR, WATER, MILK = 31, 116, 8, 30 +local FILL_TYPE_NAMES = {[STRAW] = "STRAW", [TMR] = "FORAGE", [WATER] = "WATER", [MILK] = "MILK"} +local FILL_TYPE_INDEX = {} +for index, name in pairs(FILL_TYPE_NAMES) do FILL_TYPE_INDEX[name] = index end +g_fillTypeManager = { + getFillTypeNameByIndex = function(_, index) return FILL_TYPE_NAMES[index] end, + getFillTypeIndexByName = function(_, name) return FILL_TYPE_INDEX[name] end, +} +local logLines = {} +Logging = { + info = function(fmt, ...) table.insert(logLines, string.format(fmt, ...)) end, + warning = function(fmt, ...) table.insert(logLines, "WARN " .. string.format(fmt, ...)) end, +} +Utils = {prependedFunction = function(original, extra) return function(...) extra(...); return original(...) end end} +ItemSystem = {save = function() end} +g_currentMission = {getFarmId = function() return 1 end, missionInfo = {}} + +-- ---- AutoDrive-mock ---- +local RT_NONE, RT_ONLYPICKUP = 1, 2 +local MODE_PICKUPANDDELIVER = 2 +local vehicles, markers, wayPoints = {}, {}, {} +FS25_AutoDrive = { + AutoDrive = { + RT_NONE = RT_NONE, RT_ONLYPICKUP = RT_ONLYPICKUP, MODE_PICKUPANDDELIVER = MODE_PICKUPANDDELIVER, + settings = { + useFolders = {values = {false, true}, current = 1, isVehicleSpecific = false}, + rotateTargets = {values = {1, 2, 3, 4}, current = 1, isVehicleSpecific = true}, + }, + getAllVehicles = function() return vehicles end, + getAllUnits = function(vehicle) return vehicle.trailers end, + getAllFillLevels = function(trailers) return trailers.level or 0, trailers.capacity or 0, false, 0 end, + getSupportedFillTypesOfAllUnitsAlphabetically = function(vehicle) return vehicle.carriable or {} end, + }, + ADGraphManager = { + getMapMarkers = function() return markers end, + getMapMarkerByWayPointId = function(_, id) for _, m in ipairs(markers) do if m.id == id then return m end end end, + getWayPointById = function(_, id) return wayPoints[id] end, + getDistanceFromNetwork = function(_, vehicle) return vehicle.networkDistance or 0 end, + }, +} + +local function addMarker(id, name, group, x, z) + wayPoints[id] = {x = x, z = z, incoming = {}} + table.insert(markers, {id = id, markerIndex = #markers + 1, name = name, group = group}) +end + +-- ADSmartPickup-opslag: hvilke waypoints har hvilken slags station, og hvad målet tager +local unloadAt, loadAt = {}, {} +ADSmartPickup = { + getFarmId = function(vehicle) return vehicle.farmId end, + getUnloadStationAtWayPointId = function(_, id) return unloadAt[id] end, + getLoadStationAtWayPointId = function(_, id) return loadAt[id] end, + getStationLevelAndCapacity = function(station, fillType) + if station.accepts[fillType] == nil then return nil end + return 0, station.accepts[fillType] + end, +} +ADSupplyPlanner = {isWaitMarker = function(marker) return string.find(string.lower(marker.group or ""), "%f[%a]wait%f[%A]") ~= nil end} + +local function makeVehicle(uniqueId, name, carriable, capacity, x, z) + local stateModule = {selectedFillTypes = {}, fillType = 0, active = false, loopCounter = 5, loopsDone = 3, dirtyCount = 0} + stateModule.setMode = function(self, mode) self.mode = mode end + stateModule.setFirstMarkerByWayPointId = function(self, id) self.firstMarkerId = id end + stateModule.setSecondMarkerByWayPointId = function(self, id) self.secondMarkerId = id end + stateModule.setFillType = function(self, fillType) self.fillType = fillType end + stateModule.raiseDirtyFlag = function(self) self.dirtyCount = self.dirtyCount + 1 end + stateModule.setLoopsDone = function(self, n) self.loopsDone = n end + stateModule.isActive = function(self) return self.active end + local vehicle = { + uniqueId = uniqueId, farmId = 1, rootNode = uniqueId, + carriable = carriable, trailers = {level = 0, capacity = capacity}, + getName = function() return name end, + getOwnerFarmId = function() return 1 end, + startAutoDrive = function(self) self.ad.stateModule.active = true; self.started = (self.started or 0) + 1 end, + stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = (self.stopped or 0) + 1 end, + ad = {stateModule = stateModule, settings = {rotateTargets = {values = {1, 2, 3, 4}, current = 1, new = 1, isVehicleSpecific = true}}}, + } + nodePositions = nodePositions or {} + nodePositions[uniqueId] = {x = x or 0, z = z or 0} + table.insert(vehicles, vehicle) + return vehicle +end +function getWorldTranslation(node) local p = nodePositions[node]; return p.x, 0, p.z end + +dofile("FS25_ADSmartPickup/adRuns.lua") +dofile("FS25_ADSmartPickup/adRunsStorage.lua") +dofile("FS25_ADSmartPickup/adRunsController.lua") +local adEnv = FS25_AutoDrive + +local failures = 0 +local function check(name, actual, expected) + if actual == expected then + print("OK " .. name) + else + failures = failures + 1 + print(string.format("FAIL %s: forventede %s, fik %s", name, tostring(expected), tostring(actual))) + end +end + +local function buildWorld() + vehicles, markers, wayPoints, unloadAt, loadAt, logLines = {}, {}, {}, {}, {}, {} + ADRunsController.store = ADRuns.newStore() + addMarker(94, "Cow 1 Food", "Cow Food", 5000, 0); unloadAt[94] = {accepts = {[STRAW] = 300000, [TMR] = 300000, [WATER] = 50000}} + addMarker(44, "10 Silo Storage", "Siloer", 0, 0); loadAt[44] = {} + addMarker(75, "TMR Load", "Fabrication", 900, 0); loadAt[75] = {} + addMarker(100, "Cow Wait", "Cow Wait", 5100, 0) + addMarker(101, "Cow Wait", "Cow Wait", 5200, 0) + addMarker(200, "F91 Wait", "F91", 8000, 0) + addMarker(300, "Pig Wait", "Pig Wait", 9000, 0) +end + +-- C1: køretøjs-id og farmens AD-køretøjer +buildWorld() +local fastracA = makeVehicle("vehicleA", "Fastrac A", {STRAW, TMR}, 1000000, 100, 0) +local fastracB = makeVehicle("vehicleB", "Fastrac B", {STRAW}, 700000, 4000, 0) +local noAd = {uniqueId = "vehicleC", getOwnerFarmId = function() return 1 end} +table.insert(vehicles, noAd) +local otherFarm = makeVehicle("vehicleD", "Nabo", {STRAW}, 1000, 0, 0); otherFarm.getOwnerFarmId = function() return 2 end +check("C1 uniqueId", ADRunsController.getVehicleId(fastracA), "vehicleA") +check("C1 fallback savegame-id", ADRunsController.getVehicleId({currentSavegameId = 7}), "7") +check("C1 intet id -> nil", ADRunsController.getVehicleId({}), nil) +check("C1 farmens AD-køretøjer", #ADRunsController.getFarmVehicles(adEnv), 2) +check("C1 opslag på id", ADRunsController.findVehicleById(adEnv, "vehicleB"), fastracB) +check("C1 ukendt id -> nil", ADRunsController.findVehicleById(adEnv, "nope"), nil) + +-- C2: opret run, tildel; opslag run for køretøj; flyt mellem runs +local cow = ADRunsController.createRun("Cow Feed") +check("C2 run oprettet", ADRuns.getRun(ADRunsController.store, cow.id).name, "Cow Feed") +check("C2 ingen tidligere", ADRunsController.assignVehicle(cow.id, fastracA), nil) +check("C2 run for køretøj", ADRunsController.getRunForVehicle(fastracA).id, cow.id) +check("C2 ikke tildelt -> nil", ADRunsController.getRunForVehicle(fastracB), nil) +local pig = ADRunsController.createRun("Pig Feed") +check("C2 flyttet fra Cow", ADRunsController.assignVehicle(pig.id, fastracA), cow.id) +check("C2 nu i Pig", ADRunsController.getRunForVehicle(fastracA).id, pig.id) +ADRunsController.unassignVehicle(fastracA) +check("C2 fjernet", ADRunsController.getRunForVehicle(fastracA), nil) +ADRunsController.deleteRun(pig.id) +check("C2 slettet", ADRuns.getRun(ADRunsController.store, pig.id), nil) + +-- C3: Auto-vareliste = vognens varer ∩ målets varer; manual = navne -> indeks (ukendte droppes) +ADRunsController.updateRun(cow.id, {targetWayPointId = 94}) +local cowRun = ADRuns.getRun(ADRunsController.store, cow.id) +check("C3 auto: halm+TMR (vand kan vognen ikke bære)", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracA), ","), STRAW .. "," .. TMR) +check("C3 auto: kun halm for halm-vogn", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracB), ","), tostring(STRAW)) +ADRunsController.updateRun(cow.id, {ingredientMode = "manual", ingredients = {"FORAGE", "UNKNOWN_THING"}}) +cowRun = ADRuns.getRun(ADRunsController.store, cow.id) +check("C3 manual: FORAGE -> indeks, ukendt droppes", table.concat(ADRunsController.getRunIngredients(adEnv, cowRun, fastracA), ","), tostring(TMR)) +ADRunsController.updateRun(cow.id, {ingredientMode = "auto"}) +local noTarget = ADRunsController.createRun("Uden mål") +check("C3 uden mål -> tom liste", #ADRunsController.getRunIngredients(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA), 0) + +-- C4: validering +cowRun = ADRuns.getRun(ADRunsController.store, cow.id) +check("C4 ok", ADRunsController.validateVehicle(adEnv, cowRun, fastracA).ok, true) +check("C4 kapacitet", ADRunsController.validateVehicle(adEnv, cowRun, fastracA).capacity, 1000000) +local noTrailer = makeVehicle("vehicleE", "Uden vogn", {}, 0) +check("C4 ingen vogn", ADRunsController.validateVehicle(adEnv, cowRun, noTrailer).reason, "noTrailer") +local milkTruck = makeVehicle("vehicleF", "Mælkebil", {MILK}, 20000) +check("C4 kan ikke bære", ADRunsController.validateVehicle(adEnv, cowRun, milkTruck).reason, "cannotCarry") +fastracB.networkDistance = 31 +check("C4 for langt fra vejnettet", ADRunsController.validateVehicle(adEnv, cowRun, fastracB).reason, "farFromNetwork") +fastracB.networkDistance = 0 +check("C4 intet mål", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA).reason, "noTarget") +ADRunsController.updateRun(noTarget.id, {targetWayPointId = 4242}) +check("C4 mål-markør findes ikke", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, noTarget.id), fastracA).reason, "noTarget") + +-- C5: Start skriver AD-tilstand og starter; ✖-traktorer springes over +ADRunsController.updateRun(cow.id, {loops = 4, waitPoolGroup = "Cow Wait"}) +ADRunsController.assignVehicle(cow.id, fastracA) +ADRunsController.assignVehicle(cow.id, noTrailer) +local results = ADRunsController.startRun(adEnv, cow.id) +check("C5 to resultater", #results, 2) +check("C5 A startet", fastracA.started, 1) +check("C5 A ok", results[1].ok, true) +check("C5 uden vogn sprunget over", noTrailer.started, nil) +check("C5 uden vogn grund", results[2].reason, "noTrailer") +local state = fastracA.ad.stateModule +check("C5 mode P&D", state.mode, MODE_PICKUPANDDELIVER) +check("C5 mål = run'ets mål", state.secondMarkerId, 94) +check("C5 start-pickup = nærmeste læssestation", state.firstMarkerId, 44) +check("C5 varer = auto-listen", table.concat(state.selectedFillTypes, ","), STRAW .. "," .. TMR) +check("C5 fillType = første", state.fillType, STRAW) +check("C5 loops", state.loopCounter, 4) +check("C5 loopsDone nulstillet", state.loopsDone, 0) +check("C5 rotateTargets = kun pålæsning (current)", fastracA.ad.settings.rotateTargets.current, 2) +check("C5 rotateTargets = kun pålæsning (new)", fastracA.ad.settings.rotateTargets.new, 2) +check("C5 useFolders globalt til", adEnv.AutoDrive.settings.useFolders.current, 2) +check("C5 dirty flag", state.dirtyCount > 0, true) +check("C5 status", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 1) +check("C5 total", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).total, 2) +check("C5 ukendt run -> tom", #ADRunsController.startRun(adEnv, 999), 0) + +-- C6: Stop +check("C6 stoppede", ADRunsController.stopRun(adEnv, cow.id), 1) +check("C6 A stoppet", fastracA.stopped, 1) +check("C6 status 0", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 0) +check("C6 medlemskab bevaret", ADRunsController.getRunForVehicle(fastracA).id, cow.id) + +-- C7: Start på traktor der allerede kører -> ikke startet igen, men tilstand skrives +fastracA.ad.stateModule.active = true +ADRunsController.startRun(adEnv, cow.id) +check("C7 ikke dobbelt-startet", fastracA.started, 1) + +-- C8: mål-markører og Wait-puljer til dropdowns +local targets = ADRunsController.getTargetMarkers(adEnv, 1) +check("C8 kun markører med aflæsningsstation", #targets, 1) +check("C8 navn", targets[1].name, "Cow 1 Food") +local pools = ADRunsController.getWaitPools(adEnv) +check("C8 to puljer (F91 er ikke en Wait-mappe)", #pools, 2) +check("C8 sorteret", pools[1].group, "Cow Wait") +check("C8 antal", pools[1].count, 2) + +-- C9: gem/indlæs; ukendte køretøjs-id'er logges ved indlæsning men beholdes i run'et +local savedRecords +ADRunsStorage.save = function(store) savedRecords = ADRuns.toRecords(store); return true end +ADRunsStorage.load = function() return ADRuns.fromRecords(savedRecords) end +ADRunsController.assignVehicle(cow.id, {uniqueId = "vehicleGONE", ad = {}, getOwnerFarmId = function() return 1 end, getName = function() return "Væk" end}) +check("C9 gemt", ADRunsController.save(), true) +ADRunsController.store = ADRuns.newStore() +ADRunsController.load(adEnv) +check("C9 indlæst", ADRuns.getRun(ADRunsController.store, cow.id).name, "Cow Feed") +check("C9 ukendt køretøj logget", logLines[#logLines]:find("vehicleGONE", 1, true) ~= nil, true) +check("C9 run beholder kendte", ADRunsController.getRunForVehicle(fastracA).id, cow.id) +local hooked = false +ItemSystem.save = function() hooked = true end +ADRunsController.installSaveHook() +ItemSystem.save() +check("C9 save-hook kaldes før ItemSystem.save", hooked, true) + +print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) +os.exit(failures == 0 and 0 or 1)