forked from Courseplay/courseplay
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathFieldworkAIDriver.lua
573 lines (519 loc) · 21.7 KB
/
FieldworkAIDriver.lua
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
--[[
This file is part of Courseplay (https://github.com/Courseplay/courseplay)
Copyright (C) 2018 Peter Vajko
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 <http://www.gnu.org/licenses/>.
]]
--[[
Fieldwork AI Driver
Can follow a fieldworking course, perform turn maneuvers, turn on/off and raise/lower implements,
add adjustment course if needed.
]]
---@class FieldworkAIDriver : AIDriver
FieldworkAIDriver = CpObject(AIDriver)
FieldworkAIDriver.myStates = {
ON_FIELDWORK_COURSE = {},
ON_UNLOAD_OR_REFILL_COURSE = {},
UNLOAD_OR_REFILL_ON_FIELD = {},
WAITING_FOR_UNLOAD_OR_REFILL ={}, -- while on the field
ON_CONNECTING_TRACK = {},
WAITING_FOR_LOWER = {},
WAITING_FOR_RAISE = {}
}
-- Our class implementation does not call the constructor of base classes
-- through multiple level of inheritances therefore we must explicitly call
-- the base class ctr.
function FieldworkAIDriver:init(vehicle)
AIDriver.init(self, vehicle)
self:initStates(FieldworkAIDriver.myStates)
-- waiting for tools to turn on, unfold and lower
self.waitingForTools = true
-- FieldworkAIDriver and its derived classes set the self.speed in various locations in
-- the code and then getSpeed() will pass that on to the AIDriver.
self.speed = 0
self.debugChannel = 14
-- waypoint index on main (fieldwork) course where we aborted the work before going on
-- an unload/refill course
self.fieldworkAbortedAtWaypoint = 1
-- force stop for unload/refill, for example by a tractor, otherwise the same as stopping because full or empty
self.heldForUnloadRefill = false
self.heldForUnloadRefillTimestamp = 0
-- stop and raise implements while refilling/unloading on field
self.stopImplementsWhileUnloadOrRefillOnField = true
end
--- Start the course and turn on all implements when needed
function FieldworkAIDriver:start(ix)
self:debug('Starting in mode %d', self.mode)
-- stop at the last waypoint by default
self.vehicle.cp.stopAtEnd = true
self.turnIsDriving = false
self.temporaryCourse = nil
-- any offset imposed by the driver itself (tight turns, end of course, etc.), addtional to any
-- tool offsets
self.aiDriverOffsetX = 0
self.aiDriverOffsetZ = 0
self:setUpCourses()
self.waitingForTools = true
-- on which course are we starting?
-- the ix we receive here is the waypoint index in the fieldwork course and the unload/fill
-- course concatenated.
if ix > self.fieldworkCourse:getNumberOfWaypoints() then
-- beyond the first, fieldwork course: we are on the unload/refill part
self:changeToUnloadOrRefill()
self:startCourseWithAlignment(self.unloadRefillCourse, ix - self.fieldworkCourse:getNumberOfWaypoints())
else
-- we are on the fieldwork part
self:startFieldworkWithAlignment(ix)
end
end
function FieldworkAIDriver:startFieldworkWithAlignment(ix)
if self:startCourseWithAlignment(self.fieldworkCourse, ix) then
self.state = self.states.ON_FIELDWORK_COURSE
self.fieldworkState = self.states.TEMPORARY
else
self:changeToFieldwork()
end
end
function FieldworkAIDriver:stop(msgReference)
self:stopWork()
AIDriver.stop(self, msgReference)
end
function FieldworkAIDriver:drive(dt)
if self.state == self.states.ON_FIELDWORK_COURSE then
self:driveFieldwork()
elseif self.state == self.states.ON_UNLOAD_OR_REFILL_COURSE then
if self:driveUnloadOrRefill(dt) then
-- someone else is driving, no need to call AIDriver.drive()
return
end
end
self:setRidgeMarkers()
self:resetUnloadOrRefillHold()
AIDriver.drive(self, dt)
-- reset speed limit for the next loop
self.speed = math.huge
end
-- Hold for unload (or refill) for example a combine can be asked by a an unloading tractor
-- to stop and wait. Must be called in every loop to keep waiting because it will automatically be
-- reset and the vehicle restarted. This way the users don't explicitly need to call resumeAfterUnloadOrRefill()
function FieldworkAIDriver:holdForUnloadOrRefill()
self.heldForUnloadRefill = true
self.heldForUnloadRefillTimestamp = g_updateLoopIndex
end
function FieldworkAIDriver:resumeAfterUnloadOrRefill()
self.heldForUnloadRefill = false
end
function FieldworkAIDriver:resetUnloadOrRefillHold()
if g_updateLoopIndex > self.heldForUnloadRefillTimestamp + 10 then
self:resumeAfterUnloadOrRefill()
end
end
--- Doing the fieldwork (headlands or up/down rows, including the turns)
function FieldworkAIDriver:driveFieldwork()
self:updateFieldworkOffset()
if self.fieldworkState == self.states.WAITING_FOR_LOWER then
if self:areAllWorkToolsReady() then
self:debug('all tools ready, start working')
self.fieldworkState = self.states.WORKING
self:setSpeed(self:getFieldSpeed())
else
self:setSpeed(0)
end
elseif self.fieldworkState == self.states.WORKING then
self:setSpeed(self:getFieldSpeed())
self:manageConvoy()
if not self:allFillLevelsOk() or self.heldForUnloadRefill then
if self.unloadRefillCourse and not self.heldForUnloadRefill then
---@see courseplay#setAbortWorkWaypoint if that logic needs to be implemented
-- last wp may not be available shortly after a ppc initialization like after a turn
self.fieldworkAbortedAtWaypoint = self.ppc:getLastPassedWaypointIx() or self.ppc:getCurrentWaypointIx()
self.vehicle.cp.fieldworkAbortedAtWaypoint = self.fieldworkAbortedAtWaypoint
self:debug('at least one tool is empty/full, aborting work at waypoint %d.', self.fieldworkAbortedAtWaypoint or -1)
self:changeToUnloadOrRefill()
self:startCourseWithAlignment(self.unloadRefillCourse, 1 )
else
self:changeToFieldworkUnloadOrRefill()
end
end
elseif self.fieldworkState == self.states.UNLOAD_OR_REFILL_ON_FIELD then
self:driveFieldworkUnloadOrRefill()
elseif self.fieldworkState == self.states.TEMPORARY then
self:setSpeed(self:getFieldSpeed())
elseif self.fieldworkState == self.states.ON_CONNECTING_TRACK then
self:setSpeed(self:getFieldSpeed())
end
end
---@return boolean true if unload took over the driving
function FieldworkAIDriver:driveUnloadOrRefill()
-- Workaround for issues like #3064, most likely the raiseAIEvent() call in stopWork()
-- stops the engine when the player is not in the vehicle, so restart it here.
-- Need to ask Giants' help to figure out how to do this properly.
if not courseplay:getIsEngineReady(self.vehicle) then
self.vehicle:startMotor()
end
if self.temporaryCourse then
-- use the courseplay speed limit for fields
self:setSpeed(self.vehicle.cp.speeds.field)
else
-- just drive normally
self:setSpeed(self:getRecordedSpeed())
end
-- except when in reversing, then always use reverse speed
if self.ppc:isReversing() then
self:setSpeed(self.vehicle.cp.speeds.reverse or self.vehicle.cp.speeds.crawl)
end
return false
end
--- Grain tank full during fieldwork
function FieldworkAIDriver:changeToFieldworkUnloadOrRefill()
self.fieldworkState = self.states.UNLOAD_OR_REFILL_ON_FIELD
if self.stopImplementsWhileUnloadOrRefillOnField then
self.fieldWorkUnloadOrRefillState = self.states.WAITING_FOR_RAISE
else
self.fieldWorkUnloadOrRefillState = self.states.WAITING_FOR_UNLOAD_OR_REFILL
end
end
--- Stop for unload/refill while driving the fieldwork course
function FieldworkAIDriver:driveFieldworkUnloadOrRefill()
-- don't move while empty
self:setSpeed(0)
if self.fieldWorkUnloadOrRefillState == self.states.WAITING_FOR_RAISE then
-- wait until we stopped before raising the implements
if self:isStopped() then
self:debug('implements raised, stop')
self:stopWork()
self.fieldWorkUnloadOrRefillState = self.states.WAITING_FOR_UNLOAD_OR_REFILL
end
elseif self.fieldWorkUnloadOrRefillState == self.states.WAITING_FOR_UNLOAD_OR_REFILL then
if self:allFillLevelsOk() and not self.heldForUnloadRefill then
self:debug('unloaded/refilled, continue working')
-- not full/empty anymore, maybe because Refilling to a trailer, go back to work
self:clearInfoText()
self:changeToFieldwork()
end
end
end
function FieldworkAIDriver:changeToFieldwork()
self:debug('change to fieldwork')
self.state = self.states.ON_FIELDWORK_COURSE
self.fieldworkState = self.states.WAITING_FOR_LOWER
self:startWork()
end
function FieldworkAIDriver:changeToUnloadOrRefill()
self:stopWork()
self:foldImplements()
self.state = self.states.ON_UNLOAD_OR_REFILL_COURSE
self:debug('changing to unload/refill course (%d waypoints)', self.unloadRefillCourse:getNumberOfWaypoints())
end
function FieldworkAIDriver:onEndTemporaryCourse()
if self.state == self.states.ON_FIELDWORK_COURSE then
self:debug('starting fieldwork')
self.fieldworkState = self.states.WAITING_FOR_LOWER
self:startWork()
end
end
function FieldworkAIDriver:onEndCourse()
if self.state == self.states.ON_UNLOAD_OR_REFILL_COURSE then
-- unload/refill course ended, return to fieldwork
self:debug('AI driver in mode %d continue fieldwork at %d/%d waypoints', self:getMode(), self.fieldworkAbortedAtWaypoint, self.fieldworkCourse:getNumberOfWaypoints())
self:startFieldworkWithAlignment(self.vehicle.cp.fieldworkAbortedAtWaypoint or self.fieldworkAbortedAtWaypoint)
else
AIDriver.onEndCourse(self)
end
end
function FieldworkAIDriver:onWaypointPassed(ix)
if self.turnIsDriving then
self:debug('onWaypointPassed %d, ignored as turn is driving now', ix)
return
end
self:debug('onWaypointPassed %d', ix)
if self.state == self.states.ON_FIELDWORK_COURSE then
if self.fieldworkState == self.states.WORKING then
-- check for transition to connecting track
if self.course:isOnConnectingTrack(ix) then
-- reached a connecting track (done with the headland, move to the up/down row or vice versa),
-- raise all implements while moving
self:debug('on a connecting track now, raising implements.')
courseplay:raiseImplements(self.vehicle)
self.fieldworkState = self.states.ON_CONNECTING_TRACK
end
end
if self.fieldworkState ~= self.states.TEMPORARY and self.course:isOnConnectingTrack(ix) then
-- passed a connecting track waypoint
-- check transition from connecting track to the up/down rows
-- we are close to the end of the connecting track, transition back to the up/down rows with
-- an alignment course
local d, firstUpDownWpIx = self.course:getDistanceToFirstUpDownRowWaypoint(ix)
self:debug('up/down rows start in %s meters.', tostring(d))
if d < self.vehicle.cp.turnDiameter * 2 and firstUpDownWpIx then
self:debug('End connecting track, start working on up/down rows (waypoint %d) with alignment course if needed.', firstUpDownWpIx)
self:startFieldworkWithAlignment(firstUpDownWpIx)
end
end
end
end
function FieldworkAIDriver:onWaypointChange(ix)
self:debug('onWaypointChange %d', ix)
if self.state == self.states.ON_FIELDWORK_COURSE then
if self.fieldworkState == self.states.ON_CONNECTING_TRACK then
if not self.course:isOnConnectingTrack(ix) then
-- reached the end of the connecting track, back to work
self:debug('connecting track ended, back to work, first lowering implements.')
self:changeToFieldwork()
end
end
if self.fieldworkState == self.states.TEMPORARY then
-- band aid to make sure we have our implements lowered by the time we end the
-- temporary course
if ix == self.course:getNumberOfWaypoints() then
self:debug('temporary (alignment) course is about to end, start work')
self:startWork()
end
-- towards the end of the field course make sure the implement reaches the last waypoint
elseif ix > self.course:getNumberOfWaypoints() - 3 then
if self.vehicle.cp.aiFrontMarker then
self:debug('adding offset (%.1f front marker) to make sure we do not miss anything when the course ends', self.vehicle.cp.aiFrontMarker)
self.aiDriverOffsetZ = -self.vehicle.cp.aiFrontMarker
end
end
end
AIDriver.onWaypointChange(self, ix)
end
function FieldworkAIDriver:getFieldSpeed()
-- use the speed limit supplied by Giants for fieldwork
local speedLimit = self.vehicle:getSpeedLimit() or math.huge
return math.min(self.vehicle.cp.speeds.field, speedLimit)
end
--- Set the speed. The idea is that self.speed is reset at the beginning of every loop and
-- every function calls setSpeed() and the speed will be set to the minimum
-- speed set in this loop.
function FieldworkAIDriver:setSpeed(speed)
self.speed = math.min(self.speed, speed)
end
--- Pass on self.speed set elsewhere to the AIDriver.
function FieldworkAIDriver:getSpeed()
local speed = self.speed or 10
-- as long as other CP components mess with the cruise control we need to reset this, for example after
-- a turn
self.vehicle:setCruiseControlMaxSpeed(speed)
return speed
end
--- Start the actual work. Lower and turn on implements
function FieldworkAIDriver:startWork()
self:debug('Starting work: turn on and lower implements.')
-- send the event first and _then_ lower otherwise it sometimes does not turn it on
self.vehicle:raiseAIEvent("onAIStart", "onAIImplementStart")
self.vehicle:requestActionEventUpdate()
if not courseplay:getIsEngineReady(self.vehicle) then
self.vehicle:startMotor()
end
courseplay:lowerImplements(self.vehicle)
self:unfoldImplements()
end
--- Stop working. Raise and stop implements
function FieldworkAIDriver:stopWork()
self:debug('Ending work: turn off and raise implements.')
courseplay:raiseImplements(self.vehicle)
self.vehicle:raiseAIEvent("onAIEnd", "onAIImplementEnd")
self.vehicle:requestActionEventUpdate()
end
--- Check all worktools to see if we are ready
function FieldworkAIDriver:areAllWorkToolsReady()
if not self.vehicle.cp.workTools then return true end
local allToolsReady = true
for _, workTool in pairs(self.vehicle.cp.workTools) do
allToolsReady = self:isWorktoolReady(workTool) and allToolsReady
end
return allToolsReady
end
--- Check if need to refill/unload anything
function FieldworkAIDriver:allFillLevelsOk()
if not self.vehicle.cp.workTools then return false end
-- what here comes is basically what Giants' getFillLevelInformation() does but this returns the real fillType,
-- not the fillTypeToDisplay as this latter is different for each type of seed
local fillLevelInfo = {}
self:getAllFillLevels(self.vehicle, fillLevelInfo)
return self:areFillLevelsOk(fillLevelInfo)
end
function FieldworkAIDriver:getAllFillLevels(object, fillLevelInfo)
-- get own fill levels
if object.getFillUnits then
for _, fillUnit in pairs(object:getFillUnits()) do
local fillType = self:getFillTypeFromFillUnit(fillUnit)
local fillTypeName = g_fillTypeManager:getFillTypeNameByIndex(fillType)
self:debugSparse('%s: Fill levels: %s: %.1f/%.1f', object:getName(), fillTypeName, fillUnit.fillLevel, fillUnit.capacity)
if not fillLevelInfo[fillType] then fillLevelInfo[fillType] = {fillLevel=0, capacity=0} end
fillLevelInfo[fillType].fillLevel = fillLevelInfo[fillType].fillLevel + fillUnit.fillLevel
fillLevelInfo[fillType].capacity = fillLevelInfo[fillType].capacity + fillUnit.capacity
end
end
-- collect fill levels from all attached implements recursively
for _,impl in pairs(object:getAttachedImplements()) do
self:getAllFillLevels(impl.object, fillLevelInfo)
end
end
function FieldworkAIDriver:getFillTypeFromFillUnit(fillUnit)
local fillType = fillUnit.lastValidFillType or fillUnit.fillType
-- TODO: do we need to check more supported fill types? This will probably cover 99.9% of the cases
if fillType == FillType.UNKNOWN then
-- just get the first valid supported fill type
for ft, valid in pairs(fillUnit.supportedFillTypes) do
if valid then return ft end
end
else
return fillType
end
end
--- Check if worktool is ready for work
function FieldworkAIDriver:isWorktoolReady(workTool)
local _, _, isUnfolded = courseplay:isFolding(workTool)
-- TODO: move these to a generic helper?
local isTurnedOn = true
if workTool.spec_turnOnVehicle then
isTurnedOn = workTool:getAIRequiresTurnOn() and workTool:getIsTurnedOn()
end
local isLowered = courseplay:isLowered(workTool)
local isAiImplementReady = self.vehicle:getCanAIImplementContinueWork()
courseplay.debugVehicle(12, workTool, 'islowered=%s isAiReady=%s isturnedon=%s unfolded=%s',
isLowered, isAiImplementReady, isTurnedOn, isUnfolded)
return isLowered and isTurnedOn and isUnfolded
end
-- is the fill level ok to continue?
function FieldworkAIDriver:areFillLevelsOk()
-- implement specifics in the derived classes
return true
end
--- Set up the main (fieldwork) course and the unload/refill course and initial state
-- Currently, the legacy CP code just dumps all loaded courses to vehicle.Waypoints so
-- now we have to figure out which of that is the actual fieldwork course and which is the
-- refill/unload part.
-- This should better be handled by the course management though and should be refactored.
function FieldworkAIDriver:setUpCourses()
local nWaits = 0
local endFieldCourseIx = 0
for i, wp in ipairs(self.vehicle.Waypoints) do
if wp.wait then
nWaits = nWaits + 1
-- the second wp with the wait attribute is the end of the field course (assuming
-- the field course has been loaded first.
if nWaits == 2 then
endFieldCourseIx = i
break
end
end
end
if #self.vehicle.Waypoints > endFieldCourseIx and endFieldCourseIx ~= 0 then
self:debug('There seems to be an unload/refill course starting at waypoint %d', endFieldCourseIx + 1)
---@type Course
self.fieldworkCourse = Course(self.vehicle, self.vehicle.Waypoints, 1, endFieldCourseIx)
-- apply the current offset to the fieldwork part (lane+tool, where, confusingly, totalOffsetX contains the toolOffsetX)
self.fieldworkCourse:setOffset(self.vehicle.cp.totalOffsetX, self.vehicle.cp.toolOffsetZ)
---@type Course
self.unloadRefillCourse = Course(self.vehicle, self.vehicle.Waypoints, endFieldCourseIx + 1, #self.vehicle.Waypoints)
else
self:debug('There seems to be no unload/refill course')
self.fieldworkCourse = Course(self.vehicle, self.vehicle.Waypoints, 1, #self.vehicle.Waypoints)
end
end
function FieldworkAIDriver:setRidgeMarkers()
if not self.vehicle.cp.ridgeMarkersAutomatic then return end
local active = self.state == self.states.FIELDWORK and not self.turnIsDriving
for _, workTool in ipairs(self.vehicle.cp.workTools) do
if workTool.spec_ridgeMarker then
local state = active and self.course:getRidgeMarkerState(self.ppc:getCurrentWaypointIx()) or 0
if workTool.spec_ridgeMarker.ridgeMarkerState ~= state then
self:debug('Setting ridge markers to %d', state)
workTool:setRidgeMarkerState(state)
end
end
end
end
--- We already set the offsets on the course at start, this is to update those values
-- if the user changed them during the run or the AI driver wants to add an offset
function FieldworkAIDriver:updateFieldworkOffset()
-- pass this in through the PPC instead of setting it on the course directly as we don't know what
-- course it is running at the moment
self.ppc:setOffset(self.vehicle.cp.totalOffsetX + self.aiDriverOffsetX, self.vehicle.cp.toolOffsetZ + self.aiDriverOffsetZ)
end
function FieldworkAIDriver:hasSameCourse(otherVehicle)
if otherVehicle.cp.driver and otherVehicle.cp.driver.fieldworkCourse then
return self.fieldworkCourse:equals(otherVehicle.cp.driver.fieldworkCourse)
else
return false
end
end
--- When working in a group (convoy), do I have to hold so I don't get too close to the
-- other vehicles in front of me?
function FieldworkAIDriver:manageConvoy()
if not self.vehicle.cp.convoyActive then return false end
--get my position in convoy and look for the closest combine
local position = 1
local total = 1
local closestDistance = math.huge
for _, otherVehicle in pairs(CpManager.activeCoursePlayers) do
if otherVehicle ~= self.vehicle and otherVehicle.cp.convoyActive and self:hasSameCourse(otherVehicle) then
local myWpIndex = self.ppc:getCurrentWaypointIx()
local otherVehicleWpIndex = otherVehicle.cp.ppc:getCurrentWaypointIx()
total = total + 1
if myWpIndex < otherVehicleWpIndex then
position = position + 1
local distance = (otherVehicleWpIndex - myWpIndex) * courseGenerator.waypointDistance
if distance < closestDistance then
closestDistance = distance
end
end
end
end
--when I'm too close to the combine before me, then stop
if position > 1 then
if closestDistance < 100 then
self:debugSparse('too close (%.1f) to other vehicles in group, holding.', closestDistance)
self:setSpeed(0)
end
else
closestDistance = 0
end
-- TODO: check for change should be handled by setCpVar()
if self.vehicle.cp.convoy.distance ~= closestDistance then
self.vehicle:setCpVar('convoy.distance',closestDistance)
end
if self.vehicle.cp.convoy.number ~= position then
self.vehicle:setCpVar('convoy.number',position)
end
if self.vehicle.cp.convoy.members ~= total then
self.vehicle:setCpVar('convoy.members',total)
end
return hold
end
-- Although raising the AI start/stop events supposed to fold/unfold the implements, it does not always happen.
-- So use these to explicitly do so
function FieldworkAIDriver:unfoldImplements()
for _,workTool in pairs(self.vehicle.cp.workTools) do
if courseplay:isFoldable(workTool) then
local isFolding, isFolded, isUnfolded = courseplay:isFolding(workTool)
if not isUnfolded then
self:debug('Unfolding %s', workTool:getName())
workTool:setFoldDirection(workTool.cp.realUnfoldDirection)
end
end
end
end
function FieldworkAIDriver:foldImplements()
for _,workTool in pairs(self.vehicle.cp.workTools) do
if courseplay:isFoldable(workTool) then
local isFolding, isFolded, isUnfolded = courseplay:isFolding(workTool)
if not isFolded then
self:debug('Folding %s', workTool:getName())
workTool:setFoldDirection(-workTool.cp.realUnfoldDirection)
end
end
end
end