From 2ff8e4630689d8cde32ddbfdb9e61ab790e905aa Mon Sep 17 00:00:00 2001 From: masterdraco Date: Wed, 23 Sep 2026 11:18:55 +0200 Subject: [PATCH] =?UTF-8?q?fix(gui):=20Start=20starter=20altid=20(k=C3=B8r?= =?UTF-8?q?ende=20AD-traktor=20stoppes=20og=20startes=20p=C3=A5=20run'et),?= =?UTF-8?q?=20Stop-knap=20kun=20for=20runs=20vi=20har=20startet,=20sprungn?= =?UTF-8?q?e=20traktorer=20med=20=C3=A5rsag=20i=20status/log,=20kapacitet?= =?UTF-8?q?=20<100=20l=20=3D=20ingen=20vogn,=20ASCII-tegn=20(v1.12.2.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adRunsController.lua | 51 ++++++++++++++----- FS25_ADSmartPickup/gui/SmartPickupFrame.lua | 34 +++++++++---- .../gui/SmartPickupStorageTab.lua | 4 +- FS25_ADSmartPickup/l10n/l10n_da.xml | 4 +- FS25_ADSmartPickup/l10n/l10n_en.xml | 4 +- FS25_ADSmartPickup/modDesc.xml | 2 +- tests/test_adRunsController.lua | 23 ++++++++- 7 files changed, 89 insertions(+), 33 deletions(-) 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 <en>AD Smart Pickup</en> 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