From f472f5dd8008efe507e70aa887949bf64825d50d Mon Sep 17 00:00:00 2001 From: masterdraco Date: Tue, 29 Sep 2026 14:09:12 +0200 Subject: [PATCH] =?UTF-8?q?fix(pulje):=20flow-vogne=20holdes=20altid=20af?= =?UTF-8?q?=20deres=20flow=20(ogs=C3=A5=20stoppet),=20stoppet=20flow-trakt?= =?UTF-8?q?or=20kun=20ledig=20solo;=20markredskaber=20kun=20bag=20traktore?= =?UTF-8?q?r=20(butikskategori=20tractors*,=20ikke=20lastbil/Unimog);=20pr?= =?UTF-8?q?=C3=A6cis=20vente=C3=A5rsag=20i=20loggen=20(v1.54.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adFlowWagons.lua | 57 ++++++++++++++++++++++------- FS25_ADSmartPickup/adPool.lua | 26 +++++++++++-- FS25_ADSmartPickup/modDesc.xml | 2 +- tests/test_adFlowWagons.lua | 14 ++++++- tests/test_adPool.lua | 12 ++++++ 5 files changed, 91 insertions(+), 20 deletions(-) diff --git a/FS25_ADSmartPickup/adFlowWagons.lua b/FS25_ADSmartPickup/adFlowWagons.lua index 57ccc60..ad5eed2 100644 --- a/FS25_ADSmartPickup/adFlowWagons.lua +++ b/FS25_ADSmartPickup/adFlowWagons.lua @@ -32,6 +32,13 @@ function ADFlowWagons.startAction(s) return "fetch" end +-- Hvorfor "wait" (til loggen). s som startAction. +function ADFlowWagons.waitReason(s) + if s.tractorBusy then return "tractorBusy" end + if s.wagonAttached then return "wagonAttached" end + return "wagonReserved" +end + -- Ingen husket vogn: gæt ud fra parkeringsnavnet — en ledig vogn hvis parkering indeholder flowets navn, helst med -- traktorens nummer til sidst ("Cow Food 3" -> "P Cow Food vogn 3"). candidates = {{id, parkName}}, taken = {[id]=true} function ADFlowWagons.guessWagon(runName, tractorNumber, candidates, taken) @@ -58,16 +65,10 @@ function ADFlowWagons.ownerOf(runs, wagonId) return nil end --- Holdes vognen af et flow der ikke er stoppet? (så må puljen ikke låne den) +-- Holdes vognen af et flow? Flowets vogn er flowets, også når flowet er stoppet — puljen, markarbejde og +-- marksilo må ikke låne den (in-game 29/9: marksilo lånte Cow Food-traktoren med vognen på efter stop). function ADFlowWagons.isHeld(runs, wagonId) - for _, run in ipairs(runs or {}) do - if not run.stopped then - for _, id in pairs(run.wagons or {}) do - if id == wagonId then return true end - end - end - end - return false + return ADFlowWagons.ownerOf(runs, wagonId) ~= nil end -- Et tjek af om bygningen mangler: -> ny tæller, væk? @@ -147,9 +148,23 @@ local function connectOf(adEnv, wagon) end -- Traktorer i stoppede flows er ledige i puljen; traktorer i kørende flows aldrig. +-- s = {hasRun, stopped, carriesFlowWagon, homeTrip} -> må puljen/markarbejde/marksilo låne traktoren? +function ADFlowWagons.tractorFree(s) + if not s.hasRun then return true end + return s.stopped == true and not s.carriesFlowWagon and not s.homeTrip +end + function ADFlowWagons.isFreeTractor(vehicle) local run = ADRunsController.getRunForVehicle(vehicle) - return run == nil or run.stopped == true + local carries = false + if run ~= nil then + for _, implement in ipairs(ADHitch.rearImplementsOf(vehicle)) do + local wagonId = ADRunsController.getVehicleId(implement.object) + if wagonId ~= nil and ADFlowWagons.ownerOf(runs(), wagonId) ~= nil then carries = true end + end + end + return ADFlowWagons.tractorFree({hasRun = run ~= nil, stopped = run ~= nil and run.stopped == true, + carriesFlowWagon = carries, homeTrip = emptying[vehicle] ~= nil or homing[vehicle] ~= nil}) end -- Holder flowet noget: en traktor henter/venter på sin vogn eller har en vogn bagpå (til Stop-knappen). @@ -209,6 +224,16 @@ function ADFlowWagons.abortHomeTripOnStart(s) return s.emptying == true or s.flowHoming == true end +-- Hvilken af isPoolBusy's grunde (til loggen). +function ADFlowWagons.busyWhy(vehicle, tractorId) + if ADFieldJobs ~= nil and ADFieldJobs.state.assignments[tractorId] ~= nil then return "markopgave" end + if ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle) then return "lånt af marksilo" end + if ADPool ~= nil and ADPool.isReturning(vehicle) then return "sætter redskab af" end + if ADCpBridge ~= nil and ADCpBridge.isWorking(vehicle) then return "Courseplay kører" end + if emptying[vehicle] ~= nil then return "tømmer vognen" end + return "?" +end + -- Lånt ud af puljen: markarbejde, marksilo-lån, på hjemtur eller Courseplay kører den. function ADFlowWagons.isPoolBusy(vehicle, tractorId) return (ADFieldJobs ~= nil and ADFieldJobs.state.assignments[tractorId] ~= nil) @@ -238,19 +263,23 @@ function ADFlowWagons.beforeStart(adEnv, run, vehicle) end local wagon = wagonId ~= nil and findAnyVehicle(wagonId) or nil local connectId = wagon ~= nil and connectOf(adEnv, wagon) or nil - local action = ADFlowWagons.startAction({ + local startState = { tractorBusy = ADFlowWagons.isPoolBusy(vehicle, tractorId), hasRear = #ADHitch.rearImplementsOf(vehicle) > 0, wagonId = wagonId, wagonExists = wagon ~= nil, hasConnect = connectId ~= nil, wagonAttached = wagon ~= nil and wagon.getAttacherVehicle ~= nil and wagon:getAttacherVehicle() ~= nil, wagonBusy = wagonId ~= nil and ADPool.isReserved(wagonId), - }) + } + local action = ADFlowWagons.startAction(startState) if action == "start" then return false end ADRunsController.runningRuns[run.id] = true if action == "wait" then if waiting[vehicle] == nil then - Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' venter på sin vogn (i brug andetsteds)", tostring(run.name), - tostring(ADRuns.vehicleLabel(vehicle))) + local holder = wagon ~= nil and wagon.getAttacherVehicle ~= nil and wagon:getAttacherVehicle() or nil + Logging.info(ADFlowWagons.LOG_PREFIX .. "flow '%s': '%s' venter på sin vogn (%s: %s)", tostring(run.name), + tostring(ADRuns.vehicleLabel(vehicle)), ADFlowWagons.waitReason(startState), + startState.tractorBusy and ADFlowWagons.busyWhy(vehicle, tractorId) + or (holder ~= nil and ("sidder på " .. tostring(ADRuns.vehicleLabel(holder)))) or "reserveret af puljen") end waiting[vehicle] = run.id return true, {vehicle = vehicle, ok = true, reason = "waitingWagon"} diff --git a/FS25_ADSmartPickup/adPool.lua b/FS25_ADSmartPickup/adPool.lua index 8c3e0f9..8c0abf9 100644 --- a/FS25_ADSmartPickup/adPool.lua +++ b/FS25_ADSmartPickup/adPool.lua @@ -35,6 +35,19 @@ function ADPool.powerOk(tractorHp, neededHp) return tractorHp >= neededHp end +-- Butikskategori: markredskaber kun bag traktorer (tractorsS/M/L …) — ikke lastbil/Unimog, læssere o.l. +-- Tømmevogne må alle trække. Ukendt kategori tillades (intet må gå i stå på manglende data). +ADPool.ANY_PULLER_ROLES = {unloader = true} + +function ADPool.categoryOk(category, role) + if category == nil or ADPool.ANY_PULLER_ROLES[role] then return true end + return tostring(category):lower():find("^tractors") ~= nil +end + +local function suits(tractor, implement, role) + return ADPool.powerOk(tractor.hp, implement.neededHp) and ADPool.categoryOk(tractor.category, role) +end + -- Markøren "Connect " for en parkering (navneregel). function ADPool.connectFor(parkName, markers) if type(parkName) ~= "string" then return nil end @@ -147,7 +160,7 @@ function ADPool.pairFor(role, fieldX, fieldZ, implements, tractors, blocked, acc end) local weakest = nil for _, implement in ipairs(candidates) do - local tractor = nearest(free, implement.x, implement.z, function(t) return ADPool.powerOk(t.hp, implement.neededHp) end) + local tractor = nearest(free, implement.x, implement.z, function(t) return suits(t, implement, role) end) if tractor ~= nil then return {implement = implement, tractor = tractor} end if weakest == nil or (implement.neededHp or 0) < (weakest.neededHp or 0) then weakest = implement end end @@ -162,7 +175,7 @@ function ADPool.pairAll(role, implements, tractors, blocked) for _, implement in ipairs(implements or {}) do if hasRole(implement, role) and not blocked[implement.id] then local tractor = nearest(tractors or {}, implement.x, implement.z, function(t) - return not used[t.id] and not blocked[t.id] and not t.offNetwork and ADPool.powerOk(t.hp, implement.neededHp) + return not used[t.id] and not blocked[t.id] and not t.offNetwork and suits(t, implement, role) end) if tractor ~= nil then used[tractor.id] = true @@ -298,6 +311,13 @@ function ADPool.readTractorHp(vehicle) return item ~= nil and item.specs ~= nil and ADPool.numberOf(item.specs.power) or nil end +-- Butikskategori (fx "TRACTORSM", "TRUCKS") eller nil. +local function categoryOf(vehicle) + if g_storeManager == nil or vehicle.configFileName == nil then return nil end + local item = g_storeManager:getItemByXMLFilename(vehicle.configFileName) + return item ~= nil and item.categoryName or nil +end + -- Varer og samlet kapacitet i redskabets fyldenheder (tømmevogne). function ADPool.cargoOf(vehicle) local carries, capacity = {}, 0 @@ -367,7 +387,7 @@ function ADPool.collect(adEnv, isBusy) -- uden for AD-vejnettet: den kan ikke køre ud efter redskabet (ville sende redskaber i karantæne) local networkDistance = ADFieldWork.distanceToNetwork(adEnv, x, z) table.insert(tractors, {id = id, vehicle = vehicle, name = ADRuns.vehicleLabel(vehicle), x = x, z = z, - hp = tractorHpOf(vehicle), parkId = park.id, + hp = tractorHpOf(vehicle), category = categoryOf(vehicle), parkId = park.id, offNetwork = networkDistance ~= nil and networkDistance > ADFieldWork.MAX_OFF_NETWORK}) end end diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index 8ae9ad9..6db9152 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.53.2.0 + 1.54.0.0 <en>AD Smart Pickup</en> diff --git a/tests/test_adFlowWagons.lua b/tests/test_adFlowWagons.lua index f2a68fb..7bb80d8 100644 --- a/tests/test_adFlowWagons.lua +++ b/tests/test_adFlowWagons.lua @@ -32,10 +32,10 @@ check("start: vognen uden Connect", W.startAction({wagonId = "w1", wagonExists = check("start: vognen på en anden", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = true, wagonAttached = true}), "wait") check("start: vognen lånt ud", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = true, wagonBusy = true}), "wait") check("start: hent", W.startAction({wagonId = "w1", wagonExists = true, hasConnect = true}), "fetch") --- ejer: kun et ikke-stoppet flow holder vognen +-- ejer: flowets vogn er flowets — også når flowet er stoppet (in-game 29/9: marksilo lånte Cow Food-vognen) local runs = {{id = 1, stopped = false, wagons = {t1 = "w1"}}, {id = 2, stopped = true, wagons = {t2 = "w2"}}} check("ejer: kørende flow holder", W.isHeld(runs, "w1"), true) -check("ejer: stoppet flow holder ikke", W.isHeld(runs, "w2"), false) +check("ejer: stoppet flow holder stadig", W.isHeld(runs, "w2"), true) check("ejer: ukendt vogn", W.isHeld(runs, "w3"), false) check("ejer: flowet findes", W.ownerOf(runs, "w2").id, 2) -- bygning væk: to tjek i træk @@ -88,5 +88,15 @@ check("tøm-mål: forældet/Wait-markør -> flowets første mål", W.emptyTarget check("tøm-mål: intet AD-mål -> flowets første", W.emptyTarget(nil, {12636}), 12636) check("tøm-mål: flow uden mål -> intet (silo)", W.emptyTarget(12658, {}), nil) check("tøm-mål: ingen liste -> intet", W.emptyTarget(12658, nil), nil) +-- ledig traktor (puljen/markarbejde/marksilo må låne den): kun solo fra et stoppet flow +check("ledig: intet flow", W.tractorFree({}), true) +check("ledig: kørende flow", W.tractorFree({hasRun = true}), false) +check("ledig: stoppet flow, solo", W.tractorFree({hasRun = true, stopped = true}), true) +check("ledig: stoppet flow, flow-vogn bagpå", W.tractorFree({hasRun = true, stopped = true, carriesFlowWagon = true}), false) +check("ledig: stoppet flow, tømmer/kører hjem", W.tractorFree({hasRun = true, stopped = true, homeTrip = true}), false) +-- venteårsag i loggen (før stod der altid "i brug andetsteds") +check("vent-årsag: traktor optaget", W.waitReason({tractorBusy = true}), "tractorBusy") +check("vent-årsag: vogn på en anden", W.waitReason({wagonAttached = true}), "wagonAttached") +check("vent-årsag: vogn reserveret", W.waitReason({wagonBusy = true}), "wagonReserved") print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1) diff --git a/tests/test_adPool.lua b/tests/test_adPool.lua index ecdf97e..a2235b5 100644 --- a/tests/test_adPool.lua +++ b/tests/test_adPool.lua @@ -25,6 +25,18 @@ check("hk: nok", P.powerOk(200, 180), true) check("hk: for lidt", P.powerOk(150, 180), false) check("hk: redskab uden data", P.powerOk(150, nil), true) check("hk: traktor uden data", P.powerOk(nil, 180), true) +-- kategori: markredskaber kun bag traktorer (in-game 29/9: Unimog, kategori "trucks", hentede kultivatoren) +check("kategori: traktor til kultivator", P.categoryOk("TRACTORSM", "cultivator"), true) +check("kategori: lastbil til kultivator", P.categoryOk("TRUCKS", "cultivator"), false) +check("kategori: lastbil til tømmevogn", P.categoryOk("TRUCKS", "unloader"), true) +check("kategori: teleskoplæsser til plov", P.categoryOk("TELELOADERVEHICLES", "plow"), false) +check("kategori: ukendt -> tilladt", P.categoryOk(nil, "cultivator"), true) +check("kategori: små bogstaver", P.categoryOk("tractorsL", "plow"), true) +local unimog = {id = "u", x = 0, z = 0, hp = 155, category = "TRUCKS"} +local fendt = {id = "f", x = 90, z = 0, hp = 250, category = "TRACTORSM"} +local cult = {{id = "c", role = "cultivator", roles = {cultivator = true}, x = 1, z = 0, neededHp = 120, name = "Cult"}} +local picked = P.pairFor("cultivator", 0, 0, cult, {unimog, fendt}, {}) +check("parring: nærmeste lastbil springes over", picked ~= nil and picked.tractor.id or nil, "f") -- Connect-markør til en parkering local markers = {{name = "Start", id = 1}, {name = "Bredal", id = 4}, {name = "Connect Bredal", id = 9}, {name = "connect plov 1", id = 12}} check("connect: Bredal", P.connectFor("Bredal", markers).id, 9)