From 4c88118e56d41217b84c4a17fd32543efc5ff812 Mon Sep 17 00:00:00 2001 From: masterdraco Date: Fri, 25 Sep 2026 23:25:55 +0200 Subject: [PATCH] =?UTF-8?q?feat(marker):=20vender/river/l=C3=A6ssevogn=20g?= =?UTF-8?q?enbruger=20sl=C3=A5maskinens=20Courseplay-kursus=20(v1.22.0.0)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 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). --- FS25_ADSmartPickup/adCpBridge.lua | 36 ++++++++++++++----- FS25_ADSmartPickup/adFieldJobs.lua | 28 +++++++++++++-- FS25_ADSmartPickup/adFieldModel.lua | 3 ++ FS25_ADSmartPickup/adFieldStorage.lua | 3 +- FS25_ADSmartPickup/modDesc.xml | 2 +- docs/BRUGERMANUAL.md | 7 ++-- docs/USER_MANUAL.md | 7 ++-- .../2026-09-25-markarbejde-etape1-design.md | 8 +++-- tests/test_adFieldModel.lua | 6 +++- 9 files changed, 78 insertions(+), 22 deletions(-) diff --git a/FS25_ADSmartPickup/adCpBridge.lua b/FS25_ADSmartPickup/adCpBridge.lua index fad1bde..72a40b2 100644 --- a/FS25_ADSmartPickup/adCpBridge.lua +++ b/FS25_ADSmartPickup/adCpBridge.lua @@ -74,7 +74,16 @@ function ADCpBridge.isWorking(vehicle) return vehicle.getIsAIActive ~= nil and vehicle:getIsAIActive() or false 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 return nil, "Courseplay (FIELDWORK_CP) findes ikke" end @@ -93,16 +102,27 @@ function ADCpBridge.start(vehicle, fieldX, fieldZ, onDone) handle.finished = true if not handle.cancelled then onDone(ok, reason) 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() 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 not course then return finish(false, "Courseplay kunne ikke lave kursus") end - 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) + if not generated then return finish(false, "Courseplay kunne ikke lave kursus") end + startWithCourse() end) end handle.onBoundary = function(_, success, message) diff --git a/FS25_ADSmartPickup/adFieldJobs.lua b/FS25_ADSmartPickup/adFieldJobs.lua index fb20c09..8d3f452 100644 --- a/FS25_ADSmartPickup/adFieldJobs.lua +++ b/FS25_ADSmartPickup/adFieldJobs.lua @@ -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. ADFieldJobs.MAX_MOW_PASSES = 2 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.now = 0 ADFieldJobs.loaded = false @@ -255,6 +258,18 @@ local function dispatch(adEnv, field, info, role) note(field.id, {reason = false}) 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 vehicle = assignment.vehicle 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} assignment.phase = "starting" 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) if ok then assignment.phase = "working" assignment.since = ADFieldJobs.now 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 - 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 release(assignment) note(field.id, {retryAt = ADFieldJobs.now + ADFieldModel.RETRY_MS, reason = {text = tostring(reason)}}) log(field.id, "Courseplay fejlede: %s", tostring(reason)) end - end) + end, course) if handle == nil then release(assignment) log(field.id, "Courseplay kunne ikke startes: %s", tostring(err)) diff --git a/FS25_ADSmartPickup/adFieldModel.lua b/FS25_ADSmartPickup/adFieldModel.lua index 5d12a57..5516963 100644 --- a/FS25_ADSmartPickup/adFieldModel.lua +++ b/FS25_ADSmartPickup/adFieldModel.lua @@ -92,6 +92,7 @@ function ADFieldModel.start(field) copy.retryAt = 0 copy.reason = nil copy.mowWidth = nil + copy.mowVehicleId = nil return copy end @@ -156,6 +157,7 @@ function ADFieldModel.toRecord(field) 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, " "), mowWidth = field.mowWidth or 0, + mowVehicle = field.mowVehicleId or "", } end @@ -167,5 +169,6 @@ function ADFieldModel.fromRecord(record) 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 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 end diff --git a/FS25_ADSmartPickup/adFieldStorage.lua b/FS25_ADSmartPickup/adFieldStorage.lua index 16f0d6e..d0f24b3 100644 --- a/FS25_ADSmartPickup/adFieldStorage.lua +++ b/FS25_ADSmartPickup/adFieldStorage.lua @@ -28,6 +28,7 @@ function ADFieldStorage.save(state) xmlFile:setInt(key .. "#rake", record.rake) xmlFile:setString(key .. "#store", record.store) xmlFile:setFloat(key .. "#mowWidth", record.mowWidth) + xmlFile:setString(key .. "#mowVehicle", record.mowVehicle) index = index + 1 end local vehicleIndex = 0 @@ -51,7 +52,7 @@ function ADFieldStorage.load() id = xmlFile:getInt(key .. "#id"), stage = xmlFile:getString(key .. "#stage"), mode = xmlFile:getString(key .. "#mode"), product = xmlFile:getString(key .. "#product"), 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 end) diff --git a/FS25_ADSmartPickup/modDesc.xml b/FS25_ADSmartPickup/modDesc.xml index b1eb860..bd86249 100644 --- a/FS25_ADSmartPickup/modDesc.xml +++ b/FS25_ADSmartPickup/modDesc.xml @@ -1,7 +1,7 @@ masterdraco - 1.21.6.0 + 1.22.0.0 <en>AD Smart Pickup</en> diff --git a/docs/BRUGERMANUAL.md b/docs/BRUGERMANUAL.md index 9c52f7b..a5dfcca 100644 --- a/docs/BRUGERMANUAL.md +++ b/docs/BRUGERMANUAL.md @@ -1,6 +1,6 @@ # 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)* @@ -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 markflåden). -- Moden husker, hvor bredt marken blev **slået**. Vender, river og læssevogn skal være **mindst lige - så brede**, så hver bane dækker det, slåmaskinen lagde. Med Unrealistic Pack: sæt slåmaskinen og +- Moden husker, hvor bredt marken blev **slået**, og **slåmaskinens Courseplay-kursus**. Vender, river 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. ### En mark diff --git a/docs/USER_MANUAL.md b/docs/USER_MANUAL.md index 71defab..09e2308 100644 --- a/docs/USER_MANUAL.md +++ b/docs/USER_MANUAL.md @@ -1,6 +1,6 @@ # 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)* @@ -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 fleet). -- The mod remembers how wide the field was **mowed**. Tedder, rake and loading wagon must be **at least - as wide**, so every lane covers what the mower laid down. With Unrealistic Pack: set the mower and the +- The mod remembers how wide the field was **mowed**, and the **mower's Courseplay course**. Tedder, + 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. ### A field diff --git a/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md b/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md index ae8c3f3..aa26c12 100644 --- a/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md +++ b/docs/superpowers/specs/2026-09-25-markarbejde-etape1-design.md @@ -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. 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). - Marken husker **slåbredden**; opsamling/vending/rivning kræver bredde ≥ slåbredden, så hver bane - dækker det, slåmaskinen lagde. Så behøver banerne ikke flugte med skårene. + Marken husker **slåbredden**; opsamling/vending/rivning kræver bredde ≥ slåbredden. + **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, 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 diff --git a/tests/test_adFieldModel.lua b/tests/test_adFieldModel.lua index aa9688a..0380e2a 100644 --- a/tests/test_adFieldModel.lua +++ b/tests/test_adFieldModel.lua @@ -41,6 +41,8 @@ check("M5 Start nu", M.shouldStart(requested), true) local started = M.start(ready) check("M5 start -> slås", started.stage, "mowing") 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 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) -- 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"}} local back = M.fromRecord(M.toRecord(saved)) 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 lager", table.concat(back.plan.storeBuildingIds, " "), "b12 b7") 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") -- M9: with() kopierer og fletter planen