feat(marker): vender/river/læssevogn genbruger slåmaskinens Courseplay-kursus (v1.22.0.0)

In-game 25/9: læssevognens nye CP-kursus lå forskudt i forhold til skårene -> ikke samlet korrekt.
Slåkurset gemmes pr. mark (kopi) og kopieres til næste trin med vehicle:cpCopyCourse; efter
genindlæsning hentes det fra slåmaskinen (mowVehicle gemt i ADSmartPickup_fields.xml).
This commit is contained in:
2026-09-25 23:25:55 +02:00
parent 048f5e6547
commit 4c88118e56
9 changed files with 78 additions and 22 deletions
+28 -8
View File
@@ -74,7 +74,16 @@ function ADCpBridge.isWorking(vehicle)
return vehicle.getIsAIActive ~= nil and vehicle:getIsAIActive() or false return vehicle.getIsAIActive ~= nil and vehicle:getIsAIActive() or false
end end
function ADCpBridge.start(vehicle, fieldX, fieldZ, onDone) -- Køretøjets nuværende Courseplay-kursus (slåmaskinens kursus genbruges af de næste trin).
function ADCpBridge.getCourse(vehicle)
if vehicle == nil or vehicle.getFieldWorkCourse == nil then return nil end
local ok, course = pcall(vehicle.getFieldWorkCourse, vehicle)
return ok and course or nil
end
-- course (valgfri): et færdigt kursus (slåmaskinens) der kopieres til køretøjet i stedet for at lave et
-- nyt — så vender/river/læssevogn kører præcis slåmaskinens baner (in-game 25/9: nyt kursus lå forskudt).
function ADCpBridge.start(vehicle, fieldX, fieldZ, onDone, course)
if not ADCpBridge.isAvailable() then if not ADCpBridge.isAvailable() then
return nil, "Courseplay (FIELDWORK_CP) findes ikke" return nil, "Courseplay (FIELDWORK_CP) findes ikke"
end end
@@ -93,16 +102,27 @@ function ADCpBridge.start(vehicle, fieldX, fieldZ, onDone)
handle.finished = true handle.finished = true
if not handle.cancelled then onDone(ok, reason) end if not handle.cancelled then onDone(ok, reason) end
end end
local function startWithCourse()
job:setValues()
local ok, err = job:validate(farmId)
if not ok then return finish(false, "Courseplay: " .. tostring(err)) end
g_currentMission.aiSystem:startJob(job, farmId)
finish(true, handle.copiedCourse and "copied" or nil)
end
local function generateAndStart() local function generateAndStart()
if handle.cancelled then return end if handle.cancelled then return end
job:onClickGenerateFieldWorkCourse(function(course) if course ~= nil and vehicle.cpCopyCourse ~= nil then
local copied, copyError = pcall(vehicle.cpCopyCourse, vehicle, course)
if copied then
handle.copiedCourse = true
return startWithCourse()
end
Logging.warning("ADSmartPickup: kunne ikke kopiere slåkursus: %s — laver nyt", tostring(copyError))
end
job:onClickGenerateFieldWorkCourse(function(generated)
if handle.cancelled then return end if handle.cancelled then return end
if not course then return finish(false, "Courseplay kunne ikke lave kursus") end if not generated then return finish(false, "Courseplay kunne ikke lave kursus") end
job:setValues() startWithCourse()
local ok, err = job:validate(farmId)
if not ok then return finish(false, "Courseplay: " .. tostring(err)) end
g_currentMission.aiSystem:startJob(job, farmId)
finish(true)
end) end)
end end
handle.onBoundary = function(_, success, message) handle.onBoundary = function(_, success, message)
+25 -3
View File
@@ -14,6 +14,9 @@ ADFieldJobs.PLAYER_DRIVING_KMH = 3
-- Courseplay kan efterlade hjørner på skæve marker: efter så mange færdige slå-ture går marken videre. -- Courseplay kan efterlade hjørner på skæve marker: efter så mange færdige slå-ture går marken videre.
ADFieldJobs.MAX_MOW_PASSES = 2 ADFieldJobs.MAX_MOW_PASSES = 2
ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}} ADFieldJobs.state = {fields = {}, fleet = {}, assignments = {}}
-- mark-id -> kopi af slåmaskinens Courseplay-kursus (kun i hukommelsen; efter genindlæsning hentes det
-- igen fra slåmaskinen, som CP selv gemmer kurset på)
ADFieldJobs.courses = {}
ADFieldJobs.timer = 0 ADFieldJobs.timer = 0
ADFieldJobs.now = 0 ADFieldJobs.now = 0
ADFieldJobs.loaded = false ADFieldJobs.loaded = false
@@ -255,6 +258,18 @@ local function dispatch(adEnv, field, info, role)
note(field.id, {reason = false}) note(field.id, {reason = false})
end end
-- Slåkurset for marken: gemt kopi, ellers slåmaskinens nuværende kursus (efter genindlæsning).
local function mowCourse(adEnv, field)
if ADFieldJobs.courses[field.id] ~= nil then return ADFieldJobs.courses[field.id] end
if field.mowVehicleId == nil then return nil end
for _, vehicle in ipairs(ADRunsController.getFarmVehicles(adEnv)) do
if ADRunsController.getVehicleId(vehicle) == field.mowVehicleId then
return ADCpBridge.getCourse(vehicle)
end
end
return nil
end
local function beginWork(adEnv, field, info, assignment) local function beginWork(adEnv, field, info, assignment)
local vehicle = assignment.vehicle local vehicle = assignment.vehicle
if assignment.role == "collector" then if assignment.role == "collector" then
@@ -283,20 +298,27 @@ local function beginWork(adEnv, field, info, assignment)
local point = info.points[1] or {x = info.x, z = info.z} local point = info.points[1] or {x = info.x, z = info.z}
assignment.phase = "starting" assignment.phase = "starting"
assignment.since = ADFieldJobs.now assignment.since = ADFieldJobs.now
local course = assignment.role ~= "mower" and mowCourse(adEnv, field) or nil
if assignment.role ~= "mower" and course == nil then
log(field.id, "intet slåkursus kendt — Courseplay laver et nyt kursus til %s", assignment.role)
end
local handle, err = ADCpBridge.start(vehicle, point.x, point.z, function(ok, reason) local handle, err = ADCpBridge.start(vehicle, point.x, point.z, function(ok, reason)
if ok then if ok then
assignment.phase = "working" assignment.phase = "working"
assignment.since = ADFieldJobs.now assignment.since = ADFieldJobs.now
if assignment.role == "mower" then if assignment.role == "mower" then
note(field.id, {mowWidth = assignment.width}) note(field.id, {mowWidth = assignment.width, mowVehicleId = assignment.vehicleId})
local mowed = ADCpBridge.getCourse(vehicle)
local copied = mowed ~= nil and mowed.copy ~= nil and select(2, pcall(mowed.copy, mowed)) or mowed
ADFieldJobs.courses[field.id] = type(copied) == "table" and copied or mowed
end end
log(field.id, "Courseplay startet (%s)", assignment.role) log(field.id, "Courseplay startet (%s%s)", assignment.role, reason == "copied" and ", slåkursus kopieret" or "")
else else
release(assignment) release(assignment)
note(field.id, {retryAt = ADFieldJobs.now + ADFieldModel.RETRY_MS, reason = {text = tostring(reason)}}) note(field.id, {retryAt = ADFieldJobs.now + ADFieldModel.RETRY_MS, reason = {text = tostring(reason)}})
log(field.id, "Courseplay fejlede: %s", tostring(reason)) log(field.id, "Courseplay fejlede: %s", tostring(reason))
end end
end) end, course)
if handle == nil then if handle == nil then
release(assignment) release(assignment)
log(field.id, "Courseplay kunne ikke startes: %s", tostring(err)) log(field.id, "Courseplay kunne ikke startes: %s", tostring(err))
+3
View File
@@ -92,6 +92,7 @@ function ADFieldModel.start(field)
copy.retryAt = 0 copy.retryAt = 0
copy.reason = nil copy.reason = nil
copy.mowWidth = nil copy.mowWidth = nil
copy.mowVehicleId = nil
return copy return copy
end end
@@ -156,6 +157,7 @@ function ADFieldModel.toRecord(field)
id = field.id, stage = field.stage, mode = field.plan.mode, product = field.plan.product, id = field.id, stage = field.stage, mode = field.plan.mode, product = field.plan.product,
rake = field.plan.rake and 1 or 0, store = table.concat(field.plan.storeBuildingIds, " "), rake = field.plan.rake and 1 or 0, store = table.concat(field.plan.storeBuildingIds, " "),
mowWidth = field.mowWidth or 0, mowWidth = field.mowWidth or 0,
mowVehicle = field.mowVehicleId or "",
} }
end end
@@ -167,5 +169,6 @@ function ADFieldModel.fromRecord(record)
field.plan.rake = record.rake == 1 or record.rake == true field.plan.rake = record.rake == 1 or record.rake == true
for id in string.gmatch(record.store or "", "%S+") do table.insert(field.plan.storeBuildingIds, id) end for id in string.gmatch(record.store or "", "%S+") do table.insert(field.plan.storeBuildingIds, id) end
field.mowWidth = (record.mowWidth or 0) > 0 and record.mowWidth or nil field.mowWidth = (record.mowWidth or 0) > 0 and record.mowWidth or nil
field.mowVehicleId = record.mowVehicle ~= nil and record.mowVehicle ~= "" and record.mowVehicle or nil
return field return field
end end
+2 -1
View File
@@ -28,6 +28,7 @@ function ADFieldStorage.save(state)
xmlFile:setInt(key .. "#rake", record.rake) xmlFile:setInt(key .. "#rake", record.rake)
xmlFile:setString(key .. "#store", record.store) xmlFile:setString(key .. "#store", record.store)
xmlFile:setFloat(key .. "#mowWidth", record.mowWidth) xmlFile:setFloat(key .. "#mowWidth", record.mowWidth)
xmlFile:setString(key .. "#mowVehicle", record.mowVehicle)
index = index + 1 index = index + 1
end end
local vehicleIndex = 0 local vehicleIndex = 0
@@ -51,7 +52,7 @@ function ADFieldStorage.load()
id = xmlFile:getInt(key .. "#id"), stage = xmlFile:getString(key .. "#stage"), id = xmlFile:getInt(key .. "#id"), stage = xmlFile:getString(key .. "#stage"),
mode = xmlFile:getString(key .. "#mode"), product = xmlFile:getString(key .. "#product"), mode = xmlFile:getString(key .. "#mode"), product = xmlFile:getString(key .. "#product"),
rake = xmlFile:getInt(key .. "#rake"), store = xmlFile:getString(key .. "#store"), rake = xmlFile:getInt(key .. "#rake"), store = xmlFile:getString(key .. "#store"),
mowWidth = xmlFile:getFloat(key .. "#mowWidth"), mowWidth = xmlFile:getFloat(key .. "#mowWidth"), mowVehicle = xmlFile:getString(key .. "#mowVehicle"),
}) })
if field.id ~= nil then result.fields[field.id] = field end if field.id ~= nil then result.fields[field.id] = field end
end) end)
+1 -1
View File
@@ -1,7 +1,7 @@
<?xml version="1.0" encoding="utf-8" standalone="no"?> <?xml version="1.0" encoding="utf-8" standalone="no"?>
<modDesc descVersion="107"> <modDesc descVersion="107">
<author>masterdraco</author> <author>masterdraco</author>
<version>1.21.6.0</version> <version>1.22.0.0</version>
<title> <title>
<en>AD Smart Pickup</en> <en>AD Smart Pickup</en>
</title> </title>
+4 -3
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — brugermanual # AD Smart Pickup — brugermanual
Version 1.21.6 · Farming Simulator 25 · tillæg til AutoDrive Version 1.22.0 · Farming Simulator 25 · tillæg til AutoDrive
*English version: [USER_MANUAL.md](USER_MANUAL.md)* *English version: [USER_MANUAL.md](USER_MANUAL.md)*
@@ -551,8 +551,9 @@ hvilket trin og hvilken maskine. Kræver **Courseplay** ud over AutoDrive.
- Courseplay kan højst køre **50 m** bredt. Et sæt over 50 m sendes ikke ud (`!! over 50 m` i - Courseplay kan højst køre **50 m** bredt. Et sæt over 50 m sendes ikke ud (`!! over 50 m` i
markflåden). markflåden).
- Moden husker, hvor bredt marken blev **slået**. Vender, river og læssevogn skal være **mindst lige - Moden husker, hvor bredt marken blev **slået**, og **slåmaskinens Courseplay-kursus**. Vender, river og
så brede**, så hver bane dækker det, slåmaskinen lagde. Med Unrealistic Pack: sæt slåmaskinen og læssevogn kører præcis samme kursus (kopieret), så de rammer skårene. De skal være **mindst lige så
brede** som slåmaskinen. Med Unrealistic Pack: sæt slåmaskinen og
læssevognene til samme bredde, fx 40,8 m. læssevognene til samme bredde, fx 40,8 m.
### En mark ### En mark
+4 -3
View File
@@ -1,6 +1,6 @@
# AD Smart Pickup — user manual # AD Smart Pickup — user manual
Version 1.21.6 · Farming Simulator 25 · add-on for AutoDrive Version 1.22.0 · Farming Simulator 25 · add-on for AutoDrive
*Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)* *Dansk version: [BRUGERMANUAL.md](BRUGERMANUAL.md)*
@@ -557,8 +557,9 @@ which step and which machine. Requires **Courseplay** in addition to AutoDrive.
- Courseplay can work at most **50 m** wide. A rig over 50 m is not sent out (`!! over 50 m` in the - Courseplay can work at most **50 m** wide. A rig over 50 m is not sent out (`!! over 50 m` in the
fleet). fleet).
- The mod remembers how wide the field was **mowed**. Tedder, rake and loading wagon must be **at least - The mod remembers how wide the field was **mowed**, and the **mower's Courseplay course**. Tedder,
as wide**, so every lane covers what the mower laid down. With Unrealistic Pack: set the mower and the rake and loading wagon drive exactly that course (copied), so they hit the swaths. They must be **at
least as wide** as the mower. With Unrealistic Pack: set the mower and the
loading wagons to the same width, such as 40.8 m. loading wagons to the same width, such as 40.8 m.
### A field ### A field
@@ -38,8 +38,12 @@ Ikke med i etape 1: mejetærskerafgrøder (etape 2), roer (etape 3), jordbehandl
læssevognene i Unrealistic Pack kan køre 40 m bredt og samler spredt hø op. læssevognene i Unrealistic Pack kan køre 40 m bredt og samler spredt hø op.
6. **Bredde.** Maskiner kører med deres *aktuelle* bredde (Unrealistic Packs `variableWorkWidth`, 6. **Bredde.** Maskiner kører med deres *aktuelle* bredde (Unrealistic Packs `variableWorkWidth`,
`aiKeepCurrentWidth`). Courseplay kan højst 50 m → et sæt over 50 m sendes ikke ud (advarsel). `aiKeepCurrentWidth`). Courseplay kan højst 50 m → et sæt over 50 m sendes ikke ud (advarsel).
Marken husker **slåbredden**; opsamling/vending/rivning kræver bredde ≥ slåbredden, så hver bane Marken husker **slåbredden**; opsamling/vending/rivning kræver bredde ≥ slåbredden.
dækker det, slåmaskinen lagde. Så behøver banerne ikke flugte med skårene. **Revideret efter in-game test 25/9:** et nyt Courseplay-kursus til læssevognen lå forskudt i forhold til
skårene (anden start/retning/forager). Derfor genbruger vender, river og læssevogn **slåmaskinens
kursus** (`vehicle:cpCopyCourse`). Kurset gemmes pr. mark; efter genindlæsning hentes det fra
slåmaskinen (id gemt som `mowVehicle`), som Courseplay selv gemmer kurset på. Uden kendt slåkursus
laves et nyt som reserve (logges).
7. **Silovalg** genbruger Smart Pickups motor (`ADLoadSwap`): nærmeste silo med plads, der tager varen, 7. **Silovalg** genbruger Smart Pickups motor (`ADLoadSwap`): nærmeste silo med plads, der tager varen,
med vej fra `F`-markøren. *Tving lager* pr. mark (som "Lager til" i flows). Fabrikker er ikke mål. med vej fra `F`-markøren. *Tving lager* pr. mark (som "Lager til" i flows). Fabrikker er ikke mål.
8. **Marksilo.** En silo, hvis afstand til markens polygon er ≤ 150 m, er et mellemlager. Den må gerne 8. **Marksilo.** En silo, hvis afstand til markens polygon er ≤ 150 m, er et mellemlager. Den må gerne
+5 -1
View File
@@ -41,6 +41,8 @@ check("M5 Start nu", M.shouldStart(requested), true)
local started = M.start(ready) local started = M.start(ready)
check("M5 start -> slås", started.stage, "mowing") check("M5 start -> slås", started.stage, "mowing")
check("M5 start rydder forespørgsel", started.startRequested, false) check("M5 start rydder forespørgsel", started.startRequested, false)
local oldMow = M.with(ready, {mowVehicleId = "old"})
check("M5 start glemmer gammel slåmaskine", M.start(oldMow).mowVehicleId, nil)
-- M6: videre gennem trinene; færdig mark bliver klar igen når den er vokset -- M6: videre gennem trinene; færdig mark bliver klar igen når den er vokset
local hay = M.start(M.onReadiness(M.newField(7), 1)); hay.plan.product = "hay" local hay = M.start(M.onReadiness(M.newField(7), 1)); hay.plan.product = "hay"
@@ -76,7 +78,7 @@ check("M7 wantsRig efter pause", M.wantsRig(paused, 0, 1000), "mower")
check("M7 wantsRig klar = nil", M.wantsRig(ready, 0, 0), nil) check("M7 wantsRig klar = nil", M.wantsRig(ready, 0, 0), nil)
-- M8: gem/indlæs bevarer trin, plan og slåbredde -- M8: gem/indlæs bevarer trin, plan og slåbredde
local saved = M.advance(M.start(M.onReadiness(M.newField(109), 1))); saved.mowWidth = 40.8 local saved = M.advance(M.start(M.onReadiness(M.newField(109), 1))); saved.mowWidth = 40.8; saved.mowVehicleId = "vehicleBigM"
saved.plan = {mode = "auto", product = "hay", rake = true, storeBuildingIds = {"b12", "b7"}} saved.plan = {mode = "auto", product = "hay", rake = true, storeBuildingIds = {"b12", "b7"}}
local back = M.fromRecord(M.toRecord(saved)) local back = M.fromRecord(M.toRecord(saved))
check("M8 id", back.id, 109) check("M8 id", back.id, 109)
@@ -86,6 +88,8 @@ check("M8 product", back.plan.product, "hay")
check("M8 rake", back.plan.rake, true) check("M8 rake", back.plan.rake, true)
check("M8 lager", table.concat(back.plan.storeBuildingIds, " "), "b12 b7") check("M8 lager", table.concat(back.plan.storeBuildingIds, " "), "b12 b7")
check("M8 slåbredde", back.mowWidth, 40.8) check("M8 slåbredde", back.mowWidth, 40.8)
check("M8 slåmaskine", back.mowVehicleId, "vehicleBigM")
check("M8 ingen slåmaskine", M.fromRecord({id = 4, stage = "idle"}).mowVehicleId, nil)
check("M8 ukendt trin -> venter", M.fromRecord({id = 3, stage = "bogus"}).stage, "idle") check("M8 ukendt trin -> venter", M.fromRecord({id = 3, stage = "bogus"}).stage, "idle")
-- M9: with() kopierer og fletter planen -- M9: with() kopierer og fletter planen