cf7a2a51e5
Brugerkrav 25/9: alle river-typer skal virke. Nedlægningsområdet (runtime-felt eller riverens XML) måles i traktorens ramme mens riven arbejder; læssevognen på rivens kursus får CP-toolOffsetX = egen - skårets forskydning og får sin værdi tilbage når den slippes.
206 lines
9.1 KiB
Lua
206 lines
9.1 KiB
Lua
-- 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
|