File size: 80,187 Bytes
b6a38d7
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
1001
1002
1003
1004
1005
1006
1007
1008
1009
1010
1011
1012
1013
1014
1015
1016
1017
1018
1019
1020
1021
1022
1023
1024
1025
1026
1027
1028
1029
1030
1031
1032
1033
1034
1035
1036
1037
1038
1039
1040
1041
1042
1043
1044
1045
1046
1047
1048
1049
1050
1051
1052
1053
1054
1055
1056
1057
1058
1059
1060
1061
1062
1063
1064
1065
1066
1067
1068
1069
1070
1071
1072
1073
1074
1075
1076
1077
1078
1079
1080
1081
1082
1083
1084
1085
1086
1087
1088
1089
1090
1091
1092
1093
1094
1095
1096
1097
1098
1099
1100
1101
1102
1103
1104
1105
1106
1107
1108
1109
1110
1111
1112
1113
1114
1115
1116
1117
1118
1119
1120
1121
1122
1123
1124
1125
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148
1149
1150
1151
1152
1153
1154
1155
1156
1157
1158
1159
1160
1161
1162
1163
1164
1165
1166
1167
1168
1169
1170
1171
1172
1173
1174
1175
1176
1177
1178
1179
1180
1181
1182
1183
1184
1185
1186
1187
1188
1189
1190
1191
1192
1193
1194
1195
1196
1197
1198
1199
1200
1201
1202
1203
1204
1205
1206
1207
1208
1209
1210
1211
1212
1213
1214
1215
1216
1217
1218
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230
1231
1232
1233
1234
1235
1236
1237
1238
1239
1240
1241
1242
1243
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257
1258
1259
1260
1261
1262
1263
1264
1265
1266
1267
1268
1269
1270
1271
1272
1273
1274
1275
1276
1277
1278
1279
1280
1281
1282
1283
1284
1285
1286
1287
1288
1289
1290
1291
1292
1293
1294
1295
1296
1297
1298
1299
1300
1301
1302
1303
1304
1305
1306
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316
1317
1318
1319
1320
1321
1322
1323
1324
1325
1326
1327
1328
1329
1330
1331
1332
1333
1334
1335
1336
1337
1338
1339
1340
1341
1342
1343
1344
1345
1346
1347
1348
1349
1350
1351
1352
1353
1354
1355
1356
1357
1358
1359
1360
1361
1362
1363
1364
1365
1366
1367
1368
1369
1370
1371
1372
1373
1374
1375
1376
1377
1378
1379
1380
1381
1382
1383
1384
1385
1386
1387
1388
1389
1390
1391
1392
1393
1394
1395
1396
1397
1398
1399
1400
1401
1402
1403
1404
1405
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415
1416
1417
1418
1419
1420
1421
1422
1423
1424
1425
1426
1427
1428
1429
1430
1431
1432
1433
1434
1435
1436
1437
1438
1439
1440
1441
1442
1443
1444
1445
1446
1447
1448
1449
1450
1451
1452
1453
1454
1455
1456
1457
1458
1459
1460
1461
1462
1463
1464
1465
1466
1467
1468
1469
1470
1471
1472
1473
1474
1475
1476
1477
1478
1479
1480
1481
1482
1483
1484
1485
1486
1487
1488
1489
1490
1491
1492
1493
1494
1495
1496
1497
1498
1499
1500
1501
1502
1503
1504
1505
1506
1507
1508
1509
1510
1511
1512
1513
1514
1515
1516
1517
1518
1519
1520
1521
1522
1523
1524
1525
1526
1527
1528
1529
1530
1531
1532
1533
1534
1535
1536
1537
1538
1539
1540
1541
1542
1543
1544
1545
1546
1547
1548
1549
1550
1551
1552
1553
1554
1555
1556
1557
1558
1559
1560
1561
1562
1563
1564
1565
1566
1567
1568
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585
1586
1587
1588
1589
1590
1591
1592
1593
1594
1595
1596
1597
1598
1599
1600
1601
1602
1603
1604
1605
1606
1607
1608
1609
1610
1611
1612
1613
1614
1615
1616
1617
1618
1619
1620
1621
1622
1623
1624
1625
1626
1627
1628
1629
1630
1631
1632
1633
1634
1635
1636
1637
1638
1639
1640
1641
1642
1643
1644
1645
1646
1647
1648
1649
1650
1651
1652
1653
1654
1655
1656
1657
1658
1659
1660
1661
1662
1663
1664
1665
1666
1667
1668
1669
1670
1671
1672
1673
1674
1675
1676
1677
1678
1679
1680
1681
1682
1683
1684
1685
1686
1687
1688
1689
1690
1691
1692
1693
1694
1695
1696
1697
1698
1699
1700
1701
1702
1703
1704
1705
1706
1707
1708
1709
1710
1711
1712
1713
1714
1715
1716
1717
1718
1719
1720
1721
1722
1723
1724
1725
1726
1727
1728
1729
1730
1731
1732
1733
1734
1735
1736
1737
1738
1739
1740
1741
1742
1743
1744
1745
1746
1747
1748
1749
1750
1751
1752
1753
1754
1755
1756
1757
1758
1759
1760
1761
1762
1763
1764
1765
1766
1767
1768
1769
1770
1771
1772
1773
1774
1775
1776
1777
1778
1779
1780
1781
1782
1783
1784
1785
1786
1787
1788
1789
1790
1791
1792
1793
1794
1795
1796
1797
1798
1799
1800
1801
1802
1803
1804
1805
1806
1807
1808
1809
1810
1811
1812
1813
1814
1815
1816
1817
1818
1819
1820
1821
1822
1823
1824
1825
1826
1827
1828
1829
1830
1831
1832
1833
1834
1835
1836
1837
1838
1839
1840
1841
1842
1843
1844
1845
1846
1847
1848
1849
1850
1851
1852
1853
1854
1855
1856
1857
1858
1859
1860
1861
1862
1863
1864
1865
1866
1867
1868
1869
1870
1871
1872
1873
1874
1875
1876
1877
1878
1879
1880
1881
1882
1883
1884
1885
1886
1887
1888
1889
1890
1891
1892
1893
1894
1895
1896
1897
1898
1899
1900
1901
1902
1903
1904
1905
1906
1907
1908
1909
1910
1911
1912
1913
1914
1915
1916
1917
1918
1919
1920
1921
1922
1923
1924
1925
1926
1927
1928
1929
1930
1931
1932
1933
1934
1935
1936
1937
1938
1939
1940
1941
1942
1943
1944
1945
1946
1947
1948
1949
1950
1951
1952
1953
1954
1955
1956
1957
1958
1959
1960
1961
1962
1963
1964
1965
1966
1967
1968
1969
1970
1971
1972
1973
1974
1975
1976
1977
1978
1979
1980
1981
1982
1983
1984
1985
1986
1987
1988
1989
1990
1991
1992
1993
1994
1995
1996
1997
1998
1999
2000
2001
2002
2003
2004
2005
2006
2007
2008
2009
2010
2011
2012
2013
2014
2015
2016
2017
2018
2019
2020
2021
2022
2023
2024
2025
2026
2027
2028
2029
2030
2031
2032
2033
2034
2035
2036
2037
2038
2039
2040
2041
2042
2043
2044
2045
2046
2047
2048
2049
2050
2051
2052
2053
2054
2055
2056
2057
2058
2059
2060
2061
2062
2063
2064
2065
2066
2067
2068
2069
2070
2071
2072
2073
2074
2075
2076
2077
2078
2079
2080
2081
2082
2083
2084
2085
2086
2087
2088
2089
2090
2091
2092
2093
2094
2095
2096
2097
2098
2099
2100
2101
2102
2103
2104
2105
2106
2107
2108
2109
2110
2111
2112
2113
2114
2115
2116
2117
2118
2119
2120
2121
2122
2123
2124
2125
2126
2127
2128
2129
2130
2131
2132
2133
2134
2135
2136
2137
2138
2139
2140
2141
2142
2143
2144
2145
2146
2147
2148
2149
2150
2151
2152
2153
2154
2155
2156
2157
2158
2159
2160
2161
2162
2163
2164
2165
2166
2167
2168
2169
2170
2171
2172
2173
2174
2175
2176
2177
2178
2179
2180
2181
2182
2183
2184
2185
2186
2187
2188
2189
2190
2191
2192
2193
2194
2195
2196
2197
2198
2199
2200
2201
2202
2203
2204
2205
2206
2207
2208
2209
2210
2211
2212
2213
2214
2215
2216
2217
2218
2219
2220
2221
2222
2223
2224
2225
2226
2227
2228
2229
2230
2231
2232
2233
2234
2235
2236
2237
2238
2239
2240
2241
2242
2243
2244
2245
2246
2247
2248
2249
2250
2251
2252
2253
2254
2255
2256
2257
2258
2259
2260
2261
2262
2263
2264
2265
2266
2267
2268
2269
2270
2271
2272
2273
2274
2275
2276
2277
2278
2279
2280
2281
2282
2283
2284
2285
2286
2287
2288
2289
2290
2291
2292
2293
2294
2295
2296
2297
2298
2299
2300
2301
2302
2303
2304
2305
2306
2307
2308
2309
2310
2311
2312
2313
2314
2315
2316
2317
2318
2319
2320
2321
2322
2323
2324
2325
2326
2327
2328
2329
2330
2331
2332
2333
2334
2335
2336
2337
2338
2339
2340
2341
2342
2343
2344
2345
2346
2347
2348
2349
2350
2351
2352
2353
2354
2355
2356
2357
2358
2359
2360
2361
2362
2363
2364
2365
2366
2367
2368
2369
2370
2371
2372
2373
2374
2375
2376
2377
2378
2379
2380
2381
2382
2383
2384
2385
2386
2387
2388
2389
2390
2391
2392
2393
2394
2395
2396
2397
2398
2399
2400
2401
2402
2403
2404
2405
2406
2407
2408
2409
2410
2411
2412
2413
2414
2415
2416
2417
2418
2419
2420
2421
2422
2423
2424
2425
2426
2427
2428
2429
2430
2431
2432
2433
2434
2435
2436
2437
2438
2439
2440
2441
2442
2443
2444
2445
2446
2447
2448
2449
2450
2451
2452
2453
2454
2455
2456
2457
2458
2459
2460
2461
2462
2463
2464
2465
2466
2467
2468
2469
2470
2471
2472
2473
2474
2475
2476
2477
2478
2479
2480
2481
2482
2483
2484
2485
2486
2487
2488
2489
2490
2491
2492
2493
2494
2495
2496
2497
2498
2499
2500
2501
2502
2503
2504
2505
2506
2507
2508
2509
2510
2511
2512
2513
2514
2515
2516
2517
2518
2519
MapVar("g_UnitCombatBadgesEnabled", true)

GroundOrientOffsets = {
	Human = {
		point(40,-40) * const.SlabSizeX / 100,
		point(40, 40) * const.SlabSizeX / 100,
		point(-100, 0) * const.SlabSizeX / 100,
	},
	Crocodile = {
		point(100,-40) * const.SlabSizeX / 100,
		point(100, 40) * const.SlabSizeX / 100,
		point(-100, 0) * const.SlabSizeX / 100,
	},
	OneTile = {
		point(40,-40) * const.SlabSizeX / 100,
		point(40, 40) * const.SlabSizeX / 100,
		point(-40, 0) * const.SlabSizeX / 100,
	},
}

AppearanceObjectAME.flags.gofUnitLighting = true

local AnimationStyleUnits = { "Male", "Female", "Crocodile", "Hyena", "Hen", "AmbientLifeMarker" }

WeaponVisualClasses = { "WeaponVisual", "GrenadeVisual" }

local GetAnimationStyleUnitEntities = {
	Male = "Male",
	Female = "Female",
	Crocodile = "Animal_Crocodile",
	Hyena = "Animal_Hyena",
	Hen = "Animal_Hen",
}

function GetAnimationStyleUnitEntity(set)
	return GetAnimationStyleUnitEntities[set]
end

function GetAnimationStyleUnits()
	return AnimationStyleUnits
end

function Unit:GetAnimationStyleUnit()
	return self.species == "Human" and self.gender or self.species
end

function Unit:ApplyAppearance(appearance, force)
	AppearanceObject.ApplyAppearance(self, appearance, force)
	self.gender = self:GetGender()
	if self.Headshot then
		self:SetHeadshot(true)
	end
	if self.target_dummy then
		self.target_dummy:ApplyAppearance(self.Appearance)
	end
end

local maxStainsAtDetailLevel = {
	["Very Low"] = 2,
	["Low"] = 2,
	["Medium"] = 3,
	["High"] = 5,
}

function Unit:UpdateGasMaskVisibility()
	if self:GetItemInSlot("Head", "GasMaskBase") then
		AppearanceObject.EquipGasMask(self)
	else
		AppearanceObject.UnequipGasMask(self)
	end
end

function Unit:UpdateOutfit(appearance)
	appearance = appearance or self:ChooseAppearance()
	local appear_preset = appearance and AppearancePresets[appearance]
	if not appear_preset then
		appearance = self.spawner and self.spawner.Appearance or nil
	end
	
	self:StopAnimMomentHook()
	
	if appearance and appearance ~= self.Appearance then
		local anim = self:GetStateText()
		local phase = self:GetAnimPhase()
		self:ApplyAppearance(appearance)
		self:SetStateText(anim, const.eKeepComponentTargets)
		self:SetAnimPhase(1, phase)
	end

	self:FlushCombatCache()
	local weapons_set1 
	if IsSetpiecePlaying() and IsSetpieceActor(self) then
		weapons_set1 = self:GetEquippedWeapons("SetpieceWeapon")
	end
	if not weapons_set1 or #weapons_set1 == 0 then
		weapons_set1 = self:GetEquippedWeapons(self.current_weapon)
	end
	local weapons_set2 = self:GetEquippedWeapons(self.current_weapon == "Handheld A" and "Handheld B" or "Handheld A")
	local equipped_items
	if #weapons_set1 > 0 or #weapons_set2 > 0 then
		equipped_items = {
			weapons_set1[1] or false,
			weapons_set1[2] or false,
			weapons_set2[1] or false,
			weapons_set2[2] or false,
		}
	end
	self:ForEachAttach(WeaponVisualClasses, function(o, equipped_items)
		if o.weapon and not table.find(equipped_items, o.weapon) then
			DoneObject(o)
		end
	end, equipped_items)
	local item_scale = CheatEnabled("BigGuns") and 250 or 100
	for equip_index, item in ipairs(equipped_items) do
		local o = item and IsKindOfClasses(item, "Firearm", "MeleeWeapon", "HeavyWeapon") and item:GetVisualObj()
		if o then
			o.equip_index = equip_index
			o:SetScale(item_scale)
			if o ~= self.bombard_weapon then
				local parent = o:GetParent()
				if parent ~= self then
					self:Attach(o)
				end
			end
		end
	end
	self.anim_moment_fx_target = equipped_items and (equipped_items[1] and equipped_items[1].visual_obj or equipped_items[2] and equipped_items[2].visual_obj) or self:GetAttach("WeaponVisual")
	self:UpdateAttachedWeapons()

	self:UpdateGasMaskVisibility()
	Msg("OnUpdateItemsVisuals", self)

	self:SetContourOuterOccludeRecursive(true)
	self:SetHierarchyGameFlags(const.gofUnitLighting)
	self:StartAnimMomentHook()

	DeleteBadgesFromTargetOfPreset("CombatBadge", self)
	self.combat_badge = false
	self.ui_badge = false
	DeleteBadgesFromTargetOfPreset("NpcBadge", self)

	if not self:IsDead() and (GameState.entered_sector or IsCompetitiveGame() or g_TestExploration) and g_UnitCombatBadgesEnabled then
		local badge = CreateBadgeFromPreset("CombatBadge", self, self)
		self.combat_badge = badge
		self.ui_badge = badge.ui
		
		if self.ImportantNPC then
			CreateBadgeFromPreset("NpcBadge", { target = self, spot = self:GetInteractableBadgeSpot() or "Origin" }, self)
		end
	end

	self:UpdateModifiedAnim()
	self:UpdateMoveAnim()

	--local max_stains = maxStainsAtDetailLevel[EngineOptions.ObjectDetail] or 3	
	for i, stain in ipairs(self.stains) do
		--if i > max_stains then break end 
		stain:Apply(self)
	end
	if not IsRealTimeThread() and self:IsIdleCommand() and CurrentThread() ~= self.command_thread then
		--set command is a sync event and shouldn't be done in rtt
		--it happens during load session data due to cascade onsetwhatever calls
		if self.command ~= "Hang" and self.command ~= "Cower" then
			self:SetCommand("Idle")
		end
	end
end

function Unit:UpdatePreparedAttackAndOutfit()
	-- in case of using a prepared attack, check if it can still find its weapon, cancel otherwise
	self:FlushCombatCache() -- clear the cache so we don't get a weapon that is no longer equipped
	if self:HasPreparedAttack() then
		local params = g_Combat and self.combat_behavior_params or self.behavior_params or empty_table
		local action_id = params[1]
		local action = action_id and CombatActions[action_id]
		if not action or not action:GetAttackWeapons(self) then
			self:InterruptPreparedAttack()
			self:RemovePreparedAttackVisuals()
		end
	end
	
	self:UpdateOutfit()
end

function Unit:GetGender()
	local appearance = AppearancePresets[self.Appearance]
	if appearance and self.species == "Human" then
		if appearance.Body then
			if IsKindOf(g_Classes[appearance.Body], "CharacterBodyMale") then
				return "Male"
			end
		else
			if self:GetEntity() == "Male" then
				return "Male"
			end
		end
		return "Female"
	end
	return "N/A"
end

function Unit:SetState(anim, flags, crossfade, ...)
	AnimChangeHook.SetState(self, anim, flags or 0, not GameTimeAdvanced and 0 or crossfade, ...)
end

function Unit:SetAnim(channel, anim, flags, crossfade, ...)
	AnimChangeHook.SetAnim(self, channel, anim, flags or 0, not GameTimeAdvanced and 0 or crossfade, ...)
end

function Unit:RotateAnim(angle, anim)
	self:SetState(anim, const.eKeepComponentTargets, Presets.ConstDef.Animation.BlendTimeRotateOnSpot.value)
	self:SetIK("AimIK", false)
	local duration = self:TimeToAnimEnd()
	local start_angle = self:GetVisualOrientationAngle()
	local delta = AngleDiff(angle, start_angle)
	local step_angle = self:GetStepAngle()
	if delta * step_angle < 0 then
		if delta > 0 then
			delta = delta - 360 * 60
		else
			delta = delta + 360 * 60
		end
	end
	local steps = self.ground_orient and 20 or 2
	for i = 1, steps do
		local a = start_angle + i * delta / steps
		local t = duration * i / steps - duration * (i - 1) / steps
		self:SetOrientationAngle(a, t)
		Sleep(t)
	end
	if step_angle == 0 then
		local anim = self:ModifyWeaponAnim(self:GetIdleBaseAnim())
		self:SetState(anim, const.eKeepComponentTargets, -1)
		self:SetOrientationAngle(angle)
	end
end

function Unit:Rotate180(angle, anim)
	anim = self:ModifyWeaponAnim(anim)
	self:SetState(anim, const.eKeepComponentTargets, Presets.ConstDef.Animation.BlendTimeRotateOnSpot.value)
	self:SetIK("AimIK", false)
	local duration = self:TimeToAnimEnd()
	local start_angle = self:GetVisualOrientationAngle()
	local delta = AngleDiff(angle, start_angle)
	local step_angle = self:GetStepAngle()
	if delta * step_angle < 0 then
		if delta > 0 then
			delta = delta - 360 * 60
		else
			delta = delta + 360 * 60
		end
	end
	local steps = self.ground_orient and 20 or 2
	for i = 1, steps do
		local a = start_angle + i * delta / steps
		local t = duration * i / steps - duration * (i - 1) / steps
		self:SetOrientationAngle(a, t)
		Sleep(t)
	end
end

function Unit:AnimBlendingRotation(angle)
	local start_angle = self:GetVisualOrientationAngle()
	local angle_diff = AngleDiff(angle, start_angle)
	local abs_angle_diff = abs(angle_diff)
	if angle_diff == 0 then
		if self:TimeToAngleInterpolationEnd() > 0 then
			self:SetOrientationAngle(start_angle)
		end
	elseif abs_angle_diff < 15*60 then
		self:SetOrientationAngle(angle, 300)
		Sleep(300)
	else
		if abs_angle_diff > 150*60 then
			local anim = "turn_180"
			if IsValidAnim(self, anim) then
				self:Rotate180(angle, anim)
				return
			end
		end
		self:SetIK("AimIK", false)
		local anim1 = angle_diff < 0 and "turn_L_45" or "turn_R_45"
		local anim2 = angle_diff < 0 and "turn_L_135" or "turn_R_135"
		local destructor
		if abs_angle_diff <= 45*60 then
			self:SetAnim(1, anim1, const.eKeepComponentTargets, Presets.ConstDef.Animation.BlendTimeRotateOnSpot.value)
		elseif abs_angle_diff >= 135*60 then
			self:SetAnim(1, anim2, const.eKeepComponentTargets, Presets.ConstDef.Animation.BlendTimeRotateOnSpot.value)
		else
			destructor = true
			self:PushDestructor(function(self)
				self:ClearAnim(const.PathTurnAnimChnl)
			end)
			local weight2 = Clamp(abs_angle_diff - 45*60, 0, 90*60) * 100 / (90*60) -- 45 degrees -> 0, 135 degrees -> 100
			self:SetAnim(1, anim1, const.eKeepComponentTargets, -1, 1000, 100 - weight2)
			self:SetAnim(const.PathTurnAnimChnl, anim2, const.eKeepComponentTargets, -1, 1000, weight2)
		end
		local duration = self:TimeToMoment(1, "end") or self:TimeToAnimEnd()
		local steps = self.ground_orient and 20 or 2
		for i = 1, steps do
			local a = start_angle + i * angle_diff / steps
			local t = duration * i / steps - duration * (i - 1) / steps
			self:SetOrientationAngle(a, t)
			Sleep(t)
		end
		if destructor then
			self:PopAndCallDestructor()
		end
	end
end

function Unit:GetRotateAnim(angle_diff, base_idle)
	local prefix
	if not base_idle then
		base_idle = self:GetIdleBaseAnim(self.stance)
	end
	if string.ends_with(base_idle, "_Aim") then
		prefix = base_idle
	else
		prefix = string.match(base_idle, "(.*)_%w+$")
	end
	if not prefix then
		return
	end
	local take_cover_prefix = string.match(prefix, "(.*_)TakeCover")
	if take_cover_prefix then
		prefix = take_cover_prefix .. "Crouch"
	end
	local rotate_anim
	if abs(angle_diff) >= 150*60 then
		local anim = prefix .. "_Turn180"
		if IsValidAnim(self, anim) then
			rotate_anim = anim
		end
	end
	if not rotate_anim then
		local anim = prefix .. (angle_diff < 0 and "_TurnLeft" or "_TurnRight")
		if IsValidAnim(self, anim) then
			rotate_anim = anim
		end
	end
	if not rotate_anim then
		if string.ends_with(prefix, "_Aim") then
			local anim = string.sub(prefix, 1, -5) .. (angle_diff < 0 and "_TurnLeft" or "_TurnRight")
			if IsValidAnim(self, anim) then
				rotate_anim = anim
			end
		end
	end
	if rotate_anim then
		local anim_rotation_angle = self:GetStepAngle(rotate_anim)
		if anim_rotation_angle == 0 then
			StoreErrorSource(self, string.format("%s animation %s should have compansated rotation", self:GetEntity(), rotate_anim))
		end
	end
	return rotate_anim
end

function Unit:IdleRotation(angle, time)
	if not GameTimeAdvanced then
		time = 0
	else
		time = time or 300
	end
	if time > 0 then
		local start_angle = self:GetVisualOrientationAngle()
		local angle_diff = AngleDiff(angle, start_angle)
		local steps = self.ground_orient and 20 or 2
		for i = 1, steps do
			local a = start_angle + i * angle_diff / steps
			local t = time * i / steps - time * (i - 1) / steps
			self:SetOrientationAngle(a, t)
			Sleep(t)
		end
	else
		self:SetOrientationAngle(angle)
	end
end

function Unit:AnimatedRotation(angle, base_idle)
	if not GameTimeAdvanced then
		self:SetOrientationAngle(angle)
		return
	end
	local start_angle = self:GetVisualOrientationAngle()
	if angle == start_angle then
		return
	end
	local angle_diff = AngleDiff(angle, start_angle)
	if abs(angle_diff) < 45*60 then
		self:SetOrientationAngle(angle, 300)
		return
	end
	local move_style = GetAnimationStyle(self, self.cur_move_style)
	if move_style then
		local rotate_anim
		if abs(angle_diff) >= 30*60 then
			if angle_diff < 0 then
				rotate_anim = move_style.TurnOnSpot_Left
			else
				rotate_anim = move_style.TurnOnSpot_Right
			end
		end
		if rotate_anim and IsValidAnim(self, rotate_anim) then
			self:RotateAnim(angle, rotate_anim)
		else
			self:SetOrientationAngle(angle, 300)
		end
		return
	end
	if self.species ~= "Human" then
		self:AnimBlendingRotation(angle)
		return
	end
	if not base_idle then
		base_idle = self:GetIdleBaseAnim(self.stance)
	end
	local rotate_anim = self:GetRotateAnim(angle_diff, base_idle)
	if not rotate_anim then
		self:SetRandomAnim(base_idle, const.eKeepComponentTargets)
		self:IdleRotation(angle)
	elseif string.ends_with(rotate_anim, "180") then
		self:Rotate180(angle, rotate_anim)
	else
		self:RotateAnim(angle, rotate_anim)
	end
end

function Unit:PlayTransitionAnims(target_anim, angle)
	self:ReturnToCover()
	local cur_anim = self:GetStateText()
	if IsAnimVariant(cur_anim, target_anim) then
		return
	end
	if self.bombard_weapon then
		self:PreparedBombardEnd()
	end
	local cur_anim_style = GetAnimationStyle(self, self.cur_idle_style)
	if cur_anim_style and (cur_anim_style.End or "") ~= "" and not cur_anim_style:HasAnimation(target_anim) and cur_anim_style.Start ~= target_anim then
		self:SetState(cur_anim_style.End)
		Sleep(self:TimeToAnimEnd())
	end
	PlayTransitionAnims(self, target_anim, angle)
end

local WeaponAttachSpots = {
	Hand = { "Weaponr", "Weaponl" },
	Shoulder = { "Weaponrb", "Weaponlb" },
	Leg = { "Weaponrs", "Weaponls" },
	Mortar = { "Mortar", "Mortar" },
	LegKnife = { "Weaponrknife", "Weaponlknife" },
	ShoulderKnife = { "Weaponrbknife", "Weaponlbknife" },
}

BlockedSpotsVariants = {
	["Weaponrknife"] = "Weaponrs",
	["Weaponlknife"] = "Weaponls",
}

local HolsterAttachSpots = {
	Weaponrb = true,
	Weaponlb = true,
	Weaponrs = true,
	Weaponls = true,
	Weaponrknife = true,
	Weaponlknife = true,
	Weaponrbknife = true,
	Weaponlbknife = true,
}

local mkoffset = point(0,0,30*guic)
local WeaponAttachOffset =
{	-- spot - animation - offset
	Weaponr = {
		["mk_Standing_Aim_Forward"] = mkoffset,
		["mk_Standing_Aim_Down"] = mkoffset,
		["mk_Left_Aim_Start"] = mkoffset,
		["mk_Right_Aim_Start"] = mkoffset,
		["mk_Standing_Fire"] = mkoffset,
	},
}

local MortarDrawnAnims = {
	nw_Standing_MortarIdle = true,
	nw_Standing_MortarEnd = true,
	nw_Standing_MortarLoad = true,
	nw_Standing_MortarFire = true,
}

local function GetItemAttachSpot(unit, item, equip_index, holster, avatar)
	local slot
	if holster == nil then
		if equip_index ~= 1 and equip_index ~= 2 then
			holster = true
		else
			local anim = unit:GetStateText()
			if item.WeaponType == "Mortar" then
				if MortarDrawnAnims[anim] then
					return -- the mortar command will handle it
				end
				holster = true
			elseif (avatar or unit):HasStatusEffect("ManningEmplacement") then
				holster = true
			else
				local starts_with = string.starts_with
				if starts_with(anim, "nw_") then
					holster = true
				elseif starts_with(anim, "gr_") then
					holster = true
				elseif starts_with(anim, "civ_") then
					holster = true
				elseif starts_with(anim, "mk_") then
					if item.WeaponType ~= "MeleeWeapon" then
						holster = true
					end
				end
			end
		end
	end
	if holster then
		slot = item.HolsterSlot
		if not WeaponAttachSpots[slot] then
			slot = item.HandSlot == "OneHanded" and "Leg" or "Shoulder"
		end
		for i, component in pairs(item.components) do
			local visuals = (WeaponComponents[component] or empty_table).Visuals or empty_table
			local idx = table.find(visuals, "ApplyTo", item.class)
			if idx then
				local component_data = visuals[idx]
				local override_holster_slot = component_data.OverrideHolsterSlot
				if override_holster_slot == "Sholder" then
					slot = "Shoulder"
					break
				elseif override_holster_slot == "Leg" then
					slot = "Leg"
				end
			end
		end
	else
		slot = "Hand"
	end
	if slot == "Leg" then
		if IsKindOf(item, "MeleeWeapon") then
			slot = "LegKnife"
		end
	elseif slot == "Shoulder" then
		if IsKindOf(item, "MeleeWeapon") then
			slot = "ShoulderKnife"
		end
	end
	local spot = WeaponAttachSpots[slot][(equip_index == 2 or equip_index == 4) and 2 or 1]
	return spot
end

local function GetItemSpotAttachment(unit, spot, attach)
	local item = attach.weapon
	local attach_axis, attach_angle, attach_offset, attach_state
	if HolsterAttachSpots[spot] then
		if attach:HasSpot("Holster") then
			local offset = GetWeaponRelativeSpotPos(attach, "Holster")
			if offset then
				attach_offset = -offset
			end
			if IsKindOf(item, "RPG7") then
				attach_axis = axis_z
				attach_angle = 180*60
				attach_offset = RotateAxis(attach_offset, attach_axis, attach_angle)
			end
		end
	else
		local spot_offset_by_anim = WeaponAttachOffset[spot]
		local anim = unit:GetStateText()
		if spot_offset_by_anim then
			attach_offset = spot_offset_by_anim[anim]
		end
		if spot == "Weaponr" and IsKindOf(item, "MeleeWeapon") then
			if unit.gender == "Female" then
				if IsKindOf(item, "MacheteWeapon") then
					attach_axis = axis_x
					attach_angle = 180*60
				elseif anim == "mk_Standing_Aim_Forward"  then
					attach_axis = axis_x
					attach_angle = 90*60
					attach_offset = point(0*guic,-30*guic,0*guic)
				end
			elseif	 IsKindOf(item, "MacheteWeapon") then
				attach_offset = false
			end
		end
	end
	if attach_offset then
		attach_offset = MulDivRound(attach_offset, attach:GetScale(), 100)
	end
	if item and item.WeaponType == "Mortar" then
		attach_state = "packed"
	end
	return attach_axis or axis_x, attach_angle or 0, attach_offset, attach_state
end

local function AttachVisualItem(unit, spot, attach)
	local attach_axis, attach_angle, attach_offset, attach_state = GetItemSpotAttachment(unit, spot, attach)
	unit:Attach(attach, unit:GetSpotBeginIndex(spot))
	attach:SetAttachAxis(attach_axis or axis_x)
	attach:SetAttachAngle(attach_angle or 0)
	attach:SetAttachOffset(attach_offset or point30)
	if attach_state and attach:GetStateText() ~= attach_state then
		attach:SetState(attach_state)
	end
end

function GetAttackRelativePos(unit, anim, anim_phase, visual_weapon, weapon_attach_spot, attack_spot)
	anim_phase = anim_phase or unit:GetAnimMoment(anim, "hit") or 0
	local offset
	if visual_weapon then
		if not weapon_attach_spot then
			weapon_attach_spot = GetItemAttachSpot(unit, visual_weapon.weapon, visual_weapon.equip_index, false) or "Weaponr"
		end
		local spot_pos, spot_angle, spot_axis = unit:GetRelativeAttachSpotLoc(anim, anim_phase, unit, unit:GetSpotBeginIndex(weapon_attach_spot))
		local attach_axis, attach_angle, attach_offset = GetItemSpotAttachment(unit, weapon_attach_spot, visual_weapon)
		local weapon_axis, weapon_angle = ComposeRotation(attach_axis, attach_angle, spot_axis, spot_angle)
		local weapon_spot_offset = GetWeaponRelativeSpotPos(visual_weapon, attack_spot or "Muzzle")
		offset = spot_pos + (attach_offset or point30) + (weapon_spot_offset and RotateAxis(weapon_spot_offset, weapon_axis, weapon_angle) or point30)
	else
		if not attack_spot then
			attack_spot = unit.species == "Human" and "Weaponr" or "Head"
		end
		offset = unit:GetRelativeAttachSpotLoc(anim, anim_phase, unit, unit:GetSpotBeginIndex(attack_spot))
	end
	return offset
end

function GetAttackPos(unit, pos, axis, angle, aim_pos, anim, anim_phase, visual_weapon, weapon_attach_spot, attack_spot)
	local offset = GetAttackRelativePos(unit, anim, anim_phase, visual_weapon, weapon_attach_spot, attack_spot)
	if not pos:IsValidZ() then
		pos = pos:SetTerrainZ()
	end
	local spot_pos = pos + RotateAxis(offset, axis, angle)
	if aim_pos and aim_pos:IsValid() then
		local center = pos + RotateAxis(offset:SetX(0), axis, angle)
		spot_pos = center + SetLen(aim_pos - center, spot_pos:Dist(center))
	end
	return spot_pos
end

function OnMsg.CombatActionEnd(unit)
	unit.action_visual_weapon = false
end

function Unit:AttachActionWeapon(action)
	local visual_weapon
	if action and (action.id == "KnifeThrow" or string.starts_with(action.id, "ThrowGrenade")) then
		local attack_weapon = action:GetAttackWeapons(self)
		if attack_weapon then
			if attack_weapon.visual_obj and attack_weapon.visual_obj == self then
				visual_weapon = attack_weapon.visual_obj
			else
				for i, classname in ipairs(WeaponVisualClasses) do
					visual_weapon = self:GetAttach(classname, function(o, attack_weapon)
						return o.weapon == attack_weapon
					end, attack_weapon)
					if visual_weapon then
						break
					end
				end
				if not visual_weapon then
					if IsKindOf(attack_weapon, "Grenade") then
						visual_weapon = attack_weapon:GetVisualObj(self)
					elseif IsKindOfClasses(attack_weapon, "FirearmBase", "MeleeWeapon") or IsKindOf(attack_weapon, "UnarmedWeapon") then
						visual_weapon = attack_weapon:CreateVisualObj(self)
					end
				end
			end
		end
	end
	if visual_weapon then
		self.action_visual_weapon = visual_weapon
		visual_weapon.custom_equip = true
		if visual_weapon:GetParent() ~= self then
			visual_weapon:ClearHierarchyEnumFlags(const.efVisible)
			self:Attach(visual_weapon)
			self:UpdateAttachedWeapons()
		end
	elseif self.action_visual_weapon then
		self.action_visual_weapon = false
		self:UpdateAttachedWeapons()
	end
end

function AttachVisualItems(obj, attaches, crossfading, holster, avatar)
	if not attaches or #attaches == 0 then
		return
	end
	local hidden
	if IsKindOf(obj, "Unit") then
		local part_in_combat = g_Combat and obj.team and obj.team.side ~= "neutral"
		if not part_in_combat then
			if obj:GetCommandParam("weapon_anim_prefix") == "civ_" or obj:GetCommandParam("weapon_anim_prefix", "Idle") == "civ_" then
				hidden = true
			end
		end
		if obj.carry_flare then
			hidden = not obj.visible
		end
		-- make sure we're not hiding weapons setup by a setpiece
		for _, attach in ipairs(attaches) do
			if IsKindOfClasses(attach, WeaponVisualClasses) and attach.weapon and obj:GetItemSlot(attach.weapon) == "SetpieceWeapon" then
				hidden = false
				break
			end
		end
	end
	if hidden then
		for _, attach in ipairs(attaches) do
			attach:ClearHierarchyEnumFlags(const.efVisible)
		end
		return
	end
	local custom_equip = obj.action_visual_weapon
	if custom_equip or (IsKindOf(obj, "Unit") and obj.carry_flare) then
		holster = true
	end
	for i = #attaches, 1, -1 do
		local attach = attaches[i]
		if IsKindOfClasses(attach, WeaponVisualClasses) and attach.custom_equip and attach ~= custom_equip and (attach.equip_index or 5) > 4 then
			DoneObject(attach)
			table.remove(attaches, i)
		end
	end
	local wait_crossfade, grip_modify
	local spot_attach = {}
	table.sort(attaches, function(o1, o2) return o1.equip_index < o2.equip_index end)
	for _, attach in ipairs(attaches) do
		local item = attach.weapon
		local spot
		local cur_spot = attach:GetAttachSpotName()
		if attach == custom_equip then
			spot = WeaponAttachSpots["Hand"][1] or cur_spot
		elseif item then
			spot = GetItemAttachSpot(obj, item, attach.equip_index, holster, avatar) or cur_spot
		end
		if spot then
			if spot ~= cur_spot and crossfading and not HolsterAttachSpots[spot] then
				wait_crossfade = true
			else
				AttachVisualItem(obj, spot, attach)
			end
			spot_attach[spot] = attach -- prefer displaying the other set weapon attaches
			if item and item.class == "Gewehr98" and spot == "Weaponr" then
			--	grip_modify = true
			end
		end
	end
	local channel = const.AnimChannel_RightHandGrip
	if grip_modify then
		if GetStateName(obj:GetAnim(channel)) ~= "ar_RHand_AltGrip_Rifles" then
			obj:SetAnimMask(channel, "RightHand")
			obj:SetAnim(channel, "ar_RHand_AltGrip_Rifles")
			obj:SetAnimWeight(channel, 1000)
		end
	else
		obj:ClearAnim(channel)
	end

	-- update visibility
	local blocked_spots = (avatar or obj).blocked_spots
	local flare = IsKindOf(obj, "Unit") and obj.carry_flare and obj.visible
	for _, attach in ipairs(attaches) do
		local spot = attach:GetAttachSpotName()
		local is_blocked = blocked_spots and (blocked_spots[spot] or blocked_spots[BlockedSpotsVariants[spot]])
		if is_blocked or spot_attach[spot] ~= attach then
			if flare and IsKindOf(attach, "GrenadeVisual") and attach.fx_actor_class == "FlareStick" then
				attach:SetHierarchyEnumFlags(const.efVisible)
			else
				attach:ClearHierarchyEnumFlags(const.efVisible)
			end
		else
			attach:SetHierarchyEnumFlags(const.efVisible)
			attach:SetContourOuterOccludeRecursive(true)
		end
		local parts = attach.parts
		if parts then
			local is_holstered = attach.equip_index ~= 1 and attach.equip_index ~= 2
			if parts.Bipod and parts.Bipod:HasState("folded") then
				local bipod_state = not is_holstered and IsKindOf(obj, "Unit") and obj.stance == "Prone" and "idle" or "folded"
				if parts.Bipod:GetStateText() ~= bipod_state then
					parts.Bipod:SetState(bipod_state)
				end
			end
			if parts.Under and parts.Under:HasState("folded") then
				local bipod_state = not is_holstered and IsKindOf(obj, "Unit") and obj.stance == "Prone" and "idle" or "folded"
				if parts.Under:GetStateText() ~= bipod_state then
					parts.Under:SetState(bipod_state)
				end
			end
			if parts.Barrel and parts.Barrel:HasState("folded") then
				local bipod_state = not is_holstered and IsKindOf(obj, "Unit") and obj.stance == "Prone" and "idle" or "folded"
				if parts.Barrel:GetStateText() ~= bipod_state then
					parts.Barrel:SetState(bipod_state)
				end
			end
		end
	end
	return wait_crossfade
end

function Unit:UpdateAttachedWeapons(crossfade)
	DeleteThread(self.update_attached_weapons_thread)
	self.update_attached_weapons_thread = false
	local attaches = self:GetAttaches(WeaponVisualClasses)
	if not attaches then return end
	local wait_crossfade = AttachVisualItems(self, attaches, (crossfade ~= 0) and not IsPaused())
	if wait_crossfade then
		self.update_attached_weapons_thread = CreateGameTimeThread(function(self, delay)
			Sleep(delay)
			self.update_attached_weapons_thread = false
			if IsValid(self) then
				local attaches = self:GetAttaches(WeaponVisualClasses)
				AttachVisualItems(self, attaches)
			end
		end, self, crossfade and crossfade > 0 and crossfade or hr.ObjAnimDefaultCrossfadeTime)
		return
	end
	--[[
	-- vertical grip support was canceled
	local vertical_grip, grip_spot
	local weapon1 = attached_weapons and attached_weapons[1]
	if not holster_weapon and weapon1 and weapon1.weapon and #attached_weapons == 1 then
		grip_spot = weapon1.weapon:GetLHandGripSpot()
	end
	if grip_spot then
		local grip_obj = GetWeaponSpotObject(weapon1, grip_spot)
		local grip_spot_idx = grip_obj:GetSpotBeginIndex(grip_spot)
		local grip_spot_annotation = grip_obj:GetSpotAnnotation(grip_spot_idx)
		if grip_spot_annotation == "Vert" then
			vertical_grip = true
		end
	end
	local channel = const.AnimChannel_VerticalGrip
	if vertical_grip then
		self:SetAnimMask(1, "LeftHand", "inverse")
		self:SetAnimMask(channel, "LeftHand")
		self:SetAnim(channel, "ar_Standing_Idle_Grip")
		--self:SetAnimWeight(channel, 100)
		--self:SetAnimBlendComponents(channel, true, true, false)      -- channel, translation, orientation, scale
	else
		self:ClearAnim(channel)
		self:SetAnimMask(1, false)
	end]]
end

function Unit:AnimationChanged(channel, old_anim, flags, crossfade)
	if channel == 1 then
		self:UpdateAttachedWeapons(crossfade)
		self:UpdateWeaponGrip()
	end
	AnimMomentHook.AnimationChanged(self, channel, old_anim, flags, crossfade)
end

function Unit:GetWeaponAnimPrefix()
	if self.species ~= "Human" then
		return ""
	end
	if self.die_anim_prefix then
		return self.die_anim_prefix
	end
	local prefix = self:GetCommandParam("weapon_anim_prefix") or self:GetCommandParam("weapon_anim_prefix", "Idle")
	if prefix then 
		return prefix
	end
	if self.action_visual_weapon then 
		prefix = GetWeaponAnimPrefix(self.action_visual_weapon.weapon)
		return prefix
	end
	if self.infected then
		return "inf_"
	end
	local weapon, weapon2 = self:GetActiveWeapons()
	if not weapon and (not self.team or self.team.side == "neutral") then
		return "civ_"
	end
	return GetWeaponAnimPrefix(weapon, weapon2)
end

function Unit:GetWeaponAnimPrefixFallback()
	return ""
end

local human_one_slab_anims = { "DeathOnSpot", "DeathFall", "DeathWindow" }

function Unit:GetGroundOrientOffsets(anim)
	local offsets = GroundOrientOffsets[self.species]
	if anim and self.species == "Human" then
		for _, pattern in ipairs(human_one_slab_anims) do
			if string.match(anim, pattern) then
				offsets = GroundOrientOffsets["OneTile"]
			end
		end
	end
	return offsets or GroundOrientOffsets["OneTile"]
end

function Unit:UpdateGroundOrientParams()
	local offsets = self:GetGroundOrientOffsets(self:GetStateText())
	pf.SetGroundOrientOffsets(self, table.unpack(offsets))
end

-- return: footplant, ground_orient
function Unit:GetFootPlantPosProps(stance)
	if self.species == "Human" then
		if self:HasStatusEffect("ManningEmplacement") then
			return false, false
		end
		if (stance or self.stance) == "Prone" or self:IsDead() then
			return false, true
		end
		return true, false
	elseif self.species == "Crocodile" then
		return false, true
	elseif self.species == "Hyena" then
		return true, false
	end
	return false, false
end

function Unit:SetFootPlant(set, time, stance)
	local footplant, ground_orient
	if set and not config.IKDisabled then
		footplant, ground_orient = self:GetFootPlantPosProps(stance)
	end
	local label = "FootPlantIK"
	local ikCmp = self:GetAnimComponentIndexFromLabel(1, label)
	if ikCmp ~= 0 then
		if footplant then
			self:SetAnimComponentTarget(1, ikCmp, "IKFootPlant", 10*guic, 10*guic)
		else
			self:RemoveAnimComponentTarget(1, ikCmp)
		end
	end
	if ground_orient then
		if not self.ground_orient then
			self.ground_orient = true
			self:ChangePathFlags(const.pfmGroundOrient)
			self:SetGroundOrientation(self:GetOrientationAngle(), time or 300)
		end
	else
		if self.ground_orient then
			self.ground_orient = false
			self:ChangePathFlags(0, const.pfmGroundOrient)
			self:SetAxisAngle(axis_z, self:GetVisualOrientationAngle(), time or 300)
		else
			self:ChangePathFlags(0, const.pfmGroundOrient)
			self:SetAxis(axis_z)
		end
	end
end

MapVar("g_IKDebug", false)

MapVar("g_IKDebugThread", CreateRealTimeThread(function()
	while true do
		if g_IKDebug then
			DbgClearVectors()
			DbgClearTexts()
			for unit, target in pairs(g_IKDebug) do
				--DbgAddVector(target, point(0, 0, guim), const.clrWhite)
				DbgAddText("Target", target, const.clrWhite)
				local weapon = unit:GetActiveWeapons("Firearm")
				local spot_obj = weapon and GetWeaponSpotObject(weapon:GetVisualObj(), "Muzzle")
				local wpos = spot_obj and spot_obj:GetSpotVisualPos(spot_obj:GetSpotBeginIndex("Muzzle"))
				if wpos then
					DbgAddVector(wpos, target - wpos, const.clrWhite)
				end
				--local upos = unit:GetSpotLoc(unit:GetSpotBeginIndex("Weaponr"))
				local upos = weapon and GetWeaponSpotPos(weapon:GetVisualObj(), "Muzzle")
				if upos then
					DbgAddVector(upos, target - upos, const.clrGreen)
				end
			end
		end
		Sleep(100)
	end
end))

function Unit:GetIK(label)
	local ikCmp = self:GetAnimComponentIndexFromLabel(1, label)
	if ikCmp == 0 then
		return
	end
	local direction = self:GetAnimComponentTargetDirection(1, ikCmp)
	return direction
end

function Unit:UpdateWeaponGrip(anim)
	if not IsEditorActive() then
		anim = anim or self:GetStateText()
		if string.starts_with(anim, "ar_") or string.starts_with(anim, "arg_") then
			self:SetWeaponGrip(true)
			return
		end
	end
	self:SetWeaponGrip(false)
end

function Unit:SetWeaponGrip(set)
	local ikCmp = self:GetAnimComponentIndexFromLabel(1, "LHandWeaponGrip")
	if ikCmp == 0 then
		return
	end
	if set then
		if config.Force_Selection_WeaponGripIK and self == SelectedObj then
			-- keep it
		elseif config.IKDisabled or config.WeaponGripIKDisabled then
			set = false
		end
	end
	if set then
		local weapon, weapon2 = self:GetActiveWeapons()
		local weapon_obj = not weapon2 and weapon and weapon:GetVisualObj(self)
		if weapon_obj and weapon_obj:GetAttachSpotName() == "Weaponr" then
			local weapon = weapon_obj.weapon
			local spot = weapon and weapon:GetLHandGripSpot()
			if spot then
				local offset = GetWeaponRelativeSpotPos(weapon_obj, spot)
				if offset then
					local spot = weapon_obj:GetAttachSpot()
					self:SetAnimComponentTarget(1, ikCmp, "IKWeaponGrip", spot, offset)
					return
				end
			end
		end
	end
	self:RemoveAnimComponentTarget(1, ikCmp, true)
end

function Unit:CalcIKIntermediateTarget(ikCmp, target)
	local direction = self:GetAnimComponentTargetDirection(1, ikCmp)
	if direction then
		local face_angle = self:GetOrientationAngle()
		local target_angle = (IsValid(target) and self:AngleToObject(target) or self:AngleToPoint(target)) + self:GetAngle()
		local dir_angle = CalcOrientation(direction)
		local cur_angle = AngleDiff(dir_angle, face_angle)
		local new_angle = AngleDiff(target_angle, face_angle)
		if cur_angle * new_angle < 0 and abs(cur_angle - new_angle) > 90*60 then
			local pos = self:GetVisualPos()
			local target_pos = (IsValid(target) and target:GetVisualPos() or target)
			local new_target = pos + Rotate(target_pos - pos, cur_angle + (cur_angle < 0 and 90*60 or -90*60) - new_angle)
			return new_target
		end
	end
end

function Unit:SetIK(label, target, spot, initial_dir, time, overridePoseTime)
	if config.IKDisabled then
		target = false
	end
	if self.setik_thread then
		DeleteThread(self.setik_thread)
		self.setik_thread = false
	end
	local ikCmp = self:GetAnimComponentIndexFromLabel(1, label)	
	if ikCmp == 0 then
		if target then
			GameTestsErrorf("once", "Missing IK component %s for %s(%s) in state %s", tostring(label), self.unitdatadef_id, self:GetEntity(), self:GetStateText())
		end
	else
		local intermediate_target
		initial_dir = initial_dir or InvalidPos()
		overridePoseTime = overridePoseTime or 0
		time = -1000
		if IsPoint(target) then
			if not target:IsValidZ() then target = target:SetTerrainZ() end
			intermediate_target = time ~= 0 and self:CalcIKIntermediateTarget(ikCmp, target)
			if not intermediate_target then
				self:SetAnimComponentTarget(1, ikCmp, target, initial_dir, time, overridePoseTime)
			end
		elseif IsValid(target) then
			local spot_idx = target:GetSpotBeginIndex(spot or "Origin")
			local bone = target:GetSpotBone(spot_idx)
			if bone and bone ~= "" then
				intermediate_target = time ~= 0 and self:CalcIKIntermediateTarget(ikCmp, target)
				if not intermediate_target then
					self:SetAnimComponentTarget(1, ikCmp, target, bone, initial_dir, time, overridePoseTime)
				end
			else
				local pos = target:GetSpotLocPos(spot_idx)
				intermediate_target = time ~= 0 and self:CalcIKIntermediateTarget(ikCmp, pos)
				if not intermediate_target then
					self:SetAnimComponentTarget(1, ikCmp, pos, initial_dir, time, overridePoseTime)
				end
			end
		else
			assert(not target)
			self:RemoveAnimComponentTarget(1, ikCmp, true)
		end
		if intermediate_target then
			self:SetAnimComponentTarget(1, ikCmp, intermediate_target, initial_dir, time, overridePoseTime)
			self.setik_thread = CreateGameTimeThread(function(self, label, target, spot, initial_dir, time, overridePoseTime)
				Sleep(25)
				self.setik_thread = false
				self:SetIK(label, target, spot, initial_dir, time, overridePoseTime)
			end, self, label, target, spot, initial_dir, time, overridePoseTime)
		end
	end
	if g_IKDebug then
		g_IKDebug[self] = IsPoint(target) and target or IsValid(target) and target:GetSpotLocPos(target:GetSpotBeginIndex(spot or "Origin")) or nil
	end
end

function Unit:AimIdle()
	self.aim_rotate_last_angle = false
	self.aim_rotate_cooldown_time = false
	if not g_Combat then
		local x, y, z = GetPassSlabXYZ(self)
		if not x or not self:IsEqualPos(x, y, z) or not CanDestlock(self) then
			self:GotoSlab()
		end
	end
	while self.aim_action_id do
		local time = GameTime()
		local attack_args, attack_results = self:GetAimResults()
		self:AimTarget(attack_args, attack_results, false)
		Msg("AimIdleLoop")
		if time == GameTime() then
			Sleep(50)
		end
	end
	self:ForEachAttach("GrenadeVisual", DoneObject)
end

local aim_rotate_cooldown_times = {
	Standing = 250,
	Crouch = 500,
	Prone = 700,
}

function Unit:AimTarget(attack_args, attack_results, prepare_to_attack)
	if self:HasStatusEffect("ManningEmplacement") then
		if self:GetStateText() ~= "hmg_Crouch_Idle" then
			self:SetState("hmg_Crouch_Idle", const.eKeepComponentTargets, 0)
		end
		return
	end
	if not attack_args then
		return
	end
	local action_id = attack_args.action_id
	local action = CombatActions[action_id]
	local weapon = action and action:GetAttackWeapons(self)
	local prepared_attack = attack_args.opportunity_attack_type == "PinDown" or attack_args.opportunity_attack_type == "Overwatch"
	local lof_idx = table.find(attack_args.lof, "target_spot_group", attack_args.target_spot_group or "Torso")
	local lof_data = attack_args.lof and attack_args.lof[lof_idx or 1] or attack_args
	local aim_pos = lof_data.lof_pos2
	local trajectory = attack_results and attack_results.trajectory
	if trajectory and #trajectory > 1 then
		local p1 = trajectory[1].pos
		local p2 = trajectory[2].pos
		if p1 ~= p2 then
			aim_pos = p1 + SetLen(p2 - p1, 10*guim)
		end
	end
	aim_pos = aim_pos or attack_args.target
	if attack_args.OverwatchAction and lof_data.lof_pos1 then
		if self.ground_orient then
			local axis = self:GetAxis()
			local angle = self:GetAngle()
			local p1 = RotateAxis(lof_data.lof_pos1, axis, -angle)
			local p2 = RotateAxis(aim_pos, axis, -angle)
			aim_pos = RotateAxis(p2:SetZ(p1:z()), axis, angle)
		else
			aim_pos = aim_pos:SetZ(lof_data.lof_pos1:z())
		end
	end

	local rotate_to_target = prepare_to_attack or IsValid(attack_args.target) and IsKindOf(attack_args.target, "Unit")
	local aimIK = rotate_to_target and self:CanAimIK(weapon)
	local stance = rotate_to_target and attack_args.stance or self.stance
	local quick_play = not GameTimeAdvanced or self:CanQuickPlayInCombat() 
	local idle_aiming, rotate_cooldown_disable

	if action_id == "MeleeAttack" then
		idle_aiming = true
	elseif not rotate_to_target and self.stance == "Prone" and attack_args.stance ~= "Prone" then
		idle_aiming = true
	elseif not rotate_to_target and aimIK and abs(self:AngleToPoint(aim_pos)) > 50*60 then
		if self.last_idle_aiming_time then
			if GameTime() - self.last_idle_aiming_time > config.IdleAimingDelay then
				idle_aiming = true
			end
		else
			self.last_idle_aiming_time = GameTime()
		end
	else
		self.last_idle_aiming_time = false
	end

	local aim_anim
	if idle_aiming then
		local base_idle = self:GetIdleBaseAnim(stance)
		if not IsAnimVariant(self:GetStateText(), base_idle) then
			if self.stance == "Prone" then
				-- first rotate
				local visual_stance = string.match(self:GetStateText(), "^%a+_(%a+)_")
				if visual_stance == "Standing" or visual_stance == "Crouch" then
					local angle = self:GetPosOrientation()
					if quick_play then
						self:SetOrientationAngle(angle)
					else
						self:AnimatedRotation(angle, self:GetIdleBaseAnim(visual_stance))
					end
				end
				self:SetFootPlant(true)
			end
			if not quick_play then
				PlayTransitionAnims(self, base_idle)
			end
			self:SetRandomAnim(base_idle)
			rotate_cooldown_disable = true
		end
		self:SetIK("AimIK", false)
		aimIK = false
		aim_anim = self:GetStateText()
	else
		if (stance == "Standing" or stance == "Crouch") and self.stance == "Prone" then
			local cur_anim = self:GetStateText()
			if string.match(cur_anim, "%a+_(%a+).*") == "Prone" then
				self:SetFootPlant(true, nil, stance)
				if not quick_play then
					local base_idle = self:GetIdleBaseAnim(stance)
					local angle = lof_data.angle or CalcOrientation(lof_data.step_pos, aim_pos)
					PlayTransitionAnims(self, base_idle, angle)
				end
			end
		end
		self:AttachActionWeapon(action)
		aim_anim = self:GetAimAnim(action_id, stance)
	end

	if quick_play then
		if not self.return_pos and not IsCloser2D(self, lof_data.step_pos, const.SlabSizeX/2) and not attack_args.circular_overwatch then
			self.return_pos = GetPassSlab(self)
		end
		self:SetPos(lof_data.step_pos)
		self:SetOrientationAngle(lof_data.angle or CalcOrientation(lof_data.step_pos, aim_pos))
		if self:GetStateText() ~= aim_anim then
			self:SetState(aim_anim, const.eKeepComponentTargets, 0)
		end
		self:SetFootPlant(true)
		if aimIK then
			self:SetIK("AimIK", aim_pos, nil, nil, 0)
		else
			self:SetIK("AimIK", false)
		end
		return
	end

	self:SetIK("LookAtIK", false)
	self:SetFootPlant(true, nil, stance)

	if rotate_to_target then
		local prefix = string.match(aim_anim, "^(%a+_).*") or self:GetWeaponAnimPrefix()

		while true do
			-- enter step pos
			while not IsCloser2D(self, lof_data.step_pos, const.SlabSizeX/2) do
				local dummy_angle
				if lof_data.step_pos:Dist2D(self.return_pos or self) == 0 then
					dummy_angle = CalcOrientation(self.return_pos, aim_pos)
				else
					dummy_angle = CalcOrientation(self.return_pos or self, lof_data.step_pos)
				end
				if self:ReturnToCover(prefix) then
					-- some time passed, check if the lof_data.step_pos position has been changed
				else
					-- behind a cover. place the unit to the left or right of the cover.
					local angle = CalcOrientation(self, lof_data.step_pos)
					local rotate = abs(AngleDiff(angle, self:GetVisualOrientationAngle())) > 90*60
					self:SetIK("AimIK", false)
					if rotate then
						self:AnimatedRotation(angle, aim_anim)
					end
					if not rotate or self.command ~= "AimIdle" then
						local step_to_target = CalcOrientation(lof_data.step_pos, aim_pos)
						local cover_side = AngleDiff(step_to_target, angle) < 0 and "Left" or "Right"
						local anim = string.format("%s%s_Aim_Start", prefix, cover_side)
						if not self.return_pos and not attack_args.circular_overwatch then
							self.return_pos = GetPassSlab(self)
						end
						if IsValidAnim(self, anim) then
							anim = self:ModifyWeaponAnim(anim)
							self:SetPos(lof_data.step_pos, self:GetAnimDuration(anim))
							self:RotateAnim(step_to_target, anim)
						else
							local msg = string.format('Missing animation "%s" for "%s"', anim, self.unitdatadef_id)
							StoreErrorSource(self, msg)
							self:SetState(aim_anim, const.eKeepComponentTargets)
							self:SetAngle(step_to_target, 500)
							Sleep(500)
						end
					end
				end
				-- update aiming position (the cursor position could be changed)
				if self.command ~= "AimIdle" then
					if not IsCloser2D(self, lof_data.step_pos, const.SlabSizeX/2) then
						return
					end
					break
				end
				if not self.aim_action_id then
					return
				end
				attack_args, attack_results = self:GetAimResults()
				lof_idx = table.find(attack_args.lof, "target_spot_group", attack_args.target_spot_group or "Torso")
				lof_data = attack_args.lof and attack_args.lof[lof_idx or 1] or attack_args
				aim_pos = lof_data.lof_pos2 or attack_args.target
				if attack_results and attack_results.trajectory then
					local p1 = attack_results.trajectory[1].pos
					local p2 = attack_results.trajectory[2].pos
					aim_pos = p1 + SetLen(p2 - p1, 10*guim)
				end
			end
			
			if weapon then
				self:SetAimFX(weapon:GetVisualObj(self))
			end

			local angle = CalcOrientation(self, aim_pos)
			local start_angle = self:GetVisualOrientationAngle()
			local angle_diff = AngleDiff(angle, start_angle)
			if stance == "Prone" then
				if prepared_attack and not attack_args.circular_overwatch then
					angle = start_angle
				else
					if abs(angle_diff) <= 60*60 then
						angle = start_angle
					else
						angle = FindProneAngle(self, nil, angle, 60*60)
					end
				end
			end
			-- play transition animations to target anim
			local played_anims = PlayTransitionAnims(self, aim_anim, angle)
			if played_anims and self.command == "AimIdle" then
				break
			end
			if self.command ~= "AimIdle" then
				if not attack_args.opportunity_attack or abs(AngleDiff(angle, start_angle)) > 45*60 then
					self:AnimatedRotation(angle, aim_anim)
				end
				break
			end
			-- rotate left or right
			if abs(AngleDiff(angle, self:GetOrientationAngle())) < 1*60 then
				break
			end
			local max_deviation_angle = 45*60
			if abs(angle_diff) < max_deviation_angle and not (prepare_to_attack and prepared_attack) then
				self.aim_rotate_last_angle = false
				break
			end
			if not rotate_cooldown_disable then
				if not self.aim_rotate_last_angle or abs(AngleDiff(angle, self.aim_rotate_last_angle)) > max_deviation_angle then
					self.aim_rotate_last_angle = angle
					self.aim_rotate_cooldown_time = GameTime() + (aim_rotate_cooldown_times[stance] or 1000)
					break
				end
				if GameTime() - self.aim_rotate_cooldown_time < 0 then
					break
				end
			end
			self.aim_rotate_last_angle = false
			self.aim_rotate_cooldown_time = false
			local rotate_anim = self:GetRotateAnim(angle_diff, aim_anim)
			if not IsValidAnim(self, rotate_anim) then
				self:IdleRotation(angle)
				break
			end
			self:SetIK("AimIK", false)
			rotate_anim = self:ModifyWeaponAnim(rotate_anim)
			if abs(angle_diff) > 150*60 then
				self:Rotate180(angle, rotate_anim)
			else
				self:SetState(rotate_anim, const.eKeepComponentTargets, Presets.ConstDef.Animation.BlendTimeRotateOnSpot.value)
				local anim_rotation_angle = self:GetStepAngle()
				local duration = self:TimeToAnimEnd()
				local rotation_deviation = 45*60
				local steps = 1 + duration / 20
				for i = 1, steps do
					local a = start_angle + i * angle_diff / steps
					local t = duration * i / steps - duration * (i - 1) / steps
					self:SetOrientationAngle(a, t)
					Sleep(t)
				end
			end
			self:SetState(aim_anim, const.eKeepComponentTargets)
			if aimIK then
				self:SetIK("AimIK", aim_pos)
			end
		end
	else
		if self.return_pos then
			local prefix = string.match(aim_anim, "^(%a+_).*") or self:GetWeaponAnimPrefix()
			self:ReturnToCover(prefix)
		end
	end

	local cur_anim = self:GetStateText()
	if cur_anim ~= aim_anim then
		self:SetState(aim_anim, const.eKeepComponentTargets)
	end
	if aimIK then
		if not self.aim_rotate_cooldown_time or GameTime() - self.aim_rotate_cooldown_time >= 0 then
			self:SetIK("AimIK", aim_pos)
		end
	else
		self:SetIK("AimIK", false)
	end
end

function Unit:SetAimFX(fx_target, delayed)
	if self.aim_fx_thread then
		DeleteThread(self.aim_fx_thread)
		self.aim_fx_thread = false
	end
	if self.aim_fx_target == (fx_target or false) then
		return
	end
	if delayed then
		self.aim_fx_thread = CreateGameTimeThread(function(self)
			Sleep(1)
			self.aim_fx_thread = false
			self:SetAimFX(fx_target)
		end, self, fx_target)
		return
	end
	if self.aim_fx_target then
		PlayFX("Aim", "end", self, self.aim_fx_target)
	end
	if fx_target then
		PlayFX("Aim", "start", self, fx_target)
	end
	self.aim_fx_target = fx_target
end

function Unit:CanAimIK(weapon)
	local weapon_type = weapon and weapon.WeaponType
	if not weapon_type then
		return false
	elseif weapon_type == "Grenade" then
		return false
	elseif weapon_type == "MeleeWeapon" then
		return false
	elseif weapon_type == "Mortar" then
		return false
	elseif weapon_type == "FlareGun" then
		return false
	elseif self:HasStatusEffect("ManningEmplacement") then
		return false
	end
	return true
end

function NetSyncEvents.Aim(unit, action_id, target)
	if not unit then return end
	local action = CombatActions[action_id]
	if action and action.DisableAimAnim then return end

	local changed = unit:SetAimTarget(action_id, target)
	if changed and unit.team and unit.team.control == "UI" then
		local playerId = unit:IsLocalPlayerControlled() and netUniqueId or GetOtherPlayerId()
		local targetId = IsKindOf(target, "Unit") and target.session_id
		SetCoOpPlayerAimingAtUnit(playerId, targetId)
	end
end

function OnMsg.RunCombatAction(action_id, unit)
	if unit and action_id ~= "Aim" and unit.aim_action_id then
		unit:SetAimTarget()
	end
end

local function playTurnOnFx(unit, weapon)
	local visual = weapon and weapon.visual_obj
	if not visual then return end
	
	for slot, component_id in sorted_pairs(weapon.components) do
		local component = WeaponComponents[component_id]
		if component and component.EnableAimFX then
			local fx_actor
			for _, descr in ipairs(component and component.Visuals) do
				if descr:Match(weapon.class) then
					fx_actor = visual.parts[descr.Slot]
					if fx_actor then
						break
					end
				end
			end
			fx_actor = fx_actor or visual
			PlayFX("TurnOn", "start", fx_actor)
			unit.weapon_light_fx = unit.weapon_light_fx or {}
			unit.weapon_light_fx[#unit.weapon_light_fx + 1] = fx_actor
		end
	end
end

function Unit:SetWeaponLightFx(enable)
	for _, fx_actor in ipairs(self.weapon_light_fx) do
		PlayFX("TurnOn", "end", fx_actor)
	end
	self.weapon_light_fx = false
	
	if enable and self.visible and not self:CanQuickPlayInCombat() then
		local weapon1, weapon2 = self:GetActiveWeapons()
		playTurnOnFx(self, weapon1)
		playTurnOnFx(self, weapon2)
	end	
end

function Unit:SetAimTarget(action_id, target)
	if action_id then
		local aim_target = target or false
		if IsPoint(aim_target) and not aim_target:IsValidZ() then
			aim_target = aim_target:SetTerrainZ(2*const.SlabSizeZ)
		end
		local aim_action_params = self.aim_action_params
		if not aim_action_params then
			aim_action_params = {}
			self.aim_action_params = aim_action_params
		end
		if self.aim_action_id == action_id and aim_action_params.target == aim_target then
			return false
		end

		if self.visible and self.aim_action_id ~= action_id then
			self:SetWeaponLightFx(true)
		end
		self.aim_action_id = action_id
		aim_action_params.target = aim_target 
		if self.command == "Idle" then
			self:SetCommand("Idle")
		end		
	elseif self.aim_action_id then
		self.aim_action_id = false
		self.aim_action_params = false
		self.aim_results = false
		self.aim_attack_args = false
	end
	return true
end

function Unit:GetActionResults(action_id, args)
	local action = CombatActions[action_id]
	if action then
		return action:GetActionResults(self, args)
	end
end

function Unit:GetAimResults()
	local action = CombatActions[self.aim_action_id]
	if not action then
		return
	elseif not self.aim_results then
		-- check for valid aim target
		local target = self.aim_action_params and self.aim_action_params.target
		if not IsPoint(target) and not IsValid(target) then return end
		self.aim_results, self.aim_attack_args = action:GetActionResults(self, self.aim_action_params)
	end
	return self.aim_attack_args, self.aim_results
end

local NonStanceActionAnims = {
	["Idle"] = "idle",
	["Run"] = "walk",
	["Death"] = "death",
}

function Unit:UpdateModifiedAnim()
	local modify_animations_ar = false
	local weapon, weapon2 = self:GetActiveWeapons("Firearm")
	if weapon and not weapon2 then
		if weapon.ModifyRightHandGrip then
			modify_animations_ar = true
		else
			for i, component in pairs(weapon.components) do
				local visuals = (WeaponComponents[component] or empty_table).Visuals or empty_table
				local idx = table.find(visuals, "ApplyTo", weapon.class)
				if idx then
					local component_data = visuals[idx]
					if component_data.ModifyRightHandGrip then
						modify_animations_ar = true
						break
					end
				end
			end
		end
	end
	if self.modify_animations_ar ~= modify_animations_ar then
		self.modify_animations_ar = modify_animations_ar
		local anim = self:GetStateText()
		local new_anim = modify_animations_ar and self:ModifyWeaponAnim(anim) or GetUnmodifiedAnim(anim)
		if new_anim ~= anim then
			self:SetState(new_anim, const.eKeepPhase)
		end
	end
end

function Unit:ModifyWeaponAnim(anim)
	if self.modify_animations_ar then
		if string.starts_with(anim, "ar_") then
			local new_anim = "arg_" .. string.sub(anim, 4)
			if IsValidAnim(self, new_anim) then
				return new_anim
			end
		end
	end
	return anim
end

function GetUnmodifiedAnim(anim)
	if string.starts_with(anim, "arg_") then
		return "ar_" .. string.sub(anim, 5)
	end
	return anim
end

function Unit:GetUnmodifiedAnim()
	return GetUnmodifiedAnim(self:GetStateText())
end

function Unit:GetValidAnim(prefix, stance, action_full)
	local name = stance and stance ~= "" and string.format("%s_%s", stance, action_full) or action_full
	local base_anim = name
	if stance == "" then
		base_anim = NonStanceActionAnims[action_full] or base_anim
	end
	if prefix and prefix ~= "" then
		base_anim = prefix .. base_anim
	end
	local valid = self:HasState(base_anim) and not IsErrorState(self:GetEntity(), base_anim)
	if not valid and action_full == "WalkSlow" then
		return self:GetValidAnim(prefix, stance, "Walk")
	end
	return valid, base_anim, name
end

function IsAnimVariant(anim, base_anim)
	anim = GetUnmodifiedAnim(anim)
	return (anim == base_anim or string.starts_with(anim, base_anim) and tonumber(string.sub(anim, #base_anim + 1))) and true or false
end

function GetAnimVariants(entity, base_anim)
	if not HasState(entity, base_anim) or IsErrorState(entity, base_anim) then
		return {}
	end
	
	local format = string.match(base_anim, ".*%d$") and "%s_%d" or "%s%d"
	local anim_variants = {}
	local count = 0
	while true do
		count = count + 1
		local anim = (count == 1) and base_anim or string.format(format, base_anim, count)
		if not HasState(entity, anim) or IsErrorState(entity, anim) then
			break
		end
		table.insert(anim_variants, anim)
	end
	
	return anim_variants
end

local anim_variations_weight_cache = {}
local anim_variations_phases_chunk = 1000
local anim_variations_min_time_offset = 2000
local nearby_unique_anim_distance = 12*guim

local function GetRandomAnims(entity, base_anim)
	local t = anim_variations_weight_cache[entity]
	if not t then
		t = {}
		anim_variations_weight_cache[entity] = t
	end
	if not t[base_anim] then
		local anims = {}
		t[base_anim] = anims
		local total_chunks = 0
		local total_weight = 0
		local anim_variants = GetAnimVariants(entity, base_anim)
		for idx, anim in ipairs(anim_variants) do
			local anim_metadata = (Presets.AnimMetadata[entity] or empty_table)[anim] or empty_table
			local anim_weight = anim_metadata.VariationWeight or 100
			local max_random_phase = anim_metadata.RandomizePhase or -1
			if max_random_phase < 0 then
				max_random_phase = GetAnimDuration(entity, base_anim) * 70 / 100
			end
			local chunks_count = 1 + max_random_phase / anim_variations_phases_chunk
			total_weight = total_weight + anim_weight
			anims[idx] = {
				anim = anim,
				anim_weight = anim_weight,
				total_weight = total_weight,
				max_random_phase = max_random_phase,
				chunk_idx = total_chunks,
				chunks_count = chunks_count,
			}
			total_chunks = total_chunks + chunks_count
		end
		anims.total_weight = total_weight
		anims.total_chunks = total_chunks
	end
	return	t[base_anim]
end

function Unit:GetVariationsCount(base_anim)
	if not base_anim then
		return
	end
	local anims = GetRandomAnims(self:GetEntity(), base_anim)
	return #anims
end

function Unit:GetRandomAnim(base_anim)
	if not base_anim then
		return
	end
	local anims = GetRandomAnims(self:GetEntity(), base_anim)
	if #anims == 0 then
		StoreErrorSource(self, string.format("Invalid '%s' variation request", base_anim))
		return base_anim, 1, 1
	end
	local roll = self:Random(anims.total_weight)
	local idx = GetRandomItemByWeight(anims, roll, "total_weight")
	return anims[idx].anim, idx
end

function Unit:GetNearbyUniqueRandomAnim(base_anim)
	if not base_anim then
		return
	end
	local anims = GetRandomAnims(self:GetEntity(), base_anim)
	if anims.total_chunks == 1 then
		return anims[1].anim, 0, 1 -- animation, phase, variation index
	end

	local anims_locked_chunks = {}
	MapForEach(self, nearby_unique_anim_distance, "Unit", function(o, self, anims, anims_locked_chunks)
		if o == self then
			return
		end
		if o.gender ~= self.gender then
			return
		end
		local variation_idx = table.find(anims, "anim", o:GetUnmodifiedAnim())
		if not variation_idx then
			return
		end
		local min, max
		local entry = anims[variation_idx]
		if entry.max_random_phase == 0 then
			min, max = 1, 1 -- this animation is not supposed to be played more than once
		else
			local phase = o:GetAnimPhase()
			min = Max(0, phase - anim_variations_min_time_offset) / anim_variations_phases_chunk
			max = Min(entry.max_random_phase, phase + anim_variations_min_time_offset) / anim_variations_phases_chunk
			if min > max then
				return
			end
		end
		local chunk_idx = entry.chunk_idx
		local locked_count = 0
		for i = min, max do
			if not anims_locked_chunks[chunk_idx + i] then
				anims_locked_chunks[chunk_idx + i] = true
				locked_count = locked_count + 1
			end
		end
		if locked_count > 0 then
			NetUpdateHash("GetNearbyUniqueRandomAnim_locking_anim", o, chunk_idx, variation_idx, locked_count, o:GetUnmodifiedAnim(), o:GetAnimPhase())
			anims_locked_chunks[-variation_idx] = (anims_locked_chunks[-variation_idx] or 0) + locked_count
		end
	end, self, anims, anims_locked_chunks)

	-- try first not used animation
	local total_free_animations = 0
	for idx, entry in ipairs(anims) do
		if entry.chunks_count > 0 and not anims_locked_chunks[entry.chunk_idx] then
			total_free_animations = total_free_animations + 1
		end
	end
	NetUpdateHash("GetNearbyUniqueRandomAnim_total_free_animations", total_free_animations)
	if total_free_animations > 0 then
		local value = total_free_animations > 1 and self:Random(total_free_animations) or 0
		for idx, entry in ipairs(anims) do
			if entry.chunks_count > 0 and not anims_locked_chunks[entry.chunk_idx] then
				value = value - 1
			end
			if value < 0 then
				return entry.anim, 0, idx
			end
		end
		assert(false)
	end

	-- look for not used animation segment
	local total_weight = anims.total_weight
	for idx, entry in ipairs(anims) do
		local locked_chunks_count = anims_locked_chunks[-idx]
		if locked_chunks_count then
			local locked_weight = entry.anim_weight * locked_chunks_count / entry.chunks_count
			total_weight = total_weight - locked_weight
		end
	end
	if total_weight > 0 then
		local value = self:Random(total_weight)
		for idx, entry in ipairs(anims) do
			local weight = entry.anim_weight
			local locked_chunks_count = anims_locked_chunks[-idx]
			if locked_chunks_count then
				local locked_weight = weight * locked_chunks_count / entry.chunks_count
				weight = weight - locked_weight
			end
			if weight > 0 then
				value = value - weight
				if value < 0 then
					-- return the first free chunk
					if not locked_chunks_count then
						return entry.anim, 0, idx
					end
					for i = entry.chunk_idx, entry.chunk_idx + entry.chunks_count - 1 do
						if not anims_locked_chunks[i] then
							local phase = (i - entry.chunk_idx) * anim_variations_phases_chunk
							return entry.anim, phase, idx
						end
					end
					assert(false)
				end
			end
		end
		assert(false)
	end
	-- there is no free animations
	local anim, variation_idx = self:GetRandomAnim(base_anim)
	return anim, 0, variation_idx
end

function Unit:GetNearbyUniqueRandomAnimFromList(list)
	local anims = table.icopy(list)
	MapForEach(self, nearby_unique_anim_distance, "Unit", function(o, anims)
		if o == self then
			return
		end
		local idx = table.find(anims, o:GetUnmodifiedAnim())
		if idx then
			table.remove(anims, idx)
		end
	end, anims)
	if #anims > 0 then
		return anims[1 + self:Random(#anims)]
	end
	return list[1 + self:Random(#list)]
end	

local UniversalAnimActions = {
	Climb = true,
	Drop = true,
	JumpOverShort = true,
	JumpOverLong = true,
	JumpAcross1 = true,
	JumpAcross2 = true,
}

local ActionAnimationPrefixMap = {
	["Open_Door"] = {
		["inf_"] = "nw_",
	},
	["BreakWindow"] = {
		["civ_"] = "nw_",
		["inf_"] = "nw_",
	},
	["Downed"] = {
		["civ_"] = "nw_",
		["inf_"] = "nw_",
	},
	["Death"] = {
		["inf_"] = "civ_",
	},
}

function Unit:TryGetActionAnim(action, stance, action_suffix)
	local action_full
	if not g_Combat and action == "Idle" or action == "IdlePassive" then
		action_full = self:GetCommandParam("idle_action")
		stance = self:GetCommandParam("idle_stance") or stance
	end
	if not action_full then
		action_full = action_suffix and action .. action_suffix or action
	end

	local prefix
	if self.species == "Human" then
		if UniversalAnimActions[action] then
			prefix = "civ_"
		elseif self:HasStatusEffect("ManningEmplacement") and (action == "Idle" or action == "IdlePassive" or action == "Fire") then
			prefix = "hmg_"
			stance = "Crouch"
		elseif action == "Fire" then
			local weapon, weapon2 = self:GetActiveWeapons()
			prefix = weapon and GetWeaponAnimPrefix(weapon, weapon2) or "nw_"
			if prefix == "nw_" then
				stance = "Standing"
				action_full = "Attack_Down"
			end
		elseif self.infected then
			if action == "Idle" and stance == "Prone" then
				prefix = "nw_"
			else
				prefix = "inf_"
				if action ~= "Death" and action ~= "Downed" and (stance == "Prone" or stance == "Crouch") then
					stance = "Standing"
				end
			end
		else
			if action == "CombatBegin" then
				stance = "Standing"
			end
			prefix = self:GetWeaponAnimPrefix()
		end
		local action_prefix_map = ActionAnimationPrefixMap[action]
		if action_prefix_map and action_prefix_map[prefix] then
			prefix = action_prefix_map[prefix]
		end
	else
		if stance == "Downed" then
			action = "Downed"
		else
			stance = ""
		end
		if action == "Idle" then
			if self.species == "Hyena" then
				action_full = "idle_Combat"
			end
		elseif action == "Climb" then
			action_full = action_suffix == 1 and "climb_1x" or "climb_2x"
		elseif action == "Drop" then
			action_full = action_suffix == 1 and "drop_1x" or "drop_2x"
		elseif action == "CombatBegin" then
			action_full = "combat_Begin"
		end
	end

	local valid, anim, name = self:GetValidAnim(prefix, stance, action_full)
	if valid then
		return anim
	end
	-- fallback
	if action == "Downed" then
		if self.species == "Human" then
			return "civ_DeathOnSpot_F"
		end
		return "death"
	end
	local fallback_prefix = self:GetWeaponAnimPrefixFallback()
	if fallback_prefix ~= prefix then
		local fallback_anim = string.format("%s%s", fallback_prefix, name)
		if self:HasState(fallback_anim) and not IsErrorState(self:GetEntity(), fallback_anim)then
			return fallback_anim
		end
	end

	return false, anim
end

function Unit:GetActionBaseAnim(action, stance, action_suffix)
	local anim, name = self:TryGetActionAnim(action, stance, action_suffix)
	if not anim then
		local msg = string.format('Missing animation "%s" for "%s"', name, self.unitdatadef_id)
		StoreErrorSource(self, msg)
	end
	return anim
end

function Unit:GetActionRandomAnim(action, stance, action_suffix)
	local base_anim = self:GetActionBaseAnim(action, stance, action_suffix)
	local anim = self:GetNearbyUniqueRandomAnim(base_anim)
	return anim
end

function Unit:SetRandomAnim(base_anim, flags, crossfade, force)
	if not force and IsAnimVariant(self:GetStateText(), base_anim) then
		return
	end
	local anim, phase = self:GetNearbyUniqueRandomAnim(base_anim)
	anim = self:ModifyWeaponAnim(anim)
	self:SetState(anim, flags or const.eKeepComponentTargets, crossfade or -1)
	if phase > 0 then
		self:SetAnimPhase(1, phase)
	end
end

function Unit:RandomizeAnimPhase()
	local duration = GetAnimDuration(self:GetEntity(), self:GetState())
	if duration > 1 then
		local phase = self:Random(duration - 1)
		self:SetAnimPhase(1, phase)
	end
end

function Unit:GetAttackAnim(action_id, stance)
	local attack_anim
	if self.species == "Human" then
		if action_id then
			if string.starts_with(action_id, "ThrowGrenade") then
				attack_anim = "gr_Standing_Attack"
			elseif string.match(action_id, "DoubleToss") then
				attack_anim = "gr_Standing_Attack"
			elseif action_id == "KnifeThrow" or action_id == "HundredKnives" then
				attack_anim = "mk_Standing_Fire"
			elseif action_id == "UnarmedAttack" then
				attack_anim = "nw_Standing_Attack_Down"
			elseif action_id == "Bombard" then
				attack_anim = "nw_Standing_MortarFire"
			elseif action_id == "FireFlare" then
				attack_anim = string.format("hg_%s_Flare_Fire", stance or self.stance)
			elseif action_id == "Charge" or action_id == "GloryHog" or action_id == "MeleeAttack" then
				attack_anim = IsKindOf(self:GetActiveWeapons(), "MacheteWeapon") and "mk_Standing_Machete_Attack_Forward"
					or "mk_Standing_Attack_Forward"
			elseif action_id == "Bandage" then
				return "nw_Bandaging_Idle"
			end
		end
		if not attack_anim then
			attack_anim = self:GetActionBaseAnim("Fire", stance)
		end
	else
		if self:HasState("attack") and not IsErrorState(self:GetEntity(), "attack") then
			attack_anim = "attack"
		end
	end
	attack_anim = self:ModifyWeaponAnim(attack_anim)
	return attack_anim
end

function Unit:GetAimAnim(action_id, stance)
	local aim_idle
	
	if self.species == "Human" then
		if action_id then
			if string.starts_with(action_id, "ThrowGrenade") then
				aim_idle = "gr_Standing_Aim"
			elseif string.match(action_id, "DoubleToss") then
				aim_idle = "gr_Standing_Aim"
			elseif action_id == "KnifeThrow" or action_id == "HundredKnives" then
				aim_idle = "mk_Standing_Aim_Forward"
				--aim_idle = "mk_Standing_Aim_Down"
			elseif action_id == "UnarmedAttack" then
				aim_idle = "nw_Standing_Aim_Forward"
			elseif action_id == "Bombard" then
				aim_idle = "nw_Standing_Idle"
			elseif action_id == "FireFlare" then
				aim_idle = string.format("hg_%s_Flare_Aim", stance or self.stance)
			end
		end
		if not aim_idle then
			local weapon, weapon2 = self:GetActiveWeapons()
			if IsKindOf(weapon, "MeleeWeapon") then
				aim_idle = "mk_Standing_Aim_Forward"
			elseif weapon then
				local attack_anim = self:GetActionBaseAnim("Fire", stance or self.stance)
				local prefix, stance = string.match(attack_anim or "", "(%a+)_(%a+).*")
				if prefix then
					local anim = string.format("%s_%s_Aim", prefix, stance)
					if IsValidAnim(self, anim) then
						aim_idle = anim
					end
				end
			end
			aim_idle = aim_idle or "nw_Standing_Aim_Forward"
		end
	else
		aim_idle = "idle"
	end
	if not IsValidAnim(self, aim_idle) then
		return
	end
	aim_idle = self:ModifyWeaponAnim(aim_idle)
	return aim_idle
end

function Unit:GetIdleStyle()
	local anim_style
	if self.species ~= "Human" then
		local aware = g_Combat and (self:IsAware() or self:HasStatusEffect("Surprised")) or self:HasStatusEffect("Suspicious")
		local cur_style = GetAnimationStyle(self, self.cur_idle_style)
		anim_style =
			aware and (cur_style and cur_style.VariationGroup == "CombatIdle" and cur_style
				or GetRandomAnimationStyle(self, "CombatIdle"))
			or cur_style and cur_style.VariationGroup == "Idle" and cur_style
			or GetRandomAnimationStyle(self, "Idle")
	else
		if self.carry_flare then
			anim_style = GetRandomAnimationStyle(self, "FlareIdle")
		end
	end
	return anim_style
end

function Unit:GetIdleBaseAnim(stance)
	local cur_style = GetAnimationStyle(self, self.cur_idle_style)
	local base_idle = cur_style and cur_style:GetMainAnim()
	if base_idle then
		if not IsValidAnim(self, base_idle) then
			local msg = string.format('GetIdleBaseAnim: Missing animation style "%s - %s" animation "%s". Gender: "%s". Entity: "%s". Appearance: %s', cur_style.group, cur_style.Name, base_idle or "", self.gender, self:GetEntity(), self.Appearance or "false")
			StoreErrorSource(self, msg)
		end
		return base_idle
	end
	stance = stance or self.stance
	local aware = g_Combat and (self:IsAware("pending") or self:HasStatusEffect("Surprised")) or self:HasStatusEffect("Suspicious")
	if aware and self.species == "Human" and self.team and self.team.side == "neutral" and not self.conflict_ignore and not self.infected then
		base_idle = "civ_Standing_Fear"
	end
	if not base_idle and not aware and self.species == "Human" then
		base_idle = self:TryGetActionAnim("IdlePassive", stance)
	end
	if not base_idle and self.species == "Human" and (stance == "Standing" or stance == "Crouch") and self:HasStatusEffect("Protected") then
		base_idle = self:TryGetActionAnim("TakeCover_Idle", false)
	end
	if not base_idle then
		base_idle = self:TryGetActionAnim("Idle", stance)
	end
	return base_idle or "idle"
end

function Unit:ShowActiveMeleeWeapon()
	local weapon1 = self:GetActiveWeapons()
	local wobj1 = IsKindOf(weapon1, "MeleeWeapon") and weapon1:GetVisualObj()
	if not wobj1 then
		return false
	end
	wobj1:SetEnumFlags(const.efVisible)
	return true
end

function Unit:HideActiveMeleeWeapon()
	local weapon1 = self:GetActiveWeapons()
	local wobj1 = IsKindOf(weapon1, "MeleeWeapon") and weapon1:GetVisualObj()
	if not wobj1 then
		return false
	end
	wobj1:ClearEnumFlags(const.efVisible)
	return true
end

local function lGetFallbackUnitAppearance(preset)
	if not preset then return "Soldier_Local_01" end
	if preset.gender == "Male" then
		return "Commando_Foreign_01"
	end
	return "Soldier_Local_01"
end

function GetAppearancesListTotalWeight(preset)
	local weighted_list = {total_weight = 0}
	for _, descr in ipairs(preset.AppearancesList) do
		if MatchGameState(descr.GameStates) then
			weighted_list.total_weight = weighted_list.total_weight + descr.Weight
			table.insert(weighted_list, {weight = weighted_list.total_weight, appearance = descr.Preset})
		end
	end
	
	return weighted_list
end

function GetWeightedAppearance(weighted_list, slot)
	local idx = GetRandomItemByWeight(weighted_list, slot, "weight")
	return weighted_list[idx].appearance
end

function ChooseUnitAppearance(merc_id, handle)
	local preset = UnitDataDefs[merc_id]
	if not preset or not preset.AppearancesList then
		return lGetFallbackUnitAppearance(preset)
	end
	
	local weighted_list = GetAppearancesListTotalWeight(preset)
	local slot = handle and (xxhash(handle) % weighted_list.total_weight) or InteractionRand(weighted_list.total_weight, "Appearance")
	local appearance = GetWeightedAppearance(weighted_list, slot)
	
	return appearance or lGetFallbackUnitAppearance(preset)
end

function Unit:ChooseAppearance()
	local forcedAppearance = false
	
	if self.spawner then
		local templates = self.spawner.UnitDataSpawnDefs or empty_table
		local data = table.find_value(templates, "UnitDataDefId", self.unitdatadef_id)
		forcedAppearance = data and data.ForcedAppearance
	end
	
	local unitData = gv_UnitData[self.session_id]
	if not forcedAppearance and unitData and unitData.ForcedAppearance then
		forcedAppearance = unitData.ForcedAppearance
	end

	if forcedAppearance then
		return forcedAppearance
	end

	return ChooseUnitAppearance(self.unitdatadef_id, self.handle)
end

function Unit:ExplosionFly(prev_hit_points)
	-- not flying anymore(too cartoon) - just play Pain
	self:PushDestructor(function(self)
		SetCombatActionState(self, false)
		self:InterruptPreparedAttack() -- force interrupt when the unit gets thrown off by an explosion
		self:RemoveStatusEffect("Protected") -- lose cover
		if ShouldDoDestructionPass() then
			WaitMsg("DestructionPassDone", 1000) --wait for destro if slabs beneath us get destroyed, so we can get a falldown point
		end
		
		--remove badge as the removal of it in unitdiestart is too late in this case
		if self:IsDead() then 
			DeleteBadgesFromTargetOfPreset("CombatBadge", self)
			DeleteBadgesFromTargetOfPreset("NpcBadge", self)
			if self:ShouldGetDowned() and (g_Combat or (not g_Combat and (prev_hit_points > 1))) then
				self.HitPoints = 1 -- make sure the unit is not considered dead and evicted from the UI
				self:SetCommand("GetDowned", false, "skip anim")
			elseif self.species == "Human" then
				self.on_die_hit_descr = self.on_die_hit_descr or {}
				self.on_die_hit_descr.death_explosion = true
				self:SetCommand("Die")
			else
				self:SetCommand("Die")
			end
		else
			self:Pain()
			-- the combat use the same GotoSlab (the unit should have already been interrupted)
			local pos = GetPassSlab(RotateRadius(guim/2, self:GetOrientationAngle(), self)) or GetPassSlab(self)
			if self:GetPos() ~= pos then
				self:SetCommand("GotoSlab", pos, nil, nil, nil, nil, nil, "interrupted")
			end
		end
	end)
	self:PopAndCallDestructor()
end

function Unit:AttachGrenade(grenade)
	local visual = PlaceObject("GrenadeVisual", {fx_actor_class = grenade.class})
	self:Attach(visual, self:GetSpotBeginIndex("Weaponr"))
	grenade:OnPrepareThrow(self, visual)
	return visual
end

function Unit:DetachGrenade(grenade)
	self:DestroyAttaches("GrenadeVisual")
	grenade:OnFinishThrow(self)
end

function GravityFall(obj, pos)
	obj:SetGravity()
	local fall_time = obj:GetGravityFallTime(pos)
	obj:SetPos(pos, fall_time)
	Sleep(fall_time)
	obj:SetGravity(0)
end

function Unit:FallDown(pos, cower)
	pos = ValidateZ(pos)
	local myPos = ValidateZ(self:GetPos())
	local height = myPos:z() - pos:z()
	if height > 0 then
		if self:HasPreparedAttack() then
			self:InterruptPreparedAttack()
		end
		self:LeaveEmplacement(true)
		local orientation_angle = self:GetOrientationAngle()
		local stance = self:GetValidStance(self.stance, pos)
		if self:IsDead() then
			local norm = self:GetGroundOrientation(self, pos, orientation_angle)
			self:SetOrientation(norm, orientation_angle, 300)
			GravityFall(self, pos)
		else
			if stance == "Prone" then
				orientation_angle = FindProneAngle(self, pos, orientation_angle, 60*60)
				local norm = self:GetGroundOrientation(self, pos, orientation_angle)
				self:SetOrientation(norm, orientation_angle, 300)
			else
				self:SetOrientation(axis_z, orientation_angle, 300)
			end
			local anim_style = GetAnimationStyle(self, self.cur_idle_style)
			local base_idle = anim_style and anim_style:GetMainAnim() or self:GetIdleBaseAnim(stance)
			self:SetRandomAnim(base_idle)
			self:SetTargetDummy(pos, orientation_angle, base_idle, 0, stance)
			GravityFall(self, pos)
			local floors = DivCeil(height, 4 * const.SlabSizeZ)
			local damage = 1 + self:Random(floors * 10)
			local floating_text = T{443902454775, "<damage> (High Fall)", damage = damage}
			self:TakeDirectDamage(damage, floating_text)
			if not self:IsDead() then
				if stance ~= self.stance then
					self:DoChangeStance(stance)
				end
				self:UninterruptableGoto(self:GetPos())
			end
		end
	elseif pos ~= myPos then
		if self:HasPreparedAttack() then
			self:InterruptPreparedAttack()
		end
		self:LeaveEmplacement()
		if not self:IsDead() then
			local stance = self:GetValidStance(self.stance, pos)
			if stance ~= self.stance then
				self:DoChangeStance(stance)
			end
			self:UninterruptableGoto(pos, true)
		end
	end
	self:SetTargetDummyFromPos()
	if cower and not self:IsDead() then
		self:SetCommand("Cower", "find cower spot")
		self:SetCommandParamValue("Cower", "move_anim", "Run")
		self:UpdateMoveAnim()
	end
end

function Unit:PlayAwarenessAnim(followup_cmd)
	local setPiece = GetDialog("XSetpieceDlg")
	local triggerUnit = setPiece and setPiece.triggerUnits and setPiece.triggerUnits[1]
	local isTriggerUnit = triggerUnit and triggerUnit == self
	local idleAnim = false
	if self.stance == "Prone" then
		self:DoChangeStance("Standing")
	end
	local anims
	if self:HasStatusEffect("ManningEmplacement") or self:GetBandageTarget() then
		-- do nothing
	elseif setPiece and not isTriggerUnit then
		anims = { self:TryGetActionAnim("Idle", self.stance) }
		idleAnim = true
	elseif self.species == "Human" then
		local heavyWeaponUsage = IsKindOf(self:GetActiveWeapons(), "HeavyWeapon")
		local sniperUsage = IsKindOf(self:GetActiveWeapons(), "SniperRifle")
		local base_anim = self:GetActionBaseAnim("CombatBegin", self.stance)
		if base_anim then
			if self.infected then
				-- zombies do not have variatins of _CombatBegin
				anims = { base_anim }
			else
				if self.pending_awareness_role == "alerter" then
					if heavyWeaponUsage or sniperUsage then
						anims = { base_anim } -- variation 3 shoots and with snipers and heavy weapons we do not want that
					else
						anims = { base_anim .. 3 }
					end
				elseif self.pending_awareness_role == "alerted" then
					anims = { base_anim }
				elseif self.pending_awareness_role == "attacked" then
					anims = { base_anim .. 4 }
				elseif self.pending_awareness_role == "surprised" then
					anims = { base_anim .. 2 }
				end
			end
		end
	else
		if self.pending_awareness_role == "alerter" then
			anims = { "combat_Begin" }
		else
			anims = { "combat_Begin2" }
		end
	end
	if anims then
		if self.pending_awareness_role == "alerted" and not IsValid(self.alerted_by_enemy) then
			Sleep(self:Random(500)) -- randomize phase when multiple units playing
		end
		if IsValid(self.alerted_by_enemy) and not self:HasStatusEffect("ManningEmplacement") and not self:GetBandageTarget() then
			local alerted_angle = CalcOrientation(self, self.alerted_by_enemy)
			local face_angle = self:GetPosOrientation(nil, alerted_angle, self.stance, false, true)
			if face_angle then
				self:AnimatedRotation(face_angle, anims[1])
			end
		end
		local anim = self:GetNearbyUniqueRandomAnimFromList(anims)
		anim = self:ModifyWeaponAnim(anim)
		self:SetState(anim, const.eKeepComponentTargets)
		local weapon = self:GetActiveWeapons()
		local fx_target = weapon and weapon:GetVisualObj() or false
		if fx_target and not idleAnim then
			PlayFX("AwarenessAnim", "start", self, fx_target)
			local index = 1
			while true do
				local t = self:TimeToMoment(1, "hit", index)
				if not t then break end
				Sleep(t)
				PlayFX("AwarenessAnim", "hit", self, fx_target)
				index = index + 1
			end
			Sleep(self:TimeToAnimEnd())
			PlayFX("AwarenessAnim", "end", self, fx_target)
		elseif not idleAnim then
			Sleep(self:TimeToAnimEnd())
		end
	end
	self.pending_awareness_role = nil
	if followup_cmd then
		self:SetCommand(followup_cmd)
	end
end

function Unit:BanterIdle(idle_style)
	self:PlayIdleStyle(idle_style)
end

-- setpiece stuff
function Unit:SetpieceIdle(set_idle)
	-- default command to put units into while playing setpiece and to store some behavior specific params for the setpiece
	Msg("OnSetpieceIdleStart", self)
	local wasInterruptable = self.interruptable
	if wasInterruptable then
		self:EndInterruptableMovement()
	end
	if set_idle then
		local base_idle = self:GetIdleBaseAnim()
		if not IsAnimVariant(self:GetStateText(), base_idle) then
			local anim = self:GetNearbyUniqueRandomAnim(base_idle)
			self:SetState(anim)
		end
	end
	
	repeat
		Sleep(100)
	until not IsSetpiecePlaying()
	if wasInterruptable then
		self:BeginInterruptableMovement()
	end
end

function Unit:SetpieceSetStance(anim_stance)
	if Presets.CombatStance.Default[anim_stance] then
		self.stance = anim_stance
	end
	local base_idle = self:GetIdleBaseAnim(anim_stance)
	if not IsAnimVariant(self:GetStateText(), base_idle) then
		local anim = self:GetNearbyUniqueRandomAnim(base_idle)
		self:SetState(anim)
	end
	self:SetCommand("SetpieceIdle")
end

function Unit:RestoreAiming(target_pt, lof_params)
	local weapon, weapon2 = self:GetActiveWeapons()
	if weapon then
		local attack_data = self:ResolveAttackParams(nil, target_pt, lof_params)
		local aim_idle = self:GetAimAnim(nil, attack_data.stance)
		self:SetState(aim_idle, const.eKeepComponentTargets)
		self:SetPos(attack_data.step_pos)
	else
		if self.return_pos then
			self:SetPos(self.return_pos)
			self.return_pos = false
		end
		self:SetRandomAnim(self:GetIdleBaseAnim())
	end
	self:Face(target_pt)
end

function Unit:SetpieceAimAt(target_pt)
	self:RestoreAiming(target_pt, {can_use_covers = false})
	Msg("SetpieceUnitAimed", self)
	self:SetCommand("SetpieceIdle")
end

function Unit:SetpieceGoto(pos, end_angle, stance, straight_line, animated_rotation, delay)
	self.goto_target = false
	if delay then
		Sleep(delay)
	end
	if (stance or "") ~= "" and stance ~= self.stance then
		self:DoChangeStance(stance)
	end
	-- initial animated face target
	if animated_rotation then
		local face_pos
		if straight_line then
			face_pos = pos
		else
			self:FindPath(pos)
			local pathlen = pf.GetPathPointCount(self)
			for i = pathlen, 1, -1 do
				local p = pf.GetPathPoint(self, i)
				if p and p:IsValid() and self:GetDist2D(p) > 0 then
					face_pos = p
					break
				end
			end
		end
		if face_pos then
			local angle = CalcOrientation(self, face_pos)
			if abs(AngleDiff(angle, self:GetOrientationAngle())) > 45*60 then
				self:AnimatedRotation(angle)
			end
		end
	end
	-- goto
	self:UninterruptableGoto(pos, straight_line)
	-- finish: face target
	if end_angle then
		if animated_rotation and abs(AngleDiff(end_angle, self:GetOrientationAngle())) > 45*60 then
			self:AnimatedRotation(end_angle)
		else
			self:SetOrientationAngle(end_angle, 100)
		end
	end
	self:SetCommand("SetpieceSetStance", self.stance)
end

function OnMsg.ClassesPreprocess(classdefs)
	classdefs.AppearanceObjectPart.flags.gofUnitLighting = true
end