This repository has been archived by the owner on May 23, 2023. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 525
/
base.lua
1208 lines (1010 loc) · 41.1 KB
/
base.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
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
local floor = math.floor;
function courseplay.prerequisitesPresent(specializations)
return true;
end
function courseplay:onLoad(savegame)
local xmlFile = self.xmlFile;
self.setCourseplayFunc = courseplay.setCourseplayFunc;
self.getIsCourseplayDriving = courseplay.getIsCourseplayDriving;
self.setIsCourseplayDriving = courseplay.setIsCourseplayDriving;
-- TODO: this is the worst programming practice ever. Defined as courseplay:setCpVar() but then self refers to the
-- vehicle, this is the ugliest hack I've ever seen.
self.setCpVar = courseplay.setCpVar;
--SEARCH AND SET self.name IF NOT EXISTING
if self.name == nil then
self.name = courseplay:getObjectName(self, xmlFile);
end;
if self.cp == nil then self.cp = {}; end;
-- TODO: some mods won't install properly as vehicle types and thus the courseplay event listeners are not
-- installed for those. In that case, they'll have the Courseplay spec (as checked with hasSpecialization()) but
-- onLoad is not called so they do not have a full CP setup, so as of now, we need this to verify if courseplay
-- was correctly installed in this vehicle
self.hasCourseplaySpec = true;
self.cp.varMemory = {};
-- XML FILE NAME VARIABLE
if self.cp.xmlFileName == nil then
self.cp.xmlFileName = courseplay.utils:getFileNameFromPath(self.configFileName);
end;
courseplay:setNameVariable(self);
self.cp.isCombine = courseplay:isCombine(self);
self.cp.isChopper = courseplay:isChopper(self);
self.cp.speedDebugLine = "no speed info"
self.cp.isDriving = false;
self.cp.waypointIndex = 1;
self.cp.previousWaypointIndex = 1;
self.cp.recordingTimer = 1
self.timer = 0.00
self.cp.timers = {};
self.cp.driveSlowTimer = 0;
-- RECORDING
self.cp.isRecording = false;
self.cp.recordingIsPaused = false;
self.cp.isRecordingTurnManeuver = false;
self.cp.drivingDirReverse = false;
self.cp.waitPoints = {};
self.cp.numWaitPoints = 0;
self.cp.unloadPoints = {};
self.cp.numUnloadPoints = 0;
self.cp.crossingPoints = {};
self.cp.numCrossingPoints = 0;
self.Waypoints = {}
self.cp.canDrive = false --can drive course (has >4 waypoints, is not recording)
self.cp.coursePlayerNum = nil;
self.cp.infoText = nil; -- info text in tractor
self.cp.toolTip = nil;
--- Adds the vehicle to the global info texts handler
g_globalInfoTextHandler:addVehicle(self)
--courseplay:setNextPrevModeVars(self);
-- for modes 4 and 6, this the index of the waypoint where the work begins
self.cp.startWork = nil
-- for modes 4 and 6, this the index of the waypoint where the work ends
self.cp.stopWork = nil
self.cp.canSwitchMode = false;
self.cp.slippingStage = 0;
self.cp.saveFuel = false;
self.cp.hasAugerWagon = false;
-- Visual i3D waypoint signs
self.cp.signs = {
crossing = {};
current = {};
};
self.cp.numCourses = 1;
self.cp.numWaypoints = 0;
self.cp.currentCourseName = nil;
self.cp.currentCourseId = 0;
self.cp.lastMergedWP = 0;
self.cp.loadedCourses = {}
self.cp.course = {} -- as discussed with Peter, this could be the container for all waypoint stuff in one table
-- forced waypoints
self.cp.curTarget = {};
self.cp.curTargetMode7 = {};
self.cp.nextTargets = {};
self.cp.turnTargets = {};
self.cp.curTurnIndex = 1;
-- speed limits
self.cp.speeds = {
reverse = 6;
turn = 10;
field = 24;
street = self:getCruiseControlMaxSpeed() or 50;
crawl = 3;
discharge = 8;
bunkerSilo = 20;
approach = 10;
minReverse = 3;
minTurn = 3;
minField = 3;
minStreet = 3;
max = self:getCruiseControlMaxSpeed() or 60;
};
-- data basis for the Course list
self.cp.reloadCourseItems = true
self.cp.sorted = {item={}, info={}}
self.cp.folder_settings = {}
courseplay.settings.update_folders(self)
-- DIRECTION NODE SETUP
local DirectionNode;
if self.getAIVehicleDirectionNode ~= nil then -- Check if function exist before trying to use it
if self.cp.componentNumAsDirectionNode then
-- If we have specified a component node as the derection node in the special tools section, then use it.
DirectionNode = self.components[self.cp.componentNumAsDirectionNode].node;
else
DirectionNode = self:getAIVehicleDirectionNode();
end;
else
-- TODO: (Claus) Check Wheel Loaders Direction node a bit later.
--if courseplay:isWheelloader(self)then
-- if self.spec_articulatedAxis and self.spec_articulatedAxis.rotMin then
-- local nodeIndex = Utils.getNoNil(self.cp.componentNumAsDirectionNode, 2)
-- if self.components[nodeIndex] ~= nil then
-- DirectionNode = self.components[nodeIndex].node;
-- end
-- end;
--end
end;
-- If we cant get any valid direction node, then use the rootNode
if DirectionNode == nil then
DirectionNode = self.rootNode;
end
local directionNodeOffset, isTruck = courseplay:getVehicleDirectionNodeOffset(self, DirectionNode);
if directionNodeOffset ~= 0 then
DirectionNode = courseplay:createNewLinkedNode(self, "realDirectionNode", DirectionNode);
setTranslation(DirectionNode, 0, 0, directionNodeOffset);
end;
self.cp.directionNode = DirectionNode;
-- REVERSE DRIVING SETUP
if SpecializationUtil.hasSpecialization(ReverseDriving, self.specializations) then
self.cp.reverseDrivingDirectionNode = courseplay:createNewLinkedNode(self, "realReverseDrivingDirectionNode", self.cp.directionNode);
setRotation(self.cp.reverseDrivingDirectionNode, 0, math.rad(180), 0);
end;
-- TRIGGERS
self.findTipTriggerCallback = courseplay.findTipTriggerCallback;
self.findSpecialTriggerCallback = courseplay.findSpecialTriggerCallback;
self.findFuelTriggerCallback = courseplay.findFuelTriggerCallback;
self.cp.hasRunRaycastThisLoop = {};
self.findVehicleHeights = courseplay.findVehicleHeights;
if self.maxRotation then
self.cp.steeringAngle = math.deg(self.maxRotation);
else
self.cp.steeringAngle = 30;
end
courseplay.debugVehicle( courseplay.DBG_COURSES, self, 'steering angle is %.1f', self.cp.steeringAngle)
if isTruck then
self.cp.revSteeringAngle = self.cp.steeringAngle * 0.25;
end;
if self.cp.steeringAngleCorrection then
self.cp.steeringAngle = Utils.getNoNil(self.cp.steeringAngleCorrection, self.cp.steeringAngle);
elseif self.cp.steeringAngleMultiplier then
self.cp.steeringAngle = self.cp.steeringAngle * self.cp.steeringAngleMultiplier;
end;
-- traffic collision
self.cpTrafficCollisionIgnoreList = {};
if self.trafficCollisionIgnoreList == nil then
self.trafficCollisionIgnoreList = {}
end
--aiTrafficCollisionTrigger
self.aiTrafficCollisionTrigger = nil
local ret_findAiCollisionTrigger = false
ret_findAiCollisionTrigger = courseplay:findAiCollisionTrigger(self)
-- create LegacyCollisionTriggers on load game ? -> vehicles not running CP are getting the collision snake
if not CpManager.trafficCollisionIgnoreList[g_currentMission.terrainRootNode] then
CpManager.trafficCollisionIgnoreList[g_currentMission.terrainRootNode] = true;
end;
courseplay:setOwnFillLevelsAndCapacities(self)
-- workTools
self.cp.workTools = {};
self.cp.numWorkTools = 0;
self.cp.workToolAttached = false;
self.cp.totalFillLevel = nil;
self.cp.totalCapacity = nil;
self.cp.totalFillLevelPercent = 0;
self.cp.tipRefOffset = 0;
self.cp.vehicleTurnRadius = courseplay:getVehicleTurnRadius(self);
--Offset
self.cp.totalOffsetX = 0;
self.cp.skipOffsetX = false;
--Copy course
self.cp.hasFoundCopyDriver = false;
self.cp.copyCourseFromDriver = nil;
self.cp.selectedDriverNumber = 0;
--Course generation
self.cp.hasGeneratedCourse = false;
self.cp.mouseCursorActive = false;
-- 2D pda map background -- TODO: MP?
if g_currentMission.hud.ingameMap and g_currentMission.hud.ingameMap.mapOverlay and g_currentMission.hud.ingameMap.mapOverlay.filename then
self.cp.course2dPdaMapOverlay = Overlay:new(g_currentMission.hud.ingameMap.mapOverlay.filename, 0, 0, 1, 1);
self.cp.course2dPdaMapOverlay:setColor(1, 1, 1, CpManager.course2dPdaMapOpacity);
end;
---@type SettingsContainer
self.cp.settings = SettingsContainer.createVehicleSpecificSettings(self)
---@type CourseGeneratorSettingsContainer
self.cp.courseGeneratorSettings = SettingsContainer.createCourseGeneratorSettings(self)
-- HUD
courseplay.hud:setupVehicleHud(self);
courseplay:validateCanSwitchMode(self);
courseplay.signs:updateWaypointSigns(self);
self.cp.settings.driverMode:setAIDriver()
end;
function courseplay:onPostLoad(savegame)
if savegame ~= nil and savegame.key ~= nil and not savegame.resetVehicles then
courseplay.loadVehicleCPSettings(self, savegame.xmlFile, savegame.key, savegame.resetVehicles)
end
end;
function courseplay:onLeaveVehicle()
if self.cp.mouseCursorActive then
courseplay:setMouseCursor(self, false);
courseEditor:reset()
end
--hide visual i3D waypoint signs when not in vehicle
courseplay.signs:setSignsVisibility(self, true);
end
function courseplay:onEnterVehicle()
--if the vehicle is attached to another vehicle, disable cp
if not courseplay.isEnabled(self) then
return
end
courseEditor:reset()
if self.cp.mouseCursorActive then
courseplay:setMouseCursor(self, true);
end;
--show visual i3D waypoint signs only when in vehicle
courseplay.signs:setSignsVisibility(self);
end
function courseplay:onDraw()
--if the vehicle is attached to another vehicle, disable cp
if not courseplay.isEnabled(self) then
return
end
courseEditor:draw(self, self.cp.directionNode)
courseplay:showAIMarkers(self)
courseplay:showTemporaryMarkers(self)
if self.cp.driver then
self.cp.driver.triggerHandler:onDraw()
self.cp.driver.triggerSensor:onDraw()
end
local isDriving = self:getIsCourseplayDriving();
--WORKWIDTH DISPLAY
if self.cp.settings.driverMode:get() ~= courseplay.MODE_BALE_COLLECTOR and self.cp.timers.showWorkWidth and self.cp.timers.showWorkWidth > 0 then
if courseplay:timerIsThrough(self, 'showWorkWidth') then -- stop showing, reset timer
courseplay:resetCustomTimer(self, 'showWorkWidth');
else -- timer running, show
courseplay:showWorkWidth(self);
end;
end;
--DEBUG SHOW DIRECTIONNODE
if courseplay.debugChannels[courseplay.DBG_PPC] then
-- For debugging when setting the directionNodeZOffset. (Visual points shown for old node)
local nx,ny,nz = getWorldTranslation(self.cp.directionNode);
cpDebug:drawPoint(nx, ny+4, nz, 0.6196, 0.3490 , 0);
end;
if self.cp.driver then
self.cp.driver:onDraw()
end
local errorHandler = function(err)
printCallstack()
courseplay.infoVehicle(vehicle, "Exception, error in draw hud: %s", tostring(err))
return err
end
local status, result = xpcall(courseplay.renderHud, errorHandler, self,isDriving)
if not status then
self.cp.hudBroken = true
end
if self.cp.settings.courseDrawMode:isCourseMapVisible() then
courseplay:drawCourse2D(self, false);
end;
end; --END draw()
function courseplay:renderHud(isDriving)
if self:getIsActive() and not self.cp.hudBroken then
if self.cp.hud.show then
courseplay.hud:setContent(self);
courseplay.hud:renderHud(self);
courseplay.hud:renderHudBottomInfo(self);
if self.cp.distanceCheck and (isDriving or (not self.cp.canDrive and not self.cp.isRecording and not self.cp.recordingIsPaused)) then -- turn off findFirstWaypoint when driving or no course loaded
courseplay:toggleFindFirstWaypoint(self);
end;
if self.cp.mouseCursorActive then
g_inputBinding:setShowMouseCursor(self.cp.mouseCursorActive);
end;
elseif courseplay.globalSettings.showMiniHud:is(true) then
courseplay.hud:setContent(self);
courseplay.hud:renderHudBottomInfo(self);
end;
if self.cp.distanceCheck and self.cp.numWaypoints > 1 then
courseplay:distanceCheck(self);
elseif self.cp.infoText ~= nil and StringUtil.startsWith(self.cp.infoText, 'COURSEPLAY_DISTANCE') then
self.cp.infoText = nil
end;
if self:getIsEntered() and self.cp.toolTip ~= nil then
courseplay:renderToolTip(self);
end;
end;
--RENDER
courseplay:renderInfoText(self);
end
function courseplay:showWorkWidth(vehicle)
local offsX, offsZ = vehicle.cp.settings.toolOffsetX:get() or 0, vehicle.cp.settings.toolOffsetZ:get() or 0
local workWidth = vehicle.cp.courseGeneratorSettings.workWidth:get()
WorkWidthUtil.showWorkWidth(vehicle,workWidth,offsX,offsZ)
end
function courseplay:onUpdate(dt)
--if the vehicle is attached to another vehicle, disable cp
if not courseplay.isEnabled(self) then
return
end
if self.cp.infoText ~= nil then
self.cp.infoText = nil
end
if self.cp.postInitDone == nil then
if self.cp.driver then
---Post init function, as not all giants variables are
---set correctly at the first courseplay:setAIDriver() call.
self.cp.driver:postInit()
end
--- The saved value gets applied, before all implements are attached.
--- So only allow automatic changes after all implements are attached on savegame start.
self.cp.courseGeneratorSettings.workWidth:postInit()
self.cp.settings.driverMode:postInit()
--- Refreshes all field number settings on start,
--- as clients might have a corrupted version.
FieldNumberSetting.refreshOnStart()
self.cp.postInitDone = true
end
-- we are in record mode
if self.cp.isRecording then
courseplay:record(self);
end;
-- we are in drive mode and single player /MP server
if self.cp.isDriving and g_server ~= nil then
local status, err = xpcall(self.cp.driver.update, function(err) printCallstack(); return err end, self.cp.driver, dt)
--- Resets all global info texts that were not called in this update loop.
g_globalInfoTextHandler:resetInactiveInfoTextsForVehicle(self)
if not status then
courseplay.infoVehicle(self, 'Exception, stopping Courseplay driver, %s', tostring(err))
courseplay.onStopCpAIDriver(self,AIVehicle.STOP_REASON_UNKOWN)
return
end
end
if self.cp.onSaveClick and not self.cp.doNotOnSaveClick then
if courseplay.vehicleToSaveCourseIn == self then
inputCourseNameDialogue:onSaveClick()
end
self.cp.onSaveClick = false
self.cp.doNotOnSaveClick = false
end
if self.cp.onMpSetCourses then
courseplay.courses:reloadVehicleCourses(self)
self.cp.onMpSetCourses = nil
end
if self.cp.collidingVehicleId ~= nil and g_currentMission.nodeToObject[self.cp.collidingVehicleId] ~= nil and g_currentMission.nodeToObject[self.cp.collidingVehicleId].isCpPathvehicle then
courseplay:setPathVehiclesSpeed(self,dt)
end
-- this really should be only done in one place.
self.cp.curSpeed = self.lastSpeedReal * 3600;
--- Updates all manual working tool positions if necessary.
WorkingToolPositionsSetting.updateManualToolPositions(dt)
end; --END update()
--[[
function courseplay:postUpdate(dt)
end;
]]
function courseplay:onUpdateTick(dt)
--print("base:courseplay:updateTick(dt)")
--if the vehicle is attached to another vehicle, disable cp
if not courseplay.isEnabled(self) then
return
end
if self.cp.toolsDirty then
courseplay:updateOnAttachOrDetach(self)
self.cp.toolsDirty = nil
end
if self.cp.isDriving and g_server ~= nil then
local status, err = xpcall(self.cp.driver.updateTick, function(err) printCallstack(); return err end, self.cp.driver, dt)
if not status then
courseplay.infoVehicle(self, 'Exception, stopping Courseplay driver, %s', tostring(err))
courseplay.onStopCpAIDriver(self,AIVehicle.STOP_REASON_UNKOWN)
return
end
end
self.timer = self.timer + dt;
end
--[[
function courseplay:postUpdateTick(dt)
end;
]]
function courseplay:onPreDelete()
---Delete map hotspot and all global info texts leftovers.
CpMapHotSpot.deleteMapHotSpot(self)
if g_server ~= nil then
--- Removes this vehicle form the global info texts handler.
g_globalInfoTextHandler:removeVehicle(self)
end
end
function courseplay:onDelete()
if self.cp.driver and self.cp.driver.collisionDetector then
self.cp.driver.collisionDetector:deleteTriggers()
end
if self.cp ~= nil then
if self.cp.hud.bg ~= nil then
self.cp.hud.bg:delete();
end;
if self.cp.hud.bgWithModeButtons ~= nil then
self.cp.hud.bgWithModeButtons:delete();
end;
if self.cp.directionArrowOverlay ~= nil then
self.cp.directionArrowOverlay:delete();
end;
if self.cp.buttons ~= nil then
courseplay.buttons:deleteButtonOverlays(self);
end;
if self.cp.signs ~= nil then
for _,section in pairs(self.cp.signs) do
for k,signData in pairs(section) do
courseplay.signs:deleteSign(signData.sign);
end;
end;
self.cp.signs = nil;
end;
if self.cp.course2dPdaMapOverlay then
self.cp.course2dPdaMapOverlay:delete();
end;
if self.cp.ppc then
self.cp.ppc:delete()
end
end;
end;
function courseplay:setInfoText(vehicle, text)
if not vehicle:getIsEntered() then
return
end
if vehicle.cp.infoText ~= text and text ~= nil and vehicle.cp.lastInfoText ~= text then
vehicle.cp.infoText = text
vehicle.cp.lastInfoText = text
elseif vehicle.cp.infoText ~= text and text ~= nil and vehicle.cp.lastInfoText == text then
vehicle.cp.infoText = text
end;
end;
function courseplay:renderInfoText(vehicle)
if vehicle:getIsEntered()and vehicle.cp.infoText ~= nil and vehicle.cp.toolTip == nil then
local text;
local what = StringUtil.splitString(";", vehicle.cp.infoText);
if what[1] == "COURSEPLAY_LOADING_AMOUNT"
or what[1] == "COURSEPLAY_UNLOADING_AMOUNT"
or what[1] == "COURSEPLAY_TURNING_TO_COORDS"
or what[1] == "COURSEPLAY_DRIVE_TO_WAYPOINT" then
if what[3] then
text = string.format(courseplay:loc(what[1]), tonumber(what[2]), tonumber(what[3]));
end
elseif what[1] == "COURSEPLAY_STARTING_UP_TOOL"
or what[1] == "COURSEPLAY_WAITING_POINTS_TOO_FEW"
or what[1] == "COURSEPLAY_WAITING_POINTS_TOO_MANY"
or what[1] == "COURSEPLAY_UNLOADING_POINTS_TOO_FEW"
or what[1] == "COURSEPLAY_UNLOADING_POINTS_TOO_MANY" then
if what[2] then
text = string.format(courseplay:loc(what[1]), what[2]);
end
elseif what[1] == "COURSEPLAY_WAITING_FOR_FILL_LEVEL" then
if what[3] then
text = string.format(courseplay:loc(what[1]), what[2], tonumber(what[3]));
end
elseif what[1] == "COURSEPLAY_DISTANCE" then
if what[2] then
local dist = tonumber(what[2]);
if dist >= 1000 then
text = ('%s: %.1f%s'):format(courseplay:loc('COURSEPLAY_DISTANCE'), dist * 0.001, courseplay:getMeasuringUnit());
else
text = ('%s: %d%s'):format(courseplay:loc('COURSEPLAY_DISTANCE'), dist, courseplay:loc('COURSEPLAY_UNIT_METER'));
end;
end
else
text = courseplay:loc(vehicle.cp.infoText)
end;
if text then
courseplay:setFontSettings('white', false, 'left');
renderText(courseplay.hud.infoTextPosX, courseplay.hud.infoTextPosY, courseplay.hud.fontSizes.infoText, text);
end;
end;
end;
function courseplay:setToolTip(vehicle, text)
if vehicle.cp.toolTip ~= text then
vehicle.cp.toolTip = text;
end;
end;
function courseplay:renderToolTip(vehicle)
courseplay:setFontSettings('white', false, 'left');
renderText(courseplay.hud.toolTipTextPosX, courseplay.hud.toolTipTextPosY, courseplay.hud.fontSizes.infoText, vehicle.cp.toolTip);
vehicle.cp.hud.toolTipIcon:render();
end;
function courseplay:setVehicleWaypoints(vehicle, waypoints)
vehicle.Waypoints = waypoints
vehicle.cp.numWaypoints = #waypoints
courseplay.signs:updateWaypointSigns(vehicle, "current");
if vehicle.cp.numWaypoints > 3 then
vehicle.cp.canDrive = true
end
end;
function courseplay:onReadStream(streamId, connection)
courseplay:debug("id: "..tostring(self.id).." base: readStream", courseplay.DBG_MULTIPLAYER)
for _,variable in ipairs(courseplay.multiplayerSyncTable)do
local value = courseplay.streamDebugRead(streamId, variable.dataFormat)
if variable.dataFormat == 'String' and value == 'nil' then
value = nil
end
courseplay:setVarValueFromString(self, variable.name, value)
end
courseplay:debug("id: "..tostring(NetworkUtil.getObjectId(self)).." base: read courseplay.multiplayerSyncTable end", courseplay.DBG_MULTIPLAYER)
-------------------
-- SettingsContainer:
self.cp.settings:onReadStream(streamId)
-- courseGeneratorSettingsContainer:
self.cp.courseGeneratorSettings:onReadStream(streamId)
-------------------
local copyCourseFromDriverId = streamReadInt32(streamId)
if copyCourseFromDriverId and copyCourseFromDriverId~=-1 then
self.cp.copyCourseFromDriver = NetworkUtil.getObject(copyCourseFromDriverId)
end
courseplay.courses:reinitializeCourses()
-- kurs daten
local courses = streamReadString(streamId) -- 60.
if courses ~= nil and courses~=-1 then
self.cp.loadedCourses = StringUtil.splitString(",", courses);
courseplay:reloadCourses(self, true)
end
self.cp.numCourses = streamReadInt32(streamId)
--print(string.format("%s:read: numCourses: %s loadedCourses: %s",tostring(self.name),tostring(self.cp.numCourses),tostring(#self.cp.loadedCourses)))
if self.cp.numCourses > #self.cp.loadedCourses then
self.Waypoints = {}
local wp_count = streamReadInt32(streamId)
for w = 1, wp_count do
table.insert(self.Waypoints, CourseEvent:readWaypoint(streamId))
end
self.cp.numWaypoints = #self.Waypoints
if self.cp.numCourses > 1 then
self.cp.currentCourseName = string.format("%d %s", self.cp.numCourses, courseplay:loc('COURSEPLAY_COMBINED_COURSES'));
end
end
-- SETUP 2D COURSE DRAW DATA
self.cp.course2dUpdateDrawData = true;
if streamReadBool(streamId) then
self.cp.infoText = streamReadString(streamId)
end
--Make sure every vehicle has same AIDriver as the Server
self.cp.driver:onReadStream(streamId)
courseplay.debugFormat("driver mode: %s",self.cp.settings.driverMode:get())
courseplay:debug("id: "..tostring(self.id).." base: readStream end", courseplay.DBG_MULTIPLAYER)
end
function courseplay:onWriteStream(streamId, connection)
courseplay:debug("id: "..tostring(self).." base: write stream", courseplay.DBG_MULTIPLAYER)
for _,variable in ipairs(courseplay.multiplayerSyncTable)do
courseplay.streamDebugWrite(streamId, variable.dataFormat, courseplay:getVarValueFromString(self,variable.name),variable.name)
end
courseplay:debug("id: "..tostring(self).." base: write courseplay.multiplayerSyncTable end", courseplay.DBG_MULTIPLAYER)
-------------------
-- SettingsContainer:
self.cp.settings:onWriteStream(streamId)
-- courseGeneratorSettingsContainer:
self.cp.courseGeneratorSettings:onWriteStream(streamId)
-------------
local copyCourseFromDriverID = -1;
if self.cp.copyCourseFromDriver ~= nil then
copyCourseFromDriverID = NetworkUtil.getObjectId(self.cp.copyCourseFromDriver)
end
streamWriteInt32(streamId, copyCourseFromDriverID)
local loadedCourses = "";
if #self.cp.loadedCourses then
loadedCourses = table.concat(self.cp.loadedCourses, ",")
end
streamWriteString(streamId, loadedCourses)
streamWriteInt32(streamId, self.cp.numCourses)
--print(string.format("%s:write: numCourses: %s loadedCourses: %s",tostring(self.name),tostring(self.cp.numCourses),tostring(#self.cp.loadedCourses)))
if self.cp.numCourses > #self.cp.loadedCourses then
courseplay:debug("id: "..tostring(NetworkUtil.getObjectId(self)).." sync temp course", courseplay.DBG_MULTIPLAYER)
streamWriteInt32(streamId, #(self.Waypoints))
for w = 1, #(self.Waypoints) do
--print("writing point "..tostring(w))
CourseEvent:writeWaypoint(streamId, self.Waypoints[w])
end
end
if self.cp.infoText then
streamWriteBool(streamId,true)
streamWriteString(streamId,self.cp.infoText)
else
streamWriteBool(streamId,false)
end
self.cp.driver:onWriteStream(streamId)
courseplay.debugFormat("driver mode: %s",self.cp.settings.driverMode:get())
courseplay:debug("id: "..tostring(NetworkUtil.getObjectId(self)).." base: write stream end", courseplay.DBG_MULTIPLAYER)
end
--TODO figure out how dirtyFlags work ??
function courseplay:onReadUpdateStream(streamId, timestamp, connection)
if connection:getIsServer() then
--only sync while cp is drivin!
if streamReadBool(streamId) then
self.cp.driver:readUpdateStream(streamId, timestamp, connection)
if streamReadBool(streamId) then
self.cp.waypointIndex = streamReadInt32(streamId)
else
self.cp.waypointIndex = 0
end
if streamReadBool(streamId) then -- is infoText~=nil ?
if streamReadBool(streamId) then -- has infoText changed
self.cp.infoText = streamReadString(streamId)
end
else
self.cp.infoText = nil
end
if streamReadBool(streamId) then -- is currentCourseName~=nil ?
if streamReadBool(streamId) then -- has currentCourseName changed
self.cp.currentCourseName = streamReadString(streamId)
end
else
self.cp.currentCourseName = nil
end
--gitAdditionalText ?
end
end
end
function courseplay:onWriteUpdateStream(streamId, connection, dirtyMask)
if not connection:getIsServer() then
streamWriteBool(streamId, self:getIsCourseplayDriving() or false)
if self:getIsCourseplayDriving() then
self.cp.driver:writeUpdateStream(streamId, connection, dirtyMask)
if self.cp.waypointIndex then
streamWriteBool(streamId,true)
streamWriteInt32(streamId,self.cp.waypointIndex)
else
streamWriteBool(streamId,false)
end
if self.cp.infoText then --is infoText~=nil ?
streamWriteBool(streamId,true)
if self.cp.infoText~=self.cp.infoTextSend then -- has infoText changed
streamWriteBool(streamId,true)
streamWriteString(streamId,self.cp.infoText)
self.cp.infoTextSend = self.cp.infoText
else
streamWriteBool(streamId,false)
end
else
streamWriteBool(streamId,false)
end
if self.cp.currentCourseName then -- is currentCourseName~=nil ?
streamWriteBool(streamId,true)
if self.cp.currentCourseName~=self.cp.currentCourseNameSend then -- has currentCourseName changed
streamWriteBool(streamId,true)
streamWriteString(streamId,self.cp.currentCourseName)
self.cp.currentCourseNameSend = self.cp.currentCourseName
else
streamWriteBool(streamId,false)
end
else
streamWriteBool(streamId,false)
end
end
end
end
function courseplay:loadVehicleCPSettings(xmlFile, key, resetVehicles)
if not resetVehicles and g_server ~= nil then
-- COURSEPLAY
local curKey = key .. '.courseplay.basics';
local courses = Utils.getNoNil(getXMLString(xmlFile, curKey .. '#courses'), '');
self.cp.loadedCourses = StringUtil.splitString(",", courses);
courseplay:reloadCourses(self, true);
--HUD
curKey = key .. '.courseplay.HUD';
self.cp.hud.show = Utils.getNoNil( getXMLBool(xmlFile, curKey .. '#showHud'), false);
self.cp.settings:loadFromXML(xmlFile, key .. '.courseplay')
self.cp.courseGeneratorSettings:loadFromXML(xmlFile, key .. '.courseplay')
courseplay:validateCanSwitchMode(self);
end;
return BaseMission.VEHICLE_LOAD_OK;
end
function courseplay:saveToXMLFile(xmlFile, key, usedModNames)
if not self.hasCourseplaySpec then
courseplay.infoVehicle(self, 'has no Courseplay installed, not adding Courseplay data to savegame.')
return
end
--cut the key to configure it for our needs
local keySplit = StringUtil.splitString(".", key);
local newKey = keySplit[1]
for i=2,#keySplit-2 do
newKey = newKey..'.'..keySplit[i]
end
newKey = newKey..'.courseplay'
--CP basics
if #self.cp.loadedCourses == 0 and self.cp.currentCourseId ~= 0 then
-- this is the case when a course has been generated and than saved, it is not in loadedCourses (should probably
-- fix it there), so make sure it is in the savegame
setXMLString(xmlFile, newKey..".basics #courses", tostring(self.cp.currentCourseId))
else
setXMLString(xmlFile, newKey..".basics #courses", tostring(table.concat(self.cp.loadedCourses, ",")))
end
--HUD
setXMLBool(xmlFile, newKey..".HUD #showHud", self.cp.hud.show)
self.cp.settings:saveToXML(xmlFile, newKey)
self.cp.courseGeneratorSettings:saveToXML(xmlFile, newKey)
end
---Is this one still used as cp.isTurning isn't getting set to true ??
-- This is to prevent the selfPropelledPotatoHarvester from turning off while turning
function courseplay.setIsTurnedOn(self, originalFunction, isTurnedOn, noEventSend)
if self.typeName and self.typeName == "selfPropelledPotatoHarvester" then
if self.getIsCourseplayDriving and self:getIsCourseplayDriving() and self.cp.isTurning and not isTurnedOn then
isTurnedOn = true;
end;
end;
originalFunction(self, isTurnedOn, noEventSend);
end;
TurnOnVehicle.setIsTurnedOn = Utils.overwrittenFunction(TurnOnVehicle.setIsTurnedOn, courseplay.setIsTurnedOn);
-- Workaround: onEndWorkAreaProcessing seems to cause Cutter to call stopAIVehicle when
-- driving on an already worked field, or a field where the fruit type is different than the one being processed.
-- This changes that behavior.
function courseplay:getAllowCutterAIFruitRequirements(superFunc)
return superFunc(self) and not self:getIsCourseplayDriving()
end
Cutter.getAllowCutterAIFruitRequirements = Utils.overwrittenFunction(Cutter.getAllowCutterAIFruitRequirements, courseplay.getAllowCutterAIFruitRequirements)
-- Workaround: onEndWorkAreaProcessing seems to cause Cutter to call stopAIVehicle when
-- driving on an already worked field. This will suppress that call as long as Courseplay is driving
function courseplay:stopAIVehicle(superFunc, reason, noEventSend)
if superFunc ~= nil and not self:getIsCourseplayDriving() then
superFunc(self, reason, noEventSend)
end
end
AIVehicle.stopAIVehicle = Utils.overwrittenFunction(AIVehicle.stopAIVehicle, courseplay.stopAIVehicle)
function courseplay:onSetBrokenAIVehicle(superFunc)
if self:getIsCourseplayDriving() then
if g_server ~= nil then
courseplay.onStopCpAIDriver(self,AIVehicle.STOP_REASON_UNKOWN)
end
else
superFunc(self)
end
end
AIVehicle.onSetBroken = Utils.overwrittenFunction(AIVehicle.onSetBroken, courseplay.onSetBrokenAIVehicle)
---These two AIVehicle function are overwritten for multiplayer compatibility,
---a better way would probably be to overwrite AIVehicle:startAIVehicle()
---and AIVehicle:stopAIVehicle(). For MP they could then be overloaded with
---a boolean to make sure we set a CP driver and not a giants helper or we would need to make sure
---courseplay:getIsCourseplayDriving() is set on the client before any other calls.
function courseplay:onWriteStreamAIVehicle(superFunc,streamId, connection)
if self:getIsCourseplayDriving() then
streamWriteBool(streamId,true)
local spec = self.spec_aiVehicle
streamWriteUInt8(streamId, spec.currentHelper.index)
streamWriteUIntN(streamId, spec.startedFarmId, FarmManager.FARM_ID_SEND_NUM_BITS)
else
streamWriteBool(streamId,false)
superFunc(self,streamId, connection)
end
end
AIVehicle.onWriteStream = Utils.overwrittenFunction(AIVehicle.onWriteStream, courseplay.onWriteStreamAIVehicle)
function courseplay:onReadStreamAIVehicle(superFunc,streamId, connection)
if streamReadBool(streamId) then
local helperIndex = streamReadUInt8(streamId)
local farmId = streamReadUIntN(streamId, FarmManager.FARM_ID_SEND_NUM_BITS)
courseplay.onStartCpAIDriver(self,helperIndex, true, farmId)
else
superFunc(self,streamId, connection)
end
end
AIVehicle.onReadStream = Utils.overwrittenFunction(AIVehicle.onReadStream, courseplay.onReadStreamAIVehicle)
---Disables fertilizing while sowing, if SowingMachineFertilizerEnabledSetting is false.
function courseplay.processSowingMachineArea(tool,originalFunction, superFunc, workArea, dt)
local rootVehicle = tool:getRootVehicle()
if courseplay:isAIDriverActive(rootVehicle) then
if rootVehicle.cp.settings.sowingMachineFertilizerEnabled:is(false) then
tool.spec_sprayer.workAreaParameters.sprayFillLevel = 0
end
end
return originalFunction(tool, superFunc, workArea, dt)
end
FertilizingSowingMachine.processSowingMachineArea = Utils.overwrittenFunction(FertilizingSowingMachine.processSowingMachineArea, courseplay.processSowingMachineArea)
---Speed limit is disabled while cp is driving.
function courseplay.doCheckSpeedLimit(object,superFunc,...)
local rootVehicle = object:getRootVehicle()
if not rootVehicle:getIsCourseplayDriving() then
return superFunc(object,...)
else
return false
end
end
-- Tour dialog messes up the CP yes no dialogs.
function courseplay:showTourDialog()
print('Tour dialog is disabled by Courseplay.')
end
TourIcons.showTourDialog = Utils.overwrittenFunction(TourIcons.showTourDialog, courseplay.showTourDialog)
-- TODO: make these part of AIDriver
function courseplay:setWaypointIndex(vehicle, number,isRecording)
if vehicle.cp.waypointIndex ~= number then
vehicle.cp.course.hasChangedTheWaypointIndex = true
if isRecording then
vehicle.cp.waypointIndex = number
else
vehicle.cp.waypointIndex = number
end
if vehicle.cp.waypointIndex > 1 then
vehicle.cp.previousWaypointIndex = vehicle.cp.waypointIndex - 1;
else
vehicle.cp.previousWaypointIndex = 1;
end;
end;
end;
function courseplay:getIsCourseplayDriving()
return self.cp.isDriving
end;
function courseplay:setIsCourseplayDriving(active)
self.cp.isDriving = active
end;
--- Explicit interface function for other mods (like AutoDrive) to start the Courseplay driver (by vehicle:startCpDriver())
function courseplay:startCpDriver()
courseplay.onStartCpAIDriver(self, nil, false, g_currentMission.player.farmId)
end
--- Explicit interface function for other mods (like AutoDrive) to stop the Courseplay driver (by vehicle:stopCpDriver())
function courseplay:stopCpDriver()
courseplay.onStopCpAIDriver(self, AIVehicle.STOP_REASON_REGULAR)
end
--the same code as giants AIVehicle:startAIVehicle(helperIndex, noEventSend, startedFarmId), but customized for cp
--All the code that has to be run on Server and Client from the "start_stop" file has to get in here
function courseplay.onStartCpAIDriver(vehicle,helperIndex,noEventSend, startedFarmId)
local spec = vehicle.spec_aiVehicle
if not vehicle:getIsCourseplayDriving() then
--giants code from AIVehicle:startAIVehicle()