forked from sperrgebiet/FS19_VehicleExplorer
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathVehicleSort.lua
2004 lines (1731 loc) · 77.4 KB
/
VehicleSort.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
-- VehicleSort.lua for FS19
-- Author: sperrgebiet
-- Please see https://github.com/sperrgebiet/FS19_VehicleExplorer for additional information, credits, issues and everything else
VehicleSort = {};
VehicleSort.eventName = {};
VehicleSort.ModName = g_currentModName;
VehicleSort.ModDirectory = g_currentModDirectory;
VehicleSort.Version = "0.9.3.9";
VehicleSort.debug = fileExists(VehicleSort.ModDirectory ..'debug');
VehicleSort.firstRun = true;
print(string.format('VehicleSort v%s - DebugMode %s)', VehicleSort.Version, tostring(VehicleSort.debug)));
VehicleSort.bgTransDef = 0.8;
VehicleSort.txtSizeDef = 2;
VehicleSort.infoYStart = 0.8;
VehicleSort.listAlignment = 2; -- 1 = Left, 2 = Center, 3 = Right)
VehicleSort.showImgMaxImp = 9;
VehicleSort.showInfoMaxImpl = 9;
VehicleSort.showImplementsMax = 3;
-- Integration environment for Tardis
envTardis = nil;
-- Trains get apparently handled differently for isTabbable and motorStatus, so we'll set that state on postMapload again
VehicleSort.loadTrainStatus = {};
VehicleSort.loadTrainStatus.entries = 0;
VehicleSort.loadItemsEnterable = {};
VehicleSort.config = { --Id -Order in configMenu
{'showTrain', true, 1}, -- 1 1
{'showCrane', false, 2}, -- 2 2
{'showBrand', false, 3}, -- 3 3
{'showHorsepower', true, 4}, -- 4 4
{'showNames', true, 8}, -- 5 8
{'showFillLevels', true, 5}, -- 6 5
{'showPercentages', true, 6}, -- 7 6
{'showEmpty', false, 7}, -- 8 7
{'txtSize', VehicleSort.txtSizeDef, 16}, -- 9 16
{'bgTrans', VehicleSort.bgTransDef, 17}, -- 10 17
{'showSteerableImplements', true, 11}, -- 11 11
{'showImplements', true, 9}, -- 12 9
{'showHelp', true, 27}, -- 13 27
{'saveStatus', true, 24}, -- 14 24
{'showImg', true, 12}, -- 15 12
{'showInfo', true, 14}, -- 16 14
{'infoStart', VehicleSort.infoYStart, 21}, -- 17 21
{'infoBg', true, 19}, -- 18 19
{'imageBg', true, 20}, -- 19 20
{'listAlignment', VehicleSort.listAlignment, 22}, -- 20 22
{'cleanOnRepair', true, 23}, -- 21 23
{'integrateTardis', true, 25}, -- 22 25
{'enterVehonTeleport', true, 26}, -- 23 26
{'showImgMaxImp', VehicleSort.showImgMaxImp, 13}, -- 24 13
{'showInfoMaxImpl', VehicleSort.showInfoMaxImpl, 15}, -- 25 15
{'showImplementsMax', VehicleSort.showImplementsMax, 10}, -- 26 10
{'useTwoColoredList', true, 18}, -- 27 18
{'useVeExTabOrder', true, 28}, -- 28 28
};
VehicleSort.tColor = {}; -- text colours
VehicleSort.tColor.isParked = {0.5, 0.5, 0.5, 0.7}; -- grey
VehicleSort.tColor.locked = {1.0, 0.0, 0.0, 1.0}; -- red
VehicleSort.tColor.selected = {0.8879, 0.1878, 0.0037, 1.0}; -- orange
VehicleSort.tColor.standard = {1.0, 1.0, 1.0, 1.0}; -- white
VehicleSort.tColor.standard2 = {0.8228, 0.8388, 0.7304, 1.0}; -- eggcolor
VehicleSort.tColor.hired = {0.0, 0.5, 1.0, 1.0}; -- blue
VehicleSort.tColor.followme = {0.92, 0.31, 0.69, 1.0}; -- light pink
VehicleSort.tColor.self = {0.0, 1.0, 0.0, 1.0}; -- green
VehicleSort.tColor.motorOn = {0.9301, 0.7605, 0.0232, 1.0}; -- yellow
VehicleSort.keyCon = 'VeExConfig';
VehicleSort.selectedConfigIndex = 1;
VehicleSort.selectedRealConfigIndex = 1;
VehicleSort.selectedIndex = 1;
VehicleSort.selectedLock = false;
VehicleSort.showConfig = false;
VehicleSort.showVehicles = false;
VehicleSort.xmlAttrId = '#vsid';
VehicleSort.xmlAttrOrder = '#vsorder';
VehicleSort.xmlAttrParked = '#vsparked';
VehicleSort.Sorted = {};
VehicleSort.HiddenCount = 0;
--VehicleSort.dirtyState = false; -- Used to check if we have to sync the order with g_currentMission.vehicles
VehicleSort.orderedConfig = {}; -- It's just nicer to have the config list ordered
addModEventListener(VehicleSort);
function VehicleSort:dp(val, fun, msg) -- debug mode, write to log
if not VehicleSort.debug then
return;
end
if msg == nil then
msg = ' ';
else
msg = string.format(' msg = [%s] ', tostring(msg));
end
local pre = 'VehicleSort DEBUG:';
if type(val) == 'table' then
--if #val > 0 then
print(string.format('%s BEGIN Printing table data: (%s)%s(function = [%s()])', pre, tostring(val), msg, tostring(fun)));
DebugUtil.printTableRecursively(val, '.', 0, 3);
print(string.format('%s END Printing table data: (%s)%s(function = [%s()])', pre, tostring(val), msg, tostring(fun)));
--else
-- print(string.format('%s Table is empty: (%s)%s(function = [%s()])', pre, tostring(val), msg, tostring(fun)));
--end
else
print(string.format('%s [%s]%s(function = [%s()])', pre, tostring(val), msg, tostring(fun)));
end
end
function VehicleSort:prerequisitesPresent(specializations)
return true;
end
function VehicleSort:loadMap(name)
print("--- loading VehicleSort V".. VehicleSort.Version .. " | ModName " .. VehicleSort.ModName .. " ---");
FSBaseMission.registerActionEvents = Utils.appendedFunction(FSBaseMission.registerActionEvents, VehicleSort.RegisterActionEvents);
VehicleSort:initVS();
VehicleSort:loadConfig();
-- We'd get an error when we want to access the config menu and the data is not populated yet. Same as there is some functionality in corner cases where we
-- already need a populated Sorted list, hence we're going to pouplate it once here
for i = 1, #VehicleSort.config do
local val = {i, VehicleSort.config[i]};
table.insert(VehicleSort.orderedConfig, val);
end
table.sort(VehicleSort.orderedConfig, function(a, b) return a[2][3] < b[2][3] end)
end
function VehicleSort:prepareVeEx()
-- Primarily necessary so that the train status get set
-- But it also doesn't harm to have the list ready for tabbing and opening it for the first time
VehicleSort.Sorted = VehicleSort:getOrderedVehicles();
VehicleSort.firstRun = false;
end
function VehicleSort:onLoad(savegame)
end
function VehicleSort:onPostLoad(savegame)
if savegame ~= nil then
local xmlFile = savegame.xmlFile;
local key = savegame.key..".vehicleSort";
local orderId = getXMLInt(xmlFile, key.."#UserOrder");
if orderId ~= nil then
VehicleSort:dp(string.format('Loaded orderId {%d} for vehicleId {%d}', orderId, self.id), 'onPostLoad');
end
if self.spec_vehicleSort ~= nil then
self.spec_vehicleSort.id = self.id;
if orderId ~= nil then
self.spec_vehicleSort.orderId = orderId;
end
end
local isParked = Utils.getNoNil(getXMLBool(xmlFile, key.."#isParked"), false);
if isParked then
VehicleSort:dp(string.format('Set isParked {%s} for orderId {%d} / vehicleId {%d}', tostring(isParked), orderId, self.id), 'onPostLoad');
self:setIsTabbable(false);
end
-- For any reason trains get handled differently, and simply ignore what we set at this stage. So lets store the status to hande it later on.
if self.typeName == 'locomotive' then
if VehicleSort.loadTrainStatus[self.id] == nil then
VehicleSort.loadTrainStatus[self.id] = {};
VehicleSort.loadTrainStatus.entries = VehicleSort.loadTrainStatus.entries + 1;
end
VehicleSort.loadTrainStatus[self.id]['isParked'] = isParked;
--VehicleSort:dp(string.format('Added train isParked to loadTrainStatus. orderId {%d}, id {%d}', orderId, Utils.getNoNil(self.id, 0)));
end
end
end
function VehicleSort:onDelete()
VehicleSort:dp(string.format('Going to remove vehicle realId {%d}, userOrder {%d}', Utils.getNoNil(self.spec_vehicleSort.realId, 0), Utils.getNoNil(self.spec_vehicleSort.orderId, 0)));
--VehicleSort:dp(self);
if self.spec_vehicleSort ~= nil then
table.remove(VehicleSort.Sorted, self.spec_vehicleSort.orderId);
--We've to build the list again, as we've new realId's after a vehicle got removed
VehicleSort.dirtyState = true;
VehicleSort.Sorted = VehicleSort:getOrderedVehicles();
--VehicleSort:SyncSorted();
end
end
function VehicleSort:RegisterActionEvents(isSelected, isOnActiveVehicle)
local actions = {
"vsToggleList",
"vsLockListItem",
"vsMoveCursorUp",
"vsMoveCursorDown",
"vsMoveCursorUpFast",
"vsMoveCursorDownFast",
"vsChangeVehicle",
"vsShowConfig",
"vsTogglePark",
"vsRepair",
"vsTab",
"vsTabBack"
};
for _, action in pairs(actions) do
local actionMethod = string.format("action_%s", action);
local result, eventName = InputBinding.registerActionEvent(g_inputBinding, action, self, VehicleSort[actionMethod], false, true, false, true)
if result then
table.insert(VehicleSort.eventName, eventName);
g_inputBinding.events[eventName].displayIsVisible = VehicleSort.config[13][2];
end
end
end
function VehicleSort.registerEventListeners(vehicleType)
local functionNames = { "onLoad", "onPostLoad", "saveToXMLFile", "onDelete" };
for _, functionName in ipairs(functionNames) do
SpecializationUtil.registerEventListener(vehicleType, functionName, VehicleSort);
end
end
function VehicleSort:keyEvent(unicode, sym, modifier, isDown)
end
function VehicleSort:mouseEvent(posX, posY, isDown, isUp, button)
if VehicleSort:isActionAllowed() and ( isDown and button == Input.MOUSE_BUTTON_LEFT) then
VehicleSort.action_vsChangeVehicle();
end
if VehicleSort:isActionAllowed() and ( isDown and button == Input.MOUSE_BUTTON_RIGHT) then
VehicleSort.action_vsLockListItem();
end
if VehicleSort:isActionAllowed() and ( isDown and Input.isMouseButtonPressed(Input.MOUSE_BUTTON_WHEEL_UP)) then
VehicleSort.action_vsMoveCursorUp();
end
if VehicleSort:isActionAllowed() and ( isDown and Input.isMouseButtonPressed(Input.MOUSE_BUTTON_WHEEL_DOWN)) then
VehicleSort.action_vsMoveCursorDown();
end
end
function VehicleSort:saveToXMLFile(xmlFile, key)
VehicleSort:dp(string.format('key {%s}', key), 'saveToXMLFile');
if self.spec_vehicleSort ~= nil then
if self.spec_vehicleSort.orderId ~= nil then
setXMLInt(xmlFile, key.."#UserOrder", self.spec_vehicleSort.orderId);
end
if VehicleSort:isParked(self.spec_vehicleSort.realId) then
setXMLBool(xmlFile, key.."#isParked", true);
end
end
end
function VehicleSort:update()
-- Don't really like to add VeEx to update as it's not really necessary, but haven't found another solution to set the train motor&parked stated after load
if VehicleSort.firstRun then
VehicleSort:prepareVeEx();
end
--This should allow to have the default keybindings for Tab & Shift+Tab, while still using the ordered tabbing from VeEx, at least if it's activated
if VehicleSort.config[28][2] then
VehicleSort:overwriteDefaultTabBinding();
end
end
function VehicleSort:draw()
--VehicleSort:dp(string.format('showConfig [%s] & showVehicles [%s]', tostring(VehicleSort.showConfig), tostring(VehicleSort.showVehicles)));
if VehicleSort.showConfig or VehicleSort.showVehicles then
local dbgY = VehicleSort.dbgY;
VehicleSort.bgY = nil;
VehicleSort.bgW = nil;
VehicleSort.bgH = nil;
if VehicleSort.showConfig then
VehicleSort:drawConfig();
else
VehicleSort:drawList();
end
end
end
function VehicleSort:delete()
end
function VehicleSort:deleteMap()
end
-- Functions for actionEvents/inputBindings
function VehicleSort:action_vsToggleList(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("vsToggleList fires", "vsToggleList");
if envTardis == nil and VehicleSort.config[22][2] then
-- Integration with Tardis
local TardisName = "FS19_Tardis";
if g_modIsLoaded[TardisName] then
envTardis = getfenv(0)[TardisName];
print("VehicleExplorer: Tardis integration available");
end
end
if VehicleSort.showVehicles and not VehicleSort.showConfig then
VehicleSort.showVehicles = false;
VehicleSort.selectedLock = false;
else
VehicleSort.showVehicles = true;
if VehicleSort.showConfig then
VehicleSort.saveConfig();
end
VehicleSort.showConfig = false;
end
end
function VehicleSort:action_vsLockListItem(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("vsLockListItem fires", "vsLockListItem");
if VehicleSort.showVehicles then
if not VehicleSort.selectedLock and VehicleSort.selectedIndex > 0 then
VehicleSort.selectedLock = true;
elseif VehicleSort.selectedLock then
VehicleSort.selectedLock = false;
end
elseif VehicleSort.showConfig then
if VehicleSort:contains({9, 20}, VehicleSort.selectedRealConfigIndex) then
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] + 1;
if VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] > 3 then
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = 1;
end
elseif VehicleSort:contains({24, 25, 26}, VehicleSort.selectedRealConfigIndex) then
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] + 1;
if VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] > 9 then
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = 1;
end
elseif VehicleSort:contains({10, 17}, VehicleSort.selectedRealConfigIndex) then
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] + 0.1;
if VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] > 1 then
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = 0.0;
end
else
VehicleSort.config[VehicleSort.selectedRealConfigIndex][2] = not VehicleSort.config[VehicleSort.selectedRealConfigIndex][2];
end
end
end
function VehicleSort:action_vsMoveCursorUp(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsMoveCursorUp fires", "action_vsMoveCursorUp");
if VehicleSort.showVehicles then
if Input.isKeyPressed(KEY_lalt) then
VehicleSort:moveUp(3);
else
VehicleSort:moveUp(1);
end
elseif VehicleSort.showConfig then
VehicleSort:moveConfigUp();
end
end
function VehicleSort:action_vsMoveCursorDown(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsMoveCursorDown fires", "action_vsMoveCursorDown");
if VehicleSort.showVehicles then
VehicleSort:moveDown(1);
elseif VehicleSort.showConfig then
VehicleSort:moveConfigDown();
end
end
function VehicleSort:action_vsMoveCursorUpFast(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsMoveCursorUpFast fires", "action_vsMoveCursorUpFast");
if VehicleSort.showVehicles then
VehicleSort:moveUp(3);
end
end
function VehicleSort:action_vsMoveCursorDownFast(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsMoveCursorDownFast fires", "action_vsMoveCursorDownFast");
if VehicleSort.showVehicles then
VehicleSort:moveDown(3);
end
end
function VehicleSort:action_vsChangeVehicle(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsChangeVehicle fires", "action_vsChangeVehicle");
if VehicleSort.showVehicles then
local realVeh = g_currentMission.vehicles[VehicleSort.Sorted[VehicleSort.selectedIndex]];
if realVeh.getIsControlled and not realVeh:getIsControlled() then
VehicleSort:dp(string.format('VehicleSort.wasTeleportAction {%s}', tostring(VehicleSort.wasTeleportAction)));
if envTardis == nil or
(envTardis ~= nil and not VehicleSort.wasTeleportAction) or
(envTardis ~= nil and VehicleSort.wasTeleportAction and VehicleSort.config[23][2]) then
g_currentMission:requestToEnterVehicle(realVeh);
VehicleSort.wasTeleportAction = false;
elseif envTardis ~= nil then
VehicleSort.wasTeleportAction = false;
end
end
end
end
function VehicleSort:action_vsShowConfig(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsShowConfig fires", "action_vsShowConfig");
if VehicleSort.showVehicles and not VehicleSort.showConfig then
VehicleSort.showVehicles = false;
end
VehicleSort.showConfig = not VehicleSort.showConfig;
VehicleSort:saveConfig();
--Directly set the displayIsVisible for the F1 help menu
if VehicleSort.config[13][2] then
VehicleSort:setHelpVisibility(VehicleSort.eventName, true)
--If Tardis integration is available we'll also do the same for it
if envTardis ~= nil and #Tardis.eventName > 0 then
VehicleSort:setHelpVisibility(Tardis.eventName, true)
end
else
VehicleSort:setHelpVisibility(VehicleSort.eventName, false)
if envTardis ~= nil and #Tardis.eventName > 0 then
VehicleSort:setHelpVisibility(Tardis.eventName, false)
end
end
InputBinding:notifyEventChanges();
end
function VehicleSort:action_vsTogglePark(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp("action_vsTogglePark fires", "action_vsTogglePark");
if VehicleSort.showVehicles then
VehicleSort:toggleParkState(VehicleSort.selectedIndex);
end
end
function VehicleSort:action_vsRepair(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp(string.format('action_vsRepair fires - VehicleSort.showVehicles {%s}', tostring(VehicleSort.showVehicles)), "action_vsRepair");
if VehicleSort.showVehicles then
local infoText = "";
VehicleStatus:RepairVehicleWithImplements(VehicleSort.Sorted[VehicleSort.selectedIndex]);
infoText = g_i18n.modEnvironments[VehicleSort.ModName].texts.RepairDone;
if VehicleSort.config[21][2] then
VehicleStatus:CleanVehicleWithImplements(VehicleSort.Sorted[VehicleSort.selectedIndex]);
infoText = g_i18n.modEnvironments[VehicleSort.ModName].texts.RepairCleaningDone;
end
g_currentMission:showBlinkingWarning(infoText, 2000);
end
end
function VehicleSort:action_vsTab(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp(string.format('action_vsTab fires - VehicleSort.showVehicles {%s}', tostring(VehicleSort.showVehicles)), "action_vsTab");
VehicleSort:tabVehicle();
end
function VehicleSort:action_vsTabBack(actionName, keyStatus, arg3, arg4, arg5)
VehicleSort:dp(string.format('action_vsTabBack fires - VehicleSort.showVehicles {%s}', tostring(VehicleSort.showVehicles)), "action_vsTabBack");
VehicleSort:tabVehicle(true);
end
--
-- VehicleSort specific functions
--
function VehicleSort:calcPercentage(curVal, maxVal)
local per = curVal / maxVal * 100;
return (math.floor(per * 10)/10);
end
function VehicleSort:drawConfig()
local xPos = VehicleSort.tPos.x;
local yPos = VehicleSort.tPos.y;
local size = VehicleSort:getTextSize();
local y = VehicleSort.tPos.y;
local txtOn = g_i18n.texts.ui_on;
local txtOff = g_i18n.texts.ui_off;
local texts = {};
VehicleSort.bgW = (VehicleSort.tPos.columnWidth * 2 )+ VehicleSort.tPos.padSides + getTextWidth(size, txtOff); --For config we can use wider columns
-- We render the heading seperately to have it centered, despite the user config for the list
local headingY = VehicleSort.tPos.y + size + (VehicleSort.tPos.padHeight * 6);
local txt = g_i18n.modEnvironments[VehicleSort.ModName].texts.configHeadline;
setTextAlignment(VehicleSort.tPos.alignmentC);
setTextColor(unpack(VehicleSort.tColor.standard));
renderText(VehicleSort.tPos.x, headingY, size + VehicleSort.tPos.sizeIncr, tostring(txt)); -- x, y, size, txt
setTextAlignment(VehicleSort.tPos.alignmentL);
--VehicleSort:dp(orderedConfig, 'drawConfig');
-- And now the rest of our config
for k, v in ipairs(VehicleSort.orderedConfig) do
local clr = VehicleSort.tColor.standard;
if k == VehicleSort.selectedConfigIndex then
clr = VehicleSort.tColor.selected;
end
local rText = g_i18n.modEnvironments[VehicleSort.ModName].texts[VehicleSort.config[v[1]][1]];
local state = VehicleSort.config[v[1]][2];
if VehicleSort:contains({9, 24, 25, 26}, v[1]) then --txtSize, showImgMaxImp, showInfoMaxImpl, showImplementsMax as int
state = string.format('%d', state);
elseif VehicleSort:contains({10, 17}, v[1]) then --bgTransparency, VehicleSort.infoYStart as float
state = string.format('%.1f', state);
elseif v[1] == 20 then -- List text alignment
if state == 1 then
state = g_i18n.modEnvironments[VehicleSort.ModName].texts.left;
elseif state == 2 then
state = g_i18n.modEnvironments[VehicleSort.ModName].texts.center;
elseif state == 3 then
state = g_i18n.modEnvironments[VehicleSort.ModName].texts.right;
end
elseif state then
state = txtOn;
else
state = txtOff;
end
table.insert(texts, {xPos - (VehicleSort.bgW / 2) + VehicleSort.tPos.padSides, yPos, size, clr, rText}); --config definition line
table.insert(texts, {xPos - (VehicleSort.bgW / 2) + (VehicleSort.tPos.columnWidth * 2), yPos, size, clr, state}); --config value
yPos = yPos - size - VehicleSort.tPos.spacing;
end
VehicleSort.bgY = yPos;
VehicleSort.bgH = (y - yPos) + size + VehicleSort.tPos.yOffset + VehicleSort.tPos.padHeight;
if VehicleSort.bgY ~= nil and VehicleSort.bgW ~=nil and VehicleSort.bgH ~= nil then
VehicleSort:renderBg(VehicleSort.bgX, VehicleSort.bgY, VehicleSort.bgW, VehicleSort.bgH);
end;
setTextBold(false);
for k, v in ipairs(texts) do
setTextColor(unpack(v[4]))
renderText(v[1], v[2], v[3], tostring(v[5]));
end
setTextColor(unpack(VehicleSort.tColor.standard));
--Show the last selected vehicle for info/image position & BG option
if VehicleSort:contains({17, 18, 19, 24, 25}, VehicleSort.selectedRealConfigIndex) then
if g_currentMission.vehicles[VehicleSort.Sorted[VehicleSort.selectedIndex]] ~= nil then
VehicleSort:drawInfobox(VehicleSort.Sorted[VehicleSort.selectedIndex]);
VehicleSort:drawStoreImage(VehicleSort.Sorted[VehicleSort.selectedIndex]);
end
end
end
function VehicleSort:drawList()
VehicleSort.Sorted = VehicleSort:getOrderedVehicles();
if VehicleSort.HiddenCount == #VehicleSort.Sorted then
VehicleSort:showNoVehicles();
VehicleSort.showVehicles = false;
return false;
end
--VehicleSort:dp(vehList, 'drawList', 'vehList');
local cnt = #VehicleSort.Sorted;
if cnt == 0 then
return;
end
setTextBold(true); -- for width checks, to compensate for increased width when the line is bold
local yPos = VehicleSort.tPos.y;
local bgPosY = yPos;
local size = VehicleSort.getTextSize();
local y = VehicleSort.tPos.y;
local txt = g_i18n.modEnvironments[VehicleSort.ModName].texts.vs_title;
local texts = {};
local bold = false;
local minBgW = 0;
VehicleSort.bgY = y - VehicleSort.tPos.spacing;
VehicleSort.bgW = getTextWidth(size, txt) + VehicleSort.tPos.padSides; --Background width will be dynamically adjusted later on. Just a value to get started with
-- We render the heading seperately to have it centered, despite the user config for the list
local headingY = VehicleSort.tPos.y + size + (VehicleSort.tPos.padHeight * 6);
setTextAlignment(VehicleSort.tPos.alignmentC);
setTextColor(unpack(VehicleSort.tColor.standard));
renderText(VehicleSort.tPos.x, headingY, size + VehicleSort.tPos.sizeIncr, tostring(txt)); -- x, y, size, txt
-- Now set the list alignment based on the config
if VehicleSort.config[20][2] == 1 then
setTextAlignment(VehicleSort.tPos.alignmentL);
elseif VehicleSort.config[20][2] == 3 then
setTextAlignment(VehicleSort.tPos.alignmentR);
else
setTextAlignment(VehicleSort.tPos.alignmentC);
end
--Just used to figure out if we'll have multiple columns, hence we've to loop through the amount of vehicles to get the total height of the table
--This assumes that there is just one line/vehicle. But it's a rough guess though
local chk = yPos + size + VehicleSort.tPos.spacing;
local chkColNum = 1;
--Min distance to the bottom of the screen
local minY = ((4 * (size + VehicleSort.tPos.spacing)) + VehicleSort.tPos.padHeight);
for i = 1, cnt do --loop through lines to see if there will be multiple columns needed
if not VehicleSort:isHidden(VehicleSort.Sorted[i]) then
chk = chk - size - VehicleSort.tPos.spacing;
if chk < minY then
chkColNum = chkColNum + 1;
chk = yPos + size + VehicleSort.tPos.spacing; --For a new colum we've to reset this
end
end
end
local colNum = 1; --For multiple columns this counter gets increased
--VehicleSort:dp(string.format('chk {%f} | check for chk {%f} | minY {%f}', chk, size + VehicleSort.tPos.spacing + VehicleSort.tPos.padHeight, minY));
--VehicleSort:dp(string.format('minY {%s} - chk {%s} - chkColNum {%s}', minY, chk, chkColNum));
-- Calc our maxTxtW based on the expected number of columns.
--As we support just three columns we do the calc based on 3. In case we don't show a infobox we can add the space of one additional column
local maxTxtW = 0;
if VehicleSort.config[16][2] then
maxTxtW = (VehicleSort.tPos.columnWidth - VehicleSort.tPos.padSides) * (3 / chkColNum);
else
maxTxtW = (VehicleSort.tPos.columnWidth - VehicleSort.tPos.padSides) * (3 / chkColNum) + VehicleSort.tPos.columnWidth;
end
--VehicleSort:dp(string.format('columnWidth {%s} - maxTxtW {%s} - chkColNum {%s}', VehicleSort.tPos.columnWidth, maxTxtW, chkColNum));
for i = 1, cnt do
local realId = VehicleSort.Sorted[i];
if not VehicleSort:isHidden(realId) then
local clr = VehicleSort:getTextColor(i, realId);
local fullNameTable = VehicleSort:getFullVehicleName(realId);
txt = table.concat(fullNameTable);
-- Check if the line is not longer as our max txtLength, otherwise split it up to multiple lines
-- We don't have to care about the MaxNumImplements anymore, as we already consider that in getFullVehicleName
local multiLine = {};
if getTextWidth(size, txt) > maxTxtW then
while #fullNameTable > 0 do
local line = ''
while (fullNameTable[1] ~= nil) and (getTextWidth(size, line) < maxTxtW) do
line = line .. fullNameTable[1];
table.remove(fullNameTable, 1);
end
table.insert(multiLine, line);
end
-- Otherwise we add the lines twice
txt = '';
end
bold = VehicleSort:isControlled(realId) and (not g_currentMission.missionDynamicInfo.isMultiplayer or VehicleSort:getControllerName(realId) == g_gameSettings.nickname);
if string.len(txt) > 0 then
table.insert(texts, {colNum, yPos, size, bold, clr, txt});
yPos = yPos - size - VehicleSort.tPos.spacing;
-- To find our proper background width and the position of the columns we've to keep track of the longest text
VehicleSort.bgW = math.max(VehicleSort.bgW, getTextWidth(size, txt) + VehicleSort.tPos.padSides);
end
-- And for multiline entries we've to add multiple entries to our texts table
if #multiLine > 0 then
for k, v in ipairs(multiLine) do
if string.len(v) > 0 then
table.insert(texts, {colNum, yPos, size, bold, clr, v});
yPos = yPos - size - VehicleSort.tPos.spacing;
VehicleSort.bgW = math.max(VehicleSort.bgW, getTextWidth(size, v) + VehicleSort.tPos.padSides);
end
end
end
-- We don't want our background go further than necessary
bgPosY = math.min(bgPosY, yPos);
end
if yPos < minY then -- getting near bottom of screen, start a new column
yPos = VehicleSort.tPos.y;
colNum = colNum + 1;
end
end
setTextBold(false);
--Drawing our background
bgPosY = bgPosY - VehicleSort.tPos.spacing; -- bottom padding
VehicleSort.bgY = bgPosY;
VehicleSort.bgH = (y - bgPosY) + size + VehicleSort.tPos.sizeIncr + VehicleSort.tPos.yOffset + VehicleSort.tPos.spacing;
VehicleSort.bgW = VehicleSort.bgW * colNum;
if VehicleSort.bgY ~= nil and VehicleSort.bgW ~=nil and VehicleSort.bgH ~= nil then
VehicleSort:renderBg(VehicleSort.bgX, VehicleSort.bgY, VehicleSort.bgW, VehicleSort.bgH);
end
--We've to calculate our X based on each column.
local tblColWidth = VehicleSort.bgW / colNum;
local tPosXAligned = VehicleSort.tPos.x;
if VehicleSort.config[20][2] == 1 then
tPosXAligned = VehicleSort.tPos.x - (tblColWidth / 2) + VehicleSort.tPos.padSides;
elseif VehicleSort.config[20][2] == 3 then
tPosXAligned = VehicleSort.tPos.x + (tblColWidth / 2) - VehicleSort.tPos.padSides;
end
local colX = {};
colX[0] = tPosXAligned;
if colNum == 2 then
colX[1] = tPosXAligned - (tblColWidth / 2);
colX[2] = tPosXAligned + (tblColWidth / 2);
elseif colNum == 3 then
colX[1] = tPosXAligned - tblColWidth;
colX[2] = tPosXAligned;
colX[3] = tPosXAligned + tblColWidth;
else
--We just support 3 columsn. So this case is primarily for one column and as a 'catch all' which won't work but I don't care for now
colX[1] = tPosXAligned;
end
--VehicleSort:dp(colX, 'drawList');
for k, v in ipairs(texts) do
if type(v[4]) == 'boolean' then
setTextBold(v[4]);
end
setTextColor(unpack(v[5]));
local storColNum = v[1];
--VehicleSort:dp(storColNum, 'drawList', 'storcolNum');
renderText(colX[storColNum], v[2], v[3], tostring(v[6])); -- x, y, size, txt
end
setTextBold(false);
setTextColor(unpack(VehicleSort.tColor.standard));
if VehicleSort.config[15][2] then
VehicleSort:drawStoreImage(VehicleSort.Sorted[VehicleSort.selectedIndex]);
end
if VehicleSort.config[16][2] then
VehicleSort:drawInfobox(VehicleSort.Sorted[VehicleSort.selectedIndex])
end
end
function VehicleSort:getVehicles()
local allveh = g_currentMission.vehicles
local veh = {}
for k, v in ipairs(allveh) do
if not v.isDeleted then
if v.spec_vehicleSort ~= nil then
v.spec_vehicleSort.realId = k;
table.insert(veh, v);
-- Handle trains at this stage for first load, as we loop through all vehicles anyways
if VehicleSort:isTrain(k) and VehicleSort.loadTrainStatus.entries > 0 then
VehicleSort:handlePostloadTrains(k);
end
end
end
end
return veh;
end
function VehicleSort:getVehImplements(realId)
if g_currentMission.vehicles[realId].getAttachedImplements ~= nil then
if #g_currentMission.vehicles[realId]:getAttachedImplements() > 0 then
local allImp = {}
-- Credits to Tardis from FS17
local function addAllAttached(obj)
for _, imp in pairs(obj:getAttachedImplements()) do
addAllAttached(imp.object);
table.insert(allImp, imp);
end
end
addAllAttached(g_currentMission.vehicles[realId]);
return allImp;
else
return nil;
end
else
return nil;
end
end
-- We can't use getFullName for Attachments as that's causing lua callstacks once CP or a helper is used
-- Hence we build our own full name with the help of the store & brand manager
function VehicleSort:getAttachmentName(obj)
local val = '';
if VehicleSort.config[3][2] then
local brand = VehicleSort:getAttachmentBrand(obj);
if brand ~= nil then
val = val .. string.format('%s %s', brand, obj:getName());
else
val = val .. string.format('%s ', obj:getName());
end
else
val = val .. string.format('%s', obj:getName());
end
--VehicleSort:dp(string.format('val = {%s}', val), getAttachmentName);
return val;
end
-- Not using :getFullName, as it will throw lua call stacks for not getting the helper name when using CP
function VehicleSort:getAttachmentBrand(obj)
local storeItem = g_storeManager:getItemByXMLFilename(obj.configFileName);
if storeItem ~= nil then
local brand = g_brandManager:getBrandByIndex(storeItem.brandIndex);
if brand ~= nil then
return brand.title;
else
return 'Lizard';
end
end
end
function VehicleSort:getFillLevel(obj)
local fillLevel = 0;
local cap = 0;
local fillType = "";
if obj.getFillLevelInformation ~= nil then
local fillLevelTable = {};
obj:getFillLevelInformation(fillLevelTable);
for _,fillLevelVehicle in pairs(fillLevelTable) do
fillLevel = fillLevelVehicle.fillLevel;
cap = fillLevelVehicle.capacity;
fillType = g_fillTypeManager.fillTypes[fillLevelVehicle.fillType]['title'];
--VehicleSort:dp(string.format('FillLevel - realId {%f} - fillLevel {%f} - capacity {%f}', realId, fillLevel, cap), 'getFillLevel');
--VehicleSort:dp(string.format('fillType {%s} - fillTypeIndex {%s} - filltypeTitle {%s}', fillLevelVehicle.fillType, fillTypeIndex, fillType));
end
return fillLevel, cap, fillType;
end
end
function VehicleSort:getFillDisplay(obj, infoBox)
local ret = '';
if VehicleSort.config[6][2] or infoBox then -- Fill-Level-Display active?
local f, c, t = VehicleSort:getFillLevel(obj);
if not infoBox then t = ""; end; -- we use the same method for the list and the infobox. But the fillType should just be visible in the infobox
if VehicleSort.config[8][2] or f > 0 then -- Empty should be shown or is not empty
if c > 0 then -- Capacity more than zero
if infoBox then -- show more details in the infobox
ret = string.format('%d/%d (%d %%) %s', math.floor(f), c, VehicleSort:calcPercentage(f, c), t);
elseif VehicleSort.config[7][2] then -- Display as percentage
ret = string.format(' (%d %%) %s', VehicleSort:calcPercentage(f, c), t);
else -- Display as amount of total capacity
ret = string.format(' (%d/%d) %s', math.floor(f), c, t);
end
end
end
end
return ret;
end
function VehicleSort:getFullVehicleName(realId)
local nam = '';
local ret = {};
local tmpString = '(%s) ';
if VehicleSort:isParked(realId) then
nam = '[P] '; -- Prefix for parked (not part of tab list) vehicles
end
if g_currentMission.vehicles[realId] ~= nil and g_currentMission.vehicles[realId].getIsCourseplayDriving and g_currentMission.vehicles[realId]:getIsCourseplayDriving() then -- CoursePlay
nam = nam .. string.format(tmpString, g_i18n.modEnvironments[VehicleSort.ModName].texts.courseplay);
elseif (g_currentMission.vehicles[realId].getIsFollowMeActive and g_currentMission.vehicles[realId]:getIsFollowMeActive()) then --FollowMe
nam = nam .. string.format(tmpString, g_i18n.modEnvironments[VehicleSort.ModName].texts.followme);
elseif VehicleSort:isHired(realId) then
nam = nam .. string.format(tmpString, g_i18n.modEnvironments[VehicleSort.ModName].texts.hired);
elseif VehicleSort:isControlled(realId) then
local con = VehicleSort:getControllerName(realId);
if VehicleSort.config[5][2] and con ~= nil and con ~= 'Unknown' and con ~= '' then
nam = nam .. string.format(tmpString, con);
end
end
if VehicleSort:isTrain(realId) then
nam = nam .. VehicleSort:getName(realId, string.format('%s', g_i18n.modEnvironments[VehicleSort.ModName].texts.vs_train));
elseif VehicleSort:isCrane(realId) then
nam = nam .. VehicleSort:getName(realId, string.format('%s', g_i18n.modEnvironments[VehicleSort.ModName].texts.vs_crane));
elseif VehicleSort.config[3][2] then -- Show brand
nam = nam .. string.format('%s %s', VehicleSort:getBrandName(realId), VehicleSort:getName(realId));
else
--VehicleSort:dp(veh.spec_vehicleSort, 'getFullVehicleName', 'Table spec_vehicleSort');
nam = nam .. string.format('%s', VehicleSort:getName(realId));
end
-- Show horse power
if VehicleSort.config[4][2] then
local horsePower = VehicleSort:getHorsePower(realId);
if horsePower ~= nil then
nam = nam .. " (" .. horsePower .. string.format(' %s)', g_i18n.modEnvironments[VehicleSort.ModName].texts.horsePower);
end
end
table.insert(ret, nam .. VehicleSort:getFillDisplay(g_currentMission.vehicles[realId]));
if VehicleSort:getVehImplements(realId) ~= nil and VehicleSort.config[12][2] then
local implements = VehicleSort:getVehImplements(realId);
local maxCount = VehicleSort.config[26][2];
if #implements < maxCount then
maxCount = #implements;
end
local linkWord = string.format(' %s', g_i18n.modEnvironments[VehicleSort.ModName].texts.with);
for i=1, maxCount do
local imp = implements[i];
if (imp ~= nil and imp.object ~= nil) then
if i > 1 then
linkWord = "&";
end
table.insert(ret, string.format('%s %s%s ', linkWord, VehicleSort:getAttachmentName(imp.object), VehicleSort:getFillDisplay(imp.object)));
end
end
end
return ret;
end
function VehicleSort:getName(realId, sFallback)
nam = g_currentMission.vehicles[realId]:getName();
if nam == nil then
nam = obj.typeName;
end
if nam == nil or nam == '' then
return sFallback;
else
return nam;
end
end
function VehicleSort:getBrandName(realId)
--return g_currentMission.vehicles[realId]:getFullName(); --Problem is that getFullName also returns the helper name.
local storeItem = g_storeManager:getItemByXMLFilename(g_currentMission.vehicles[realId].configFileName);
if storeItem ~= nil then
local brand = g_brandManager:getBrandByIndex(storeItem.brandIndex);
if brand ~= nil then
return brand.title;
else
return 'Lizard';
end
end
end
function VehicleSort:getOrderedVehicles()
local ordered = {};
local unordered = {};
local orderedToOrder = {};
VehicleSort.HiddenCount = 0;
local vehList = VehicleSort:getVehicles();
-- We don't want to do everything all the time, unless we know that something has changed, like after a vehicle got deleted
-- TEST: Always return a Sorted list. Lets see if that helps us avoid the mixup with implements
--[[
if #VehicleSort.Sorted == #vehList and not VehicleSort.dirtyState then
--VehicleSort:dp("Sorted list seems to be up to date. No need to redo everything", "getOrderedVehicles");
return VehicleSort.Sorted;
end
]]
--VehicleSort:dp("Sorted list seems outdated. So doing the ordering again.", "getOrderedVehicles");
for _, veh in pairs(vehList) do
if veh.spec_vehicleSort.orderId ~= nil then
table.insert(orderedToOrder, {orderId=veh.spec_vehicleSort.orderId, realId=veh.spec_vehicleSort.realId} );
else
table.insert(unordered, veh.spec_vehicleSort.realId);
end
-- Keep track of hidden items, so that we're not showing an empty list
if VehicleSort:isHidden(veh.spec_vehicleSort.realId) then
VehicleSort.HiddenCount = VehicleSort.HiddenCount + 1;
end
end
-- Now order our temp table based on the actual orderId
table.sort(orderedToOrder, function(a,b) return a['orderId'] < b['orderId'] end)
-- And to avoid any holes in the order or dups we'll just add them to a new ordered table
for _, v in ipairs(orderedToOrder) do
table.insert(ordered, v['realId']);
end
local cntOrdered = #ordered;