diff --git a/AIDriver.lua b/AIDriver.lua
index c5d9f295d..c52014a75 100644
--- a/AIDriver.lua
+++ b/AIDriver.lua
@@ -1438,6 +1438,18 @@ function AIDriver:resetBGASiloTables()
self.bestColumnToFill = nil
end
+--- Helper functions to generate a straight course
+function AIDriver:getStraightForwardCourse(length)
+ local l = length or 100
+ return Course.createFromNode(self.vehicle, self.vehicle.rootNode, 0, 0, l, 5, false)
+end
+
+function AIDriver:getStraightReverseCourse(length)
+ local lastTrailer = AIDriverUtil.getLastAttachedImplement(self.vehicle)
+ local l = length or 100
+ return Course.createFromNode(self.vehicle, lastTrailer.rootNode or self.vehicle.rootNode, 0, 0, -l, -5, true)
+end
+
------------------------------------------------------------------------------
--- PATHFINDING
------------------------------------------------------------------------------
diff --git a/AITurn.lua b/AITurn.lua
index f81c8e4cf..037bee6e5 100644
--- a/AITurn.lua
+++ b/AITurn.lua
@@ -537,8 +537,6 @@ function CourseTurn:changeDirectionWhenAligned()
end
function CourseTurn:generateCalculatedTurn()
- -- TODO: fix ugly dependency on global variables, there should be one function to create the turn maneuver
- self.vehicle.cp.settings.turnStage:set(true)
-- call turn() with stage 1 which will generate the turn waypoints (dt isn't used by that part)
courseplay:turn(self.vehicle, 1, self.turnContext)
-- they waypoints should now be in turnTargets, create a course based on that
diff --git a/BaleCollectorAIDriver.lua b/BaleCollectorAIDriver.lua
new file mode 100644
index 000000000..1e0ee9b0d
--- /dev/null
+++ b/BaleCollectorAIDriver.lua
@@ -0,0 +1,304 @@
+--[[
+This file is part of Courseplay (https://github.com/Courseplay/courseplay)
+Copyright (C) 2021 Peter Vaiko
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+]]
+
+--[[
+
+A bale loader AI driver who can find and collect bales on a field
+without a field course.
+
+For unloading, it has the same behavior as the BaleLoaderAIDriver.
+
+--]]
+
+---@class BaleCollectorAIDriver : BaleLoaderAIDriver
+BaleCollectorAIDriver = CpObject(BaleLoaderAIDriver)
+
+BaleCollectorAIDriver.myStates = {
+ SEARCHING_FOR_NEXT_BALE = {},
+ WAITING_FOR_PATHFINDER = {},
+ DRIVING_TO_NEXT_BALE = {},
+ APPROACHING_BALE = {},
+ PICKING_UP_BALE = {}
+}
+
+function BaleCollectorAIDriver:init(vehicle)
+ courseplay.debugVehicle(11,vehicle,'BaleCollectorAIDriver:init()')
+ BaleLoaderAIDriver.init(self, vehicle)
+ self:initStates(BaleCollectorAIDriver.myStates)
+ self.fieldId = 0
+ self.bales = {}
+ -- make sure we have a good turning radius set
+ self.turnRadius = AIDriverUtil.getTurningRadius(self.vehicle)
+end
+
+function BaleCollectorAIDriver:setHudContent()
+ -- skip the inheritance from fieldwork/bale loader as this is very special
+ AIDriver.setHudContent(self)
+ courseplay.hud:setBaleCollectorAIDriverContent(self.vehicle)
+end
+
+function BaleCollectorAIDriver:setUpAndStart(startingPoint)
+ -- we only have an unload course since we are driving on the field autonomously
+ self.unloadRefillCourse = Course(self.vehicle, self.vehicle.Waypoints, false)
+ -- Set the offset to 0, we'll take care of getting the grabber to the right place
+ self.vehicle.cp.settings.toolOffsetX:set(0)
+
+ if startingPoint:is(StartingPointSetting.START_COLLECTING_BALES) then
+ -- to always have a valid course (for the traffic conflict detector mainly)
+ self.fieldworkCourse = self:getStraightForwardCourse(25)
+ self:startCourse(self.fieldworkCourse, 1)
+ local myField = self.vehicle.cp.settings.baleCollectionField:get()
+ if not myField or myField < 1 then
+ self:stop("NO_FIELD_SELECTED")
+ return
+ end
+ self.bales = self:findBales(myField)
+ self:changeToFieldwork()
+ self:collectNextBale()
+ else
+ local closestIx, _, closestIxRightDirection, _ =
+ self.unloadRefillCourse:getNearestWaypoints(AIDriverUtil.getDirectionNode(self.vehicle))
+ local startIx = 1
+ if startingPoint:is(StartingPointSetting.START_AT_NEAREST_POINT) then
+ startIx = closestIx
+ elseif startingPoint:is(StartingPointSetting.START_AT_NEXT_POINT) then
+ startIx = closestIxRightDirection
+ end
+ self:changeToUnloadOrRefill()
+ self:startCourseWithAlignment(self.unloadRefillCourse, startIx)
+ end
+end
+
+function BaleCollectorAIDriver:setBaleCollectingState(state)
+ self.baleCollectingState = state
+ self:debug('baleCollectingState: %s', self.baleCollectingState.name)
+end
+
+
+function BaleCollectorAIDriver:collectNextBale()
+ self:setBaleCollectingState(self.states.SEARCHING_FOR_NEXT_BALE)
+ if #self.bales > 0 then
+ self:findPathToNextBale()
+ else
+ self:info('No bales found.')
+ if self:getFillLevel() > 0.1 then
+ self:changeToUnloadOrRefill()
+ self:startCourseWithPathfinding(self.unloadRefillCourse, 1)
+ else
+ self:stop('WORK_END')
+ end
+ end
+end
+
+--- Find bales on field
+---@return BaleToCollect[] list of bales found
+function BaleCollectorAIDriver:findBales(fieldId)
+ self:debug('Finding bales on field %d...', fieldId or 0)
+ local balesFound = {}
+ for _, object in pairs(g_currentMission.nodeToObject) do
+ if object:isa(Bale) then
+ local bale = BaleToCollect(object)
+ -- if the bale has a mountObject it is already on the loader so ignore it
+ if (not fieldId or fieldId == 0 or bale:getFieldId() == fieldId) and
+ not object.mountObject and
+ object:getOwnerFarmId() == self.vehicle:getOwnerFarmId()
+ then
+ -- bales may have multiple nodes, using the object.id deduplicates the list
+ balesFound[object.id] = bale
+ end
+ end
+ end
+ -- convert it to a normal array so lua can give us the number of entries
+ local bales = {}
+ for _, bale in pairs(balesFound) do
+ table.insert(bales, bale)
+ end
+ self:debug('Found %d bales on field %d', #bales, fieldId)
+ return bales
+end
+
+---@return BaleToCollect, number closest bale and its distance
+function BaleCollectorAIDriver:findClosestBale(bales)
+ local closestBale, minDistance, ix = nil, math.huge
+ for i, bale in ipairs(bales) do
+ local _, _, _, d = bale:getPositionInfoFromNode(AIDriverUtil.getDirectionNode(self.vehicle))
+ self:debug('%d. bale (%d) in %.1f m', i, bale:getId(), d)
+ if d < self.vehicle.cp.turnDiameter * 2 then
+ -- if it is really close, check the length of the Dubins path
+ -- as we may need to drive a loop first to get to it
+ d = self:getDubinsPathLengthToBale(bale)
+ self:debug(' Dubins length is %.1f m', d)
+ end
+ if d < minDistance then
+ closestBale = bale
+ minDistance = d
+ ix = i
+ end
+ end
+ return closestBale, minDistance, ix
+end
+
+function BaleCollectorAIDriver:getDubinsPathLengthToBale(bale)
+ local start = PathfinderUtil.getVehiclePositionAsState3D(self.vehicle)
+ local goal = self:getBaleTarget(bale)
+ local solution = PathfinderUtil.dubinsSolver:solve(start, goal, self.turnRadius)
+ return solution:getLength(self.turnRadius)
+end
+
+function BaleCollectorAIDriver:findPathToNextBale()
+ if not self.bales then return end
+ local bale, d, ix = self:findClosestBale(self.bales)
+ if ix then
+ self:startPathfindingToBale(bale)
+ -- remove bale from list
+ table.remove(self.bales, ix)
+ end
+end
+
+--- The trick here is to get a target direction at the bale
+function BaleCollectorAIDriver:getBaleTarget(bale)
+ -- first figure out the direction at the goal, as the pathfinder needs that.
+ -- for now, just use the direction from our location towards the bale
+ local xb, zb, yRot, d = bale:getPositionInfoFromNode(AIDriverUtil.getDirectionNode(self.vehicle))
+ return State3D(xb, -zb, courseGenerator.fromCpAngle(yRot))
+end
+
+---@param bale BaleToCollect
+function BaleCollectorAIDriver:startPathfindingToBale(bale)
+ if not self.pathfinder or not self.pathfinder:isActive() then
+ self.pathfindingStartedAt = self.vehicle.timer
+ local safeDistanceFromBale = bale:getSafeDistance()
+ local halfVehicleWidth = self.vehicle.sizeWidth and self.vehicle.sizeWidth / 2 or 1.5
+ self:debug('Start pathfinding to next bale (%d), safe distance from bale %.1f, half vehicle width %.1f',
+ bale:getId(), safeDistanceFromBale, halfVehicleWidth)
+ local goal = self:getBaleTarget(bale)
+ local offset = Vector(0, safeDistanceFromBale + halfVehicleWidth + 0.2)
+ goal:add(offset:rotate(goal.t))
+ local done, path, goalNodeInvalid
+ self.pathfinder, done, path, goalNodeInvalid =
+ PathfinderUtil.startPathfindingFromVehicleToGoal(self.vehicle, goal, false, self.fieldId, {})
+ if done then
+ return self:onPathfindingDoneToNextBale(path, goalNodeInvalid)
+ else
+ self:setBaleCollectingState(self.states.WAITING_FOR_PATHFINDER)
+ self:setPathfindingDoneCallback(self, self.onPathfindingDoneToNextBale)
+ return true
+ end
+ else
+ self:debug('Pathfinder already active')
+ end
+end
+
+function BaleCollectorAIDriver:onPathfindingDoneToNextBale(path, goalNodeInvalid)
+ if path and #path > 2 then
+ self:debug('Found path (%d waypoints, %d ms)', #path, self.vehicle.timer - (self.pathfindingStartedAt or 0))
+ self.fieldworkCourse = Course(self.vehicle, courseGenerator.pointsToXzInPlace(path), true)
+ self:startCourse(self.fieldworkCourse, 1)
+ self:debug('Driving to next bale')
+ self:setBaleCollectingState(self.states.DRIVING_TO_NEXT_BALE)
+ return true
+ else
+ self:setBaleCollectingState(self.states.SEARCHING_FOR_NEXT_BALE)
+ return false
+ end
+end
+
+function BaleCollectorAIDriver:onLastWaypoint()
+ if self.state == self.states.ON_FIELDWORK_COURSE and self.fieldworkState == self.states.WORKING then
+ if self.baleCollectingState == self.states.DRIVING_TO_NEXT_BALE then
+ self:debug('last waypoint while driving to next bale reached')
+ self:approachBale()
+ elseif self.baleCollectingState == self.states.PICKING_UP_BALE then
+ self:debug('last waypoint on bale pickup reached, start collecting bales again')
+ self:collectNextBale()
+ elseif self.baleCollectingState == self.states.APPROACHING_BALE then
+ self:debug('looks like somehow missed a bale, rescanning field')
+ self.bales = self:findBales(self.vehicle.cp.settings.baleCollectionField:get())
+ self:collectNextBale()
+ end
+ else
+ BaleLoaderAIDriver.onLastWaypoint(self)
+ end
+end
+
+function BaleCollectorAIDriver:onEndCourse()
+ if self.state == self.states.ON_UNLOAD_OR_REFILL_COURSE or
+ self.state == self.states.ON_UNLOAD_OR_REFILL_WITH_AUTODRIVE then
+ self:debug('Back from unload course, check for bales again')
+ self.bales = self:findBales(self.vehicle.cp.settings.baleCollectionField:get())
+ self:changeToFieldwork()
+ self:collectNextBale()
+ else
+ BaleLoaderAIDriver.onEndCourse(self)
+ end
+end
+
+function BaleCollectorAIDriver:approachBale()
+ self:debug('Approaching bale...')
+ self:startCourse(self:getStraightForwardCourse(20), 1)
+ self:setBaleCollectingState(self.states.APPROACHING_BALE)
+end
+
+--- Called from the generic driveFieldwork(), this the part doing the actual work on the field after/before all
+--- implements are started/lowered etc.
+function BaleCollectorAIDriver:work()
+ if self.baleCollectingState == self.states.SEARCHING_FOR_NEXT_BALE then
+ self:setSpeed(0)
+ self:debug('work: searching for next bale')
+ self:collectNextBale()
+ elseif self.baleCollectingState == self.states.WAITING_FOR_PATHFINDER then
+ self:setSpeed(0)
+ elseif self.baleCollectingState == self.states.DRIVING_TO_NEXT_BALE then
+ self:setSpeed(self.vehicle:getSpeedLimit())
+ elseif self.baleCollectingState == self.states.APPROACHING_BALE then
+ self:setSpeed(self:getWorkSpeed() / 2)
+ self:debug('%s %s', tostring(self.baleLoader.spec_baleLoader.grabberIsMoving), tostring(self.baleLoader.spec_baleLoader.grabberMoveState))
+ if self.baleLoader.spec_baleLoader.grabberMoveState then
+ self:debug('Start picking up bale')
+ self:setBaleCollectingState(self.states.PICKING_UP_BALE)
+ end
+ elseif self.baleCollectingState == self.states.PICKING_UP_BALE then
+ self:setSpeed(0)
+ if not self.baleLoader.spec_baleLoader.grabberMoveState then
+ self:debug('Bale picked up, moving on to the next')
+ self:collectNextBale()
+ end
+ end
+ self:checkFillLevels()
+end
+
+function BaleCollectorAIDriver:calculateTightTurnOffset()
+ self.tightTurnOffset = 0
+end
+
+function BaleCollectorAIDriver:getFillLevel()
+ local fillLevelInfo = {}
+ self:getAllFillLevels(self.vehicle, fillLevelInfo)
+ for fillType, info in pairs(fillLevelInfo) do
+ if fillType == FillType.SQUAREBALE or
+ fillType == FillType.SQUAREBALE_WHEAT or
+ fillType == FillType.SQUAREBALE_BARLEY or
+ fillType == FillType.ROUNDBALE or
+ fillType == FillType.ROUNDBALE_WHEAT or
+ fillType == FillType.ROUNDBALE_BARLEY or
+ fillType == FillType.ROUNDBALE_GRASS or
+ fillType == FillType.ROUNDBALE_DRYGRASS then
+ return info.fillLevel
+ end
+ end
+end
\ No newline at end of file
diff --git a/BaleLoaderAIDriver.lua b/BaleLoaderAIDriver.lua
index 7384c1fdc..e5cc41356 100644
--- a/BaleLoaderAIDriver.lua
+++ b/BaleLoaderAIDriver.lua
@@ -26,7 +26,7 @@ BaleLoaderAIDriver.myStates = {
}
--- Make sure the the bale loader behaves like a proper AIImplement and reacts on AIImplementStart/End
---- events so there's no special handling is needed elswhere.
+--- events so there's no special handling is needed elsewhere.
function BaleLoaderAIDriver.register()
BaleLoader.onAIImplementStart = Utils.overwrittenFunction(BaleLoader.onAIImplementStart,
@@ -59,8 +59,8 @@ end
function BaleLoaderAIDriver:init(vehicle)
courseplay.debugVehicle(11,vehicle,'BaleLoaderAIDriver:init()')
UnloadableFieldworkAIDriver.init(self, vehicle)
- self.baleLoader = AIDriverUtil.getAIImplementWithSpecialization(vehicle, BaleLoader)
-
+ self.baleLoader = AIDriverUtil.getImplementWithSpecialization(vehicle, BaleLoader)
+ self:debug('baleloader %s', tostring(self.baleLoader))
-- Bale loaders have no AI markers (as they are not AIImplements according to Giants) so add a function here
-- to get the markers
self.baleLoader.getAIMarkers = function(self)
@@ -78,12 +78,6 @@ function BaleLoaderAIDriver:init(vehicle)
self:debug('Initialized, bale loader: %s', self.baleLoader:getName())
end
-function BaleLoaderAIDriver:setHudContent()
- UnloadableFieldworkAIDriver.setHudContent(self)
- courseplay.hud:setBaleLoaderAIDriverContent(self.vehicle)
-end
-
-
---@return boolean true if unload took over the driving
function BaleLoaderAIDriver:driveUnloadOrRefill(dt)
self:updateOffset()
diff --git a/BaleToCollect.lua b/BaleToCollect.lua
new file mode 100644
index 000000000..c1be44e54
--- /dev/null
+++ b/BaleToCollect.lua
@@ -0,0 +1,69 @@
+--[[
+This file is part of Courseplay (https://github.com/Courseplay/courseplay)
+Copyright (C) 2021 Peter Vaiko
+
+This program is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or
+(at your option) any later version.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program. If not, see .
+]]
+
+--[[
+
+A wrapper :) for the standard Bale object
+
+--]]
+
+---@class BaleToCollect
+BaleToCollect = CpObject()
+
+---@param baleObject : Bale
+function BaleToCollect:init(baleObject)
+ self.bale = baleObject
+ local x, _, z = getWorldTranslation(self.bale.nodeId)
+ self.fieldId = PathfinderUtil.getFieldIdAtWorldPosition(x, z)
+end
+
+function BaleToCollect:getFieldId()
+ return self.fieldId
+end
+
+function BaleToCollect:getId()
+ return self.bale.id
+end
+
+function BaleToCollect:getPosition()
+ return getWorldTranslation(self.bale.nodeId)
+end
+
+---@return number, number, number, number x, z, direction from node, distance from node
+function BaleToCollect:getPositionInfoFromNode(node)
+ local xb, _, zb = self:getPosition()
+ local x, _, z = getWorldTranslation(node)
+ local dx, dz = xb - x, zb - z
+ local yRot = MathUtil.getYRotationFromDirection(dx, dz)
+ return xb, zb, yRot, math.sqrt(dx * dx + dz * dz)
+end
+
+function BaleToCollect:getPositionAsState3D()
+ local xb, _, zb = self:getPosition()
+ local _, yRot, _ = getWorldRotation(self.bale.nodeId)
+ return State3D(xb, -zb, courseGenerator.fromCpAngle(yRot))
+end
+
+--- Minimum distance from the bale's center (node) to avoid hitting the bale
+--- when driving by in any direction
+function BaleToCollect:getSafeDistance()
+ -- round bales don't have length, just diameter
+ local length = self.bale.baleDiameter and self.bale.baleDiameter or self.bale.baleLength
+ -- no matter what kind of bale, the footprint is a rectangle, get the diagonal
+ return math.sqrt(length * length + self.bale.baleWidth * self.bale.baleWidth) / 2
+end
\ No newline at end of file
diff --git a/CombineUnloadAIDriver.lua b/CombineUnloadAIDriver.lua
index 030079efc..1648df9a6 100644
--- a/CombineUnloadAIDriver.lua
+++ b/CombineUnloadAIDriver.lua
@@ -609,17 +609,6 @@ function CombineUnloadAIDriver:getCourseToAlignTo(vehicle,offset)
return tempCourse
end
-function CombineUnloadAIDriver:getStraightForwardCourse(length)
- local l = length or 100
- return Course.createFromNode(self.vehicle, self.vehicle.rootNode, 0, 0, l, 5, false)
-end
-
-function CombineUnloadAIDriver:getStraightReverseCourse(length)
- local lastTrailer = AIDriverUtil.getLastAttachedImplement(self.vehicle)
- local l = length or 100
- return Course.createFromNode(self.vehicle, lastTrailer.rootNode, 0, 0, -l, -5, true)
-end
-
function CombineUnloadAIDriver:getTrailersTargetNode()
local allTrailersFull = true
for i=1, #self.vehicle.cp.workTools do
diff --git a/CpManager.lua b/CpManager.lua
index 5a6d607ae..733f86b35 100644
--- a/CpManager.lua
+++ b/CpManager.lua
@@ -991,6 +991,7 @@ function CpManager:setupGlobalInfoText()
RUNCOUNTER_ERROR_FOR_TRIGGER = { level = 0, text = 'COURSEPLAY_RUNCOUNTER_ERROR_FOR_TRIGGER' };
WAITING_FOR_UNLOADERS = { level = 0, text = 'COURSEPLAY_WAITING_FOR_UNLOADERS' };
WAITING_FOR_LEVELCOMPACTAIDRIVER = { level = 0, text = 'COURSEPLAY_WAITING_FOR_LEVELCOMPACTAIDRIVER' };
+ NO_FIELD_SELECTED = { level = -1, text = 'COURSEPLAY_NO_FIELD_SELECTED' };
};
end;
diff --git a/DevHelper.lua b/DevHelper.lua
index bb7cf826c..bd09bfc17 100644
--- a/DevHelper.lua
+++ b/DevHelper.lua
@@ -108,7 +108,11 @@ function DevHelper:overlapBoxCallback(transformId)
if collidingObject.getRootVehicle then
text = 'vehicle' .. collidingObject:getName()
else
- text = collidingObject.getName and collidingObject:getName() or 'N/A'
+ if collidingObject:isa(Bale) then
+ text = 'Bale'
+ else
+ text = collidingObject.getName and collidingObject:getName() or 'N/A'
+ end
end
else
text = ''
@@ -138,12 +142,15 @@ function DevHelper:updateProximitySensors(vehicle)
end
end
+-- Left-Alt + , (<) = mark current position as start for pathfinding
-- Left-Alt + , (<) = mark current position as start for pathfinding
-- Left-Alt + . (>) = mark current position as goal for pathfinding
-- Left-Ctrl + . (>) = start pathfinding from marked start to marked goal
-- Left-Ctrl + , (<) = mark current field as field for pathfinding
-- Left-Alt + Space = save current vehicle position
-- Left-Ctrl + Space = restore current vehicle position
+-- Left-Alt + / = look for bales
+-- Left-Ctrl + / = find path to next bale
function DevHelper:keyEvent(unicode, sym, modifier, isDown)
if not CpManager.isDeveloper then return end
if bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_comma then
@@ -179,6 +186,10 @@ function DevHelper:keyEvent(unicode, sym, modifier, isDown)
elseif bitAND(modifier, Input.MOD_LCTRL) ~= 0 and isDown and sym == Input.KEY_space then
-- restore vehicle position
DevHelper.restoreVehiclePosition(g_currentMission.controlledVehicle)
+ elseif bitAND(modifier, Input.MOD_LALT) ~= 0 and isDown and sym == Input.KEY_slash then
+ self:initBales()
+ elseif bitAND(modifier, Input.MOD_LCTRL) ~= 0 and isDown and sym == Input.KEY_slash then
+ self:findPathToNextBale()
end
end
@@ -196,7 +207,7 @@ function DevHelper:startPathfinding()
tostring(self.start), tostring(self.goal), self.fieldNumForPathfinding or 0)
local start = State3D:copy(self.start)
- self.pathfinder, done, path = PathfinderUtil.startPathfindingFromVehicleToGoal(self.vehicle, start, self.goal,
+ self.pathfinder, done, path = PathfinderUtil.startPathfindingFromVehicleToGoal(self.vehicle, self.goal,
false, self.fieldNumForPathfinding or 0, {}, 10)
end
@@ -260,7 +271,6 @@ function DevHelper:drawCourse()
end
end
-
function DevHelper:showFillNodes()
for _, vehicle in pairs(g_currentMission.vehicles) do
if SpecializationUtil.hasSpecialization(Trailer, vehicle.specializations) then
@@ -274,6 +284,61 @@ function DevHelper:showFillNodes()
end
end
+function DevHelper:findBales(fieldId)
+ self:debug('Finding bales on field %d...', fieldId or 0)
+ local bales = {}
+ for _, object in pairs(g_currentMission.nodeToObject) do
+ if object:isa(Bale) then
+ local bale = BaleToCollect(object)
+ if not fieldId or fieldId == 0 or bale:getFieldId() == fieldId then
+ bales[object.id] = bale
+ end
+ end
+ end
+ return bales
+end
+
+function DevHelper:initBales()
+ local allBales = self:findBales()
+ local closestBale, d = self:findClosestBale(allBales, self.node)
+ self:debug('Closest bale is at %.1f m, on field %d', d, closestBale:getFieldId())
+ self.bales = self:findBales(closestBale:getFieldId())
+end
+
+---@return BaleToCollect, number closest bale and its distance
+function DevHelper:findClosestBale(bales, node)
+ local closestBale, minDistance = nil, math.huge
+ for _, bale in pairs(bales) do
+ local _, _, _, d = bale:getPositionFromNodeInfo(node)
+ if d < minDistance then
+ closestBale = bale
+ minDistance = d
+ end
+ end
+ return closestBale, minDistance
+end
+
+function DevHelper:findPathToNextBale()
+ if not self.bales then return end
+ local baleId
+ for id, bale in pairs(self.bales) do
+ -- first figure out the direction at the goal, as the pathfinder needs that.
+ -- for now, just use the direction from our location towards the bale
+ local xb, zb, yRot, d = bale:getPositionFromNodeInfo(self.node)
+
+ self.start = State3D(self.data.x, -self.data.z, courseGenerator.fromCpAngleDeg(self.data.yRotDeg))
+ self.goal = State3D(xb, -zb, courseGenerator.fromCpAngle(yRot))
+ local offset = Vector(0, 3.5)
+ self.goal:add(offset:rotate(self.goal.t))
+
+ self:startPathfinding()
+ baleId = id
+ break
+ end
+ -- remove bale from list
+ if baleId then self.bales[baleId] = nil end
+end
+
function DevHelper:showVehicleSize()
local vehicle = g_currentMission.controlledVehicle
if not vehicle then return end
diff --git a/FieldworkAIDriver.lua b/FieldworkAIDriver.lua
index 6e89add57..b57ebee35 100644
--- a/FieldworkAIDriver.lua
+++ b/FieldworkAIDriver.lua
@@ -52,8 +52,6 @@ function FieldworkAIDriver:init(vehicle)
courseplay.debugVehicle(11,vehicle,'FieldworkAIDriver:init()')
AIDriver.init(self, vehicle)
self:initStates(FieldworkAIDriver.myStates)
- -- waiting for tools to turn on, unfold and lower
- self.waitingForTools = true
self.debugChannel = 14
-- waypoint index on main (fieldwork) course where we aborted the work before going on
-- an unload/refill course
@@ -190,7 +188,12 @@ function FieldworkAIDriver:start(startingPoint)
self.aiDriverOffsetZ = 0
self.workWidth = courseplay:getWorkWidth(self.vehicle)
+ self.ppc:setNormalLookaheadDistance()
+
+ self:setUpAndStart(startingPoint)
+end
+function FieldworkAIDriver:setUpAndStart(startingPoint)
self:setUpCourses()
-- now that we have our unload/refill and fieldwork courses set up, see where to start
@@ -250,9 +253,6 @@ function FieldworkAIDriver:start(startingPoint)
startWithFieldwork = true
end
- self.waitingForTools = true
- self.ppc:setNormalLookaheadDistance()
-
if startWithFieldwork then
self:startFieldworkWithPathfinding(ix)
else
@@ -422,10 +422,7 @@ function FieldworkAIDriver:driveFieldwork(dt)
self:checkFillLevels()
end
elseif self.fieldworkState == self.states.WORKING then
- self:setSpeed(self:getWorkSpeed())
- self:manageConvoy()
- self:checkWeather()
- self:checkFillLevels()
+ self:work()
elseif self.fieldworkState == self.states.UNLOAD_OR_REFILL_ON_FIELD then
self:driveFieldworkUnloadOrRefill()
elseif self.fieldworkState == self.states.TEMPORARY then
@@ -441,6 +438,16 @@ function FieldworkAIDriver:driveFieldwork(dt)
return iAmDriving
end
+--- Do the actual fieldwork. This is extracted here so derived classes can use the
+--- existing start/end work mechanisms to lower/raise start/stop implements and only
+--- need to implement the actual work themselves.
+function FieldworkAIDriver:work()
+ self:setSpeed(self:getWorkSpeed())
+ self:manageConvoy()
+ self:checkWeather()
+ self:checkFillLevels()
+end
+
function FieldworkAIDriver:getNominalSpeed()
if self.state == self.states.ON_FIELDWORK_COURSE then
return self:getWorkSpeed()
diff --git a/UnloadableFieldworkAIDriver.lua b/UnloadableFieldworkAIDriver.lua
index 0437058db..24ff693ce 100644
--- a/UnloadableFieldworkAIDriver.lua
+++ b/UnloadableFieldworkAIDriver.lua
@@ -48,7 +48,7 @@ function UnloadableFieldworkAIDriver:setHudContent()
end
function UnloadableFieldworkAIDriver.create(vehicle)
- if AIDriverUtil.hasAIImplementWithSpecialization(vehicle, BaleLoader) then
+ if AIDriverUtil.hasImplementWithSpecialization(vehicle, BaleLoader) then
return BaleLoaderAIDriver(vehicle)
elseif AIDriverUtil.hasAIImplementWithSpecialization(vehicle, BaleWrapper) then
-- Bale wrapper is derived from baler so must check it first to make sure that we instantiate a
diff --git a/base.lua b/base.lua
index f68cc3d75..8919fd245 100644
--- a/base.lua
+++ b/base.lua
@@ -412,7 +412,7 @@ end;
function courseplay:onLeaveVehicle()
if self.cp.mouseCursorActive then
courseplay:setMouseCursor(self, false);
- courseEditor:reset()
+ courseEditor:reset()
end
--hide visual i3D waypoint signs when not in vehicle
@@ -1160,9 +1160,9 @@ function courseplay:loadVehicleCPSettings(xmlFile, key, resetVehicles)
if not resetVehicles and g_server ~= nil then
-- COURSEPLAY
local curKey = key .. '.courseplay.basics';
- courseplay:setCpMode(self, Utils.getNoNil( getXMLInt(xmlFile, curKey .. '#aiMode'), self.cp.mode));
- self.cp.waitTime = Utils.getNoNil( getXMLInt(xmlFile, curKey .. '#waitTime'), 0);
- local courses = Utils.getNoNil(getXMLString(xmlFile, curKey .. '#courses'), '');
+ courseplay:setCpMode(self, Utils.getNoNil(getXMLInt(xmlFile, curKey .. '#aiMode'), self.cp.mode), true);
+ self.cp.waitTime = Utils.getNoNil(getXMLInt(xmlFile, curKey .. '#waitTime'), 0);
+ local courses = Utils.getNoNil(getXMLString(xmlFile, curKey .. '#courses'), '');
self.cp.loadedCourses = StringUtil.splitString(",", courses);
courseplay:reloadCourses(self, true);
diff --git a/config/ValidModeSetup.xml b/config/ValidModeSetup.xml
index 1e2270057..fcf015216 100644
--- a/config/ValidModeSetup.xml
+++ b/config/ValidModeSetup.xml
@@ -174,13 +174,11 @@
-
+
-
-
diff --git a/course-generator/PathfinderUtil.lua b/course-generator/PathfinderUtil.lua
index 8a72dfc03..52144a8ae 100644
--- a/course-generator/PathfinderUtil.lua
+++ b/course-generator/PathfinderUtil.lua
@@ -35,7 +35,7 @@ PathfinderUtil.VehicleData = CpObject()
--- If the vehicle has a trailer, it is handled separately from other implements to allow for the
--- pathfinding to consider the trailer's heading (which will be different from the vehicle's heading)
function PathfinderUtil.VehicleData:init(vehicle, withImplements, buffer)
- self.turnRadius = vehicle.cp and vehicle.cp.turnDiameter and vehicle.cp.turnDiameter / 2 or 10
+ self.turnRadius = vehicle.cp and vehicle.cp.driver and AIDriverUtil.getTurningRadius(vehicle) or 10
self.vehicle = vehicle
self.rootVehicle = vehicle:getRootVehicle()
self.name = vehicle.getName and vehicle:getName() or 'N/A'
@@ -283,8 +283,11 @@ function PathfinderUtil.CollisionDetector:overlapBoxCallback(transformId)
-- just bumped into myself or a vehicle we want to ignore
return
end
- --courseplay.debugFormat(7, 'collision: %s', collidingObject:getName())
+ courseplay.debugFormat(7, 'collision: %s', collidingObject:getName())
end
+ if collidingObject and collidingObject:isa(Bale) then
+ courseplay.debugFormat(7, 'collision with bale')
+ end
if not getHasClassId(transformId, ClassIds.TERRAIN_TRANSFORM_GROUP) then
--[[
local text = ''
@@ -316,14 +319,14 @@ function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData,
self.collidingShapes = 0
- overlapBox(x, y + 1, z, 0, yRot, 0, width, 1, length, 'overlapBoxCallback', self, bitOR(AIVehicleUtil.COLLISION_MASK, 2), true, true, true)
+ overlapBox(x, y + 0.2, z, 0, yRot, 0, width, 1, length, 'overlapBoxCallback', self, bitOR(AIVehicleUtil.COLLISION_MASK, 2), true, true, true)
if log and self.collidingShapes > 0 then
table.insert(PathfinderUtil.overlapBoxes,
- { x = x, y = y + 1, z = z, yRot = yRot, width = width, length = length})
+ { x = x, y = y + 0.2, z = z, yRot = yRot, width = width, length = length})
courseplay.debugFormat(7, 'pathfinder colliding shapes (%s) at x = %.1f, z = %.1f, (%.1fx%.1f), yRot = %d',
vehicleData.name, x, z, width, length, math.deg(yRot))
end
- --DebugUtil.drawOverlapBox(x, y, z, 0, yRot, 0, width, 1, length, 100, 0, 0)
+ DebugUtil.drawOverlapBox(x, y, z, 0, yRot, 0, width, 1, length, 100, 0, 0)
return self.collidingShapes
end
@@ -601,9 +604,12 @@ end
---@param start State3D
---@param goal State3D
-function PathfinderUtil.startPathfindingFromVehicleToGoal(vehicle, start, goal,
+function PathfinderUtil.startPathfindingFromVehicleToGoal(vehicle, goal,
allowReverse, fieldNum,
vehiclesToIgnore, maxFruitPercent, offFieldPenalty, mustBeAccurate)
+
+ local start = PathfinderUtil.getVehiclePositionAsState3D(vehicle)
+
local otherVehiclesCollisionData = PathfinderUtil.setUpVehicleCollisionData(vehicle, vehiclesToIgnore)
local vehicleData = PathfinderUtil.VehicleData(vehicle, true, 0.5)
@@ -737,6 +743,7 @@ function PathfinderUtil.findDubinsPath(vehicle, startOffset, goalReferenceNode,
return dubinsPath, solution:getLength(turnRadius)
end
+
function PathfinderUtil.getNodePositionAndDirection(node, xOffset, zOffset)
local x, _, z = localToWorld(node, xOffset or 0, 0, zOffset or 0)
local lx, _, lz = localDirectionToWorld(node, 0, 0, 1)
@@ -744,6 +751,13 @@ function PathfinderUtil.getNodePositionAndDirection(node, xOffset, zOffset)
return x, z, yRot
end
+---@param vehicle Vehicle
+---@return State3D position/heading of vehicle
+function PathfinderUtil.getVehiclePositionAsState3D(vehicle)
+ local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(AIDriverUtil.getDirectionNode(vehicle))
+ return State3D(x, -z, courseGenerator.fromCpAngle(yRot))
+end
+
------------------------------------------------------------------------------------------------------------------------
--- Interface function to start the pathfinder in the game
------------------------------------------------------------------------------------------------------------------------
@@ -760,13 +774,12 @@ end
function PathfinderUtil.startPathfindingFromVehicleToWaypoint(vehicle, goalWaypoint,
xOffset, zOffset, allowReverse,
fieldNum, vehiclesToIgnore, maxFruitPercent, offFieldPenalty)
- local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(AIDriverUtil.getDirectionNode(vehicle))
- local start = State3D(x, -z, courseGenerator.fromCpAngle(yRot))
+
local goal = State3D(goalWaypoint.x, -goalWaypoint.z, courseGenerator.fromCpAngleDeg(goalWaypoint.angle))
local offset = Vector(zOffset, -xOffset)
goal:add(offset:rotate(goal.t))
return PathfinderUtil.startPathfindingFromVehicleToGoal(
- vehicle, start, goal, allowReverse, fieldNum, vehiclesToIgnore, maxFruitPercent, offFieldPenalty)
+ vehicle, goal, allowReverse, fieldNum, vehiclesToIgnore, maxFruitPercent, offFieldPenalty)
end
------------------------------------------------------------------------------------------------------------------------
--- Interface function to start the pathfinder in the game. The goal is a point at sideOffset meters from the goal node
@@ -786,12 +799,10 @@ function PathfinderUtil.startPathfindingFromVehicleToNode(vehicle, goalNode,
xOffset, zOffset, allowReverse,
fieldNum, vehiclesToIgnore, maxFruitPercent, offFieldPenalty,
mustBeAccurate)
- local x, z, yRot = PathfinderUtil.getNodePositionAndDirection(AIDriverUtil.getDirectionNode(vehicle))
- local start = State3D(x, -z, courseGenerator.fromCpAngle(yRot))
x, z, yRot = PathfinderUtil.getNodePositionAndDirection(goalNode, xOffset, zOffset)
local goal = State3D(x, -z, courseGenerator.fromCpAngle(yRot))
return PathfinderUtil.startPathfindingFromVehicleToGoal(
- vehicle, start, goal, allowReverse, fieldNum,
+ vehicle, goal, allowReverse, fieldNum,
vehiclesToIgnore, maxFruitPercent, offFieldPenalty, mustBeAccurate)
end
diff --git a/courseplay.lua b/courseplay.lua
index 4bacf08e2..47a108188 100644
--- a/courseplay.lua
+++ b/courseplay.lua
@@ -84,6 +84,7 @@ local function initialize()
'Waypoint',
'StateModule',
'TriggerHandler',
+ 'BaleToCollect',
'AIDriver',
'CombineUnloadAIDriver',
'OverloaderAIDriver',
@@ -95,6 +96,7 @@ local function initialize()
'PlowAIDriver',
'UnloadableFieldworkAIDriver',
'BaleLoaderAIDriver',
+ 'BaleCollectorAIDriver',
'BalerAIDriver',
'BaleWrapperAIDriver',
'CombineAIDriver',
@@ -184,7 +186,7 @@ local function setGlobalData()
courseplay.MODE_SEED_FERTILIZE = 4;
courseplay.MODE_TRANSPORT = 5;
courseplay.MODE_FIELDWORK = 6;
- courseplay.MODE_COMBINE_SELF_UNLOADING = 7; --removed by Tommi
+ courseplay.MODE_BALE_COLLECTOR = 7;
courseplay.MODE_FIELD_SUPPLY = 8;
courseplay.MODE_SHOVEL_FILL_AND_EMPTY = 9;
courseplay.MODE_BUNKERSILO_COMPACTER = 10;
diff --git a/hud.lua b/hud.lua
index 7d4f3e1f3..4965e033d 100644
--- a/hud.lua
+++ b/hud.lua
@@ -231,7 +231,7 @@ function courseplay.hud:setup()
[courseplay.MODE_SEED_FERTILIZE] = { 220, 72, 252,40 };
[courseplay.MODE_TRANSPORT] = { 4,108, 36,76 };
[courseplay.MODE_FIELDWORK] = { 40,108, 72,76 };
- [courseplay.MODE_COMBINE_SELF_UNLOADING] = { 76,108, 108,76 };
+ [courseplay.MODE_BALE_COLLECTOR] = { 76,108, 108,76 };
[courseplay.MODE_FIELD_SUPPLY] = { 112,108, 144,76 };
[courseplay.MODE_SHOVEL_FILL_AND_EMPTY] = { 148,108, 180,76 };
[courseplay.MODE_BUNKERSILO_COMPACTER] = { 219,431, 251,399 };
@@ -323,7 +323,7 @@ function courseplay.hud:setup()
[courseplay.MODE_SEED_FERTILIZE] = { 40,144, 72,112 };
[courseplay.MODE_TRANSPORT] = { 76,144, 108,112 };
[courseplay.MODE_FIELDWORK] = { 112,144, 144,112 };
- [courseplay.MODE_COMBINE_SELF_UNLOADING] = { 148,144, 180,112 };
+ [courseplay.MODE_BALE_COLLECTOR] = { 148,144, 180,112 };
[courseplay.MODE_FIELD_SUPPLY] = { 184,144, 216,112 };
[courseplay.MODE_SHOVEL_FILL_AND_EMPTY] = { 220,144, 252,112 };
[courseplay.MODE_BUNKERSILO_COMPACTER] = { 219,394, 251,362 };
@@ -355,8 +355,8 @@ function courseplay.hud:setup()
[courseplay.MODE_SEED_FERTILIZE] = courseplay.utils:rgbToNormal( 30, 255, 156, 1), -- Light Green
[courseplay.MODE_TRANSPORT] = courseplay.utils:rgbToNormal( 21, 198, 255, 1), -- Blue
[courseplay.MODE_FIELDWORK] = courseplay.utils:rgbToNormal( 49, 52, 140, 1), -- Dark Blue
- [courseplay.MODE_COMBINE_SELF_UNLOADING] = courseplay.utils:rgbToNormal(159, 29, 250, 1), -- Purple
- [courseplay.MODE_FIELD_SUPPLY] = courseplay.utils:rgbToNormal(255, 27, 231, 1), -- Pink
+ [courseplay.MODE_BALE_COLLECTOR] = courseplay.utils:rgbToNormal(159, 29, 250, 1), -- Purple
+ [courseplay.MODE_FIELD_SUPPLY] = courseplay.utils:rgbToNormal(255, 27, 231, 1), -- Pink
[courseplay.MODE_SHOVEL_FILL_AND_EMPTY] = courseplay.utils:rgbToNormal(231, 19, 19, 1), -- Red
[courseplay.MODE_BUNKERSILO_COMPACTER] = courseplay.utils:rgbToNormal(231, 19, 19, 1), -- Red
};
@@ -654,7 +654,6 @@ function courseplay.hud:updatePageContent(vehicle, page)
--autoDriveModeSetting
if not vehicle.cp.settings.autoDriveMode:isDisabled() then
self:enableButtonWithFunction(vehicle,page, 'changeByX',vehicle.cp.settings.autoDriveMode)
- -- self:disableButtonWithFunction(vehicle,page, 'toggle',vehicle.cp.settings.stopAtEnd)
vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.autoDriveMode:getLabel()
vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.autoDriveMode:getText()
else
@@ -671,7 +670,7 @@ function courseplay.hud:updatePageContent(vehicle, page)
end
elseif entry.functionToCall == 'returnToFirstPoint:changeByX' then
--ReturnToFirstPointSetting
- if vehicle.cp.canDrive then
+ if not vehicle.cp.settings.returnToFirstPoint:isDisabled() then
self:enableButtonWithFunction(vehicle,page, 'changeByX',vehicle.cp.settings.returnToFirstPoint)
self:disableButtonWithFunction(vehicle,page, 'setCustomSingleFieldEdge')
vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.returnToFirstPoint:getLabel()
@@ -1001,18 +1000,6 @@ function courseplay.hud:updatePageContent(vehicle, page)
else
self:disableButtonWithFunction(vehicle,page, 'switchCourseplayerSide')
end
- elseif entry.functionToCall == 'turnStage:toggle' then
- --TurnStageSetting
- if g_combineUnloadManager:getHasUnloaders(vehicle) then
- --manual chopping: initiate/end turning maneuver
- if not vehicle:getIsCourseplayDriving() then
- self:enableButtonWithFunction(vehicle,page, 'toggle',vehicle.cp.settings.turnStage)
- vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.turnStage:getLabel()
- vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.turnStage:getText()
- end
- else
- self:disableButtonWithFunction(vehicle,page, 'toggle',vehicle.cp.settings.turnStage)
- end
elseif entry.functionToCall == 'driverPriorityUseFillLevel:toggle' then
--DriverPriorityUseFillLevelSetting
vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.driverPriorityUseFillLevel:getLabel()
@@ -1151,16 +1138,9 @@ function courseplay.hud:updatePageContent(vehicle, page)
else
self:disableButtonWithFunction(vehicle,page,'changeByX',vehicle.cp.settings.separateFillTypeLoading)
end
- elseif entry.functionToCall == 'automaticUnloadingOnField:toggle' then
- --not used right now!
- --AutomaticUnloadingOnFieldSetting
- if not vehicle.cp.hasUnloadingRefillingCourse then
- self:enableButtonWithFunction(vehicle,page,'toggle',vehicle.cp.settings.automaticUnloadingOnField)
- vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.automaticUnloadingOnField:getLabel()
- vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.automaticUnloadingOnField:getText()
- else
- self:disableButtonWithFunction(vehicle,page,'toggle',vehicle.cp.settings.automaticUnloadingOnField)
- end
+ elseif entry.functionToCall == 'baleCollectionField:changeByX' then
+ vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.baleCollectionField:getLabel()
+ vehicle.cp.hud.content.pages[page][line][2].text = vehicle.cp.settings.baleCollectionField:getText()
elseif entry.functionToCall == 'shovelStopAndGo:toggle' then
--ShovelStopAndGoSetting
vehicle.cp.hud.content.pages[page][line][1].text = vehicle.cp.settings.shovelStopAndGo:getLabel()
@@ -2166,7 +2146,6 @@ function courseplay.hud:setCombineAIDriverContent(vehicle)
self:addRowButton(vehicle,nil,'sendCourseplayerHome', 0, 3, 1 )
if vehicle.cp.isChopper then
self:addRowButton(vehicle,nil,'switchCourseplayerSide', 0, 4, 1 )
- self:addRowButton(vehicle,vehicle.cp.settings.turnStage,'toggle', 0, 5, 1 )
else
self:addRowButton(vehicle,vehicle.cp.settings.driverPriorityUseFillLevel,'toggle', 0, 4, 1 )
self:addRowButton(vehicle,vehicle.cp.settings.stopForUnload,'toggle', 0, 5, 1 )
@@ -2264,9 +2243,23 @@ function courseplay.hud:setLevelCompactAIDriverContent(vehicle)
end
-function courseplay.hud:setBaleLoaderAIDriverContent(vehicle)
- self:debug(vehicle,"setBaleLoaderAIDriverContent")
- self:addRowButton(vehicle,vehicle.cp.settings.automaticUnloadingOnField,'toggle', 3, 6, 1 )
+function courseplay.hud:setBaleCollectorAIDriverContent(vehicle)
+ self:debug(vehicle,"setBaleCollectorAIDriverContent")
+ self:addRowButton(vehicle,vehicle.cp.settings.autoDriveMode,'changeByX', 1, 3, 1 )
+ self:addSettingsRow(vehicle, vehicle.cp.settings.baleCollectionField,'changeByX', 1, 4, 1 )
+ self:addRowButton(vehicle,nil,'setCustomSingleFieldEdge', 1, 5, 1 )
+ self:addSettingsRow(vehicle,nil,'setCustomFieldEdgePathNumber', 1, 5, 2 )
+ self:setupCustomFieldEdgeButtons(vehicle,1,5)
+ self:addRowButton(vehicle,nil,'addCustomSingleFieldEdgeToList', 1, 6, 1 )
+ -- shown in place of the custom field row when a course is loaded
+
+ --page 3 settings
+ self:enablePageButton(vehicle, 3)
+ self:addSettingsRow(vehicle,nil,'changeTurnDiameter', 3, 1, 1 )
+
+ --page 8 fieldwork settings
+ self:enablePageButton(vehicle, 8)
+ self:addRowButton(vehicle,vehicle.cp.settings.useRealisticDriving,'toggle', 8, 4, 1 )
self:setReloadPageOrder(vehicle, -1, true)
end
diff --git a/img/iconSprite.dds b/img/iconSprite.dds
index 80dff3f76..5d2ca1948 100644
Binary files a/img/iconSprite.dds and b/img/iconSprite.dds differ
diff --git a/modDesc.xml b/modDesc.xml
index c79564b47..31369b859 100644
--- a/modDesc.xml
+++ b/modDesc.xml
@@ -1,6 +1,6 @@
- 6.03.00042
+ 6.03.00043
CoursePlay SIX
diff --git a/reloadAIDriver.bat b/reloadAIDriver.bat
index f69caeb63..46d770f1f 100644
--- a/reloadAIDriver.bat
+++ b/reloadAIDriver.bat
@@ -9,6 +9,7 @@ type PlowAIDriver.lua >> %outfile%
type UnloadableFieldworkAIDriver.lua >> %outfile%
type GrainTransportAIDriver.lua >> %outfile%
type BaleLoaderAIDriver.lua >> %outfile%
+type BaleCollectorAIDriver.lua >> %outfile%
type BaleWrapperAIDriver.lua >> %outfile%
type BalerAIDriver.lua >> %outfile%
type CombineAIDriver.lua >> %outfile%
diff --git a/settings.lua b/settings.lua
index 66e22546c..a2f92203d 100644
--- a/settings.lua
+++ b/settings.lua
@@ -11,14 +11,16 @@ function courseplay:openCloseHud(vehicle, open)
end;
end;
-function courseplay:setCpMode(vehicle, modeNum)
+function courseplay:setCpMode(vehicle, modeNum, dontSetAIDriver)
if vehicle.cp.mode ~= modeNum then
vehicle.cp.mode = modeNum;
- --courseplay:setNextPrevModeVars(vehicle);
courseplay.utils:setOverlayUVsPx(vehicle.cp.hud.currentModeIcon, courseplay.hud.bottomInfo.modeUVsPx[modeNum], courseplay.hud.iconSpriteSize.x, courseplay.hud.iconSpriteSize.y);
- --courseplay.buttons:setActiveEnabled(vehicle, 'all');
- --end
- courseplay:setAIDriver(vehicle, modeNum)
+ if not dontSetAIDriver then
+ -- another ugly hack: when this is called from loadVehicleCPSettings,
+ -- setAIDriver fails as not everything is loaded yet, so just for that case,
+ -- don't call it from here.
+ courseplay:setAIDriver(vehicle, modeNum)
+ end
end;
end;
@@ -43,6 +45,8 @@ function courseplay:setAIDriver(vehicle, mode)
status,driver,err,errDriverName = xpcall(FillableFieldworkAIDriver, function(err) printCallstack(); return self,err,"FillableFieldworkAIDriver" end, vehicle)
elseif mode == courseplay.MODE_FIELDWORK then
status,driver,err,errDriverName = xpcall(UnloadableFieldworkAIDriver.create, function(err) printCallstack(); return self,err,"UnloadableFieldworkAIDriver" end, vehicle)
+ elseif mode == courseplay.MODE_BALE_COLLECTOR then
+ status,driver,err,errDriverName = xpcall(BaleCollectorAIDriver, function(err) printCallstack(); return self,err,"BaleCollectorAIDriver" end, vehicle)
elseif mode == courseplay.MODE_BUNKERSILO_COMPACTER then
status,driver,err,errDriverName = xpcall(LevelCompactAIDriver, function(err) printCallstack(); return self,err,"LevelCompactAIDriver" end, vehicle)
elseif mode == courseplay.MODE_FIELD_SUPPLY then
@@ -1806,35 +1810,63 @@ StartingPointSetting.START_AT_FIRST_POINT = 2 -- first waypoint
StartingPointSetting.START_AT_CURRENT_POINT = 3 -- current waypoint
StartingPointSetting.START_AT_NEXT_POINT = 4 -- nearest waypoint with approximately same direction as vehicle
StartingPointSetting.START_WITH_UNLOAD = 5 -- start with unloading the combine (only for CombineUnloadAIDriver)
+StartingPointSetting.START_COLLECTING_BALES = 6 -- start with unloading the combine (only for CombineUnloadAIDriver)
function StartingPointSetting:init(vehicle)
- SettingList.init(self, 'startingPoint', 'COURSEPLAY_START_AT_POINT', 'COURSEPLAY_START_AT_POINT', vehicle,
- {
- StartingPointSetting.START_AT_NEAREST_POINT,
- StartingPointSetting.START_AT_FIRST_POINT ,
- StartingPointSetting.START_AT_CURRENT_POINT,
- StartingPointSetting.START_AT_NEXT_POINT,
- StartingPointSetting.START_WITH_UNLOAD
- },
- {
- "COURSEPLAY_NEAREST_POINT",
- "COURSEPLAY_FIRST_POINT" ,
- "COURSEPLAY_CURRENT_POINT",
- "COURSEPLAY_NEXT_POINT",
- "COURSEPLAY_UNLOAD"
- })
+ local values, texts = self:getValuesForMode(vehicle.cp.mode)
+ SettingList.init(self, 'startingPoint',
+ 'COURSEPLAY_START_AT_POINT', 'COURSEPLAY_START_AT_POINT', vehicle, values, texts)
+end
+
+function StartingPointSetting:getValuesForMode(mode)
+ if mode == courseplay.MODE_COMBI or mode == courseplay.MODE_OVERLOADER then
+ return {
+ StartingPointSetting.START_AT_NEAREST_POINT,
+ StartingPointSetting.START_AT_FIRST_POINT ,
+ StartingPointSetting.START_AT_CURRENT_POINT,
+ StartingPointSetting.START_AT_NEXT_POINT,
+ StartingPointSetting.START_WITH_UNLOAD
+ },
+ {
+ "COURSEPLAY_NEAREST_POINT",
+ "COURSEPLAY_FIRST_POINT" ,
+ "COURSEPLAY_CURRENT_POINT",
+ "COURSEPLAY_NEXT_POINT",
+ "COURSEPLAY_UNLOAD",
+ }
+ elseif mode == courseplay.MODE_BALE_COLLECTOR then
+ return {
+ StartingPointSetting.START_AT_NEAREST_POINT,
+ StartingPointSetting.START_AT_FIRST_POINT ,
+ StartingPointSetting.START_AT_NEXT_POINT,
+ StartingPointSetting.START_COLLECTING_BALES
+ },
+ {
+ "COURSEPLAY_NEAREST_POINT",
+ "COURSEPLAY_FIRST_POINT" ,
+ "COURSEPLAY_NEXT_POINT",
+ "COURSEPLAY_COLLECT_BALES",
+ }
+ else
+ return {
+ StartingPointSetting.START_AT_NEAREST_POINT,
+ StartingPointSetting.START_AT_FIRST_POINT ,
+ StartingPointSetting.START_AT_CURRENT_POINT,
+ StartingPointSetting.START_AT_NEXT_POINT,
+ },
+ {
+ "COURSEPLAY_NEAREST_POINT",
+ "COURSEPLAY_FIRST_POINT" ,
+ "COURSEPLAY_CURRENT_POINT",
+ "COURSEPLAY_NEXT_POINT",
+ }
+ end
end
function StartingPointSetting:checkAndSetValidValue(new)
- -- enable unload only for CombineUnloadAIDriver/Overloader
- if self.vehicle.cp.driver and
- self.vehicle.cp.mode ~= courseplay.MODE_COMBI and
- self.vehicle.cp.mode ~= courseplay.MODE_OVERLOADER and
- self.values[new] == StartingPointSetting.START_WITH_UNLOAD then
- return 1
- else
- return SettingList.checkAndSetValidValue(self, new)
- end
+ -- make sure we always have a valid set for the current mode
+ self.values, self.texts = self:getValuesForMode(self.vehicle.cp.mode)
+ return SettingList.checkAndSetValidValue(self, new)
end
---@class StartingLocationSetting : SettingList
@@ -2053,6 +2085,10 @@ function ReturnToFirstPointSetting:init(vehicle)
})
end
+function ReturnToFirstPointSetting:isDisabled()
+ return not self.vehicle.cp.canDrive or self.vehicle.cp.mode == courseplay.MODE_BALE_COLLECTOR
+end
+
function ReturnToFirstPointSetting:isReturnToStartActive()
return self:get() == self.RETURN_TO_START or self:get() == self.RETURN_TO_START_AND_RELEASE_DRIVER
end
@@ -2119,10 +2155,9 @@ end
--- Setting to select a field
---@class FieldNumberSetting : SettingList
FieldNumberSetting = CpObject(SettingList)
-function FieldNumberSetting:init(vehicle)
+function FieldNumberSetting:init(name, label, toolTip, vehicle)
local values, texts = self:loadFields()
- SettingList.init(self, 'fieldNumbers', 'COURSEPLAY_FIELD', 'COURSEPLAY_FIELD',
- vehicle, values, texts)
+ SettingList.init(self, name, label, toolTip, vehicle, values, texts)
end
function FieldNumberSetting:loadFields()
@@ -2130,10 +2165,13 @@ function FieldNumberSetting:loadFields()
local texts = {}
for fieldNumber, _ in pairs( courseplay.fields.fieldData ) do
table.insert(values, fieldNumber)
- table.insert(texts, fieldNumber)
end
+ -- numeric sort first
table.sort( values, function( a, b ) return a < b end )
- table.sort( texts, function( a, b ) return a < b end )
+ -- then convert to text
+ for _, fieldNumber in ipairs(values) do
+ table.insert(texts, tostring(fieldNumber))
+ end
return values, texts
end
@@ -2149,16 +2187,40 @@ function FieldNumberSetting:refresh()
self.current = math.min(self.current, #self.values)
end
+-- see above, refresh in case it was not initialized
+function FieldNumberSetting:get()
+ if #self.values == 0 then
+ self:refresh()
+ end
+ return SettingList.get(self)
+end
+
+-- see above, refresh in case it was not initialized
+function FieldNumberSetting:getText()
+ if #self.values == 0 then
+ self:refresh()
+ end
+ return SettingList.getText(self)
+end
+
+function FieldNumberSetting:changeByX(x)
+ self:refresh()
+ SettingList.changeByX(self, x)
+end
+
+--- Field to collect the bales from
+---@class BaleCollectionFieldSetting : FieldNumberSetting
+BaleCollectionFieldSetting = CpObject(FieldNumberSetting)
+function BaleCollectionFieldSetting:init(vehicle)
+ FieldNumberSetting.init(self, 'baleCollectionField', 'COURSEPLAY_FIELD', 'COURSEPLAY_FIELD', vehicle)
+end
+
--- Search combine on field
---@class SearchCombineOnFieldSetting : FieldNumberSetting
SearchCombineOnFieldSetting = CpObject(FieldNumberSetting)
function SearchCombineOnFieldSetting:init(vehicle)
- FieldNumberSetting.init(self, vehicle)
- self.name = 'searchCombineOnField'
- self.label = 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD'
- self.tooltip = 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD'
- self.xmlKey = 'searchCombineOnField'
- self.xmlAttribute = '#fieldNumber'
+ FieldNumberSetting.init(self, 'searchCombineOnField',
+ 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD', 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD', vehicle)
self:addNoneSelected()
end
@@ -2188,7 +2250,6 @@ end
SelectedCombineToUnloadSetting = CpObject(SettingList)
function SelectedCombineToUnloadSetting:init()
- print("SelectedCombineToUnloadSetting:init()")
self.name = 'selectedCombineToUnload'
self.label = 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD'
self.tooltip = 'COURSEPLAY_SEARCH_COMBINE_ON_FIELD'
@@ -2386,14 +2447,6 @@ function AutomaticCoverHandlingSetting:init(vehicle)
self:set(true)
end
---no Function!!
----@class AutomaticUnloadingOnFieldSetting : BooleanSetting
-AutomaticUnloadingOnFieldSetting = CpObject(BooleanSetting)
-function AutomaticUnloadingOnFieldSetting:init(vehicle)
- BooleanSetting.init(self, 'automaticUnloadingOnField', 'COURSEPLAY_UNLOADING_ON_FIELD', 'COURSEPLAY_UNLOADING_ON_FIELD',vehicle, {'COURSEPLAY_MANUAL','COURSEPLAY_AUTOMATIC'})
- self:set(false)
-end
-
---@class DriverPriorityUseFillLevelSetting : BooleanSetting
DriverPriorityUseFillLevelSetting = CpObject(BooleanSetting)
function DriverPriorityUseFillLevelSetting:init(vehicle)
@@ -2491,9 +2544,6 @@ function ShowMapHotspotSetting:getMapHotspotText(vehicle)
end
function ShowMapHotspotSetting:createMapHotspot()
- if self.vehicle.cp.mode == courseplay.MODE_COMBINE_SELF_UNLOADING then
- return
- end
--[[
local hotspotX, _, hotspotZ = getWorldTranslation(vehicle.rootNode);
local _, textSize = getNormalizedScreenValues(0, 6);
@@ -3032,13 +3082,6 @@ function TurnOnFieldSetting:init(vehicle)
self:set(true)
end
----@class TurnStageSetting : BooleanSetting
-TurnStageSetting = CpObject(BooleanSetting)
-function TurnStageSetting:init(vehicle)
- BooleanSetting.init(self, 'turnStage','COURSEPLAY_TURN_MANEUVER', 'COURSEPLAY_TURN_MANEUVER', vehicle, {'COURSEPLAY_START','COURSEPLAY_FINISH'})
- self:set(false)
-end
-
---@class RefillUntilPctSetting : PercentageSettingList
RefillUntilPctSetting = CpObject(PercentageSettingList)
function RefillUntilPctSetting:init(vehicle)
@@ -4143,7 +4186,7 @@ function SettingsContainer.createVehicleSpecificSettings(vehicle)
container:addSetting(EnableVisualWaypointsTemporary, vehicle)
container:addSetting(StopAtEndSetting, vehicle)
container:addSetting(AutomaticCoverHandlingSetting, vehicle)
- container:addSetting(AutomaticUnloadingOnFieldSetting, vehicle)
+ container:addSetting(BaleCollectionFieldSetting, vehicle)
container:addSetting(DriverPriorityUseFillLevelSetting, vehicle)
container:addSetting(UseRecordingSpeedSetting, vehicle)
container:addSetting(WarningLightsModeSetting, vehicle)
@@ -4154,7 +4197,6 @@ function SettingsContainer.createVehicleSpecificSettings(vehicle)
container:addSetting(DriveUnloadNowSetting, vehicle)
container:addSetting(CombineWantsCourseplayerSetting, vehicle)
container:addSetting(TurnOnFieldSetting, vehicle)
- container:addSetting(TurnStageSetting, vehicle)
container:addSetting(GrainTransportDriver_SiloSelectedFillTypeSetting, vehicle)
container:addSetting(FillableFieldWorkDriver_SiloSelectedFillTypeSetting, vehicle)
container:addSetting(FieldSupplyDriver_SiloSelectedFillTypeSetting, vehicle)
diff --git a/test/mock-Courseplay.lua b/test/mock-Courseplay.lua
index 3e21d8798..06db1427d 100644
--- a/test/mock-Courseplay.lua
+++ b/test/mock-Courseplay.lua
@@ -73,7 +73,7 @@ courseplay.MODE_OVERLOADER = 3;
courseplay.MODE_SEED_FERTILIZE = 4;
courseplay.MODE_TRANSPORT = 5;
courseplay.MODE_FIELDWORK = 6;
-courseplay.MODE_COMBINE_SELF_UNLOADING = 7;
+courseplay.MODE_BALE_COLLECTOR = 7;
courseplay.MODE_LIQUIDMANURE_TRANSPORT = 8;
courseplay.MODE_SHOVEL_FILL_AND_EMPTY = 9;
courseplay.MODE_BUNKERSILO_COMPACTER = 10;
diff --git a/toolManager.lua b/toolManager.lua
index cd92d29cf..9d30cc23d 100644
--- a/toolManager.lua
+++ b/toolManager.lua
@@ -37,18 +37,18 @@ function courseplay:updateOnAttachOrDetach(vehicle)
-- TODO: refactor this (if it is even still needed), this ignore list is all over the place...
vehicle.cpTrafficCollisionIgnoreList = {}
- if vehicle.cp and vehicle.cp.settings then
- vehicle.cp.settings:validateCurrentValues()
- if vehicle.cp.driver then
- vehicle.cp.driver:refreshHUD()
- end
- end
-
courseplay:resetTools(vehicle)
+ courseplay:setAIDriver(vehicle, vehicle.cp.mode)
+
+ vehicle.cp.settings:validateCurrentValues()
-- reset tool offset to the preconfigured value if exists
vehicle.cp.settings.toolOffsetX:setToConfiguredValue()
+ if vehicle.cp.driver then
+ vehicle.cp.driver:refreshHUD()
+ end
+
end
--- Set up tool configuration after something is attached or detached
@@ -490,9 +490,6 @@ function courseplay:getIsToolCombiValidForCpMode(vehicle,cpModeToCheck)
if cpModeToCheck == 5 then
return true;
end
- if cpModeToCheck == 7 then
- return false;
- end
local callback = {}
callback.isDisallowedOkay = true
courseplay:getIsToolValidForCpMode(vehicle,cpModeToCheck,callback)
diff --git a/translations/translation_br.xml b/translations/translation_br.xml
index b890686f3..80ad9b501 100644
--- a/translations/translation_br.xml
+++ b/translations/translation_br.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_cs.xml b/translations/translation_cs.xml
index 137cc33e5..69f50b744 100644
--- a/translations/translation_cs.xml
+++ b/translations/translation_cs.xml
@@ -5,9 +5,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_cz.xml b/translations/translation_cz.xml
index ab338e0e1..93e6a7224 100644
--- a/translations/translation_cz.xml
+++ b/translations/translation_cz.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_de.xml b/translations/translation_de.xml
index 23a67ff42..1ea904561 100644
--- a/translations/translation_de.xml
+++ b/translations/translation_de.xml
@@ -5,9 +5,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_en.xml b/translations/translation_en.xml
index 7de65bac4..2b6efd5cc 100644
--- a/translations/translation_en.xml
+++ b/translations/translation_en.xml
@@ -5,9 +5,6 @@
-
-
-
@@ -41,7 +38,7 @@
-
+
@@ -75,6 +72,7 @@
+
@@ -122,10 +120,10 @@
-
+
-
+
@@ -171,7 +169,7 @@
-
+
@@ -232,7 +230,7 @@
-
+
@@ -245,7 +243,7 @@
-
+
@@ -286,6 +284,7 @@
+
@@ -334,7 +333,7 @@
-
+
@@ -398,16 +397,16 @@
-
-
+
+
-
+
-
+
@@ -448,8 +447,8 @@
-
-
+
+
@@ -469,8 +468,8 @@
-
-
+
+
@@ -481,23 +480,23 @@
-
-
+
+
-
+
-
-
+
+
-
+
-
+
diff --git a/translations/translation_es.xml b/translations/translation_es.xml
index df0fb4f24..ed668c63c 100644
--- a/translations/translation_es.xml
+++ b/translations/translation_es.xml
@@ -5,9 +5,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -286,6 +284,7 @@
+
diff --git a/translations/translation_fr.xml b/translations/translation_fr.xml
index 42217941e..496a87871 100644
--- a/translations/translation_fr.xml
+++ b/translations/translation_fr.xml
@@ -5,9 +5,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,8 @@
+
+
diff --git a/translations/translation_hu.xml b/translations/translation_hu.xml
index 647215cc5..beb963981 100644
--- a/translations/translation_hu.xml
+++ b/translations/translation_hu.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -71,6 +68,7 @@
+
@@ -248,11 +246,11 @@
-
-
-
-
-
+
+
+
+
+
@@ -278,9 +276,10 @@
-
+
-
+
+
diff --git a/translations/translation_it.xml b/translations/translation_it.xml
index 1bcfdbfd4..f45d22bb4 100644
--- a/translations/translation_it.xml
+++ b/translations/translation_it.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_jp.xml b/translations/translation_jp.xml
index ebce25fbf..b63f8a143 100644
--- a/translations/translation_jp.xml
+++ b/translations/translation_jp.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_nl.xml b/translations/translation_nl.xml
index 213ff158d..ad6193956 100644
--- a/translations/translation_nl.xml
+++ b/translations/translation_nl.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -71,6 +68,7 @@
+
@@ -281,6 +279,7 @@
+
diff --git a/translations/translation_pl.xml b/translations/translation_pl.xml
index a52d4d944..277010571 100644
--- a/translations/translation_pl.xml
+++ b/translations/translation_pl.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/translations/translation_pt.xml b/translations/translation_pt.xml
index 6f42157fc..c13ad2dc1 100644
--- a/translations/translation_pt.xml
+++ b/translations/translation_pt.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -71,6 +68,7 @@
+
@@ -281,6 +279,7 @@
+
diff --git a/translations/translation_ru.xml b/translations/translation_ru.xml
index 1b200b29a..0b0bb83dc 100644
--- a/translations/translation_ru.xml
+++ b/translations/translation_ru.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -286,6 +284,7 @@
+
diff --git a/translations/translation_sl.xml b/translations/translation_sl.xml
index a9b2a9090..56db05325 100644
--- a/translations/translation_sl.xml
+++ b/translations/translation_sl.xml
@@ -3,9 +3,6 @@
-
-
-
@@ -75,6 +72,7 @@
+
@@ -285,6 +283,7 @@
+
diff --git a/turn.lua b/turn.lua
index f2ceb2d8d..b5c540807 100644
--- a/turn.lua
+++ b/turn.lua
@@ -99,235 +99,215 @@ function courseplay:turn(vehicle, dt, turnContext)
----------------------------------------------------------
- -- TURN STAGES 1 - Create Turn maneuver (Creating waypoints to follow)
+ -- Create Turn maneuver (Creating waypoints to follow)
----------------------------------------------------------
- if vehicle.cp.settings.turnStage:is(true) then
- --- Cleanup in case we already have old info
- courseplay:clearTurnTargets(vehicle); -- Make sure we have cleaned it from any previus usage.
-
- --- Setting default turnInfo values
- local turnInfo = {};
- turnInfo.directionNode = realDirectionNode
- turnInfo.frontMarker = frontMarker;
- turnInfo.backMarker = backMarker;
- turnInfo.halfVehicleWidth = 2.5;
- turnInfo.directionNodeToTurnNodeLength = directionNodeToTurnNodeLength + 0.5; -- 0.5 is to make the start turn point just a tiny in front of the tractor
- -- when PPC is driving we don't have to care about wp change distances, PPC takes care of that. Still use
- -- a small value to make sure none of the turn generator functions end up with overlapping waypoints
- turnInfo.wpChangeDistance = 0.5
- turnInfo.reverseWPChangeDistance = 0.5
- turnInfo.direction = -1;
- turnInfo.haveHeadlands = courseplay:haveHeadlands(vehicle);
-
- -- find out the headland height to figure out if we have enough room on the headland to make turns
- if vehicle.cp.courseWorkWidth and vehicle.cp.courseWorkWidth > 0 and vehicle.cp.courseNumHeadlandLanes and vehicle.cp.courseNumHeadlandLanes > 0 then
- -- First headland is only half the work width
- turnInfo.headlandHeight = vehicle.cp.courseWorkWidth / 2 + ((vehicle.cp.courseNumHeadlandLanes - 1) * vehicle.cp.courseWorkWidth)
- else
- turnInfo.headlandHeight = 0
- end
+ --- Cleanup in case we already have old info
+ courseplay:clearTurnTargets(vehicle); -- Make sure we have cleaned it from any previus usage.
+
+ --- Setting default turnInfo values
+ local turnInfo = {};
+ turnInfo.directionNode = realDirectionNode
+ turnInfo.frontMarker = frontMarker;
+ turnInfo.backMarker = backMarker;
+ turnInfo.halfVehicleWidth = 2.5;
+ turnInfo.directionNodeToTurnNodeLength = directionNodeToTurnNodeLength + 0.5; -- 0.5 is to make the start turn point just a tiny in front of the tractor
+ -- when PPC is driving we don't have to care about wp change distances, PPC takes care of that. Still use
+ -- a small value to make sure none of the turn generator functions end up with overlapping waypoints
+ turnInfo.wpChangeDistance = 0.5
+ turnInfo.reverseWPChangeDistance = 0.5
+ turnInfo.direction = -1;
+ turnInfo.haveHeadlands = courseplay:haveHeadlands(vehicle);
+
+ -- find out the headland height to figure out if we have enough room on the headland to make turns
+ if vehicle.cp.courseWorkWidth and vehicle.cp.courseWorkWidth > 0 and vehicle.cp.courseNumHeadlandLanes and vehicle.cp.courseNumHeadlandLanes > 0 then
+ -- First headland is only half the work width
+ turnInfo.headlandHeight = vehicle.cp.courseWorkWidth / 2 + ((vehicle.cp.courseNumHeadlandLanes - 1) * vehicle.cp.courseWorkWidth)
+ else
+ turnInfo.headlandHeight = 0
+ end
- -- if the headland is not perpendicular, we have less room to turn
- turnInfo.headlandHeight = turnInfo.headlandHeight * math.cos(turnContext:getHeadlandAngle())
-
- -- Headland height in the waypoint overrides the generic headland height calculation. This is for the
- -- short edge headlands where we make 180 turns on te headland course. The generic calculation would use
- -- the number of headlands and think there is room on the headland to make the turn.
- -- Therefore, the course generator will add a headlandHeightForTurn = 0 for these turn waypoints to make
- -- sure on field turns are calculated correctly.
- turnInfo.headlandHeight = turnContext.turnStartWp.headlandHeightForTurn and
- turnContext.turnStartWp.headlandHeightForTurn or turnInfo.headlandHeight;
-
- turnInfo.numLanes ,turnInfo.onLaneNum = courseplay:getLaneInfo(vehicle);
- turnInfo.turnOnField = vehicle.cp.settings.turnOnField:is(true);
- turnInfo.reverseOffset = 0;
- turnInfo.extraAlignLength = 6;
- turnInfo.haveWheeledImplement = reversingWorkTool ~= nil;
- if turnInfo.haveWheeledImplement then
- turnInfo.reversingWorkTool = reversingWorkTool;
- turnInfo.extraAlignLength = turnInfo.extraAlignLength + directionNodeToTurnNodeLength * 2;
- end;
- turnInfo.isHarvester = isHarvester;
- turnInfo.noReverse = g_vehicleConfigurations:getRecursively(vehicle, 'noReverse')
-
- -- headland turn data
- vehicle.cp.headlandTurn = turnContext:isHeadlandCorner() and {} or nil
- -- direction halfway between dir of turnStart and turnEnd
- turnInfo.halfAngle = math.deg( getAverageAngle( math.rad( turnContext.turnEndWp.angle ),
- math.rad( turnContext.turnStartWp.angle )))
- -- delta between turn start and turn end
- turnInfo.deltaAngle = math.pi - ( math.rad( turnContext.turnEndWp.angle )
- - math.rad( turnContext.turnStartWp.angle ))
-
- turnInfo.startDirection = turnContext.turnStartWp.angle
-
- --- Get the turn radius either by the automatic or user provided turn circle.
- local extRadius = 0.5 + (0.15 * directionNodeToTurnNodeLength); -- The extra calculation is for dynamic trailer length to prevent jackknifing;
- turnInfo.turnRadius = vehicle.cp.turnDiameter * 0.5 + extRadius;
- turnInfo.turnDiameter = turnInfo.turnRadius * 2;
-
-
- --- Create temp target node and translate it.
- turnInfo.targetNode = turnContext.turnEndWpNode.node
- local cx,cz = turnContext.turnEndWp.x, turnContext.turnEndWp.z
-
- --- Debug Print
- if courseplay.debugChannels[14] then
- local x,y,z = getWorldTranslation(turnInfo.targetNode);
- local ctx,_,ctz = localToWorld(turnInfo.targetNode, 0, 0, 20);
- --drawDebugLine(x, y+5, z, 1, 0, 0, ctx, y+5, ctz, 0, 1, 0);
- cpDebug:drawLine(x, y+5, z, 1, 0, 0, ctx, y+5, ctz);
- -- this is an test
- courseplay:debug(("%s:(Turn) wp%d=%.1f°, wp%d=%.1f°, directionChangeDeg = %.1f° halfAngle = %.1f"):format(nameNum(vehicle),
- turnContext.beforeTurnStartWp.cpIndex, turnContext.beforeTurnStartWp.angle, turnContext.turnEndWp.cpIndex, turnContext.turnEndWp.angle, turnContext.directionChangeDeg, turnInfo.halfAngle), 14);
- end;
+ -- if the headland is not perpendicular, we have less room to turn
+ turnInfo.headlandHeight = turnInfo.headlandHeight * math.cos(turnContext:getHeadlandAngle())
+
+ -- Headland height in the waypoint overrides the generic headland height calculation. This is for the
+ -- short edge headlands where we make 180 turns on te headland course. The generic calculation would use
+ -- the number of headlands and think there is room on the headland to make the turn.
+ -- Therefore, the course generator will add a headlandHeightForTurn = 0 for these turn waypoints to make
+ -- sure on field turns are calculated correctly.
+ turnInfo.headlandHeight = turnContext.turnStartWp.headlandHeightForTurn and
+ turnContext.turnStartWp.headlandHeightForTurn or turnInfo.headlandHeight;
+
+ turnInfo.numLanes ,turnInfo.onLaneNum = courseplay:getLaneInfo(vehicle);
+ turnInfo.turnOnField = vehicle.cp.settings.turnOnField:is(true);
+ turnInfo.reverseOffset = 0;
+ turnInfo.extraAlignLength = 6;
+ turnInfo.haveWheeledImplement = reversingWorkTool ~= nil;
+ if turnInfo.haveWheeledImplement then
+ turnInfo.reversingWorkTool = reversingWorkTool;
+ turnInfo.extraAlignLength = turnInfo.extraAlignLength + directionNodeToTurnNodeLength * 2;
+ end;
+ turnInfo.isHarvester = isHarvester;
+ turnInfo.noReverse = g_vehicleConfigurations:getRecursively(vehicle, 'noReverse')
+
+ -- headland turn data
+ vehicle.cp.headlandTurn = turnContext:isHeadlandCorner() and {} or nil
+ -- direction halfway between dir of turnStart and turnEnd
+ turnInfo.halfAngle = math.deg( getAverageAngle( math.rad( turnContext.turnEndWp.angle ),
+ math.rad( turnContext.turnStartWp.angle )))
+ -- delta between turn start and turn end
+ turnInfo.deltaAngle = math.pi - ( math.rad( turnContext.turnEndWp.angle )
+ - math.rad( turnContext.turnStartWp.angle ))
- --- Get the local delta distances from the tractor to the targetNode
- turnInfo.targetDeltaX, _, turnInfo.targetDeltaZ = worldToLocal(turnInfo.directionNode, cx, vehicleY, cz);
- courseplay:debug(string.format("%s:(Turn) targetDeltaX=%.2f, targetDeltaZ=%.2f", nameNum(vehicle), turnInfo.targetDeltaX, turnInfo.targetDeltaZ), 14);
+ turnInfo.startDirection = turnContext.turnStartWp.angle
- --- Get the turn direction
- if turnContext:isHeadlandCorner() then
- -- headland corner turns have a targetDeltaX around 0 so use the direction diff
- if turnContext.directionChangeDeg > 0 then
- turnInfo.direction = 1;
- end
- else
- if turnInfo.targetDeltaX > 0 then
- turnInfo.direction = 1;
- end;
- end
+ --- Get the turn radius either by the automatic or user provided turn circle.
+ local extRadius = 0.5 + (0.15 * directionNodeToTurnNodeLength); -- The extra calculation is for dynamic trailer length to prevent jackknifing;
+ turnInfo.turnRadius = vehicle.cp.turnDiameter * 0.5 + extRadius;
+ turnInfo.turnDiameter = turnInfo.turnRadius * 2;
- -- Relative position of the turn start waypoint from the vehicle.
- -- Note that as we start the turn when the backMarkerOffset reaches the turn start point, this zOffset
- -- is the same as the backMarkerOffset
- _, _, turnInfo.zOffset = worldToLocal(turnInfo.directionNode, turnContext.turnStartWp.x, vehicleY, turnContext.turnStartWp.z);
- -- remember this as we'll need it later
- turnInfo.deltaZBetweenVehicleAndTarget = turnInfo.targetDeltaZ
- -- targetDeltaZ is now the delta Z between the turn start and turn end waypoints.
- turnInfo.targetDeltaZ = turnInfo.targetDeltaZ - turnInfo.zOffset;
-
- -- Calculate reverseOffset in case we need to reverse.
- -- This is used in both wide turns and in the question mark turn
- local offset = turnInfo.zOffset;
- -- only if all implements are in the front
- if turnInfo.frontMarker > 0 and turnInfo.backMarker > 0 then
- offset = -turnInfo.zOffset - turnInfo.frontMarker;
- end;
- if turnInfo.turnOnField and not turnInfo.isHarvester and not turnInfo.noReverse then
- turnInfo.reverseOffset = max((turnInfo.turnRadius + turnInfo.halfVehicleWidth - turnInfo.headlandHeight), offset);
- elseif turnInfo.isHarvester and turnInfo.frontMarker > 0 then
- -- without fully understanding this reverseOffset, correct it for combines so they don't make
- -- unnecessarily wide turns (and hit trees outside the field)
- turnInfo.reverseOffset = -turnInfo.frontMarker
- else
- -- the weird thing about this is that reverseOffset here equals to zOffset and this is why
- -- the wide turn works at all, even if there's no reversing.
- turnInfo.reverseOffset = offset;
+
+ --- Create temp target node and translate it.
+ turnInfo.targetNode = turnContext.turnEndWpNode.node
+ local cx,cz = turnContext.turnEndWp.x, turnContext.turnEndWp.z
+
+ --- Debug Print
+ if courseplay.debugChannels[14] then
+ local x,y,z = getWorldTranslation(turnInfo.targetNode);
+ local ctx,_,ctz = localToWorld(turnInfo.targetNode, 0, 0, 20);
+ --drawDebugLine(x, y+5, z, 1, 0, 0, ctx, y+5, ctz, 0, 1, 0);
+ cpDebug:drawLine(x, y+5, z, 1, 0, 0, ctx, y+5, ctz);
+ -- this is an test
+ courseplay:debug(("%s:(Turn) wp%d=%.1f°, wp%d=%.1f°, directionChangeDeg = %.1f° halfAngle = %.1f"):format(nameNum(vehicle),
+ turnContext.beforeTurnStartWp.cpIndex, turnContext.beforeTurnStartWp.angle, turnContext.turnEndWp.cpIndex, turnContext.turnEndWp.angle, turnContext.directionChangeDeg, turnInfo.halfAngle), 14);
+ end;
+
+ --- Get the local delta distances from the tractor to the targetNode
+ turnInfo.targetDeltaX, _, turnInfo.targetDeltaZ = worldToLocal(turnInfo.directionNode, cx, vehicleY, cz);
+ courseplay:debug(string.format("%s:(Turn) targetDeltaX=%.2f, targetDeltaZ=%.2f", nameNum(vehicle), turnInfo.targetDeltaX, turnInfo.targetDeltaZ), 14);
+
+ --- Get the turn direction
+ if turnContext:isHeadlandCorner() then
+ -- headland corner turns have a targetDeltaX around 0 so use the direction diff
+ if turnContext.directionChangeDeg > 0 then
+ turnInfo.direction = 1;
+ end
+ else
+ if turnInfo.targetDeltaX > 0 then
+ turnInfo.direction = 1;
end;
+ end
- courseplay:debug(("%s:(Turn Data) frontMarker=%q, backMarker=%q, halfVehicleWidth=%q, directionNodeToTurnNodeLength=%q, wpChangeDistance=%q"):format(nameNum(vehicle), tostring(turnInfo.frontMarker), tostring(backMarker), tostring(turnInfo.halfVehicleWidth), tostring(turnInfo.directionNodeToTurnNodeLength), tostring(turnInfo.wpChangeDistance)), 14);
- courseplay:debug(("%s:(Turn Data) reverseWPChangeDistance=%q, direction=%q, haveHeadlands=%q, headlandHeight=%q"):format(nameNum(vehicle), tostring(turnInfo.reverseWPChangeDistance), tostring(turnInfo.direction), tostring(turnInfo.haveHeadlands), tostring(turnInfo.headlandHeight)), 14);
- courseplay:debug(("%s:(Turn Data) numLanes=%q, onLaneNum=%q, turnOnField=%q, reverseOffset=%q"):format(nameNum(vehicle), tostring(turnInfo.numLanes), tostring(turnInfo.onLaneNum), tostring(turnInfo.turnOnField), tostring(turnInfo.reverseOffset)), 14);
- courseplay:debug(("%s:(Turn Data) haveWheeledImplement=%q, reversingWorkTool=%q, turnRadius=%q, turnDiameter=%q"):format(nameNum(vehicle), tostring(turnInfo.haveWheeledImplement), tostring(turnInfo.reversingWorkTool), tostring(turnInfo.turnRadius), tostring(turnInfo.turnDiameter)), 14);
- courseplay:debug(("%s:(Turn Data) targetNode=%q, targetDeltaX=%q, targetDeltaZ=%q, zOffset=%q"):format(nameNum(vehicle), tostring(turnInfo.targetNode), tostring(turnInfo.targetDeltaX), tostring(turnInfo.targetDeltaZ), tostring(turnInfo.zOffset)), 14);
- courseplay:debug(("%s:(Turn Data) reverseOffset=%q, isHarvester=%q, noReverse=%q"):format(nameNum(vehicle), tostring(turnInfo.reverseOffset), tostring(turnInfo.isHarvester), tostring(turnInfo.noReverse)), 14);
+ -- Relative position of the turn start waypoint from the vehicle.
+ -- Note that as we start the turn when the backMarkerOffset reaches the turn start point, this zOffset
+ -- is the same as the backMarkerOffset
+ _, _, turnInfo.zOffset = worldToLocal(turnInfo.directionNode, turnContext.turnStartWp.x, vehicleY, turnContext.turnStartWp.z);
+ -- remember this as we'll need it later
+ turnInfo.deltaZBetweenVehicleAndTarget = turnInfo.targetDeltaZ
+ -- targetDeltaZ is now the delta Z between the turn start and turn end waypoints.
+ turnInfo.targetDeltaZ = turnInfo.targetDeltaZ - turnInfo.zOffset;
+
+ -- Calculate reverseOffset in case we need to reverse.
+ -- This is used in both wide turns and in the question mark turn
+ local offset = turnInfo.zOffset;
+ -- only if all implements are in the front
+ if turnInfo.frontMarker > 0 and turnInfo.backMarker > 0 then
+ offset = -turnInfo.zOffset - turnInfo.frontMarker;
+ end;
+ if turnInfo.turnOnField and not turnInfo.isHarvester and not turnInfo.noReverse then
+ turnInfo.reverseOffset = max((turnInfo.turnRadius + turnInfo.halfVehicleWidth - turnInfo.headlandHeight), offset);
+ elseif turnInfo.isHarvester and turnInfo.frontMarker > 0 then
+ -- without fully understanding this reverseOffset, correct it for combines so they don't make
+ -- unnecessarily wide turns (and hit trees outside the field)
+ turnInfo.reverseOffset = -turnInfo.frontMarker
+ else
+ -- the weird thing about this is that reverseOffset here equals to zOffset and this is why
+ -- the wide turn works at all, even if there's no reversing.
+ turnInfo.reverseOffset = offset;
+ end;
+ courseplay:debug(("%s:(Turn Data) frontMarker=%q, backMarker=%q, halfVehicleWidth=%q, directionNodeToTurnNodeLength=%q, wpChangeDistance=%q"):format(nameNum(vehicle), tostring(turnInfo.frontMarker), tostring(backMarker), tostring(turnInfo.halfVehicleWidth), tostring(turnInfo.directionNodeToTurnNodeLength), tostring(turnInfo.wpChangeDistance)), 14);
+ courseplay:debug(("%s:(Turn Data) reverseWPChangeDistance=%q, direction=%q, haveHeadlands=%q, headlandHeight=%q"):format(nameNum(vehicle), tostring(turnInfo.reverseWPChangeDistance), tostring(turnInfo.direction), tostring(turnInfo.haveHeadlands), tostring(turnInfo.headlandHeight)), 14);
+ courseplay:debug(("%s:(Turn Data) numLanes=%q, onLaneNum=%q, turnOnField=%q, reverseOffset=%q"):format(nameNum(vehicle), tostring(turnInfo.numLanes), tostring(turnInfo.onLaneNum), tostring(turnInfo.turnOnField), tostring(turnInfo.reverseOffset)), 14);
+ courseplay:debug(("%s:(Turn Data) haveWheeledImplement=%q, reversingWorkTool=%q, turnRadius=%q, turnDiameter=%q"):format(nameNum(vehicle), tostring(turnInfo.haveWheeledImplement), tostring(turnInfo.reversingWorkTool), tostring(turnInfo.turnRadius), tostring(turnInfo.turnDiameter)), 14);
+ courseplay:debug(("%s:(Turn Data) targetNode=%q, targetDeltaX=%q, targetDeltaZ=%q, zOffset=%q"):format(nameNum(vehicle), tostring(turnInfo.targetNode), tostring(turnInfo.targetDeltaX), tostring(turnInfo.targetDeltaZ), tostring(turnInfo.zOffset)), 14);
+ courseplay:debug(("%s:(Turn Data) reverseOffset=%q, isHarvester=%q, noReverse=%q"):format(nameNum(vehicle), tostring(turnInfo.reverseOffset), tostring(turnInfo.isHarvester), tostring(turnInfo.noReverse)), 14);
- if not turnContext:isHeadlandCorner() then
- ----------------------------------------------------------
- -- SWITCH TO THE NEXT LANE
- ----------------------------------------------------------
- courseplay:debug(string.format("%s:(Turn) Direction difference is %.1f, this is a lane switch.", nameNum(vehicle), turnContext.directionChangeDeg), 14);
- ----------------------------------------------------------
- -- WIDE TURNS (Turns where the distance to next lane is bigger than the turning Diameter)
- ----------------------------------------------------------
- if abs(turnInfo.targetDeltaX) >= turnInfo.turnDiameter then
- if abs(turnInfo.targetDeltaX) >= (turnInfo.turnDiameter * 2) and abs(turnInfo.targetDeltaZ) >= (turnInfo.turnRadius * 3) then
- courseplay:generateTurnTypeWideTurnWithAvoidance(vehicle, turnInfo);
- else
- courseplay:generateTurnTypeWideTurn(vehicle, turnInfo);
- end
- ----------------------------------------------------------
- -- NAROW TURNS (Turns where the distance to next lane is smaller than the turning Diameter)
- ----------------------------------------------------------
+ if not turnContext:isHeadlandCorner() then
+ ----------------------------------------------------------
+ -- SWITCH TO THE NEXT LANE
+ ----------------------------------------------------------
+ courseplay:debug(string.format("%s:(Turn) Direction difference is %.1f, this is a lane switch.", nameNum(vehicle), turnContext.directionChangeDeg), 14);
+ ----------------------------------------------------------
+ -- WIDE TURNS (Turns where the distance to next lane is bigger than the turning Diameter)
+ ----------------------------------------------------------
+ if abs(turnInfo.targetDeltaX) >= turnInfo.turnDiameter then
+ if abs(turnInfo.targetDeltaX) >= (turnInfo.turnDiameter * 2) and abs(turnInfo.targetDeltaZ) >= (turnInfo.turnRadius * 3) then
+ courseplay:generateTurnTypeWideTurnWithAvoidance(vehicle, turnInfo);
else
- --- If we have wheeled implement, then do turns based on that.
- if turnInfo.haveWheeledImplement then
- --- Get the Triangle sides
- local centerOffset = abs(turnInfo.targetDeltaX) / 2;
- local sideC = turnInfo.turnDiameter;
- local sideB = centerOffset + turnInfo.turnRadius;
- local centerHeight = square(sideC^2 - sideB^2);
-
- --- Check if there is enough space to make Ohm turn on the headland.
- local useOhmTurn = false;
- if (-turnInfo.zOffset + centerHeight + turnInfo.turnRadius + turnInfo.halfVehicleWidth) < turnInfo.headlandHeight then
- useOhmTurn = true;
- end;
+ courseplay:generateTurnTypeWideTurn(vehicle, turnInfo);
+ end
- --- Ohm Turn
- if useOhmTurn or turnInfo.isHarvester or turnInfo.noReverse or not turnInfo.turnOnField then
- courseplay:generateTurnTypeOhmTurn(vehicle, turnInfo);
- else
- --- Questionmark Turn
- courseplay:generateTurnTypeQuestionmarkTurn(vehicle, turnInfo);
- end;
+ ----------------------------------------------------------
+ -- NARROW TURNS (Turns where the distance to next lane is smaller than the turning Diameter)
+ ----------------------------------------------------------
+ else
+ --- If we have wheeled implement, then do turns based on that.
+ if turnInfo.haveWheeledImplement then
+ --- Get the Triangle sides
+ local centerOffset = abs(turnInfo.targetDeltaX) / 2;
+ local sideC = turnInfo.turnDiameter;
+ local sideB = centerOffset + turnInfo.turnRadius;
+ local centerHeight = square(sideC^2 - sideB^2);
+
+ --- Check if there is enough space to make Ohm turn on the headland.
+ local useOhmTurn = false;
+ if (-turnInfo.zOffset + centerHeight + turnInfo.turnRadius + turnInfo.halfVehicleWidth) < turnInfo.headlandHeight then
+ useOhmTurn = true;
+ end;
- --- If not wheeled implement, then do the short turns.
+ --- Ohm Turn
+ if useOhmTurn or turnInfo.isHarvester or turnInfo.noReverse or not turnInfo.turnOnField then
+ courseplay:generateTurnTypeOhmTurn(vehicle, turnInfo);
else
- --- Get the Triangle sides
- turnInfo.centerOffset = (turnInfo.targetDeltaX * turnInfo.direction) - turnInfo.turnRadius;
- local sideC = turnInfo.turnDiameter;
- local sideB = turnInfo.turnRadius + turnInfo.centerOffset; -- which is exactly targetDeltaX, see above
- turnInfo.centerHeight = square(sideC^2 - sideB^2);
-
- local neededSpace = abs(turnInfo.targetDeltaZ) + turnInfo.zOffset + 1 + turnInfo.centerHeight + (turnInfo.reverseWPChangeDistance * 1.5);
- --- Forward 3 Point Turn
- if neededSpace < turnInfo.headlandHeight or turnInfo.isHarvester or not turnInfo.turnOnField then
- courseplay:generateTurnTypeForward3PointTurn(vehicle, turnInfo);
-
- --- Reverse 3 Point Turn
- else
- courseplay:generateTurnTypeReverse3PointTurn(vehicle, turnInfo);
- end;
+ --- Questionmark Turn
+ courseplay:generateTurnTypeQuestionmarkTurn(vehicle, turnInfo);
end;
- end
- else
- -------------------------------------------------------------
- -- A SHARP TURN, LIKELY ON THE HEADLAND BUT NOT A LANE SWITCH
- -------------------------------------------------------------
- courseplay:debug(string.format("%s:(Turn) Direction difference is %.1f, this is a corner, maneuver type = %d.",
- nameNum(vehicle), turnContext.directionChangeDeg, vehicle.cp.headland.reverseManeuverType), 14);
- vehicle.cp.turnCorner = turnContext:createCorner(vehicle, turnInfo.turnRadius)
-
- courseplay.generateTurnTypeHeadlandCornerReverseStraightTractor(vehicle, turnInfo)
+ --- If not wheeled implement, then do the short turns.
+ else
+ --- Get the Triangle sides
+ turnInfo.centerOffset = (turnInfo.targetDeltaX * turnInfo.direction) - turnInfo.turnRadius;
+ local sideC = turnInfo.turnDiameter;
+ local sideB = turnInfo.turnRadius + turnInfo.centerOffset; -- which is exactly targetDeltaX, see above
+ turnInfo.centerHeight = square(sideC^2 - sideB^2);
+
+ local neededSpace = abs(turnInfo.targetDeltaZ) + turnInfo.zOffset + 1 + turnInfo.centerHeight + (turnInfo.reverseWPChangeDistance * 1.5);
+ --- Forward 3 Point Turn
+ if neededSpace < turnInfo.headlandHeight or turnInfo.isHarvester or not turnInfo.turnOnField then
+ courseplay:generateTurnTypeForward3PointTurn(vehicle, turnInfo);
+
+ --- Reverse 3 Point Turn
+ else
+ courseplay:generateTurnTypeReverse3PointTurn(vehicle, turnInfo);
+ end;
+ end;
end
+ else
+ -------------------------------------------------------------
+ -- A SHARP TURN, LIKELY ON THE HEADLAND BUT NOT A LANE SWITCH
+ -------------------------------------------------------------
+ courseplay:debug(string.format("%s:(Turn) Direction difference is %.1f, this is a corner, maneuver type = %d.",
+ nameNum(vehicle), turnContext.directionChangeDeg, vehicle.cp.headland.reverseManeuverType), 14);
- cpPrintLine(14, 1);
- courseplay:debug(string.format("%s:(Turn) Generated %d Turn Waypoints", nameNum(vehicle), #vehicle.cp.turnTargets), 14);
- cpPrintLine(14, 3);
+ vehicle.cp.turnCorner = turnContext:createCorner(vehicle, turnInfo.turnRadius)
+ courseplay.generateTurnTypeHeadlandCornerReverseStraightTractor(vehicle, turnInfo)
end
- ----------------------------------------------------------
- --Set the driving direction
- ----------------------------------------------------------
- if curTurnTarget then
- local posX, posZ = curTurnTarget.revPosX or curTurnTarget.posX, curTurnTarget.revPosZ or curTurnTarget.posZ;
- local directionNode = vehicle.aiVehicleDirectionNode or vehicle.cp.directionNode;
- dtpX,_,dtpZ = worldToLocal(directionNode, posX, vehicleY, posZ);
- if courseplay:isWheelloader(vehicle) then
- dtpZ = dtpZ * 0.5; -- wheel loaders need to turn more
- end;
-
- lx, lz = AIVehicleUtil.getDriveDirection(vehicle.cp.directionNode, posX, vehicleY, posZ);
- if curTurnTarget.turnReverse then
- lx, lz, moveForwards = courseplay:goReverse(vehicle,lx,lz);
- end;
- end;
+ cpPrintLine(14, 1);
+ courseplay:debug(string.format("%s:(Turn) Generated %d Turn Waypoints", nameNum(vehicle), #vehicle.cp.turnTargets), 14);
+ cpPrintLine(14, 3);
end
function courseplay:generateTurnTypeWideTurn(vehicle, turnInfo)
@@ -1495,7 +1475,6 @@ function courseplay:addTurnTarget(vehicle, posX, posZ, turnEnd, turnReverse, rev
end
function courseplay:clearTurnTargets(vehicle)
- vehicle.cp.settings.turnStage:set(false)
vehicle.cp.turnTargets = {};
vehicle.cp.curTurnIndex = 1;
vehicle.cp.haveCheckedMarkersThisTurn = false;
diff --git a/vehicles.lua b/vehicles.lua
index 64688f811..4ac25c9cd 100644
--- a/vehicles.lua
+++ b/vehicles.lua
@@ -1656,11 +1656,14 @@ function AIDriverUtil.getLastAttachedImplement(vehicle)
return lastImplement, minDistance
end
-
function AIDriverUtil.hasAIImplementWithSpecialization(vehicle, specialization)
return AIDriverUtil.getAIImplementWithSpecialization(vehicle, specialization) ~= nil
end
+function AIDriverUtil.hasImplementWithSpecialization(vehicle, specialization)
+ return AIDriverUtil.getImplementWithSpecialization(vehicle, specialization) ~= nil
+end
+
function AIDriverUtil.getAIImplementWithSpecialization(vehicle, specialization)
local aiImplements = vehicle:getAttachedAIImplements()
return AIDriverUtil.getImplementWithSpecializationFromList(specialization, aiImplements)