[yquake2] 01/05: New upstream version 7.10+ctf1.05~dfsg

Simon McVittie smcv at debian.org
Sun Dec 10 21:16:22 UTC 2017


This is an automated email from the git hooks/post-receive script.

smcv pushed a commit to annotated tag debian/7.10+ctf1.05_dfsg-1
in repository yquake2.

commit ec97f12c8d5b898fb4652c4e60b3d38409f612ef
Author: Simon McVittie <smcv at debian.org>
Date:   Sun Dec 10 19:55:14 2017 +0000

    New upstream version 7.10+ctf1.05~dfsg
---
 CHANGELOG                         |  15 +
 CMakeLists.txt                    |   3 +
 Makefile                          |   6 +-
 src/backends/generic/vid.c        |  23 +-
 src/backends/sdl/input.c          | 724 +++++++++++++++++++++++++++++++++++++-
 src/backends/sdl/refresh.c        |  32 +-
 src/backends/unix/system.c        |  49 ++-
 src/backends/windows/system.c     |   2 +-
 src/client/cl_cin.c               |  17 +-
 src/client/cl_keyboard.c          |  58 ++-
 src/client/cl_main.c              |  10 +-
 src/client/cl_prediction.c        |   4 +-
 src/client/cl_screen.c            |   2 +-
 src/client/cl_tempentities.c      |  20 +-
 src/client/header/client.h        |   6 +-
 src/client/header/keyboard.h      |  14 +-
 src/client/menu/menu.c            | 280 +++++++++------
 src/client/menu/videomenu.c       |  11 +-
 src/client/refresh/files/pcx.c    |  64 +++-
 src/client/refresh/gl/r_main.c    |  11 +
 src/client/refresh/gl/r_sdl.c     |  13 +
 src/client/refresh/gl3/gl3_main.c |  11 +
 src/client/refresh/gl3/gl3_sdl.c  |  16 +-
 src/client/sound/ogg.c            |  21 +-
 src/client/sound/sound.c          |  10 +
 src/common/collision.c            |  24 +-
 src/common/frame.c                |  16 +
 src/common/header/common.h        |   2 +-
 src/common/header/shared.h        |   4 +-
 29 files changed, 1264 insertions(+), 204 deletions(-)

diff --git a/CHANGELOG b/CHANGELOG
index 17aeb32..862e844 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -1,3 +1,18 @@
+Quake II 7.02 to 7.10:
+- Joystick support including haptic feedback. This fantastic work was
+  done by Denis Pauk. The dirty work is done by SDL, how good or bad
+  a joystick or gamepad is supported depends on SDLs support for it.
+- Fix the old SDL sound backend, s_openal set to 0 is working again.
+- Fix possible Vorbis buffer underruns if too many sound samples are
+  in flight. This occured only in large multi player games with at
+  least 6 custom models.
+- Fix a possible crash on Windows if MSAA was set to a value not
+  supported by the driver.
+- It's now possible to play through the whole game on a Raspberry PI
+  and other ARM boards. Please note that the RPIs hardware is really
+  limited. Only the OpenGL 1.4 renderer is supported and the framerate
+  is highly dependend on the screen resolution.
+
 Quake II 7.01 to 7.02:
 - Fix several corner cases regarding render library loading. The game
   should now always fall back to the OpenGL 1.4 renderer if the new
diff --git a/CMakeLists.txt b/CMakeLists.txt
index c836f02..84e260d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -531,6 +531,7 @@ set_target_properties(game PROPERTIES
 		PREFIX ""
 		LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release/baseq2
 		RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release/baseq2
+		SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}
 		)
 target_link_libraries(game ${yquake2LinkerFlags})
 
@@ -540,6 +541,7 @@ set_target_properties(ref_gl1 PROPERTIES
 		PREFIX ""
 		LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release
 		RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release
+		SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}
 		)
 target_link_libraries(ref_gl1 ${yquake2LinkerFlags} ${yquake2OpenGLLinkerFlags} ${yquake2SDLLinkerFlags})
 
@@ -549,5 +551,6 @@ set_target_properties(ref_gl3 PROPERTIES
 		PREFIX ""
 		LIBRARY_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release
 		RUNTIME_OUTPUT_DIRECTORY ${CMAKE_BINARY_DIR}/release
+		SUFFIX ${CMAKE_SHARED_LIBRARY_SUFFIX}
 		)
 target_link_libraries(ref_gl3 ${yquake2LinkerFlags} ${yquake2SDLLinkerFlags})
diff --git a/Makefile b/Makefile
index 801db08..a34985f 100755
--- a/Makefile
+++ b/Makefile
@@ -277,7 +277,7 @@ endif
 CFLAGS += -fvisibility=hidden
 LDFLAGS += -fvisibility=hidden
 
-ifneq ($(YQ2_OSTYPE), $(filter $(YQ2_OSTYPE), Darwin, OpenBSD))
+ifneq ($(YQ2_OSTYPE), $(filter $(YQ2_OSTYPE), Darwin OpenBSD))
 # for some reason the OSX & OpenBSD linker doesn't support this
 LDFLAGS += -Wl,--no-undefined
 endif
@@ -451,7 +451,7 @@ ifeq ($(YQ2_OSTYPE), OpenBSD)
 release/quake2 : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"libopenal.so"' -DDLOPEN_OPENAL
 else ifeq ($(YQ2_OSTYPE), Darwin)
 release/quake2 : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"libopenal.dylib"' -I/usr/local/opt/openal-soft/include -DDLOPEN_OPENAL
-release/quake2 : LDFLAGS += -L/usr/local/opt/openal-soft/lib
+release/quake2 : LDFLAGS += -L/usr/local/opt/openal-soft/lib -rpath /usr/local/opt/openal-soft/lib
 else
 release/quake2 : CFLAGS += -DUSE_OPENAL -DDEFAULT_OPENAL_DRIVER='"libopenal.so.1"' -DDLOPEN_OPENAL
 endif
@@ -460,7 +460,7 @@ release/quake2 : CFLAGS += -DUSE_OPENAL
 release/quake2 : LDFLAGS += -lopenal
 ifeq ($(YQ2_OSTYPE), Darwin)
 release/quake2 : CFLAGS += -I/usr/local/opt/openal-soft/include
-release/quake2 : LDFLAGS += -L/usr/local/opt/openal-soft/lib
+release/quake2 : LDFLAGS += -L/usr/local/opt/openal-soft/lib -rpath /usr/local/opt/openal-soft/lib
 endif # Darwin
 endif # !DLOPEN_OPENAL
 endif # WITH_OPENAL
diff --git a/src/backends/generic/vid.c b/src/backends/generic/vid.c
index 76f6a9c..8a653b6 100644
--- a/src/backends/generic/vid.c
+++ b/src/backends/generic/vid.c
@@ -101,6 +101,14 @@ vidmode_t vid_modes[] = {
 	{"Mode 21: 1920x1080", 1920, 1080, 21},
 	{"Mode 22: 1920x1200", 1920, 1200, 22},
 	{"Mode 23: 2048x1536", 2048, 1536, 23},
+	{"Mode 24: 2560x1080", 2560, 1080, 24},
+	{"Mode 25: 2560x1440", 2560, 1440, 25},
+	{"Mode 26: 2560x1600", 2560, 1600, 26},
+	{"Mode 27: 3440x1440", 3440, 1440, 27},
+	{"Mode 28: 3840x1600", 3840, 1600, 28},
+	{"Mode 29: 3840x2160", 3840, 2160, 29},
+	{"Mode 30: 4096x2160", 4096, 2160, 30},
+	{"Mode 31: 5120x2880", 5120, 2880, 31},
 };
 
 /* Console variables that we need to access from this module */
@@ -177,11 +185,18 @@ VID_CheckChanges(void)
 		cls.disable_screen = true;
 
 		// Proceed to reboot the refresher
-		if(!VID_LoadRefresh() && (strcmp(vid_renderer->string, "gl1") != 0))
+		if(!VID_LoadRefresh())
 		{
-			Com_Printf("\n ... trying again with standard OpenGL1.x renderer ... \n\n");
-			Cvar_Set("vid_renderer", "gl1");
-			VID_LoadRefresh();
+			if (strcmp(vid_renderer->string, "gl1") != 0)
+			{
+				Com_Printf("\n ... trying again with standard OpenGL1.x renderer ... \n\n");
+				Cvar_Set("vid_renderer", "gl1");
+				VID_LoadRefresh();
+			}
+			else
+			{
+				Com_Error(ERR_FATAL, "Couldn't load a rendering backend!\n");
+			}
 		}
 		cls.disable_screen = false;
 	}
diff --git a/src/backends/sdl/input.c b/src/backends/sdl/input.c
index 2034617..23233bb 100644
--- a/src/backends/sdl/input.c
+++ b/src/backends/sdl/input.c
@@ -1,6 +1,6 @@
 /*
  * Copyright (C) 2010 Yamagi Burmeister
- * Copyright (C) 1997-2001 Id Software, Inc.
+ * Copyright (C) 1997-2005 Id Software, Inc.
  *
  * This program is free software; you can redistribute it and/or modify
  * it under the terms of the GNU General Public License as published by
@@ -23,6 +23,8 @@
  * This is the Quake II input system backend, implemented with SDL.
  *
  * =======================================================================
+ *
+ * Joystick threshold code is partially based on http://ioquake3.org code.
  */
 
 #include "../../client/header/keyboard.h"
@@ -61,16 +63,65 @@
 
 #define MOUSE_MAX 3000
 #define MOUSE_MIN 40
- 
+
 /* Globals */
 static int mouse_x, mouse_y;
 static int old_mouse_x, old_mouse_y;
 static qboolean mlooking;
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+static float joystick_yaw, joystick_pitch;
+static float joystick_forwardmove, joystick_sidemove;
+static float joystick_up;
+static int back_button_id = -1;
+static char last_hat = SDL_HAT_CENTERED;
+static qboolean left_trigger = false;
+static qboolean right_trigger = false;
+qboolean show_haptic = false;
+
+/* Haptic feedback types */
+enum QHARPICTYPES {
+	HAPTIC_EFFECT_UNKNOWN = -1,
+	HAPTIC_EFFECT_BLASTER = 0,
+	HAPTIC_EFFECT_MENY,
+	HAPTIC_EFFECT_HYPER_BLASTER,
+	HAPTIC_EFFECT_MACHINEGUN,
+	HAPTIC_EFFECT_SHOTGUN,
+	HAPTIC_EFFECT_SSHOTGUN,
+	HAPTIC_EFFECT_RAILGUN,
+	HAPTIC_EFFECT_ROCKETGUN,
+	HAPTIC_EFFECT_GRENADE,
+	HAPTIC_EFFECT_BFG,
+	HAPTIC_EFFECT_PALANX,
+	HAPTIC_EFFECT_IONRIPPER,
+	HAPTIC_EFFECT_ETFRIFLE,
+	HAPTIC_EFFECT_SHOTGUN2,
+	HAPTIC_EFFECT_TRACKER,
+	HAPTIC_EFFECT_PAIN,
+	HAPTIC_EFFECT_STEP,
+	HAPTIC_EFFECT_TRAPCOCK,
+	HAPTIC_EFFECT_LAST
+};
+
+struct hapric_effects_cache {
+    int effect_type;
+    int effect_id;
+};
+
+static int last_haptic_volume = 0;
+static struct hapric_effects_cache last_haptic_efffect[HAPTIC_EFFECT_LAST];
+static int last_haptic_efffect_size = HAPTIC_EFFECT_LAST;
+static int last_haptic_efffect_pos = 0;
+
+/* Joystick */
+static SDL_Haptic *joystick_haptic = NULL;
+static SDL_Joystick *joystick = NULL;
+static SDL_GameController *controller = NULL;
+#endif
+
 /* CVars */
 cvar_t *vid_fullscreen;
 static cvar_t *in_grab;
-static cvar_t *in_mouse;
 static cvar_t *exponential_speedup;
 cvar_t *freelook;
 cvar_t *lookstrafe;
@@ -78,10 +129,35 @@ cvar_t *m_forward;
 static cvar_t *m_filter;
 cvar_t *m_pitch;
 cvar_t *m_side;
+cvar_t *m_up;
 cvar_t *m_yaw;
 cvar_t *sensitivity;
 static cvar_t *windowed_mouse;
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+/* Joystick sensitivity */
+static cvar_t *joy_yawsensitivity;
+static cvar_t *joy_pitchsensitivity;
+static cvar_t *joy_forwardsensitivity;
+static cvar_t *joy_sidesensitivity;
+static cvar_t *joy_upsensitivity;
+/* Joystick direction settings */
+static cvar_t *joy_axis_leftx;
+static cvar_t *joy_axis_lefty;
+static cvar_t *joy_axis_rightx;
+static cvar_t *joy_axis_righty;
+static cvar_t *joy_axis_triggerleft;
+static cvar_t *joy_axis_triggerright;
+/* Joystick threshold settings */
+static cvar_t *joy_axis_leftx_threshold;
+static cvar_t *joy_axis_lefty_threshold;
+static cvar_t *joy_axis_rightx_threshold;
+static cvar_t *joy_axis_righty_threshold;
+static cvar_t *joy_axis_triggerleft_threshold;
+static cvar_t *joy_axis_triggerright_threshold;
+/* Joystick haptic */
+static cvar_t *joy_haptic_magnitude;
+#endif
 
 extern void GLimp_GrabInput(qboolean grab);
 
@@ -447,10 +523,162 @@ IN_Update(void)
 				}
 #endif
 				break;
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+			case SDL_CONTROLLERBUTTONUP:
+			case SDL_CONTROLLERBUTTONDOWN: /* Handle Controller Back button */
+			{
+				qboolean down = (event.type == SDL_CONTROLLERBUTTONDOWN);
+				if(event.cbutton.button == SDL_CONTROLLER_BUTTON_BACK) {
+					Key_Event(K_JOY_BACK, down, true);
+				}
+			}
+				break;
+			case SDL_CONTROLLERAXISMOTION:  /* Handle Controller Motion */
+			{
+				char* direction_type;
+				float threshold = 0;
+				float fix_value = 0;
+				int axis_value = event.caxis.value;
+				switch (event.caxis.axis)
+				{
+					/* left/right */
+					case SDL_CONTROLLER_AXIS_LEFTX:
+						direction_type = joy_axis_leftx->string;
+						threshold = joy_axis_leftx_threshold->value;
+						break;
+					/* top/bottom */
+					case SDL_CONTROLLER_AXIS_LEFTY:
+						direction_type = joy_axis_lefty->string;
+						threshold = joy_axis_lefty_threshold->value;
+						break;
+					/* second left/right */
+					case SDL_CONTROLLER_AXIS_RIGHTX:
+						direction_type = joy_axis_rightx->string;
+						threshold = joy_axis_rightx_threshold->value;
+						break;
+					/* second top/bottom */
+					case SDL_CONTROLLER_AXIS_RIGHTY:
+						direction_type = joy_axis_righty->string;
+						threshold = joy_axis_righty_threshold->value;
+						break;
+					case SDL_CONTROLLER_AXIS_TRIGGERLEFT:
+						direction_type = joy_axis_triggerleft->string;
+						threshold = joy_axis_triggerleft_threshold->value;
+						break;
+					case SDL_CONTROLLER_AXIS_TRIGGERRIGHT:
+						direction_type = joy_axis_triggerright->string;
+						threshold = joy_axis_triggerright_threshold->value;
+						break;
+					default:
+						direction_type = "none";
+				}
+
+				if (threshold > 0.9)
+					threshold = 0.9;
+
+				if (axis_value < 0 && (axis_value > (32768 * threshold)))
+					axis_value = 0;
+				else if (axis_value > 0 && (axis_value < (32768 * threshold)))
+					axis_value = 0;
+
+				// Smoothly ramp from dead zone to maximum value (from ioquake)
+				// https://github.com/ioquake/ioq3/blob/master/code/sdl/sdl_input.c
+				fix_value = ((float)abs(axis_value) / 32767.0f - threshold) / (1.0f - threshold);
+				if (fix_value < 0.0f)
+					fix_value = 0.0f;
+
+				axis_value = (int)(32767 * ((axis_value < 0) ? -fix_value : fix_value));
+
+				if (cls.key_dest == key_game && (int)cl_paused->value == 0)
+				{
+					if (strcmp(direction_type, "sidemove") == 0)
+					{
+						joystick_sidemove = axis_value * joy_sidesensitivity->value;
+						// We need to be twice faster because with joystic we run...
+						joystick_sidemove *= cl_sidespeed->value * 2.0f;
+					}
+					else if (strcmp(direction_type, "forwardmove") == 0)
+					{
+						joystick_forwardmove = axis_value * joy_forwardsensitivity->value;
+						// We need to be twice faster because with joystic we run...
+						joystick_forwardmove *= cl_forwardspeed->value * 2.0f;
+					}
+					else if (strcmp(direction_type, "yaw") == 0)
+					{
+						joystick_yaw = axis_value * joy_yawsensitivity->value;
+						joystick_yaw *= cl_yawspeed->value;
+					}
+					else if (strcmp(direction_type, "pitch") == 0)
+					{
+						joystick_pitch = axis_value * joy_pitchsensitivity->value;
+						joystick_pitch *= cl_pitchspeed->value;
+					}
+					else if (strcmp(direction_type, "updown") == 0)
+					{
+						joystick_up = axis_value * joy_upsensitivity->value;
+						joystick_up *= cl_upspeed->value;
+					}
+				}
+
+				if (strcmp(direction_type, "triggerleft") == 0)
+				{
+					qboolean new_left_trigger = abs(axis_value) > (32767 / 4);
+					if (new_left_trigger != left_trigger)
+					{
+						left_trigger = new_left_trigger;
+						Key_Event(K_TRIG_LEFT, left_trigger, true);
+					}
+				}
+				else if (strcmp(direction_type, "triggerright") == 0)
+				{
+					qboolean new_right_trigger = abs(axis_value) > (32767 / 4);
+					if (new_right_trigger != right_trigger)
+					{
+						right_trigger = new_right_trigger;
+						Key_Event(K_TRIG_RIGHT, right_trigger, true);
+					}
+				}
+			}
+				break;
+			/* Joystick can have more buttons than on general game controller
+			 * so try to map not free buttons */
+			case SDL_JOYBUTTONUP:
+			case SDL_JOYBUTTONDOWN:
+			{
+				qboolean down = (event.type == SDL_JOYBUTTONDOWN);
+				/* Ignore back button, we dont need event for such button */
+				if (back_button_id == event.jbutton.button)
+					return;
+				if(event.jbutton.button <= (K_JOY32 - K_JOY1)) {
+					Key_Event(event.jbutton.button + K_JOY1, down, true);
+				}
+			}
+				break;
+			case SDL_JOYHATMOTION:
+			{
+				if (last_hat != event.jhat.value)
+				{
+					char diff = last_hat ^ event.jhat.value;
+					int i;
+					for (i=0; i < 4; i++) {
+						if (diff & (1 << i)) {
+							/* check that we have button up for some bit */
+							if (last_hat & (1 << i))
+								Key_Event(i + K_HAT_UP, false, true);
 
+							/* check that we have button down for some bit */
+							if (event.jhat.value & (1 << i))
+								Key_Event(i + K_HAT_UP, true, true);
+						}
+					}
+					last_hat = event.jhat.value;
+				}
+			}
+				break;
+#endif
 			case SDL_QUIT:
 				Com_Quit();
-				
+
 				break;
 		}
 	}
@@ -480,7 +708,7 @@ In_FlushQueue(void)
 
 	Key_MarkAllUp();
 }
- 
+
 /*
  * Move handling
  */
@@ -559,8 +787,41 @@ IN_Move(usercmd_t *cmd)
 
 		mouse_x = mouse_y = 0;
 	}
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	// to make the the viewangles changes independent of framerate
+	// we need to scale with frametime (assuming the configured values are for 60hz)
+	// 1/32768 is to normalize the input values from SDL (they're between -32768 and 32768 and we want -1 to 1)
+	// (for movement this is not needed, as those are absolute values independent of framerate)
+	float joyViewFactor = (1.0f/32768.0f) * (cls.rframetime/0.01666f);
+
+	if (joystick_yaw)
+	{
+		cl.viewangles[YAW] -= (m_yaw->value * joystick_yaw) * joyViewFactor;
+	}
+
+	if(joystick_pitch)
+	{
+		cl.viewangles[PITCH] += (m_pitch->value * joystick_pitch) * joyViewFactor;
+	}
+
+	if (joystick_forwardmove)
+	{
+		cmd->forwardmove -= (m_forward->value * joystick_forwardmove) / 32768;
+	}
+
+	if (joystick_sidemove)
+	{
+		cmd->sidemove += (m_side->value * joystick_sidemove) / 32768;
+	}
+
+	if (joystick_up)
+	{
+		cmd->upmove -= (m_up->value * joystick_up) / 32768;
+	}
+#endif
 }
- 
+
 /* ------------------------------------------------------------------ */
 
 /*
@@ -582,7 +843,317 @@ IN_MLookUp(void)
 	IN_CenterView();
 }
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+/*
+ * Shutdown haptic functionality
+ */
+static void IN_Haptic_Shutdown(void);
+
 /* ------------------------------------------------------------------ */
+/*
+ * Init haptic effects
+ */
+static int
+IN_Haptic_Effect_Init(int dir, int period, int magnitude, int length, int attack, int fade)
+{
+	/*
+	 * Direction:
+	 * North - 0
+	 * East - 9000
+	 * South - 18000
+	 * West - 27000
+	 */
+	int effect_id;
+	static SDL_HapticEffect haptic_effect;
+	SDL_memset(&haptic_effect, 0, sizeof(SDL_HapticEffect)); // 0 is safe default
+	haptic_effect.type = SDL_HAPTIC_SINE;
+	haptic_effect.periodic.direction.type = SDL_HAPTIC_POLAR; // Polar coordinates
+	haptic_effect.periodic.direction.dir[0] = dir;
+	haptic_effect.periodic.period = period;
+	haptic_effect.periodic.magnitude = magnitude;
+	haptic_effect.periodic.length = length;
+	haptic_effect.periodic.attack_length = attack;
+	haptic_effect.periodic.fade_length = fade;
+	effect_id = SDL_HapticNewEffect(joystick_haptic, &haptic_effect);
+	if (effect_id < 0)
+	{
+		Com_Printf ("SDL_HapticNewEffect failed: %s\n", SDL_GetError());
+		Com_Printf ("Please try to rerun game. Effects will be disabled for now.\n");
+		IN_Haptic_Shutdown();
+	}
+	return effect_id;
+}
+
+static int
+IN_Haptic_Effects_To_Id(int haptic_effect)
+{
+	if ((SDL_HapticQuery(joystick_haptic) & SDL_HAPTIC_SINE)==0)
+		return -1;
+
+	int hapric_volume = joy_haptic_magnitude->value * 255; // * 128 = 32767 max strength;
+	if (hapric_volume > 255)
+		hapric_volume = 255;
+	else if (hapric_volume < 0)
+		hapric_volume = 0;
+
+	switch(haptic_effect) {
+	case HAPTIC_EFFECT_MENY:
+	case HAPTIC_EFFECT_TRAPCOCK:
+	case HAPTIC_EFFECT_STEP:
+		/* North */
+		return IN_Haptic_Effect_Init(
+			0/* Force comes from N*/, 500/* 500 ms*/, hapric_volume * 48,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_PAIN:
+		return IN_Haptic_Effect_Init(
+			0/* Force comes from N*/, 700/* 700 ms*/, hapric_volume * 196,
+			300/* 0.3 seconds long */, 200/* Takes 0.2 second to get max strength */,
+			200/* Takes 0.2 second to fade away */);
+	case HAPTIC_EFFECT_BLASTER:
+		/* 30 degrees */
+		return IN_Haptic_Effect_Init(
+			2000/* Force comes from NNE*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_HYPER_BLASTER:
+		return IN_Haptic_Effect_Init(
+			4000/* Force comes from NNE*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_ETFRIFLE:
+		/* 60 degrees */
+		return IN_Haptic_Effect_Init(
+			5000/* Force comes from NEE*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_TRACKER:
+		return IN_Haptic_Effect_Init(
+			7000/* Force comes from NEE*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_MACHINEGUN:
+		/* 90 degrees */
+		return IN_Haptic_Effect_Init(
+			9000/* Force comes from E*/, 800/* 800 ms*/, hapric_volume * 88,
+			600/* 0.6 seconds long */, 200/* Takes 0.2 second to get max strength */,
+			400/* Takes 0.4 second to fade away */);
+	case HAPTIC_EFFECT_SHOTGUN:
+		/* 120 degrees */
+		return IN_Haptic_Effect_Init(
+			12000/* Force comes from EES*/, 700/* 700 ms*/, hapric_volume * 100,
+			500/* 0.5 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			200/* Takes 0.2 second to fade away */);
+	case HAPTIC_EFFECT_SHOTGUN2:
+		/* 150 degrees */
+		return IN_Haptic_Effect_Init(
+			14000/* Force comes from ESS*/, 700/* 700 ms*/, hapric_volume * 96,
+			500/* 0.5 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_SSHOTGUN:
+		return IN_Haptic_Effect_Init(
+			16000/* Force comes from ESS*/, 700/* 700 ms*/, hapric_volume * 96,
+			500/* 0.5 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_RAILGUN:
+		/* 180 degrees */
+		return IN_Haptic_Effect_Init(
+			18000/* Force comes from S*/, 700/* 700 ms*/, hapric_volume * 64,
+			400/* 0.4 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_ROCKETGUN:
+		/* 210 degrees */
+		return IN_Haptic_Effect_Init(
+			21000/* Force comes from SSW*/, 700/* 700 ms*/, hapric_volume * 128,
+			400/* 0.4 seconds long */, 300/* Takes 0.3 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_GRENADE:
+		/* 240 degrees */
+		return IN_Haptic_Effect_Init(
+			24000/* Force comes from SWW*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_BFG:
+		/* 270 degrees */
+		return IN_Haptic_Effect_Init(
+			27000/* Force comes from W*/, 800/* 800 ms*/, hapric_volume * 100,
+			600/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_PALANX:
+		/* 300 degrees */
+		return IN_Haptic_Effect_Init(
+			30000/* Force comes from WWN*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	case HAPTIC_EFFECT_IONRIPPER:
+		/* 330 degrees */
+		return IN_Haptic_Effect_Init(
+			33000/* Force comes from WNN*/, 500/* 500 ms*/, hapric_volume * 64,
+			200/* 0.2 seconds long */, 100/* Takes 0.1 second to get max strength */,
+			100/* Takes 0.1 second to fade away */);
+	default:
+		return -1;
+	}
+}
+
+static void
+IN_Haptic_Effects_Info(void)
+{
+	show_haptic = true;
+	Com_Printf ("Joystic/Mouse haptic:\n");
+	Com_Printf (" * %d effects\n", SDL_HapticNumEffects(joystick_haptic));
+	Com_Printf (" * %d effects in same time\n", SDL_HapticNumEffectsPlaying(joystick_haptic));
+	Com_Printf (" * %d haptic axis\n", SDL_HapticNumAxes(joystick_haptic));
+}
+
+static void
+IN_Haptic_Effects_Init(void)
+{
+	last_haptic_efffect_size = SDL_HapticNumEffectsPlaying(joystick_haptic);
+	if (last_haptic_efffect_size > HAPTIC_EFFECT_LAST)
+		last_haptic_efffect_size = HAPTIC_EFFECT_LAST;
+	for (int i=0; i<HAPTIC_EFFECT_LAST; i++)
+	{
+		last_haptic_efffect[i].effect_type = HAPTIC_EFFECT_UNKNOWN;
+		last_haptic_efffect[i].effect_id = -1;
+	}
+}
+
+/*
+ * Shuts the backend down
+ */
+static void
+IN_Haptic_Effect_Shutdown(int * effect_id)
+{
+	if (!effect_id)
+		return;
+	if (*effect_id >= 0)
+		SDL_HapticDestroyEffect(joystick_haptic, *effect_id);
+	*effect_id = -1;
+}
+
+static void
+IN_Haptic_Effects_Shutdown(void)
+{
+	for (int i=0; i<HAPTIC_EFFECT_LAST; i++)
+	{
+		last_haptic_efffect[i].effect_type = HAPTIC_EFFECT_UNKNOWN;
+		IN_Haptic_Effect_Shutdown(&last_haptic_efffect[i].effect_id);
+	}
+}
+#endif
+
+void
+Haptic_Feedback(char *name)
+{
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	int effect_type = HAPTIC_EFFECT_UNKNOWN;
+
+	if (joy_haptic_magnitude->value <= 0)
+		return;
+
+	if (!joystick_haptic)
+		return;
+
+	if (last_haptic_volume != (int)(joy_haptic_magnitude->value * 255))
+	{
+		IN_Haptic_Effects_Shutdown();
+		IN_Haptic_Effects_Init();
+	}
+	last_haptic_volume = joy_haptic_magnitude->value * 255;
+
+	if (strstr(name, "misc/menu"))
+	{
+		effect_type = HAPTIC_EFFECT_MENY;
+	}
+	else if (strstr(name, "weapons/blastf1a"))
+	{
+		effect_type = HAPTIC_EFFECT_BLASTER;
+	}
+	else if (strstr(name, "weapons/hyprbf1a"))
+	{
+		effect_type = HAPTIC_EFFECT_HYPER_BLASTER;
+	}
+	else if (strstr(name, "weapons/machgf"))
+	{
+		effect_type = HAPTIC_EFFECT_MACHINEGUN;
+	}
+	else if (strstr(name, "weapons/shotgf1b"))
+	{
+		effect_type = HAPTIC_EFFECT_SHOTGUN;
+	}
+	else if (strstr(name, "weapons/sshotf1b"))
+	{
+		effect_type = HAPTIC_EFFECT_SSHOTGUN;
+	}
+	else if (strstr(name, "weapons/railgf1a"))
+	{
+		effect_type = HAPTIC_EFFECT_RAILGUN;
+	}
+	else if (strstr(name, "weapons/rocklf1a"))
+	{
+		effect_type = HAPTIC_EFFECT_ROCKETGUN;
+	}
+	else if (strstr(name, "weapons/grenlf1a") || strstr(name, "weapons/hgrent1a"))
+	{
+		effect_type = HAPTIC_EFFECT_GRENADE;
+	}
+	else if (strstr(name, "weapons/bfg__f1y"))
+	{
+		effect_type = HAPTIC_EFFECT_BFG;
+	}
+	else if (strstr(name, "weapons/plasshot"))
+	{
+		effect_type = HAPTIC_EFFECT_PALANX;
+	}
+	else if (strstr(name, "weapons/rippfire"))
+	{
+		effect_type = HAPTIC_EFFECT_IONRIPPER;
+	}
+	else if (strstr(name, "weapons/nail1"))
+	{
+		effect_type = HAPTIC_EFFECT_ETFRIFLE;
+	}
+	else if (strstr(name, "weapons/shotg2"))
+	{
+		effect_type = HAPTIC_EFFECT_SHOTGUN2;
+	}
+	else if (strstr(name, "weapons/disint2"))
+	{
+		effect_type = HAPTIC_EFFECT_TRACKER;
+	}
+	else if (strstr(name, "player/male/pain") ||
+		strstr(name, "player/female/pain") ||
+		strstr(name, "players/male/pain") ||
+		strstr(name, "players/female/pain"))
+	{
+		effect_type = HAPTIC_EFFECT_PAIN;
+	}
+	else if (strstr(name, "player/step") ||
+		strstr(name, "player/land"))
+	{
+		effect_type = HAPTIC_EFFECT_STEP;
+	}
+	else if (strstr(name, "weapons/trapcock"))
+	{
+		effect_type = HAPTIC_EFFECT_TRAPCOCK;
+	}
+
+	if (effect_type != HAPTIC_EFFECT_UNKNOWN)
+	{
+		// check last effect for reuse
+		if (last_haptic_efffect[last_haptic_efffect_pos].effect_type != effect_type)
+		{
+			// FIFO for effects
+			last_haptic_efffect_pos = (last_haptic_efffect_pos+1) % last_haptic_efffect_size;
+			IN_Haptic_Effect_Shutdown(&last_haptic_efffect[last_haptic_efffect_pos].effect_id);
+			last_haptic_efffect[last_haptic_efffect_pos].effect_type = effect_type;
+			last_haptic_efffect[last_haptic_efffect_pos].effect_id = IN_Haptic_Effects_To_Id(effect_type);
+		}
+		SDL_HapticRunEffect(joystick_haptic, last_haptic_efffect[last_haptic_efffect_pos].effect_id, 1);
+	}
+#endif
+}
 
 /*
  * Initializes the backend
@@ -594,17 +1165,46 @@ IN_Init(void)
 
 	mouse_x = mouse_y = 0;
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	joystick_yaw = joystick_pitch = joystick_forwardmove = joystick_sidemove = 0;
+#endif
+
 	exponential_speedup = Cvar_Get("exponential_speedup", "0", CVAR_ARCHIVE);
 	freelook = Cvar_Get("freelook", "1", 0);
 	in_grab = Cvar_Get("in_grab", "2", CVAR_ARCHIVE);
-	in_mouse = Cvar_Get("in_mouse", "0", CVAR_ARCHIVE);
 	lookstrafe = Cvar_Get("lookstrafe", "0", 0);
 	m_filter = Cvar_Get("m_filter", "0", CVAR_ARCHIVE);
+	m_up = Cvar_Get("m_up", "1", 0);
 	m_forward = Cvar_Get("m_forward", "1", 0);
 	m_pitch = Cvar_Get("m_pitch", "0.022", 0);
 	m_side = Cvar_Get("m_side", "0.8", 0);
 	m_yaw = Cvar_Get("m_yaw", "0.022", 0);
 	sensitivity = Cvar_Get("sensitivity", "3", 0);
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	joy_haptic_magnitude = Cvar_Get("joy_haptic_magnitude", "0.0", CVAR_ARCHIVE);
+
+	joy_yawsensitivity = Cvar_Get("joy_yawsensitivity", "1.0", CVAR_ARCHIVE);
+	joy_pitchsensitivity = Cvar_Get("joy_pitchsensitivity", "1.0", CVAR_ARCHIVE);
+	joy_forwardsensitivity = Cvar_Get("joy_forwardsensitivity", "1.0", CVAR_ARCHIVE);
+	joy_sidesensitivity = Cvar_Get("joy_sidesensitivity", "1.0", CVAR_ARCHIVE);
+	joy_upsensitivity = Cvar_Get("joy_upsensitivity", "1.0", CVAR_ARCHIVE);
+
+	joy_axis_leftx = Cvar_Get("joy_axis_leftx", "sidemove", CVAR_ARCHIVE);
+	joy_axis_lefty = Cvar_Get("joy_axis_lefty", "forwardmove", CVAR_ARCHIVE);
+	joy_axis_rightx = Cvar_Get("joy_axis_rightx", "yaw", CVAR_ARCHIVE);
+	joy_axis_righty = Cvar_Get("joy_axis_righty", "pitch", CVAR_ARCHIVE);
+	joy_axis_triggerleft = Cvar_Get("joy_axis_triggerleft", "triggerleft", CVAR_ARCHIVE);
+	joy_axis_triggerright = Cvar_Get("joy_axis_triggerright", "triggerright", CVAR_ARCHIVE);
+
+	joy_axis_leftx_threshold = Cvar_Get("joy_axis_leftx_threshold", "0.15", CVAR_ARCHIVE);
+	joy_axis_lefty_threshold = Cvar_Get("joy_axis_lefty_threshold", "0.15", CVAR_ARCHIVE);
+	joy_axis_rightx_threshold = Cvar_Get("joy_axis_rightx_threshold", "0.15", CVAR_ARCHIVE);
+	joy_axis_righty_threshold = Cvar_Get("joy_axis_righty_threshold", "0.15", CVAR_ARCHIVE);
+	joy_axis_triggerleft_threshold = Cvar_Get("joy_axis_triggerleft_threshold", "0.15", CVAR_ARCHIVE);
+	joy_axis_triggerright_threshold = Cvar_Get("joy_axis_triggerright_threshold", "0.15", CVAR_ARCHIVE);
+#endif
+
 	vid_fullscreen = Cvar_Get("vid_fullscreen", "0", CVAR_ARCHIVE);
 	windowed_mouse = Cvar_Get("windowed_mouse", "1", CVAR_USERINFO | CVAR_ARCHIVE);
 
@@ -617,12 +1217,103 @@ IN_Init(void)
 	SDL_EnableKeyRepeat(SDL_DEFAULT_REPEAT_DELAY, SDL_DEFAULT_REPEAT_INTERVAL);
 #endif
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	/* joystik init */
+	if (!SDL_WasInit(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC))
+	{
+		if (SDL_Init(SDL_INIT_GAMECONTROLLER | SDL_INIT_HAPTIC) == -1)
+		{
+			Com_Printf ("Couldn't init SDL joystick: %s.\n", SDL_GetError ());
+		} else {
+			Com_Printf ("%i joysticks were found.\n", SDL_NumJoysticks());
+			if (SDL_NumJoysticks() > 0) {
+				int i;
+				for (i=0; i<SDL_NumJoysticks(); i ++) {
+					joystick = SDL_JoystickOpen(i);
+					Com_Printf ("The name of the joystick is '%s'\n", SDL_JoystickName(joystick));
+					Com_Printf ("Number of Axes: %d\n", SDL_JoystickNumAxes(joystick));
+					Com_Printf ("Number of Buttons: %d\n", SDL_JoystickNumButtons(joystick));
+					Com_Printf ("Number of Balls: %d\n", SDL_JoystickNumBalls(joystick));
+					Com_Printf ("Number of Hats: %d\n", SDL_JoystickNumHats(joystick));
+
+					joystick_haptic = SDL_HapticOpenFromJoystick(joystick);
+					if (joystick_haptic == NULL)
+						Com_Printf ("Most likely joystick isn't haptic\n");
+					else
+						IN_Haptic_Effects_Info();
+
+					if(SDL_IsGameController(i))
+					{
+						SDL_GameControllerButtonBind backBind;
+						controller = SDL_GameControllerOpen(i);
+						Com_Printf ("Controller settings: %s\n", SDL_GameControllerMapping(controller));
+						Com_Printf ("Controller axis: \n");
+						Com_Printf (" * leftx = %s\n", joy_axis_leftx->string);
+						Com_Printf (" * lefty = %s\n", joy_axis_lefty->string);
+						Com_Printf (" * rightx = %s\n", joy_axis_rightx->string);
+						Com_Printf (" * righty = %s\n", joy_axis_righty->string);
+						Com_Printf (" * triggerleft = %s\n", joy_axis_triggerleft->string);
+						Com_Printf (" * triggerright = %s\n", joy_axis_triggerright->string);
+
+						Com_Printf ("Controller thresholds: \n");
+						Com_Printf (" * leftx = %f\n", joy_axis_leftx_threshold->value);
+						Com_Printf (" * lefty = %f\n", joy_axis_lefty_threshold->value);
+						Com_Printf (" * rightx = %f\n", joy_axis_rightx_threshold->value);
+						Com_Printf (" * righty = %f\n", joy_axis_righty_threshold->value);
+						Com_Printf (" * triggerleft = %f\n", joy_axis_triggerleft_threshold->value);
+						Com_Printf (" * triggerright = %f\n", joy_axis_triggerright_threshold->value);
+
+						backBind = SDL_GameControllerGetBindForButton(controller, SDL_CONTROLLER_BUTTON_BACK);
+
+						if (backBind.bindType == SDL_CONTROLLER_BINDTYPE_BUTTON) {
+							back_button_id = backBind.value.button;
+							Com_Printf ("\nBack button JOY%d will be unbindable.\n", back_button_id+1);
+						}
+						break;
+					}
+					else
+					{
+						char joystick_guid[256] = {0};
+						SDL_JoystickGUID guid;
+						guid = SDL_JoystickGetDeviceGUID(i);
+						SDL_JoystickGetGUIDString(guid, joystick_guid, 255);
+						Com_Printf ("For use joystic as game contoller please set SDL_GAMECONTROLLERCONFIG:\n");
+						Com_Printf ("e.g.: SDL_GAMECONTROLLERCONFIG='%s,%s,leftx:a0,lefty:a1,rightx:a2,righty:a3,back:b1,...\n", joystick_guid, SDL_JoystickName(joystick));
+					}
+				}
+			}
+			else
+			{
+				joystick_haptic = SDL_HapticOpenFromMouse();
+				if (joystick_haptic == NULL)
+					Com_Printf ("Most likely mouse isn't haptic\n");
+				else
+					IN_Haptic_Effects_Info();
+			}
+		}
+	}
+#endif
+
 	Com_Printf("------------------------------------\n\n");
 }
 
+#if SDL_VERSION_ATLEAST(2, 0, 0)
 /*
  * Shuts the backend down
  */
+static void
+IN_Haptic_Shutdown(void)
+{
+	if (joystick_haptic)
+	{
+		IN_Haptic_Effects_Shutdown();
+
+		SDL_HapticClose(joystick_haptic);
+		joystick_haptic = NULL;
+	}
+}
+#endif
+
 void
 IN_Shutdown(void)
 {
@@ -630,7 +1321,24 @@ IN_Shutdown(void)
 	Cmd_RemoveCommand("+mlook");
 	Cmd_RemoveCommand("-mlook");
 
-    Com_Printf("Shutting down input.\n");
+	Com_Printf("Shutting down input.\n");
+
+#if SDL_VERSION_ATLEAST(2, 0, 0)
+	IN_Haptic_Shutdown();
+
+	if (controller)
+	{
+		back_button_id = -1;
+		SDL_GameControllerClose(controller);
+		controller  = NULL;
+	}
+
+	if (joystick)
+	{
+		SDL_JoystickClose(joystick);
+		joystick = NULL;
+	}
+#endif
 }
 
 /* ------------------------------------------------------------------ */
diff --git a/src/backends/sdl/refresh.c b/src/backends/sdl/refresh.c
index 073db64..569bce7 100644
--- a/src/backends/sdl/refresh.c
+++ b/src/backends/sdl/refresh.c
@@ -232,6 +232,7 @@ static qboolean GetWindowSize(int* w, int* h)
 	return true;
 }
 
+static qboolean initSuccessful = false;
 
 /*
  * Initializes the OpenGL window
@@ -257,7 +258,9 @@ GLimp_InitGraphics(int fullscreen, int *pwidth, int *pheight)
 	}
 #endif
 
-	if (GetWindowSize(&curWidth, &curHeight) && (curWidth == width) && (curHeight == height))
+	// only do this if we already have a working window and fully initialized rendering backend
+	// (GLimp_InitGraphics() is also called when recovering if creating GL context fails or the one we got is unusable)
+	if (initSuccessful && GetWindowSize(&curWidth, &curHeight) && (curWidth == width) && (curHeight == height))
 	{
 		/* If we want fullscreen, but aren't */
 		if (fullscreen != IsFullscreen())
@@ -319,20 +322,17 @@ GLimp_InitGraphics(int fullscreen, int *pwidth, int *pheight)
 	{
 		if (!CreateSDLWindow(flags, width, height))
 		{
-			if (flags & SDL_OPENGL)
+			if((flags & SDL_OPENGL) && gl_msaa_samples->value)
 			{
-				if (gl_msaa_samples->value)
-				{
-					Com_Printf("SDL SetVideoMode failed: %s\n", SDL_GetError());
-					Com_Printf("Reverting to %s gl_mode %i (%ix%i) without MSAA.\n",
-					           (flags & fs_flag) ? "fullscreen" : "windowed",
-					           (int) Cvar_VariableValue("gl_mode"), width, height);
-
-					/* Try to recover */
-					Cvar_SetValue("gl_msaa_samples", 0);
-					SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
-					SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
-				}
+				Com_Printf("SDL SetVideoMode failed: %s\n", SDL_GetError());
+				Com_Printf("Reverting to %s gl_mode %i (%ix%i) without MSAA.\n",
+					        (flags & fs_flag) ? "fullscreen" : "windowed",
+					        (int) Cvar_VariableValue("gl_mode"), width, height);
+
+				/* Try to recover */
+				Cvar_SetValue("gl_msaa_samples", 0);
+				SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+				SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
 			}
 			else if (width != 640 || height != 480 || (flags & fs_flag))
 			{
@@ -374,6 +374,8 @@ GLimp_InitGraphics(int fullscreen, int *pwidth, int *pheight)
 	/* No cursor */
 	SDL_ShowCursor(0);
 
+	initSuccessful = true;
+
 	return true;
 }
 
@@ -453,6 +455,8 @@ VID_ShutdownWindow(void)
 	// make sure that after vid_restart the refreshrate will be queried from SDL2 again.
 	glimp_refreshRate = -1;
 
+	initSuccessful = false; // not initialized anymore
+
 	if (SDL_WasInit(SDL_INIT_EVERYTHING) == SDL_INIT_VIDEO)
 	{
 		SDL_Quit();
diff --git a/src/backends/unix/system.c b/src/backends/unix/system.c
index 410f436..1b6409a 100644
--- a/src/backends/unix/system.c
+++ b/src/backends/unix/system.c
@@ -46,6 +46,11 @@
 #include <dirent.h>
 #include <time.h>
 
+#ifdef __APPLE__
+#include <mach/clock.h>
+#include <mach/mach.h>
+#endif
+
 #include "../../common/header/common.h"
 #include "../../common/header/glob.h"
 #include "../generic/header/input.h"
@@ -82,19 +87,49 @@ Sys_Init(void)
 long long
 Sys_Microseconds(void)
 {
-	static struct timespec last;
-	struct timespec now;
+#ifdef __APPLE__
+	// OSX didn't have clock_gettime() until recently, so use Mach's clock_get_time()
+	// instead. fortunately its mach_timespec_t seems identical to POSIX struct timespec
+	// so lots of code can be shared
+	clock_serv_t cclock;
+	mach_timespec_t now;
+	static mach_timespec_t first;
+
+	host_get_clock_service(mach_host_self(), SYSTEM_CLOCK, &cclock);
+	clock_get_time(cclock, &now);
+	mach_port_deallocate(mach_task_self(), cclock);
+
+#else // not __APPLE__ - other Unix-likes will hopefully support clock_gettime()
 
+	struct timespec now;
+	static struct timespec first;
+  #ifdef _POSIX_MONOTONIC_CLOCK
 	clock_gettime(CLOCK_MONOTONIC, &now);
+  #else
+	clock_gettime(CLOCK_REALTIME, &now);
+  #endif
+
+#endif // not __APPLE__
 
-	if(last.tv_sec == 0)
+	if(first.tv_sec == 0)
 	{
-		clock_gettime(CLOCK_MONOTONIC, &last);
-		return last.tv_nsec / 1000ll;
+		long long nsec = now.tv_nsec;
+		long long sec = now.tv_sec;
+		// set back first by 1ms so neither this function nor Sys_Milliseconds()
+		// (which calls this) will ever return 0
+		nsec -= 1000000; 
+		if(nsec < 0)
+		{
+			nsec += 1000000000ll; // 1s in ns => definitely positive now
+			--sec;
+		}
+
+		first.tv_sec = sec;
+		first.tv_nsec = nsec;
 	}
 
-	long long sec = now.tv_sec - last.tv_sec;
-	long long nsec = now.tv_nsec - last.tv_nsec;
+	long long sec = now.tv_sec - first.tv_sec;
+	long long nsec = now.tv_nsec - first.tv_nsec;
 
 	if(nsec < 0)
 	{
diff --git a/src/backends/windows/system.c b/src/backends/windows/system.c
index 94c4267..3fa5147 100644
--- a/src/backends/windows/system.c
+++ b/src/backends/windows/system.c
@@ -432,7 +432,7 @@ Sys_Microseconds(void)
 
 	if (!uSecbase)
 	{
-		uSecbase = microseconds / 1000ll;
+		uSecbase = microseconds - 1001ll;
 	}
 
 	return microseconds - uSecbase;
diff --git a/src/client/cl_cin.c b/src/client/cl_cin.c
index 36b278b..62644e4 100644
--- a/src/client/cl_cin.c
+++ b/src/client/cl_cin.c
@@ -66,7 +66,7 @@ SCR_LoadPCX(char *filename, byte **pic, byte **palette, int *width, int *height)
 	byte *raw;
 	pcx_t *pcx;
 	int x, y;
-	int len;
+	int len, full_size;
 	int dataByte, runLength;
 	byte *out, *pix;
 
@@ -75,7 +75,7 @@ SCR_LoadPCX(char *filename, byte **pic, byte **palette, int *width, int *height)
 	/* load the file */
 	len = FS_LoadFile(filename, (void **)&raw);
 
-	if (!raw)
+	if (!raw || len < sizeof(pcx_t))
 	{
 		return;
 	}
@@ -95,7 +95,8 @@ SCR_LoadPCX(char *filename, byte **pic, byte **palette, int *width, int *height)
 		return;
 	}
 
-	out = Z_Malloc((pcx->ymax + 1) * (pcx->xmax + 1));
+	full_size = (pcx->ymax + 1) * (pcx->xmax + 1);
+	out = Z_Malloc(full_size);
 
 	*pic = out;
 
@@ -135,7 +136,15 @@ SCR_LoadPCX(char *filename, byte **pic, byte **palette, int *width, int *height)
 
 			while (runLength-- > 0)
 			{
-				pix[x++] = dataByte;
+				if ((*pic + full_size) <= (pix + x))
+				{
+					x += runLength;
+					runLength = 0;
+				}
+				else
+				{
+					pix[x++] = dataByte;
+				}
 			}
 		}
 	}
diff --git a/src/client/cl_keyboard.c b/src/client/cl_keyboard.c
index 2b3c26c..3e612b3 100644
--- a/src/client/cl_keyboard.c
+++ b/src/client/cl_keyboard.c
@@ -106,6 +106,49 @@ keyname_t keynames[] = {
 	{"MOUSE4", K_MOUSE4},
 	{"MOUSE5", K_MOUSE5},
 
+	{"JOY1", K_JOY1},
+	{"JOY2", K_JOY2},
+	{"JOY3", K_JOY3},
+	{"JOY4", K_JOY4},
+	{"JOY5", K_JOY5},
+	{"JOY6", K_JOY6},
+	{"JOY7", K_JOY7},
+	{"JOY8", K_JOY8},
+	{"JOY9", K_JOY9},
+	{"JOY10", K_JOY10},
+	{"JOY11", K_JOY11},
+	{"JOY12", K_JOY12},
+	{"JOY13", K_JOY13},
+	{"JOY14", K_JOY14},
+	{"JOY15", K_JOY15},
+	{"JOY16", K_JOY16},
+	{"JOY17", K_JOY17},
+	{"JOY18", K_JOY18},
+	{"JOY19", K_JOY19},
+	{"JOY20", K_JOY20},
+	{"JOY21", K_JOY21},
+	{"JOY22", K_JOY22},
+	{"JOY23", K_JOY23},
+	{"JOY24", K_JOY24},
+	{"JOY25", K_JOY25},
+	{"JOY26", K_JOY26},
+	{"JOY27", K_JOY27},
+	{"JOY28", K_JOY28},
+	{"JOY29", K_JOY29},
+	{"JOY30", K_JOY30},
+	{"JOY31", K_JOY31},
+	{"JOY32", K_JOY32},
+
+	{"HAT_UP", K_HAT_UP},
+	{"HAT_RIGHT", K_HAT_RIGHT},
+	{"HAT_DOWN", K_HAT_DOWN},
+	{"HAT_LEFT", K_HAT_LEFT},
+
+	{"TRIG_LEFT", K_TRIG_LEFT},
+	{"TRIG_RIGHT", K_TRIG_RIGHT},
+
+	{"JOY_BACK", K_JOY_BACK},
+
 	{"AUX1", K_AUX1},
 	{"AUX2", K_AUX2},
 	{"AUX3", K_AUX3},
@@ -562,7 +605,7 @@ Key_Message(int key)
 }
 
 /*
- * Returns a key number to be used to index 
+ * Returns a key number to be used to index
  * keybindings[] by looking at the given string.
  * Single ascii characters return themselves, while
  * the K_* names are matched up.
@@ -715,7 +758,7 @@ Key_Bind_f(void)
 	}
 
 	/* don't allow binding escape or the special console keys */
-	if(b == K_ESCAPE || b == '^' || b == '`' || b == '~')
+	if(b == K_ESCAPE || b == '^' || b == '`' || b == '~' || b == K_JOY_BACK)
 	{
 		if(doneWithDefaultCfg)
 		{
@@ -773,7 +816,7 @@ Key_WriteBindings(FILE *f)
 	{
 		if (keybindings[i] && keybindings[i][0])
 		{
-			fprintf(f, "bind %s \"%s\"\n", 
+			fprintf(f, "bind %s \"%s\"\n",
 					Key_KeynumToString(i), keybindings[i]);
 		}
 	}
@@ -1061,12 +1104,12 @@ Key_Event(int key, qboolean down, qboolean special)
 	}
 
 	/* Key is unbound */
-	if ((key >= 200) && !keybindings[key] && (cls.key_dest != key_console))
+	if ((key >= K_MOUSE1 && key != K_JOY_BACK) && !keybindings[key] && (cls.key_dest != key_console))
 	{
 		Com_Printf("%s is unbound, hit F4 to set.\n", Key_KeynumToString(key));
 	}
 
-    /* While in attract loop all keys besides F1 to F12 (to
+	/* While in attract loop all keys besides F1 to F12 (to
 	   allow quick load and the like) are treated like escape. */
 	if (cl.attractloop && (cls.key_dest != key_menu) &&
 		!((key >= K_F1) && (key <= K_F12)))
@@ -1081,10 +1124,11 @@ Key_Event(int key, qboolean down, qboolean special)
 	   - moves one menu level up
 	   - closes the menu
 	   - closes the help computer
-	   - closes the chat window */
+	   - closes the chat window
+	   Fully same logic for K_JOY_BACK */
 	if (!cls.disable_screen)
 	{
-		if (key == K_ESCAPE)
+		if (key == K_ESCAPE || key == K_JOY_BACK)
 		{
 			if (!down)
 			{
diff --git a/src/client/cl_main.c b/src/client/cl_main.c
index 3ee4091..4180206 100644
--- a/src/client/cl_main.c
+++ b/src/client/cl_main.c
@@ -57,7 +57,6 @@ cvar_t *cl_showclamp;
 
 cvar_t *cl_paused;
 
-cvar_t *lookspring;
 cvar_t *lookstrafe;
 cvar_t *sensitivity;
 
@@ -93,6 +92,11 @@ centity_t cl_entities[MAX_EDICTS];
 
 entity_state_t cl_parse_entities[MAX_PARSE_ENTITIES];
 
+/*Evil hack against too many power screen and power
+  shield impact sounds. For example if the player
+  fires his shotgun onto a Brain. */
+int num_power_sounds;
+
 extern cvar_t *allow_download;
 extern cvar_t *allow_download_players;
 extern cvar_t *allow_download_models;
@@ -490,7 +494,6 @@ CL_InitLocal(void)
 
 	cl_run = Cvar_Get("cl_run", "0", CVAR_ARCHIVE);
 	freelook = Cvar_Get("freelook", "1", CVAR_ARCHIVE);
-	lookspring = Cvar_Get("lookspring", "0", CVAR_ARCHIVE);
 	lookstrafe = Cvar_Get("lookstrafe", "0", CVAR_ARCHIVE);
 	sensitivity = Cvar_Get("sensitivity", "3", CVAR_ARCHIVE);
 
@@ -735,6 +738,9 @@ CL_Frame(int packetdelta, int renderdelta, int timedelta, qboolean packetframe,
 		cls.netchan.last_received = Sys_Milliseconds();
 	}
 
+	// Reset power shield / power screen sound counter.
+	num_power_sounds = 0;
+
 	if (!cl_timedemo->value)
 	{
 		// Don't throttle too much when connecting / loading.
diff --git a/src/client/cl_prediction.c b/src/client/cl_prediction.c
index a451e23..b607f9e 100644
--- a/src/client/cl_prediction.c
+++ b/src/client/cl_prediction.c
@@ -231,6 +231,7 @@ CL_PredictMovement(void)
 	pmove_t pm;
 	int i;
 	int step;
+	vec3_t tmp;
 
 	if (cls.state != ca_active)
 	{
@@ -296,9 +297,10 @@ CL_PredictMovement(void)
 	}
 
 	step = pm.s.origin[2] - (int)(cl.predicted_origin[2] * 8);
+	VectorCopy(tmp, pm.s.velocity);
 
 	if (((step > 126 && step < 130))
-		&& !VectorCompare((float *)pm.s.velocity, vec3_origin)
+		&& !VectorCompare(tmp, vec3_origin)
 		&& (pm.s.pm_flags & PMF_ON_GROUND))
 	{
 		cl.predicted_step = step * 0.125f;
diff --git a/src/client/cl_screen.c b/src/client/cl_screen.c
index e7a6f9d..1c0334d 100644
--- a/src/client/cl_screen.c
+++ b/src/client/cl_screen.c
@@ -1445,7 +1445,7 @@ SCR_Framecounter(void) {
 
 		char str[10];
 		snprintf(str, sizeof(str), "%3.2ffps", (1000.0 * 1000.0) / (avg / num));
-		DrawStringScaled(scale*(viddef.width - 80), 0, str, scale);
+		DrawStringScaled(viddef.width - scale*(strlen(str)*8 + 2), 0, str, scale);
 	} else if (cl_drawfps->value >= 2) {
 		// Calculate average of frames.
 		int avg = 0;
diff --git a/src/client/cl_tempentities.c b/src/client/cl_tempentities.c
index 0cb4f69..5b3f539 100644
--- a/src/client/cl_tempentities.c
+++ b/src/client/cl_tempentities.c
@@ -24,7 +24,9 @@
  * =======================================================================
  */
 
+#include <SDL2/SDL_scancode.h>
 #include "header/client.h"
+#include "sound/header/local.h"
 
 typedef enum
 {
@@ -716,7 +718,23 @@ CL_ParseTEnt(void)
 				CL_ParticleEffect(pos, dir, 0xb0, 40);
 			}
 
-			S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
+			num_power_sounds++;
+
+			/* If too many of these sounds are started in one frame (for
+			 * example if the player shoots with the super shotgun into
+			 * the power screen of a Brain) things get too loud and OpenAL
+			 * is forced to scale the volume of several other sounds and
+			 * the background music down. That leads to a noticable and
+			 * annoying drop in the overall volume.
+			 *
+			 * Work around that by limiting the number of sounds started.
+			 * 16 was choosen by empirical testing.
+			 */
+			if (sound_started == SS_OAL && num_power_sounds < 16)
+			{
+				S_StartSound(pos, 0, 0, cl_sfx_lashit, 1, ATTN_NORM, 0);
+			}
+
 			break;
 
 		case TE_SHOTGUN: /* bullet hitting wall */
diff --git a/src/client/header/client.h b/src/client/header/client.h
index eacad82..9c58b0e 100644
--- a/src/client/header/client.h
+++ b/src/client/header/client.h
@@ -249,6 +249,11 @@ typedef struct
 
 extern client_static_t	cls;
 
+/*Evil hack against too many power screen and power
+  shield impact sounds. For example if the player
+  fires his shotgun onto a Brain. */
+extern int num_power_sounds;
+
 /* cvars */
 extern	cvar_t	*gl_stereo_separation;
 extern	cvar_t	*gl_stereo_convergence;
@@ -271,7 +276,6 @@ extern	cvar_t	*cl_anglespeedkey;
 extern	cvar_t	*cl_shownet;
 extern	cvar_t	*cl_showmiss;
 extern	cvar_t	*cl_showclamp;
-extern	cvar_t	*lookspring;
 extern	cvar_t	*lookstrafe;
 extern	cvar_t	*sensitivity;
 extern	cvar_t	*m_pitch;
diff --git a/src/client/header/keyboard.h b/src/client/header/keyboard.h
index c6431c0..668606b 100644
--- a/src/client/header/keyboard.h
+++ b/src/client/header/keyboard.h
@@ -144,6 +144,17 @@ enum QKEYS {
 	K_JOY31,
 	K_JOY32,
 
+	K_HAT_UP,
+	K_HAT_RIGHT,
+	K_HAT_DOWN,
+	K_HAT_LEFT,
+
+	K_TRIG_LEFT,
+	K_TRIG_RIGHT,
+
+	/* Can't be mapped to any action */
+	K_JOY_BACK,
+
 	K_AUX1,
 	K_AUX2,
 	K_AUX3,
@@ -305,6 +316,7 @@ void Key_ReadConsoleHistory();
 void Key_WriteConsoleHistory();
 void Key_SetBinding(int keynum, char *binding);
 void Key_MarkAllUp(void);
-int Key_GetKey(void);
+void Haptic_Feedback(char *name);
+int Key_GetMenuKey(int key);
 
 #endif
diff --git a/src/client/menu/menu.c b/src/client/menu/menu.c
index b8c07f6..93336f1 100644
--- a/src/client/menu/menu.c
+++ b/src/client/menu/menu.c
@@ -165,7 +165,7 @@ M_PushMenu(void (*draw)(void), const char *(*key)(int))
     }
 
 #ifdef USE_OPENAL
-    if (cl.cinematic_file)
+    if (cl.cinematic_file && sound_started == SS_OAL)
     {
         AL_UnqueueRawSamples();
     }
@@ -215,11 +215,122 @@ M_PushMenu(void (*draw)(void), const char *(*key)(int))
     cls.key_dest = key_menu;
 }
 
+int
+Key_GetMenuKey(int key)
+{
+	switch (key)
+	{
+		case K_KP_UPARROW:
+		case K_UPARROW:
+		case K_HAT_UP:
+			return K_UPARROW;
+
+		case K_TAB:
+		case K_KP_DOWNARROW:
+		case K_DOWNARROW:
+		case K_HAT_DOWN:
+			return K_DOWNARROW;
+
+		case K_KP_LEFTARROW:
+		case K_LEFTARROW:
+		case K_HAT_LEFT:
+		case K_TRIG_LEFT:
+			return K_LEFTARROW;
+
+		case K_KP_RIGHTARROW:
+		case K_RIGHTARROW:
+		case K_HAT_RIGHT:
+		case K_TRIG_RIGHT:
+			return K_RIGHTARROW;
+
+		case K_MOUSE1:
+		case K_MOUSE2:
+		case K_MOUSE3:
+		case K_MOUSE4:
+		case K_MOUSE5:
+
+		case K_JOY1:
+		case K_JOY2:
+		case K_JOY3:
+		case K_JOY4:
+		case K_JOY5:
+		case K_JOY6:
+		case K_JOY7:
+		case K_JOY8:
+		case K_JOY9:
+		case K_JOY10:
+		case K_JOY11:
+		case K_JOY12:
+		case K_JOY13:
+		case K_JOY14:
+		case K_JOY15:
+		case K_JOY16:
+		case K_JOY17:
+		case K_JOY18:
+		case K_JOY19:
+		case K_JOY20:
+		case K_JOY21:
+		case K_JOY22:
+		case K_JOY23:
+		case K_JOY24:
+		case K_JOY25:
+		case K_JOY26:
+		case K_JOY27:
+		case K_JOY28:
+		case K_JOY29:
+		case K_JOY30:
+		case K_JOY31:
+
+		case K_AUX1:
+		case K_AUX2:
+		case K_AUX3:
+		case K_AUX4:
+		case K_AUX5:
+		case K_AUX6:
+		case K_AUX7:
+		case K_AUX8:
+		case K_AUX9:
+		case K_AUX10:
+		case K_AUX11:
+		case K_AUX12:
+		case K_AUX13:
+		case K_AUX14:
+		case K_AUX15:
+		case K_AUX16:
+		case K_AUX17:
+		case K_AUX18:
+		case K_AUX19:
+		case K_AUX20:
+		case K_AUX21:
+		case K_AUX22:
+		case K_AUX23:
+		case K_AUX24:
+		case K_AUX25:
+		case K_AUX26:
+		case K_AUX27:
+		case K_AUX28:
+		case K_AUX29:
+		case K_AUX30:
+		case K_AUX31:
+		case K_AUX32:
+
+		case K_KP_ENTER:
+		case K_ENTER:
+			return K_ENTER;
+
+		case K_ESCAPE:
+		case K_JOY_BACK:
+			return K_ESCAPE;
+	}
+
+	return key;
+}
 const char *
 Default_MenuKey(menuframework_s *m, int key)
 {
     const char *sound = NULL;
     menucommon_s *item;
+    int menu_key = Key_GetMenuKey(key);
 
     if (m)
     {
@@ -235,110 +346,51 @@ Default_MenuKey(menuframework_s *m, int key)
         }
     }
 
-    switch (key)
+    switch (menu_key)
     {
     case K_ESCAPE:
         M_PopMenu();
         return menu_out_sound;
-    case K_KP_UPARROW:
-    case K_UPARROW:
 
+    case K_UPARROW:
         if (m)
         {
             m->cursor--;
             Menu_AdjustCursor(m, -1);
             sound = menu_move_sound;
         }
-
         break;
-    case K_TAB:
 
-        if (m)
-        {
-            m->cursor++;
-            Menu_AdjustCursor(m, 1);
-            sound = menu_move_sound;
-        }
-
-        break;
-    case K_KP_DOWNARROW:
     case K_DOWNARROW:
-
         if (m)
         {
             m->cursor++;
             Menu_AdjustCursor(m, 1);
             sound = menu_move_sound;
         }
-
         break;
-    case K_KP_LEFTARROW:
-    case K_LEFTARROW:
 
+    case K_LEFTARROW:
         if (m)
         {
             Menu_SlideItem(m, -1);
             sound = menu_move_sound;
         }
-
         break;
-    case K_KP_RIGHTARROW:
-    case K_RIGHTARROW:
 
+    case K_RIGHTARROW:
         if (m)
         {
             Menu_SlideItem(m, 1);
             sound = menu_move_sound;
         }
-
         break;
 
-    case K_MOUSE1:
-    case K_MOUSE2:
-    case K_MOUSE3:
-    case K_MOUSE4:
-    case K_MOUSE5:
-    case K_AUX1:
-    case K_AUX2:
-    case K_AUX3:
-    case K_AUX4:
-    case K_AUX5:
-    case K_AUX6:
-    case K_AUX7:
-    case K_AUX8:
-    case K_AUX9:
-    case K_AUX10:
-    case K_AUX11:
-    case K_AUX12:
-    case K_AUX13:
-    case K_AUX14:
-    case K_AUX15:
-    case K_AUX16:
-    case K_AUX17:
-    case K_AUX18:
-    case K_AUX19:
-    case K_AUX20:
-    case K_AUX21:
-    case K_AUX22:
-    case K_AUX23:
-    case K_AUX24:
-    case K_AUX25:
-    case K_AUX26:
-    case K_AUX27:
-    case K_AUX28:
-    case K_AUX29:
-    case K_AUX30:
-    case K_AUX31:
-    case K_AUX32:
-
-    case K_KP_ENTER:
     case K_ENTER:
-
         if (m)
         {
             Menu_SelectItem(m);
         }
-
         sound = menu_move_sound;
         break;
     }
@@ -593,35 +645,29 @@ M_Main_Draw(void)
 const char *
 M_Main_Key(int key)
 {
-    const char *sound = menu_move_sound;
+	const char *sound = menu_move_sound;
+	int menu_key = Key_GetMenuKey(key);
 
-    switch (key)
+    switch (menu_key)
     {
     case K_ESCAPE:
         M_PopMenu();
         break;
 
-    case K_KP_DOWNARROW:
     case K_DOWNARROW:
-
         if (++m_main_cursor >= MAIN_ITEMS)
         {
             m_main_cursor = 0;
         }
-
         return sound;
 
-    case K_KP_UPARROW:
     case K_UPARROW:
-
         if (--m_main_cursor < 0)
         {
             m_main_cursor = MAIN_ITEMS - 1;
         }
-
         return sound;
 
-    case K_KP_ENTER:
     case K_ENTER:
         m_entersound = true;
 
@@ -1003,10 +1049,12 @@ static menuslider_s s_options_sensitivity_slider;
 static menulist_s s_options_freelook_box;
 static menulist_s s_options_alwaysrun_box;
 static menulist_s s_options_invertmouse_box;
-static menulist_s s_options_lookspring_box;
 static menulist_s s_options_lookstrafe_box;
 static menulist_s s_options_crosshair_box;
 static menuslider_s s_options_sfxvolume_slider;
+#ifdef SDL2
+static menuslider_s s_options_haptic_slider;
+#endif
 #if defined(OGG) || defined(CDA)
 static menulist_s s_options_cdshuffle_box;
 #endif
@@ -1023,6 +1071,14 @@ CrosshairFunc(void *unused)
     Cvar_SetValue("crosshair", (float)s_options_crosshair_box.curvalue);
 }
 
+#ifdef SDL2
+static void
+HapticMagnitudeFunc(void *unused)
+{
+    Cvar_SetValue("joy_haptic_magnitude", s_options_haptic_slider.curvalue / 10.0F);
+}
+#endif
+
 static void
 CustomizeControlsFunc(void *unused)
 {
@@ -1097,13 +1153,15 @@ ControlsSetMenuItemValues(void)
 
     s_options_invertmouse_box.curvalue = (m_pitch->value < 0);
 
-    s_options_lookspring_box.curvalue = (lookspring->value != 0);
-
     s_options_lookstrafe_box.curvalue = (lookstrafe->value != 0);
 
     s_options_freelook_box.curvalue = (freelook->value != 0);
 
     s_options_crosshair_box.curvalue = ClampCvar(0, 3, crosshair->value);
+
+#ifdef SDL2
+    s_options_haptic_slider.curvalue = Cvar_VariableValue("joy_haptic_magnitude") * 10.0F;
+#endif
 }
 
 static void
@@ -1123,12 +1181,6 @@ InvertMouseFunc(void *unused)
 }
 
 static void
-LookspringFunc(void *unused)
-{
-    Cvar_SetValue("lookspring", (float)!lookspring->value);
-}
-
-static void
 LookstrafeFunc(void *unused)
 {
     Cvar_SetValue("lookstrafe", (float)!lookstrafe->value);
@@ -1319,7 +1371,11 @@ Options_MenuInit(void)
         0
     };
 
-	float scale = SCR_GetMenuScale();
+    float scale = SCR_GetMenuScale();
+
+#ifdef SDL2
+    extern qboolean show_haptic;
+#endif
 
     /* configure controls menu and menu items */
     s_options_menu.x = viddef.width / 2;
@@ -1389,34 +1445,37 @@ Options_MenuInit(void)
     s_options_invertmouse_box.generic.callback = InvertMouseFunc;
     s_options_invertmouse_box.itemnames = yesno_names;
 
-    s_options_lookspring_box.generic.type = MTYPE_SPINCONTROL;
-    s_options_lookspring_box.generic.x = 0;
-    s_options_lookspring_box.generic.y = 90;
-    s_options_lookspring_box.generic.name = "lookspring";
-    s_options_lookspring_box.generic.callback = LookspringFunc;
-    s_options_lookspring_box.itemnames = yesno_names;
-
     s_options_lookstrafe_box.generic.type = MTYPE_SPINCONTROL;
     s_options_lookstrafe_box.generic.x = 0;
-    s_options_lookstrafe_box.generic.y = 100;
+    s_options_lookstrafe_box.generic.y = 90;
     s_options_lookstrafe_box.generic.name = "lookstrafe";
     s_options_lookstrafe_box.generic.callback = LookstrafeFunc;
     s_options_lookstrafe_box.itemnames = yesno_names;
 
     s_options_freelook_box.generic.type = MTYPE_SPINCONTROL;
     s_options_freelook_box.generic.x = 0;
-    s_options_freelook_box.generic.y = 110;
+    s_options_freelook_box.generic.y = 100;
     s_options_freelook_box.generic.name = "free look";
     s_options_freelook_box.generic.callback = FreeLookFunc;
     s_options_freelook_box.itemnames = yesno_names;
 
     s_options_crosshair_box.generic.type = MTYPE_SPINCONTROL;
     s_options_crosshair_box.generic.x = 0;
-    s_options_crosshair_box.generic.y = 120;
+    s_options_crosshair_box.generic.y = 110;
     s_options_crosshair_box.generic.name = "crosshair";
     s_options_crosshair_box.generic.callback = CrosshairFunc;
     s_options_crosshair_box.itemnames = crosshair_names;
 
+#ifdef SDL2
+    s_options_haptic_slider.generic.type = MTYPE_SLIDER;
+    s_options_haptic_slider.generic.x = 0;
+    s_options_haptic_slider.generic.y = 120;
+    s_options_haptic_slider.generic.name = "haptic magnitude";
+    s_options_haptic_slider.generic.callback = HapticMagnitudeFunc;
+    s_options_haptic_slider.minvalue = 0;
+    s_options_haptic_slider.maxvalue = 22;
+#endif
+
     s_options_customize_options_action.generic.type = MTYPE_ACTION;
     s_options_customize_options_action.generic.x = 0;
     s_options_customize_options_action.generic.y = 140;
@@ -1450,10 +1509,15 @@ Options_MenuInit(void)
     Menu_AddItem(&s_options_menu, (void *)&s_options_sensitivity_slider);
     Menu_AddItem(&s_options_menu, (void *)&s_options_alwaysrun_box);
     Menu_AddItem(&s_options_menu, (void *)&s_options_invertmouse_box);
-    Menu_AddItem(&s_options_menu, (void *)&s_options_lookspring_box);
     Menu_AddItem(&s_options_menu, (void *)&s_options_lookstrafe_box);
     Menu_AddItem(&s_options_menu, (void *)&s_options_freelook_box);
     Menu_AddItem(&s_options_menu, (void *)&s_options_crosshair_box);
+
+#ifdef SDL2
+    if (show_haptic)
+        Menu_AddItem(&s_options_menu, (void *)&s_options_haptic_slider);
+#endif
+
     Menu_AddItem(&s_options_menu, (void *)&s_options_customize_options_action);
     Menu_AddItem(&s_options_menu, (void *)&s_options_defaults_action);
     Menu_AddItem(&s_options_menu, (void *)&s_options_console_action);
@@ -2305,10 +2369,10 @@ static const char *
 LoadGame_MenuKey(int key)
 {
     static menuframework_s *m = &s_loadgame_menu;
+    int menu_key = Key_GetMenuKey(key);
 
-    switch (key)
+    switch (menu_key)
     {
-    case K_KP_UPARROW:
     case K_UPARROW:
         if (m->cursor == 0)
         {
@@ -2316,8 +2380,7 @@ LoadGame_MenuKey(int key)
             LoadGame_MenuInit();
         }
         break;
-    case K_TAB:
-    case K_KP_DOWNARROW:
+
     case K_DOWNARROW:
         if (m->cursor == m->nitems - 1)
         {
@@ -2325,16 +2388,17 @@ LoadGame_MenuKey(int key)
             LoadGame_MenuInit();
         }
         break;
-    case K_KP_LEFTARROW:
+
     case K_LEFTARROW:
         LoadSave_AdjustPage(-1);
         LoadGame_MenuInit();
         return menu_move_sound;
-    case K_KP_RIGHTARROW:
+
     case K_RIGHTARROW:
         LoadSave_AdjustPage(1);
         LoadGame_MenuInit();
         return menu_move_sound;
+
     default:
         s_savegame_menu.cursor = s_loadgame_menu.cursor;
         break;
@@ -2416,6 +2480,7 @@ static const char *
 SaveGame_MenuKey(int key)
 {
     static menuframework_s *m = &s_savegame_menu;
+    int menu_key = Key_GetMenuKey(key);
 
     if (m_popup_string)
     {
@@ -2423,9 +2488,8 @@ SaveGame_MenuKey(int key)
         return NULL;
     }
 
-    switch (key)
+    switch (menu_key)
     {
-    case K_KP_UPARROW:
     case K_UPARROW:
         if (m->cursor == 0)
         {
@@ -2433,8 +2497,7 @@ SaveGame_MenuKey(int key)
             SaveGame_MenuInit();
         }
         break;
-    case K_TAB:
-    case K_KP_DOWNARROW:
+
     case K_DOWNARROW:
         if (m->cursor == m->nitems - 1)
         {
@@ -2442,16 +2505,17 @@ SaveGame_MenuKey(int key)
             SaveGame_MenuInit();
         }
         break;
-    case K_KP_LEFTARROW:
+
     case K_LEFTARROW:
         LoadSave_AdjustPage(-1);
         SaveGame_MenuInit();
         return menu_move_sound;
-    case K_KP_RIGHTARROW:
+
     case K_RIGHTARROW:
         LoadSave_AdjustPage(1);
         SaveGame_MenuInit();
         return menu_move_sound;
+
     default:
         s_loadgame_menu.cursor = s_savegame_menu.cursor;
         break;
@@ -4257,7 +4321,8 @@ M_Menu_PlayerConfig_f(void)
 static const char *
 M_Quit_Key(int key)
 {
-    switch (key)
+    int menu_key = Key_GetMenuKey(key);
+    switch (menu_key)
     {
     case K_ESCAPE:
     case 'n':
@@ -4265,6 +4330,7 @@ M_Quit_Key(int key)
         M_PopMenu();
         break;
 
+    case K_ENTER:
     case 'Y':
     case 'y':
         cls.key_dest = key_console;
diff --git a/src/client/menu/videomenu.c b/src/client/menu/videomenu.c
index 331dbf7..8511907 100644
--- a/src/client/menu/videomenu.c
+++ b/src/client/menu/videomenu.c
@@ -261,6 +261,14 @@ VID_MenuInit(void)
 		"[1920 1080 ]",
 		"[1920 1200 ]",
 		"[2048 1536 ]",
+		"[2560x1080 ]",
+		"[2560x1440 ]",
+		"[2560x1600 ]",
+		"[3440x1440 ]",
+		"[3840x1600 ]",
+		"[3840x2160 ]",
+		"[4096x2160 ]",
+		"[5120x2880 ]",
 		"[custom    ]",
 		0
 	};
@@ -522,8 +530,9 @@ VID_MenuKey(int key)
 
 	menuframework_s *m = &s_opengl_menu;
 	static const char *sound = "misc/menu1.wav";
+	int menu_key = Key_GetMenuKey(key);
 
-	switch (key)
+	switch (menu_key)
 	{
 		case K_ESCAPE:
 			M_PopMenu();
diff --git a/src/client/refresh/files/pcx.c b/src/client/refresh/files/pcx.c
index 010a224..926e0ea 100644
--- a/src/client/refresh/files/pcx.c
+++ b/src/client/refresh/files/pcx.c
@@ -122,7 +122,9 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 	byte *raw;
 	pcx_t *pcx;
 	int x, y;
-	int len;
+	int len, full_size;
+	int pcx_width, pcx_height;
+	qboolean image_issues = false;
 	int dataByte, runLength;
 	byte *out, *pix;
 	char filename[256];
@@ -145,7 +147,7 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 	/* load the file */
 	len = ri.FS_LoadFile(filename, (void **)&raw);
 
-	if (!raw)
+	if (!raw || len < sizeof(pcx_t))
 	{
 		R_Printf(PRINT_DEVELOPER, "Bad pcx file %s\n", filename);
 		return;
@@ -165,15 +167,19 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 
 	raw = &pcx->data;
 
+	pcx_width = pcx->xmax - pcx->xmin;
+	pcx_height = pcx->ymax - pcx->ymin;
+
 	if ((pcx->manufacturer != 0x0a) || (pcx->version != 5) ||
 		(pcx->encoding != 1) || (pcx->bits_per_pixel != 8) ||
-		(pcx->xmax >= 640) || (pcx->ymax >= 480))
+		(pcx_width >= 4096) || (pcx_height >= 4096))
 	{
 		R_Printf(PRINT_ALL, "Bad pcx file %s\n", filename);
 		return;
 	}
 
-	out = malloc((pcx->ymax + 1) * (pcx->xmax + 1));
+	full_size = (pcx_height + 1) * (pcx_width + 1);
+	out = malloc(full_size);
 
 	*pic = out;
 
@@ -182,28 +188,49 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 	if (palette)
 	{
 		*palette = malloc(768);
-		memcpy(*palette, (byte *)pcx + len - 768, 768);
+		if (len > 768)
+		{
+			memcpy(*palette, (byte *)pcx + len - 768, 768);
+		}
+		else
+		{
+			image_issues = true;
+		}
 	}
 
 	if (width)
 	{
-		*width = pcx->xmax + 1;
+		*width = pcx_width + 1;
 	}
 
 	if (height)
 	{
-		*height = pcx->ymax + 1;
+		*height = pcx_height + 1;
 	}
 
-	for (y = 0; y <= pcx->ymax; y++, pix += pcx->xmax + 1)
+	for (y = 0; y <= pcx_height; y++, pix += pcx_width + 1)
 	{
-		for (x = 0; x <= pcx->xmax; )
+		for (x = 0; x <= pcx_width; )
 		{
+			if (raw - (byte *)pcx > len)
+			{
+				// no place for read
+				image_issues = true;
+				x = pcx_width;
+				break;
+			}
 			dataByte = *raw++;
 
 			if ((dataByte & 0xC0) == 0xC0)
 			{
 				runLength = dataByte & 0x3F;
+				if (raw - (byte *)pcx > len)
+				{
+					// no place for read
+					image_issues = true;
+					x = pcx_width;
+					break;
+				}
 				dataByte = *raw++;
 			}
 			else
@@ -213,7 +240,17 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 
 			while (runLength-- > 0)
 			{
-				pix[x++] = dataByte;
+				if ((*pic + full_size) <= (pix + x))
+				{
+					// no place for write
+					image_issues = true;
+					x += runLength;
+					runLength = 0;
+				}
+				else
+				{
+					pix[x++] = dataByte;
+				}
 			}
 		}
 	}
@@ -224,7 +261,7 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 		free(*pic);
 		*pic = NULL;
 	}
-	else if(pcx->xmax == 319 && pcx->ymax == 239
+	else if(pcx_width == 319 && pcx_height == 239
 			&& Q_strcasecmp(origname, "pics/quit.pcx") == 0
 			&& Com_BlockChecksum(pcx, len) == 3329419434u)
 	{
@@ -233,6 +270,11 @@ LoadPCX(char *origname, byte **pic, byte **palette, int *width, int *height)
 		fixQuitScreen(*pic);
 	}
 
+	if (image_issues)
+	{
+		R_Printf(PRINT_ALL, "PCX file %s has possible size issues.\n", filename);
+	}
+
 	ri.FS_FreeFile(pcx);
 }
 
diff --git a/src/client/refresh/gl/r_main.c b/src/client/refresh/gl/r_main.c
index 003465b..bc0e213 100644
--- a/src/client/refresh/gl/r_main.c
+++ b/src/client/refresh/gl/r_main.c
@@ -1344,6 +1344,17 @@ R_SetMode(void)
 		else if (err == rserr_invalid_mode)
 		{
 			R_Printf(PRINT_ALL, "ref_gl::R_SetMode() - invalid mode\n");
+			if (gl_msaa_samples->value != 0.0f)
+			{
+				R_Printf(PRINT_ALL, "gl_msaa_samples was %d - will try again with gl_msaa_samples = 0\n", (int)gl_msaa_samples->value);
+				ri.Cvar_SetValue("gl_msaa_samples", 0.0f);
+				gl_msaa_samples->modified = false;
+
+				if ((err = SetMode_impl(&vid.width, &vid.height, gl_mode->value, 0)) == rserr_ok)
+				{
+					return true;
+				}
+			}
 			if(gl_mode->value == gl_state.prev_mode)
 			{
 				// trying again would result in a crash anyway, give up already
diff --git a/src/client/refresh/gl/r_sdl.c b/src/client/refresh/gl/r_sdl.c
index dd64c3c..7a6f261 100644
--- a/src/client/refresh/gl/r_sdl.c
+++ b/src/client/refresh/gl/r_sdl.c
@@ -360,6 +360,11 @@ int RI_PrepareForWindow(void)
 			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
 		}
 	}
+	else
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+	}
 
 	/* Initiate the flags */
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -410,6 +415,14 @@ int RI_InitContext(void* win)
 
 #endif
 
+	const char* glver = (char *)glGetString(GL_VERSION);
+	sscanf(glver, "%d.%d", &gl_config.major_version, &gl_config.minor_version);
+	if (gl_config.major_version < 1 || (gl_config.major_version == 1 && gl_config.minor_version < 4))
+	{
+		R_Printf(PRINT_ALL, "R_InitContext(): Got an OpenGL version %d.%d context - need (at least) 1.4!\n", gl_config.major_version, gl_config.minor_version);
+		return false;
+	}
+
 	if (gl_msaa_samples->value)
 	{
 		if (SDL_GL_GetAttribute(SDL_GL_MULTISAMPLESAMPLES, &msaa_samples) == 0)
diff --git a/src/client/refresh/gl3/gl3_main.c b/src/client/refresh/gl3/gl3_main.c
index b40cff0..ea517d3 100644
--- a/src/client/refresh/gl3/gl3_main.c
+++ b/src/client/refresh/gl3/gl3_main.c
@@ -406,6 +406,17 @@ GL3_SetMode(void)
 		{
 			R_Printf(PRINT_ALL, "ref_gl3::GL3_SetMode() - invalid mode\n");
 
+			if (gl_msaa_samples->value != 0.0f)
+			{
+				R_Printf(PRINT_ALL, "gl_msaa_samples was %d - will try again with gl_msaa_samples = 0\n", (int)gl_msaa_samples->value);
+				ri.Cvar_SetValue("gl_msaa_samples", 0.0f);
+				gl_msaa_samples->modified = false;
+
+				if ((err = SetMode_impl(&vid.width, &vid.height, gl_mode->value, 0)) == rserr_ok)
+				{
+					return true;
+				}
+			}
 			if(gl_mode->value == gl3state.prev_mode)
 			{
 				// trying again would result in a crash anyway, give up already
diff --git a/src/client/refresh/gl3/gl3_sdl.c b/src/client/refresh/gl3/gl3_sdl.c
index 45519c6..c932364 100644
--- a/src/client/refresh/gl3/gl3_sdl.c
+++ b/src/client/refresh/gl3/gl3_sdl.c
@@ -113,6 +113,11 @@ int GL3_PrepareForWindow(void)
 			SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
 		}
 	}
+	else
+	{
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLEBUFFERS, 0);
+		SDL_GL_SetAttribute(SDL_GL_MULTISAMPLESAMPLES, 0);
+	}
 
 	/* Initiate the flags */
 #if SDL_VERSION_ATLEAST(2, 0, 0)
@@ -131,7 +136,7 @@ enum {
 	QGL_DEBUG_SEVERITY_NOTIFICATION = 0x826B
 };
 
-static void
+static void APIENTRY
 DebugCallback(GLenum source, GLenum type, GLuint id, GLenum severity, GLsizei length,
               const GLchar *message, const void *userParam)
 {
@@ -230,12 +235,17 @@ int GL3_InitContext(void* win)
 
 	if(!gladLoadGLLoader(SDL_GL_GetProcAddress))
 	{
-		R_Printf(PRINT_ALL, "R_InitContext(): loading OpenGL function pointers failed!\n");
+		R_Printf(PRINT_ALL, "GL3_InitContext(): ERROR: loading OpenGL function pointers failed!\n");
+		return false;
+	}
+	else if (GLVersion.major < 3 || (GLVersion.major == 3 && GLVersion.minor < 2))
+	{
+		R_Printf(PRINT_ALL, "GL3_InitContext(): ERROR: glad only got GL version %d.%d!\n", GLVersion.major, GLVersion.minor);
 		return false;
 	}
 	else
 	{
-		R_Printf(PRINT_ALL, "Successfully loaded OpenGL function pointers using glad!\n");
+		R_Printf(PRINT_ALL, "Successfully loaded OpenGL function pointers using glad, got version %d.%d!\n", GLVersion.major, GLVersion.minor);
 	}
 
 #if SDL_VERSION_ATLEAST(2, 0, 0)
diff --git a/src/client/sound/ogg.c b/src/client/sound/ogg.c
index 4f521f7..35fab85 100644
--- a/src/client/sound/ogg.c
+++ b/src/client/sound/ogg.c
@@ -636,18 +636,12 @@ OGG_Stream(void)
 			/* Calculate the number of buffers used
 			   for storing decoded OGG/Vorbis data.
 			   We take the number of active buffers
-			   at startup (at this point most of the
-			   samples should be precached and loaded
-			   into buffers) and add 64. Empircal
-			   testing showed, that at most times
-			   at least 52 buffers remain available
-			   for OGG/Vorbis, enough for about 3
-			   seconds playback. The music won't
-			   stutter as long as the framerate
-			   stayes over 1 FPS. */
-			if (ogg_numbufs == 0)
+			   and add 256. 256 are about 12 seconds
+			   worth of sound, more than enough to
+			   be resilent against underruns. */
+			if (ogg_numbufs == 0 || active_buffers < ogg_numbufs - 256)
 			{
-				ogg_numbufs = active_buffers + 64;
+				ogg_numbufs = active_buffers + 256;
 			}
 
 			/* active_buffers are all active OpenAL buffers,
@@ -761,7 +755,10 @@ OGG_PauseCmd(void)
 	}
 
 #ifdef USE_OPENAL
-	AL_UnqueueRawSamples();
+	if (sound_started == SS_OAL)
+	{
+		AL_UnqueueRawSamples();
+	}
 #endif
 }
 
diff --git a/src/client/sound/sound.c b/src/client/sound/sound.c
index 6f98c39..36d384d 100644
--- a/src/client/sound/sound.c
+++ b/src/client/sound/sound.c
@@ -669,6 +669,16 @@ S_StartSound(vec3_t origin, int entnum, int entchannel, sfx_t *sfx,
 		ps->fixed_origin = false;
 	}
 
+	if (sfx->name[0])
+	{
+		// with !fixed we have all sounds related directly to player,
+		// e.g. players fire, pain, menu
+		if (!ps->fixed_origin)
+		{
+			Haptic_Feedback(sfx->name);
+		}
+	}
+
 	ps->entnum = entnum;
 	ps->entchannel = entchannel;
 	ps->attenuation = attenuation;
diff --git a/src/common/collision.c b/src/common/collision.c
index f494013..bdaa5e4 100644
--- a/src/common/collision.c
+++ b/src/common/collision.c
@@ -119,7 +119,7 @@ vec3_t trace_extents;
 int		c_pointcontents;
 int		c_traces, c_brush_traces;
 #endif
- 
+
 /* 1/32 epsilon to keep floating point happy */
 #define DIST_EPSILON (0.03125f)
 
@@ -738,7 +738,7 @@ CM_ClipBoxToBrush(vec3_t mins, vec3_t maxs, vec3_t p1,
 }
 
 void
-CM_TestBoxInBrush(vec3_t mins, vec3_t maxs, vec3_t p1, 
+CM_TestBoxInBrush(vec3_t mins, vec3_t maxs, vec3_t p1,
 		trace_t *trace, cbrush_t *brush)
 {
 	int i, j;
@@ -1186,7 +1186,7 @@ CMod_LoadSubmodels(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadSubmodels: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1230,7 +1230,7 @@ CMod_LoadSurfaces(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadSurfaces: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1269,7 +1269,7 @@ CMod_LoadNodes(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadNodes: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1311,7 +1311,7 @@ CMod_LoadBrushes(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadBrushes: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1345,7 +1345,7 @@ CMod_LoadLeafs(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadLeafs: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1415,7 +1415,7 @@ CMod_LoadPlanes(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadPlanes: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1466,7 +1466,7 @@ CMod_LoadLeafBrushes(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadLeafBrushes: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1504,7 +1504,7 @@ CMod_LoadBrushSides(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadBrushSides: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1545,7 +1545,7 @@ CMod_LoadAreas(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadAreas: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
@@ -1578,7 +1578,7 @@ CMod_LoadAreaPortals(lump_t *l)
 
 	if (l->filelen % sizeof(*in))
 	{
-		Com_Error(ERR_DROP, "MOD_LoadBmodel: funny lump size");
+		Com_Error(ERR_DROP, "Mod_LoadAreaPortals: funny lump size");
 	}
 
 	count = l->filelen / sizeof(*in);
diff --git a/src/common/frame.c b/src/common/frame.c
index 1fcc90f..606ac14 100644
--- a/src/common/frame.c
+++ b/src/common/frame.c
@@ -293,6 +293,22 @@ Qcommon_Frame(int msec)
 		c_pointcontents = 0;
 	}
 
+	// gl_maxfps > 1000 breaks things, and so does <= 0
+	// so cap to 1000 and treat <= 0 as "as fast as possible", which is 1000
+	if (gl_maxfps->value > 1000 || gl_maxfps->value < 1)
+	{
+		Cvar_SetValue("gl_maxfps", 1000);
+	}
+
+	if(cl_maxfps->value > 250)
+	{
+		Cvar_SetValue("cl_maxfps", 130);
+	}
+	else if(cl_maxfps->value < 1)
+	{
+		Cvar_SetValue("cl_maxfps", 60);
+	}
+
 
 	// Save global time for network- und input code.
 	curtime = Sys_Milliseconds();
diff --git a/src/common/header/common.h b/src/common/header/common.h
index 2f8afe2..2d6c598 100644
--- a/src/common/header/common.h
+++ b/src/common/header/common.h
@@ -32,7 +32,7 @@
 #include "shared.h"
 #include "crc.h"
 
-#define YQ2VERSION "7.02"
+#define YQ2VERSION "7.10"
 #define BASEDIRNAME "baseq2"
 
 #ifndef YQ2OSTYPE
diff --git a/src/common/header/shared.h b/src/common/header/shared.h
index b6813c0..d12ab68 100644
--- a/src/common/header/shared.h
+++ b/src/common/header/shared.h
@@ -457,8 +457,8 @@ typedef struct cmodel_s
 typedef struct csurface_s
 {
 	char name[16];
-	int flags;
-	int value;
+	int flags; /* SURF_* */
+	int value; /* unused */
 } csurface_t;
 
 typedef struct mapsurface_s  /* used internally due to name len probs */

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-games/yquake2.git



More information about the Pkg-games-commits mailing list