Skip to content
This repository was archived by the owner on May 23, 2023. It is now read-only.

Commit 7128841

Browse files
committed
6.03.00056 MP and baler fixes
- Fixes #7100 - Bale unload at triggers fixed (#6859) - Made the Anderson round balers work - Baler offset is now configurable in VehicleConfigurations.xml (see baleCollectorOffset description)
1 parent b3c088f commit 7128841

File tree

8 files changed

+117
-51
lines changed

8 files changed

+117
-51
lines changed

BaleCollectorAIDriver.lua

Lines changed: 46 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -149,19 +149,32 @@ end
149149
---@return BaleToCollect, number closest bale and its distance
150150
function BaleCollectorAIDriver:findClosestBale(bales)
151151
local closestBale, minDistance, ix = nil, math.huge
152+
local invalidBales = 0
152153
for i, bale in ipairs(bales) do
153-
local _, _, _, d = bale:getPositionInfoFromNode(AIDriverUtil.getDirectionNode(self.vehicle))
154-
self:debug('%d. bale (%d) in %.1f m', i, bale:getId(), d)
155-
if d < self.vehicle.cp.turnDiameter * 2 then
156-
-- if it is really close, check the length of the Dubins path
157-
-- as we may need to drive a loop first to get to it
158-
d = self:getDubinsPathLengthToBale(bale)
159-
self:debug(' Dubins length is %.1f m', d)
160-
end
161-
if d < minDistance then
162-
closestBale = bale
163-
minDistance = d
164-
ix = i
154+
if bale:isStillValid() then
155+
local _, _, _, d = bale:getPositionInfoFromNode(AIDriverUtil.getDirectionNode(self.vehicle))
156+
self:debug('%d. bale (%d, %s) in %.1f m', i, bale:getId(), bale:getBaleObject(), d)
157+
if d < self.vehicle.cp.turnDiameter * 2 then
158+
-- if it is really close, check the length of the Dubins path
159+
-- as we may need to drive a loop first to get to it
160+
d = self:getDubinsPathLengthToBale(bale)
161+
self:debug(' Dubins length is %.1f m', d)
162+
end
163+
if d < minDistance then
164+
closestBale = bale
165+
minDistance = d
166+
ix = i
167+
end
168+
else
169+
--- When a bale gets wrapped it changes its identity and the node becomes invalid. This can happen
170+
--- when we pick up (and wrap) a bale other than the target bale, for example because there's another bale
171+
--- in the grabber's way. That is now wrapped but our bale list does not know about it so let's rescan the field
172+
self:debug('%d. bale (%d, %s) INVALID', i, bale:getId(), bale:getBaleObject())
173+
invalidBales = invalidBales + 1
174+
self:debug('Found an invalid bales, rescanning field', invalidBales)
175+
self.bales = self:findBales(self.vehicle.cp.settings.baleCollectionField:get())
176+
-- return empty, next time this is called everything should be ok
177+
return
165178
end
166179
end
167180
return closestBale, minDistance, ix
@@ -209,11 +222,14 @@ function BaleCollectorAIDriver:startPathfindingToBale(bale)
209222
self:debug('Start pathfinding to next bale (%d), safe distance from bale %.1f, half vehicle width %.1f',
210223
bale:getId(), safeDistanceFromBale, halfVehicleWidth)
211224
local goal = self:getBaleTarget(bale)
212-
local offset = Vector(0, safeDistanceFromBale + halfVehicleWidth + 0.2)
225+
local configuredOffset = self:getConfiguredOffset()
226+
local offset = Vector(0, safeDistanceFromBale +
227+
(configuredOffset and configuredOffset or (halfVehicleWidth + 0.2)))
213228
goal:add(offset:rotate(goal.t))
214229
local done, path, goalNodeInvalid
215230
self.pathfinder, done, path, goalNodeInvalid =
216-
PathfinderUtil.startPathfindingFromVehicleToGoal(self.vehicle, goal, false, self.fieldId, {})
231+
PathfinderUtil.startPathfindingFromVehicleToGoal(self.vehicle, goal, false, self.fieldId,
232+
{}, self.lastBale and {self.lastBale} or {})
217233
if done then
218234
return self:onPathfindingDoneToNextBale(path, goalNodeInvalid)
219235
else
@@ -272,8 +288,11 @@ function BaleCollectorAIDriver:isObstacleAhead()
272288
return true
273289
end
274290
end
275-
-- then a more thorough check
276-
local leftOk, rightOk, straightOk = PathfinderUtil.checkForObstaclesAhead(self.vehicle, self.turnRadius)
291+
-- then a more thorough check, we want to ignore the last bale we worked on as that may lay around too close
292+
-- to the baler. This happens for example to the Andersen bale wrapper.
293+
self:debug('Check obstacles ahead, ignoring bale object %s', self.lastBale and self.lastBale or 'nil')
294+
local leftOk, rightOk, straightOk =
295+
PathfinderUtil.checkForObstaclesAhead(self.vehicle, self.turnRadius, self.lastBale and{self.lastBale})
277296
-- if at least one is ok, we are good to go.
278297
return not (leftOk or rightOk or straightOk)
279298
end
@@ -297,7 +316,7 @@ function BaleCollectorAIDriver:onLastWaypoint()
297316
self:debug('last waypoint on bale pickup reached, start collecting bales again')
298317
self:collectNextBale()
299318
elseif self.baleCollectingState == self.states.APPROACHING_BALE then
300-
self:debug('looks like somehow missed a bale, rescanning field')
319+
self:debug('looks like somehow we missed a bale, rescanning field')
301320
self.bales = self:findBales(self.vehicle.cp.settings.baleCollectionField:get())
302321
self:collectNextBale()
303322
elseif self.baleCollectingState == self.states.REVERSING_AFTER_PATHFINDER_FAILURE then
@@ -376,7 +395,8 @@ function BaleCollectorAIDriver:workOnBale()
376395
if self.baleWrapper then
377396
BaleWrapperAIDriver.handleBaleWrapper(self)
378397
if self.baleWrapper.spec_baleWrapper.baleWrapperState == BaleWrapper.STATE_NONE then
379-
self:debug('Bale wrapped, moving on to the next')
398+
self.lastBale = self.baleWrapper.spec_baleWrapper.lastDroppedBale
399+
self:debug('Bale wrapped, moving on to the next, last dropped bale %s', self.lastBale)
380400
self:collectNextBale()
381401
end
382402
end
@@ -386,6 +406,14 @@ function BaleCollectorAIDriver:calculateTightTurnOffset()
386406
self.tightTurnOffset = 0
387407
end
388408

409+
function BaleCollectorAIDriver:getConfiguredOffset()
410+
if self.baleLoader then
411+
return g_vehicleConfigurations:get(self.baleLoader, 'baleCollectorOffset')
412+
elseif self.baleWrapper then
413+
return g_vehicleConfigurations:get(self.baleWrapper, 'baleCollectorOffset')
414+
end
415+
end
416+
389417
function BaleCollectorAIDriver:getFillLevel()
390418
local fillLevelInfo = {}
391419
self:getAllFillLevels(self.vehicle, fillLevelInfo)

BaleLoaderAIDriver.lua

Lines changed: 11 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -115,10 +115,9 @@ function BaleLoaderAIDriver:driveUnloadOrRefill(dt)
115115
self:debug('Approaching unload point.')
116116

117117
elseif self:haveBales() and self.unloadRefillState == self.states.APPROACHING_UNLOAD_POINT then
118-
local unloadNode = self:getUnloadNode(nearUnloadPoint, unloadPointIx)
119-
dz = calcDistanceFrom(unloadNode, self.baleLoader.cp.realUnloadOrFillNode)
120-
self:debugSparse('distance to unload point: %.1f', dz)
121-
if math.abs(dz) < 1 or self:tooCloseToOtherBales() then
118+
local d = self:getDistanceFromUnloadNode(nearUnloadPoint, unloadPointIx)
119+
self:debugSparse('distance to unload point: %.1f', d)
120+
if math.abs(d) < 1 or self:tooCloseToOtherBales() then
122121
self:debug('Unload point reached.')
123122
self.unloadRefillState = self.states.UNLOADING
124123
end
@@ -203,13 +202,18 @@ function BaleLoaderAIDriver:getFillType()
203202
end
204203

205204
--- Unload node is either an unload waypoint or an unload trigger
206-
function BaleLoaderAIDriver:getUnloadNode(isUnloadpoint, unloadPointIx)
205+
function BaleLoaderAIDriver:getDistanceFromUnloadNode(isUnloadpoint, unloadPointIx)
207206
if isUnloadpoint then
208207
self:debugSparse('manual unload point at ix = %d', unloadPointIx)
209208
self.manualUnloadNode:setToWaypoint(self.course, unloadPointIx)
210-
return self.manualUnloadNode.node
209+
-- don't use dz here as the course to the manual unload point as it is often on a reverse
210+
-- section and other parts of the course may be very close to the unload point, triggering
211+
-- this way too early
212+
return calcDistanceFrom(self.manualUnloadNode.node, self.baleLoader.cp.realUnloadOrFillNode)
211213
else
212-
return self.vehicle.cp.currentTipTrigger.triggerId
214+
local _, _, d = localToLocal(self.vehicle.cp.currentTipTrigger.triggerId,
215+
self.baleLoader.cp.realUnloadOrFillNode, 0, 0, 0)
216+
return d
213217
end
214218
end
215219

BaleToCollect.lua

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,10 @@ function BaleToCollect.isValidBale(object, baleWrapper)
5656
end
5757
end
5858

59+
function BaleToCollect:isStillValid()
60+
return BaleToCollect.isValidBale(self.bale)
61+
end
62+
5963
function BaleToCollect:isLoaded()
6064
return self.bale.mountObject
6165
end
@@ -68,6 +72,14 @@ function BaleToCollect:getId()
6872
return self.bale.id
6973
end
7074

75+
function BaleToCollect:getBaleObjectId()
76+
return NetworkUtil.getObjectId(self.bale)
77+
end
78+
79+
function BaleToCollect:getBaleObject()
80+
return self.bale
81+
end
82+
7183
function BaleToCollect:getPosition()
7284
return getWorldTranslation(self.bale.nodeId)
7385
end

DevHelper.lua

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -108,7 +108,7 @@ function DevHelper:overlapBoxCallback(transformId)
108108
text = 'vehicle' .. collidingObject:getName()
109109
else
110110
if collidingObject:isa(Bale) then
111-
text = 'Bale'
111+
text = 'Bale ' .. tostring(collidingObject) .. ' ' .. tostring(NetworkUtil.getObjectId(collidingObject))
112112
else
113113
text = collidingObject.getName and collidingObject:getName() or 'N/A'
114114
end
@@ -202,7 +202,7 @@ function DevHelper:startPathfinding()
202202
local start = State3D:copy(self.start)
203203

204204
self.pathfinder, done, path = PathfinderUtil.startPathfindingFromVehicleToGoal(self.vehicle, self.goal,
205-
false, self.fieldNumForPathfinding or 0, {}, 10)
205+
false, self.fieldNumForPathfinding or 0, {}, {}, 10)
206206

207207
end
208208

VehicleConfigurations.lua

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -34,9 +34,10 @@ VehicleConfigurations.attributes = {
3434
{name = 'turnRadius', getXmlFunction = getXMLFloat},
3535
{name = 'workingWidth', getXmlFunction = getXMLFloat},
3636
{name = 'balerUnloadDistance', getXmlFunction = getXMLFloat},
37-
{name = 'directionNodeToOffsetZ', getXmlFunction = getXMLFloat},
37+
{name = 'directionNodeOffsetZ', getXmlFunction = getXMLFloat},
3838
{name = 'implementWheelAlwaysOnGround', getXmlFunction = getXMLBool},
39-
{name = 'ignoreCollisionBoxesWhenFolded', getXmlFunction = getXMLBool}
39+
{name = 'ignoreCollisionBoxesWhenFolded', getXmlFunction = getXMLBool},
40+
{name = 'baleCollectorOffset', getXmlFunction = getXMLFloat},
4041
}
4142

4243
function VehicleConfigurations:init()

config/VehicleConfigurations.xml

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,11 @@ You can define the following custom settings:
6161
For this scenario the collision box is useless when folded, so when ignoreCollisionBoxesOnStreet is true,
6262
Courseplay will not detect collisions for this vehicle when it is folded.
6363
64+
- baleCollectorOffset: number
65+
Offset in meters to use in bale collector mode (Mode 7). This is the distance between the tractor's centerline
66+
and the edge of the bale when the bale grabber is right where it should be to pick up the bale.
67+
Courseplay will adjust this offset according to the bale's dimensions but you may want to add a little buffer.
68+
6469
-->
6570
<VehicleConfigurations>
6671
<!--[GIANTS]-->
@@ -170,13 +175,20 @@ You can define the following custom settings:
170175
<!--Harvester-->
171176

172177
<!--Implements-->
178+
<!--Anderson-->
179+
<Vehicle name="RB580.xml"
180+
baleCollectorOffset="1.4"
181+
/>
173182
<!--Kverneland-->
174183
<Vehicle name="iXterB18.xml"
175184
ignoreCollisionBoxesWhenFolded="true"
176185
/>
177186
<Vehicle name="iXtrackT4.xml"
178187
ignoreCollisionBoxesWhenFolded="true"
179188
/>
189+
<Vehicle name="wrapper7850C.xml"
190+
baleCollectorOffset="1.5"
191+
/>
180192
<!--Bourgault-->
181193
<Vehicle name="series3320.xml"
182194
noReverse="true"
@@ -216,6 +228,7 @@ You can define the following custom settings:
216228
<Vehicle name="z586.xml"
217229
toolOffsetX="-2.5"
218230
noReverse="true"
231+
baleCollectorOffset="1.3"
219232
/>
220233
<!--Tractors and Others-->
221234

course-generator/PathfinderUtil.lua

Lines changed: 29 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -203,11 +203,12 @@ end
203203
--- Pathfinder context
204204
---@class PathfinderUtil.Context
205205
PathfinderUtil.Context = CpObject()
206-
function PathfinderUtil.Context:init(vehicle, vehiclesToIgnore)
206+
function PathfinderUtil.Context:init(vehicle, vehiclesToIgnore, objectsToIgnore)
207207
self.vehicleData = PathfinderUtil.VehicleData(vehicle, true, 0.5)
208208
self.trailerHitchLength = AIDriverUtil.getTowBarLength(vehicle)
209209
self.turnRadius = vehicle.cp and vehicle.cp.driver and AIDriverUtil.getTurningRadius(vehicle) or 10
210-
self.vehiclesToIgnore = vehiclesToIgnore
210+
self.vehiclesToIgnore = vehiclesToIgnore or {}
211+
self.objectsToIgnore = objectsToIgnore or {}
211212
end
212213

213214
--- Calculate the four corners of a rectangle around a node (for example the area covered by a vehicle)
@@ -276,6 +277,10 @@ end
276277

277278
function PathfinderUtil.CollisionDetector:overlapBoxCallback(transformId)
278279
local collidingObject = g_currentMission.nodeToObject[transformId]
280+
if collidingObject and PathfinderUtil.elementOf(self.objectsToIgnore, collidingObject) then
281+
-- an object we want to ignore
282+
return
283+
end
279284
if collidingObject and collidingObject.getRootVehicle then
280285
local rootVehicle = collidingObject:getRootVehicle()
281286
if rootVehicle == self.vehicleData.rootVehicle or
@@ -292,13 +297,14 @@ function PathfinderUtil.CollisionDetector:overlapBoxCallback(transformId)
292297
text = text .. ' ' .. key
293298
end
294299
end
295-
self.collidingShapesText = text
300+
self.collidingShapesText = text
296301
self.collidingShapes = self.collidingShapes + 1
297302
end
298303
end
299304

300-
function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, vehiclesToIgnore, log)
305+
function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData, vehiclesToIgnore, objectsToIgnore, log)
301306
self.vehiclesToIgnore = vehiclesToIgnore or {}
307+
self.objectsToIgnore = objectsToIgnore or {}
302308
self.vehicleData = vehicleData
303309
-- the box for overlapBox() is symmetric, so if our root node is not in the middle of the vehicle rectangle,
304310
-- we have to translate it into the middle
@@ -311,8 +317,8 @@ function PathfinderUtil.CollisionDetector:findCollidingShapes(node, vehicleData,
311317
local xRot, yRot, zRot = getWorldRotation(node)
312318
local x, y, z = localToWorld(node, xOffset, 1, zOffset)
313319

314-
self.collidingShapes = 0
315-
self.collidingShapesText = 'unknown'
320+
self.collidingShapes = 0
321+
self.collidingShapesText = 'unknown'
316322

317323
overlapBox(x, y + 0.2, z, xRot, yRot, zRot, width, 1, length, 'overlapBoxCallback', self, bitOR(AIVehicleUtil.COLLISION_MASK, 2), true, true, true)
318324
if log and self.collidingShapes > 0 then
@@ -488,17 +494,18 @@ function PathfinderConstraints:isValidNode(node, log, ignoreTrailer)
488494
local myCollisionData = PathfinderUtil.getBoundingBoxInWorldCoordinates(PathfinderUtil.helperNode, self.context.vehicleData, 'me')
489495
-- for debug purposes only, store validity info on node
490496
node.collidingShapes = PathfinderUtil.collisionDetector:findCollidingShapes(
491-
PathfinderUtil.helperNode, self.context.vehicleData, self.context.vehiclesToIgnore, log)
497+
PathfinderUtil.helperNode, self.context.vehicleData, self.context.vehiclesToIgnore, self.context.objectsToIgnore, log)
492498
if self.context.vehicleData.trailer and not ignoreTrailer then
493499
-- now check the trailer or towed implement
494500
-- move the node to the rear of the vehicle (where approximately the trailer is attached)
495-
local x, y, z = localToWorld(PathfinderUtil.helperNode, 0, 0, self.context.vehicleData.trailerHitchOffset)
501+
local x, y, z = localToWorld(PathfinderUtil.helperNode, 0, 0, self.context.vehicleData.trailerHitchOffset)
496502

497-
PathfinderUtil.setWorldPositionAndRotationOnTerrain(PathfinderUtil.helperNode, x, z,
498-
courseGenerator.toCpAngle(node.tTrailer), 0.5)
503+
PathfinderUtil.setWorldPositionAndRotationOnTerrain(PathfinderUtil.helperNode, x, z,
504+
courseGenerator.toCpAngle(node.tTrailer), 0.5)
499505

500506
node.collidingShapes = node.collidingShapes + PathfinderUtil.collisionDetector:findCollidingShapes(
501-
PathfinderUtil.helperNode, self.context.vehicleData.trailerRectangle, self.context.vehiclesToIgnore, log)
507+
PathfinderUtil.helperNode, self.context.vehicleData.trailerRectangle, self.context.vehiclesToIgnore,
508+
self.context.objectsToIgnore, log)
502509
end
503510
local isValid = node.collidingShapes == 0
504511
if not isValid then
@@ -542,17 +549,18 @@ end
542549
---@param goal State3D
543550
function PathfinderUtil.startPathfindingFromVehicleToGoal(vehicle, goal,
544551
allowReverse, fieldNum,
545-
vehiclesToIgnore, maxFruitPercent, offFieldPenalty, mustBeAccurate)
552+
vehiclesToIgnore, objectsToIgnore,
553+
maxFruitPercent, offFieldPenalty, mustBeAccurate)
546554

547-
local start = PathfinderUtil.getVehiclePositionAsState3D(vehicle)
555+
local start = PathfinderUtil.getVehiclePositionAsState3D(vehicle)
548556

549-
local vehicleData = PathfinderUtil.VehicleData(vehicle, true, 0.5)
557+
local vehicleData = PathfinderUtil.VehicleData(vehicle, true, 0.5)
550558

551-
PathfinderUtil.initializeTrailerHeading(start, vehicleData)
559+
PathfinderUtil.initializeTrailerHeading(start, vehicleData)
552560

553-
local context = PathfinderUtil.Context(vehicle, vehiclesToIgnore)
561+
local context = PathfinderUtil.Context(vehicle, vehiclesToIgnore, objectsToIgnore)
554562

555-
local constraints = PathfinderConstraints(context,
563+
local constraints = PathfinderConstraints(context,
556564
maxFruitPercent or (vehicle.cp.settings.useRealisticDriving:is(true) and 50 or math.huge),
557565
offFieldPenalty or PathfinderUtil.defaultOffFieldPenalty,
558566
fieldNum)
@@ -709,7 +717,7 @@ function PathfinderUtil.startPathfindingFromVehicleToWaypoint(vehicle, goalWaypo
709717
local offset = Vector(zOffset, -xOffset)
710718
goal:add(offset:rotate(goal.t))
711719
return PathfinderUtil.startPathfindingFromVehicleToGoal(
712-
vehicle, goal, allowReverse, fieldNum, vehiclesToIgnore, maxFruitPercent, offFieldPenalty)
720+
vehicle, goal, allowReverse, fieldNum, vehiclesToIgnore, {}, maxFruitPercent, offFieldPenalty)
713721
end
714722
------------------------------------------------------------------------------------------------------------------------
715723
--- Interface function to start the pathfinder in the game. The goal is a point at sideOffset meters from the goal node
@@ -733,7 +741,7 @@ function PathfinderUtil.startPathfindingFromVehicleToNode(vehicle, goalNode,
733741
local goal = State3D(x, -z, courseGenerator.fromCpAngle(yRot))
734742
return PathfinderUtil.startPathfindingFromVehicleToGoal(
735743
vehicle, goal, allowReverse, fieldNum,
736-
vehiclesToIgnore, maxFruitPercent, offFieldPenalty, mustBeAccurate)
744+
vehiclesToIgnore, {}, maxFruitPercent, offFieldPenalty, mustBeAccurate)
737745
end
738746

739747
------------------------------------------------------------------------------------------------------------------------
@@ -778,7 +786,7 @@ end
778786
-- Then check all three for collisions with obstacles.
779787
---@return boolean, boolean, boolean true if no obstacles left, right, straight ahead
780788
------------------------------------------------------------------------------------------------------------------------
781-
function PathfinderUtil.checkForObstaclesAhead(vehicle, turnRadius)
789+
function PathfinderUtil.checkForObstaclesAhead(vehicle, turnRadius, objectsToIgnore)
782790

783791
local function isValidPath(constraints, path)
784792
for i, node in ipairs(path) do
@@ -806,7 +814,7 @@ function PathfinderUtil.checkForObstaclesAhead(vehicle, turnRadius)
806814
local start = PathfinderUtil.getVehiclePositionAsState3D(vehicle)
807815
local vehicleData = PathfinderUtil.VehicleData(vehicle, true, 0.5)
808816
PathfinderUtil.initializeTrailerHeading(start, vehicleData)
809-
local context = PathfinderUtil.Context(vehicle, {})
817+
local context = PathfinderUtil.Context(vehicle, {}, objectsToIgnore)
810818
local constraints = PathfinderConstraints(context, math.huge, 0, 0)
811819
ensureHelperNode()
812820

modDesc.xml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
<?xml version="1.0" encoding="utf-8" standalone="no" ?>
22
<modDesc descVersion="53">
3-
<version>6.03.00055</version>
3+
<version>6.03.00056</version>
44
<author><![CDATA[Courseplay.devTeam]]></author>
55
<title><!-- en=English de=German fr=French es=Spanish ru=Russian pl=Polish it=Italian br=Brazilian-Portuguese cs=Chinese(Simplified) ct=Chinese(Traditional) cz=Czech nl=Netherlands hu=Hungary jp=Japanese kr=Korean pt=Portuguese ro=Romanian tr=Turkish -->
66
<en>CoursePlay SIX</en>

0 commit comments

Comments
 (0)