1/*
2Copyright (C) 1996-1997 Id Software, Inc.
3
4This program is free software; you can redistribute it and/or
5modify it under the terms of the GNU General Public License
6as published by the Free Software Foundation; either version 2
7of the License, or (at your option) any later version.
8
9This program is distributed in the hope that it will be useful,
10but WITHOUT ANY WARRANTY; without even the implied warranty of
11MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
12
13See the GNU General Public License for more details.
14
15You should have received a copy of the GNU General Public License
16along with this program; if not, write to the Free Software
17Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18
19*/
20// in_win.c -- windows 95 mouse and joystick code
21// 02/21/97 JCB Added extended DirectInput code to support external controllers.
22
23#include <dinput.h>
24#include "quakedef.h"
25#include "winquake.h"
26//#include "dosisms.h"
27
28#define DINPUT_BUFFERSIZE           16
29#define iDirectInputCreate(a,b,c,d)	pDirectInputCreate(a,b,c,d)
30
31HRESULT (WINAPI *pDirectInputCreate)(HINSTANCE hinst, DWORD dwVersion,
32	LPDIRECTINPUT * lplpDirectInput, LPUNKNOWN punkOuter);
33
34// mouse variables
35cvar_t	m_filter = {"m_filter","0"};
36
37int			mouse_buttons;
38int			mouse_oldbuttonstate;
39POINT		current_pos;
40int			mouse_x, mouse_y, old_mouse_x, old_mouse_y, mx_accum, my_accum;
41
42static qboolean	restore_spi;
43static int		originalmouseparms[3], newmouseparms[3] = {0, 0, 1};
44qboolean		mouseinitialized;
45static qboolean	mouseparmsvalid, mouseactivatetoggle;
46static qboolean	mouseshowtoggle = 1;
47static qboolean	dinput_acquired;
48static unsigned int		mstate_di;
49unsigned int uiWheelMessage;
50
51qboolean	mouseactive;
52
53// joystick defines and variables
54// where should defines be moved?
55#define JOY_ABSOLUTE_AXIS	0x00000000		// control like a joystick
56#define JOY_RELATIVE_AXIS	0x00000010		// control like a mouse, spinner, trackball
57#define	JOY_MAX_AXES		6				// X, Y, Z, R, U, V
58#define JOY_AXIS_X			0
59#define JOY_AXIS_Y			1
60#define JOY_AXIS_Z			2
61#define JOY_AXIS_R			3
62#define JOY_AXIS_U			4
63#define JOY_AXIS_V			5
64
65enum _ControlList
66{
67	AxisNada = 0, AxisForward, AxisLook, AxisSide, AxisTurn
68};
69
70DWORD	dwAxisFlags[JOY_MAX_AXES] =
71{
72	JOY_RETURNX, JOY_RETURNY, JOY_RETURNZ, JOY_RETURNR, JOY_RETURNU, JOY_RETURNV
73};
74
75DWORD	dwAxisMap[JOY_MAX_AXES];
76DWORD	dwControlMap[JOY_MAX_AXES];
77PDWORD	pdwRawValue[JOY_MAX_AXES];
78
79// none of these cvars are saved over a session
80// this means that advanced controller configuration needs to be executed
81// each time.  this avoids any problems with getting back to a default usage
82// or when changing from one controller to another.  this way at least something
83// works.
84cvar_t	in_joystick = {"joystick","0", true};
85cvar_t	joy_name = {"joyname", "joystick"};
86cvar_t	joy_advanced = {"joyadvanced", "0"};
87cvar_t	joy_advaxisx = {"joyadvaxisx", "0"};
88cvar_t	joy_advaxisy = {"joyadvaxisy", "0"};
89cvar_t	joy_advaxisz = {"joyadvaxisz", "0"};
90cvar_t	joy_advaxisr = {"joyadvaxisr", "0"};
91cvar_t	joy_advaxisu = {"joyadvaxisu", "0"};
92cvar_t	joy_advaxisv = {"joyadvaxisv", "0"};
93cvar_t	joy_forwardthreshold = {"joyforwardthreshold", "0.15"};
94cvar_t	joy_sidethreshold = {"joysidethreshold", "0.15"};
95cvar_t	joy_pitchthreshold = {"joypitchthreshold", "0.15"};
96cvar_t	joy_yawthreshold = {"joyyawthreshold", "0.15"};
97cvar_t	joy_forwardsensitivity = {"joyforwardsensitivity", "-1.0"};
98cvar_t	joy_sidesensitivity = {"joysidesensitivity", "-1.0"};
99cvar_t	joy_pitchsensitivity = {"joypitchsensitivity", "1.0"};
100cvar_t	joy_yawsensitivity = {"joyyawsensitivity", "-1.0"};
101cvar_t	joy_wwhack1 = {"joywwhack1", "0.0"};
102cvar_t	joy_wwhack2 = {"joywwhack2", "0.0"};
103
104qboolean	joy_avail, joy_advancedinit, joy_haspov;
105DWORD		joy_oldbuttonstate, joy_oldpovstate;
106
107int			joy_id;
108DWORD		joy_flags;
109DWORD		joy_numbuttons;
110
111static LPDIRECTINPUT		g_pdi;
112static LPDIRECTINPUTDEVICE	g_pMouse;
113
114static JOYINFOEX	ji;
115
116static HINSTANCE hInstDI;
117
118static qboolean	dinput;
119
120typedef struct MYDATA {
121	LONG  lX;                   // X axis goes here
122	LONG  lY;                   // Y axis goes here
123	LONG  lZ;                   // Z axis goes here
124	BYTE  bButtonA;             // One button goes here
125	BYTE  bButtonB;             // Another button goes here
126	BYTE  bButtonC;             // Another button goes here
127	BYTE  bButtonD;             // Another button goes here
128} MYDATA;
129
130static DIOBJECTDATAFORMAT rgodf[] = {
131  { &GUID_XAxis,    FIELD_OFFSET(MYDATA, lX),       DIDFT_AXIS | DIDFT_ANYINSTANCE,   0,},
132  { &GUID_YAxis,    FIELD_OFFSET(MYDATA, lY),       DIDFT_AXIS | DIDFT_ANYINSTANCE,   0,},
133  { &GUID_ZAxis,    FIELD_OFFSET(MYDATA, lZ),       0x80000000 | DIDFT_AXIS | DIDFT_ANYINSTANCE,   0,},
134  { 0,              FIELD_OFFSET(MYDATA, bButtonA), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
135  { 0,              FIELD_OFFSET(MYDATA, bButtonB), DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
136  { 0,              FIELD_OFFSET(MYDATA, bButtonC), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
137  { 0,              FIELD_OFFSET(MYDATA, bButtonD), 0x80000000 | DIDFT_BUTTON | DIDFT_ANYINSTANCE, 0,},
138};
139
140#define NUM_OBJECTS (sizeof(rgodf) / sizeof(rgodf[0]))
141
142static DIDATAFORMAT	df = {
143	sizeof(DIDATAFORMAT),       // this structure
144	sizeof(DIOBJECTDATAFORMAT), // size of object data format
145	DIDF_RELAXIS,               // absolute axis coordinates
146	sizeof(MYDATA),             // device data size
147	NUM_OBJECTS,                // number of objects
148	rgodf,                      // and here they are
149};
150
151// forward-referenced functions
152void IN_StartupJoystick (void);
153void Joy_AdvancedUpdate_f (void);
154void IN_JoyMove (usercmd_t *cmd);
155
156
157/*
158===========
159Force_CenterView_f
160===========
161*/
162void Force_CenterView_f (void)
163{
164	cl.viewangles[PITCH] = 0;
165}
166
167
168/*
169===========
170IN_UpdateClipCursor
171===========
172*/
173void IN_UpdateClipCursor (void)
174{
175
176	if (mouseinitialized && mouseactive && !dinput)
177	{
178		ClipCursor (&window_rect);
179	}
180}
181
182
183/*
184===========
185IN_ShowMouse
186===========
187*/
188void IN_ShowMouse (void)
189{
190
191	if (!mouseshowtoggle)
192	{
193		ShowCursor (TRUE);
194		mouseshowtoggle = 1;
195	}
196}
197
198
199/*
200===========
201IN_HideMouse
202===========
203*/
204void IN_HideMouse (void)
205{
206
207	if (mouseshowtoggle)
208	{
209		ShowCursor (FALSE);
210		mouseshowtoggle = 0;
211	}
212}
213
214
215/*
216===========
217IN_ActivateMouse
218===========
219*/
220void IN_ActivateMouse (void)
221{
222
223	mouseactivatetoggle = true;
224
225	if (mouseinitialized)
226	{
227		if (dinput)
228		{
229			if (g_pMouse)
230			{
231				if (!dinput_acquired)
232				{
233					IDirectInputDevice_Acquire(g_pMouse);
234					dinput_acquired = true;
235				}
236			}
237			else
238			{
239				return;
240			}
241		}
242		else
243		{
244			if (mouseparmsvalid)
245				restore_spi = SystemParametersInfo (SPI_SETMOUSE, 0, newmouseparms, 0);
246
247			SetCursorPos (window_center_x, window_center_y);
248			SetCapture (mainwindow);
249			ClipCursor (&window_rect);
250		}
251
252		mouseactive = true;
253	}
254}
255
256
257/*
258===========
259IN_SetQuakeMouseState
260===========
261*/
262void IN_SetQuakeMouseState (void)
263{
264	if (mouseactivatetoggle)
265		IN_ActivateMouse ();
266}
267
268
269/*
270===========
271IN_DeactivateMouse
272===========
273*/
274void IN_DeactivateMouse (void)
275{
276
277	mouseactivatetoggle = false;
278
279	if (mouseinitialized)
280	{
281		if (dinput)
282		{
283			if (g_pMouse)
284			{
285				if (dinput_acquired)
286				{
287					IDirectInputDevice_Unacquire(g_pMouse);
288					dinput_acquired = false;
289				}
290			}
291		}
292		else
293		{
294		if (restore_spi)
295			SystemParametersInfo (SPI_SETMOUSE, 0, originalmouseparms, 0);
296
297		ClipCursor (NULL);
298		ReleaseCapture ();
299	}
300
301		mouseactive = false;
302	}
303}
304
305
306/*
307===========
308IN_RestoreOriginalMouseState
309===========
310*/
311void IN_RestoreOriginalMouseState (void)
312{
313	if (mouseactivatetoggle)
314	{
315		IN_DeactivateMouse ();
316		mouseactivatetoggle = true;
317	}
318
319// try to redraw the cursor so it gets reinitialized, because sometimes it
320// has garbage after the mode switch
321	ShowCursor (TRUE);
322	ShowCursor (FALSE);
323}
324
325
326/*
327===========
328IN_InitDInput
329===========
330*/
331qboolean IN_InitDInput (void)
332{
333    HRESULT		hr;
334	DIPROPDWORD	dipdw = {
335		{
336			sizeof(DIPROPDWORD),        // diph.dwSize
337			sizeof(DIPROPHEADER),       // diph.dwHeaderSize
338			0,                          // diph.dwObj
339			DIPH_DEVICE,                // diph.dwHow
340		},
341		DINPUT_BUFFERSIZE,              // dwData
342	};
343
344	if (!hInstDI)
345	{
346		hInstDI = LoadLibrary("dinput.dll");
347
348		if (hInstDI == NULL)
349		{
350			Con_SafePrintf ("Couldn't load dinput.dll\n");
351			return false;
352		}
353	}
354
355	if (!pDirectInputCreate)
356	{
357		pDirectInputCreate = (void *)GetProcAddress(hInstDI,"DirectInputCreateA");
358
359		if (!pDirectInputCreate)
360		{
361			Con_SafePrintf ("Couldn't get DI proc addr\n");
362			return false;
363		}
364	}
365
366// register with DirectInput and get an IDirectInput to play with.
367	hr = iDirectInputCreate(global_hInstance, DIRECTINPUT_VERSION, &g_pdi, NULL);
368
369	if (FAILED(hr))
370	{
371		return false;
372	}
373
374// obtain an interface to the system mouse device.
375	hr = IDirectInput_CreateDevice(g_pdi, &GUID_SysMouse, &g_pMouse, NULL);
376
377	if (FAILED(hr))
378	{
379		Con_SafePrintf ("Couldn't open DI mouse device\n");
380		return false;
381	}
382
383// set the data format to "mouse format".
384	hr = IDirectInputDevice_SetDataFormat(g_pMouse, &df);
385
386	if (FAILED(hr))
387	{
388		Con_SafePrintf ("Couldn't set DI mouse format\n");
389		return false;
390	}
391
392// set the cooperativity level.
393	hr = IDirectInputDevice_SetCooperativeLevel(g_pMouse, mainwindow,
394			DISCL_EXCLUSIVE | DISCL_FOREGROUND);
395
396	if (FAILED(hr))
397	{
398		Con_SafePrintf ("Couldn't set DI coop level\n");
399		return false;
400	}
401
402
403// set the buffer size to DINPUT_BUFFERSIZE elements.
404// the buffer size is a DWORD property associated with the device
405	hr = IDirectInputDevice_SetProperty(g_pMouse, DIPROP_BUFFERSIZE, &dipdw.diph);
406
407	if (FAILED(hr))
408	{
409		Con_SafePrintf ("Couldn't set DI buffersize\n");
410		return false;
411	}
412
413	return true;
414}
415
416
417/*
418===========
419IN_StartupMouse
420===========
421*/
422void IN_StartupMouse (void)
423{
424	HDC			hdc;
425
426	if ( COM_CheckParm ("-nomouse") )
427		return;
428
429	mouseinitialized = true;
430
431	if (COM_CheckParm ("-dinput"))
432	{
433		dinput = IN_InitDInput ();
434
435		if (dinput)
436		{
437			Con_SafePrintf ("DirectInput initialized\n");
438		}
439		else
440		{
441			Con_SafePrintf ("DirectInput not initialized\n");
442		}
443	}
444
445	if (!dinput)
446	{
447		mouseparmsvalid = SystemParametersInfo (SPI_GETMOUSE, 0, originalmouseparms, 0);
448
449		if (mouseparmsvalid)
450		{
451			if ( COM_CheckParm ("-noforcemspd") )
452				newmouseparms[2] = originalmouseparms[2];
453
454			if ( COM_CheckParm ("-noforcemaccel") )
455			{
456				newmouseparms[0] = originalmouseparms[0];
457				newmouseparms[1] = originalmouseparms[1];
458			}
459
460			if ( COM_CheckParm ("-noforcemparms") )
461			{
462				newmouseparms[0] = originalmouseparms[0];
463				newmouseparms[1] = originalmouseparms[1];
464				newmouseparms[2] = originalmouseparms[2];
465			}
466		}
467	}
468
469	mouse_buttons = 3;
470
471// if a fullscreen video mode was set before the mouse was initialized,
472// set the mouse state appropriately
473	if (mouseactivatetoggle)
474		IN_ActivateMouse ();
475}
476
477
478/*
479===========
480IN_Init
481===========
482*/
483void IN_Init (void)
484{
485	// mouse variables
486	Cvar_RegisterVariable (&m_filter);
487
488	// joystick variables
489	Cvar_RegisterVariable (&in_joystick);
490	Cvar_RegisterVariable (&joy_name);
491	Cvar_RegisterVariable (&joy_advanced);
492	Cvar_RegisterVariable (&joy_advaxisx);
493	Cvar_RegisterVariable (&joy_advaxisy);
494	Cvar_RegisterVariable (&joy_advaxisz);
495	Cvar_RegisterVariable (&joy_advaxisr);
496	Cvar_RegisterVariable (&joy_advaxisu);
497	Cvar_RegisterVariable (&joy_advaxisv);
498	Cvar_RegisterVariable (&joy_forwardthreshold);
499	Cvar_RegisterVariable (&joy_sidethreshold);
500	Cvar_RegisterVariable (&joy_pitchthreshold);
501	Cvar_RegisterVariable (&joy_yawthreshold);
502	Cvar_RegisterVariable (&joy_forwardsensitivity);
503	Cvar_RegisterVariable (&joy_sidesensitivity);
504	Cvar_RegisterVariable (&joy_pitchsensitivity);
505	Cvar_RegisterVariable (&joy_yawsensitivity);
506	Cvar_RegisterVariable (&joy_wwhack1);
507	Cvar_RegisterVariable (&joy_wwhack2);
508
509	Cmd_AddCommand ("force_centerview", Force_CenterView_f);
510	Cmd_AddCommand ("joyadvancedupdate", Joy_AdvancedUpdate_f);
511
512	uiWheelMessage = RegisterWindowMessage ( "MSWHEEL_ROLLMSG" );
513
514	IN_StartupMouse ();
515	IN_StartupJoystick ();
516}
517
518/*
519===========
520IN_Shutdown
521===========
522*/
523void IN_Shutdown (void)
524{
525
526	IN_DeactivateMouse ();
527	IN_ShowMouse ();
528
529    if (g_pMouse)
530	{
531		IDirectInputDevice_Release(g_pMouse);
532		g_pMouse = NULL;
533	}
534
535    if (g_pdi)
536	{
537		IDirectInput_Release(g_pdi);
538		g_pdi = NULL;
539	}
540}
541
542
543/*
544===========
545IN_MouseEvent
546===========
547*/
548void IN_MouseEvent (int mstate)
549{
550	int		i;
551
552	if (mouseactive && !dinput)
553	{
554	// perform button actions
555		for (i=0 ; i<mouse_buttons ; i++)
556		{
557			if ( (mstate & (1<<i)) &&
558				!(mouse_oldbuttonstate & (1<<i)) )
559			{
560				Key_Event (K_MOUSE1 + i, true);
561			}
562
563			if ( !(mstate & (1<<i)) &&
564				(mouse_oldbuttonstate & (1<<i)) )
565			{
566					Key_Event (K_MOUSE1 + i, false);
567			}
568		}
569
570		mouse_oldbuttonstate = mstate;
571	}
572}
573
574
575/*
576===========
577IN_MouseMove
578===========
579*/
580void IN_MouseMove (usercmd_t *cmd)
581{
582	int		mx, my;
583	HDC	hdc;
584	int					i;
585	DIDEVICEOBJECTDATA	od;
586	DWORD				dwElements;
587	HRESULT				hr;
588
589	if (!mouseactive)
590		return;
591
592	if (dinput)
593	{
594		mx = 0;
595		my = 0;
596
597		for (;;)
598		{
599			dwElements = 1;
600
601			hr = IDirectInputDevice_GetDeviceData(g_pMouse,
602					sizeof(DIDEVICEOBJECTDATA), &od, &dwElements, 0);
603
604			if ((hr == DIERR_INPUTLOST) || (hr == DIERR_NOTACQUIRED))
605			{
606				dinput_acquired = true;
607				IDirectInputDevice_Acquire(g_pMouse);
608				break;
609			}
610
611			/* Unable to read data or no data available */
612			if (FAILED(hr) || dwElements == 0)
613			{
614				break;
615			}
616
617			/* Look at the element to see what happened */
618
619			switch (od.dwOfs)
620			{
621				case DIMOFS_X:
622					mx += od.dwData;
623					break;
624
625				case DIMOFS_Y:
626					my += od.dwData;
627					break;
628
629				case DIMOFS_BUTTON0:
630					if (od.dwData & 0x80)
631						mstate_di |= 1;
632					else
633						mstate_di &= ~1;
634					break;
635
636				case DIMOFS_BUTTON1:
637					if (od.dwData & 0x80)
638						mstate_di |= (1<<1);
639					else
640						mstate_di &= ~(1<<1);
641					break;
642
643				case DIMOFS_BUTTON2:
644					if (od.dwData & 0x80)
645						mstate_di |= (1<<2);
646					else
647						mstate_di &= ~(1<<2);
648					break;
649			}
650		}
651
652	// perform button actions
653		for (i=0 ; i<mouse_buttons ; i++)
654		{
655			if ( (mstate_di & (1<<i)) &&
656				!(mouse_oldbuttonstate & (1<<i)) )
657			{
658				Key_Event (K_MOUSE1 + i, true);
659			}
660
661			if ( !(mstate_di & (1<<i)) &&
662				(mouse_oldbuttonstate & (1<<i)) )
663			{
664				Key_Event (K_MOUSE1 + i, false);
665			}
666		}
667
668		mouse_oldbuttonstate = mstate_di;
669	}
670	else
671	{
672		GetCursorPos (&current_pos);
673	mx = current_pos.x - window_center_x + mx_accum;
674	my = current_pos.y - window_center_y + my_accum;
675	mx_accum = 0;
676	my_accum = 0;
677	}
678
679	if (m_filter.value)
680	{
681		mouse_x = (mx + old_mouse_x) * 0.5;
682		mouse_y = (my + old_mouse_y) * 0.5;
683	}
684	else
685	{
686		mouse_x = mx;
687		mouse_y = my;
688	}
689
690	old_mouse_x = mx;
691	old_mouse_y = my;
692
693	mouse_x *= sensitivity.value;
694	mouse_y *= sensitivity.value;
695
696// add mouse X/Y movement to cmd
697	if ( (in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1) ))
698		cmd->sidemove += m_side.value * mouse_x;
699	else
700		cl.viewangles[YAW] -= m_yaw.value * mouse_x;
701
702	if (in_mlook.state & 1)
703		V_StopPitchDrift ();
704
705	if ( (in_mlook.state & 1) && !(in_strafe.state & 1))
706	{
707		cl.viewangles[PITCH] += m_pitch.value * mouse_y;
708		if (cl.viewangles[PITCH] > 80)
709			cl.viewangles[PITCH] = 80;
710		if (cl.viewangles[PITCH] < -70)
711			cl.viewangles[PITCH] = -70;
712	}
713	else
714	{
715		if ((in_strafe.state & 1) && noclip_anglehack)
716			cmd->upmove -= m_forward.value * mouse_y;
717		else
718			cmd->forwardmove -= m_forward.value * mouse_y;
719	}
720
721// if the mouse has moved, force it to the center, so there's room to move
722	if (mx || my)
723	{
724		SetCursorPos (window_center_x, window_center_y);
725	}
726}
727
728
729/*
730===========
731IN_Move
732===========
733*/
734void IN_Move (usercmd_t *cmd)
735{
736
737	if (ActiveApp && !Minimized)
738	{
739		IN_MouseMove (cmd);
740		IN_JoyMove (cmd);
741	}
742}
743
744
745/*
746===========
747IN_Accumulate
748===========
749*/
750void IN_Accumulate (void)
751{
752	int		mx, my;
753	HDC	hdc;
754
755	if (mouseactive)
756	{
757		GetCursorPos (&current_pos);
758
759		mx_accum += current_pos.x - window_center_x;
760		my_accum += current_pos.y - window_center_y;
761
762	// force the mouse to the center, so there's room to move
763		SetCursorPos (window_center_x, window_center_y);
764	}
765}
766
767
768/*
769===================
770IN_ClearStates
771===================
772*/
773void IN_ClearStates (void)
774{
775
776	if (mouseactive)
777	{
778		mx_accum = 0;
779		my_accum = 0;
780		mouse_oldbuttonstate = 0;
781	}
782}
783
784
785/*
786===============
787IN_StartupJoystick
788===============
789*/
790void IN_StartupJoystick (void)
791{
792	int			i, numdevs;
793	JOYCAPS		jc;
794	MMRESULT	mmr;
795
796 	// assume no joystick
797	joy_avail = false;
798
799	// abort startup if user requests no joystick
800	if ( COM_CheckParm ("-nojoy") )
801		return;
802
803	// verify joystick driver is present
804	if ((numdevs = joyGetNumDevs ()) == 0)
805	{
806		Con_Printf ("\njoystick not found -- driver not present\n\n");
807		return;
808	}
809
810	// cycle through the joystick ids for the first valid one
811	for (joy_id=0 ; joy_id<numdevs ; joy_id++)
812	{
813		memset (&ji, 0, sizeof(ji));
814		ji.dwSize = sizeof(ji);
815		ji.dwFlags = JOY_RETURNCENTERED;
816
817		if ((mmr = joyGetPosEx (joy_id, &ji)) == JOYERR_NOERROR)
818			break;
819	}
820
821	// abort startup if we didn't find a valid joystick
822	if (mmr != JOYERR_NOERROR)
823	{
824		Con_Printf ("\njoystick not found -- no valid joysticks (%x)\n\n", mmr);
825		return;
826	}
827
828	// get the capabilities of the selected joystick
829	// abort startup if command fails
830	memset (&jc, 0, sizeof(jc));
831	if ((mmr = joyGetDevCaps (joy_id, &jc, sizeof(jc))) != JOYERR_NOERROR)
832	{
833		Con_Printf ("\njoystick not found -- invalid joystick capabilities (%x)\n\n", mmr);
834		return;
835	}
836
837	// save the joystick's number of buttons and POV status
838	joy_numbuttons = jc.wNumButtons;
839	joy_haspov = jc.wCaps & JOYCAPS_HASPOV;
840
841	// old button and POV states default to no buttons pressed
842	joy_oldbuttonstate = joy_oldpovstate = 0;
843
844	// mark the joystick as available and advanced initialization not completed
845	// this is needed as cvars are not available during initialization
846
847	joy_avail = true;
848	joy_advancedinit = false;
849
850	Con_Printf ("\njoystick detected\n\n");
851}
852
853
854/*
855===========
856RawValuePointer
857===========
858*/
859PDWORD RawValuePointer (int axis)
860{
861	switch (axis)
862	{
863	case JOY_AXIS_X:
864		return &ji.dwXpos;
865	case JOY_AXIS_Y:
866		return &ji.dwYpos;
867	case JOY_AXIS_Z:
868		return &ji.dwZpos;
869	case JOY_AXIS_R:
870		return &ji.dwRpos;
871	case JOY_AXIS_U:
872		return &ji.dwUpos;
873	case JOY_AXIS_V:
874		return &ji.dwVpos;
875	}
876}
877
878
879/*
880===========
881Joy_AdvancedUpdate_f
882===========
883*/
884void Joy_AdvancedUpdate_f (void)
885{
886
887	// called once by IN_ReadJoystick and by user whenever an update is needed
888	// cvars are now available
889	int	i;
890	DWORD dwTemp;
891
892	// initialize all the maps
893	for (i = 0; i < JOY_MAX_AXES; i++)
894	{
895		dwAxisMap[i] = AxisNada;
896		dwControlMap[i] = JOY_ABSOLUTE_AXIS;
897		pdwRawValue[i] = RawValuePointer(i);
898	}
899
900	if( joy_advanced.value == 0.0)
901	{
902		// default joystick initialization
903		// 2 axes only with joystick control
904		dwAxisMap[JOY_AXIS_X] = AxisTurn;
905		// dwControlMap[JOY_AXIS_X] = JOY_ABSOLUTE_AXIS;
906		dwAxisMap[JOY_AXIS_Y] = AxisForward;
907		// dwControlMap[JOY_AXIS_Y] = JOY_ABSOLUTE_AXIS;
908	}
909	else
910	{
911		if (Q_strcmp (joy_name.string, "joystick") != 0)
912		{
913			// notify user of advanced controller
914			Con_Printf ("\n%s configured\n\n", joy_name.string);
915		}
916
917		// advanced initialization here
918		// data supplied by user via joy_axisn cvars
919		dwTemp = (DWORD) joy_advaxisx.value;
920		dwAxisMap[JOY_AXIS_X] = dwTemp & 0x0000000f;
921		dwControlMap[JOY_AXIS_X] = dwTemp & JOY_RELATIVE_AXIS;
922		dwTemp = (DWORD) joy_advaxisy.value;
923		dwAxisMap[JOY_AXIS_Y] = dwTemp & 0x0000000f;
924		dwControlMap[JOY_AXIS_Y] = dwTemp & JOY_RELATIVE_AXIS;
925		dwTemp = (DWORD) joy_advaxisz.value;
926		dwAxisMap[JOY_AXIS_Z] = dwTemp & 0x0000000f;
927		dwControlMap[JOY_AXIS_Z] = dwTemp & JOY_RELATIVE_AXIS;
928		dwTemp = (DWORD) joy_advaxisr.value;
929		dwAxisMap[JOY_AXIS_R] = dwTemp & 0x0000000f;
930		dwControlMap[JOY_AXIS_R] = dwTemp & JOY_RELATIVE_AXIS;
931		dwTemp = (DWORD) joy_advaxisu.value;
932		dwAxisMap[JOY_AXIS_U] = dwTemp & 0x0000000f;
933		dwControlMap[JOY_AXIS_U] = dwTemp & JOY_RELATIVE_AXIS;
934		dwTemp = (DWORD) joy_advaxisv.value;
935		dwAxisMap[JOY_AXIS_V] = dwTemp & 0x0000000f;
936		dwControlMap[JOY_AXIS_V] = dwTemp & JOY_RELATIVE_AXIS;
937	}
938
939	// compute the axes to collect from DirectInput
940	joy_flags = JOY_RETURNCENTERED | JOY_RETURNBUTTONS | JOY_RETURNPOV;
941	for (i = 0; i < JOY_MAX_AXES; i++)
942	{
943		if (dwAxisMap[i] != AxisNada)
944		{
945			joy_flags |= dwAxisFlags[i];
946		}
947	}
948}
949
950
951/*
952===========
953IN_Commands
954===========
955*/
956void IN_Commands (void)
957{
958	int		i, key_index;
959	DWORD	buttonstate, povstate;
960
961	if (!joy_avail)
962	{
963		return;
964	}
965
966
967	// loop through the joystick buttons
968	// key a joystick event or auxillary event for higher number buttons for each state change
969	buttonstate = ji.dwButtons;
970	for (i=0 ; i < joy_numbuttons ; i++)
971	{
972		if ( (buttonstate & (1<<i)) && !(joy_oldbuttonstate & (1<<i)) )
973		{
974			key_index = (i < 4) ? K_JOY1 : K_AUX1;
975			Key_Event (key_index + i, true);
976		}
977
978		if ( !(buttonstate & (1<<i)) && (joy_oldbuttonstate & (1<<i)) )
979		{
980			key_index = (i < 4) ? K_JOY1 : K_AUX1;
981			Key_Event (key_index + i, false);
982		}
983	}
984	joy_oldbuttonstate = buttonstate;
985
986	if (joy_haspov)
987	{
988		// convert POV information into 4 bits of state information
989		// this avoids any potential problems related to moving from one
990		// direction to another without going through the center position
991		povstate = 0;
992		if(ji.dwPOV != JOY_POVCENTERED)
993		{
994			if (ji.dwPOV == JOY_POVFORWARD)
995				povstate |= 0x01;
996			if (ji.dwPOV == JOY_POVRIGHT)
997				povstate |= 0x02;
998			if (ji.dwPOV == JOY_POVBACKWARD)
999				povstate |= 0x04;
1000			if (ji.dwPOV == JOY_POVLEFT)
1001				povstate |= 0x08;
1002		}
1003		// determine which bits have changed and key an auxillary event for each change
1004		for (i=0 ; i < 4 ; i++)
1005		{
1006			if ( (povstate & (1<<i)) && !(joy_oldpovstate & (1<<i)) )
1007			{
1008				Key_Event (K_AUX29 + i, true);
1009			}
1010
1011			if ( !(povstate & (1<<i)) && (joy_oldpovstate & (1<<i)) )
1012			{
1013				Key_Event (K_AUX29 + i, false);
1014			}
1015		}
1016		joy_oldpovstate = povstate;
1017	}
1018}
1019
1020
1021/*
1022===============
1023IN_ReadJoystick
1024===============
1025*/
1026qboolean IN_ReadJoystick (void)
1027{
1028
1029	memset (&ji, 0, sizeof(ji));
1030	ji.dwSize = sizeof(ji);
1031	ji.dwFlags = joy_flags;
1032
1033	if (joyGetPosEx (joy_id, &ji) == JOYERR_NOERROR)
1034	{
1035		// this is a hack -- there is a bug in the Logitech WingMan Warrior DirectInput Driver
1036		// rather than having 32768 be the zero point, they have the zero point at 32668
1037		// go figure -- anyway, now we get the full resolution out of the device
1038		if (joy_wwhack1.value != 0.0)
1039		{
1040			ji.dwUpos += 100;
1041		}
1042		return true;
1043	}
1044	else
1045	{
1046		// read error occurred
1047		// turning off the joystick seems too harsh for 1 read error,\
1048		// but what should be done?
1049		// Con_Printf ("IN_ReadJoystick: no response\n");
1050		// joy_avail = false;
1051		return false;
1052	}
1053}
1054
1055
1056/*
1057===========
1058IN_JoyMove
1059===========
1060*/
1061void IN_JoyMove (usercmd_t *cmd)
1062{
1063	float	speed, aspeed;
1064	float	fAxisValue, fTemp;
1065	int		i;
1066
1067	// complete initialization if first time in
1068	// this is needed as cvars are not available at initialization time
1069	if( joy_advancedinit != true )
1070	{
1071		Joy_AdvancedUpdate_f();
1072		joy_advancedinit = true;
1073	}
1074
1075	// verify joystick is available and that the user wants to use it
1076	if (!joy_avail || !in_joystick.value)
1077	{
1078		return;
1079	}
1080
1081	// collect the joystick data, if possible
1082	if (IN_ReadJoystick () != true)
1083	{
1084		return;
1085	}
1086
1087	if (in_speed.state & 1)
1088		speed = cl_movespeedkey.value;
1089	else
1090		speed = 1;
1091	aspeed = speed * host_frametime;
1092
1093	// loop through the axes
1094	for (i = 0; i < JOY_MAX_AXES; i++)
1095	{
1096		// get the floating point zero-centered, potentially-inverted data for the current axis
1097		fAxisValue = (float) *pdwRawValue[i];
1098		// move centerpoint to zero
1099		fAxisValue -= 32768.0;
1100
1101		if (joy_wwhack2.value != 0.0)
1102		{
1103			if (dwAxisMap[i] == AxisTurn)
1104			{
1105				// this is a special formula for the Logitech WingMan Warrior
1106				// y=ax^b; where a = 300 and b = 1.3
1107				// also x values are in increments of 800 (so this is factored out)
1108				// then bounds check result to level out excessively high spin rates
1109				fTemp = 300.0 * pow(abs(fAxisValue) / 800.0, 1.3);
1110				if (fTemp > 14000.0)
1111					fTemp = 14000.0;
1112				// restore direction information
1113				fAxisValue = (fAxisValue > 0.0) ? fTemp : -fTemp;
1114			}
1115		}
1116
1117		// convert range from -32768..32767 to -1..1
1118		fAxisValue /= 32768.0;
1119
1120		switch (dwAxisMap[i])
1121		{
1122		case AxisForward:
1123			if ((joy_advanced.value == 0.0) && (in_mlook.state & 1))
1124			{
1125				// user wants forward control to become look control
1126				if (fabs(fAxisValue) > joy_pitchthreshold.value)
1127				{
1128					// if mouse invert is on, invert the joystick pitch value
1129					// only absolute control support here (joy_advanced is false)
1130					if (m_pitch.value < 0.0)
1131					{
1132						cl.viewangles[PITCH] -= (fAxisValue * joy_pitchsensitivity.value) * aspeed * cl_pitchspeed.value;
1133					}
1134					else
1135					{
1136						cl.viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity.value) * aspeed * cl_pitchspeed.value;
1137					}
1138					V_StopPitchDrift();
1139				}
1140				else
1141				{
1142					// no pitch movement
1143					// disable pitch return-to-center unless requested by user
1144					// *** this code can be removed when the lookspring bug is fixed
1145					// *** the bug always has the lookspring feature on
1146					if(lookspring.value == 0.0)
1147						V_StopPitchDrift();
1148				}
1149			}
1150			else
1151			{
1152				// user wants forward control to be forward control
1153				if (fabs(fAxisValue) > joy_forwardthreshold.value)
1154				{
1155					cmd->forwardmove += (fAxisValue * joy_forwardsensitivity.value) * speed * cl_forwardspeed.value;
1156				}
1157			}
1158			break;
1159
1160		case AxisSide:
1161			if (fabs(fAxisValue) > joy_sidethreshold.value)
1162			{
1163				cmd->sidemove += (fAxisValue * joy_sidesensitivity.value) * speed * cl_sidespeed.value;
1164			}
1165			break;
1166
1167		case AxisTurn:
1168			if ((in_strafe.state & 1) || (lookstrafe.value && (in_mlook.state & 1)))
1169			{
1170				// user wants turn control to become side control
1171				if (fabs(fAxisValue) > joy_sidethreshold.value)
1172				{
1173					cmd->sidemove -= (fAxisValue * joy_sidesensitivity.value) * speed * cl_sidespeed.value;
1174				}
1175			}
1176			else
1177			{
1178				// user wants turn control to be turn control
1179				if (fabs(fAxisValue) > joy_yawthreshold.value)
1180				{
1181					if(dwControlMap[i] == JOY_ABSOLUTE_AXIS)
1182					{
1183						cl.viewangles[YAW] += (fAxisValue * joy_yawsensitivity.value) * aspeed * cl_yawspeed.value;
1184					}
1185					else
1186					{
1187						cl.viewangles[YAW] += (fAxisValue * joy_yawsensitivity.value) * speed * 180.0;
1188					}
1189
1190				}
1191			}
1192			break;
1193
1194		case AxisLook:
1195			if (in_mlook.state & 1)
1196			{
1197				if (fabs(fAxisValue) > joy_pitchthreshold.value)
1198				{
1199					// pitch movement detected and pitch movement desired by user
1200					if(dwControlMap[i] == JOY_ABSOLUTE_AXIS)
1201					{
1202						cl.viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity.value) * aspeed * cl_pitchspeed.value;
1203					}
1204					else
1205					{
1206						cl.viewangles[PITCH] += (fAxisValue * joy_pitchsensitivity.value) * speed * 180.0;
1207					}
1208					V_StopPitchDrift();
1209				}
1210				else
1211				{
1212					// no pitch movement
1213					// disable pitch return-to-center unless requested by user
1214					// *** this code can be removed when the lookspring bug is fixed
1215					// *** the bug always has the lookspring feature on
1216					if(lookspring.value == 0.0)
1217						V_StopPitchDrift();
1218				}
1219			}
1220			break;
1221
1222		default:
1223			break;
1224		}
1225	}
1226
1227	// bounds check pitch
1228	if (cl.viewangles[PITCH] > 80.0)
1229		cl.viewangles[PITCH] = 80.0;
1230	if (cl.viewangles[PITCH] < -70.0)
1231		cl.viewangles[PITCH] = -70.0;
1232}
1233