diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua
index 57efd45..d0c330a 100644
--- a/FS25_ADSmartPickup/adRunsController.lua
+++ b/FS25_ADSmartPickup/adRunsController.lua
@@ -10,6 +10,11 @@ ADRunsController.LOG_PREFIX = "ADSmartPickup: "
ADRunsController.MAX_NETWORK_DISTANCE = 30
ADRunsController.store = ADRuns.newStore()
+-- Trækkerens egne tanke (diesel/AdBlue) kan give en lille "kapacitet" uden vogn.
+ADRunsController.MIN_LOAD_CAPACITY = 100
+-- run-id -> true når run'et er startet fra fanen (Start/Stop-knappen følger DETTE, ikke om
+-- traktorerne tilfældigvis kører AutoDrive — en manuelt startet traktor må ikke vende knappen).
+ADRunsController.runningRuns = {}
local function copyList(list)
local copy = {}
@@ -261,7 +266,7 @@ local function getLoadCapacity(adEnv, vehicle)
return nil
end
local _, capacity = adEnv.AutoDrive.getAllFillLevels(trailers)
- return capacity ~= nil and capacity > 0 and capacity or nil
+ return capacity ~= nil and capacity >= ADRunsController.MIN_LOAD_CAPACITY and capacity or nil
end
local function intersects(listA, listB)
@@ -596,17 +601,28 @@ local function isVehicleActive(vehicle)
return stateModule ~= nil and stateModule.isActive ~= nil and stateModule:isActive()
end
+-- Som ADInputManager:input_start_stop: loops nulstilles og AD stoppes.
+local function stopVehicle(vehicle)
+ local stateModule = vehicle.ad.stateModule
+ if stateModule.setLoopsDone ~= nil then
+ stateModule:setLoopsDone(0)
+ end
+ vehicle:stopAutoDrive()
+end
+
-- Anvend run-tilstand OG start traktoren i ét pcall'et kald: kaster vehicle:startAutoDrive()
-- (fx AD's egen validering), skal den ene traktor fejle uden at hele startRun-loopet
-- afbrydes — ellers når de øvrige tildelte traktorer aldrig at blive startet.
-- Start præcis som AD's HUD (ADInputManager:input_start_stop): det er MODE'ens start(),
-- der opretter køreopgaverne og selv kalder startAutoDrive — startAutoDrive alene sætter
-- kun "aktiv", og traktoren bliver holdende.
+-- Kører traktoren AutoDrive i forvejen (startet manuelt eller i en gammel tilstand), stoppes
+-- den først, så run'ets tilstand og opgaver gælder fra nu.
local function applyAndStart(adEnv, run, vehicle, ingredients)
- applyRunToVehicle(adEnv, run, vehicle, ingredients)
if isVehicleActive(vehicle) then
- return
+ stopVehicle(vehicle)
end
+ applyRunToVehicle(adEnv, run, vehicle, ingredients)
local stateModule = vehicle.ad.stateModule
local farmId = ADSmartPickup.getFarmId(vehicle)
if stateModule.setActualFarmId ~= nil and farmId ~= nil then
@@ -624,7 +640,8 @@ local function applyAndStart(adEnv, run, vehicle, ingredients)
end
end
--- Pr. tildelt traktor: validér, skriv AD-tilstand, start. ✖-traktorer springes over.
+-- Pr. tildelt traktor: validér, skriv AD-tilstand, start. Afviste traktorer springes over
+-- og logges med årsag (samme nøgle som fanen viser).
function ADRunsController.startRun(adEnv, runId)
local run = ADRuns.getRun(ADRunsController.store, runId)
local results = {}
@@ -632,15 +649,20 @@ function ADRunsController.startRun(adEnv, runId)
return results
end
local vehiclesById = getFarmVehiclesById(adEnv)
+ local started = 0
for _, vehicleId in ipairs(run.vehicleIds) do
local vehicle = vehiclesById[vehicleId]
if vehicle ~= nil then
local validation = ADRunsController.validateVehicle(adEnv, run, vehicle)
local result = {vehicle = vehicle, ok = validation.ok, reason = validation.reason}
+ if not validation.ok then
+ Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' sprunget over (%s)", tostring(run.name), tostring(vehicle:getName()), tostring(validation.reason))
+ end
if validation.ok then
local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle)
local ok, err = pcall(applyAndStart, adEnv, run, vehicle, ingredients)
if ok then
+ started = started + 1
local target = run.kind == "outbound" and ("kilde " .. tostring(run.sourceWayPointIds[1])) or ("markør " .. tostring(run.targetWayPointId))
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' startet mod %s", tostring(run.name), tostring(vehicle:getName()), target)
else
@@ -651,16 +673,10 @@ function ADRunsController.startRun(adEnv, runId)
table.insert(results, result)
end
end
- return results
-end
-
--- Som ADInputManager:input_start_stop: loops nulstilles og AD stoppes.
-local function stopVehicle(vehicle)
- local stateModule = vehicle.ad.stateModule
- if stateModule.setLoopsDone ~= nil then
- stateModule:setLoopsDone(0)
+ if started > 0 then
+ ADRunsController.runningRuns[runId] = true
end
- vehicle:stopAutoDrive()
+ return results
end
-- En kørende traktor der fjernes fra sit run (eller hvis run slettes) kører run'ets job —
@@ -689,9 +705,12 @@ function ADRunsController.stopRun(adEnv, runId)
Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' stoppet", tostring(run.name), tostring(vehicle:getName()))
end
end
+ ADRunsController.runningRuns[runId] = nil
return stopped
end
+-- running = traktorer med AutoDrive aktiv (uanset hvem der startede dem); isRunning = run'et er
+-- startet fra fanen og mindst én traktor kører stadig (Start/Stop-knappen følger isRunning).
function ADRunsController.getRunStatus(adEnv, run)
local running = 0
local vehiclesById = getFarmVehiclesById(adEnv)
@@ -701,7 +720,11 @@ function ADRunsController.getRunStatus(adEnv, run)
running = running + 1
end
end
- return {running = running, total = #run.vehicleIds}
+ local isRunning = ADRunsController.runningRuns[run.id] == true and running > 0
+ if ADRunsController.runningRuns[run.id] and running == 0 then
+ ADRunsController.runningRuns[run.id] = nil
+ end
+ return {running = running, total = #run.vehicleIds, isRunning = isRunning}
end
-- ---- overblik ----
diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua
index a0b6d8f..6131b8e 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua
@@ -7,6 +7,7 @@ SmartPickupFrame = {}
SmartPickupFrame._mt = Class(SmartPickupFrame, TabbedMenuFrameElement)
SmartPickupFrame.REFRESH_INTERVAL_MS = 1000
+SmartPickupFrame.STATUS_OVERRIDE_TICKS = 6
SmartPickupFrame.LOG_PREFIX = "ADSmartPickup: "
SmartPickupFrame.STATE_OFF, SmartPickupFrame.STATE_ON = 1, 2
SmartPickupFrame.TAB_OVERVIEW, SmartPickupFrame.TAB_SUPPLY, SmartPickupFrame.TAB_OUTBOUND, SmartPickupFrame.TAB_STORAGE = 1, 2, 3, 4
@@ -475,9 +476,9 @@ function SmartPickupFrame:describeVehicle(adEnv, run, vehicle, isInRun)
end
end
if validation.ok then
- table.insert(parts, "✔")
+ table.insert(parts, "OK")
else
- table.insert(parts, "✖ " .. self.i18n:getText("spu_reason_" .. tostring(validation.reason)))
+ table.insert(parts, "X " .. self.i18n:getText("spu_reason_" .. tostring(validation.reason)))
end
return table.concat(parts, " — ")
end
@@ -687,8 +688,8 @@ function SmartPickupFrame:refreshLive()
self:refreshNeedsRows(adEnv, run)
end
local status = ADRunsController.getRunStatus(adEnv, run)
- isRunning = status.running > 0
- local text = isRunning and string.format(self.i18n:getText("spu_status_running"), status.running, status.total)
+ isRunning = status.isRunning == true
+ local text = status.running > 0 and string.format(self.i18n:getText("spu_status_running"), status.running, status.total)
or string.format(self.i18n:getText("spu_status_stopped"), status.total)
self:setStatusText(self.statusOverride or text)
for option, data in pairs(self.rowData) do
@@ -701,9 +702,12 @@ function SmartPickupFrame:refreshLive()
else
self:setStatusText(self.statusOverride or self.i18n:getText("spu_noRuns"))
end
- -- Engangsbesked ("Startet 2, sprunget over 1", "flyttet fra ..."): vist nu, ryddet her,
- -- så næste opdatering om et sekund viser den levende status igen.
- self.statusOverride = nil
+ -- Engangsbesked ("Startet 2, sprunget over 1: …", "flyttet fra ..."): står i
+ -- STATUS_OVERRIDE_TICKS opdateringer (sekunder), så man når at læse hvorfor.
+ self.statusOverrideTicks = (self.statusOverrideTicks or 0) - 1
+ if self.statusOverrideTicks <= 0 then
+ self.statusOverride = nil
+ end
self.startButtonInfo.text = self.i18n:getText(isRunning and "spu_button_stop" or "spu_button_start")
self.startButtonInfo.disabled = run == nil
self.renameButtonInfo.disabled = run == nil
@@ -758,6 +762,7 @@ function SmartPickupFrame:onClickVehicle(state, element)
if previousRunId ~= nil then
local previous = ADRuns.getRun(ADRunsController.store, previousRunId)
frame.statusOverride = string.format(frame.i18n:getText("spu_movedFrom"), previous ~= nil and previous.name or tostring(previousRunId))
+ frame.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
end
else
ADRunsController.unassignVehicle(data.vehicle)
@@ -772,15 +777,24 @@ function SmartPickupFrame:onClickStartStop()
self:safe(function(frame)
local adEnv, run = frame.getAdEnv(), frame:getRun()
if adEnv == nil or run == nil then return end
- if ADRunsController.getRunStatus(adEnv, run).running > 0 then
+ if ADRunsController.getRunStatus(adEnv, run).isRunning then
ADRunsController.stopRun(adEnv, run.id)
frame.statusOverride = nil
else
- local started, skipped = 0, 0
+ local started, skipped, reasons = 0, 0, {}
for _, result in ipairs(ADRunsController.startRun(adEnv, run.id)) do
- if result.ok then started = started + 1 else skipped = skipped + 1 end
+ if result.ok then
+ started = started + 1
+ else
+ skipped = skipped + 1
+ table.insert(reasons, string.format("%s: %s", tostring(result.vehicle:getName()), frame.i18n:getText("spu_reason_" .. tostring(result.reason))))
+ end
end
frame.statusOverride = string.format(frame.i18n:getText("spu_status_started"), started, skipped)
+ if #reasons > 0 then
+ frame.statusOverride = frame.statusOverride .. " — " .. table.concat(reasons, "; ")
+ end
+ frame.statusOverrideTicks = SmartPickupFrame.STATUS_OVERRIDE_TICKS
end
ADRunsController.save()
frame:requestRebuild()
diff --git a/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua b/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua
index 9f6e243..02472f3 100644
--- a/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua
+++ b/FS25_ADSmartPickup/gui/SmartPickupStorageTab.lua
@@ -3,7 +3,7 @@
-- Kun bygninger med indhold vises; teksterne opdateres hvert sekund uden genopbygning.
-- Funktionerne lægges på SmartPickupFrame (indlæses efter SmartPickupFrame.lua).
--- "Sugar mill (fabrik): Sukker 150.0k/180.0k ▸ · Roer 356.8k/360.0k" (▸ = output)
+-- "Sugar mill (fabrik): *Sukker 150.0k/180.0k · Roer 356.8k/360.0k" (* = output)
function SmartPickupFrame:describeInventory(entry)
local parts = {}
for _, item in ipairs(entry.entries) do
@@ -13,7 +13,7 @@ function SmartPickupFrame:describeInventory(entry)
else
amount = string.format("%s/%s", SmartPickupFrame.formatCompact(item.level or 0), SmartPickupFrame.formatCompact(item.capacity or 0))
end
- table.insert(parts, string.format("%s %s%s", tostring(item.title), amount, item.isOutput and " ▸" or ""))
+ table.insert(parts, string.format("%s%s %s", item.isOutput and "*" or "", tostring(item.title), amount))
end
local kindText = self.i18n:getText("spu_source_kind_" .. tostring(entry.kind))
return string.format("%s (%s): %s", tostring(entry.name), kindText, table.concat(parts, " · "))
diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml
index d58b915..7a13968 100644
--- a/FS25_ADSmartPickup/l10n/l10n_da.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_da.xml
@@ -61,7 +61,7 @@
-
+
@@ -95,7 +95,7 @@
-
+
diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml
index e972231..f93a22e 100644
--- a/FS25_ADSmartPickup/l10n/l10n_en.xml
+++ b/FS25_ADSmartPickup/l10n/l10n_en.xml
@@ -61,7 +61,7 @@
-
+
@@ -95,7 +95,7 @@
-
+
diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml
index b4ef959..750d19f 100644
--- a/FS25_ADSmartPickup/modDesc.xml
+++ b/FS25_ADSmartPickup/modDesc.xml
@@ -1,7 +1,7 @@
masterdraco
- 1.12.1.0
+ 1.12.2.0
AD Smart Pickup
diff --git a/tests/test_adRunsController.lua b/tests/test_adRunsController.lua
index 22aaa66..705c30e 100644
--- a/tests/test_adRunsController.lua
+++ b/tests/test_adRunsController.lua
@@ -225,10 +225,29 @@ 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
+-- C7: Start på traktor der allerede kører AD (fx startet manuelt) -> stoppes og startes på run'et
fastracA.ad.stateModule.active = true
+fastracA.stopped = 0
ADRunsController.startRun(adEnv, cow.id)
-check("C7 ikke dobbelt-startet", fastracA.started, 1)
+check("C7 kørende traktor stoppes først", fastracA.stopped, 1)
+check("C7 og startes på run'et", fastracA.started, 2)
+check("C7 run markeret som kørende", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, true)
+ADRunsController.stopRun(adEnv, cow.id)
+check("C7 stop rydder markeringen", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, false)
+-- en traktor der kører AD uden at run'et er startet af os, gør IKKE run'et "kørende"
+fastracA.ad.stateModule.active = true
+check("C7 manuel AD tæller ikke som kørende run", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).isRunning, false)
+check("C7 men aktive tæller stadig", ADRunsController.getRunStatus(adEnv, ADRuns.getRun(ADRunsController.store, cow.id)).running, 1)
+fastracA.ad.stateModule.active = false
+-- sprungne traktorer logges med årsag
+logLines = {}
+ADRunsController.startRun(adEnv, cow.id)
+local skippedLogged = false
+for _, line in ipairs(logLines) do if line:find("sprunget over", 1, true) and line:find("noTrailer", 1, true) then skippedLogged = true end end
+check("C7 sprunget over logges med årsag", skippedLogged, true)
+-- kapacitet under 100 l (kun trækkerens egne tanke) = ingen vogn
+local tinyCapacity = makeVehicle("vehicleTiny", "T255", {STRAW, TMR}, 1, 100, 0)
+check("C7 1 l kapacitet -> noTrailer", ADRunsController.validateVehicle(adEnv, ADRuns.getRun(ADRunsController.store, cow.id), tinyCapacity).reason, "noTrailer")
-- C8: mål-markører (kun stalde og fabrikker — siloer tager alt og er ikke mål; stalde først,
-- så fabrikker, så navn) og Wait-puljer til dropdowns