From f974062fbc8c2a2f086f036c2e8664517434b7e1 Mon Sep 17 00:00:00 2001
From: Mario <mario.mario@y7mail.com>
Date: Mon, 9 Jun 2014 09:57:41 +1000
Subject: [PATCH] Client-selectable physics configs

---
 commands.cfg                  |   1 +
 defaultXonotic.cfg            |   1 +
 physics.cfg                   | 228 ++++++++++++++++++++++++++++++++++
 qcsrc/common/constants.qh     |  21 ++++
 qcsrc/server/autocvars.qh     |  23 +---
 qcsrc/server/cl_physics.qc    | 124 ++++++++++++------
 qcsrc/server/command/cmd.qc   |  41 ++++++
 qcsrc/server/defs.qh          |  23 ++++
 qcsrc/server/g_world.qc       |  21 ++++
 qcsrc/server/miscfunctions.qc |   1 +
 10 files changed, 423 insertions(+), 61 deletions(-)
 create mode 100644 physics.cfg

diff --git a/commands.cfg b/commands.cfg
index 20f957029a..812e572b27 100644
--- a/commands.cfg
+++ b/commands.cfg
@@ -160,6 +160,7 @@ alias sentcvar             "qc_cmd_cmd    sentcvar             ${* ?}" // New sy
 alias mobedit              "qc_cmd_cmd    mobedit              ${* ?}" // Edit a monster's properties
 alias mobkill              "qc_cmd_cmd    mobkill              ${* ?}" // Kill a monster
 alias mobspawn             "qc_cmd_cmd    mobspawn             ${* ?}" // Spawn a monster infront of the player
+alias physics              "qc_cmd_cmd    physics              ${* ?}" // Change physics set
 alias spectate             "qc_cmd_cmd    spectate             ${* ?}" // Become an observer
 alias suggestmap           "qc_cmd_cmd    suggestmap           ${* ?}" // Suggest a map to the mapvote at match end
 //alias tell               "qc_cmd_cmd    tell                 ${* ?}" // Send a message directly to a player
diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg
index 2c048b39ea..09cc6119ce 100644
--- a/defaultXonotic.cfg
+++ b/defaultXonotic.cfg
@@ -1553,6 +1553,7 @@ exec gamemodes.cfg
 exec mutators.cfg
 exec notifications.cfg
 exec monsters.cfg
+exec physics.cfg
 
 // load console command aliases and settings
 exec commands.cfg
diff --git a/physics.cfg b/physics.cfg
new file mode 100644
index 0000000000..0c442ad950
--- /dev/null
+++ b/physics.cfg
@@ -0,0 +1,228 @@
+// ==================================================
+//  Main configuration for client selectable physics
+// ==================================================
+
+
+// ==============
+//  main options
+// ==============
+seta cl_physics default "client selected physics set"
+
+set g_physics_clientselect 0 "allow clients to select their physics set"
+set g_physics_clientselect_options "xonotic nexuiz quake warsow defrag quake3 vecxis quake2"
+
+// =========
+//  Xonotic
+// =========
+set g_physics_xonotic_airaccel_qw -0.8
+set g_physics_xonotic_airstrafeaccel_qw -0.95
+set g_physics_xonotic_airspeedlimit_nonqw 900
+set g_physics_xonotic_maxspeed 360
+set g_physics_xonotic_jumpvelocity 248
+set g_physics_xonotic_maxairstrafespeed 100
+set g_physics_xonotic_maxairspeed 360
+set g_physics_xonotic_airstrafeaccelerate 18
+set g_physics_xonotic_warsowbunny_turnaccel 0
+set g_physics_xonotic_airaccel_qw_stretchfactor 2
+set g_physics_xonotic_airaccel_sideways_friction 0
+set g_physics_xonotic_aircontrol 100
+set g_physics_xonotic_aircontrol_power 2
+set g_physics_xonotic_aircontrol_penalty 0
+set g_physics_xonotic_warsowbunny_airforwardaccel 1.00001
+set g_physics_xonotic_warsowbunny_topspeed 925
+set g_physics_xonotic_warsowbunny_accel 0.1593
+set g_physics_xonotic_warsowbunny_backtosideratio 0.8
+set g_physics_xonotic_friction 6
+set g_physics_xonotic_accelerate 15
+set g_physics_xonotic_stopspeed 100
+set g_physics_xonotic_airaccelerate 2
+set g_physics_xonotic_airstopaccelerate 3
+
+// ========
+//  Nexuiz
+// ========
+set g_physics_nexuiz_airaccel_qw 0.95
+set g_physics_nexuiz_airstrafeaccel_qw 0
+set g_physics_nexuiz_airspeedlimit_nonqw 0
+set g_physics_nexuiz_maxspeed 400
+set g_physics_nexuiz_jumpvelocity 300 "333 to match xonotic physics"
+set g_physics_nexuiz_maxairstrafespeed 0
+set g_physics_nexuiz_maxairspeed 220
+set g_physics_nexuiz_airstrafeaccelerate 0
+set g_physics_nexuiz_warsowbunny_turnaccel 0
+set g_physics_nexuiz_airaccel_qw_stretchfactor 0
+set g_physics_nexuiz_airaccel_sideways_friction 0.35
+set g_physics_nexuiz_aircontrol 0
+set g_physics_nexuiz_aircontrol_power 2
+set g_physics_nexuiz_aircontrol_penalty 0
+set g_physics_nexuiz_warsowbunny_airforwardaccel 1.00001
+set g_physics_nexuiz_warsowbunny_topspeed 925
+set g_physics_nexuiz_warsowbunny_accel 0.1593
+set g_physics_nexuiz_warsowbunny_backtosideratio 0.8
+set g_physics_nexuiz_friction 7
+set g_physics_nexuiz_accelerate 8
+set g_physics_nexuiz_stopspeed 100
+set g_physics_nexuiz_airaccelerate 5.5
+set g_physics_nexuiz_airstopaccelerate 0
+
+// =======
+//  Quake
+// =======
+set g_physics_quake_airaccel_qw 1
+set g_physics_quake_airstrafeaccel_qw 0
+set g_physics_quake_airspeedlimit_nonqw 0
+set g_physics_quake_maxspeed 320
+set g_physics_quake_jumpvelocity 270
+set g_physics_quake_maxairstrafespeed 0
+set g_physics_quake_maxairspeed 30
+set g_physics_quake_airstrafeaccelerate 0
+set g_physics_quake_warsowbunny_turnaccel 0
+set g_physics_quake_airaccel_qw_stretchfactor 0
+set g_physics_quake_airaccel_sideways_friction 0
+set g_physics_quake_aircontrol 0
+set g_physics_quake_aircontrol_power 2
+set g_physics_quake_aircontrol_penalty 0
+set g_physics_quake_warsowbunny_airforwardaccel 1.00001
+set g_physics_quake_warsowbunny_topspeed 925
+set g_physics_quake_warsowbunny_accel 0.1593
+set g_physics_quake_warsowbunny_backtosideratio 0.8
+set g_physics_quake_friction 4
+set g_physics_quake_accelerate 10
+set g_physics_quake_stopspeed 100
+set g_physics_quake_airaccelerate 106.66666666666666666666
+set g_physics_quake_airstopaccelerate 0
+
+// ========
+//  Warsow
+// ========
+set g_physics_warsow_airaccel_qw 1
+set g_physics_warsow_airstrafeaccel_qw 0
+set g_physics_warsow_airspeedlimit_nonqw 0
+set g_physics_warsow_maxspeed 320
+set g_physics_warsow_jumpvelocity 280
+set g_physics_warsow_maxairstrafespeed 30
+set g_physics_warsow_maxairspeed 320
+set g_physics_warsow_airstrafeaccelerate 70
+set g_physics_warsow_warsowbunny_turnaccel 9
+set g_physics_warsow_airaccel_qw_stretchfactor 0
+set g_physics_warsow_airaccel_sideways_friction 0
+set g_physics_warsow_aircontrol 0
+set g_physics_warsow_aircontrol_power 2
+set g_physics_warsow_aircontrol_penalty 0
+set g_physics_warsow_warsowbunny_airforwardaccel 1.00001
+set g_physics_warsow_warsowbunny_topspeed 925
+set g_physics_warsow_warsowbunny_accel 0.1593
+set g_physics_warsow_warsowbunny_backtosideratio 0.8
+set g_physics_warsow_friction 8
+set g_physics_warsow_accelerate 15
+set g_physics_warsow_stopspeed 100
+set g_physics_warsow_airaccelerate 1
+set g_physics_warsow_airstopaccelerate 2.5
+
+// ========
+//  DeFrag
+// ========
+set g_physics_defrag_airaccel_qw 0.95
+set g_physics_defrag_airstrafeaccel_qw 1
+set g_physics_defrag_airspeedlimit_nonqw 0
+set g_physics_defrag_maxspeed 320
+set g_physics_defrag_jumpvelocity 270
+set g_physics_defrag_maxairstrafespeed 30
+set g_physics_defrag_maxairspeed 320
+set g_physics_defrag_airstrafeaccelerate 70
+set g_physics_defrag_warsowbunny_turnaccel 0
+set g_physics_defrag_airaccel_qw_stretchfactor 0
+set g_physics_defrag_airaccel_sideways_friction 0
+set g_physics_defrag_aircontrol 150
+set g_physics_defrag_aircontrol_power 2
+set g_physics_defrag_aircontrol_penalty 0
+set g_physics_defrag_warsowbunny_airforwardaccel 1.00001
+set g_physics_defrag_warsowbunny_topspeed 925
+set g_physics_defrag_warsowbunny_accel 0.1593
+set g_physics_defrag_warsowbunny_backtosideratio 0.8
+set g_physics_defrag_friction 5.8
+set g_physics_defrag_accelerate 15
+set g_physics_defrag_stopspeed 100
+set g_physics_defrag_airaccelerate 1
+set g_physics_defrag_airstopaccelerate 2.5
+
+// =========
+//  Quake 3
+// =========
+set g_physics_quake3_airaccel_qw 1
+set g_physics_quake3_airstrafeaccel_qw 0
+set g_physics_quake3_airspeedlimit_nonqw 0
+set g_physics_quake3_maxspeed 320
+set g_physics_quake3_jumpvelocity 270
+set g_physics_quake3_maxairstrafespeed 0
+set g_physics_quake3_maxairspeed 320
+set g_physics_quake3_airstrafeaccelerate 0
+set g_physics_quake3_warsowbunny_turnaccel 0
+set g_physics_quake3_airaccel_qw_stretchfactor 0
+set g_physics_quake3_airaccel_sideways_friction 0
+set g_physics_quake3_aircontrol 0
+set g_physics_quake3_aircontrol_power 2
+set g_physics_quake3_aircontrol_penalty 0
+set g_physics_quake3_warsowbunny_airforwardaccel 1.00001
+set g_physics_quake3_warsowbunny_topspeed 925
+set g_physics_quake3_warsowbunny_accel 0.1593
+set g_physics_quake3_warsowbunny_backtosideratio 0.8
+set g_physics_quake3_friction 6
+set g_physics_quake3_accelerate 10
+set g_physics_quake3_stopspeed 100
+set g_physics_quake3_airaccelerate 1
+set g_physics_quake3_airstopaccelerate 0
+
+// ========
+//  Vecxis
+// ========
+set g_physics_vecxis_airaccel_qw 0.93
+set g_physics_vecxis_airstrafeaccel_qw 0
+set g_physics_vecxis_airspeedlimit_nonqw 0
+set g_physics_vecxis_maxspeed 400
+set g_physics_vecxis_jumpvelocity 300 "333 to match xonotic physics"
+set g_physics_vecxis_maxairstrafespeed 0
+set g_physics_vecxis_maxairspeed 220
+set g_physics_vecxis_airstrafeaccelerate 0
+set g_physics_vecxis_warsowbunny_turnaccel 0
+set g_physics_vecxis_airaccel_qw_stretchfactor 0
+set g_physics_vecxis_airaccel_sideways_friction 0.3
+set g_physics_vecxis_aircontrol 0
+set g_physics_vecxis_aircontrol_power 2
+set g_physics_vecxis_aircontrol_penalty 0
+set g_physics_vecxis_warsowbunny_airforwardaccel 1.00001
+set g_physics_vecxis_warsowbunny_topspeed 925
+set g_physics_vecxis_warsowbunny_accel 0.1593
+set g_physics_vecxis_warsowbunny_backtosideratio 0.8
+set g_physics_vecxis_friction 5
+set g_physics_vecxis_accelerate 5.5
+set g_physics_vecxis_stopspeed 100
+set g_physics_vecxis_airaccelerate 5.5
+set g_physics_vecxis_airstopaccelerate 0
+
+// =========
+//  Quake 2
+// =========
+set g_physics_quake2_airaccel_qw 1
+set g_physics_quake2_airstrafeaccel_qw 0
+set g_physics_quake2_airspeedlimit_nonqw 0
+set g_physics_quake2_maxspeed 300
+set g_physics_quake2_jumpvelocity 270
+set g_physics_quake2_maxairstrafespeed 0
+set g_physics_quake2_maxairspeed 300
+set g_physics_quake2_airstrafeaccelerate 0
+set g_physics_quake2_warsowbunny_turnaccel 0
+set g_physics_quake2_airaccel_qw_stretchfactor 0
+set g_physics_quake2_airaccel_sideways_friction 0
+set g_physics_quake2_aircontrol 0
+set g_physics_quake2_aircontrol_power 2
+set g_physics_quake2_aircontrol_penalty 0
+set g_physics_quake2_warsowbunny_airforwardaccel 1.00001
+set g_physics_quake2_warsowbunny_topspeed 925
+set g_physics_quake2_warsowbunny_accel 0.1593
+set g_physics_quake2_warsowbunny_backtosideratio 0.8
+set g_physics_quake2_friction 6
+set g_physics_quake2_accelerate 10
+set g_physics_quake2_stopspeed 100
+set g_physics_quake2_airaccelerate 1
+set g_physics_quake2_airstopaccelerate 0
diff --git a/qcsrc/common/constants.qh b/qcsrc/common/constants.qh
index e02fad45f0..d4912ebb8d 100644
--- a/qcsrc/common/constants.qh
+++ b/qcsrc/common/constants.qh
@@ -211,6 +211,27 @@ const float STAT_MOVEVARS_AIRSTRAFEACCEL_QW = 223;
 const float STAT_MOVEVARS_MAXSPEED = 244;
 const float STAT_MOVEVARS_AIRACCEL_QW = 254;
 
+// new properties
+const float STAT_MOVEVARS_JUMPVELOCITY = 250;
+const float STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR = 220;
+const float STAT_MOVEVARS_MAXAIRSTRAFESPEED = 233;
+const float STAT_MOVEVARS_MAXAIRSPEED = 252;
+const float STAT_MOVEVARS_AIRSTRAFEACCELERATE = 232;
+const float STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL = 229;
+const float STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION = 255;
+const float STAT_MOVEVARS_AIRCONTROL = 234;
+const float STAT_MOVEVARS_AIRCONTROL_POWER = 224;
+const float STAT_MOVEVARS_AIRCONTROL_PENALTY = 221;
+const float STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL = 226;
+const float STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED = 228;
+const float STAT_MOVEVARS_WARSOWBUNNY_ACCEL = 227;
+const float STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO = 230;
+const float STAT_MOVEVARS_FRICTION = 238;
+const float STAT_MOVEVARS_ACCELERATE = 246;
+const float STAT_MOVEVARS_STOPSPEED = 243;
+const float STAT_MOVEVARS_AIRACCELERATE = 247;
+const float STAT_MOVEVARS_AIRSTOPACCELERATE = 231;
+
 const float CTF_STATE_ATTACK = 1;
 const float CTF_STATE_DEFEND = 2;
 const float CTF_STATE_COMMANDER = 3;
diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh
index 080d701d86..47765df065 100644
--- a/qcsrc/server/autocvars.qh
+++ b/qcsrc/server/autocvars.qh
@@ -1068,20 +1068,8 @@ float autocvar_skill_auto;
 float autocvar_snd_soundradius;
 float autocvar_spawn_debug;
 float autocvar_speedmeter;
-float autocvar_sv_accelerate;
 var float autocvar_sv_accuracy_data_share = 1;
 string autocvar_sv_adminnick;
-float autocvar_sv_airaccel_qw;
-float autocvar_sv_airaccel_qw_stretchfactor;
-float autocvar_sv_airaccel_sideways_friction;
-float autocvar_sv_airaccelerate;
-float autocvar_sv_aircontrol;
-float autocvar_sv_aircontrol_penalty;
-float autocvar_sv_aircontrol_power;
-float autocvar_sv_airspeedlimit_nonqw;
-float autocvar_sv_airstopaccelerate;
-float autocvar_sv_airstrafeaccel_qw;
-float autocvar_sv_airstrafeaccelerate;
 float autocvar_sv_autoscreenshot;
 float autocvar_sv_cheats;
 float autocvar_sv_clientcommand_antispam_time;
@@ -1114,7 +1102,6 @@ float autocvar_sv_eventlog_files_counter;
 string autocvar_sv_eventlog_files_nameprefix;
 string autocvar_sv_eventlog_files_namesuffix;
 float autocvar_sv_eventlog_files_timestamps;
-float autocvar_sv_friction;
 float autocvar_sv_friction_on_land;
 float autocvar_sv_gameplayfix_q2airaccelerate;
 float autocvar_sv_gentle;
@@ -1130,7 +1117,6 @@ float autocvar_sv_logscores_file;
 string autocvar_sv_logscores_filename;
 float autocvar_sv_mapchange_delay;
 float autocvar_sv_maxairspeed;
-float autocvar_sv_maxairstrafespeed;
 float autocvar_sv_maxspeed;
 string autocvar_sv_motd;
 float autocvar_sv_precacheplayermodels;
@@ -1144,7 +1130,6 @@ float autocvar_sv_spectate;
 float autocvar_sv_spectator_speed_multiplier;
 float autocvar_sv_status_privacy;
 float autocvar_sv_stepheight;
-float autocvar_sv_stopspeed;
 float autocvar_sv_strengthsound_antispam_refire_threshold;
 float autocvar_sv_strengthsound_antispam_time;
 float autocvar_sv_teamnagger;
@@ -1173,11 +1158,6 @@ float autocvar_sv_vote_stop;
 float autocvar_sv_vote_timeout;
 float autocvar_sv_vote_wait;
 float autocvar_sv_vote_gamestart;
-float autocvar_sv_warsowbunny_accel;
-float autocvar_sv_warsowbunny_airforwardaccel;
-float autocvar_sv_warsowbunny_backtosideratio;
-float autocvar_sv_warsowbunny_topspeed;
-float autocvar_sv_warsowbunny_turnaccel;
 float autocvar_sv_waypointsprite_deadlifetime;
 float autocvar_sv_waypointsprite_deployed_lifetime;
 float autocvar_sv_waypointsprite_limitedrange;
@@ -1283,3 +1263,6 @@ float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay;
 float autocvar_g_spawn_near_teammate_ignore_spawnpoint_delay_death;
 float autocvar_g_spawn_near_teammate_ignore_spawnpoint_check_health;
 float autocvar_g_spawn_near_teammate_ignore_spawnpoint_closetodeath;
+float autocvar_g_physics_clientselect;
+string autocvar_g_physics_clientselect_options;
+
diff --git a/qcsrc/server/cl_physics.qc b/qcsrc/server/cl_physics.qc
index 43c7be5179..91f91e89e1 100644
--- a/qcsrc/server/cl_physics.qc
+++ b/qcsrc/server/cl_physics.qc
@@ -10,6 +10,27 @@
 .float wasFlying;
 .float spectatorspeed;
 
+// client side physics
+float Physics_Valid(string thecvar)
+{
+	if(!autocvar_g_physics_clientselect) { return FALSE; }
+
+	string l = strcat(" ", autocvar_g_physics_clientselect_options, " ");
+
+	if(strstrofs(l, strcat(" ", thecvar, " "), 0) >= 0)
+		return TRUE;
+
+	return FALSE;
+}
+
+float Physics_ClientOption(entity pl, string option)
+{
+	if(Physics_Valid(pl.cvar_cl_physics) && (cvar_type(sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option)) & 1))
+		return cvar(sprintf("g_physics_%s_%s", pl.cvar_cl_physics, option));
+	else
+		return cvar(strcat("sv_", option));
+}
+
 /*
 =============
 PlayerJump
@@ -47,7 +68,7 @@ void PlayerJump (void)
 		}
 	}
 
-	mjumpheight = autocvar_sv_jumpvelocity;
+	mjumpheight = self.stat_sv_jumpvelocity;
 	if (self.waterlevel >= WATERLEVEL_SWIMMING)
 	{
 		self.velocity_z = self.stat_sv_maxspeed * 0.7;
@@ -431,7 +452,7 @@ void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
 		return;
 #endif
 
-	k *= bound(0, wishspeed / autocvar_sv_maxairspeed, 1);
+	k *= bound(0, wishspeed / self.stat_sv_maxairspeed, 1);
 
 	zspeed = self.velocity_z;
 	self.velocity_z = 0;
@@ -441,9 +462,9 @@ void CPM_PM_Aircontrol(vector wishdir, float wishspeed)
 
 	if(dot > 0) // we can't change direction while slowing down
 	{
-		k *= pow(dot, autocvar_sv_aircontrol_power)*frametime;
-		xyspeed = max(0, xyspeed - autocvar_sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
-		k *= autocvar_sv_aircontrol;
+		k *= pow(dot, self.stat_sv_aircontrol_power)*frametime;
+		xyspeed = max(0, xyspeed - self.stat_sv_aircontrol_penalty * sqrt(max(0, 1 - dot*dot)) * k/32);
+		k *= self.stat_sv_aircontrol;
 		self.velocity = normalize(self.velocity * xyspeed + wishdir * k);
 	}
 
@@ -553,26 +574,26 @@ void PM_AirAccelerate(vector wishdir, float wishspeed)
 
 	if(wishspeed > curspeed * 1.01)
 	{
-		wishspeed = min(wishspeed, curspeed + autocvar_sv_warsowbunny_airforwardaccel * self.stat_sv_maxspeed * frametime);
+		wishspeed = min(wishspeed, curspeed + self.stat_sv_warsowbunny_airforwardaccel * self.stat_sv_maxspeed * frametime);
 	}
 	else
 	{
-		f = max(0, (autocvar_sv_warsowbunny_topspeed - curspeed) / (autocvar_sv_warsowbunny_topspeed - self.stat_sv_maxspeed));
-		wishspeed = max(curspeed, self.stat_sv_maxspeed) + autocvar_sv_warsowbunny_accel * f * self.stat_sv_maxspeed * frametime;
+		f = max(0, (self.stat_sv_warsowbunny_topspeed - curspeed) / (self.stat_sv_warsowbunny_topspeed - self.stat_sv_maxspeed));
+		wishspeed = max(curspeed, self.stat_sv_maxspeed) + self.stat_sv_warsowbunny_accel * f * self.stat_sv_maxspeed * frametime;
 	}
 	wishvel = wishdir * wishspeed;
 	acceldir = wishvel - curvel;
 	addspeed = vlen(acceldir);
 	acceldir = normalize(acceldir);
 
-	accelspeed = min(addspeed, autocvar_sv_warsowbunny_turnaccel * self.stat_sv_maxspeed * frametime);
+	accelspeed = min(addspeed, self.stat_sv_warsowbunny_turnaccel * self.stat_sv_maxspeed * frametime);
 
-	if(autocvar_sv_warsowbunny_backtosideratio < 1)
+	if(self.stat_sv_warsowbunny_backtosideratio < 1)
 	{
 		curdir = normalize(curvel);
 		dot = acceldir * curdir;
 		if(dot < 0)
-			acceldir = acceldir - (1 - autocvar_sv_warsowbunny_backtosideratio) * dot * curdir;
+			acceldir = acceldir - (1 - self.stat_sv_warsowbunny_backtosideratio) * dot * curdir;
 	}
 
 	self.velocity += accelspeed * acceldir;
@@ -646,13 +667,34 @@ void SV_PlayerPhysics()
 
 	// fix physics stats for g_movement_highspeed
 	// TODO maybe rather use maxairspeed? needs testing
-	self.stat_sv_airaccel_qw = AdjustAirAccelQW(autocvar_sv_airaccel_qw, maxspd_mod);
-	if(autocvar_sv_airstrafeaccel_qw)
-		self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(autocvar_sv_airstrafeaccel_qw, maxspd_mod);
+	self.stat_sv_airaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airaccel_qw"), maxspd_mod);
+	if(Physics_ClientOption(self, "airstrafeaccel_qw"))
+		self.stat_sv_airstrafeaccel_qw = AdjustAirAccelQW(Physics_ClientOption(self, "airstrafeaccel_qw"), maxspd_mod);
 	else
 		self.stat_sv_airstrafeaccel_qw = 0;
-	self.stat_sv_airspeedlimit_nonqw = autocvar_sv_airspeedlimit_nonqw * maxspd_mod;
-	self.stat_sv_maxspeed = autocvar_sv_maxspeed * maxspd_mod; // also slow walking
+	self.stat_sv_airspeedlimit_nonqw = Physics_ClientOption(self, "airspeedlimit_nonqw") * maxspd_mod;
+	self.stat_sv_maxspeed = Physics_ClientOption(self, "maxspeed") * maxspd_mod; // also slow walking
+	
+	// fix some new settings
+	self.stat_sv_airaccel_qw_stretchfactor = Physics_ClientOption(self, "airaccel_qw_stretchfactor");
+	self.stat_sv_maxairstrafespeed = Physics_ClientOption(self, "maxairstrafespeed");
+	self.stat_sv_maxairspeed = Physics_ClientOption(self, "maxairspeed");
+	self.stat_sv_airstrafeaccelerate = Physics_ClientOption(self, "airstrafeaccelerate");
+	self.stat_sv_warsowbunny_turnaccel = Physics_ClientOption(self, "warsowbunny_turnaccel");
+	self.stat_sv_airaccel_sideways_friction = Physics_ClientOption(self, "airaccel_sideways_friction");
+	self.stat_sv_aircontrol = Physics_ClientOption(self, "aircontrol");
+	self.stat_sv_aircontrol_power = Physics_ClientOption(self, "aircontrol_power");
+	self.stat_sv_aircontrol_penalty = Physics_ClientOption(self, "aircontrol_penalty");
+	self.stat_sv_warsowbunny_airforwardaccel = Physics_ClientOption(self, "warsowbunny_airforwardaccel");
+	self.stat_sv_warsowbunny_topspeed = Physics_ClientOption(self, "warsowbunny_topspeed");
+	self.stat_sv_warsowbunny_accel = Physics_ClientOption(self, "warsowbunny_accel");
+	self.stat_sv_warsowbunny_backtosideratio = Physics_ClientOption(self, "warsowbunny_backtosideratio");
+	self.stat_sv_friction = Physics_ClientOption(self, "friction");
+	self.stat_sv_accelerate = Physics_ClientOption(self, "accelerate");
+	self.stat_sv_stopspeed = Physics_ClientOption(self, "stopspeed");
+	self.stat_sv_airaccelerate = Physics_ClientOption(self, "airaccelerate");
+	self.stat_sv_airstopaccelerate = Physics_ClientOption(self, "airstopaccelerate");
+	self.stat_sv_jumpvelocity = Physics_ClientOption(self, "jumpvelocity");
 
     if(self.PlayerPhysplug)
         if(self.PlayerPhysplug())
@@ -834,7 +876,7 @@ void SV_PlayerPhysics()
 		maxspd_mod = self.spectatorspeed;
 	}
 
-	spd = max(self.stat_sv_maxspeed, autocvar_sv_maxairspeed) * maxspd_mod * swampspd_mod;
+	spd = max(self.stat_sv_maxspeed, self.stat_sv_maxairspeed) * maxspd_mod * swampspd_mod;
 	if(self.speed != spd)
 	{
 		self.speed = spd;
@@ -907,7 +949,7 @@ void SV_PlayerPhysics()
 		// noclipping or flying
 		self.flags &= ~FL_ONGROUND;
 
-		self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction);
+		self.velocity = self.velocity * (1 - frametime * self.stat_sv_friction);
 		makevectors(self.v_angle);
 		//wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
 		wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
@@ -917,7 +959,7 @@ void SV_PlayerPhysics()
 		if (wishspeed > self.stat_sv_maxspeed*maxspd_mod)
 			wishspeed = self.stat_sv_maxspeed*maxspd_mod;
 		if (time >= self.teleport_time)
-			PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
+			PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
 	}
 	else if (self.waterlevel >= WATERLEVEL_SWIMMING)
 	{
@@ -937,10 +979,10 @@ void SV_PlayerPhysics()
 		wishspeed = wishspeed * 0.7;
 
 		// water friction
-		self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction);
+		self.velocity = self.velocity * (1 - frametime * self.stat_sv_friction);
 
 		// water acceleration
-		PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
+		PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
 	}
 	else if (time < self.ladder_time)
 	{
@@ -957,7 +999,7 @@ void SV_PlayerPhysics()
 			self.velocity_z += g;
 		}
 
-		self.velocity = self.velocity * (1 - frametime * autocvar_sv_friction);
+		self.velocity = self.velocity * (1 - frametime * self.stat_sv_friction);
 		makevectors(self.v_angle);
 		//wishvel = v_forward * self.movement_x + v_right * self.movement_y + v_up * self.movement_z;
 		wishvel = v_forward * self.movement_x + v_right * self.movement_y + '0 0 1' * self.movement_z;
@@ -990,7 +1032,7 @@ void SV_PlayerPhysics()
 		if (time >= self.teleport_time)
 		{
 			// water acceleration
-			PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
+			PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
 		}
 	}
 	else if ((self.items & IT_JETPACK) && self.BUTTON_HOOK && (!autocvar_g_jetpack_fuel || self.ammo_fuel >= 0.01 || self.items & IT_UNLIMITED_WEAPON_AMMO) && !self.freezetag_frozen)
@@ -999,7 +1041,7 @@ void SV_PlayerPhysics()
 		makevectors(self.v_angle);
 		wishvel = v_forward * self.movement_x + v_right * self.movement_y;
 		// add remaining speed as Z component
-		maxairspd = autocvar_sv_maxairspeed*max(1, maxspd_mod);
+		maxairspd = self.stat_sv_maxairspeed*max(1, maxspd_mod);
 		// fix speedhacks :P
 		wishvel = normalize(wishvel) * min(vlen(wishvel) / maxairspd, 1);
 		// add the unused velocity as up component
@@ -1132,10 +1174,10 @@ void SV_PlayerPhysics()
 		f = vlen(v);
 		if(f > 0)
 		{
-			if (f < autocvar_sv_stopspeed)
-				f = 1 - frametime * (autocvar_sv_stopspeed / f) * autocvar_sv_friction;
+			if (f < self.stat_sv_stopspeed)
+				f = 1 - frametime * (self.stat_sv_stopspeed / f) * self.stat_sv_friction;
 			else
-				f = 1 - frametime * autocvar_sv_friction;
+				f = 1 - frametime * self.stat_sv_friction;
 			if (f > 0)
 				self.velocity = self.velocity * f;
 			else
@@ -1172,7 +1214,7 @@ void SV_PlayerPhysics()
 		if (self.crouch)
 			wishspeed = wishspeed * 0.5;
 		if (time >= self.teleport_time)
-			PM_Accelerate(wishdir, wishspeed, wishspeed, autocvar_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
+			PM_Accelerate(wishdir, wishspeed, wishspeed, self.stat_sv_accelerate*maxspd_mod, 1, 0, 0, 0);
 	}
 	else
 	{
@@ -1183,13 +1225,13 @@ void SV_PlayerPhysics()
 
 		if(maxspd_mod < 1)
 		{
-			maxairspd = autocvar_sv_maxairspeed*maxspd_mod;
-			airaccel = autocvar_sv_airaccelerate*maxspd_mod;
+			maxairspd = self.stat_sv_maxairspeed*maxspd_mod;
+			airaccel = self.stat_sv_airaccelerate*maxspd_mod;
 		}
 		else
 		{
-			maxairspd = autocvar_sv_maxairspeed;
-			airaccel = autocvar_sv_airaccelerate;
+			maxairspd = self.stat_sv_maxairspeed;
+			airaccel = self.stat_sv_airaccelerate;
 		}
 		// airborn
 		makevectors(self.v_angle_y * '0 1 0');
@@ -1215,13 +1257,13 @@ void SV_PlayerPhysics()
 			wishspeed2 = wishspeed;
 
 			// CPM
-			if(autocvar_sv_airstopaccelerate)
+			if(self.stat_sv_airstopaccelerate)
 			{
 				vector curdir;
 				curdir = self.velocity;
 				curdir_z = 0;
 				curdir = normalize(curdir);
-				airaccel = airaccel + (autocvar_sv_airstopaccelerate*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
+				airaccel = airaccel + (self.stat_sv_airstopaccelerate*maxspd_mod - airaccel) * max(0, -(curdir * wishdir));
 			}
 			// note that for straight forward jumping:
 			// step = accel * frametime * wishspeed0;
@@ -1232,20 +1274,20 @@ void SV_PlayerPhysics()
 			// log dv/dt = logaccel + logmaxspeed (when slow)
 			// log dv/dt = logaccel + logmaxspeed + log(1 - accelqw) (when fast)
 			strafity = IsMoveInDirection(self.movement, -90) + IsMoveInDirection(self.movement, +90); // if one is nonzero, other is always zero
-			if(autocvar_sv_maxairstrafespeed)
-				wishspeed = min(wishspeed, GeomLerp(autocvar_sv_maxairspeed*maxspd_mod, strafity, autocvar_sv_maxairstrafespeed*maxspd_mod));
-			if(autocvar_sv_airstrafeaccelerate)
-				airaccel = GeomLerp(airaccel, strafity, autocvar_sv_airstrafeaccelerate*maxspd_mod);
+			if(self.stat_sv_maxairstrafespeed)
+				wishspeed = min(wishspeed, GeomLerp(self.stat_sv_maxairspeed*maxspd_mod, strafity, self.stat_sv_maxairstrafespeed*maxspd_mod));
+			if(self.stat_sv_airstrafeaccelerate)
+				airaccel = GeomLerp(airaccel, strafity, self.stat_sv_airstrafeaccelerate*maxspd_mod);
 			if(self.stat_sv_airstrafeaccel_qw)
 				airaccelqw = copysign(1-GeomLerp(1-fabs(self.stat_sv_airaccel_qw), strafity, 1-fabs(self.stat_sv_airstrafeaccel_qw)), ((strafity > 0.5) ? self.stat_sv_airstrafeaccel_qw : self.stat_sv_airaccel_qw));
 			// !CPM
 
-			if(autocvar_sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0)
+			if(self.stat_sv_warsowbunny_turnaccel && accelerating && self.movement_y == 0 && self.movement_x != 0)
 				PM_AirAccelerate(wishdir, wishspeed);
 			else
-				PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, autocvar_sv_airaccel_qw_stretchfactor, autocvar_sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw);
+				PM_Accelerate(wishdir, wishspeed, wishspeed0, airaccel, airaccelqw, self.stat_sv_airaccel_qw_stretchfactor, self.stat_sv_airaccel_sideways_friction / maxairspd, self.stat_sv_airspeedlimit_nonqw);
 
-			if(autocvar_sv_aircontrol)
+			if(self.stat_sv_aircontrol)
 				CPM_PM_Aircontrol(wishdir, wishspeed2);
 		}
 	}
diff --git a/qcsrc/server/command/cmd.qc b/qcsrc/server/command/cmd.qc
index 78424484aa..1321f26b3a 100644
--- a/qcsrc/server/command/cmd.qc
+++ b/qcsrc/server/command/cmd.qc
@@ -339,6 +339,46 @@ void ClientCommand_mobspawn(float request, float argc)
 	}
 }
 
+void ClientCommand_physics(float request, float argc)
+{
+	switch(request)
+	{
+		case CMD_REQUEST_COMMAND:
+		{
+			string command = strtolower(argv(1));
+			
+			if(!autocvar_g_physics_clientselect)
+			{
+				sprint(self, "Client physics selection is currently disabled.\n");
+				return;
+			}
+
+			if(command == "list" || command == "help")
+			{
+				sprint(self, strcat("Available physics sets: \n\n", autocvar_g_physics_clientselect_options, " default\n"));
+				return;
+			}
+			
+			if(Physics_Valid(command) || command == "default")
+			{
+				stuffcmd(self, strcat("\nseta cl_physics ", command, "\nsendcvar cl_physics\n"));
+				sprint(self, strcat("^2Physics set successfully changed to ^3", command, "\n"));
+				return;
+			}
+		}
+	
+		default:
+			sprint(self, strcat("Current physics set: ^3", self.cvar_cl_physics, "\n"));
+		case CMD_REQUEST_USAGE:
+		{
+			sprint(self, "\nUsage:^3 cmd physics <physics>\n");
+			sprint(self, "  See 'cmd physics list' for available physics sets.\n");
+			sprint(self, "  Argument 'default' resets to standard physics.\n");
+			return;
+		}
+	}
+}
+
 void ClientCommand_ready(float request) // todo: anti-spam for toggling readyness
 {
 	switch(request)
@@ -735,6 +775,7 @@ void ClientCommand_(float request)
 	CLIENT_COMMAND("mobedit", ClientCommand_mobedit(request, arguments), "Edit your monster's properties") \
 	CLIENT_COMMAND("mobkill", ClientCommand_mobkill(request), "Kills your monster") \
 	CLIENT_COMMAND("mobspawn", ClientCommand_mobspawn(request, arguments), "Spawn monsters infront of yourself") \
+	CLIENT_COMMAND("physics", ClientCommand_physics(request, arguments), "Change physics set") \
 	CLIENT_COMMAND("ready", ClientCommand_ready(request), "Qualify as ready to end warmup stage (or restart server if allowed)") \
 	CLIENT_COMMAND("say", ClientCommand_say(request, arguments, command), "Print a message to chat to all players") \
 	CLIENT_COMMAND("say_team", ClientCommand_say_team(request, arguments, command), "Print a message to chat to all team mates") \
diff --git a/qcsrc/server/defs.qh b/qcsrc/server/defs.qh
index c32ef7716f..bca3823962 100644
--- a/qcsrc/server/defs.qh
+++ b/qcsrc/server/defs.qh
@@ -454,6 +454,29 @@ float round_starttime; //point in time when the countdown to round start is over
 .float stat_sv_airspeedlimit_nonqw;
 .float stat_sv_maxspeed;
 
+// new properties
+.float stat_sv_jumpvelocity;
+.float stat_sv_airaccel_qw_stretchfactor;
+.float stat_sv_maxairstrafespeed;
+.float stat_sv_maxairspeed;
+.float stat_sv_airstrafeaccelerate;
+.float stat_sv_warsowbunny_turnaccel;
+.float stat_sv_airaccel_sideways_friction;
+.float stat_sv_aircontrol;
+.float stat_sv_aircontrol_power;
+.float stat_sv_aircontrol_penalty;
+.float stat_sv_warsowbunny_airforwardaccel;
+.float stat_sv_warsowbunny_topspeed;
+.float stat_sv_warsowbunny_accel;
+.float stat_sv_warsowbunny_backtosideratio;
+.float stat_sv_friction;
+.float stat_sv_accelerate;
+.float stat_sv_stopspeed;
+.float stat_sv_airaccelerate;
+.float stat_sv_airstopaccelerate;
+
+.string cvar_cl_physics;
+
 void W_Porto_Remove (entity p);
 
 .float projectiledeathtype;
diff --git a/qcsrc/server/g_world.qc b/qcsrc/server/g_world.qc
index f635bff2a2..02c1c8f758 100644
--- a/qcsrc/server/g_world.qc
+++ b/qcsrc/server/g_world.qc
@@ -800,6 +800,27 @@ void spawnfunc_worldspawn (void)
 	addstat(STAT_MOVEVARS_AIRACCEL_QW, AS_FLOAT, stat_sv_airaccel_qw);
 	addstat(STAT_MOVEVARS_AIRSTRAFEACCEL_QW, AS_FLOAT, stat_sv_airstrafeaccel_qw);
 
+	// new properties
+	addstat(STAT_MOVEVARS_JUMPVELOCITY, AS_FLOAT, stat_sv_jumpvelocity);
+	addstat(STAT_MOVEVARS_AIRACCEL_QW_STRETCHFACTOR, AS_FLOAT, stat_sv_airaccel_qw_stretchfactor);
+	addstat(STAT_MOVEVARS_MAXAIRSTRAFESPEED, AS_FLOAT, stat_sv_maxairstrafespeed);
+	addstat(STAT_MOVEVARS_MAXAIRSPEED, AS_FLOAT, stat_sv_maxairspeed);
+	addstat(STAT_MOVEVARS_AIRSTRAFEACCELERATE, AS_FLOAT, stat_sv_airstrafeaccelerate);
+	addstat(STAT_MOVEVARS_WARSOWBUNNY_TURNACCEL, AS_FLOAT, stat_sv_warsowbunny_turnaccel);
+	addstat(STAT_MOVEVARS_AIRACCEL_SIDEWAYS_FRICTION, AS_FLOAT, stat_sv_airaccel_sideways_friction);
+	addstat(STAT_MOVEVARS_AIRCONTROL, AS_FLOAT, stat_sv_aircontrol);
+	addstat(STAT_MOVEVARS_AIRCONTROL_POWER, AS_FLOAT, stat_sv_aircontrol_power);
+	addstat(STAT_MOVEVARS_AIRCONTROL_PENALTY, AS_FLOAT, stat_sv_aircontrol_penalty);
+	addstat(STAT_MOVEVARS_WARSOWBUNNY_AIRFORWARDACCEL, AS_FLOAT, stat_sv_warsowbunny_airforwardaccel);
+	addstat(STAT_MOVEVARS_WARSOWBUNNY_TOPSPEED, AS_FLOAT, stat_sv_warsowbunny_topspeed);
+	addstat(STAT_MOVEVARS_WARSOWBUNNY_ACCEL, AS_FLOAT, stat_sv_warsowbunny_accel);
+	addstat(STAT_MOVEVARS_WARSOWBUNNY_BACKTOSIDERATIO, AS_FLOAT, stat_sv_warsowbunny_backtosideratio);
+	addstat(STAT_MOVEVARS_FRICTION, AS_FLOAT, stat_sv_friction);
+	addstat(STAT_MOVEVARS_ACCELERATE, AS_FLOAT, stat_sv_accelerate);
+	addstat(STAT_MOVEVARS_STOPSPEED, AS_FLOAT, stat_sv_stopspeed);
+	addstat(STAT_MOVEVARS_AIRACCELERATE, AS_FLOAT, stat_sv_airaccelerate);
+	addstat(STAT_MOVEVARS_AIRSTOPACCELERATE, AS_FLOAT, stat_sv_airstopaccelerate);
+
 	// secrets
 	addstat(STAT_SECRETS_TOTAL, AS_FLOAT, stat_secrets_total);
 	addstat(STAT_SECRETS_FOUND, AS_FLOAT, stat_secrets_found);
diff --git a/qcsrc/server/miscfunctions.qc b/qcsrc/server/miscfunctions.qc
index 5dc6f2e393..9ee5daff6b 100644
--- a/qcsrc/server/miscfunctions.qc
+++ b/qcsrc/server/miscfunctions.qc
@@ -490,6 +490,7 @@ void GetCvars(float f)
 	GetCvars_handleFloat(s, f, autoswitch, "cl_autoswitch");
 	GetCvars_handleFloat(s, f, cvar_cl_autoscreenshot, "cl_autoscreenshot");
 	GetCvars_handleString(s, f, cvar_g_xonoticversion, "g_xonoticversion");
+	GetCvars_handleString(s, f, cvar_cl_physics, "cl_physics");
 	GetCvars_handleFloat(s, f, cvar_cl_handicap, "cl_handicap");
 	GetCvars_handleFloat(s, f, cvar_cl_clippedspectating, "cl_clippedspectating");
 	GetCvars_handleString_Fixup(s, f, cvar_cl_weaponpriority, "cl_weaponpriority", W_FixWeaponOrder_ForceComplete_AndBuildImpulseList);
-- 
2.39.5