-
Notifications
You must be signed in to change notification settings - Fork 2
/
Copy pathcockpitlook.cpp
2453 lines (2206 loc) · 93.8 KB
/
cockpitlook.cpp
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
/*
* Copyright 2019, Justagai.
* Extended for VR by Leo Reyes and Marcos Orallo, 2019, 2020, 2021, 2022.
* Justagai coded the whole mouse hook, I (blue_max) just added support for FreePIE and SteamVR tracking.
* December 2019: Added support for positional tracking by hijacking the cockpit shake variables.
The function that process keyboard input is L004FBA80. The pressed key is stored in the variable at
address 0x08053C0. You may want to hook this function and filter the pressed key.
*/
// TODO: Move the initialization of FreePIE/SteamVR to the dllmain's DLL_PROCESS_ATTACH (assuming this is possible)
#include "cockpitlook.h"
#include "XWAFramework.h"
#include "targetver.h"
#include "Hex.h"
#include <windows.h>
#include <Stdio.h>
#include <stdarg.h>
#include "FreePIE.h"
#include "SteamVR.h"
#include "TrackIR.h"
#include "Vectors.h"
#include "Matrices.h"
#include "UDP.h"
#include "YawVR.h"
#include "Telemetry.h"
#include "SharedMem.h"
// Unfortunately, applying roll inertia modifies the worldview transform in such a way
// that roll inertia is "inherited" to the lights, causing shadows to "dance" in VR.
// Not sure how to keep roll inertia and fix the shadows, so, disabling this feature for now
//#define APPLY_ROLL_INERTIA 1
#undef APPLY_ROLL_INERTIA
// TrackIR requires an HWND to register, so let's keep track of one.
HWND g_hWnd = NULL;
extern bool g_bSteamVRInitialized;
bool g_bForceSteamVRShutdown = false;
bool g_bHeadtrackingApplied = false;
vr::TrackedDevicePose_t g_hmdPose;
Matrix3 g_headRotation;
float g_headYaw = 0.0f, g_headPitch = 0.0f, g_headRoll = 0.0f, g_rollInertia = 0.0f;
vr::HmdQuaternionf_t rotationToQuaternion(vr::HmdMatrix34_t m);
void quatToEuler(vr::HmdQuaternionf_t q, float *yaw, float *roll, float *pitch);
#define DEBUG_TO_FILE 1
//#undef DEBUG_TO_FILE
#ifdef DEBUG_TO_FILE
FILE *g_DebugFile = NULL;
#endif
#define DEBUG_INERTIA 0
#if DEBUG_INERTIA == 1
#include <vector>
class Inertia {
public:
float LastSpeed, CurSpeed, RawAccelDisp, ClampedAccelDisp;
};
std::vector<Inertia> g_InertiaData;
#endif
void log_debug(const char *format, ...)
{
static char buf[300];
static char out[300];
#ifdef DEBUG_TO_FILE
if (g_DebugFile == NULL) {
try {
errno_t error = fopen_s(&g_DebugFile, "./CockpitLook.log", "wt");
}
catch (...) {
OutputDebugString("[DBG] [Cockpitlook] Could not open CockpitLook.log");
}
}
#endif
va_list args;
va_start(args, format);
vsprintf_s(buf, 300, format, args);
sprintf_s(out, 300, "[DBG] [Cockpitlook] %s\n", buf);
OutputDebugString(out);
#ifdef DEBUG_TO_FILE
if (g_DebugFile != NULL) {
fprintf(g_DebugFile, "%s\n", buf);
fflush(g_DebugFile);
}
#endif
va_end(args);
}
bool g_bGlobalDebug = false;
/*
* HYPERSPACE variables
*/
HyperspacePhaseEnum g_HyperspacePhaseFSM = HS_INIT_ST, g_PrevHyperspacePhaseFSM = HS_INIT_ST;
bool g_bHyperspaceFirstFrame = false, g_bInHyperspace = false, g_bHyperspaceLastFrame = false, g_bHyperspaceTunnelLastFrame = false;
int g_iHyperspaceFrame = -1, g_iFramesSinceHyperExit = 60, g_iFramesSinceHyperTunnel = 60;
Vector4 g_LastRsBeforeHyperspace;
Vector4 g_LastFsBeforeHyperspace;
Matrix4 g_prevHeadingMatrix;
float g_fLastSpeedBeforeHyperspace = 0.0f;
/*
* Updates the hyperspace FSM. This is a "lightweight" version of the code in ddraw.
* Here, we mostly care about how to handle cockpit inertia when we jump into hyperspace.
* The problem is that XWA will snap the camera when entering hyperspace, and that will
* cause this hook to miscalculate the inertia on that frame. We need to ignore the frame
* where the head snaps since ddraw will restore the previous camera orientation on the next
* frame.
*/
void UpdateHyperspaceState(int playerIndex) {
g_PrevHyperspacePhaseFSM = g_HyperspacePhaseFSM;
// Reset the Hyperspace FSM regardless of the previous state. This helps reset the
// state if we quit on the middle of a movie that is playing back the hyperspace
// effect. If we do reset the FSM, we need to update the control variables too:
if (PlayerDataTable[playerIndex].hyperspacePhase == 0) {
g_bInHyperspace = false;
g_bHyperspaceLastFrame = (g_HyperspacePhaseFSM == HS_HYPER_EXIT_ST);
g_iHyperspaceFrame = -1;
g_HyperspacePhaseFSM = HS_INIT_ST;
/*if (g_bHyperspaceLastFrame) {
log_debug("yaw,pitch at hyper exit: %0.3f, %0.3f",
PlayerDataTable[playerIndex].yaw / 65536.0f * 360.0f,
PlayerDataTable[playerIndex].pitch / 65536.0f * 360.0f);
}*/
}
switch (g_HyperspacePhaseFSM) {
case HS_INIT_ST:
g_bInHyperspace = false;
g_bHyperspaceFirstFrame = false;
g_bHyperspaceTunnelLastFrame = false;
//g_bHyperspaceLastFrame = false; // No need to update this here, we do it at the beginning of this function
g_iHyperspaceFrame = -1;
if (PlayerDataTable[playerIndex].hyperspacePhase == 2) {
// Hyperspace has *just* been engaged. Save the current cockpit camera heading so we can restore it
g_bHyperspaceFirstFrame = true;
g_bInHyperspace = true;
g_iHyperspaceFrame = 0;
g_HyperspacePhaseFSM = HS_HYPER_ENTER_ST;
}
break;
case HS_HYPER_ENTER_ST:
g_bInHyperspace = true;
g_bHyperspaceFirstFrame = false;
g_bHyperspaceTunnelLastFrame = false;
g_bHyperspaceLastFrame = false;
g_iHyperspaceFrame++;
if (PlayerDataTable[playerIndex].hyperspacePhase == 4)
g_HyperspacePhaseFSM = HS_HYPER_TUNNEL_ST;
break;
case HS_HYPER_TUNNEL_ST:
g_bInHyperspace = true;
g_bHyperspaceFirstFrame = false;
g_bHyperspaceTunnelLastFrame = false;
g_bHyperspaceLastFrame = false;
if (PlayerDataTable[playerIndex].hyperspacePhase == 3) {
//log_debug("[DBG] [FSM] HS_HYPER_TUNNEL_ST --> HS_HYPER_EXIT_ST");
g_bHyperspaceTunnelLastFrame = true;
//g_bInHyperspace = true;
g_HyperspacePhaseFSM = HS_HYPER_EXIT_ST;
/*log_debug("yaw,pitch at hyper tunnel exit: %0.3f, %0.3f",
PlayerDataTable[playerIndex].yaw / 65536.0f * 360.0f,
PlayerDataTable[playerIndex].pitch / 65536.0f * 360.0f);
*/
}
break;
case HS_HYPER_EXIT_ST:
g_bInHyperspace = true;
g_bHyperspaceFirstFrame = false;
g_bHyperspaceTunnelLastFrame = false;
g_bHyperspaceLastFrame = false;
// If we're playing back a film, we may stop the movie while in hyperspace. In
// that case, we need to "hard-reset" the FSM to its initial state as soon as
// we see hyperspacePhase == 0 or we'll mess up the state. However, that means
// that the final transition must also be done at the beginning of this
// function
/*
if (PlayerDataTable[playerIndex].hyperspacePhase == 0) {
log_debug("[DBG] [FSM] HS_HYPER_EXIT_ST --> HS_INIT_ST");
g_bInHyperspace = false;
g_bHyperspaceLastFrame = true;
log_debug("g_bHyperspaceLastFrame <- true");
g_iHyperspaceFrame = -1;
g_HyperspacePhaseFSM = HS_INIT_ST;
}
*/
break;
}
}
void LoadParams();
// cockpitlook.cfg parameter names
const char *TRACKER_TYPE = "tracker_type"; // Defines which tracker to use
const char *TRACKER_TYPE_FREEPIE = "FreePIE"; // Use FreePIE as the tracker
const char *TRACKER_TYPE_STEAMVR = "SteamVR"; // Use SteamVR as the tracker
const char *TRACKER_TYPE_TRACKIR = "TrackIR"; // Use TrackIR (or OpenTrack) as the tracker
const char *TRACKER_TYPE_NONE = "None";
const char *DISABLE_HANGAR_RANDOM_CAMERA = "disable_hangar_random_camera";
const char *YAW_MULTIPLIER = "yaw_multiplier";
const char *PITCH_MULTIPLIER = "pitch_multiplier";
const char *ROLL_MULTIPLIER = "roll_multiplier";
const char *YAW_OFFSET = "yaw_offset";
const char *PITCH_OFFSET = "pitch_offset";
const char *ROLL_OFFSET = "roll_offset";
const char *FREEPIE_SLOT = "freepie_slot";
const char *POS_X_MULTIPLIER_VRPARAM = "positional_x_multiplier";
const char *POS_Y_MULTIPLIER_VRPARAM = "positional_y_multiplier";
const char *POS_Z_MULTIPLIER_VRPARAM = "positional_z_multiplier";
const char *MIN_POSITIONAL_X_VRPARAM = "min_positional_track_x";
const char *MAX_POSITIONAL_X_VRPARAM = "max_positional_track_x";
const char *MIN_POSITIONAL_Y_VRPARAM = "min_positional_track_y";
const char *MAX_POSITIONAL_Y_VRPARAM = "max_positional_track_y";
const char *MIN_POSITIONAL_Z_VRPARAM = "min_positional_track_z";
const char *MAX_POSITIONAL_Z_VRPARAM = "max_positional_track_z";
// Tracker-specific constants
// Some people might want to use the regular (non-VR) game with a tracker. In that case
// they usually want to be able to look around the cockpit while still having the screen
// in front of them, so we need a multiplier for the yaw and pitch. Or, you know, just
// give users the option to invert the axis if needed.
const float DEFAULT_YAW_MULTIPLIER = 1.0f;
const float DEFAULT_PITCH_MULTIPLIER = 1.0f;
const float DEFAULT_ROLL_MULTIPLIER = 1.0f;
const float DEFAULT_YAW_OFFSET = 0.0f;
const float DEFAULT_PITCH_OFFSET = 0.0f;
const float DEFAULT_ROLL_OFFSET = 0.0f;
const int DEFAULT_FREEPIE_SLOT = 0;
// See https://docs.microsoft.com/en-us/windows/win32/inputdev/virtual-key-codes
// for a full list of virtual key codes
const int VK_I_KEY = 0x49; // Ctrl+I is used to toggle cockpit inertia
const int VK_J_KEY = 0x4a; // Ctrl+J is used to reload the cockpitlook.cfg params
const int VK_T_KEY = 0x54;
const int VK_U_KEY = 0x55;
//const int VK_K_KEY = 0x4b;
//const int VK_L_KEY = 0x4c;
const int VK_X_KEY = 0x58; // Ctrl+X is used to dump debug info
bool g_bNumPadAdd = false, g_bNumPadSub = false, g_bCtrl = false, g_bAlt;
bool g_bNumPad7 = false, g_bNumPad9 = false;
// General types and globals
typedef enum {
TRACKER_NONE,
TRACKER_FREEPIE,
TRACKER_STEAMVR,
TRACKER_TRACKIR
} TrackerType;
TrackerType g_TrackerType = TRACKER_NONE;
float g_fYawMultiplier = DEFAULT_YAW_MULTIPLIER;
float g_fPitchMultiplier = DEFAULT_PITCH_MULTIPLIER;
float g_fRollMultiplier = DEFAULT_ROLL_MULTIPLIER;
float g_fYawOffset = DEFAULT_YAW_OFFSET;
float g_fPitchOffset = DEFAULT_PITCH_OFFSET;
float g_fRollOffset = DEFAULT_ROLL_OFFSET;
int g_iFreePIESlot = DEFAULT_FREEPIE_SLOT;
bool g_bYawPitchFromMouseOverride = false;
bool g_bKeyboardLean = false, g_bKeyboardLook = false;
bool g_bTestJoystick = true;
Vector4 g_headCenter(0, 0, 0, 0), g_headPos(0, 0, 0, 0), g_headRotationHome(0, 0, 0, 0);
Vector3 g_headPosFromKeyboard(0, 0, 0);
Vector4 g_prevRs(1, 0, 0, 0);
Vector4 g_prevFs(0, 0, 1, 0);
int g_FreePIEOutputSlot = -1;
bool g_bTrackIRLoaded = false;
int g_iNumPadSpeed = -1;
const auto XwaGetConnectedJoysticksCount = (int(*)())0x00541030;
/*********************************************************************/
/* Cockpit Inertia */
/*********************************************************************/
bool g_bCockpitInertiaEnabled = false, g_bExtInertiaEnabled = false, g_bEnableExternalInertiaInHangar = false;
// External inertia does not work when the map is active. However, in previous versions,it was still applied
// anyway. This caused the distance and orientation from the map to be applied to the external view
// once the map is turned off. Some people may be using this as a "feature" to set up external
// shots from a distance; but I think most people don't expect this behavior. The following flag
// will control whether external inertia is still applied when the map is displayed so that people can
// decide if they want the map to affect the external view.
bool g_bEnableExternalInertiaInMap = false;
// The following flag is set when the MapCameraUpdateHook() is running, so that the external inertia
// can be disabled while the map is displayed.
bool g_bInsideMapCameraUpdateHook = false;
float g_fCockpitInertia = 0.35f, g_fCockpitSpeedInertia = 0.005f, g_fExtDistInertia = 0.0f;
float g_fCockpitMaxInertia = 0.2f, g_fExtInertia = -16384.0f, g_fExtMaxInertia = 0.025f;
short g_externalTilt = -1820; // -10 degrees
// tilt = degrees * 32768 / 180
/*********************************************************************/
/* Code used to enable leaning in the cockpit with the arrow keys */
/*********************************************************************/
// In XWA, the Y+ axis points forward (towards the horizon) and Z+ points up.
// To make this consistent with regular PixelShader coords, we need to swap these
// coordinates using a rotation and reflection. The following matrix stores that
// transformation and is only used in GetCurrentHeadingMatrix().
Matrix4 g_ReflRotX;
void InitHeadingMatrix() {
/*
Matrix4 rotX, refl;
rotX.identity();
rotX.rotateX(90.0f);
refl.set(
1, 0, 0, 0,
0, -1, 0, 0,
0, 0, 1, 0,
0, 0, 0, 1
);
g_ReflRotX = refl * rotX;
*/
g_ReflRotX.set(
1.0, 0.0, 0.0, 0.0, // 1st column
0.0, 0.0, 1.0, 0.0,
0.0, 1.0, 0.0, 0.0,
0.0, 0.0, 0.0, 1.0
);
}
inline float clamp(float x, const float lowerlimit, const float upperlimit) {
if (x < lowerlimit) x = lowerlimit; else if (x > upperlimit) x = upperlimit;
return x;
}
inline float smoothstep(const float min, const float max, float x) {
// Scale, bias and saturate x to 0..1 range
x = clamp((x - min) / (max - min), 0.0f, 1.0f);
// Evaluate polynomial
return x * x * (3.0f - 2.0f * x);
}
inline float lerp(const float x, const float y, const float s) {
return x + s * (y - x);
}
void Matrix4toHmdMatrix34(const Matrix4& m4, vr::HmdMatrix34_t& mat) {
mat.m[0][0] = m4[0]; mat.m[1][0] = m4[1]; mat.m[2][0] = m4[2];
mat.m[0][1] = m4[4]; mat.m[1][1] = m4[5]; mat.m[2][1] = m4[6];
mat.m[0][2] = m4[8]; mat.m[1][2] = m4[9]; mat.m[2][2] = m4[10];
mat.m[0][3] = m4[12]; mat.m[1][3] = m4[13]; mat.m[2][3] = m4[14];
}
/*
* Compute the current ship's orientation. Returns:
* Rs: The "Right" vector in global coordinates
* Us: The "Up" vector in global coordinates
* Fs: The "Forward" vector in global coordinates
* A viewMatrix that maps [Rs, Us, Fs] to the major [X, Y, Z] axes
*/
Matrix4 GetCurrentHeadingMatrix(int playerIndex, Vector4 &Rs, Vector4 &Us, Vector4 &Fs, bool invert = false)
{
const float DEG2RAD = 3.141593f / 180;
float yaw, pitch, roll;
Matrix4 rotMatrixFull, rotMatrixYaw, rotMatrixPitch, rotMatrixRoll;
Vector4 T, B, N;
// Compute the full rotation
yaw = PlayerDataTable[playerIndex].Camera.CraftYaw / 65536.0f * 360.0f;
pitch = PlayerDataTable[playerIndex].Camera.CraftPitch / 65536.0f * 360.0f;
roll = PlayerDataTable[playerIndex].Camera.CraftRoll / 65536.0f * 360.0f;
// yaw-pitch-roll gets reset to: ypr: 0.000, 90.000, 0.000 when entering hyperspace
/*if (!g_bInHyperspace)
log_debug("ypr: %0.3f, %0.3f, %0.3f", yaw, pitch, roll);
else
log_debug("[H] ypr: %0.3f, %0.3f, %0.3f", yaw, pitch, roll);*/
// To test how (x,y,z) is aligned with either the Y+ or Z+ axis, just multiply rotMatrixPitch * rotMatrixYaw * (x,y,z)
//Matrix4 rotMatrixFull, rotMatrixYaw, rotMatrixPitch, rotMatrixRoll;
rotMatrixFull.identity();
rotMatrixYaw.identity(); rotMatrixYaw.rotateY(-yaw);
rotMatrixPitch.identity(); rotMatrixPitch.rotateX(-pitch);
rotMatrixRoll.identity(); rotMatrixRoll.rotateY(roll);
// rotMatrixYaw aligns the orientation with the y-z plane (x --> 0)
// rotMatrixPitch * rotMatrixYaw aligns the orientation with y+ (x --> 0 && z --> 0)
// so the remaining rotation must be around the y axis (?)
// DEBUG, x = z, y = x, z = y;
// The yaw is indeed the y-axis rotation, it goes from -180 to 0 to 180.
// When pitch == 90, the craft is actually seeing the horizon
// When pitch == 0, the craft is looking towards the sun
// New approach: let's build a TBN system here to avoid the gimbal lock problem
float cosTheta, cosPhi, sinTheta, sinPhi;
cosTheta = cos(yaw * DEG2RAD), sinTheta = sin(yaw * DEG2RAD);
cosPhi = cos(pitch * DEG2RAD), sinPhi = sin(pitch * DEG2RAD);
N.z = cosTheta * sinPhi;
N.x = sinTheta * sinPhi;
N.y = cosPhi;
N.w = 0;
// This transform chain will always transform (N.x,N.y,N.z) into (0, 1, 0)
// To make an orthonormal basis, we need x+ and z+
N = rotMatrixPitch * rotMatrixYaw * N;
//log_debug("[DBG] N(DEBUG): %0.3f, %0.3f, %0.3f", N.x, N.y, N.z); // --> displays (0,1,0)
B.x = 0; B.y = 0; B.z = -1; B.w = 0;
T.x = 1; T.y = 0; T.z = 0; T.w = 0;
B = rotMatrixRoll * B;
T = rotMatrixRoll * T;
// Our new basis is T,B,N; but we need to invert the yaw/pitch rotation we applied
rotMatrixFull = rotMatrixPitch * rotMatrixYaw;
rotMatrixFull.invert();
T = rotMatrixFull * T;
B = rotMatrixFull * B;
N = rotMatrixFull * N;
// Our TBN basis is now in absolute coordinates
Fs = g_ReflRotX * N;
Us = g_ReflRotX * B;
Rs = g_ReflRotX * T;
Fs.w = 0; Rs.w = 0; Us.w = 0;
// This transform chain gets us the orientation of the craft in XWA's coord system:
// [1,0,0] is right, [0,1,0] is forward, [0,0,1] is up
Matrix4 viewMatrix;
if (!invert) { // Transform current ship's heading to Global Coordinates (Major Axes)
viewMatrix = Matrix4(
Rs.x, Us.x, Fs.x, 0,
Rs.y, Us.y, Fs.y, 0,
Rs.z, Us.z, Fs.z, 0,
0, 0, 0, 1
);
// Rs, Us, Fs is an orthonormal basis
}
else { // Transform Global Coordinates to the Ship's Coordinate System
viewMatrix = Matrix4(
Rs.x, Rs.y, Rs.z, 0,
Us.x, Us.y, Us.z, 0,
Fs.x, Fs.y, Fs.z, 0,
0, 0, 0, 1
);
// Rs, Us, Fs is an orthonormal basis
}
// Store data for the next frame if we're not in hyperspace
if (!g_bInHyperspace || g_HyperspacePhaseFSM == HS_HYPER_EXIT_ST) {
g_LastRsBeforeHyperspace = Rs;
g_LastFsBeforeHyperspace = Fs;
g_prevHeadingMatrix = viewMatrix;
g_fLastSpeedBeforeHyperspace = (float)PlayerDataTable[playerIndex].currentSpeed;
}
return viewMatrix;
}
/*
* Computes cockpit inertia
* Input:
* H: The current Heading matrix
* Fs: The forward vector
* Rs: The right vector
* Output: The X,Y,Z displacement plus the acceleration inertia
*/
void ComputeInertia(const Matrix4 &H, Vector4 Rs, Vector4 Fs, float fRawCurSpeed, int playerIndex, float *XDisp, float *YDisp, float *ZDisp, float *AccelDisp) {
static bool bFirstFrame = true;
static float fCurSpeed = 0.0f;
static float fLastSpeed = 0.0f;
static time_t prevT = 0;
time_t curT = time(NULL);
bool InertiaEnabled = g_bCockpitInertiaEnabled || g_bExtInertiaEnabled;
#if DEBUG_INERTIA == 1
Inertia inertia;
#endif
// Smooth the speed. The speed is stored as an integer by the game so it will
// have discreet jumps that will be noticed as jerkiness in the cockpit.
// Let's smooth the speed before we use it to compute inertia:
fCurSpeed = 0.05f * fRawCurSpeed + 0.95f * fCurSpeed;
// Reset the first frame if the time between successive queries is too big: this
// implies the game was either paused or a new mission was loaded
bFirstFrame = curT - prevT > 2; // Reset if +2s have elapsed
// Skip the very first frame: there's no inertia to compute yet
if (bFirstFrame || !InertiaEnabled || g_bHyperspaceTunnelLastFrame || g_bHyperspaceLastFrame)
{
bFirstFrame = false;
*XDisp = *YDisp = *ZDisp = *AccelDisp = 0.0f;
//log_debug("Resetting X/Y/ZDisp");
// Update the previous heading vectors
g_prevRs = Rs;
g_prevFs = Fs;
prevT = curT;
fLastSpeed = fCurSpeed;
return;
}
Matrix4 HT = H;
HT.transpose();
// Multiplying the current Rs, Us, Fs with H will yield the major axes:
//Vector4 X = HT * Rs; // --> always returns [1, 0, 0]
//Vector4 Y = HT * Us; // --> always returns [0, 1, 0]
//Vector4 Z = HT * Fs; // --> always returns [0, 0, 1]
//log_debug("[DBG] X: [%0.3f, %0.3f, %0.3f], Y: [%0.3f, %0.3f, %0.3f], Z: [%0.3f, %0.3f, %0.3f]",
// X.x, X.y, X.z, Y.x, Y.y, Y.z, Z.x, Z.y, Z.z);
Vector4 X = HT * g_prevRs; // --> returns something close to [1, 0, 0]
//Vector4 Y = HT * g_prevUs; // --> returns something close to [0, 1, 0]
Vector4 Z = HT * g_prevFs; // --> returns something close to [0, 0, 1]
//log_debug("[DBG] X: [%0.3f, %0.3f, %0.3f], Y: [%0.3f, %0.3f, %0.3f], Z: [%0.3f, %0.3f, %0.3f]",
// X.x, X.y, X.z, Y.x, Y.y, Y.z, Z.x, Z.y, Z.z);
Vector4 curRs(1, 0, 0, 0), curFs(0, 0, 1, 0);
Vector4 diffX = curRs - X;
Vector4 diffZ = curFs - Z;
float MaxAngularInertia = g_fCockpitMaxInertia * 60.0f;
*XDisp = g_fCockpitInertia * diffZ.x;
*YDisp = g_fCockpitInertia * diffZ.y;
*ZDisp = g_fCockpitInertia * 60.0f * diffX.y; // Roll inertia
*AccelDisp = -g_fCockpitSpeedInertia * (fCurSpeed - fLastSpeed);
#if DEBUG_INERTIA == 1
inertia.LastSpeed = fLastSpeed;
inertia.CurSpeed = fCurSpeed;
inertia.RawAccelDisp = *AccelDisp;
#endif
if (*XDisp < -g_fCockpitMaxInertia) *XDisp = -g_fCockpitMaxInertia; else if (*XDisp > g_fCockpitMaxInertia) *XDisp = g_fCockpitMaxInertia;
if (*YDisp < -g_fCockpitMaxInertia) *YDisp = -g_fCockpitMaxInertia; else if (*YDisp > g_fCockpitMaxInertia) *YDisp = g_fCockpitMaxInertia;
if (*ZDisp < -MaxAngularInertia) *ZDisp = -MaxAngularInertia; else if (*ZDisp > MaxAngularInertia) *ZDisp = MaxAngularInertia;
if (*AccelDisp < -g_fCockpitMaxInertia) *AccelDisp = -g_fCockpitMaxInertia; else if (*AccelDisp > g_fCockpitMaxInertia) *AccelDisp = g_fCockpitMaxInertia;
#if DEBUG_INERTIA == 1
inertia.ClampedAccelDisp = *AccelDisp;
g_InertiaData.push_back(inertia);
#endif
// Update the previous heading smoothly, otherwise the cockpit may shake a bit
g_prevRs = 0.05f * Rs + 0.95f * g_prevRs;
g_prevFs = 0.05f * Fs + 0.95f * g_prevFs;
fLastSpeed = 0.1f * fCurSpeed + 0.9f * fLastSpeed;
prevT = curT;
if (g_HyperspacePhaseFSM == HS_HYPER_EXIT_ST || g_bHyperspaceLastFrame)
{
*AccelDisp = 0.0f;
fLastSpeed = fCurSpeed;
}
//if (g_HyperspacePhaseFSM == HS_HYPER_ENTER_ST || g_HyperspacePhaseFSM == HS_INIT_ST)
//if (g_bHyperspaceLastFrame || g_bHyperspaceTunnelLastFrame)
// log_debug("[%d] X/YDisp: %0.3f, %0.3f", g_iHyperspaceFrame, *XDisp, *YDisp);
}
/*
* Takes a [yaw,pitch] "linear" inertia vector and returns a smooth transition between 0 at
* the origin and 1 near the edges.
*/
void SmoothInertia(float *inout_yawInertia, float *inout_pitchInertia)
{
float yawInertia = *inout_yawInertia;
float pitchInertia = *inout_pitchInertia;
// First, compute the length of the inertia vector:
float x, L = sqrt(yawInertia * yawInertia + pitchInertia * pitchInertia);
// Normalize the [yawInertia, pitchInertia] vector, our vector is now unitary and lies
// in a circle around the origin
yawInertia /= L; pitchInertia /= L;
// Normalize the range of L between 0 and +1. We'll clamp it to 1 using smoothstep below
x = L / g_fExtMaxInertia;
// Here, I'm dividing the smoothstep graph and taking the middle point to the right
// so that we get a curve that starts linear and then tapers off towards 1. Formally,
// I should multiply x by 0.5 below, but using 0.45 makes a nicer curve. See the following
// link to visualize the curve we're using:
// https://www.iquilezles.org/apps/graphtoy/?f1(x)=2.0%20*%20clamp(smoothstep(0,%201,%20x%20*%200.45%20+%200.5)%20-%200.5,%200.0,%201.0)
L = g_fExtMaxInertia * 2.0f * clamp(smoothstep(0.0f, 1.0f, x * 0.45f + 0.5f) - 0.5f, 0.0, 1.0f);
// The length of the vector now goes from 0 to g_fExtMaxInertia smoothly and tapers off
// when approaching g_fExtMaxInertia. Our [yawInertia, pitchInertia] vector is still unitary
// so we multiply it by the smooth L to extend it back to the right range:
yawInertia *= L; pitchInertia *= L;
*inout_yawInertia = yawInertia;
*inout_pitchInertia = pitchInertia;
}
/*
* Returns a matrix that transforms from canonical axes to the gunner turret frame of reference and
* then into world space. This function uses the craft's current heading internally.
* The matrix returned can be used to transform a vector in ViewSpace (like the SteamVR positional
* tracking data) into World space, so that the translation happens in the Gunner Turret framework.
*/
void GetGunnerTurretMatrix(Matrix4 *result) {
constexpr float factor = 32768.0f;
Vector3 F(
PlayerDataTable[*localPlayerIndex].gunnerTurretF[0] / factor,
PlayerDataTable[*localPlayerIndex].gunnerTurretF[1] / factor,
PlayerDataTable[*localPlayerIndex].gunnerTurretF[2] / factor
);
Vector3 R(
PlayerDataTable[*localPlayerIndex].gunnerTurretR[0] / factor,
PlayerDataTable[*localPlayerIndex].gunnerTurretR[1] / factor,
PlayerDataTable[*localPlayerIndex].gunnerTurretR[2] / factor
);
Vector3 U(
PlayerDataTable[*localPlayerIndex].gunnerTurretU[0] / factor,
PlayerDataTable[*localPlayerIndex].gunnerTurretU[1] / factor,
PlayerDataTable[*localPlayerIndex].gunnerTurretU[2] / factor
);
//log_debug("(1) R: [%0.3f, %0.3f, %0.3f], U: [%0.3f, %0.3f, %0.3f], F: [%0.3f, %0.3f, %0.3f]",
// R.x, R.y, R.z, U.x, U.y, U.z, F.x, F.y, F.z);
Vector4 Rs, Us, Fs;
Matrix4 Heading = GetCurrentHeadingMatrix(*localPlayerIndex, Rs, Us, Fs);
//log_debug("[DBG] [GUN] (H) Rs: [%0.3f, %0.3f, %0.3f], Us: [%0.3f, %0.3f, %0.3f], Fs: [%0.3f, %0.3f, %0.3f]",
// Rs.x, Rs.y, Rs.z, Us.x, Us.y, Us.z, Fs.x, Fs.y, Fs.z);
// This matrix transforms the Gunner Turret orientation to the canonical axes.
// Multiplying viewMatrixDebug * [R, U, F] should always map to [1,0,0], [0,-1,0], [0,0,1], even if the turret moves
/*
Matrix4 viewMatrixDebug = Matrix4(
R.x, -U.x, F.x, 0,
R.y, -U.y, F.y, 0,
R.z, -U.z, F.z, 0,
0, 0, 0, 1
);
*/
Matrix4 viewMatrixInv = Matrix4(
R.x, R.y, R.z, 0,
-U.x, -U.y, -U.z, 0,
F.x, F.y, F.z, 0,
0, 0, 0, 1
);
int curTurret = PlayerDataTable[*localPlayerIndex].gunnerTurretActive;
*result = Heading * viewMatrixInv;
// The YT-2000 has two (2) turrets and for some reason, the second turret has the R and F axes
// oriented "backwards" (it's rotated by 180 degrees around the U axis). If you transform both
// turrets' [R,U,F] by Heading, you'll notice that U = [-1,0,0] for both of them. But their
// R and F are flipped. This is equivalent to rotating around U (-X) by 180 degrees. I couldn't
// find a way to do this automatically, so here I'm adding the 180 degree rotation about U to
// take care of that. If people complain later that positional tracking is broken for their
// XYZ custom OPT, then we can come back here and maybe read additional data from external files
// to give us a hint how to fix this.
if (curTurret == 2)
*result = Matrix4().rotateX(180.0f) * (*result);
}
typedef struct HeadPosStruct {
float x, y, z;
} HeadPos;
/* Maps (-6, 6) to (-0.5, 0.5) using a sigmoid function */
float centeredSigmoid(float x) {
return 1.0f / (1.0f + exp(-x)) - 0.5f;
}
// TODO: Remove all these variables from ddraw once the migration is complete.
//float g_fXWAUnitsToMetersScale = 655.36f; // This is technically correct; but it seems too much for me
// float g_fXWAUnitsToMetersScale = 400.0f; // This value feels better
float g_fXWAUnitsToMetersScale = 25.0f; // New value to be applied in CockpitPositionReferenceHook
float g_fPosXMultiplier = 1.666f, g_fPosYMultiplier = 1.666f, g_fPosZMultiplier = 1.666f;
float g_fMinPositionX = -2.50f, g_fMaxPositionX = 2.50f;
float g_fMinPositionY = -2.50f, g_fMaxPositionY = 2.50f;
float g_fMinPositionZ = -2.50f, g_fMaxPositionZ = 2.50f;
HeadPos g_HeadPosAnim = { 0 };
bool g_bLeftKeyDown = false, g_bRightKeyDown = false, g_bUpKeyDown = false, g_bDownKeyDown = false;
bool g_bUpKeyDownShift = false, g_bDownKeyDownShift = false, g_bStickyArrowKeys = true, g_bLimitCockpitLean = true;
bool g_bInvertCockpitLeanY = false;
bool g_bResetHeadCenter = false, g_bSteamVRPosFromFreePIE = false;
bool g_bFlipYZAxes = false;
// if true then the arrow keys will modify the cockpit camera's yaw/pitch
// if false, then the arrow keys will perform lean right/left up/down
bool g_bToggleKeyboardCaps = false;
const float ANIM_INCR = 0.01f;
float MAX_LEAN_X = 25.0f, MAX_LEAN_Y = 25.0f, MAX_LEAN_Z = 25.0f;
const float RESET_ANIM_INCR = 2.0f * ANIM_INCR;
// The MAX_LEAN values will be clamped by the limits from vrparams.cfg
void animTickX(Vector3 *headPos) {
if (g_bRightKeyDown)
g_HeadPosAnim.x -= ANIM_INCR;
else if (g_bLeftKeyDown)
g_HeadPosAnim.x += ANIM_INCR;
else if (!g_bRightKeyDown && !g_bLeftKeyDown && !g_bStickyArrowKeys) {
if (g_HeadPosAnim.x < 0.0)
g_HeadPosAnim.x += RESET_ANIM_INCR;
if (g_HeadPosAnim.x > 0.0)
g_HeadPosAnim.x -= RESET_ANIM_INCR;
}
// Range clamping
if (g_bLimitCockpitLean) {
if (g_HeadPosAnim.x > MAX_LEAN_X) g_HeadPosAnim.x = MAX_LEAN_X;
if (g_HeadPosAnim.x < -MAX_LEAN_X) g_HeadPosAnim.x = -MAX_LEAN_X;
}
//headPos->x = centeredSigmoid(g_HeadPosAnim.x) * MAX_LEAN_X;
headPos->x = g_HeadPosAnim.x;
}
void animTickY(Vector3 *headPos) {
float sign = g_bInvertCockpitLeanY ? -1.0f : 1.0f;
if (g_bDownKeyDown)
g_HeadPosAnim.y += sign * ANIM_INCR;
else if (g_bUpKeyDown)
g_HeadPosAnim.y += -sign * ANIM_INCR;
else if (!g_bDownKeyDown && !g_bUpKeyDown && !g_bStickyArrowKeys) {
if (g_HeadPosAnim.y < 0.0)
g_HeadPosAnim.y += RESET_ANIM_INCR;
if (g_HeadPosAnim.y > 0.0)
g_HeadPosAnim.y -= RESET_ANIM_INCR;
}
// Range clamping
if (g_bLimitCockpitLean) {
if (g_HeadPosAnim.y > MAX_LEAN_Y) g_HeadPosAnim.y = MAX_LEAN_Y;
if (g_HeadPosAnim.y < -MAX_LEAN_Y) g_HeadPosAnim.y = -MAX_LEAN_Y;
}
//headPos->y = centeredSigmoid(g_HeadPosAnim.y) * MAX_LEAN_Y;
headPos->y = g_HeadPosAnim.y;
}
void animTickZ(Vector3 *headPos) {
if (g_bDownKeyDownShift)
g_HeadPosAnim.z -= ANIM_INCR;
else if (g_bUpKeyDownShift)
g_HeadPosAnim.z += ANIM_INCR;
else if (!g_bDownKeyDownShift && !g_bUpKeyDownShift && !g_bStickyArrowKeys) {
if (g_HeadPosAnim.z < 0.0)
g_HeadPosAnim.z += RESET_ANIM_INCR;
if (g_HeadPosAnim.z > 0.0 /* 0.0001 */)
g_HeadPosAnim.z -= RESET_ANIM_INCR;
}
// Range clamping
if (g_bLimitCockpitLean) {
if (g_HeadPosAnim.z > MAX_LEAN_Z) g_HeadPosAnim.z = MAX_LEAN_Z;
if (g_HeadPosAnim.z < -MAX_LEAN_Z) g_HeadPosAnim.z = -MAX_LEAN_Z;
}
//headPos->z = centeredSigmoid(g_HeadPosAnim.z) * MAX_LEAN_Z;
headPos->z = g_HeadPosAnim.z;
headPos->z = -headPos->z; // The z-axis is inverted in XWA w.r.t. the original view-centric definition
}
void DumpDebugInfo(int playerIndex) {
static int counter = 0;
FILE *filePD = NULL, *fileCI = NULL;
int error = 0;
char sFileNamePD[128], sFileNameCI[128];
sprintf_s(sFileNamePD, 128, "./PlayerDataTable%d", counter);
sprintf_s(sFileNameCI, 128, "./CraftInstance%d", counter);
counter++;
int16_t objectIndex = (int16_t)PlayerDataTable[*localPlayerIndex].objectIndex;
ObjectEntry *object = &((*objects)[objectIndex]);
MobileObjectEntry *mobileObject = object->MobileObjectPtr;
CraftInstance *craftInstance = mobileObject->craftInstancePtr;
log_debug("primarySecondaryArmed: %d, warheadArmed: %d",
PlayerDataTable[*localPlayerIndex].primarySecondaryArmed,
PlayerDataTable[*localPlayerIndex].warheadArmed);
//log_debug("activeWeapon: %d", PlayerDataTable[*localPlayerIndex].activeWeapon); // This was useless (it's always 1)
log_debug("WarheadArmed: %d, NumberOfLaserSets: %d, NumberOfLasers: %d, NumWarheadLauncherGroups: %d, Countermeasures: %d",
PlayerDataTable[*localPlayerIndex].warheadArmed,
craftInstance->NumberOfLaserSets,
craftInstance->NumberOfLasers,
craftInstance->NumWarheadLauncherGroups,
craftInstance->CountermeasureAmount);
//for (int i = 0; i < 2; i++) {
// For the first warhead group:
// WarheadNextHardpoint[0] is 1 when the left tube is ready and 129 (-1 for signed byte?) when the right tube is ready.
// For the second warhead group:
// WarheadNextHardpoint[1] is 1 when the left tube is ready, 129 when the right tube is ready.
//log_debug("WarheadNextHardpoint[%d]: %d", i, craftInstance->WarheadNextHardpoint[i]);
//}
/*
LaserNextHardpoint tells us which laser cannon is ready to fire.
LaserLinkStatus: 0x1 -- Single fire.
LaserLinkStatus: 0x2 -- Dual fire. LaserHardpoint will now be either 0x0 or 0x1 and it will skip one laser
LaserLinkStatus: 0x3 -- Fire all lasers in the current group. LaserHardpoint stays at 0x0 -- all cannons ready to fire
LaserLinkStatus: 0x4 -- Fire all lasers in all groups. All LaserLinkStatus indices become 0x4 as soon as one of them is 0x4
X/W, single fire:
[808][DBG][Cockpitlook] WarheadArmed: 0, NumberOfLaserSets : 1, NumberOfLasers : 4, NumWarheadLauncherGroups : 1, Countermeasures : 0
[808][DBG][Cockpitlook] LaserNextHardpoint[0] : 0x0,
[808][DBG][Cockpitlook] LaserNextHardpoint[0] : 0x1,
[808][DBG][Cockpitlook] LaserNextHardpoint[0] : 0x2,
[808][DBG][Cockpitlook] LaserNextHardpoint[0] : 0x3, ... then it goes back to 0
The other indices in LaserNextHardpoint remain at 0:
[808] [DBG] [Cockpitlook] LaserNextHardpoint[1]: 0x0, <-- This is the second set of lasers/ions (when it exists)
[808] [DBG] [Cockpitlook] LaserNextHardpoint[2]: 0x0, <-- Maybe the third set of lasers, if it exists?
For the B-Wing the first LaserNextHardpoint group goes 0x0, 0x1, 0x2
The second group (ions) goes 0x3, 0x4, 0x5.
LaserLinkStatus is either 0x1 or 0x3. There's no dual fire
The B-Wing has 6 lasers and 2 groups
For the T/D the first LaserNextHardpoint group goes 0x0, 0x1, 0x2, 0x3
The second group goes: 0x4, 0x5
LaserLinkStatus goes 0x1, 0x2, 0x3 and 0x4. There's single fire, dual fire, set fire and all sets fire.
The T/D has 6 lasers and 2 groups.
*/
for (int i = 0; i < 3; i++)
log_debug("LaserNextHardpoint[%d]: 0x%x, LaserLinkStatus[%d]: 0x%x",
i, craftInstance->LaserNextHardpoint[i],
i, craftInstance->LaserLinkStatus[i]);
/*
MIS:
[14848] [DBG] [Cockpitlook] NumberOfLaserSets: 1, NumberOfLasers: 1, NumWarheadLauncherGroups: 2
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[0]: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[1]: 1
[14848] [DBG] [Cockpitlook] [0] Type: 1, Count: 0 <-- Only 1 laser
[14848] [DBG] [Cockpitlook] [1] Type: 3, Count: 20 <-- First warhead group
[14848] [DBG] [Cockpitlook] [2] Type: 3, Count: 20
[14848] [DBG] [Cockpitlook] [3] Type: 3, Count: 20 <-- Second warhead group
[14848] [DBG] [Cockpitlook] [4] Type: 3, Count: 20
GUN:
2 laser sets (lasers, ion), 4 lasers in total (2 lasers 2 ion)
WeaponType 1 is laser, 2 is ion, 3 is missiles
[14848] [DBG] [Cockpitlook] NumberOfLaserSets: 2, NumberOfLasers: 4, NumWarheadLauncherGroups: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[0]: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[1]: 1
[14848] [DBG] [Cockpitlook] [0] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [1] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [2] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [3] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [4] Type: 3, Count: 8
[14848] [DBG] [Cockpitlook] [5] Type: 3, Count: 8
T/D:
2 laser sets (lasers, ion), 6 lasers in total (4 lasers, 2 ion)
[14848] [DBG] [Cockpitlook] NumberOfLaserSets: 2, NumberOfLasers: 6, NumWarheadLauncherGroups: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[0]: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[1]: 1
[14848] [DBG] [Cockpitlook] [0] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [1] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [2] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [3] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [4] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [5] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [6] Type: 3, Count: 1
[14848] [DBG] [Cockpitlook] [7] Type: 3, Count: 1
B/W:
[14848] [DBG] [Cockpitlook] NumberOfLaserSets: 2, NumberOfLasers: 6, NumWarheadLauncherGroups: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[0]: 1
[14848] [DBG] [Cockpitlook] WarheadNextHardpoint[1]: 1
[14848] [DBG] [Cockpitlook] [0] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [1] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [2] Type: 1, Count: 0 <-- Laser
[14848] [DBG] [Cockpitlook] [3] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [4] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [5] Type: 2, Count: 0 <-- Ion
[14848] [DBG] [Cockpitlook] [6] Type: 3, Count: 6
[14848] [DBG] [Cockpitlook] [7] Type: 3, Count: 6
*/
#ifdef DISPLAY_HARDPOINT_DEBUG_DATA
for (int i = 0; i < 16; i++) {
// WeaponType: 0 == None, 1 == Lasers? 3 == Concussion Missiles? 4 == Gunner hardpoint!
// NOTE: Gunner hardpoint's energy level never depletes.
if (craftInstance->Hardpoints[i].WeaponType == 0)
break;
log_debug("[%d] Type: %d, Count: %d, Energy: %d", i,
craftInstance->Hardpoints[i].WeaponType,
craftInstance->Hardpoints[i].Count, // This only seems to apply to warheads, for ions and lasers this is 0
craftInstance->Hardpoints[i].Energy // Only applies for lasers, max is 127, min is 0. For warheads, this is always 127
);
}
#endif
//log_debug("Throttle: %0.3f", (float)craftInstance->EngineThrottleInput / 65535.0f);
// 0x0001 is the CMD/Targeting computer.
// 0x000E is the laser/ion display. Looks like all 3 bits must be on, but not sure what happens if the craft doesn't have ions
// 0x0010 is the beam weapon
// 0x0020 is the shields display
// 0x0040 is the throttle (text) display
// 0x0180 both sensors. Both bits must be on, if either bit is off, both sensors will shut down
// 0x0200 lasers recharge rate
// 0x0400 engine level
// 0x0800 shields recharge rate
// 0x1000 beam recharge rate
/*
FILE *FileMask = NULL;
fopen_s(&FileMask, "CockpitDamage.txt", "rt");
if (FileMask != NULL) {
uint32_t Mask = 0x0;
fscanf_s(FileMask, "0x%x", &Mask);
fclose(FileMask);
log_debug("InitialCockpitInstruments: 0x%x, CockpitInstrumentStatus: 0x%x",
craftInstance->InitialCockpitInstruments, craftInstance->CockpitInstrumentStatus);
//log_debug("InitialSubsystems: 0x%x, SubsystemStatus: 0x%x",
// craftInstance->InitialSubsystems, craftInstance->SubsystemStatus);
craftInstance->CockpitInstrumentStatus = craftInstance->InitialCockpitInstruments & Mask;
log_debug("Current Cockpit Instruments: 0x%x", craftInstance->CockpitInstrumentStatus);
}
*/
//log_debug("External Camera Distance: %d", PlayerDataTable[playerIndex].Camera.ExternalCameraZoomDist);
//log_debug("Dumping Debug info...");
// Dump the current PlayerDataTable and CraftInstance
/*
try {
error = fopen_s(&filePD, sFileNamePD, "wb");
}
catch (...) {
log_debug("Could not create %s", sFileNamePD);
}
if (error != 0)
return;
size = fwrite(&(PlayerDataTable[*localPlayerIndex]), sizeof(PlayerDataEntry), 1, filePD);
fclose(filePD);
log_debug("Dumped %s", sFileNamePD);
try {
error = fopen_s(&fileCI, sFileNameCI, "wb");
}
catch (...) {
log_debug("Could not create %s", sFileNameCI);
}
if (error != 0)
return;
size = fwrite(craftInstance, sizeof(CraftInstance), 1, fileCI);
fclose(fileCI);
log_debug("Dumped %s", sFileNameCI);
*/
}
void ProcessKeyboard(int playerIndex, __int16 keycodePressed) {
static bool bLastIKeyState = false, bLastJKeyState = false, bLastXKeyState = false, bLastTKeyState = false, bLastUKeyState = false;
static bool bCurIKeyState = false, bCurJKeyState = false, bCurXKeyState = false, bCurTKeyState = false, bCurUKeyState = false;
static bool bLastPeriodKeyState = false, bCurPeriodKeyState = false;
//bool bControl = *s_XwaIsControlKeyPressed;
//bool bShift = *s_XwaIsShiftKeyPressed;
//bool bAlt = *s_XwaIsAltKeyPressed;
g_bCtrl = GetAsyncKeyState(VK_CONTROL);
bool bShift = GetAsyncKeyState(VK_SHIFT);
g_bAlt = GetAsyncKeyState(VK_MENU);
bool bRightAlt = GetAsyncKeyState(VK_RMENU);
bool bLeftAlt = GetAsyncKeyState(VK_LMENU);
g_bNumPadAdd = GetAsyncKeyState(VK_ADD);
g_bNumPadSub = GetAsyncKeyState(VK_SUBTRACT);
g_bNumPad7 = GetAsyncKeyState(VK_NUMPAD7);
g_bNumPad9 = GetAsyncKeyState(VK_NUMPAD9);
// I,J,X,T key states:
bLastIKeyState = bCurIKeyState;
bLastJKeyState = bCurJKeyState;
bLastXKeyState = bCurXKeyState;
bLastTKeyState = bCurTKeyState;
bLastUKeyState = bCurUKeyState;
bLastPeriodKeyState = bCurPeriodKeyState;
bCurJKeyState = GetAsyncKeyState(VK_J_KEY);
bCurIKeyState = GetAsyncKeyState(VK_I_KEY);
bCurXKeyState = GetAsyncKeyState(VK_X_KEY);
bCurTKeyState = GetAsyncKeyState(VK_T_KEY);
bCurUKeyState = GetAsyncKeyState(VK_U_KEY);
bCurPeriodKeyState = GetAsyncKeyState(VK_OEM_PERIOD);
//log_debug("L: %d, ACS: %d,%d,%d", bLKey, bAlt, bCtrl, bShift);
// It's not a good idea to use Ctrl+L to reload the cockpit look hook settings because
// Ctrl+L is already used by the landing gear hook. So, let's use a different key: J
if (g_bCtrl && bLastJKeyState && !bCurJKeyState)
{
log_debug("*********** RELOADING CockpitLookHook.cfg ***********");
LoadParams();
}
if (g_TrackerType == TRACKER_STEAMVR && (bLastPeriodKeyState && !bCurPeriodKeyState)) {
ResetZeroPose();
}
// Ctrl+X: Dump debug info
if (g_bCtrl && bLastXKeyState && !bCurXKeyState) {
//DumpDebugInfo(playerIndex);
}
// Alt+T: Reload TrackIR
if (g_TrackerType == TRACKER_TRACKIR)
{
if (g_bAlt && bLastTKeyState && !bCurTKeyState) {
if (g_bTrackIRLoaded) {
log_debug("Unloading TrackIR");
ShutdownTrackIR();