From e9070cc793a0c832bd7316f57b4338d4c481bbb8 Mon Sep 17 00:00:00 2001
From: Rudolf Polzer <divverent@xonotic.org>
Date: Tue, 4 Feb 2014 17:17:34 +0100
Subject: [PATCH] Refactor respawn delay logic in preparation for implementing
 vote #215.

---
 defaultXonotic.cfg        |   5 +-
 gamemodes.cfg             |  92 ++++++++++++++++++++++-----
 qcsrc/server/autocvars.qh |   7 ++-
 qcsrc/server/cl_player.qc | 127 +++++++++++++++++++++++++++++---------
 4 files changed, 183 insertions(+), 48 deletions(-)

diff --git a/defaultXonotic.cfg b/defaultXonotic.cfg
index 6a99a1f88..747e0c419 100644
--- a/defaultXonotic.cfg
+++ b/defaultXonotic.cfg
@@ -470,7 +470,10 @@ set g_spawn_alloweffects 1 "allow clients to enable spawn point and event effect
 set g_spawn_furthest 1 "this amount of the spawns shall be far away from any players"
 set g_spawn_useallspawns 0 "use all spawns, e.g. also team spawns in non-teamplay, and all spawns, even enemy spawns, in teamplay"
 // respawn delay
-set g_respawn_delay 2 "number of seconds you have to wait before you can respawn again"
+set g_respawn_delay_small 2 "small game number of seconds you have to wait before you can respawn again"
+set g_respawn_delay_small_count 0 "Player count per team for g_respawn_delay_small. <=0 values mean the minimum amount of players to have gameplay (typically 2 in FFA, 1 in teamplay)."
+set g_respawn_delay_large 2 "large game number of seconds you have to wait before you can respawn again"
+set g_respawn_delay_large_count 8 "Player count per team for g_respawn_delay_large. <=0 values mean the minimum amount of players to have gameplay (typically 2 in FFA, 1 in teamplay)."
 set g_respawn_delay_max 0 "number of seconds you can wait before you're forced to respawn (only effective with g_forced_respawn 1)"
 set g_respawn_waves 0 "respawn in waves (every n seconds), intended to decrease overwhelming base attacks"
 
diff --git a/gamemodes.cfg b/gamemodes.cfg
index a44b83ad5..9addcbc7d 100644
--- a/gamemodes.cfg
+++ b/gamemodes.cfg
@@ -86,52 +86,112 @@ seta g_invasion_round_limit -1 "Invasion round limit overriding the mapinfo spec
 // =================================
 //  respawn delay/waves/weapon_stay
 // =================================
-// when variables are set to anything other than 0, they take over the global setting...
+// when variables are set to anything other than 0, they take over the global setting. Negative values force an output value of zero.
 // to force disable delay or waves, set them to 0.125
-set g_ctf_respawn_delay 5
+set g_ctf_respawn_delay_small 5
+set g_ctf_respawn_delay_small_count 0
+set g_ctf_respawn_delay_large 5
+set g_ctf_respawn_delay_large_count 0
+set g_ctf_respawn_delay_max 0
 set g_ctf_respawn_waves 0
 set g_ctf_weapon_stay 0
-set g_dm_respawn_delay 0
+set g_dm_respawn_delay_small 0
+set g_dm_respawn_delay_small_count 0
+set g_dm_respawn_delay_large 0
+set g_dm_respawn_delay_large_count 0
+set g_dm_respawn_delay_max 0
 set g_dm_respawn_waves 0
 set g_dm_weapon_stay 0
-set g_dom_respawn_delay 0
+set g_dom_respawn_delay_small 0
+set g_dom_respawn_delay_small_count 0
+set g_dom_respawn_delay_large 0
+set g_dom_respawn_delay_large_count 0
+set g_dom_respawn_delay_max 0
 set g_dom_respawn_waves 0
 set g_dom_weapon_stay 0
-set g_lms_respawn_delay 0
+set g_lms_respawn_delay_small 0
+set g_lms_respawn_delay_small_count 0
+set g_lms_respawn_delay_large 0
+set g_lms_respawn_delay_large_count 0
+set g_lms_respawn_delay_max 0
 set g_lms_respawn_waves 0
 set g_lms_weapon_stay 0
-set g_tdm_respawn_delay 0
+set g_tdm_respawn_delay_small 0
+set g_tdm_respawn_delay_small_count 0
+set g_tdm_respawn_delay_large 0
+set g_tdm_respawn_delay_large_count 0
+set g_tdm_respawn_delay_max 0
 set g_tdm_respawn_waves 0
 set g_tdm_weapon_stay 0
-set g_ka_respawn_delay 0
+set g_ka_respawn_delay_small 0
+set g_ka_respawn_delay_small_count 0
+set g_ka_respawn_delay_large 0
+set g_ka_respawn_delay_large_count 0
+set g_ka_respawn_delay_max 0
 set g_ka_respawn_waves 0
 set g_ka_weapon_stay 0
-set g_kh_respawn_delay 0
+set g_kh_respawn_delay_small 0
+set g_kh_respawn_delay_small_count 0
+set g_kh_respawn_delay_large 0
+set g_kh_respawn_delay_large_count 0
+set g_kh_respawn_delay_max 0
 set g_kh_respawn_waves 0
 set g_kh_weapon_stay 0
-set g_ca_respawn_delay 0
+set g_ca_respawn_delay_small 0
+set g_ca_respawn_delay_small_count 0
+set g_ca_respawn_delay_large 0
+set g_ca_respawn_delay_large_count 0
+set g_ca_respawn_delay_max 0
 set g_ca_respawn_waves 0
 set g_ca_weapon_stay 0
-set g_nb_respawn_delay 0
+set g_nb_respawn_delay_small 0
+set g_nb_respawn_delay_small_count 0
+set g_nb_respawn_delay_large 0
+set g_nb_respawn_delay_large_count 0
+set g_nb_respawn_delay_max 0
 set g_nb_respawn_waves 0
 set g_nb_weapon_stay 0
-set g_as_respawn_delay 0
+set g_as_respawn_delay_small 0
+set g_as_respawn_delay_small_count 0
+set g_as_respawn_delay_large 0
+set g_as_respawn_delay_large_count 0
+set g_as_respawn_delay_max 0
 set g_as_respawn_waves 0
 set g_as_weapon_stay 0
-set g_ons_respawn_delay 0
+set g_ons_respawn_delay_small 0
+set g_ons_respawn_delay_small_count 0
+set g_ons_respawn_delay_large 0
+set g_ons_respawn_delay_large_count 0
+set g_ons_respawn_delay_max 0
 set g_ons_respawn_waves 0
 set g_ons_weapon_stay 0
+set g_rc_respawn_delay_small 0
+set g_rc_respawn_delay_small_count 0
+set g_rc_respawn_delay_large 0
+set g_rc_respawn_delay_large_count 0
+set g_rc_respawn_delay_max 0
 set g_rc_respawn_waves 0
-set g_rc_respawn_delay 0
 set g_rc_weapon_stay 0
+set g_cts_respawn_delay_small -1  // CTS shall have instant respawn.
+set g_cts_respawn_delay_small_count 0
+set g_cts_respawn_delay_large -1  // CTS shall have instant respawn.
+set g_cts_respawn_delay_large_count 0
+set g_cts_respawn_delay_max 0
 set g_cts_respawn_waves 0
-set g_cts_respawn_delay 0
 set g_cts_weapon_stay 2
+set g_ft_respawn_delay_small 0
+set g_ft_respawn_delay_small_count 0
+set g_ft_respawn_delay_large 0
+set g_ft_respawn_delay_large_count 0
+set g_ft_respawn_delay_max 0
 set g_ft_respawn_waves 0
-set g_ft_respawn_delay 0
 set g_ft_weapon_stay 0
+set g_inv_respawn_delay_small 0
+set g_inv_respawn_delay_small_count 0
+set g_inv_respawn_delay_large 0
+set g_inv_respawn_delay_large_count 0
+set g_inv_respawn_delay_max 0
 set g_inv_respawn_waves 0
-set g_inv_respawn_delay 0
 set g_inv_weapon_stay 0
 
 
diff --git a/qcsrc/server/autocvars.qh b/qcsrc/server/autocvars.qh
index fc9181d56..69d66cc04 100644
--- a/qcsrc/server/autocvars.qh
+++ b/qcsrc/server/autocvars.qh
@@ -795,7 +795,6 @@ float autocvar_g_domination_point_leadlimit;
 float autocvar_g_domination_point_rate;
 float autocvar_g_domination_teams_override;
 float autocvar_g_forced_respawn;
-float autocvar_g_respawn_delay_max;
 string autocvar_g_forced_team_blue;
 string autocvar_g_forced_team_otherwise;
 string autocvar_g_forced_team_pink;
@@ -944,7 +943,11 @@ float autocvar_g_projectiles_spread_style;
 float autocvar_g_race_qualifying_timelimit;
 float autocvar_g_race_qualifying_timelimit_override;
 float autocvar_g_race_teams;
-float autocvar_g_respawn_delay;
+float autocvar_g_respawn_delay_small;
+float autocvar_g_respawn_delay_small_count;
+float autocvar_g_respawn_delay_large;
+float autocvar_g_respawn_delay_large_count;
+float autocvar_g_respawn_delay_max;
 float autocvar_g_respawn_ghosts;
 float autocvar_g_respawn_ghosts_maxtime;
 float autocvar_g_respawn_ghosts_speed;
diff --git a/qcsrc/server/cl_player.qc b/qcsrc/server/cl_player.qc
index 0229d3ed5..67738c4c6 100644
--- a/qcsrc/server/cl_player.qc
+++ b/qcsrc/server/cl_player.qc
@@ -322,11 +322,107 @@ void PlayerCorpseDamage (entity inflictor, entity attacker, float damage, float
 	}
 }
 
+// g_<gametype>_str:
+// If 0, default is used.
+// If <0, 0 is used.
+// Otherwise, g_str (default value) is used.
+// For consistency, negative values there are mapped to zero too.
+#define GAMETYPE_DEFAULTED_SETTING(str) \
+	((gametype_setting_tmp = cvar(strcat("g_", GetGametype(), "_" #str))), \
+	 (gametype_setting_tmp < 0) ? 0 : \
+	 (gametype_setting_tmp == 0) ? max(0, autocvar_g_##str) : \
+	 gametype_setting_tmp)
+
+
+void calculate_player_respawn_time()
+{
+	float gametype_setting_tmp;
+	float sdelay_max = GAMETYPE_DEFAULTED_SETTING(respawn_delay_max);
+	float sdelay_small = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small);
+	float sdelay_large = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large);
+	float sdelay_small_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_small_count);
+	float sdelay_large_count = GAMETYPE_DEFAULTED_SETTING(respawn_delay_large_count);
+	float waves = GAMETYPE_DEFAULTED_SETTING(respawn_waves);
+
+	float pcount = 1;  // Include myself whether or not team is already set right and I'm a "player".
+	entity pl;
+	if (teamplay)
+	{
+		FOR_EACH_PLAYER(pl)
+			if (pl != self)
+				if (pl.team == self.team)
+					++pcount;
+		if (sdelay_small_count == 0)
+			sdelay_small_count = 1;
+		if (sdelay_large_count == 0)
+			sdelay_large_count = 1;
+	}
+	else
+	{
+		FOR_EACH_PLAYER(pl)
+			if (pl != self)
+				++pcount;
+		if (sdelay_small_count == 0)
+		{
+			if (g_cts)
+			{
+				// Players play independently. No point in requiring enemies.
+				sdelay_small_count = 1;
+			}
+			else
+			{
+				// Players play AGAINST each other. Enemies required.
+				sdelay_small_count = 2;
+			}
+		}
+		if (sdelay_large_count == 0)
+		{
+			if (g_cts)
+			{
+				// Players play independently. No point in requiring enemies.
+				sdelay_large_count = 1;
+			}
+			else
+			{
+				// Players play AGAINST each other. Enemies required.
+				sdelay_large_count = 2;
+			}
+		}
+	}
+
+	float sdelay;
+
+	if (pcount <= sdelay_small_count)
+		sdelay = sdelay_small;
+	else if (pcount >= sdelay_large_count)
+		sdelay = sdelay_large;
+	else  // NOTE: this case implies sdelay_large_count > sdelay_small_count.
+		sdelay = sdelay_small + (sdelay_large - sdelay_small) * (pcount - sdelay_small_count) / (sdelay_large_count - sdelay_small_count);
+
+	if(waves)
+		self.respawn_time = ceil((time + sdelay) / waves) * waves;
+	else
+		self.respawn_time = time + sdelay;
+
+	if(sdelay < sdelay_max)
+		self.respawn_time_max = time + sdelay_max;
+	else
+		self.respawn_time_max = self.respawn_time;
+
+	if((sdelay + waves >= 5.0) && (self.respawn_time - time > 1.75))
+		self.respawn_countdown = 10; // first number to count down from is 10
+	else
+		self.respawn_countdown = -1; // do not count down
+
+	if(g_cts || autocvar_g_forced_respawn)
+		self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
+}
+
 void ClientKill_Now_TeamChange();
 
 void PlayerDamage (entity inflictor, entity attacker, float damage, float deathtype, vector hitloc, vector force)
 {
-	float take, save, waves, sdelay, dh, da, j;
+	float take, save, dh, da, j;
 	vector v;
 	float valid_damage_for_weaponstats;
 	float excess;
@@ -621,34 +717,7 @@ void PlayerDamage (entity inflictor, entity attacker, float damage, float deatht
 		// dying animation
 		self.deadflag = DEAD_DYING;
 		// when to allow respawn
-		sdelay = 0;
-		waves = 0;
-		sdelay = cvar(strcat("g_", GetGametype(), "_respawn_delay"));
-		if(!sdelay)
-		{
-			if(g_cts)
-				sdelay = 0; // no respawn delay in CTS
-			else
-				sdelay = autocvar_g_respawn_delay;
-		}
-		waves = cvar(strcat("g_", GetGametype(), "_respawn_waves"));
-		if(!waves)
-			waves = autocvar_g_respawn_waves;
-		if(waves)
-			self.respawn_time = ceil((time + sdelay) / waves) * waves;
-		else
-			self.respawn_time = time + sdelay;
-		if(autocvar_g_respawn_delay_max > sdelay)
-			self.respawn_time_max = time + autocvar_g_respawn_delay_max;
-		else
-			self.respawn_time_max = self.respawn_time;
-		if((sdelay + waves >= 5.0) && (self.respawn_time - time > 1.75))
-			self.respawn_countdown = 10; // first number to count down from is 10
-		else
-			self.respawn_countdown = -1; // do not count down
-
-		if(g_cts || autocvar_g_forced_respawn)
-			self.respawn_flags = self.respawn_flags | RESPAWN_FORCE;
+		calculate_player_respawn_time();
 
 		self.death_time = time;
 		if (random() < 0.5)
-- 
2.39.5