fix(marksilo): review — flerværdi-valg som MultiTextOption, lagerreservation på tværs af runder, vagt kun før start, rutetjek + straks-fejl pauses, rotation fra på lånte vogne, kræver parkering, flow-siloer udelades, slettede vogne og små rester håndteres

This commit is contained in:
2026-09-27 10:50:48 +02:00
parent 31c993c6eb
commit fca26f5b4d
8 changed files with 176 additions and 37 deletions
@@ -61,6 +61,11 @@ function ADFieldSiloPlanner.plan(input)
for _, loan in ipairs(input.loans or {}) do for _, loan in ipairs(input.loans or {}) do
local k = key(loan.siloId, loan.fillType) local k = key(loan.siloId, loan.fillType)
onWay[k] = (onWay[k] or 0) + (loan.liters or 0) onWay[k] = (onWay[k] or 0) + (loan.liters or 0)
-- læs på vej til et lager optager pladsen, til de er aflæsset (også i senere runder)
if loan.storeId ~= nil then
local reserveKey = loan.storeId .. ":" .. tostring(loan.fillType)
reserved[reserveKey] = (reserved[reserveKey] or 0) + (loan.liters or 0)
end
end end
local status, tasks = {}, {} local status, tasks = {}, {}
for _, silo in ipairs(input.silos or {}) do for _, silo in ipairs(input.silos or {}) do
+78 -25
View File
@@ -9,6 +9,8 @@ ADFieldSilos.PLAN_INTERVAL_MS = 60000
ADFieldSilos.CHECK_INTERVAL_MS = 2000 ADFieldSilos.CHECK_INTERVAL_MS = 2000
ADFieldSilos.STUCK_MS = 120000 ADFieldSilos.STUCK_MS = 120000
ADFieldSilos.PAUSE_MS = 600000 ADFieldSilos.PAUSE_MS = 600000
ADFieldSilos.FAIL_MS = 30000 -- AD slukket inden for 30 s uden fremdrift = turen kunne ikke køres
ADFieldSilos.CARGO_MIN_LITERS = 100 -- rest under dette tæller ikke som last
ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive
ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml" ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml"
ADFieldSilos.ROOT = "ADSmartPickupFieldSilos" ADFieldSilos.ROOT = "ADSmartPickupFieldSilos"
@@ -22,13 +24,18 @@ local planTimerMs, checkTimerMs, nowMs = ADFieldSilos.PLAN_INTERVAL_MS, 0, 0
-- ---- ren frigivelsesregel ---- -- ---- ren frigivelsesregel ----
-- vehicleState = {adActive, controlled, fill, moving} -- vehicleState = {adActive, controlled, fill, moving}. Fremdrift = kører, eller fyldningen ændrer sig (læsser
-- eller aflæsser). Vagten gælder kun før turen er kommet i gang — kø ved siloen/lageret er normalt.
function ADFieldSilos.releaseReason(loan, vehicleState, now) function ADFieldSilos.releaseReason(loan, vehicleState, now)
if vehicleState.controlled then return "player" end if vehicleState.controlled then return "player" end
if not vehicleState.adActive then return "done" end local progressing = vehicleState.moving or (vehicleState.fill or 0) ~= (loan.lastFill or 0)
if vehicleState.moving then return nil end local hasProgressed = loan.hasProgressed or progressing
if (vehicleState.fill or 0) > (loan.lastFill or 0) then return nil end local age = now - (loan.startedMs or 0)
if now - (loan.lastProgressMs or loan.startedMs or 0) > ADFieldSilos.STUCK_MS then return "stuck" end if not vehicleState.adActive then
return (not hasProgressed and age < ADFieldSilos.FAIL_MS) and "failed" or "done"
end
if hasProgressed then return nil end
if age > ADFieldSilos.STUCK_MS then return "stuck" end
return nil return nil
end end
@@ -91,6 +98,18 @@ local function firstMarker(building, role)
return markers[1] ~= nil and markers[1].id or nil return markers[1] ~= nil and markers[1].id or nil
end end
-- Bygninger der er kilde, mål eller lager i et flow: dem tømmer marksilo-flowet ikke (de hører til den drift).
local function runBuildingIds()
local ids = {}
local store = ADRunsController ~= nil and ADRunsController.store or nil
for _, run in ipairs(store ~= nil and store.runs or {}) do
for _, list in ipairs({run.sourceBuildingIds or {}, run.targetBuildingIds or {}, run.storeBuildingIds or {}}) do
for _, id in ipairs(list) do ids[id] = true end
end
end
return ids
end
-- Gårdlagre fra opsætningen + marksiloer (alle markers marksiloer, uden dubletter og uden gårdlagre). -- Gårdlagre fra opsætningen + marksiloer (alle markers marksiloer, uden dubletter og uden gårdlagre).
function ADFieldSilos.collectBuildings(adEnv) function ADFieldSilos.collectBuildings(adEnv)
local fid = farmId() local fid = farmId()
@@ -103,7 +122,7 @@ function ADFieldSilos.collectBuildings(adEnv)
x = x, z = z, free = siloFree(building.placeable)}) x = x, z = z, free = siloFree(building.placeable)})
end end
end end
local silos, seen = {}, {} local silos, seen = {}, runBuildingIds()
for _, info in ipairs(ADFields.list()) do for _, info in ipairs(ADFields.list()) do
for _, building in ipairs(ADFields.fieldSilos(adEnv, fid, info)) 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 if not seen[building.id] and not ADFieldSiloConfig.hasStore(ADFieldSilos.config, building.id) then
@@ -126,7 +145,7 @@ local function trailerInfo(adEnv, vehicle)
for fillType in pairs(object.getFillUnitSupportedFillTypes ~= nil and object:getFillUnitSupportedFillTypes(index) or {}) do for fillType in pairs(object.getFillUnitSupportedFillTypes ~= nil and object:getFillUnitSupportedFillTypes(index) or {}) do
carries[fillType] = true carries[fillType] = true
end end
if (object:getFillUnitFillLevel(index) or 0) > 0 then cargo = object:getFillUnitFillType(index) end if (object:getFillUnitFillLevel(index) or 0) >= ADFieldSilos.CARGO_MIN_LITERS then cargo = object:getFillUnitFillType(index) end
end end
end end
return capacity, carries, cargo return capacity, carries, cargo
@@ -140,7 +159,9 @@ end
function ADFieldSilos.collectVehicles(adEnv) function ADFieldSilos.collectVehicles(adEnv)
local vehicles, byId = {}, {} local vehicles, byId = {}, {}
for _, rig in ipairs(ADFieldWork.getRigs(adEnv)) do for _, rig in ipairs(ADFieldWork.getRigs(adEnv)) do
if rig.role == "unloader" and rig.enabled and not rig.busy and not rig.controlled then -- uden AD-parkeringspunkt bliver vognen holdende på lagerets markør og spærrer for de næste
local canPark = ADFieldWork.parkMarkerId == nil or ADFieldWork.parkMarkerId(adEnv, rig.vehicle) ~= nil
if rig.role == "unloader" and rig.enabled and not rig.busy and not rig.controlled and canPark then
local capacity, carries, cargo = trailerInfo(adEnv, rig.vehicle) 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}) table.insert(vehicles, {id = rig.id, x = rig.x, z = rig.z, capacity = capacity, carries = carries, cargo = cargo})
byId[rig.id] = rig.vehicle byId[rig.id] = rig.vehicle
@@ -166,10 +187,14 @@ local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
module.loopCounter = 1 module.loopCounter = 1
if module.setLoopsDone ~= nil then module:setLoopsDone(0) end if module.setLoopsDone ~= nil then module:setLoopsDone(0) end
ADFieldWork.enableParkAtJobFinished(vehicle) ADFieldWork.enableParkAtJobFinished(vehicle)
-- rotation fra: ellers kan modens egne pickup-hooks sende vognen til en anden silo i mappen
if ADRunsController ~= nil and ADRunsController.setSetting ~= nil then
ADRunsController.setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_NONE)
end
module:raiseDirtyFlag() module:raiseDirtyFlag()
ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_PICKUPANDDELIVER, order.siloMarkerId, order.storeMarkerId, order.fillType) ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_PICKUPANDDELIVER, order.siloMarkerId, order.storeMarkerId, order.fillType)
local silo, store = silosById[order.siloId], storesById[order.storeId] 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, loans[vehicle] = {siloId = order.siloId, storeId = order.storeId, siloName = silo ~= nil and silo.name or order.siloId, fillType = order.fillType,
liters = order.liters, startedMs = nowMs, lastProgressMs = nowMs, lastFill = fillLevel(adEnv, vehicle)} 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)), 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), math.floor(order.liters), fillTypeName(order.fillType), tostring(silo ~= nil and silo.name or order.siloId),
@@ -180,12 +205,13 @@ local function release(adEnv, vehicle, reason)
local loan = loans[vehicle] local loan = loans[vehicle]
loans[vehicle] = nil loans[vehicle] = nil
if loan == nil then return end if loan == nil then return end
if reason == "stuck" then if reason == "stuck" or reason == "failed" then
local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType) local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType)
paused[k] = nowMs + ADFieldSilos.PAUSE_MS paused[k] = nowMs + ADFieldSilos.PAUSE_MS
pcall(vehicle.stopAutoDrive, vehicle) 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", local why = reason == "stuck" and "kom ikke i gang på 2 min — stoppet" or "AutoDrive stoppede turen med det samme (rute/læssested?)"
tostring(ADRuns.vehicleLabel(vehicle)), tostring(loan.siloName), fillTypeName(loan.fillType)) Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' ved %s (%s): %s; siloen springes over i 10 min",
tostring(ADRuns.vehicleLabel(vehicle)), tostring(loan.siloName), fillTypeName(loan.fillType), why)
elseif reason == "stopped" then elseif reason == "stopped" then
pcall(vehicle.stopAutoDrive, vehicle) pcall(vehicle.stopAutoDrive, vehicle)
end end
@@ -202,18 +228,28 @@ local function checkLoans(adEnv)
local list = {} local list = {}
for vehicle in pairs(loans) do table.insert(list, vehicle) end for vehicle in pairs(loans) do table.insert(list, vehicle) end
for _, vehicle in ipairs(list) do for _, vehicle in ipairs(list) do
local loan = loans[vehicle] if vehicle.isDeleted then
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil loans[vehicle] = nil -- solgt/slettet mens den var lånt
local fill = fillLevel(adEnv, vehicle) else
local vehicleState = { local ok, err = pcall(function()
adActive = stateModule ~= nil and stateModule:isActive(), local loan = loans[vehicle]
controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false, local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
fill = fill, moving = (vehicle.lastSpeedReal or 0) >= ADFieldSilos.MOVING_SPEED, local fill = fillLevel(adEnv, vehicle)
} local vehicleState = {
if vehicleState.moving or fill > (loan.lastFill or 0) then loan.lastProgressMs = nowMs end adActive = stateModule ~= nil and stateModule:isActive(),
loan.lastFill = math.max(loan.lastFill or 0, fill) controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false,
local reason = ADFieldSilos.releaseReason(loan, vehicleState, nowMs) fill = fill, moving = (vehicle.lastSpeedReal or 0) >= ADFieldSilos.MOVING_SPEED,
if reason ~= nil then release(adEnv, vehicle, reason) end }
local reason = ADFieldSilos.releaseReason(loan, vehicleState, nowMs)
if vehicleState.moving or fill ~= (loan.lastFill or 0) then loan.hasProgressed = true end
loan.lastFill = fill
if reason ~= nil then release(adEnv, vehicle, reason) end
end)
if not ok then
loans[vehicle] = nil
Logging.warning(ADFieldSilos.LOG_PREFIX .. "lån opgivet (fejl): %s", tostring(err))
end
end
end end
end end
@@ -227,10 +263,24 @@ end
local function loanList() local function loanList()
local list = {} local list = {}
for _, loan in pairs(loans) do table.insert(list, {siloId = loan.siloId, fillType = loan.fillType, liters = loan.liters}) end for _, loan in pairs(loans) do table.insert(list, {siloId = loan.siloId, storeId = loan.storeId, fillType = loan.fillType, liters = loan.liters}) end
return list return list
end end
-- Rute fra vognen til siloen og videre til lageret (samme opslag som Wait-punkterne); én advarsel pr. kombination.
local warnedRoute = {}
local function hasRoutes(adEnv, vehicle, order)
if ADUnloadWait == nil or ADUnloadWait.hasRouteTo == nil then return true end
local ok = ADUnloadWait.hasRouteTo(adEnv, vehicle, order.siloMarkerId) and ADUnloadWait.hasRouteTo(adEnv, vehicle, order.storeMarkerId)
local warnKey = tostring(order.vehicleId) .. ">" .. tostring(order.siloMarkerId)
if not ok and not warnedRoute[warnKey] then
warnedRoute[warnKey] = true
Logging.warning(ADFieldSilos.LOG_PREFIX .. "ingen AutoDrive-rute for '%s' til marksilo-markør %s eller gårdlager-markør %s",
tostring(ADRuns.vehicleLabel(vehicle)), tostring(order.siloMarkerId), tostring(order.storeMarkerId))
end
return ok
end
function ADFieldSilos.planNow(adEnv) function ADFieldSilos.planNow(adEnv)
local cfg = ADFieldSilos.config local cfg = ADFieldSilos.config
local silos, stores = ADFieldSilos.collectBuildings(adEnv) local silos, stores = ADFieldSilos.collectBuildings(adEnv)
@@ -244,6 +294,9 @@ function ADFieldSilos.planNow(adEnv)
for _, store in ipairs(stores) do storesById[store.id] = store end for _, store in ipairs(stores) do storesById[store.id] = store end
for _, order in ipairs(plan.dispatch) do for _, order in ipairs(plan.dispatch) do
local vehicle = vehiclesById[order.vehicleId] local vehicle = vehiclesById[order.vehicleId]
if vehicle ~= nil and not hasRoutes(adEnv, vehicle, order) then
vehicle = nil
end
if vehicle ~= nil then if vehicle ~= nil then
local ok, err = pcall(dispatchOne, adEnv, order, vehicle, silosById, storesById) local ok, err = pcall(dispatchOne, adEnv, order, vehicle, silosById, storesById)
if not ok then if not ok then
+1
View File
@@ -99,6 +99,7 @@ local function parkMarkerId(adEnv, vehicle)
local marker = adEnv.ADGraphManager:getMapMarkerById(index) local marker = adEnv.ADGraphManager:getMapMarkerById(index)
return marker ~= nil and marker.id or nil return marker ~= nil and marker.id or nil
end end
ADFieldWork.parkMarkerId = parkMarkerId
local function restLoad(adEnv, vehicle) local function restLoad(adEnv, vehicle)
local ok, level = pcall(function() local ok, level = pcall(function()
+1
View File
@@ -911,6 +911,7 @@ local function setSetting(adEnv, vehicle, settingName, value)
end end
end end
end end
ADRunsController.setSetting = setSetting
-- Nærmeste markør med en læssestation: kun en gyldig start — moden vælger den reelle kilde pr. tur. -- 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 function findNearestLoadMarker(adEnv, vehicle, farmId)
@@ -21,14 +21,24 @@ local function indexOf(list, value)
return 1 return 1
end end
-- Valg med flere værdier: productPrefab (MultiTextOption). ingredientPrefab er en BinaryOption med kun to sider.
function SmartPickupFrame:addFieldSiloChoiceRow(kind, title, texts, state)
local element = self:addRow(self.productPrefab, {kind = kind})
local option = element:getDescendantByName("option")
option:setTexts(texts)
option:setState(state, false)
element:getDescendantByName("title"):setText(title)
local tooltip = option:getDescendantByName("ignore")
if tooltip ~= nil then tooltip:setText("") end
return option
end
function SmartPickupFrame:fillFieldSiloRows(adEnv) function SmartPickupFrame:fillFieldSiloRows(adEnv)
local i18n, cfg = self.i18n, ADFieldSilos.config local i18n, cfg = self.i18n, ADFieldSilos.config
local onOff = {i18n:getText("ui_no"), i18n:getText("ui_yes")} local onOff = {i18n:getText("ui_no"), i18n:getText("ui_yes")}
self:addToggleRow("fsEnabled", i18n:getText("spu_fs_enabled"), onOff, cfg.enabled) self:addToggleRow("fsEnabled", i18n:getText("spu_fs_enabled"), onOff, cfg.enabled)
local maxOption = self:addToggleRow("fsMaxVehicles", i18n:getText("spu_fs_maxVehicles"), numberTexts(1, ADFieldSiloConfig.MAX_VEHICLES), false) self:addFieldSiloChoiceRow("fsMaxVehicles", i18n:getText("spu_fs_maxVehicles"), numberTexts(1, ADFieldSiloConfig.MAX_VEHICLES), cfg.maxVehicles)
maxOption:setState(cfg.maxVehicles, false) self:addFieldSiloChoiceRow("fsMinLiters", i18n:getText("spu_fs_minLiters"), literTexts(), indexOf(ADFieldSiloConfig.MIN_LITERS_CHOICES, cfg.minLiters))
local minOption = self:addToggleRow("fsMinLiters", i18n:getText("spu_fs_minLiters"), literTexts(), false)
minOption:setState(indexOf(ADFieldSiloConfig.MIN_LITERS_CHOICES, cfg.minLiters), false)
local plan = ADFieldSilos.lastPlan local plan = ADFieldSilos.lastPlan
local waiting = 0 local waiting = 0
@@ -119,6 +119,9 @@ function SmartPickupFrame:onClickProduct(state, element)
if clicked ~= nil and clicked.kind == "storageBuilding" then if clicked ~= nil and clicked.kind == "storageBuilding" then
return self:safe(function(frame) frame:onClickStorageBuilding(state) end) return self:safe(function(frame) frame:onClickStorageBuilding(state) end)
end end
if clicked ~= nil and SmartPickupFrame.FIELDSILO_ROW_KINDS ~= nil and SmartPickupFrame.FIELDSILO_ROW_KINDS[clicked.kind] then
return self:onClickFieldSiloRow(state, clicked)
end
if clicked ~= nil and SmartPickupFrame.FIELD_ROW_KINDS ~= nil and SmartPickupFrame.FIELD_ROW_KINDS[clicked.kind] then if clicked ~= nil and SmartPickupFrame.FIELD_ROW_KINDS ~= nil and SmartPickupFrame.FIELD_ROW_KINDS[clicked.kind] then
return self:onClickFieldRow(state, clicked) return self:onClickFieldRow(state, clicked)
end end
+9
View File
@@ -75,5 +75,14 @@ local input = base({silos = {silo("A", 500, {[WHEAT] = 90000})}, vehicles = {veh
P.plan(input) P.plan(input)
check("input.stores urørt", input.stores[1].free[WHEAT], 1000000) check("input.stores urørt", input.stores[1].free[WHEAT], 1000000)
-- I1: lån på vej til et lager reserverer pladsen på tværs af planlægningsrunder
r = P.plan(base({stores = {store("s", 400, {[WHEAT] = 30000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
loans = {{siloId = "A", fillType = WHEAT, liters = 30000, storeId = "s"}}, vehicles = {vehicle("v1", 400, 50000)}}))
check("I1 lager fyldt af lån på vej -> ingen ny vogn dertil", #r.dispatch, 0)
r = P.plan(base({stores = {store("s", 400, {[WHEAT] = 30000}), store("t", 900, {[WHEAT] = 500000})}, silos = {silo("A", 500, {[WHEAT] = 90000})},
loans = {{siloId = "A", fillType = WHEAT, liters = 30000, storeId = "s"}}, vehicles = {vehicle("v1", 400, 50000)}}))
check("I1 næste vogn til det andet lager", r.dispatch[1] ~= nil and r.dispatch[1].storeId, "t")
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))
os.exit(failures == 0 and 0 or 1) os.exit(failures == 0 and 0 or 1)
+65 -8
View File
@@ -12,13 +12,20 @@ local function check(name, actual, expected)
end end
local loan = {startedMs = 0, lastProgressMs = 0, lastFill = 0} 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("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("AD slukket efter 40 s -> done", S.releaseReason(loan, {adActive = false, controlled = false, fill = 0}, 40000), "done")
check("spiller i vognen -> player", S.releaseReason(loan, {adActive = true, controlled = true, fill = 0}, 5000), "player") 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("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) 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} 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) check("fyldning steg for nylig -> behold", S.releaseReason(loading, {adActive = true, controlled = false, fill = 20000, moving = false}, 125000), nil)
-- I2/I3: vagten gælder kun før turen er kommet i gang; AD der slukker straks uden fremdrift = fejl
local going = {startedMs = 0, lastFill = 30000, hasProgressed = true}
check("I2 i gang, står i kø 500 s -> behold", S.releaseReason(going, {adActive = true, controlled = false, fill = 30000, moving = false}, 500000), nil)
check("I2 aflæsning (fyld falder) er fremdrift", S.releaseReason({startedMs = 0, lastFill = 30000}, {adActive = true, controlled = false, fill = 20000, moving = false}, 500000), nil)
check("I3 AD slukket efter 10 s uden fremdrift -> failed", S.releaseReason({startedMs = 0, lastFill = 0}, {adActive = false, controlled = false, fill = 0}, 10000), "failed")
check("I3 AD slukket efter 10 s med fremdrift -> done", S.releaseReason({startedMs = 0, lastFill = 0, hasProgressed = true}, {adActive = false, controlled = false, fill = 0}, 10000), "done")
-- Røgtest af spil-laget med mocks: planNow låner nærmeste tømmevogn, isLoaned = optaget, -- 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). -- frakobling (setConfig enabled=false) stopper AD og frigiver (Review Focus).
do do
@@ -34,7 +41,9 @@ do
ADBuildings = {list = function() return {fieldSilo, farm} end, ADBuildings = {list = function() return {fieldSilo, farm} end,
getMarkers = function(b, role) return {{id = b.id .. "-" .. role}} end} getMarkers = function(b, role) return {{id = b.id .. "-" .. role}} end}
ADFields = {list = function() return {{id = 1}} end, fieldSilos = function() return {fieldSilo, farm} end} ADFields = {list = function() return {{id = 1}} end, fieldSilos = function() return {fieldSilo, farm} end}
local started = {} local started, settingsSet, noPark, noRoute, residue = {}, {}, {}, {}, 0
ADRunsController = {store = {runs = {}}, setSetting = function(_, v, name, value) settingsSet[v] = settingsSet[v] or {}; settingsSet[v][name] = value end}
ADUnloadWait = {hasRouteTo = function(_, _, id) return not noRoute[id] end}
local function makeVehicle(name, x) local function makeVehicle(name, x)
local module = {active = false} local module = {active = false}
function module:setMode() end function module:setMode() end
@@ -48,18 +57,23 @@ do
stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = true end} stopAutoDrive = function(self) self.ad.stateModule.active = false; self.stopped = true end}
end end
local near, far = makeVehicle("Nær", 900), makeVehicle("Fjern", 5000) local near, far = makeVehicle("Nær", 900), makeVehicle("Fjern", 5000)
local adEnv = {AutoDrive = {MODE_PICKUPANDDELIVER = 2, local adEnv = {AutoDrive = {MODE_PICKUPANDDELIVER = 2, RT_NONE = 0,
getAllDischargeableUnits = function() return {{fillUnitIndex = 1, object = { getAllDischargeableUnits = function() return {{fillUnitIndex = 1, object = {
getFillUnitCapacity = function() return 40000 end, getFillUnitCapacity = function() return 40000 end,
getFillUnitSupportedFillTypes = function() return {[WHEAT] = true} end, getFillUnitSupportedFillTypes = function() return {[WHEAT] = true} end,
getFillUnitFillLevel = function() return 0 end, getFillUnitFillType = function() return WHEAT end}}} end, getFillUnitFillLevel = function() return residue end, getFillUnitFillType = function() return WHEAT end}}} end,
getAllUnits = function() return {} end, getAllFillLevels = function() return 0 end}} getAllUnits = function() return {} end, getAllFillLevels = function() return 0 end}}
ADFieldWork = { ADFieldWork = {
getRigs = function() return { getRigs = function()
{id = "n", vehicle = near, role = "unloader", enabled = true, busy = S.isLoaned(near), controlled = false, x = 900, z = 0}, local rigs = {}
{id = "f", vehicle = far, role = "unloader", enabled = true, busy = S.isLoaned(far), controlled = false, x = 5000, z = 0}, -- som spillets vehicleSystem: slettede køretøjer er ikke med
{id = "h", vehicle = far, role = "harvester", enabled = true, busy = false, controlled = false, x = 1000, z = 0}} end, if not near.isDeleted then table.insert(rigs, {id = "n", vehicle = near, role = "unloader", enabled = true, busy = S.isLoaned(near), controlled = false, x = 900, z = 0}) end
if not far.isDeleted then table.insert(rigs, {id = "f", vehicle = far, role = "unloader", enabled = true, busy = S.isLoaned(far), controlled = false, x = 5000, z = 0}) end
table.insert(rigs, {id = "h", vehicle = far, role = "harvester", enabled = true, busy = false, controlled = false, x = 1000, z = 0})
return rigs
end,
enableParkAtJobFinished = function() end, enableParkAtJobFinished = function() end,
parkMarkerId = function(_, v) if noPark[v] then return nil end return 777 end,
startAd = function(_, v, _, first, second, fillType) v.ad.stateModule.active = true; table.insert(started, {v = v, first = first, second = second, fillType = fillType}) 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})) S.setConfig(ADFieldSiloConfig.with(ADFieldSiloConfig.toggleStore(ADFieldSiloConfig.new(), "farm"), {enabled = true, maxVehicles = 1}))
@@ -72,9 +86,52 @@ do
check("RØG gårdlager er ikke marksilo", #plan.status, 1) 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 vognen er lånt", S.isLoaned(near), true)
check("RØG x nået -> ingen flere", #S.planNow(adEnv).dispatch, 0) check("RØG x nået -> ingen flere", #S.planNow(adEnv).dispatch, 0)
check("I4 rotation slået fra på lånt vogn", settingsSet[near] ~= nil and settingsSet[near].rotateTargets, 0)
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false})) S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
check("RØG frakobling stopper AD", near.stopped, true) check("RØG frakobling stopper AD", near.stopped, true)
check("RØG frakobling frigiver", S.isLoaned(near), false) check("RØG frakobling frigiver", S.isLoaned(near), false)
-- I5: vogn uden parkeringspunkt lånes ikke (ville blive holdende på lagerets markør)
started = {}
noPark[near] = true
near.stopped = nil
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = true}))
S.planNow(adEnv)
check("I5 uden parkering -> fjern vogn med parkering", started[1] ~= nil and started[1].v.name, "Fjern")
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
noPark[near] = nil
-- I3: ingen rute til siloen -> ingen afsendelse
started = {}
noRoute["fs-load"] = true
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = true}))
S.planNow(adEnv)
check("I3 ingen rute -> ingen afsendelse", #started, 0)
noRoute["fs-load"] = nil
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
-- I6: silo der er kilde/mål i et flow er ikke marksilo
ADRunsController.store.runs = {{kind = "supply", sourceBuildingIds = {}, targetBuildingIds = {"fs"}, storeBuildingIds = {}}}
local silos = S.collectBuildings(adEnv)
check("I6 silo i et flow tømmes ikke", #silos, 0)
ADRunsController.store.runs = {}
-- M5: rest under 100 l tæller ikke som last
residue = 50
local vehicles = S.collectVehicles(adEnv)
check("M5 rest på 50 l er ikke last", vehicles[1].cargo, nil)
residue = 0
-- M4: slettet lånt vogn fjernes uden at vælte tick
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = true}))
started = {}
S.planNow(adEnv)
local loaned = started[1] ~= nil and started[1].v or nil
if loaned ~= nil then loaned.isDeleted = true end
local okTick = pcall(S.tick, adEnv, 2000)
check("M4 tick tåler slettet vogn", okTick, true)
check("M4 slettet vogn frigivet", loaned ~= nil and S.isLoaned(loaned), false)
S.setConfig(ADFieldSiloConfig.with(S.config, {enabled = false}))
end end
print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL"))