fix(pulje): review v1.50 — egne tømmevogne virkede ikke under høst (global 'implement'), hentning kan afbrydes (høst slut/Stop/spiller), hjemtur ved tømmevogns-fejl, marksilo-lån kan ikke hænge i 'henter' (tidsgrænse, spiller, fejl i start), ingen ekstra parkeringstur med puljevogn, Stop holder en bar mark; regressionstest for fejlagtige globale (v1.50.1.0)

This commit is contained in:
2026-09-28 19:02:28 +02:00
parent 6f46bb543a
commit 5a2701fc62
11 changed files with 123 additions and 14 deletions
+5 -1
View File
@@ -40,7 +40,11 @@ end
ADFieldFleet.EXTRA_ROLES = {plow = {plow = true, cultivator = true}}
function ADFieldFleet.rolesFor(role)
return ADFieldFleet.EXTRA_ROLES[role]
local roles = ADFieldFleet.EXTRA_ROLES[role]
if roles == nil then return nil end
local copy = {}
for key, value in pairs(roles) do copy[key] = value end
return copy
end
-- Kandidater med rollen, sorteret nærmest først, delt i brugbare og afviste (med første grund).
+5
View File
@@ -159,6 +159,7 @@ end
function ADFieldModel.setTarget(field, fruitName)
local copy = copyField(field)
copy.plan.targetFruit = fruitName or ""
copy.held = nil
copy.plan.mode = copy.plan.targetFruit ~= "" and "auto" or "manual"
if not ADFieldModel.isWorking(field) and field.stage == "done" then
copy.stage = "idle"
@@ -172,6 +173,7 @@ end
function ADFieldModel.shouldStartBare(field, kind)
if field.plan.targetFruit == nil or field.plan.targetFruit == "" then return false end
-- kun fra idle: efter egen såning står marken "done" (nysået kan læses som bar) — ingen dobbelt-såning
if field.held and not field.startRequested then return false end
return kind == "bare" and field.stage == "idle"
end
@@ -277,6 +279,7 @@ end
-- currentFruit (valgfri): afgrøden på marken nu; med mål-afgrøde udfyldes planen ud fra den.
function ADFieldModel.start(field, kind, needs, currentFruit)
local copy = copyField(field)
copy.held = nil
copy.plan = ADFieldModel.applyTarget(copy.plan, kind, currentFruit)
local stages = ADFieldModel.workStages(copy.plan, kind, needs)
copy.stage = stages[1] or "done"
@@ -320,6 +323,8 @@ function ADFieldModel.stop(field)
end
copy.startRequested = false
copy.reason = nil
-- stoppet af spilleren: en bar mark med mål-afgrøde starter ikke selv igen (Start eller nyt mål slipper)
copy.held = true
return copy
end
+32 -6
View File
@@ -17,6 +17,7 @@ ADFieldSilos.CARGO_MIN_LITERS = 100 -- rest under dette tæller ikke som last
ADFieldSilos.FARM_RADIUS = 100
-- Siloer højst så langt fra en nabo hører til samme klynge (Farma-rækker: 15 m mellem siloer, 18 m mellem rækker)
ADFieldSilos.CLUSTER_LINK = 60
ADFieldSilos.FETCH_TIMEOUT_MS = 300000 -- så længe må en traktor være om at hente en puljevogn
ADFieldSilos.MOVING_SPEED = 0.00028 -- m/ms, samme grænse som AutoDrive
ADFieldSilos.FILE_NAME = "ADSmartPickup_fieldsilos.xml"
ADFieldSilos.ROOT = "ADSmartPickupFieldSilos"
@@ -340,7 +341,7 @@ local function fillTypeName(fillType)
return desc ~= nil and (desc.title or desc.name) or tostring(fillType)
end
local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
local function dispatchOne(adEnv, order, vehicle, silosById, storesById, isPool)
local module = vehicle.ad.stateModule
module:setMode(adEnv.AutoDrive.MODE_PICKUPANDDELIVER)
module:setFirstMarkerByWayPointId(order.siloMarkerId)
@@ -349,7 +350,9 @@ local function dispatchOne(adEnv, order, vehicle, silosById, storesById)
module:setFillType(order.fillType)
module.loopCounter = 1
if module.setLoopsDone ~= nil then module:setLoopsDone(0) end
ADFieldWork.enableParkAtJobFinished(vehicle)
-- puljevogn: ingen AD-parkering — ved turens slut sætter returnHome vognen af og kører traktoren hjem
-- (med AD-parkering kørte den først hjem med vognen og så tilbage efter den)
if not isPool then ADFieldWork.enableParkAtJobFinished(vehicle) end
-- rotation fra: ellers kan modens egne pickup-hooks sende vognen til en anden silo i mappen
if ADRunsController ~= nil and ADRunsController.setSetting ~= nil then
ADRunsController.setSetting(adEnv, vehicle, "rotateTargets", adEnv.AutoDrive.RT_NONE)
@@ -371,7 +374,6 @@ local function dispatchPool(adEnv, order, pair, silosById, storesById)
local owner = {siloId = order.siloId, storeId = order.storeId, siloName = silo ~= nil and silo.name or order.siloId,
fillType = order.fillType, liters = order.liters, startedMs = nowMs, lastProgressMs = nowMs, lastFill = 0, fetching = true}
loans[tractor] = owner
ADPool.reserve(owner, {pair.implement.id, pair.tractor.id})
ADHitch.fetch(adEnv, tractor, pair.implement.connectId, function(_, ok, detail)
ADPool.release(owner)
if loans[tractor] ~= owner then return end -- lånet blev afbrudt imens (Stop alt / flow slået fra)
@@ -385,8 +387,14 @@ local function dispatchPool(adEnv, order, pair, silosById, storesById)
if park ~= nil then ADFieldWork.startAd(adEnv, tractor, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
return
end
dispatchOne(adEnv, order, tractor, silosById, storesById)
local okDispatch, err = pcall(dispatchOne, adEnv, order, tractor, silosById, storesById, true)
if not okDispatch then
Logging.warning(ADFieldSilos.LOG_PREFIX .. "'%s' kunne ikke starte turen med puljevognen: %s", tostring(ADRuns.vehicleLabel(tractor)), tostring(err))
ADFieldSilos.releaseVehicle(adEnv, tractor, "failed")
end
end)
-- reservationen først når hentningen er sat i gang (fejler fetch, er der intet at frigive)
ADPool.reserve(owner, {pair.implement.id, pair.tractor.id})
Logging.info(ADFieldSilos.LOG_PREFIX .. "'%s' henter puljevognen '%s' til %d l %s i %s", tostring(ADRuns.vehicleLabel(tractor)),
tostring(pair.implement.name), math.floor(order.liters), fillTypeName(order.fillType), tostring(silo ~= nil and silo.name or order.siloId))
end
@@ -395,6 +403,11 @@ local function release(adEnv, vehicle, reason)
local loan = loans[vehicle]
loans[vehicle] = nil
if loan == nil then return end
if loan.fetching then
-- henter stadig puljevognen: afbryd koblingen og frigiv reservationen
if ADHitch ~= nil then ADHitch.cancel(vehicle) end
if ADPool ~= nil then ADPool.release(loan) end
end
if reason == "stuck" or reason == "failed" then
local k = ADFieldSiloPlanner.key(loan.siloId, loan.fillType)
paused[k] = nowMs + ADFieldSilos.PAUSE_MS
@@ -416,6 +429,10 @@ local function release(adEnv, vehicle, reason)
end
end
function ADFieldSilos.releaseVehicle(adEnv, vehicle, reason)
release(adEnv, vehicle, reason)
end
function ADFieldSilos.stopAll(adEnv)
local list = {}
for vehicle in pairs(loans) do table.insert(list, vehicle) end
@@ -431,8 +448,17 @@ local function checkLoans(adEnv)
else
local ok, err = pcall(function()
local loan = loans[vehicle]
-- henter puljevognen: AD holder stille under koblingen — ikke "færdig"
if loan.fetching then return end
-- henter puljevognen: AD holder stille under koblingen — ikke "færdig"; men spilleren kan overtage,
-- og en hentning der ikke bliver færdig (trafik, rute), opgives
if loan.fetching then
local controlled = vehicle.getIsControlled ~= nil and vehicle:getIsControlled() or false
if controlled then
release(adEnv, vehicle, "player")
elseif nowMs - (loan.startedMs or nowMs) > ADFieldSilos.FETCH_TIMEOUT_MS then
release(adEnv, vehicle, "stuck")
end
return
end
local stateModule = vehicle.ad ~= nil and vehicle.ad.stateModule or nil
local fill = fillLevel(adEnv, vehicle)
local vehicleState = {
+23 -4
View File
@@ -287,6 +287,10 @@ end
function ADFieldWork.release(assignment, phase)
restoreToolOffset(assignment)
if assignment.handle ~= nil then assignment.handle.cancelled = true end
-- henter stadig puljeredskabet: afbryd koblingen, så det ikke kobles på uden en opgave
if assignment.phase == "fetching" and assignment.hitch ~= nil and not assignment.hitch.done and ADHitch ~= nil then
ADHitch.cancel(assignment.vehicle)
end
assignment.phase = phase or "released"
if ADPool ~= nil and assignment.pool ~= nil then ADPool.release(assignment) end
ADFieldJobs.state.assignments[assignment.vehicleId] = nil
@@ -447,8 +451,6 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
local offNetwork = {}
local accept = function(candidate)
if roleAccept ~= nil and not roleAccept(candidate) then return false end
-- tømmevogn: skal kunne bære høstens vare
if role == "unloader" and not ADPool.canCarry(implement, info.harvestFillType) then return false end
-- manuel transport: spilleren kører den — afstand til AutoDrive-vejnettet er ligegyldig
if role == "harvester" and candidate.manual then return true end
local d = candidate.x ~= nil and ADFieldWork.distanceToNetwork(adEnv, candidate.x, candidate.z) or nil
@@ -697,6 +699,7 @@ local function beginUnloader(adEnv, field, info, assignment)
local store, why = ADFieldWork.chooseStore(adEnv, field, info, vehicle, fillType)
if store == nil then
ADFieldWork.release(assignment)
ADHitch.returnHome(adEnv, vehicle)
fail(field.id, "intet lager til høsten: " .. tostring(why))
return
end
@@ -716,11 +719,13 @@ local function beginUnloader(adEnv, field, info, assignment)
log(field.id, "aflæsningsvogn '%s' følger høsteren -> %s", ADRuns.vehicleLabel(vehicle), tostring(store.name))
else
ADFieldWork.release(assignment)
ADHitch.returnHome(adEnv, vehicle)
fail(field.id, "aflæsningsvogn: " .. tostring(reason))
end
end)
if handle == nil then
ADFieldWork.release(assignment)
ADHitch.returnHome(adEnv, vehicle)
fail(field.id, "aflæsningsvogn: " .. tostring(err))
return
end
@@ -1284,7 +1289,16 @@ end
-- Høsten er færdig: aflæsningsvognene stoppes; har de rest, kører den til lager, ellers parkering.
function ADFieldWork.windDownHelpers(adEnv, fieldId, stage)
for _, assignment in ipairs(ADFieldJobs.assignmentsFor(fieldId)) do
if assignment.helper and assignment.stage == stage then
if assignment.helper and assignment.stage == stage and assignment.phase == "fetching" then
-- høsten sluttede mens puljevognen blev hentet: afbryd, vognen tilbage hvis den nåede at komme på
local vehicle = assignment.vehicle
ADFieldWork.release(assignment)
if not ADHitch.returnHome(adEnv, vehicle) then
local park = parkMarkerId(adEnv, vehicle)
if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
end
log(fieldId, "tømmevogn '%s' afbrudt under hentning — høsten er færdig", ADRuns.vehicleLabel(vehicle))
elseif assignment.helper and assignment.stage == stage then
if assignment.handle ~= nil then assignment.handle.cancelled = true end
ADCpBridge.stop(assignment.vehicle)
assignment.phase = "working"
@@ -1315,7 +1329,12 @@ function ADFieldWork.stopAll(adEnv, fieldId)
for _, assignment in ipairs(ADFieldJobs.assignmentsFor(fieldId)) do
pcall(stopAd, assignment.vehicle)
ADCpBridge.stop(assignment.vehicle)
local wasFetching = assignment.phase == "fetching"
ADFieldWork.release(assignment)
pcall(ADHitch.returnHome, adEnv, assignment.vehicle)
local okHome, pooled = pcall(ADHitch.returnHome, adEnv, assignment.vehicle)
if wasFetching and not (okHome and pooled) then
local park = parkMarkerId(adEnv, assignment.vehicle)
if park ~= nil then pcall(startAd, adEnv, assignment.vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
end
end
end
+10
View File
@@ -253,6 +253,16 @@ local function start(adEnv, vehicle, markerId, action, onDone)
return job
end
-- Afbryd et hente-/koble-job (Stop alt, høsten slut, spilleren overtager): intet kobles på bagefter, og
-- onDone kaldes ikke. -> true hvis der var et job.
function ADHitch.cancel(vehicle)
local had = jobs[vehicle] ~= nil
jobs[vehicle] = nil
if ADPool ~= nil and ADPool.setReturning ~= nil then ADPool.setReturning(vehicle, false) end
if had and vehicle.stopAutoDrive ~= nil then pcall(vehicle.stopAutoDrive, vehicle) end
return had
end
-- Kør til en Connect-markør og kobl på. onDone(vehicle, ok, redskab eller årsag) kaldes én gang.
function ADHitch.fetch(adEnv, vehicle, markerId, onDone)
return start(adEnv, vehicle, markerId, "attach", onDone)
+13 -2
View File
@@ -277,8 +277,18 @@ function ADPool.readNeededHp(vehicle)
return item.specs ~= nil and ADPool.numberOf(item.specs.neededPower) or nil
end
-- Traktorens effekt i hk: motorens topeffekt (kW) omregnet; ellers butiksdata.
local tractorHpCache = setmetatable({}, {__mode = "k"}) -- vehicle -> hk eller false
-- Traktorens effekt i hk: motorens topeffekt (kW) omregnet; ellers butiksdata. Gemmes pr. traktor.
local function tractorHpOf(vehicle)
local cached = tractorHpCache[vehicle]
if cached ~= nil then return cached or nil end
local value = ADPool.readTractorHp(vehicle)
tractorHpCache[vehicle] = value or false
return value
end
function ADPool.readTractorHp(vehicle)
local motor = vehicle.spec_motorized ~= nil and vehicle.spec_motorized.motor or nil
local kw = motor ~= nil and motor.peakMotorPower or nil
if type(kw) == "number" and kw > 0 then return kw * 1.35962 end
@@ -385,8 +395,9 @@ function ADPool.overview(adEnv)
local state = ADFieldJobs.state
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
local id = ADRunsController.getVehicleId(vehicle)
-- en traktor med et redskab der giver den en markrolle, står allerede i flåden som kombination
if id ~= nil and vehicle.spec_attacherJoints ~= nil and ADRunsController.getRunForVehicle(vehicle) == nil
and ADPool.canPull(ADPool.ownRole(vehicle)) then
and ADPool.canPull(ADPool.ownRole(vehicle)) and ADFieldFleet.roleFromSpecs(ADCpBridge.getSpecs(vehicle)) == nil then
local x, z = positionOf(vehicle)
local distance = ADFieldWork.distanceToNetwork(adEnv, x, z)
local assignment = state.assignments[id]
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107">
<author>masterdraco</author>
<version>1.50.0.0</version>
<version>1.50.1.0</version>
<title>
<en>AD Smart Pickup</en>
</title>