-- AD Courseplay Bridge -- Den eneste kode der rører Courseplay: start markarbejde på en mark (grænse -> kursus -> start, alt -- async), og læs om et sæt arbejder. Opskriften er bevist i spillet i AI-Bonden (AIHelperGlue.startCP): -- CP kalder grænse-callbacken to gange (først false ved fejl) — kun første kald tæller. ADCpBridge = {} ADCpBridge.SPECS = {"spec_mower", "spec_tedder", "spec_windrower", "spec_forageWagon"} function ADCpBridge.isAvailable() return AIJobType ~= nil and AIJobType.FIELDWORK_CP ~= nil end local function collect(vehicle, list) list = list or {} table.insert(list, vehicle) if vehicle.getAttachedImplements ~= nil then for _, implement in ipairs(vehicle:getAttachedImplements()) do if implement.object ~= nil then collect(implement.object, list) end end end return list end function ADCpBridge.getSpecs(vehicle) local specs = {} for _, object in ipairs(collect(vehicle)) do for _, name in ipairs(ADCpBridge.SPECS) do if object[name] ~= nil then specs[name] = true end end end return specs end -- Variabel bredde (Unrealistic Pack m.fl.) først, som Courseplays WorkWidthUtil: AI-markørerne står på -- grundbredden (læssevogn 2,4 m selv om den er sat til 40,8 m). local function variableWidth(object) if object.getVariableWorkWidth == nil then return nil end local okLeft, left, _, leftValid = pcall(object.getVariableWorkWidth, object, true) local okRight, right, _, rightValid = pcall(object.getVariableWorkWidth, object) if okLeft and okRight and leftValid and rightValid and type(left) == "number" and type(right) == "number" then return math.abs(left) + math.abs(right) end return nil end -- Courseplays egen bredde-indstilling (den i HUD'en) er den marken faktisk køres med. local function courseplayWidth(vehicle) if vehicle.getCourseGeneratorSettings == nil then return nil end local ok, width = pcall(function() return vehicle:getCourseGeneratorSettings().workWidth:getValue() end) if ok and type(width) == "number" and width > 0 then return width end return nil end function ADCpBridge.getWorkWidth(vehicle) local cpWidth = courseplayWidth(vehicle) if cpWidth ~= nil then return cpWidth end local best = 0 for _, object in ipairs(collect(vehicle)) do local variable = variableWidth(object) if variable ~= nil then best = math.max(best, variable) elseif object.getAIMarkers ~= nil then pcall(function() if object.updateAIMarkerWidth ~= nil then object:updateAIMarkerWidth() end end) local ok, _, _, _, _, width = pcall(object.getAIMarkers, object) if ok and type(width) == "number" and width > best then best = width end end end return best end function ADCpBridge.isWorking(vehicle) if vehicle.getIsCpFieldWorkActive ~= nil and vehicle:getIsCpFieldWorkActive() then return true end return vehicle.getIsAIActive ~= nil and vehicle:getIsAIActive() or false end -- Courseplays værktøjsforskydning (venstre positiv) på køretøjet; den der flytter kursuslinjen. function ADCpBridge.getToolOffset(vehicle) if vehicle == nil or vehicle.getCpSettings == nil then return 0 end local ok, value = pcall(function() return vehicle:getCpSettings().toolOffsetX:getValue() end) return ok and type(value) == "number" and value or 0 end function ADCpBridge.setToolOffset(vehicle, value) if vehicle == nil or vehicle.getCpSettings == nil then return false end return (pcall(function() vehicle:getCpSettings().toolOffsetX:setFloatValue(value) end)) end -- Riverens XML: workArea-nr (1-baseret) -> dropWindrowWorkAreaIndex. Bruges når spillet ikke har feltet -- på workArea-tabellen. Cachet pr. fil. ADCpBridge.dropIndexCache = {} function ADCpBridge.readDropIndicesFromXml(fileName) if ADCpBridge.dropIndexCache[fileName] ~= nil then return ADCpBridge.dropIndexCache[fileName] end local indices = {} local ok = pcall(function() local xmlFile = XMLFile.load("adspuRake", fileName) if xmlFile == nil then return end local position = 0 xmlFile:iterate("vehicle.workAreas.workArea", function(_, key) position = position + 1 local dropIndex = xmlFile:getInt(key .. ".windrower#dropWindrowWorkAreaIndex") if dropIndex ~= nil then indices[position] = dropIndex end end) xmlFile:delete() end) if not ok then indices = {} end ADCpBridge.dropIndexCache[fileName] = indices return indices end -- Hvor langt til venstre (positiv) for rivens kursuslinje skåret lægges: midten af nedlægningsområdet i -- traktorens ramme minus rivens egen CP-forskydning. Midterrive ≈ 0, siderive fx 3 m. nil = ingen river. function ADCpBridge.getWindrowOffset(vehicle) local root = vehicle.rootNode local sum, count = 0, 0 for _, object in ipairs(collect(vehicle)) do local areas = object.spec_windrower ~= nil and object.spec_workArea ~= nil and object.spec_workArea.workAreas or nil if areas ~= nil then local fromXml = nil for position, workArea in ipairs(areas) do local dropIndex = workArea.dropWindrowWorkAreaIndex if dropIndex == nil and object.configFileName ~= nil then fromXml = fromXml or ADCpBridge.readDropIndicesFromXml(object.configFileName) dropIndex = fromXml[position] end local drop = dropIndex ~= nil and areas[dropIndex] or nil if drop ~= nil and drop.width ~= nil and drop.height ~= nil then local widthX = localToLocal(drop.width, root, 0, 0, 0) local heightX = localToLocal(drop.height, root, 0, 0, 0) sum, count = sum + (widthX + heightX) / 2, count + 1 end end end end if count == 0 then return nil end return sum / count - ADCpBridge.getToolOffset(vehicle) end -- 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 local farmId = vehicle:getOwnerFarmId() local job = g_currentMission.aiJobTypeManager:createJob(AIJobType.FIELDWORK_CP) if job == nil then return nil, "kunne ikke oprette Courseplay-job" end job:applyCurrentState(vehicle, g_currentMission, farmId, false, false) local vx, _, vz = getWorldTranslation(vehicle.rootNode) local params = job.cpJobParameters if params ~= nil and params.fieldPosition ~= nil then params.fieldPosition:setPosition(fieldX, fieldZ) end if params ~= nil and params.startPosition ~= nil then params.startPosition:setPosition(vx, vz) end job:setValues() local handle = {cancelled = false, fired = false, finished = false} local function finish(ok, reason) if handle.finished then return end 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 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 generated then return finish(false, "Courseplay kunne ikke lave kursus") end startWithCourse() end) end handle.onBoundary = function(_, success, message) if handle.fired then return end handle.fired = true if handle.cancelled then return end if not success then return finish(false, "Courseplay fandt ikke markgrænsen: " .. tostring(message)) end generateAndStart() end job:registerFieldBoundaryDetectionCallback(handle, handle.onBoundary) local isValid, isRunning, message = job:detectFieldBoundary() if isValid and not isRunning then generateAndStart() elseif not isRunning then return nil, "Courseplay: " .. tostring(message) end return handle end