From 9c1c87b8dd5fd6e8b8cb7ff3961cfd447211c1e1 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Sun, 27 Sep 2026 15:20:41 +0200 Subject: [PATCH] =?UTF-8?q?feat(oversigt):=20'Stop=20alt=20og=20park=C3=A9?= =?UTF-8?q?r'=20=E2=80=94=20flows,=20markarbejde=20og=20marksilo-l=C3=A5n?= =?UTF-8?q?=20stoppes,=20k=C3=B8ret=C3=B8jer=20til=20AD-parkering=20(manue?= =?UTF-8?q?lle=20h=C3=B8stere=20n=C3=A6vnes),=20pause=20der=20gemmes;=20'S?= =?UTF-8?q?tart=20alle'=20oph=C3=A6ver=20pausen=20og=20starter=20traktorer?= =?UTF-8?q?ne=20=C3=A9n=20ad=20gangen=20hvert=2010.=20sekund=20(v1.42.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adFieldJobs.lua | 10 +++++ FS25_ADSmartPickup/adFieldSilos.lua | 3 +- FS25_ADSmartPickup/adFieldStorage.lua | 4 +- FS25_ADSmartPickup/adFieldWork.lua | 15 +++++++ FS25_ADSmartPickup/adRunsController.lua | 50 +++++++++++++++++++++ FS25_ADSmartPickup/adSmartPickup.lua | 3 ++ FS25_ADSmartPickup/gui/SmartPickupFrame.lua | 41 ++++++++++++++--- FS25_ADSmartPickup/l10n/l10n_da.xml | 7 +++ FS25_ADSmartPickup/l10n/l10n_en.xml | 7 +++ FS25_ADSmartPickup/modDesc.xml | 2 +- tests/test_adFieldStorage.lua | 8 ++++ tests/test_adRunsController.lua | 33 ++++++++++++++ 12 files changed, 173 insertions(+), 10 deletions(-) diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index bd6857f..82d7d28 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -208,6 +208,7 @@ function ADFieldJobs.load() ADFieldJobs.state.fleetMode = loaded.fleetMode or "all" ADFieldJobs.state.courseRecords = loaded.courseRecords or {} ADFieldJobs.state.manual = loaded.manual or {} + ADFieldJobs.state.paused = loaded.paused == true else Logging.warning(ADFieldJobs.LOG_PREFIX .. "marker kunne ikke indlæses: %s", tostring(loaded)) end @@ -252,6 +253,13 @@ function ADFieldJobs.setFleetMode(mode) ADFieldJobs.save() end +-- Pause (Stop alt og parkér): intet nyt sendes ud, ingen genstart efter indlæsning. Gemmes. +function ADFieldJobs.setPaused(value) + ADFieldJobs.state.paused = value == true + ADFieldJobs.save() + Logging.info(ADFieldJobs.LOG_PREFIX .. "markarbejde %s", value and "på pause" or "genoptaget") +end + function ADFieldJobs.toggleManual(vehicleId) ADFieldJobs.state.manual = ADFieldFleet.toggle(ADFieldJobs.state.manual or {}, vehicleId) ADFieldJobs.save() @@ -407,6 +415,8 @@ function ADFieldJobs.tick(adEnv, dt) -- lidt efter start, og indtil da ligner de maskiner ledige (in-game 26/9: kultivatoren blev taget midt -- i spillerens eget CP-job 45 s efter indlæsning) if ADFieldJobs.now < ADFieldJobs.STARTUP_GRACE_MS then return end + -- Stop alt og parkér: intet sendes ud og intet genstartes, før spilleren trykker Start alle + if ADFieldJobs.state.paused then return end if not ADFieldJobs.resumedAfterLoad then -- én gang, før noget nyt sendes ud: CP-maskiner der var midt i et job, fortsætter (og tæller som aktive) ADFieldJobs.resumedAfterLoad = true diff --git a/FS25_ADSmartPickup/adFieldSilos.lua b/FS25_ADSmartPickup/adFieldSilos.lua index 397d88d..d2c4a47 100644 --- a/FS25_ADSmartPickup/adFieldSilos.lua +++ b/FS25_ADSmartPickup/adFieldSilos.lua @@ -337,7 +337,8 @@ function ADFieldSilos.planNow(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 paused = ADFieldJobs ~= nil and ADFieldJobs.state ~= nil and ADFieldJobs.state.paused + if not cfg.enabled or paused 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 diff --git a/FS25_ADSmartPickup/adFieldStorage.lua b/FS25_ADSmartPickup/adFieldStorage.lua index 8df1e2c..1b576c9 100644 --- a/FS25_ADSmartPickup/adFieldStorage.lua +++ b/FS25_ADSmartPickup/adFieldStorage.lua @@ -68,6 +68,7 @@ function ADFieldStorage.save(state) index = index + 1 end xmlFile:setString(ADFieldStorage.ROOT .. "#fleetMode", state.fleetMode or "all") + xmlFile:setInt(ADFieldStorage.ROOT .. "#paused", state.paused == true and 1 or 0) local vehicleIndex = 0 for vehicleId in pairs(state.fleet) do xmlFile:setString(string.format("%s.fleet.vehicle(%d)#id", ADFieldStorage.ROOT, vehicleIndex), vehicleId) @@ -89,12 +90,13 @@ function ADFieldStorage.save(state) end function ADFieldStorage.load() - local result = {fields = {}, fleet = {}, fleetMode = "all", courseRecords = {}, manual = {}} + local result = {fields = {}, fleet = {}, fleetMode = "all", courseRecords = {}, manual = {}, paused = false} local path = ADFieldStorage.getPath() if path == nil or not fileExists(path) then return result end local xmlFile = XMLFile.load("ADSmartPickupFields", path) if xmlFile == nil then return result end result.fleetMode = xmlFile:getString(ADFieldStorage.ROOT .. "#fleetMode") == "ticked" and "ticked" or "all" + result.paused = xmlFile:getInt(ADFieldStorage.ROOT .. "#paused") == 1 xmlFile:iterate(ADFieldStorage.ROOT .. ".field", function(_, key) local field = ADFieldModel.fromRecord(ADFieldStorage.readRecord(xmlFile, key)) if field.id ~= nil then result.fields[field.id] = field end diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index 47d457a..f6a0bae 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -1083,6 +1083,21 @@ function ADFieldWork.windDownHelpers(adEnv, fieldId, stage) end end +-- Stop alle markarbejdets opgaver (Stop alt og parkér). Returnerer {{vehicle, manual}}; markernes trin bevares, +-- så arbejdet fortsætter efter pausen. +function ADFieldWork.stopAllAssignments(adEnv) + local stopped = {} + local list = {} + for _, assignment in pairs(ADFieldJobs.state.assignments) do table.insert(list, assignment) end + for _, assignment in ipairs(list) do + pcall(stopAd, assignment.vehicle) + pcall(ADCpBridge.stop, assignment.vehicle) + table.insert(stopped, {vehicle = assignment.vehicle, manual = assignment.manual == true}) + ADFieldWork.release(assignment) + end + return stopped +end + function ADFieldWork.stopAll(adEnv, fieldId) for _, assignment in ipairs(ADFieldJobs.assignmentsFor(fieldId)) do pcall(stopAd, assignment.vehicle) diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index bdb0c50..a1d7efc 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -1153,6 +1153,56 @@ function ADRunsController.queueStartAll(adEnv) return summary end +-- Stop alt og parkér (Oversigt): flows, markarbejde og marksilo-lån stoppes, køretøjerne sendes til deres +-- AD-parkeringspunkt, og moden sættes på pause (gemmes), så intet starter igen før "Start alle". +-- Høstere med manuel transport parkeres ikke (må ikke køre på vejen med skærebord). Returnerer +-- {stopped, parked, noPark = {navne}, manual = {navne}}. +function ADRunsController.stopAllAndPark(adEnv) + local summary = {stopped = 0, parked = 0, noPark = {}, manual = {}} + ADRunsController.clearStartQueue() + local toPark, seen = {}, {} + local vehiclesById = getFarmVehiclesById(adEnv) + for _, run in ipairs(ADRunsController.store.runs) do + for _, vehicleId in ipairs(run.vehicleIds) do + local vehicle = vehiclesById[vehicleId] + if vehicle ~= nil and not seen[vehicle] then + seen[vehicle] = true + if isVehicleActive(vehicle) then + stopVehicle(vehicle) + summary.stopped = summary.stopped + 1 + end + table.insert(toPark, vehicle) + end + end + ADRunsController.runningRuns[run.id] = nil + end + if ADFieldWork ~= nil and ADFieldWork.stopAllAssignments ~= nil then + for _, stopped in ipairs(ADFieldWork.stopAllAssignments(adEnv) or {}) do + summary.stopped = summary.stopped + 1 + if stopped.manual then + table.insert(summary.manual, tostring(ADRuns.vehicleLabel(stopped.vehicle))) + elseif not seen[stopped.vehicle] then + seen[stopped.vehicle] = true + table.insert(toPark, stopped.vehicle) + end + end + end + if ADFieldSilos ~= nil and ADFieldSilos.stopAll ~= nil then pcall(ADFieldSilos.stopAll, adEnv) end + if ADFieldJobs ~= nil and ADFieldJobs.setPaused ~= nil then ADFieldJobs.setPaused(true) end + for _, vehicle in ipairs(toPark) do + local park = ADFieldWork ~= nil and ADFieldWork.parkMarkerId ~= nil and ADFieldWork.parkMarkerId(adEnv, vehicle) or nil + if park ~= nil then + local ok = pcall(ADFieldWork.startAd, adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) + if ok then summary.parked = summary.parked + 1 end + else + table.insert(summary.noPark, tostring(ADRuns.vehicleLabel(vehicle))) + end + end + Logging.info(ADRunsController.LOG_PREFIX .. "Stop alt og parkér: %d stoppet, %d til parkering, %d uden parkeringspunkt, %d høstere hentes manuelt — pause", + summary.stopped, summary.parked, #summary.noPark, #summary.manual) + return summary +end + -- Starter næste traktor i køen, når der er gået STAGGER_MS siden forrige. function ADRunsController.processStartQueue(adEnv, dt) if #ADRunsController.startQueue == 0 then return end diff --git a/FS25_ADSmartPickup/adSmartPickup.lua b/FS25_ADSmartPickup/adSmartPickup.lua index 87fdf36..dc4472b 100644 --- a/FS25_ADSmartPickup/adSmartPickup.lua +++ b/FS25_ADSmartPickup/adSmartPickup.lua @@ -1254,6 +1254,9 @@ function ADSmartPickup:update(dt) if ADOutbound ~= nil and ADOutbound.releaseInactive ~= nil then pcall(ADOutbound.releaseInactive) end + if ADRunsController ~= nil and ADRunsController.processStartQueue ~= nil then + pcall(ADRunsController.processStartQueue, getAutoDriveEnv(), dt) + end if ADRunsController ~= nil and ADRunsController.applyVehicleNamesThrottled ~= nil then pcall(ADRunsController.applyVehicleNamesThrottled, getAutoDriveEnv(), dt) end diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua index 3ddfd64..26686d3 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua @@ -58,7 +58,9 @@ function SmartPickupFrame.new(i18n, getAdEnv) self.renameBuildingButtonInfo = {inputAction = InputAction.MENU_EXTRA_2, text = i18n:getText("spu_button_renameBuilding"), callback = function() self:onClickRenameBuilding() end} self.storageButtons = {self.backButtonInfo, self.renameBuildingButtonInfo} self.flowButtons = {self.backButtonInfo, self.startButtonInfo, self.newButtonInfo, self.renameButtonInfo, self.deleteButtonInfo} - self.overviewButtons = {self.backButtonInfo, self.startAllButtonInfo, self.stopAllButtonInfo, self.newButtonInfo} + -- Stop alt og parkér: flows + markarbejde + marksilo stoppes, køretøjer til parkering, pause til Start alle + self.stopParkButtonInfo = {inputAction = InputAction.MENU_CANCEL, text = i18n:getText("spu_button_stopPark"), callback = function() self:onClickStopAndPark() end} + self.overviewButtons = {self.backButtonInfo, self.startAllButtonInfo, self.stopAllButtonInfo, self.stopParkButtonInfo, self.newButtonInfo} -- Marker: Start nu / Stop for den valgte mark -- Marker: [Start nu/Stop], [Opsæt mark], [Markflåde] (gui/SmartPickupFieldGuide.lua); guidens knapper self:initFieldButtons(i18n) @@ -628,7 +630,15 @@ function SmartPickupFrame:refreshLive() running, total = running + status.running, total + status.total if status.running > 0 then activeRuns = activeRuns + 1 end end - self:setStatusText(self.statusOverride or string.format(self.i18n:getText("spu_status_overview"), running, total, activeRuns, #ADRunsController.store.runs)) + local statusText = string.format(self.i18n:getText("spu_status_overview"), running, total, activeRuns, #ADRunsController.store.runs) + local queued = #(ADRunsController.startQueue or {}) + if queued > 0 then + local totalQueued = ADRunsController.startQueueTotal or queued + statusText = string.format(self.i18n:getText("spu_status_starting"), totalQueued - queued, totalQueued) + elseif ADFieldJobs ~= nil and ADFieldJobs.state ~= nil and ADFieldJobs.state.paused then + statusText = self.i18n:getText("spu_status_paused") + end + self:setStatusText(self.statusOverride or statusText) self.stopAllButtonInfo.disabled = running == 0 elseif run ~= nil then self:syncOptionsFromModel(adEnv, run) @@ -786,11 +796,28 @@ function SmartPickupFrame:onClickStartAll() self:safe(function(frame) local adEnv = frame.getAdEnv() if adEnv == nil then return end - local summary = ADRunsController.startAllRuns(adEnv) - local _, skipped, reasons = frame:summarizeStartResults(summary.results) - Logging.info(SmartPickupFrame.LOG_PREFIX .. "Start alle: %d flows startet (%d traktorer), %d kørte allerede, %d sprunget over", - summary.runsStarted, summary.vehiclesStarted, summary.alreadyRunning, skipped) - frame:showOverride(string.format(frame.i18n:getText("spu_status_startedAll"), summary.runsStarted, summary.vehiclesStarted, summary.alreadyRunning, skipped), reasons) + -- pausen (Stop alt og parkér) ophæves; traktorerne startes én ad gangen med mellemrum + if ADFieldJobs ~= nil and ADFieldJobs.state ~= nil and ADFieldJobs.state.paused then ADFieldJobs.setPaused(false) end + local summary = ADRunsController.queueStartAll(adEnv) + local _, skipped, reasons = frame:summarizeStartResults(summary.skipped) + Logging.info(SmartPickupFrame.LOG_PREFIX .. "Start alle: %d traktorer i kø (én hvert %d. sekund), %d flows kørte allerede, %d sprunget over", + summary.queued, math.floor(ADRunsController.STAGGER_MS / 1000), summary.alreadyRunning, skipped) + frame:showOverride(string.format(frame.i18n:getText("spu_status_queuedAll"), summary.queued, math.floor(ADRunsController.STAGGER_MS / 1000), summary.alreadyRunning, skipped), reasons) + ADRunsController.save() + frame:requestRebuild() + end) +end + +function SmartPickupFrame:onClickStopAndPark() + self:safe(function(frame) + local adEnv = frame.getAdEnv() + if adEnv == nil then return end + local summary = ADRunsController.stopAllAndPark(adEnv) + local text = string.format(frame.i18n:getText("spu_status_stopParked"), summary.stopped, summary.parked) + local notes = {} + if #summary.noPark > 0 then table.insert(notes, string.format(frame.i18n:getText("spu_status_noPark"), table.concat(summary.noPark, ", "))) end + if #summary.manual > 0 then table.insert(notes, string.format(frame.i18n:getText("spu_status_fetchManual"), table.concat(summary.manual, ", "))) end + frame:showOverride(text, notes) ADRunsController.save() frame:requestRebuild() end) diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 9d4c41d..d2013f2 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -218,6 +218,13 @@ + + + + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index 919a3e9..df664da 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -218,6 +218,13 @@ + + + + + + + diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index bad3104..c9d70b5 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.41.1.0 + 1.42.0.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adFieldStorage.lua b/tests/test_adFieldStorage.lua index 4910b33..3aa09ec 100644 --- a/tests/test_adFieldStorage.lua +++ b/tests/test_adFieldStorage.lua @@ -98,5 +98,13 @@ ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all", manual = {vehic check("ST8 manuel transport indlæst", ADFieldStorage.load().manual.vehicleCombine, true) check("ST8 gammel fil: ingen manuelle", next(old.manual or {}), nil) +-- ST9: pause ("Stop alt og parkér") gemmes, så intet starter af sig selv efter indlæsning +disk = {} +ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all", paused = true}) +check("ST9 pause indlæst", ADFieldStorage.load().paused, true) +disk = {} +ADFieldStorage.save({fields = {}, fleet = {}, fleetMode = "all"}) +check("ST9 ingen pause", ADFieldStorage.load().paused, false) + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1) diff --git a/tests/test_adRunsController.lua b/tests/test_adRunsController.lua index 4040129..3164f7b 100644 --- a/tests/test_adRunsController.lua +++ b/tests/test_adRunsController.lua @@ -863,5 +863,38 @@ do check("C20 run markeret kørende", ADRunsController.runningRuns[qrun.id], true) end + +-- C21: Stop alt og parkér — stopper flow-traktorerne, sender dem til AD-parkering, sætter pause +do + buildWorld() + local p1 = makeVehicle("vehicleP1", "P1", {STRAW, TMR}, 1000000, 100, 0) + local p2 = makeVehicle("vehicleP2", "P2", {STRAW, TMR}, 1000000, 110, 0) + local prun = ADRunsController.createRun("Park") + ADRunsController.updateRun(prun.id, {targetWayPointIds = {94}}) + ADRunsController.assignVehicle(prun.id, p1) + ADRunsController.assignVehicle(prun.id, p2) + ADRunsController.startRun(adEnv, prun.id) + local parked, paused = {}, nil + local oldWork, oldJobs, oldSilos = ADFieldWork, ADFieldJobs, ADFieldSilos + ADFieldWork = { + parkMarkerId = function(_, v) return v == p1 and 700 or nil end, + startAd = function(_, v, mode, first) parked[v] = first end, + stopAllAssignments = function() return {} end, + } + ADFieldJobs = {setPaused = function(value) paused = value end} + ADFieldSilos = {stopAll = function() end} + ADRunsController.queueStartAll(adEnv) + local summary = ADRunsController.stopAllAndPark(adEnv) + check("C21 begge stoppet", (p1.stopped or 0) >= 1 and (p2.stopped or 0) >= 1, true) + check("C21 P1 kører til parkering", parked[p1], 700) + check("C21 P2 uden parkering bliver stående", parked[p2], nil) + check("C21 tæller parkerede", summary.parked, 1) + check("C21 nævner dem uden parkering", summary.noPark[1], "P2") + check("C21 pause sat", paused, true) + check("C21 startkø tømt", #ADRunsController.startQueue, 0) + check("C21 run ikke længere kørende", ADRunsController.runningRuns[prun.id], nil) + ADFieldWork, ADFieldJobs, ADFieldSilos = oldWork, oldJobs, oldSilos +end + print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)