From 2d170a30ffb75335be2350d10f1b8f787de7dc82 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Mon, 28 Sep 2026 18:05:42 +0200 Subject: [PATCH] =?UTF-8?q?feat(pulje):=20markarbejdet=20sender=20en=20sol?= =?UTF-8?q?o-traktor=20efter=20et=20puljeredskab=20n=C3=A5r=20ingen=20f?= =?UTF-8?q?=C3=A6rdig=20kombination=20er=20ledig=20(fase=20fetching)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- FS25_ADSmartPickup/adFieldJobs.lua | 2 +- FS25_ADSmartPickup/adFieldWork.lua | 99 +++++++++++++++++++++++++++-- FS25_ADSmartPickup/l10n/l10n_da.xml | 4 ++ FS25_ADSmartPickup/l10n/l10n_en.xml | 4 ++ tests/test_adFieldJobs.lua | 2 + 5 files changed, 106 insertions(+), 5 deletions(-) diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index d67cd20..99b3e20 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -28,7 +28,7 @@ ADFieldJobs.now = 0 ADFieldJobs.loaded = false -- awaitingPlayer: høster med manuel transport, spilleren kører den til marken (tæller som i gang) -ADFieldJobs.ACTIVE_PHASES = {driving = true, starting = true, waitingCourse = true, working = true, awaitingPlayer = true} +ADFieldJobs.ACTIVE_PHASES = {fetching = true, driving = true, starting = true, waitingCourse = true, working = true, awaitingPlayer = true} -- Andel græs i mellemhøjde (høstbart, men halv ydelse) der får en afbrudt slåning til at vente. ADFieldJobs.REGROW_RATIO = 0.1 diff --git a/FS25_ADSmartPickup/adFieldWork.lua b/FS25_ADSmartPickup/adFieldWork.lua index 9f42c8f..725bc9c 100644 --- a/FS25_ADSmartPickup/adFieldWork.lua +++ b/FS25_ADSmartPickup/adFieldWork.lua @@ -129,6 +129,13 @@ end -- ---- flåde ---- +-- Optaget for markarbejdet: vores egen opgave, AutoDrive/Courseplay kører den, lånt af marksilo-flowet, +-- eller på hjemtur fra puljen (sætter redskab af). +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)) +end + function ADFieldWork.getRigs(adEnv) local rigs = {} local state = ADFieldJobs.state @@ -144,12 +151,12 @@ function ADFieldWork.getRigs(adEnv) enabled = ADFieldFleet.isEnabled(state.fleetMode, state.fleet, id), -- optaget: vores egen opgave, eller AutoDrive/Courseplay kører den allerede (fx spillerens -- egen parkeringstur) — den må ikke kapres midt i en anden kørsel - busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle) - or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)), + busy = isBusyVehicle(vehicle, id), -- hvorfor optaget (til loggen, når ingen maskine kan sendes ud) busyWhy = (state.assignments[id] ~= nil and ("opgave F" .. tostring(state.assignments[id].fieldId))) or (isAdActive(vehicle) and "AutoDrive kører") or (ADCpBridge.isWorking(vehicle) and "Courseplay kører") - or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle) and "lånt af marksilo-flow") or nil, + or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle) and "lånt af marksilo-flow") + or (ADPool ~= nil and ADPool.isReturning(vehicle) and "sætter redskab af") or nil, controlled = isControlledByPlayer(vehicle), -- manuel transport: spilleren kører den til marken (for bred til vejene med skærebord) manual = (state.manual or {})[id] == true, @@ -279,6 +286,7 @@ function ADFieldWork.release(assignment, phase) restoreToolOffset(assignment) if assignment.handle ~= nil then assignment.handle.cancelled = true end assignment.phase = phase or "released" + if ADPool ~= nil and assignment.pool ~= nil then ADPool.release(assignment) end ADFieldJobs.state.assignments[assignment.vehicleId] = nil end @@ -379,6 +387,44 @@ local function askPlayer(field, info, assignment, withDialog) end end +-- Puljekombination: nærmeste puljeredskab til marken + nærmeste ledige traktor med nok hk. Traktoren kører til +-- "Connect X", kobler på og fortsætter så som en almindelig udsendelse (fase fetching -> driving). +function ADFieldWork.dispatchPool(adEnv, field, info, role, roleAccept) + local implements, tractors = ADPool.collect(adEnv, function(vehicle) + return isBusyVehicle(vehicle, ADRunsController.getVehicleId(vehicle)) + end) + local accept = function(implement) + -- samme felter som getRigs giver sæt, så markens krav (frø, afgrøde, ukrudt) tjekkes på redskabet selv + local candidate = {role = implement.role, roles = implement.roles, vehicle = implement.vehicle} + if role == "seeder" then + candidate.seedFruits = ADCpBridge.getSeedFruits(implement.vehicle) + candidate.hasSeeds = ADCpBridge.hasSeeds(implement.vehicle) + end + if role == "weeder" then candidate.mechanical = ADCpBridge.isMechanicalWeeder(implement.vehicle) end + if roleAccept ~= nil and not roleAccept(candidate) then return false end + local required = ADFieldModel.requiredWidth(field, role) + return (implement.width or 0) <= 0 or ADFieldModel.checkWidth(role, implement.width, required) + end + local pair, why, args = ADPool.pairFor(role, info.x, info.z, implements, tractors, ADPool.blocked(now()), accept) + if pair == nil then return false, why, args end + local tractor, implement = pair.tractor, pair.implement + local assignment = { + vehicleId = tractor.id, vehicle = tractor.vehicle, fieldId = field.id, role = role, stage = field.stage, + helper = false, phase = "fetching", since = now(), width = implement.width, groupSize = 1, leader = true, + startX = tractor.x, startZ = tractor.z, + pool = {implementId = implement.id, implementName = implement.name, connectId = implement.connectId}, + hitch = {done = false}, + } + ADFieldJobs.state.assignments[tractor.id] = assignment + ADPool.reserve(assignment, {implement.id, tractor.id}) + ADHitch.fetch(adEnv, tractor.vehicle, implement.connectId, function(_, ok, detail) + assignment.hitch = {done = true, ok = ok, detail = detail} + end) + note(field.id, {reason = {key = "spu_field_reason_fetching", name = implement.name}}) + log(field.id, "%s: '%s' henter '%s' (%s)", role, tractor.name, implement.name, "pulje") + return true +end + function ADFieldWork.dispatch(adEnv, field, info, decision) local role = decision.role local ok, reason = preflight(field, role) @@ -414,6 +460,20 @@ function ADFieldWork.dispatch(adEnv, field, info, decision) rigs = rig ~= nil and {rig} or nil end if rigs == nil then + -- ingen færdig kombination: en ledig solo-traktor henter et puljeredskab (kun hovedopgaver, ikke tømmevogne) + if not decision.helper and (decision.count or 1) == 1 and ADPool ~= nil + and (why == "noRig" or why == "allBusy" or why == "cannotDo") then + local pooled, poolWhy, poolArgs = ADFieldWork.dispatchPool(adEnv, field, info, role, roleAccept) + if pooled then return end + if poolWhy == "tooWeak" then + note(field.id, {reason = {text = string.format("ingen ledig traktor med ≥%.0f hk til %s-redskabet", + (poolArgs or {})[1] or 0, tostring(role))}, retryAt = now() + ADFieldWork.NO_RIG_RETRY_MS}) + return + end + if poolWhy == "noPoolTractor" and why ~= "allBusy" then + why, args = poolWhy, poolArgs + end + end if #offNetwork > 0 and (why == "cannotDo" or why == "noRig") then -- de eneste med rollen står uden for AD-vejnettet note(field.id, {reason = {text = string.format("ingen %s kan køre til %s — står for langt fra AutoDrive-vejnettet (%s)", @@ -1093,7 +1153,38 @@ function ADFieldWork.follow(adEnv, assignment) local field = ADFieldJobs.getField(assignment.fieldId) local info = ADFields.getInfo(assignment.fieldId) wake(vehicle) - if assignment.phase == "driving" then + if assignment.phase == "fetching" then + local hitch = assignment.hitch + if not hitch.done then return end + ADPool.release(assignment) + if not hitch.ok then + -- koblingen fejlede: redskabet i karantæne, traktoren hjem, marken prøver igen + ADPool.quarantine(assignment.pool.implementId, now()) + ADFieldWork.release(assignment) + note(assignment.fieldId, {retryAt = now() + ADFieldWork.NO_RIG_RETRY_MS, + reason = {text = string.format("kunne ikke koble '%s' på (%s) — prøver et andet om lidt", + tostring(assignment.pool.implementName), tostring(hitch.detail))}}) + local park = parkMarkerId(adEnv, vehicle) + if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end + return + end + -- koblet på: bredden med redskabet på (for bred -> hjem igen) + local width = ADCpBridge.getWorkWidth(vehicle) + local okWidth, widthReason, widthArgs = ADFieldModel.checkWidth(assignment.role, width, ADFieldModel.requiredWidth(field, assignment.role)) + if not okWidth then + ADFieldWork.release(assignment) + note(assignment.fieldId, {retryAt = now() + ADFieldModel.RETRY_MS, reason = {key = "spu_field_reason_" .. tostring(widthReason), role = assignment.role, args = widthArgs}}) + ADHitch.returnHome(adEnv, vehicle) + return + end + assignment.width, assignment.phase, assignment.since = width, "driving", now() + assignment.startX, assignment.startZ = vehiclePosition(vehicle) + startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, info.markerId, nil) + assignment.drive = {mode = adEnv.AutoDrive.MODE_DRIVETO, first = info.markerId} + note(assignment.fieldId, {reason = false}) + log(assignment.fieldId, "%s '%s' har '%s' på — kører til %s (%.1f m)", assignment.role, ADRuns.vehicleLabel(vehicle), + tostring(assignment.pool.implementName), tostring(info.markerName), width or 0) + elseif assignment.phase == "driving" then if not isAdActive(vehicle) and now() - assignment.since > 2000 then local x, z = vehiclePosition(vehicle) local wayPoint = info ~= nil and adEnv.ADGraphManager:getWayPointById(info.markerId) or nil diff --git a/FS25_ADSmartPickup/l10n/l10n_da.xml b/FS25_ADSmartPickup/l10n/l10n_da.xml index 0a2d595..3f8635e 100644 --- a/FS25_ADSmartPickup/l10n/l10n_da.xml +++ b/FS25_ADSmartPickup/l10n/l10n_da.xml @@ -85,6 +85,10 @@ + + + + diff --git a/FS25_ADSmartPickup/l10n/l10n_en.xml b/FS25_ADSmartPickup/l10n/l10n_en.xml index 2c5cc30..9ce1e9c 100644 --- a/FS25_ADSmartPickup/l10n/l10n_en.xml +++ b/FS25_ADSmartPickup/l10n/l10n_en.xml @@ -85,6 +85,10 @@ + + + + diff --git a/tests/test_adFieldJobs.lua b/tests/test_adFieldJobs.lua index a2fd084..5059575 100644 --- a/tests/test_adFieldJobs.lua +++ b/tests/test_adFieldJobs.lua @@ -198,5 +198,7 @@ do check("NP uden position -> første", ADFieldJobs.nearestPoint(pts, nil, nil).x, 1600) end +-- pulje: en traktor der henter et redskab tæller som i gang (ellers sendes endnu en ud) +check("pulje: henter -> ingenting", J.decide(mowing, info, {{phase = "fetching"}}, 0).action, "none") print(failures == 0 and "\nALLE TESTS OK" or ("\n" .. failures .. " FEJL")) os.exit(failures == 0 and 0 or 1)