feat(markarbejde): manuel transport af brede høstere — flueben pr. høster i markflåden; moden beder spilleren køre den til marken og montere skærebordet (dialog + påmindelse hvert 5. min), overtager når den står på marken med passende skærebord og spilleren er steget ud (eller ved Start nu), tømmevogne først når høsten kører, ingen AD-parkering bagefter (v1.38.0.0)

This commit is contained in:
2026-09-27 14:05:27 +02:00
parent 12647087ef
commit 6afcae6fe1
11 changed files with 231 additions and 34 deletions
+87 -23
View File
@@ -138,6 +138,8 @@ function ADFieldWork.getRigs(adEnv)
busy = state.assignments[id] ~= nil or isAdActive(vehicle) or ADCpBridge.isWorking(vehicle)
or (ADFieldSilos ~= nil and ADFieldSilos.isLoaned(vehicle)),
controlled = isControlledByPlayer(vehicle),
-- manuel transport: spilleren kører den til marken (for bred til vejene med skærebord)
manual = (state.manual or {})[id] == true,
width = ADCpBridge.getWorkWidth(vehicle), x = x, z = z,
-- tom spreder kan flere ting (gødning og kalk): alle dens roller
roles = specs.sprayRoles, filledRole = specs.filledRole, dumpable = specs.dumpable,
@@ -300,7 +302,7 @@ end
local function acceptFor(field, info, role)
if role == "harvester" then
local fruit = info.fruitIndex
return function(candidate) return fruit ~= nil and candidate.fruits ~= nil and candidate.fruits[fruit] == true end
return function(candidate) return ADManualTransport.acceptsHarvester(candidate, fruit) end
elseif role == "seeder" then
local fruitIndex = ADFieldWork.sowFruitIndex(field)
return function(candidate) return candidate.hasSeeds and fruitIndex ~= nil and candidate.seedFruits[fruitIndex] == true end
@@ -351,6 +353,19 @@ local function refillPlan(adEnv, fieldId, vehicle, role, x, z)
return {fillType = fillType, empty = empty, refillId = refillId}
end
-- Manuel transport: bed spilleren køre høsteren til marken (dialog første gang, ellers besked i hjørnet).
local function askPlayer(field, info, assignment, withDialog)
local text = string.format("F%s skal høstes: kør '%s' til %s, montér skærebordet og stig ud — så overtager Smart Pickup",
tostring(field.id), ADRuns.vehicleLabel(assignment.vehicle), tostring(info.markerName or ("F" .. tostring(field.id))))
assignment.remindedAt = now()
log(field.id, "venter på spilleren: %s", text)
if withDialog and InfoDialog ~= nil and InfoDialog.show ~= nil then
pcall(InfoDialog.show, "Smart Pickup: " .. text)
else
ADFieldJobs.notify(text, true)
end
end
function ADFieldWork.dispatch(adEnv, field, info, decision)
local role = decision.role
local ok, reason = preflight(field, role)
@@ -409,26 +424,34 @@ function ADFieldWork.dispatch(adEnv, field, info, decision)
groupSize = decision.helper and 1 or count, lane = count > 1 and lanes[index] or nil, leader = index == 1,
fillType = decision.helper and info.harvestFillType or nil,
}
local refill = refillPlan(adEnv, field.id, rig.vehicle, role, info.x, info.z)
if refill ~= nil and refill.empty and refill.refillId == nil then
-- tom og intet sted at fylde op: ud på marken ville den bare køre kurset uden at sprede
fail(field.id, string.format("'%s' er tom for %s og der er ingen påfyldning med AutoDrive-markør — fyld den op", rig.label, fillTypeName(refill.fillType)))
return
end
assignment.startX, assignment.startZ = rig.x, rig.z
ADFieldJobs.state.assignments[rig.id] = assignment
if refill ~= nil and refill.empty then
-- AutoDrive LOAD: påfyldningen (2. markør), fyld op, videre til F-markøren (1.) — så Courseplay
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_LOAD, info.markerId, refill.refillId, refill.fillType)
assignment.drive = {mode = adEnv.AutoDrive.MODE_LOAD, first = info.markerId, second = refill.refillId, fillType = refill.fillType}
local marker = adEnv.ADGraphManager:getMapMarkerByWayPointId(refill.refillId)
log(field.id, "%s '%s' er tom — fyldes med %s ved %s og kører så til %s", role, rig.label, fillTypeName(refill.fillType),
tostring(marker ~= nil and marker.name or refill.refillId), tostring(info.markerName))
if role == "harvester" and rig.manual and not assignment.helper then
-- for bred til vejene: spilleren kører den selv til marken og monterer skærebordet
assignment.phase, assignment.manual = "awaitingPlayer", true
assignment.startX, assignment.startZ = rig.x, rig.z
ADFieldJobs.state.assignments[rig.id] = assignment
askPlayer(field, info, assignment, true)
else
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_DRIVETO, info.markerId, nil)
assignment.drive = {mode = adEnv.AutoDrive.MODE_DRIVETO, first = info.markerId}
log(field.id, "%s '%s' sendt til %s (%.1f m%s)", role, rig.label, tostring(info.markerName), rig.width or 0,
assignment.lane ~= nil and string.format(", bane %d", assignment.lane) or "")
local refill = refillPlan(adEnv, field.id, rig.vehicle, role, info.x, info.z)
if refill ~= nil and refill.empty and refill.refillId == nil then
-- tom og intet sted at fylde op: ud på marken ville den bare køre kurset uden at sprede
fail(field.id, string.format("'%s' er tom for %s og der er ingen påfyldning med AutoDrive-markør — fyld den op", rig.label, fillTypeName(refill.fillType)))
return
end
assignment.startX, assignment.startZ = rig.x, rig.z
ADFieldJobs.state.assignments[rig.id] = assignment
if refill ~= nil and refill.empty then
-- AutoDrive LOAD: påfyldningen (2. markør), fyld op, videre til F-markøren (1.) — så Courseplay
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_LOAD, info.markerId, refill.refillId, refill.fillType)
assignment.drive = {mode = adEnv.AutoDrive.MODE_LOAD, first = info.markerId, second = refill.refillId, fillType = refill.fillType}
local marker = adEnv.ADGraphManager:getMapMarkerByWayPointId(refill.refillId)
log(field.id, "%s '%s' er tom — fyldes med %s ved %s og kører så til %s", role, rig.label, fillTypeName(refill.fillType),
tostring(marker ~= nil and marker.name or refill.refillId), tostring(info.markerName))
else
startAd(adEnv, rig.vehicle, adEnv.AutoDrive.MODE_DRIVETO, info.markerId, nil)
assignment.drive = {mode = adEnv.AutoDrive.MODE_DRIVETO, first = info.markerId}
log(field.id, "%s '%s' sendt til %s (%.1f m%s)", role, rig.label, tostring(info.markerName), rig.width or 0,
assignment.lane ~= nil and string.format(", bane %d", assignment.lane) or "")
end
end
end
note(field.id, {reason = false})
@@ -790,14 +813,53 @@ local function finishAssignment(adEnv, assignment)
end
log(assignment.fieldId, "%s '%s' færdig", assignment.role, ADRuns.vehicleLabel(vehicle))
-- læssevogn/påfyldte sæt parkeres af AutoDrive selv (onCpFinished med parkering sat)
local park = not assignment.adHandlesEnd and parkMarkerId(adEnv, vehicle) or nil
-- manuel transport: ALDRIG ud på vejen med AutoDrive (skærebordet sidder på) — spilleren henter den
local park = not assignment.adHandlesEnd and not assignment.manual and parkMarkerId(adEnv, vehicle) or nil
if park ~= nil then startAd(adEnv, vehicle, adEnv.AutoDrive.MODE_DRIVETO, park, nil) end
if assignment.manual then
ADFieldJobs.notify(string.format("F%s høstet — afmontér skærebordet og kør '%s' hjem", tostring(assignment.fieldId), ADRuns.vehicleLabel(vehicle)), true)
end
restoreToolOffset(assignment)
assignment.phase = "finished"
table.insert(ADFieldJobs.finished, assignment)
ADFieldJobs.state.assignments[assignment.vehicleId] = nil
end
local MANUAL_REASON_TEXT = {
notThere = "kør den til marken",
noHeader = "montér skærebordet",
wrongHeader = "skærebordet kan ikke høste afgrøden — montér det rigtige",
playerInside = "stig ud, så overtager Smart Pickup",
}
-- Manuel transport: overtag når høsteren står på marken med passende skærebord og spilleren er steget ud
-- (eller når spilleren trykker "Start nu"); påmind hvert 5. minut indtil da.
local function followManual(adEnv, field, info, assignment)
if field == nil or info == nil then return ADFieldWork.release(assignment) end
local vehicle = assignment.vehicle
local x, z = vehiclePosition(vehicle)
local atField = #(info.verts or {}) >= 3 and ADFieldJobs.insidePolygon(x, z, info.verts)
if not atField and info.markerId ~= nil then
local wayPoint = adEnv.ADGraphManager:getWayPointById(info.markerId)
atField = wayPoint ~= nil and math.sqrt((wayPoint.x - x) ^ 2 + (wayPoint.z - z) ^ 2) <= ADFieldWork.ARRIVE_DISTANCE
end
local forced = ADFieldJobs.takeoverRequested[field.id] == true
ADFieldJobs.takeoverRequested[field.id] = nil
local ok, why = ADManualTransport.readiness({atField = atField, fruits = ADCpBridge.getHarvestFruits(vehicle) or {},
fruit = info.fruitIndex, controlled = isControlledByPlayer(vehicle), forced = forced})
if ok then
log(field.id, "overtager '%s'%s", ADRuns.vehicleLabel(vehicle), forced and " (Start nu)" or "")
return beginWork(adEnv, field, info, assignment)
end
local reason = MANUAL_REASON_TEXT[why] or tostring(why)
note(field.id, {reason = {text = string.format("venter på dig: '%s' — %s", ADRuns.vehicleLabel(vehicle), reason)}})
if forced then
ADFieldJobs.notify(string.format("F%s: kan ikke starte '%s' endnu — %s", tostring(field.id), ADRuns.vehicleLabel(vehicle), reason), true)
elseif ADManualTransport.shouldRemind(assignment.remindedAt, now()) then
askPlayer(field, info, assignment, false)
end
end
local function followWorking(adEnv, field, assignment)
local vehicle = assignment.vehicle
if assignment.role == "rake" and field.windrowOffset == nil and now() - assignment.since >= ADFieldWork.WINDROW_MEASURE_MS
@@ -859,7 +921,7 @@ function ADFieldWork.resumeAfterLoad(adEnv)
if fieldId ~= nil then
ADFieldJobs.state.assignments[rig.id] = {vehicleId = rig.id, vehicle = vehicle, fieldId = fieldId, role = rig.role,
stage = stageById[fieldId], helper = false, phase = "working", since = now(), width = rig.width,
groupSize = 1, leader = true, resumed = true}
groupSize = 1, leader = true, resumed = true, manual = rig.manual == true}
-- lager til læssevognens rest, som ved en normal start
local assignment = ADFieldJobs.state.assignments[rig.id]
local field, info = ADFieldJobs.getField(fieldId), ADFields.getInfo(fieldId)
@@ -886,7 +948,7 @@ function ADFieldWork.follow(adEnv, assignment)
-- førersædet og kigge må ikke slippe det (in-game 25/9).
local steeredByAi = ADCpBridge.isWorking(vehicle) or isAdActive(vehicle) or assignment.phase == "starting"
local speed = vehicle.getLastSpeed ~= nil and vehicle:getLastSpeed() or 0
if isControlledByPlayer(vehicle) and not steeredByAi and speed > ADFieldWork.PLAYER_DRIVING_KMH then
if assignment.phase ~= "awaitingPlayer" and isControlledByPlayer(vehicle) and not steeredByAi and speed > ADFieldWork.PLAYER_DRIVING_KMH then
log(assignment.fieldId, "'%s' frigivet — spilleren kører det", ADRuns.vehicleLabel(vehicle))
return ADFieldWork.release(assignment)
end
@@ -935,6 +997,8 @@ function ADFieldWork.follow(adEnv, assignment)
end
elseif assignment.phase == "working" then
followWorking(adEnv, field, assignment)
elseif assignment.phase == "awaitingPlayer" then
followManual(adEnv, field, info, assignment)
end
end