From ccc6b608dcafa1d637f7f507f6148d2c4bd3ca89 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sun, 27 Sep 2026 10:41:46 +0200 Subject: [PATCH] =?UTF-8?q?feat(marksilo):=20l=C3=A5ner=20ledige=20t=C3=B8?= =?UTF-8?q?mmevogne=20hvert=20minut,=20AD=20hent-og-aflever=20til=20g?= =?UTF-8?q?=C3=A5rdlager,=20frigiver=20ved=20f=C3=A6rdig/spiller/2=20min?= =?UTF-8?q?=20stilstand;=20ops=C3=A6tning=20gemmes?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adFieldSilos.lua | 315 ++++++++++++++++++++++++ FS25_ADSmartPickup/adFieldWork.lua | 6 +- FS25_ADSmartPickup/adRunsController.lua | 2 + FS25_ADSmartPickup/adSmartPickup.lua | 6 + FS25_ADSmartPickup/modDesc.xml | 3 + tests/test_adFieldSilos.lua | 81 ++++++ 6 files changed, 412 insertions(+), 1 deletion(-) create mode 100644 FS25_ADSmartPickup/adFieldSilos.lua create mode 100644 tests/test_adFieldSilos.lua diff --git a/FS25_ADSmartPickup/adFieldSilos.lua b/FS25_ADSmartPickup/adFieldSilos.lua new file mode 100644 index 0000000..11fb6cd --- /dev/null +++ b/FS25_ADSmartPickup/adFieldSilos.lua @@ -0,0 +1,315 @@ +-- AD Field Silos +-- Marksilo-flowet i spillet: samler marksiloer, gårdlagre og ledige vogne, lader ADFieldSiloPlanner vælge, +-- låner vogne (AutoDrive "Hent og aflever", én tur, parkering bagefter) og frigiver dem igen. +-- Lån ligger kun i hukommelsen; opsætningen gemmes i ADSmartPickup_fieldsilos.xml. + +ADFieldSilos = {} + +ADFieldSilos.PLAN_INTERVAL_MS = 60000 +ADFieldSilos.CHECK_INTERVAL_MS = 2000 +ADFieldSilos.STUCK_MS = 120000 +ADFieldSilos.PAUSE_MS = 600000 +ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive +ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml" +ADFieldSilos.ROOT = "ADSmartPickupFieldSilos" +ADFieldSilos.LOG_PREFIX = "ADSmartPickup: marksilo: " + +ADFieldSilos.config = ADFieldSiloConfig.new() +ADFieldSilos.lastPlan = nil +local loans = setmetatable({}, {__mode = "k"}) -- vehicle -> {siloId, siloName, fillType, liters, startedMs, lastProgressMs, lastFill} +local paused = {} -- key -> untilMs +local planTimerMs, checkTimerMs, nowMs = ADFieldSilos.PLAN_INTERVAL_MS, 0, 0 + +-- ---- ren frigivelsesregel ---- + +-- vehicleState = {adActive, controlled, fill, moving} +function ADFieldSilos.releaseReason(loan, vehicleState, now) + if vehicleState.controlled then return "player" end + if not vehicleState.adActive then return "done" end + if vehicleState.moving then return nil end + if (vehicleState.fill or 0) > (loan.lastFill or 0) then return nil end + if now - (loan.lastProgressMs or loan.startedMs or 0) > ADFieldSilos.STUCK_MS then return "stuck" end + return nil +end + +-- ---- opsætning ---- + +function ADFieldSilos.setConfig(cfg) + local wasEnabled = ADFieldSilos.config.enabled + ADFieldSilos.config = cfg + if wasEnabled and not cfg.enabled then ADFieldSilos.stopAll(ADFieldSilos.adEnv) end + planTimerMs = ADFieldSilos.PLAN_INTERVAL_MS -- planlæg straks ved næste tick +end + +function ADFieldSilos.isLoaned(vehicle) + return loans[vehicle] ~= nil +end + +function ADFieldSilos.loanCount() + local n = 0 + for _ in pairs(loans) do n = n + 1 end + return n +end + +-- ---- spil-input ---- + +local function farmId() + return g_currentMission ~= nil and g_currentMission.getFarmId ~= nil and g_currentMission:getFarmId() or 1 +end + +local function position(node) + if node == nil then return 0, 0 end + local x, _, z = getWorldTranslation(node) + return x, z +end + +local function siloStock(placeable) + local stock = {} + local spec = placeable ~= nil and placeable.spec_silo or nil + for _, storage in pairs(spec ~= nil and spec.storages or {}) do + for fillType, level in pairs(storage.fillLevels or {}) do + if (level or 0) > 0 then stock[fillType] = (stock[fillType] or 0) + level end + end + end + return stock +end + +local function siloFree(placeable) + local free = {} + local spec = placeable ~= nil and placeable.spec_silo or nil + for _, storage in pairs(spec ~= nil and spec.storages or {}) do + for fillType in pairs(storage.fillTypes or {}) do + local amount = storage.getFreeCapacity ~= nil and (storage:getFreeCapacity(fillType) or 0) or 0 + free[fillType] = (free[fillType] or 0) + amount + end + end + return free +end + +local function firstMarker(building, role) + local markers = ADBuildings.getMarkers(building, role) + return markers[1] ~= nil and markers[1].id or nil +end + +-- Gårdlagre fra opsætningen + marksiloer (alle markers marksiloer, uden dubletter og uden gårdlagre). +function ADFieldSilos.collectBuildings(adEnv) + local fid = farmId() + local stores, byId = {}, {} + for _, building in ipairs(ADBuildings.list(adEnv, fid)) do + byId[building.id] = building + if building.kind == "silo" and ADFieldSiloConfig.hasStore(ADFieldSilos.config, building.id) then + local x, z = position(building.placeable ~= nil and building.placeable.rootNode or nil) + table.insert(stores, {id = building.id, name = building.name, markerId = firstMarker(building, "unload"), + x = x, z = z, free = siloFree(building.placeable)}) + end + end + local silos, seen = {}, {} + for _, info in ipairs(ADFields.list()) do + for _, building in ipairs(ADFields.fieldSilos(adEnv, fid, info)) do + if not seen[building.id] and not ADFieldSiloConfig.hasStore(ADFieldSilos.config, building.id) then + seen[building.id] = true + local x, z = position(building.placeable ~= nil and building.placeable.rootNode or nil) + table.insert(silos, {id = building.id, name = building.name, markerId = firstMarker(building, "load"), + x = x, z = z, stock = siloStock(building.placeable)}) + end + end + end + return silos, stores +end + +local function trailerInfo(adEnv, vehicle) + local capacity, carries, cargo = 0, {}, nil + for _, unit in ipairs(adEnv.AutoDrive.getAllDischargeableUnits(vehicle) or {}) do + local object, index = unit.object, unit.fillUnitIndex + if object ~= nil and object.getFillUnitCapacity ~= nil then + capacity = capacity + (object:getFillUnitCapacity(index) or 0) + for fillType in pairs(object.getFillUnitSupportedFillTypes ~= nil and object:getFillUnitSupportedFillTypes(index) or {}) do + carries[fillType] = true + end + if (object:getFillUnitFillLevel(index) or 0) > 0 then cargo = object:getFillUnitFillType(index) end + end + end + return capacity, carries, cargo +end + +local function fillLevel(adEnv, vehicle) + local ok, level = pcall(function() return (adEnv.AutoDrive.getAllFillLevels(adEnv.AutoDrive.getAllUnits(vehicle))) end) + return ok and tonumber(level) or 0 +end + +function ADFieldSilos.collectVehicles(adEnv) + local vehicles, byId = {}, {} + for _, rig in ipairs(ADFieldWork.getRigs(adEnv)) do + if rig.role == "unloader" and rig.enabled and not rig.busy and not rig.controlled then + local capacity, carries, cargo = trailerInfo(adEnv, rig.vehicle) + table.insert(vehicles, {id = rig.id, x = rig.x, z = rig.z, capacity = capacity, carries = carries, cargo = cargo}) + byId[rig.id] = rig.vehicle + end + end + return vehicles, byId +end + +-- ---- lån ---- + +local function fillTypeName(fillType) + local desc = g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil + return desc ~= nil and (desc.title or desc.name) or tostring(fillType) +end + +local function dispatchOne(adEnv, order, vehicle, silosById, storesById) + local module = vehicle.ad.stateModule + module:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER) + module:setFirstMarkerByWayPointId(order.siloMarkerId) + module:setSecondMarkerByWayPointId(order.storeMarkerId) + module.selectedFillTypes = {order.fillType} + module:setFillType(order.fillType) + module.loopCounter = 1 + if module.setLoopsDone ~= nil then module:setLoopsDone(0) end + ADFieldWork.enableParkAtJobFinished(vehicle) + module:raiseDirtyFlag() + ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_PICKUPANDDELIVER, order.siloMarkerId, order.storeMarkerId, order.fillType) + local silo, store = silosById[order.siloId], storesById[order.storeId] + loans[vehicle] = {siloId = order.siloId, siloName = silo ~= nil and silo.name or order.siloId, fillType = order.fillType, + liters = order.liters, startedMs = nowMs, lastProgressMs = nowMs, lastFill = fillLevel(adEnv, vehicle)} + Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' henter %d l %s i %s -> %s", tostring(ADRuns.vehicleLabel(vehicle)), + math.floor(order.liters), fillTypeName(order.fillType), tostring(silo ~= nil and silo.name or order.siloId), + tostring(store ~= nil and store.name or order.storeId)) +end + +local function release(adEnv, vehicle, reason) + local loan = loans[vehicle] + loans[vehicle] = nil + if loan == nil then return end + if reason == "stuck" then + local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType) + paused[k] = nowMs + ADFieldSilos.PAUSE_MS + pcall(vehicle.stopAutoDrive, vehicle) + Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' kom ikke i gang ved %s (%s) på 2 min — stoppet; siloen springes over i 10 min", + tostring(ADRuns.vehicleLabel(vehicle)), tostring(loan.siloName), fillTypeName(loan.fillType)) + elseif reason == "stopped" then + pcall(vehicle.stopAutoDrive, vehicle) + end + Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' frigivet (%s)", tostring(ADRuns.vehicleLabel(vehicle)), tostring(reason)) +end + +function ADFieldSilos.stopAll(adEnv) + local list = {} + for vehicle in pairs(loans) do table.insert(list, vehicle) end + for _, vehicle in ipairs(list) do release(adEnv, vehicle, "stopped") end +end + +local function checkLoans(adEnv) + local list = {} + for vehicle in pairs(loans) do table.insert(list, vehicle) end + for _, vehicle in ipairs(list) do + local loan = loans[vehicle] + local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil + local fill = fillLevel(adEnv, vehicle) + local vehicleState = { + adActive = stateModule ~= nil and stateModule:isActive(), + controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false, + fill = fill, moving = (vehicle.lastSpeedReal or 0) >= ADFieldSilos.MOVING_SPEED, + } + if vehicleState.moving or fill > (loan.lastFill or 0) then loan.lastProgressMs = nowMs end + loan.lastFill = math.max(loan.lastFill or 0, fill) + local reason = ADFieldSilos.releaseReason(loan, vehicleState, nowMs) + if reason ~= nil then release(adEnv, vehicle, reason) end + end +end + +local function currentPaused() + local result = {} + for k, untilMs in pairs(paused) do + if untilMs > nowMs then result[k] = true else paused[k] = nil end + end + return result +end + +local function loanList() + local list = {} + for _, loan in pairs(loans) do table.insert(list, {siloId = loan.siloId, fillType = loan.fillType, liters = loan.liters}) end + return list +end + +function ADFieldSilos.planNow(adEnv) + local cfg = ADFieldSilos.config + local silos, stores = ADFieldSilos.collectBuildings(adEnv) + local vehicles, vehiclesById = ADFieldSilos.collectVehicles(adEnv) + local plan = ADFieldSiloPlanner.plan({silos = silos, stores = stores, loans = loanList(), vehicles = vehicles, + paused = currentPaused(), maxVehicles = cfg.maxVehicles, minLiters = cfg.minLiters}) + ADFieldSilos.lastPlan = plan + if not cfg.enabled then return plan end + local silosById, storesById = {}, {} + for _, silo in ipairs(silos) do silosById[silo.id] = silo end + for _, store in ipairs(stores) do storesById[store.id] = store end + for _, order in ipairs(plan.dispatch) do + local vehicle = vehiclesById[order.vehicleId] + if vehicle ~= nil then + local ok, err = pcall(dispatchOne, adEnv, order, vehicle, silosById, storesById) + if not ok then + loans[vehicle] = nil + Logging.warning(ADFieldSilos.LOG_PREFIX .. "kunne ikke sende '%s': %s", tostring(ADRuns.vehicleLabel(vehicle)), tostring(err)) + end + end + end + return plan +end + +function ADFieldSilos.tick(adEnv, dt) + if adEnv == nil then return end + ADFieldSilos.adEnv = adEnv + nowMs = nowMs + (dt or 0) + checkTimerMs = checkTimerMs + (dt or 0) + if checkTimerMs >= ADFieldSilos.CHECK_INTERVAL_MS then + checkTimerMs = 0 + checkLoans(adEnv) + end + planTimerMs = planTimerMs + (dt or 0) + if planTimerMs >= ADFieldSilos.PLAN_INTERVAL_MS then + planTimerMs = 0 + ADFieldSilos.planNow(adEnv) + end +end + +-- ---- gem / indlæs (samme sti-opslag som runs) ---- + +local function getPath() + local path = ADRunsStorage ~= nil and ADRunsStorage.getPath() or nil + if path == nil then return nil end + return (path:gsub(ADRunsStorage.FILE_NAME .. "$", ADFieldSilos.FILE_NAME)) +end + +function ADFieldSilos.save() + local path = getPath() + if path == nil then return false end + local xmlFile = XMLFile.create("ADSmartPickupFieldSilos", path, ADFieldSilos.ROOT) + if xmlFile == nil then return false end + local record = ADFieldSiloConfig.toRecord(ADFieldSilos.config) + xmlFile:setInt(ADFieldSilos.ROOT .. "#enabled", record.enabled) + xmlFile:setString(ADFieldSilos.ROOT .. "#stores", record.stores) + xmlFile:setInt(ADFieldSilos.ROOT .. "#maxVehicles", record.maxVehicles) + xmlFile:setInt(ADFieldSilos.ROOT .. "#minLiters", record.minLiters) + xmlFile:save() + xmlFile:delete() + return true +end + +function ADFieldSilos.load() + local path = getPath() + if path == nil or not fileExists(path) then + ADFieldSilos.config = ADFieldSiloConfig.new() + return + end + local xmlFile = XMLFile.load("ADSmartPickupFieldSilos", path) + if xmlFile == nil then + ADFieldSilos.config = ADFieldSiloConfig.new() + return + end + ADFieldSilos.config = ADFieldSiloConfig.fromRecord({ + enabled = xmlFile:getInt(ADFieldSilos.ROOT .. "#enabled"), + stores = xmlFile:getString(ADFieldSilos.ROOT .. "#stores"), + maxVehicles = xmlFile:getInt(ADFieldSilos.ROOT .. "#maxVehicles"), + minLiters = xmlFile:getInt(ADFieldSilos.ROOT .. "#minLiters"), + }) + xmlFile:delete() +end diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index 9ecfaad..60dc928 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -88,6 +88,9 @@ local function startAd(adEnv, vehicle, mode, firstId, secondId, fillType) wake(vehicle, true) end +ADFieldWork.startAd = startAd +ADFieldWork.enableParkAtJobFinished = enableParkAtJobFinished + -- AD-parkering: sidder på redskabet og kopieres af AD til traktorens stateModule (markør-INDEKS). local function parkMarkerId(adEnv, vehicle) local module = stateModule(vehicle) @@ -130,7 +133,8 @@ function ADFieldWork.getRigs(adEnv) enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id), -- optaget: vores egen opgave, eller AutoDrive/Courseplay kører den allerede (fx spillerens -- egen parkeringstur) — den må ikke kapres midt i en anden kørsel - busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle), + busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle) + or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)), controlled = isControlledByPlayer(vehicle), width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z, -- tom spreder kan flere ting (gødning og kalk): alle dens roller diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index cffc0b1..9ef8897 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -1318,6 +1318,7 @@ function ADRunsController.save() if ADFieldJobs ~= nil and ADFieldJobs.loaded then pcall(ADFieldJobs.save) end + if ADFieldSilos ~= nil then pcall(ADFieldSilos.save) end return saved end @@ -1330,6 +1331,7 @@ function ADRunsController.load(adEnv) end ADRunsController.store = store pcall(ADRunsController.migrateBuildings, adEnv) + if ADFieldSilos ~= nil then pcall(ADFieldSilos.load) end local vehiclesById = getFarmVehiclesById(adEnv) for _, run in ipairs(store.runs) do for _, vehicleId in ipairs(run.vehicleIds) do diff --git a/FS25_ADSmartPickup/adSmartPickup.lua b/FS25_ADSmartPickup/adSmartPickup.lua index 8620a46..a62b66c 100644 --- a/FS25_ADSmartPickup/adSmartPickup.lua +++ b/FS25_ADSmartPickup/adSmartPickup.lua @@ -1262,6 +1262,12 @@ function ADSmartPickup:update(dt) Logging.warning(ADSmartPickup.LOG_PREFIX .. "markarbejde fejlede: %s", tostring(errFields)) end end + if ADFieldSilos ~= nil then + local okSilos, errSilos = pcall(ADFieldSilos.tick, getAutoDriveEnv(), dt) + if not okSilos then + Logging.warning(ADSmartPickup.LOG_PREFIX .. "marksilo-flow fejlede: %s", tostring(errSilos)) + end + end if ADStuckDiag ~= nil then pcall(ADStuckDiag.tick, dt) end if ADRunsController ~= nil and ADRunsController.adoptLoadedThrottled ~= nil then pcall(ADRunsController.adoptLoadedThrottled, getAutoDriveEnv(), dt) diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index d637af2..aff94c1 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -39,6 +39,9 @@ + + + diff --git a/tests/test_adFieldSilos.lua b/tests/test_adFieldSilos.lua new file mode 100644 index 0000000..b81b144 --- /dev/null +++ b/tests/test_adFieldSilos.lua @@ -0,0 +1,81 @@ +-- Kør: luajit tests/test_adFieldSilos.lua (fra repo-roden) +dofile("FS25_ADSmartPickup/adFieldSiloPlanner.lua") +dofile("FS25_ADSmartPickup/adFieldSiloConfig.lua") +dofile("FS25_ADSmartPickup/adFieldSilos.lua") +local S = ADFieldSilos +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 loan = {startedMs = 0, lastProgressMs = 0, lastFill = 0} +check("kører, tidligt -> behold", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = true}, 5000), nil) +check("AD slukket -> done", S.releaseReason(loan, {adActive = false, controlled = false, fill = 0}, 5000), "done") +check("spiller i vognen -> player", S.releaseReason(loan, {adActive = true, controlled = true, fill = 0}, 5000), "player") +check("står stille uden fyld i 120 s -> stuck", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = false}, 120001), "stuck") +check("kører stadig efter 120 s -> behold", S.releaseReason(loan, {adActive = true, controlled = false, fill = 0, moving = true}, 120001), nil) +local loading = {startedMs = 0, lastProgressMs = 110000, lastFill = 0} +check("fyldning steg for nylig -> behold", S.releaseReason(loading, {adActive = true, controlled = false, fill = 20000, moving = false}, 125000), nil) + +-- Røgtest af spil-laget med mocks: planNow låner nærmeste tømmevogn, isLoaned = optaget, +-- frakobling (setConfig enabled=false) stopper AD og frigiver (Review Focus). +do + local WHEAT = 1 + Logging = {info = function() end, warning = function() end} + g_currentMission = {getFarmId = function() return 1 end} + g_fillTypeManager = {getFillTypeByIndex = function() return {title = "Hvede"} end} + function getWorldTranslation(node) return node.x, 0, node.z end + ADRuns = {vehicleLabel = function(v) return v.name end} + local function storage(levels, free) return {fillLevels = levels, fillTypes = {[WHEAT] = true}, getFreeCapacity = function() return free end} end + local fieldSilo = {id = "fs", name = "Marksilo 1", kind = "silo", placeable = {rootNode = {x = 1000, z = 0}, spec_silo = {storages = {storage({[WHEAT] = 60000}, 0)}}}} + local farm = {id = "farm", name = "Gårdsilo", kind = "silo", placeable = {rootNode = {x = 0, z = 0}, spec_silo = {storages = {storage({}, 500000)}}}} + ADBuildings = {list = function() return {fieldSilo, farm} end, + getMarkers = function(b, role) return {{id = b.id .. "-" .. role}} end} + ADFields = {list = function() return {{id = 1}} end, fieldSilos = function() return {fieldSilo, farm} end} + local started = {} + local function makeVehicle(name, x) + local module = {active = false} + function module:setMode() end + function module:setFirstMarkerByWayPointId(id) self.first = id end + function module:setSecondMarkerByWayPointId(id) self.second = id end + function module:setFillType() end + function module:setLoopsDone() end + function module:raiseDirtyFlag() end + function module:isActive() return self.active end + return {name = name, x = x, lastSpeedReal = 0, ad = {stateModule = module}, + stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = true end} + end + local near, far = makeVehicle("Nær", 900), makeVehicle("Fjern", 5000) + local adEnv = {AutoDrive = {MODE_PICKUPANDDELIVER = 2, + getAllDischargeableUnits = function() return {{fillUnitIndex = 1, object = { + getFillUnitCapacity = function() return 40000 end, + getFillUnitSupportedFillTypes = function() return {[WHEAT] = true} end, + getFillUnitFillLevel = function() return 0 end, getFillUnitFillType = function() return WHEAT end}}} end, + getAllUnits = function() return {} end, getAllFillLevels = function() return 0 end}} + ADFieldWork = { + getRigs = function() return { + {id = "n", vehicle = near, role = "unloader", enabled = true, busy = S.isLoaned(near), controlled = false, x = 900, z = 0}, + {id = "f", vehicle = far, role = "unloader", enabled = true, busy = S.isLoaned(far), controlled = false, x = 5000, z = 0}, + {id = "h", vehicle = far, role = "harvester", enabled = true, busy = false, controlled = false, x = 1000, z = 0}} end, + enableParkAtJobFinished = function() end, + startAd = function(_, v, _, first, second, fillType) v.ad.stateModule.active = true; table.insert(started, {v = v, first = first, second = second, fillType = fillType}) end, + } + S.setConfig(ADFieldSiloConfig.with(ADFieldSiloConfig.toggleStore(ADFieldSiloConfig.new(), "farm"), {enabled = true, maxVehicles = 1})) + local plan = S.planNow(adEnv) + check("RØG én afsendelse (x=1)", #started, 1) + check("RØG nærmeste tømmevogn", started[1] ~= nil and started[1].v.name, "Nær") + check("RØG fra marksilo-markør", started[1] ~= nil and started[1].first, "fs-load") + check("RØG til gårdlager-markør", started[1] ~= nil and started[1].second, "farm-unload") + check("RØG varen med", started[1] ~= nil and started[1].fillType, WHEAT) + check("RØG gårdlager er ikke marksilo", #plan.status, 1) + check("RØG vognen er lånt", S.isLoaned(near), true) + check("RØG x nået -> ingen flere", #S.planNow(adEnv).dispatch, 0) + S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false})) + check("RØG frakobling stopper AD", near.stopped, true) + check("RØG frakobling frigiver", S.isLoaned(near), false) +end + +print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) +os.exit(failures == 0 and 0 or 1)