From a5c52cee0998380bc78a9123880d66c0cf2abee4 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Tue, 29 Sep 2026 00:24:18 +0200 Subject: [PATCH] =?UTF-8?q?feat(flows):=20stoppet=20flow=20t=C3=B8mmer=20v?= =?UTF-8?q?ognen=20f=C3=B8r=20den=20parkeres=20=E2=80=94=20f=C3=B8rst=20ve?= =?UTF-8?q?d=20flowets=20m=C3=A5l,=20rest=20til=20n=C3=A6rmeste=20silo/ege?= =?UTF-8?q?n=20fabrik=20der=20tager=20varen,=20s=C3=A5=20p=C3=A5=20plads?= =?UTF-8?q?=20og=20hjem=20(v1.51.4.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adFieldWork.lua | 1 + FS25_ADSmartPickup/adFlowWagons.lua | 149 ++++++++++++++++++++++-- FS25_ADSmartPickup/adRunsController.lua | 9 +- FS25_ADSmartPickup/modDesc.xml | 2 +- tests/test_adFlowWagons.lua | 10 ++ 5 files changed, 157 insertions(+), 14 deletions(-) diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index 414b606..057ab1d 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -134,6 +134,7 @@ end local function isBusyVehicle(vehicle, id) return ADFieldJobs.state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle) or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)) or (ADPool ~= nil and ADPool.isReturning(vehicle)) + or (ADFlowWagons ~= nil and ADFlowWagons.isEmptying(vehicle)) end ADFieldWork.isBusyVehicle = isBusyVehicle diff --git a/FS25_ADSmartPickup/adFlowWagons.lua b/FS25_ADSmartPickup/adFlowWagons.lua index 11de8a5..e9de3d1 100644 --- a/FS25_ADSmartPickup/adFlowWagons.lua +++ b/FS25_ADSmartPickup/adFlowWagons.lua @@ -89,6 +89,24 @@ function ADFlowWagons.missingBuildings(run, exists, seen) return missing end +-- Tøm vognen før den parkeres (flowet stoppet): flowets mål -> silo/egen fabrik -> hjem. +-- stage: "start" | "target" | "dump"; s = {cargo, hasTarget, adActive, elapsedMs} +-- -> "home" | "target" | "dump" | "wait" | "giveUp" +ADFlowWagons.EMPTY_MIN_LITERS = 100 +ADFlowWagons.EMPTY_TIMEOUT_MS = 600000 +function ADFlowWagons.emptyStep(stage, s) + local cargo = s.cargo or 0 + if stage == "start" then + if cargo < ADFlowWagons.EMPTY_MIN_LITERS then return "home" end + return s.hasTarget and "target" or "dump" + end + if (s.elapsedMs or 0) >= ADFlowWagons.EMPTY_TIMEOUT_MS then return "giveUp" end + if s.adActive then return "wait" end + if cargo < ADFlowWagons.EMPTY_MIN_LITERS then return "home" end + if stage == "target" then return "dump" end + return "giveUp" +end + -- ---- spil ---- local waiting = setmetatable({}, {__mode = "k"}) -- traktor -> runId (venter på sin vogn) @@ -178,6 +196,7 @@ function ADFlowWagons.isPoolBusy(vehicle, tractorId) or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)) or (ADPool ~= nil and ADPool.isReturning(vehicle)) or (ADCpBridge ~= nil and ADCpBridge.isWorking(vehicle)) + or (ADFlowWagons.isEmptying ~= nil and ADFlowWagons.isEmptying(vehicle)) end -- Før en flow-traktor startes: hent sin vogn / vent på den. -> håndteret, resultat (som startVehicle) @@ -239,20 +258,119 @@ function ADFlowWagons.beforeStart(adEnv, run, vehicle) return true, {vehicle = vehicle, ok = true, reason = "fetchingWagon"} end --- Stop/slet/bygning væk: vognen på plads, traktoren hjem. +-- Stop/slet/bygning væk: vognen tømmes (flowets mål, ellers silo/egen fabrik), sættes på plads, traktoren hjem. +local emptying = setmetatable({}, {__mode = "k"}) -- traktor -> {stage, since, fillType, runName} +local nowMs = 0 + +local function cargoOf(adEnv, vehicle) + local level, fillType = 0, nil + local ok, units = pcall(adEnv.AutoDrive.getAllDischargeableUnits, vehicle) + for _, unit in ipairs(ok and units or {}) do + local object, index = unit.object, unit.fillUnitIndex + if object ~= nil and object.getFillUnitFillLevel ~= nil then + local unitLevel = object:getFillUnitFillLevel(index) or 0 + if unitLevel > 0 then + level = level + unitLevel + fillType = fillType or object:getFillUnitFillType(index) + end + end + end + return level, fillType +end + +local function goHome(adEnv, vehicle) + emptying[vehicle] = nil + local okHome, pooled = pcall(ADHitch.returnHome, adEnv, vehicle) + if not (okHome and pooled) then + local park = ADFieldWork.parkMarkerId(adEnv, vehicle) + if park ~= nil then pcall(ADFieldWork.startAd, adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end + end +end + +local function fillName(fillType) + local desc = fillType ~= nil and g_fillTypeManager ~= nil and g_fillTypeManager:getFillTypeByIndex(fillType) or nil + return desc ~= nil and (desc.title or desc.name) or tostring(fillType) +end + +local function startDump(adEnv, vehicle, state) + local cargo, fillType = cargoOf(adEnv, vehicle) + local isReachable = function(markerId) + return ADUnloadWait == nil or ADUnloadWait.hasRouteTo == nil or ADUnloadWait.hasRouteTo(adEnv, vehicle, markerId) + end + local ok, marker, why = pcall(ADLoadSwap.findDumpMarker, adEnv, vehicle, fillType, cargo, nil, isReachable, nil, nil, true) + if not ok or marker == nil then + Logging.warning(ADFlowWagons.LOG_PREFIX .. "'%s' har stadig %d l %s — intet sted at aflæsse (%s); parkeres med last", + tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType), tostring(ok and why or marker)) + goHome(adEnv, vehicle) + return + end + state.stage, state.since = "dump", nowMs + ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DELIVERTO, marker.id, nil, fillType) + Logging.info(ADFlowWagons.LOG_PREFIX .. "'%s' aflæsser resten (%d l %s) ved %s før parkering", + tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType), tostring(marker.name)) +end + +-- Én traktor hjem (fra et stoppet/slettet flow): tøm først, så parkering. +function ADFlowWagons.homeVehicle(adEnv, vehicle, runName) + waiting[vehicle] = nil + if fetching[vehicle] ~= nil then + fetching[vehicle] = nil + ADHitch.cancel(vehicle) + end + local module = vehicle.ad ~= nil and vehicle.ad.stateModule or nil + local target = module ~= nil and module.getSecondMarker ~= nil and module:getSecondMarker() or nil + local cargo, fillType = cargoOf(adEnv, vehicle) + local action = ADFlowWagons.emptyStep("start", {cargo = cargo, hasTarget = target ~= nil}) + if action == "home" then return goHome(adEnv, vehicle) end + local state = {stage = action, since = nowMs, runName = runName} + emptying[vehicle] = state + if action == "target" then + ADFieldWork.startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DELIVERTO, target.id, nil, fillType) + Logging.info(ADFlowWagons.LOG_PREFIX .. "'%s' afleverer sit læs (%d l %s) ved %s før parkering", + tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType), tostring(target.name)) + else + startDump(adEnv, vehicle, state) + end +end + +function ADFlowWagons.isEmptying(vehicle) + return emptying[vehicle] ~= nil +end + function ADFlowWagons.sendHome(adEnv, run) for _, vehicleId in ipairs(run.vehicleIds or {}) do local vehicle = ADRunsController.findVehicleById(adEnv, vehicleId) if vehicle ~= nil then - waiting[vehicle] = nil - if fetching[vehicle] ~= nil then - fetching[vehicle] = nil - ADHitch.cancel(vehicle) + local ok, err = pcall(ADFlowWagons.homeVehicle, adEnv, vehicle, run.name) + if not ok then + Logging.warning(ADFlowWagons.LOG_PREFIX .. "hjem fejlede: %s", tostring(err)) + goHome(adEnv, vehicle) end - local okHome, pooled = pcall(ADHitch.returnHome, adEnv, vehicle) - if not (okHome and pooled) then - local park = ADFieldWork.parkMarkerId(adEnv, vehicle) - if park ~= nil then pcall(ADFieldWork.startAd, adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end + end + end +end + +local function tickEmptying(adEnv) + local list = {} + for vehicle, state in pairs(emptying) do table.insert(list, {vehicle = vehicle, state = state}) end + for _, item in ipairs(list) do + local vehicle, state = item.vehicle, item.state + local module = vehicle.ad ~= nil and vehicle.ad.stateModule or nil + local cargo, fillType = cargoOf(adEnv, vehicle) + local controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() + if controlled then + emptying[vehicle] = nil -- spilleren har overtaget + else + local action = ADFlowWagons.emptyStep(state.stage, {cargo = cargo, adActive = module ~= nil and module:isActive(), + elapsedMs = nowMs - state.since}) + if action == "home" then + goHome(adEnv, vehicle) + elseif action == "dump" then + startDump(adEnv, vehicle, state) + elseif action == "giveUp" then + Logging.warning(ADFlowWagons.LOG_PREFIX .. "'%s' kunne ikke tømme vognen (%d l %s tilbage) — parkeres med last", + tostring(ADRuns.vehicleLabel(vehicle)), math.floor(cargo), fillName(fillType)) + goHome(adEnv, vehicle) end end end @@ -329,17 +447,26 @@ local function cleanupAfterLoad(adEnv) local controlled = vehicle ~= nil and vehicle.getIsControlled ~= nil and vehicle:getIsControlled() if module ~= nil and not module:isActive() and not controlled and #ADHitch.poolParkIds(adEnv, vehicle) > 0 and not ADFlowWagons.isPoolBusy(vehicle, vehicleId) then - Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s' er stoppet: '%s' sætter sin vogn på plads", tostring(run.name), + Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s' er stoppet: '%s' tømmer og sætter sin vogn på plads", tostring(run.name), tostring(ADRuns.vehicleLabel(vehicle))) - pcall(ADHitch.returnHome, adEnv, vehicle) + pcall(ADFlowWagons.homeVehicle, adEnv, vehicle, run.name) end end end end end +local emptyTimer = 0 + function ADFlowWagons.tick(adEnv, dt) if adEnv == nil or ADRunsController.store == nil then return end + nowMs = nowMs + dt + emptyTimer = emptyTimer + dt + if emptyTimer >= 2000 then + emptyTimer = 0 + local okEmpty, errEmpty = pcall(tickEmptying, adEnv) + if not okEmpty then Logging.warning(ADFlowWagons.LOG_PREFIX .. "tømning: %s", tostring(errEmpty)) end + end if not cleanedUp then sinceLoad = sinceLoad + dt if sinceLoad >= ADFlowWagons.AFTER_LOAD_MS then diff --git a/FS25_ADSmartPickup/adRunsController.lua b/FS25_ADSmartPickup/adRunsController.lua index 581a325..91bd946 100644 --- a/FS25_ADSmartPickup/adRunsController.lua +++ b/FS25_ADSmartPickup/adRunsController.lua @@ -1203,9 +1203,14 @@ function ADRunsController.stopAllAndPark(adEnv) 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 - -- flow-vogn/puljeredskab bag traktoren: sæt det af på sin plads og kør hjem + -- flow-traktor: tøm vognen, sæt den af på sin plads og kør hjem (ADFlowWagons klarer hele turen) local okHome, pooled = false, false - if ADHitch ~= nil then okHome, pooled = pcall(ADHitch.returnHome, adEnv, vehicle) end + if ADFlowWagons ~= nil and ADRunsController.getRunForVehicle(vehicle) ~= nil then + okHome = pcall(ADFlowWagons.homeVehicle, adEnv, vehicle) + pooled = okHome + elseif ADHitch ~= nil then + okHome, pooled = pcall(ADHitch.returnHome, adEnv, vehicle) + end local park = not (okHome and pooled) and ADFieldWork ~= nil and ADFieldWork.parkMarkerId ~= nil and ADFieldWork.parkMarkerId(adEnv, vehicle) or nil if okHome and pooled then summary.parked = summary.parked + 1 diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 4ec19c0..071e487 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.51.3.0 + 1.51.4.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adFlowWagons.lua b/tests/test_adFlowWagons.lua index 6b5abed..c2590d1 100644 --- a/tests/test_adFlowWagons.lua +++ b/tests/test_adFlowWagons.lua @@ -66,5 +66,15 @@ check("gæt: andet flow", W.guessWagon("Goat Food", 2, cands, {}), "g1") check("gæt: intet passer", W.guessWagon("Pig Food", 1, cands, {}), nil) check("gæt: store/små bogstaver", W.guessWagon("cow food", 2, cands, {}), "v2") check("gæt: 'Cow Food 1' matcher ikke 'Cow Food 10'", W.guessWagon("Cow Food", 1, {{id = "x", parkName = "P Cow Food vogn 10"}, {id = "y", parkName = "P Cow Food vogn 1"}}, {}), "y") +-- tøm vognen før den parkeres (flow stoppet): mål -> silo/fabrik -> hjem +check("tøm: tom vogn -> hjem", W.emptyStep("start", {cargo = 0}), "home") +check("tøm: last -> flowets mål", W.emptyStep("start", {cargo = 5000, hasTarget = true}), "target") +check("tøm: last uden mål -> silo", W.emptyStep("start", {cargo = 5000, hasTarget = false}), "dump") +check("tøm: kører stadig -> vent", W.emptyStep("target", {cargo = 5000, adActive = true}), "wait") +check("tøm: mål fuldt, rest -> silo", W.emptyStep("target", {cargo = 2000}), "dump") +check("tøm: målet tog det hele -> hjem", W.emptyStep("target", {cargo = 0}), "home") +check("tøm: silo tog det -> hjem", W.emptyStep("dump", {cargo = 0}), "home") +check("tøm: silo kunne ikke -> giv op, hjem", W.emptyStep("dump", {cargo = 2000}), "giveUp") +check("tøm: for længe -> giv op", W.emptyStep("target", {cargo = 5000, adActive = true, elapsedMs = W.EMPTY_TIMEOUT_MS}), "giveUp") print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)