-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathVehicleMotor.lua
More file actions
2537 lines (2036 loc) · 104 KB
/
VehicleMotor.lua
File metadata and controls
2537 lines (2036 loc) · 104 KB
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
---Class for vehicle motors
local VehicleMotor_mt = Class(VehicleMotor)
---Creating new motor
-- @param integer minRpm min rpm
-- @param integer maxRpm max rpm
-- @param float maxForwardSpeed max forward speed
-- @param float maxBackwardSpeed max backward speed
-- @param table torqueCurve torque curve (AnimCurve)
-- @param float brakeForce brake force
-- @param float forwardGears list of gear ratios to use when driving forwards (in decreasing order)
-- @param float backwardGears list of gear ratios to use when driving backwards (in decreasing order)
-- @param float minForwardGearRatio min forward gear ratio
-- @param float maxForwardGearRatio max forward gear ratio
-- @param float minBackwardGearRatio min backward gear ratio
-- @param float maxBackwardGearRatio max backward gear ratio
-- @param integer ptoMotorRpmRatio pto motor rpm ratio
-- @return table motorInstance motor instance
function VehicleMotor.new(vehicle, minRpm, maxRpm, maxForwardSpeed, maxBackwardSpeed, torqueCurve, brakeForce, forwardGears, backwardGears, minForwardGearRatio, maxForwardGearRatio, minBackwardGearRatio, maxBackwardGearRatio, ptoMotorRpmRatio, minSpeed)
local self = setmetatable({}, VehicleMotor_mt)
self.vehicle = vehicle
self.minRpm = minRpm
self.maxRpm = maxRpm
self.minSpeed = minSpeed
self.maxForwardSpeed = maxForwardSpeed -- speed in m/s
self.maxBackwardSpeed = maxBackwardSpeed
self.maxClutchTorque = 5 -- amount of torque that can be transferred from motor to clutch/wheels [t m s^-2]
self.torqueCurve = torqueCurve
self.brakeForce = brakeForce
self.lastAcceleratorPedal = 0
self.idleGearChangeTimer = 0 -- if this timer reaches 0 the automatic gear change while not moving is allowed
self.doSecondBestGearSelection = 0
self.gear = 0
self.bestGearSelected = 0
self.minGearRatio = 0
self.maxGearRatio = 0
self.allowGearChangeTimer = 0
self.allowGearChangeDirection = 0
self.forwardGears = forwardGears
self.backwardGears = backwardGears
self.currentGears = self.forwardGears
self.minForwardGearRatio = minForwardGearRatio
self.maxForwardGearRatio = maxForwardGearRatio
self.minBackwardGearRatio = minBackwardGearRatio
self.maxBackwardGearRatio = maxBackwardGearRatio
self.transmissionDirection = 1
self.maxClutchSpeedDifference = 0
self.defaultForwardGear = 1
if self.forwardGears ~= nil then
for i=1, #self.forwardGears do
self.maxClutchSpeedDifference = math.max(self.maxClutchSpeedDifference, self.minRpm / self.forwardGears[i].ratio * math.pi / 30)
if self.forwardGears[i].default then
self.defaultForwardGear = i
end
end
end
self.defaultBackwardGear = 1
if self.backwardGears ~= nil then
for i=1, #self.backwardGears do
self.maxClutchSpeedDifference = math.max(self.maxClutchSpeedDifference, self.minRpm / self.backwardGears[i].ratio * math.pi / 30)
if self.backwardGears[i].default then
self.defaultBackwardGear = i
end
end
end
self.gearType = VehicleMotor.TRANSMISSION_TYPE.DEFAULT
self.groupType = VehicleMotor.TRANSMISSION_TYPE.DEFAULT
self.manualTargetGear = nil
self.targetGear = 0
self.previousGear = 0
self.gearChangeTimer = -1
self.gearChangeTime = 250
self.gearChangeTimeOrig = self.gearChangeTime
self.autoGearChangeTimer = -1
self.autoGearChangeTime = 1000
self.manualClutchValue = 0
self.stallTimer = 0
self.lastGearChangeTime = 0
self.gearChangeTimeAutoReductionTime = 500
self.gearChangeTimeAutoReductionTimer = 0
self.lastManualShifterActive = false
self.clutchSlippingTime = 1000
self.clutchSlippingTimer = 0
self.clutchSlippingGearRatio = 0
self.groupChangeTime = 500
self.groupChangeTimer = 0
self.gearGroupUpShiftTime = 3000
self.gearGroupUpShiftTimer = 0
self.currentDirection = 1 -- current used gear direction
self.directionChangeTimer = 0
self.directionChangeTime = 500
self.directionChangeUseGear = false -- use a backward gear for direction change buttons
self.directionChangeGearIndex = 1 -- backward gear to activate if direction changes
self.directionLastGear = -1 -- last forward gear, activated if direction is changed again
self.directionChangeUseGroup = false -- use a group for direction change buttons
self.directionChangeGroupIndex = 1 -- group to activate if direction changes
self.directionLastGroup = -1 -- last selected group, acitvated if direction changes again
self.directionChangeUseInverse = true -- if true the forward gears are just inverted for driving backwards
self.gearChangedIsLocked = false
self.gearGroupChangedIsLocked = false
self.startGearValues = {
slope = 0,
mass = 0,
lastMass = 0,
maxForce = 0,
massDirectionDifferenceXZ = 0,
massDirectionDifferenceY = 0,
massDirectionFactor = 0,
availablePower = 0,
massFactor = 0
}
self.startGearThreshold = VehicleMotor.GEAR_START_THRESHOLD
self.lastSmoothedClutchPedal = 0
self.lastRealMotorRpm = 0
self.lastMotorRpm = 0
self.lastModulationPercentage = 0
self.lastModulationTimer = 0
self.rawLoadPercentage = 0
self.rawLoadPercentageBuffer = 0
self.rawLoadPercentageBufferIndex = 0
self.smoothedLoadPercentage = 0
self.loadPercentageChangeCharge = 0
self.accelerationLimitLoadScale = 1
self.accelerationLimitLoadScaleTimer = 0
self.accelerationLimitLoadScaleDelay = 2000 -- after running this time at max acceleration we decrease the motor load slowly again
self.constantRpmCharge = 0
self.constantAccelerationCharge = 0
self.lastTurboScale = 0
self.blowOffValveState = 0
self.overSpeedTimer = 0
self.rpmLimit = math.huge
self.speedLimit = math.huge -- Speed limit in km/h
self.speedLimitAcc = math.huge
self.accelerationLimit = 2 -- m s^-2
self.motorRotationAccelerationLimit = (maxRpm - minRpm)*math.pi/30 / 2 -- rad s^-2 default accelerate from min rpm to max rpm in 2 sec
self.equalizedMotorRpm = 0
self.requiredMotorPower = 0
if self.maxForwardSpeed == nil then
self.maxForwardSpeed = self:calculatePhysicalMaximumForwardSpeed()
end
if self.maxBackwardSpeed == nil then
self.maxBackwardSpeed = self:calculatePhysicalMaximumBackwardSpeed()
end
-- saving the original values to be able to inverse them
self.maxForwardSpeedOrigin = self.maxForwardSpeed
self.maxBackwardSpeedOrigin = self.maxBackwardSpeed
self.minForwardGearRatioOrigin = self.minForwardGearRatio
self.maxForwardGearRatioOrigin = self.maxForwardGearRatio
self.minBackwardGearRatioOrigin = self.minBackwardGearRatio
self.maxBackwardGearRatioOrigin = self.maxBackwardGearRatio
self.peakMotorTorque = self.torqueCurve:getMaximum()
-- Calculate peak power. Assume we have a linear interpolation on the torque values
-- For each segment, find the maximum power (D[torque(x, i) * x] == 0) and take the maximum segment
-- D[ ((x-x0) / (x1-x0) (y1-y0) + y0) x] == 0
-- -> (x1 y0 - x0 y1) / (2 (y0 - y1)) if y0 != y1
self.peakMotorPower = 0
self.peakMotorPowerRotSpeed = 0
local numKeyFrames = #self.torqueCurve.keyframes
if numKeyFrames >= 2 then
for i=2,numKeyFrames do
local v0 = self.torqueCurve.keyframes[i-1]
local v1 = self.torqueCurve.keyframes[i]
local torque0 = self.torqueCurve:getFromKeyframes(v0, v0, i-1, i-1, 0)
local torque1 = self.torqueCurve:getFromKeyframes(v1, v1, i, i, 0)
local rpm, torque
if math.abs(torque0 - torque1) > 0.0001 then
rpm = (v1.time * torque0 - v0.time * torque1) / (2.0 * (torque0 - torque1))
rpm = math.min(math.max(rpm, v0.time), v1.time)
torque = self.torqueCurve:getFromKeyframes(v0, v1, i-1, i, (v1.time - rpm) / (v1.time - v0.time))
else
rpm = v0.time
torque = torque0
end
local power = torque * rpm
if power > self.peakMotorPower then
self.peakMotorPower = power
self.peakMotorPowerRotSpeed = rpm
end
end
-- Convert from rpm to rad/s
self.peakMotorPower = self.peakMotorPower * math.pi/30
self.peakMotorPowerRotSpeed = self.peakMotorPowerRotSpeed * math.pi/30
else
local v = self.torqueCurve.keyframes[1]
local rotSpeed = v.time*math.pi/30
local torque = self.torqueCurve:getFromKeyframes(v, v, 1, 1, 0)
self.peakMotorPower = rotSpeed*torque
self.peakMotorPowerRotSpeed = rotSpeed
end
self.ptoMotorRpmRatio = ptoMotorRpmRatio
self.rotInertia = self.peakMotorTorque / 600 -- Rotational inertia of the motor, mostly defined by the flywheel [t m^2]
self.dampingRateFullThrottle = VehicleMotor.DEFAULT_DAMPING_RATE_FULL_THROTTLE -- Damping rate of the motor if the acceleration pedal is 1 [t m^2 s^-1]
self.dampingRateZeroThrottleClutchEngaged = VehicleMotor.DEFAULT_DAMPING_RATE_ZERO_THROTTLE_CLUTCH_EN -- Damping rate of the motor if the acceleration pedal is 0 and the clutch is engaged [t m^2 s^-1]
self.dampingRateZeroThrottleClutchDisengaged = VehicleMotor.DEFAULT_DAMPING_RATE_ZERO_THROTTLE_CLUTCH_DIS -- Damping rate of the motor if the acceleration pedal is 0 and the clutch is disengaged [t m^2 s^-1]
-- Motor properties as read from the physics engine
self.gearRatio = 0
self.motorRotSpeed = 0 -- motor rotation speed [rad/s]
self.motorRotSpeedClutchEngaged = 0 -- additional rotation speed when clutch is engaged
self.motorRotAcceleration = 0 -- motor rotation acceleration [rad/s^2]
self.motorRotAccelerationSmoothed = 0 -- motor rotation acceleration smoothed [rad/s^2]
self.motorAvailableTorque, self.lastMotorAvailableTorque = 0, 0 -- torque that was available to the physics simulation [kN == t m/s^2]
self.motorAppliedTorque, self.lastMotorAppliedTorque = 0, 0 -- torque that was applied (<= available), can be smaller when acceleration/speed is limited [kN == t m/s^2]
self.motorExternalTorque, self.lastMotorExternalTorque = 0, 0 -- torque that was removed from the motor and was not applied to the wheels (e.g. PTO) [kN == t m/s^2]
-- externalTorqueVirtualMultiplicator: is used to have virtually more motor load due to external torque (pto) but still reduce the motor by the same external torque
-- like this we can increase the motor load without cutting the motor to hard so we cannot accelerate anymore
self.externalTorqueVirtualMultiplicator = 1
self.differentialRotSpeed = 0 -- rotation speed of the main differential [rad/s]
self.differentialRotAcceleration = 0 -- rotation accleration of the main differential [rad/s^2]
self.differentialRotAccelerationSmoothed = 0 -- smoothed rotation accleration of the main differential [rad/s^2]
self.differentialRotAccelerationIndex = 1
self.differentialRotAccelerationSamples = {}
for _=1, 10 do
table.insert(self.differentialRotAccelerationSamples, 0)
end
self.lastDifference = 0
self.directionChangeMode = g_gameSettings:getValue(GameSettings.SETTING.DIRECTION_CHANGE_MODE)
self.gearShiftMode = g_gameSettings:getValue(GameSettings.SETTING.GEAR_SHIFT_MODE)
return self
end
---Post load motor
-- @param table savegame savegame information
function VehicleMotor:postLoad(savegame)
if self.gearGroups ~= nil then
SpecializationUtil.raiseEvent(self.vehicle, "onGearGroupChanged", self.activeGearGroupIndex, 0)
end
end
---
function VehicleMotor:delete()
g_messageCenter:unsubscribeAll(self)
end
---Set power shift stages
-- @param table gearGroups gearGroups
function VehicleMotor:setGearGroups(gearGroups, groupType, groupChangeTime)
self.gearGroups = gearGroups
self.groupType = VehicleMotor.TRANSMISSION_TYPE[groupType:upper()] or VehicleMotor.TRANSMISSION_TYPE.DEFAULT
self.groupChangeTime = groupChangeTime
if gearGroups ~= nil then
self.numGearGroups = #gearGroups
self.defaultGearGroup = 1
-- use first forward gear group
for i=1, self.numGearGroups do
if self.gearGroups[i].ratio > 0 then
self.defaultGearGroup = i
break
end
end
-- use group with default attribute set
for i=1, self.numGearGroups do
if self.gearGroups[i].isDefault then
self.defaultGearGroup = i
break
end
end
self.activeGearGroupIndex = self.defaultGearGroup
end
end
---Set power shift stages
-- @param table gearGroups gearGroups
function VehicleMotor:setDirectionChange(directionChangeUseGear, directionChangeGearIndex, directionChangeUseGroup, directionChangeGroupIndex, directionChangeTime)
self.directionChangeUseGear = directionChangeUseGear
self.directionChangeGearIndex = directionChangeGearIndex
self.directionChangeUseGroup = directionChangeUseGroup
self.directionChangeGroupIndex = directionChangeGroupIndex
self.directionChangeTime = directionChangeTime
self.directionChangeUseInverse = not directionChangeUseGear and not directionChangeUseGroup
end
---Sets the manual shift settings
-- @param boolean gears gears can be shifted manually
-- @param boolean groups groups can be shifted manually
function VehicleMotor:setManualShift(manualShiftGears, manualShiftGroups)
self.manualShiftGears = manualShiftGears
self.manualShiftGroups = manualShiftGroups
end
---Sets custom start gear threshold
-- @param float lowBrakeForceScale low brake force scale
-- @param float lowBrakeForceSpeedLimit low brake force speed limit
function VehicleMotor:setStartGearThreshold(startGearThreshold)
self.startGearThreshold = startGearThreshold
end
---Set low brake force
-- @param float lowBrakeForceScale low brake force scale
-- @param float lowBrakeForceSpeedLimit low brake force speed limit
function VehicleMotor:setLowBrakeForce(lowBrakeForceScale, lowBrakeForceSpeedLimit)
self.lowBrakeForceScale = lowBrakeForceScale
self.lowBrakeForceSpeedLimit = lowBrakeForceSpeedLimit
end
---Returns max clutch torque
-- @return float maxClutchTorque max clutch torque
function VehicleMotor:getMaxClutchTorque()
return self.maxClutchTorque
end
---Returns rotation inertia
-- @return float rotInertia rotation inertia
function VehicleMotor:getRotInertia()
return self.rotInertia
end
---Sets rotation inertia
-- @param float rotInertia rotation inertia
function VehicleMotor:setRotInertia(rotInertia)
self.rotInertia = rotInertia
end
---Returns the damping rate of the motor if the acceleration pedal is 1
-- @return float dampingRate damping rate [t m^2 s^-1]
function VehicleMotor:getDampingRateFullThrottle()
return self.dampingRateFullThrottle
end
---Returns the damping rate of the motor if the acceleration pedal is 0 and the clutch is engaged
-- @return float dampingRate damping rate [t m^2 s^-1]
function VehicleMotor:getDampingRateZeroThrottleClutchEngaged()
return self.dampingRateZeroThrottleClutchEngaged
end
---Returns the damping rate of the motor if the acceleration pedal is 0 and the clutch is disengaged
-- @return float dampingRate damping rate [t m^2 s^-1]
function VehicleMotor:getDampingRateZeroThrottleClutchDisengaged()
return self.dampingRateZeroThrottleClutchDisengaged
end
---Scales all damping rate values with this factor
-- @param float dampingRateScale scale of damping rate [0-1]
function VehicleMotor:setDampingRateScale(dampingRateScale)
self.dampingRateFullThrottle = VehicleMotor.DEFAULT_DAMPING_RATE_FULL_THROTTLE * dampingRateScale
self.dampingRateZeroThrottleClutchEngaged = VehicleMotor.DEFAULT_DAMPING_RATE_ZERO_THROTTLE_CLUTCH_EN * dampingRateScale
self.dampingRateZeroThrottleClutchDisengaged = VehicleMotor.DEFAULT_DAMPING_RATE_ZERO_THROTTLE_CLUTCH_DIS * dampingRateScale
end
---Sets the time it takes change gears
-- @param float gearChangeTime gear change time [ms]
function VehicleMotor:setGearChangeTime(gearChangeTime)
self.gearChangeTime = gearChangeTime
self.gearChangeTimeOrig = gearChangeTime
self.gearChangeTimer = math.min(self.gearChangeTimer, gearChangeTime)
self.gearType = gearChangeTime == 0 and VehicleMotor.TRANSMISSION_TYPE.POWERSHIFT or VehicleMotor.TRANSMISSION_TYPE.DEFAULT
end
---Sets the time that needs to pass since the last gear change until an automatic gear change is allowed
-- @param float autoGearChangeTime automatic gear change time [ms]
function VehicleMotor:setAutoGearChangeTime(autoGearChangeTime)
self.autoGearChangeTime = autoGearChangeTime
self.autoGearChangeTimer = math.min(self.autoGearChangeTimer, autoGearChangeTime)
end
---Returns max torque
-- @return float maxMotorTorque max motor torque
function VehicleMotor:getPeakTorque()
return self.peakMotorTorque
end
---Returns brake force
-- @return float brakeForce brake force
function VehicleMotor:getBrakeForce()
return self.brakeForce
end
---Returns min rpm
-- @return float minRpm min rpm
function VehicleMotor:getMinRpm()
return self.minRpm
end
---Returns max rpm
-- @return float maxRpm max rpm
function VehicleMotor:getMaxRpm()
return self.maxRpm
end
---Returns the currently required motor rpm range (e.g. defined by the activated pto)
-- @return float minRequiredRpm min required rpm
-- @return float minRequiredRpm max required rpm
function VehicleMotor:getRequiredMotorRpmRange()
local motorPtoRpm = math.min(PowerConsumer.getMaxPtoRpm(self.vehicle)*self.ptoMotorRpmRatio, self.maxRpm)
if motorPtoRpm ~= 0 then
return motorPtoRpm, self.maxRpm
end
return self.minRpm, self.maxRpm
end
---Returns last motor rpm damped
-- @return float lastMotorRpm last motor rpm
function VehicleMotor:getLastMotorRpm()
return self.lastMotorRpm
end
---Returns last motor rpm modulated
-- @return float lastModulatedMotorRpm last modulated motor rpm
function VehicleMotor:getLastModulatedMotorRpm()
local modulationIntensity = math.clamp((self.smoothedLoadPercentage - MODULATION_RPM_MIN_REF_LOAD) / (MODULATION_RPM_MAX_REF_LOAD - MODULATION_RPM_MIN_REF_LOAD), MODULATION_RPM_MIN_INTENSITY, 1)
local modulationOffset = self.lastModulationPercentage * (MODULATION_RPM_MAX_OFFSET * modulationIntensity) * self.constantRpmCharge
-- apply only if clutch is released since with slipping clutch the rpm is already decreased
local loadChangeChargeDrop = 0
if self:getClutchPedal() < 0.1 and self.minGearRatio > 0 then
local rpmRange = self.maxRpm - self.minRpm
local dropScale = (self.lastMotorRpm - self.minRpm) / rpmRange * 0.5
loadChangeChargeDrop = self.loadPercentageChangeCharge * rpmRange * dropScale
else
self.loadPercentageChangeCharge = 0
end
return self.lastMotorRpm + modulationOffset - loadChangeChargeDrop
end
---Returns last motor rpm real
-- @return float lastMotorRpm last motor rpm
function VehicleMotor:getLastRealMotorRpm()
return self.lastRealMotorRpm
end
---Returns the last smoothed load percentage
-- @return float load load [0-1]
function VehicleMotor:getSmoothLoadPercentage()
local modulationIntensity = math.clamp((self.smoothedLoadPercentage - MODULATION_LOAD_MIN_REF_LOAD) / (MODULATION_LOAD_MAX_REF_LOAD - MODULATION_LOAD_MIN_REF_LOAD), MODULATION_LOAD_MIN_INTENSITY, 1)
return self.smoothedLoadPercentage - self.lastModulationPercentage * (MODULATION_LOAD_MAX_OFFSET * modulationIntensity)
end
---Returns clutch pedal state
-- @return float state state [0-1]
function VehicleMotor:getClutchPedal()
if not self.vehicle.isServer or self.gearShiftMode == VehicleMotor.SHIFT_MODE_MANUAL_CLUTCH then
return self.manualClutchValue
end
local clutchRpm = self:getNonClampedMotorRpm()
if clutchRpm == 0 then
return 0
end
return 1 - math.max(math.min((self:getClutchRotSpeed() * 30 / math.pi + 50) / clutchRpm, 1), 0) -- have 50 rpm tolerance
end
---Returns smoothed clutch pedal state
-- @return float state state [0-1]
function VehicleMotor:getSmoothedClutchPedal()
return self.lastSmoothedClutchPedal
end
---Returns manual clutch pedal state
-- @return float state state [0-1]
function VehicleMotor:getManualClutchPedal()
if self.gearShiftMode == VehicleMotor.SHIFT_MODE_MANUAL_CLUTCH then
return self.manualClutchValue
end
return 0
end
---Returns the current gear as string
-- @return string gear gear
function VehicleMotor:getGearToDisplay(isDashboard)
local gearName = "N"
if self.backwardGears or self.forwardGears then
if self.targetGear > 0 then
local gear = self.currentGears[self.targetGear]
if gear ~= nil then
local gearNameDirection = self.currentGears == self.forwardGears and self.currentDirection or 1
if isDashboard then
gearName = gearNameDirection == 1 and (gear.dashboardName or gear.name) or (gear.dashboardReverseName or gear.reverseName)
else
gearName = gearNameDirection == 1 and gear.name or gear.reverseName
end
end
end
else
local direction = self:getDrivingDirection()
if direction > 0 then
gearName = "D"
elseif direction < 0 then
gearName = "R"
end
end
return gearName
end
---Returns the current gear information to display
-- @return string gear gear
-- @return boolean available gears are available
-- @return boolean isAutomatic is variable transmission
-- @return string prevGearName previous gear name
-- @return string nextGearName next gear name
-- @return string prevPrevGearName second previous gear name
-- @return string nextNextGearName second next gear name
-- @return boolean isGearChanging is currently changing the gear
function VehicleMotor:getGearInfoToDisplay()
local gearName, available = "N", false
local prevGearName, nextGearName
local prevPrevGearName, nextNextGearName
local isAutomatic = false
local isGearChanging = false
if self.backwardGears or self.forwardGears then
if self.targetGear > 0 then
local gear = self.currentGears[self.targetGear]
if gear ~= nil then
local displayDirection = self.currentDirection
local gearNameDirection = self.currentGears == self.forwardGears and self.currentDirection or 1
gearName = gearNameDirection == 1 and gear.name or gear.reverseName
local prevGear = self.currentGears[self.targetGear + 1 * -displayDirection]
if prevGear ~= nil then
prevGearName = gearNameDirection == 1 and prevGear.name or prevGear.reverseName
prevGear = self.currentGears[self.targetGear + 2 * -displayDirection]
if prevGear ~= nil then
prevPrevGearName = gearNameDirection == 1 and prevGear.name or prevGear.reverseName
end
end
local nextGear = self.currentGears[self.targetGear + 1 * displayDirection]
if nextGear ~= nil then
nextGearName = gearNameDirection == 1 and nextGear.name or nextGear.reverseName
nextGear = self.currentGears[self.targetGear + 2 * displayDirection]
if nextGear ~= nil then
nextNextGearName = gearNameDirection == 1 and nextGear.name or nextGear.reverseName
end
end
if self.gear ~= self.targetGear then
isGearChanging = true
end
end
end
available = true
else
local direction = self:getDrivingDirection()
if direction > 0 then
gearName = "D"
prevGearName = "N"
elseif direction < 0 then
gearName = "R"
nextGearName = "N"
else
nextGearName = "D"
prevGearName = "R"
end
isAutomatic = true
end
return gearName, available, isAutomatic, prevGearName, nextGearName, prevPrevGearName, nextNextGearName, isGearChanging
end
---Returns the current driving direction or preselected direction
function VehicleMotor:getDrivingDirection()
if self.directionChangeMode == VehicleMotor.DIRECTION_CHANGE_MODE_MANUAL or self.gearShiftMode ~= VehicleMotor.SHIFT_MODE_AUTOMATIC then
return self.currentDirection * self.transmissionDirection
else
if self.vehicle:getLastSpeed() > 0.95 then
return self.vehicle.movingDirection * self.transmissionDirection
end
end
return 0
end
---Returns the current gear group
-- @return string group group
-- @return boolean groupsAvailable groupsAvailable
function VehicleMotor:getGearGroupToDisplay(isDashboard)
local gearGroupName, available = "N", false
if self.backwardGears or self.forwardGears then
if self.gearGroups ~= nil then
if self.activeGearGroupIndex > 0 then
local gearGroup = self.gearGroups[self.activeGearGroupIndex]
if gearGroup ~= nil then
if isDashboard then
gearGroupName = gearGroup.dashboardName or gearGroup.name
else
gearGroupName = gearGroup.name
end
end
end
available = true
end
end
return gearGroupName, available
end
---Reads current gear data from stream
function VehicleMotor:readGearDataFromStream(streamId)
self.currentDirection = streamReadUIntN(streamId, 2) - 1
if streamReadBool(streamId) then
local gear = streamReadUIntN(streamId, 6)
local changingGear = streamReadBool(streamId)
if streamReadBool(streamId) then
self.currentGears = self.forwardGears
else
self.currentGears = self.backwardGears
end
local activeGearGroupIndex
if self.gearGroups ~= nil then
activeGearGroupIndex = streamReadUIntN(streamId, 5)
end
if gear ~= self.gear then
if changingGear and self.gear ~= 0 then
self.lastGearChangeTime = g_time
end
self.gear = changingGear and 0 or gear
self.targetGear = gear
local directionMultiplier = self.directionChangeUseGear and self.currentDirection or 1
SpecializationUtil.raiseEvent(self.vehicle, "onGearChanged", self.gear * directionMultiplier, self.targetGear * directionMultiplier, 0)
end
if activeGearGroupIndex ~= self.activeGearGroupIndex then
self.activeGearGroupIndex = activeGearGroupIndex
SpecializationUtil.raiseEvent(self.vehicle, "onGearGroupChanged", self.activeGearGroupIndex, self.groupType == VehicleMotor.TRANSMISSION_TYPE.DEFAULT and self.groupChangeTime or 0)
end
end
end
---Writes current gear data to stream
function VehicleMotor:writeGearDataToStream(streamId)
streamWriteUIntN(streamId, math.sign(self.currentDirection) + 1, 2)
if streamWriteBool(streamId, self.backwardGears ~= nil or self.forwardGears ~= nil) then
streamWriteUIntN(streamId, self.targetGear, 6)
streamWriteBool(streamId, self.targetGear ~= self.gear)
streamWriteBool(streamId, self.currentGears == self.forwardGears)
if self.gearGroups ~= nil then
streamWriteUIntN(streamId, self.activeGearGroupIndex, 5)
end
end
end
---Sets last motor rpm
-- @param float lastRpm new last motor rpm
function VehicleMotor:setLastRpm(lastRpm)
local oldMotorRpm = self.lastMotorRpm
self.lastRealMotorRpm = lastRpm
local interpolationSpeed = 0.05
-- fast rpm drop for power shift transmissions to have a clear audible drop
if self.gearType == VehicleMotor.TRANSMISSION_TYPE.POWERSHIFT and (g_time - self.lastGearChangeTime) < 200 then
interpolationSpeed = 0.2
end
self.lastMotorRpm = self.lastMotorRpm * (1 - interpolationSpeed) + self.lastRealMotorRpm * interpolationSpeed
-- calculate turbo speed scale depending on rpm and motor load
local rpmPercentage = (self.lastMotorRpm - math.max(self.lastPtoRpm or self.minRpm, self.minRpm)) / (self.maxRpm - self.minRpm)
local targetTurboRpm = rpmPercentage * self:getSmoothLoadPercentage()
self.lastTurboScale = self.lastTurboScale * 0.95 + targetTurboRpm * 0.05
if self.lastAcceleratorPedal == 0 or (self.minGearRatio == 0 and self.autoGearChangeTime > 0) then
self.blowOffValveState = self.lastTurboScale
else
self.blowOffValveState = 0
end
self.constantRpmCharge = 1 - math.min(math.abs(self.lastMotorRpm - oldMotorRpm) * 0.15, 1)
end
---Returns the last applied torque to the motor
-- @return float appliedTorque torque [kN]
function VehicleMotor:getMotorAppliedTorque()
return self.motorAppliedTorque
end
---Returns the last applied external torque (torque used by external power consumers like the PTO)
-- @return float externalTorque external torque [kN]
function VehicleMotor:getMotorExternalTorque()
return self.motorExternalTorque
end
---Returns the last total available motor torque
-- @return float torque external torque [kN]
function VehicleMotor:getMotorAvailableTorque()
return self.motorAvailableTorque
end
---Returns equalized motor rpm
-- @return float equalizedMotorRpm equalized motor rpm
function VehicleMotor:getEqualizedMotorRpm()
return self.equalizedMotorRpm
end
---Sets equalized motor rpm
-- @param float equalizedMotorRpm equalized motor rpm
function VehicleMotor:setEqualizedMotorRpm(rpm)
self.equalizedMotorRpm = rpm
self:setLastRpm(rpm)
end
---Returns pto motor rpm ratio
-- @return float ptoMotorRpmRatio pto motor rpm ratio
function VehicleMotor:getPtoMotorRpmRatio()
return self.ptoMotorRpmRatio
end
---Sets the virtual external torque multiplicator
-- @return float externalTorqueVirtualMultiplicator virtual external torque multiplicator
function VehicleMotor:setExternalTorqueVirtualMultiplicator(externalTorqueVirtualMultiplicator)
self.externalTorqueVirtualMultiplicator = externalTorqueVirtualMultiplicator or 1
end
---Returns non clamped motor rpm
-- @return float nonClampedMotorRpm non clamped motor rpm
function VehicleMotor:getNonClampedMotorRpm()
return self.motorRotSpeed * 30 / math.pi
end
---Returns non clamped motor rpm
-- @return float nonClampedMotorRpm non clamped motor rpm
function VehicleMotor:getMotorRotSpeed()
return self.motorRotSpeed
end
---Returns clutch rpm
-- @return float clutchRpm clutch rpm
function VehicleMotor:getClutchRotSpeed()
return self.differentialRotSpeed * self.gearRatio
end
---Returns torque curve
-- @return table torqueCurve torque curve
function VehicleMotor:getTorqueCurve()
return self.torqueCurve
end
---Returns torque of the motor at the current rpm with the given accelerator pedal
-- @param float acceleration acceleration
-- @return float torque torque
function VehicleMotor:getTorque(acceleration)
-- Note: the torque curve is undefined outside the min/max rpm range. Clamping makes the curve flat at the outside range
local torque = self:getTorqueCurveValue(math.clamp(self.motorRotSpeed * 30/math.pi, self.minRpm, self.maxRpm))
torque = torque * math.abs(acceleration)
return torque
end
---Returns torque of the motor at the given rpm
-- @param float rpm rpm
-- @return float torque torque
function VehicleMotor:getTorqueCurveValue(rpm)
local damage = 1 - (self.vehicle:getVehicleDamage() * VehicleMotor.DAMAGE_TORQUE_REDUCTION)
return self:getTorqueCurve():get(rpm) * damage
end
---
function VehicleMotor:getTorqueAndSpeedValues()
local rotationSpeeds = {}
local torques = {}
for _,v in ipairs(self:getTorqueCurve().keyframes) do
table.insert(rotationSpeeds, v.time*math.pi/30)
table.insert(torques, self:getTorqueCurveValue(v.time))
end
return torques, rotationSpeeds
end
---Returns maximum forward speed
-- @return float maxForwardSpeed maximum forward speed
function VehicleMotor:getMaximumForwardSpeed()
return self.maxForwardSpeed
end
---Returns maximum backward speed
-- @return float maxBackwardSpeed maximum backward speed
function VehicleMotor:getMaximumBackwardSpeed()
return self.maxBackwardSpeed
end
---Returns physical maximum forward speed
-- @return float physicalMaxForwardSpeed physical maximum forward speed
function VehicleMotor:calculatePhysicalMaximumForwardSpeed()
return VehicleMotor.calculatePhysicalMaximumSpeed(self.minForwardGearRatio, self.forwardGears, self.maxRpm)
end
---Returns physical maximum backward speed
-- @return float physicalMaxBackwardSpeed physical maximum backward speed
function VehicleMotor:calculatePhysicalMaximumBackwardSpeed()
return VehicleMotor.calculatePhysicalMaximumSpeed(self.minBackwardGearRatio, self.backwardGears or self.forwardGears, self.maxRpm)
end
---Returns physical maximum speed
-- @param float minGearRatio min gear ratio
-- @param table gears gears
-- @param integer maxRpm max rpm
-- @return float physicalMaxSpeed physical maximum speed
function VehicleMotor.calculatePhysicalMaximumSpeed(minGearRatio, gears, maxRpm)
local minRatio
if minGearRatio ~= nil then
minRatio = minGearRatio
elseif gears ~= nil then
minRatio = math.huge
for _, gear in pairs(gears) do
minRatio = math.min(minRatio, gear.ratio)
end
else
printCallstack()
return 0
end
return maxRpm * math.pi / (30 * minRatio)
end
---Update the state of the motor (sync with physics simulation)
-- @param float dt time since last call in ms
function VehicleMotor:update(dt)
local vehicle = self.vehicle
if next(vehicle.spec_motorized.differentials) ~= nil and vehicle.spec_motorized.motorizedNode ~= nil then
local lastMotorRotSpeed = self.motorRotSpeed
local lastDiffRotSpeed = self.differentialRotSpeed
self.motorRotSpeed, self.differentialRotSpeed, self.gearRatio = getMotorRotationSpeed(vehicle.spec_motorized.motorizedNode)
if self.gearShiftMode ~= VehicleMotor.SHIFT_MODE_MANUAL_CLUTCH then
-- dynamically adjust the max gear ratio while starting in a gear and have not reached the min. differential speed
-- this simulates clutch slipping and allows a smooth acceleration
if (self.backwardGears or self.forwardGears) and self.gearRatio ~= 0 and self.maxGearRatio ~= 0 then
if self.lastAcceleratorPedal ~= 0 then
local minDifferentialSpeed = self.minRpm / math.abs(self.maxGearRatio) * math.pi / 30
if math.abs(self.differentialRotSpeed) < minDifferentialSpeed * 0.75 then
self.clutchSlippingTimer = self.clutchSlippingTime
self.clutchSlippingGearRatio = self.gearRatio
else
self.clutchSlippingTimer = math.max(self.clutchSlippingTimer - dt, 0)
end