From 856b94974969f853a491fc2239034776387f7c34 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Tue, 22 Sep 2026 23:35:47 +0200 Subject: [PATCH] =?UTF-8?q?fix:=20slut-review=20=E2=80=94=20pcall=20om=20s?= =?UTF-8?q?tart/dialoger,=20Auto-varer=20fra=20aktivt=20AD-m=C3=A5l,=20nil?= =?UTF-8?q?-guards,=20README?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adLoadSwap.lua | 15 +++++--- FS25_ADSmartPickup/adRunsController.lua | 42 ++++++++++++++++----- FS25_ADSmartPickup/gui/SmartPickupFrame.lua | 19 +++++++--- FS25_ADSmartPickup/l10n/l10n_da.xml | 1 - FS25_ADSmartPickup/l10n/l10n_en.xml | 1 - README.md | 11 ++++-- tests/test_adRunsController.lua | 37 ++++++++++++++++++ 7 files changed, 99 insertions(+), 27 deletions(-) diff --git a/FS25_ADSmartPickup/adLoadSwap.lua b/FS25_ADSmartPickup/adLoadSwap.lua index 899372b..ec65758 100644 --- a/FS25_ADSmartPickup/adLoadSwap.lua +++ b/FS25_ADSmartPickup/adLoadSwap.lua @@ -53,12 +53,15 @@ function ADLoadSwap.findDumpMarker(adEnv, vehicle, fillType, rest, homeWayPointI local station = ADSmartPickup.getUnloadStationAtWayPointId(adEnv, marker.id, farmId) if station ~= nil and isSilo(station, homeStation) and acceptsRest(station, fillType, rest, farmId) then local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id) - local dx, dz = wayPoint.x - x, wayPoint.z - z - table.insert(candidates, { - marker = marker, - hasFillType = ADSmartPickup.getOwnStorageLevel(station, {fillType}) > 0, - distance = dx * dx + dz * dz, - }) + -- En markør uden waypoint (stale AD-graf) skal springes over, ikke crashe hele opslaget. + if wayPoint ~= nil then + local dx, dz = wayPoint.x - x, wayPoint.z - z + table.insert(candidates, { + marker = marker, + hasFillType = ADSmartPickup.getOwnStorageLevel(station, {fillType}) > 0, + distance = dx * dx + dz * dz, + }) + end end end table.sort(candidates, function(a, b) diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index 11a4ade..8855a58 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -112,7 +112,20 @@ end -- findSupplyPickup selv til de ønskede produktioners inputs bagefter. function ADRunsController.getTargetFillTypes(adEnv, run, vehicle) local farmId = ADSmartPickup.getFarmId(vehicle) - local target = run.targetWayPointId ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, run.targetWayPointId, farmId) or nil + -- En kørende traktor følger AD's eget mål (stateModule:getSecondMarker(), det samme + -- choosePickup/findSupplyPickup bruger) indtil næste Start — IKKE run'ets gemte + -- targetWayPointId. Redigerer brugeren run'ets Mål mens tildelte traktorer kører, + -- skal Auto-listen derfor følge det LIVE mål, ellers kan den blive tom og fejlagtigt + -- smide traktoren over i mappe-logikken midt i turen. + local wayPointId = run.targetWayPointId + local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil + if stateModule ~= nil and stateModule.isActive ~= nil and stateModule:isActive() and stateModule.getSecondMarker ~= nil then + local secondMarker = stateModule:getSecondMarker() + if secondMarker ~= nil and secondMarker.id ~= nil then + wayPointId = secondMarker.id + end + end + local target = wayPointId ~= nil and ADSmartPickup.getUnloadStationAtWayPointId(adEnv, wayPointId, farmId) or nil if target == nil then return {} end @@ -251,10 +264,14 @@ local function findNearestLoadMarker(adEnv, vehicle, farmId) for _, marker in pairs(adEnv.ADGraphManager:getMapMarkers()) do if ADSmartPickup.getLoadStationAtWayPointId(adEnv, marker.id, farmId) ~= nil then local wayPoint = adEnv.ADGraphManager:getWayPointById(marker.id) - local dx, dz = wayPoint.x - x, wayPoint.z - z - local distance = dx * dx + dz * dz - if distance < bestDistance then - best, bestDistance = marker, distance + -- En stale markør uden waypoint skal springes over — ellers fejler Start med "error" + -- for ALLE tildelte traktorer, ikke kun den der ramte den ugyldige markør. + if wayPoint ~= nil then + local dx, dz = wayPoint.x - x, wayPoint.z - z + local distance = dx * dx + dz * dz + if distance < bestDistance then + best, bestDistance = marker, distance + end end end end @@ -284,6 +301,16 @@ local function isVehicleActive(vehicle) return stateModule ~= nil and stateModule.isActive ~= nil and stateModule:isActive() 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. +local function applyAndStart(adEnv, run, vehicle, ingredients) + applyRunToVehicle(adEnv, run, vehicle, ingredients) + if not isVehicleActive(vehicle) then + vehicle:startAutoDrive() + end +end + -- Pr. tildelt traktor: validér, skriv AD-tilstand, start. ✖-traktorer springes over. function ADRunsController.startRun(adEnv, runId) local run = ADRuns.getRun(ADRunsController.store, runId) @@ -299,11 +326,8 @@ function ADRunsController.startRun(adEnv, runId) local result = {vehicle = vehicle, ok = validation.ok, reason = validation.reason} if validation.ok then local ingredients = ADRunsController.getRunIngredients(adEnv, run, vehicle) - local ok, err = pcall(applyRunToVehicle, adEnv, run, vehicle, ingredients) + local ok, err = pcall(applyAndStart, adEnv, run, vehicle, ingredients) if ok then - if not isVehicleActive(vehicle) then - vehicle:startAutoDrive() - end Logging.info(ADRunsController.LOG_PREFIX .. "run '%s': '%s' startet mod markør %s", tostring(run.name), tostring(vehicle:getName()), tostring(run.targetWayPointId)) else result.ok, result.reason = false, "error" diff --git a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua index 7cd3a0f..173f55e 100644 --- a/FS25_ADSmartPickup/gui/SmartPickupFrame.lua +++ b/FS25_ADSmartPickup/gui/SmartPickupFrame.lua @@ -424,9 +424,13 @@ function SmartPickupFrame:onClickNew() end function SmartPickupFrame:onClickRename() - local run = self:getRun() - if run == nil then return end - TextInputDialog.show(SmartPickupFrame.onRenameResult, self, run.name, self.i18n:getText("spu_renameTitle"), nil, 30, g_i18n:getText("button_ok")) + -- Dialogen kan kaste (fx manglende i18n-nøgle) — fang det som resten af GUI'et, i stedet + -- for at lade fejlen undslippe klik-callbacken uden statuslinje-besked. + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + TextInputDialog.show(SmartPickupFrame.onRenameResult, frame, run.name, frame.i18n:getText("spu_renameTitle"), nil, 30, g_i18n:getText("button_ok")) + end) end function SmartPickupFrame:onRenameResult(value, clickOk) @@ -440,9 +444,12 @@ function SmartPickupFrame:onRenameResult(value, clickOk) end function SmartPickupFrame:onClickDelete() - local run = self:getRun() - if run == nil then return end - YesNoDialog.show(SmartPickupFrame.onDeleteResult, self, string.format(self.i18n:getText("spu_deleteConfirm"), run.name), self.i18n:getText("spu_button_delete")) + -- Samme begrundelse som onClickRename: dialog-opkaldet skal ikke kunne undslippe uden om safe(). + self:safe(function(frame) + local run = frame:getRun() + if run == nil then return end + YesNoDialog.show(SmartPickupFrame.onDeleteResult, frame, string.format(frame.i18n:getText("spu_deleteConfirm"), run.name), frame.i18n:getText("spu_button_delete")) + end) end function SmartPickupFrame:onDeleteResult(yes) diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 16d65fe..e78b0fa 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -22,7 +22,6 @@ - diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index ea4064e..1ec52db 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -22,7 +22,6 @@ - diff --git a/README.md b/README.md index 1c42bbf..a1f6f74 100644 --- a/README.md +++ b/README.md @@ -97,7 +97,9 @@ startes/stoppes samlet for alle traktorer i det. - **Ture** — loop-tælleren (0–99, 0 = uendeligt), som normalt i AutoDrives Pickup&Deliver. - **Traktorer** — rækker med Ja/Nej (er traktoren med i run'et), kapacitet og et ✔/✖-ikon. En **✖-traktor springes over ved Start**, med en grund vist ved siden af (fx "ingen vogn", "vognen - kan ikke bære run'ets varer", "for langt fra vejnettet" eller "intet mål"). + kan ikke bære run'ets varer", "for langt fra vejnettet" eller "intet mål"). Sættes en traktor til + Nej (fjernet fra run'et), rører det IKKE dens AutoDrive-vareliste — run'ets varer forbliver valgt + i traktorens AD-vareliste (synlig/redigerbar i AD's eget HUD) indtil de ændres dér manuelt. **Start/Stop:** "Start run" sætter AutoDrive op på hver ✔-traktor: mode Pickup&Deliver, målmarkør, nærmeste pålæsningsmarkør som start, run'ets varer valgt, run'ets tur-tæller, mapper ("Brug mapper") @@ -107,8 +109,8 @@ traktorens hidtidige manuelle AutoDrive-indstillinger.** "Stop run" stopper Auto i run'et (medlemskabet i run'et bevares, så det kan startes igen). Traktorer der ikke er del af noget run, kører som før — bortset fra at "Brug mapper" kan være blevet slået til globalt af et run. -**Wait-puljer-fanen** viser hver Wait-mappe skrivebeskyttet: antal punkter og hvilken traktor der -holder hvor. +**Wait-puljer-afsnittet** nederst på siden viser hver Wait-mappe skrivebeskyttet: antal punkter og +hvilken traktor der holder hvor. **Gemning:** Runs gemmes i `savegameN/ADSmartPickup_runs.xml` (ved gem og ved Start/Stop). De **genstartes ikke automatisk** ved indlæsning af et savegame — kør "Start run" igen efter load, hvis @@ -144,7 +146,8 @@ aldrig fejlagtigt bliver opfattet som et fuldt mål. - Wrapper `ADMultipleTargetsManager.getNextPickup` (via den globale `FS25_AutoDrive`-tabel); rører ikke AutoDrive-zip'en. - Siloens eget lager rangeres før nabo-lån (udvidelses-siloer deler lager inden for `storageRadius`). - Intet fund, intet behov eller fejl i et spil-API → AutoDrives originale rotation. Valg logges som `ADSmartPickup:` i log.txt. -- Kræver i AutoDrive: "Brug mapper" + "Rotér mål: pålæsning" på køretøjet. +- Kræver i AutoDrive: "Brug mapper" (global AutoDrive-indstilling, gælder hele spillet) + + "Rotér mål: pålæsning" på køretøjet (pr. køretøj). - **Markør ↔ silo (v1.3):** markøren er dér hvor trækkerens næse stopper; vognen holder et vogntog bagved. Markørens silo er derfor nærmeste trigger **bag næsen** set i ankomstretningen (forrige waypoint), højst 2 m foran. I tætte rækker (15 m mellem siloerne) er nabosiloens trigger ellers diff --git a/tests/test_adRunsController.lua b/tests/test_adRunsController.lua index fb7cbd0..5949eb3 100644 --- a/tests/test_adRunsController.lua +++ b/tests/test_adRunsController.lua @@ -78,6 +78,8 @@ local function makeVehicle(uniqueId, name, carriable, capacity, x, z) stateModule.raiseDirtyFlag = function(self) self.dirtyCount = self.dirtyCount + 1 end stateModule.setLoopsDone = function(self, n) self.loopsDone = n end stateModule.isActive = function(self) return self.active end + -- Bruges af C13: getTargetFillTypes skal følge AD's LIVE mål (mock'er AD's eget getSecondMarker). + stateModule.getSecondMarker = function(self) return self.secondMarkerId ~= nil and {id = self.secondMarkerId} or nil end local vehicle = { uniqueId = uniqueId, farmId = 1, rootNode = uniqueId, carriable = carriable, trailers = {level = 0, capacity = capacity}, @@ -253,6 +255,41 @@ check("C11 global rotateTargets urørt", adEnv.AutoDrive.settings.rotateTargets. check("C11 useFolders global stadig skrevet", adEnv.AutoDrive.settings.useFolders.current, 2) check("C11 køretøjet startet", noSettings.started, 1) +-- C12: startAutoDrive() kaster for én traktor -> pcall'et applyAndStart fanger fejlen, +-- resultatet markeres ok=false/reason="error", og de ØVRIGE traktorer i run'et startes stadig +-- (fejlen må ikke undslippe startRun-loopet). +buildWorld() +local brokenRun = ADRunsController.createRun("Broken Start") +ADRunsController.updateRun(brokenRun.id, {targetWayPointId = 94, loops = 2}) +local broken = makeVehicle("vehicleBroken", "Fejl-traktor", {STRAW, TMR}, 500000, 100, 0) +broken.startAutoDrive = function() error("boom") end +local normal = makeVehicle("vehicleNormal", "Normal-traktor", {STRAW, TMR}, 500000, 200, 0) +ADRunsController.assignVehicle(brokenRun.id, broken) +ADRunsController.assignVehicle(brokenRun.id, normal) +local c12Results = ADRunsController.startRun(adEnv, brokenRun.id) +check("C12 to resultater", #c12Results, 2) +check("C12 fejl-traktor ok=false", c12Results[1].ok, false) +check("C12 fejl-traktor reason=error", c12Results[1].reason, "error") +check("C12 fejl-traktor ikke startet", broken.started, nil) +check("C12 normal-traktor ok=true", c12Results[2].ok, true) +check("C12 normal-traktor startet", normal.started, 1) + +-- C13: Auto-listen følger traktorens LIVE AD-mål (stateModule:getSecondMarker()) mens den kører, +-- IKKE run'ets gemte targetWayPointId — ellers afviger listen fra det choosePickup/findSupplyPickup +-- faktisk bruger, hvis brugeren redigerer run'ets Mål mens traktoren kører. +addMarker(95, "Alt Cow Food", "Cow Food", 5300, 0) +unloadAt[95] = {accepts = {[STRAW] = 1000}} +local liveRun = ADRunsController.createRun("Live Target") +ADRunsController.updateRun(liveRun.id, {targetWayPointId = 94}) +local liveRunData = ADRuns.getRun(ADRunsController.store, liveRun.id) +local liveVehicle = makeVehicle("vehicleLive", "Live-traktor", {STRAW, TMR}, 500000, 300, 0) +check("C13 inaktiv: auto-liste fra run'ets mål (94)", table.concat(ADRunsController.getRunIngredients(adEnv, liveRunData, liveVehicle), ","), STRAW .. "," .. TMR) +liveVehicle.ad.stateModule.active = true +liveVehicle.ad.stateModule.secondMarkerId = 95 +check("C13 aktiv: auto-liste fra AD's live mål (95)", table.concat(ADRunsController.getRunIngredients(adEnv, liveRunData, liveVehicle), ","), tostring(STRAW)) +liveVehicle.ad.stateModule.active = false +liveVehicle.ad.stateModule.secondMarkerId = nil + -- C10: målets accepterede varer (til afkrydsning i GUI'et) + køretøjs-label buildWorld() local labelA = makeVehicle("vehicleA", "Fastrac A", {STRAW, TMR}, 1000000)