- wget -nv -O data/maps/stormkeep.waypoints https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints\r
- wget -nv -O data/maps/stormkeep.waypoints.cache https://gitlab.com/xonotic/xonotic-maps.pk3dir/raw/master/maps/stormkeep.waypoints.cache\r
\r
- - EXPECT=8fb740a3cb3754ca7b9295ddc5c911b5\r
+ - EXPECT=55338fabce73c671336171e6cb055f74\r
- HASH=$(${ENGINE} +timestamps 1 +exec serverbench.cfg\r
| tee /dev/stderr\r
| sed -e 's,^\[[^]]*\] ,,'\r
{
float warmup_timelimit = STAT(WARMUP_TIMELIMIT);
if(warmup_timelimit > 0)
- timeleft = max(0, warmup_timelimit - time);
+ timeleft = max(0, warmup_timelimit + starttime - time);
else
timeleft = 0;
}
InfoMessage(s);
}
- if(warmup_stage)
- {
- s = _("^2Currently in ^1warmup^2 stage!");
- InfoMessage(s);
- }
-
string blinkcolor;
if(time % 1 >= 0.5)
blinkcolor = "^1";
else
blinkcolor = "^3";
- if(warmup_stage && STAT(WARMUP_TIMELIMIT) <= 0 && srv_minplayers)
- {
- Scoreboard_UpdatePlayerTeams(); // ensure numplayers is current
- if(srv_minplayers - numplayers == 1)
- s = _("^31^2 more player is needed for the match to start.");
- else
- s = sprintf(_("^3%d^2 more players are needed for the match to start."), srv_minplayers - numplayers);
- InfoMessage(s);
- }
- else if(ready_waiting && !spectatee_status)
- {
- if(ready_waiting_for_me)
- s = sprintf(_("%sPress ^3%s%s to end warmup"), blinkcolor, getcommandkey(_("ready"), "ready"), blinkcolor);
- else
- s = _("^2Waiting for others to ready up to end warmup...");
- InfoMessage(s);
- }
- else if(warmup_stage && !spectatee_status)
+ if(warmup_stage)
{
- s = sprintf(_("^2Press ^3%s^2 to end warmup"), getcommandkey(_("ready"), "ready"));
- InfoMessage(s);
+ InfoMessage(_("^2Currently in ^1warmup^2 stage!"));
+
+ int players_needed = 0;
+ if(STAT(WARMUP_TIMELIMIT) <= 0 && srv_minplayers)
+ {
+ Scoreboard_UpdatePlayerTeams(); // ensure numplayers is current
+ players_needed = srv_minplayers - numplayers;
+ }
+
+ if(players_needed > 0)
+ {
+ if(players_needed == 1)
+ s = _("^31^2 more player is needed for the match to start.");
+ else
+ s = sprintf(_("^3%d^2 more players are needed for the match to start."), players_needed);
+ InfoMessage(s);
+ }
+ else if(!spectatee_status)
+ {
+ if(ready_waiting)
+ {
+ if(ready_waiting_for_me)
+ s = sprintf(_("%sPress ^3%s%s to end warmup"), blinkcolor, getcommandkey(_("ready"), "ready"), blinkcolor);
+ else
+ s = _("^2Waiting for others to ready up to end warmup...");
+ }
+ else
+ s = sprintf(_("^2Press ^3%s^2 to end warmup"), getcommandkey(_("ready"), "ready"));
+ InfoMessage(s);
+ }
}
if(teamplay && !spectatee_status && teamnagger)
}
drawcolorcodedstring(pos + '1 0 0' * (panel_size.x - stringwidth(str, true, sb_gameinfo_detail_fontsize)), str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align right
// map name and player count
- str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
+ if (campaign)
+ str = "";
+ else
+ str = sprintf(_("^5%d^7/^5%d ^7players"), numplayers, srv_maxplayers ? srv_maxplayers : maxclients);
str = strcat("^7", _("Map:"), " ^2", mi_shortname, " ", str); // reusing "Map:" translatable string
drawcolorcodedstring(pos, str, sb_gameinfo_detail_fontsize, panel_fg_alpha, DRAWFLAG_NORMAL); // align left
}
if (STAT(WARMUP_TIMELIMIT) > 0)
subtext = _("Warmup");
else
- subtext = srv_minplayers ? _("Warmup: too few players") : _("Warmup: no time limit");
+ {
+ Scoreboard_UpdatePlayerTeams(); // ensure numplayers is current
+ if (srv_minplayers - numplayers > 0)
+ subtext = _("Warmup: too few players");
+ else
+ subtext = _("Warmup: no time limit");
+ }
}
else if(STAT(TIMEOUT_STATUS) == 2)
subtext = _("Timeout");
NET_HANDLE(ENT_CLIENT_NAGGER, bool isnew)
{
make_pure(this);
- int i, j, b, f;
int nags = ReadByte(); // NAGS NAGS NAGS NAGS NAGS NAGS NADZ NAGS NAGS NAGS
strcpy(vote_called_vote, ReadString());
}
- if(nags & 1)
- {
- for(j = 0; j < maxclients; ++j)
- if(playerslots[j])
- playerslots[j].ready = true;
- for(i = 1; i <= maxclients; i += 8)
- {
- f = ReadByte();
- for(j = i-1, b = BIT(0); b < BIT(8); b <<= 1, ++j)
- if (!(f & b))
- if(playerslots[j])
- playerslots[j].ready = false;
- }
- }
+ if(nags & BIT(0))
+ for(int i = 0; i < maxclients;)
+ for(int f = ReadByte(), b = 0; b < 8 && i < maxclients; ++b, ++i)
+ if(playerslots[i])
+ playerslots[i].ready = f & BIT(b);
return = true;
bool net_handle_ServerWelcome()
{
- bool campaign = ReadByte();
+ campaign = ReadByte();
if (campaign)
{
string campaign_title = ReadString();
float zoomin_effect;
bool warmup_stage;
+bool campaign;
string hostname;
string welcome_msg;
int srv_minplayers;
#include <server/weapons/accuracy.qh>
#include <server/weapons/selection.qh>
#include <server/world.qh>
+#include <server/command/vote.qh>
STATIC_INIT(bot) { bot_calculate_stepheightvec(); }
W_NextWeapon(this, 0, weaponentity);
// block the bot during the countdown to game start
CS(this).movement = '0 0 0';
- this.bot_nextthink = game_starttime;
return;
}
else if(this.aistatus & AI_STATUS_STUCK)
navigation_unstuck(this);
+ if (warmup_stage && !this.ready)
+ {
+ this.ready = true;
+ ReadyCount(); // this must be delayed until the bot has spawned
+ }
+
// now call the current bot AI (havocbot for example)
this.bot_ai(this);
}
{
if(autocvar_g_chat_flood_notify_flooder)
{
- sourcemsgstr = strcat(msgstr, "\n^3FLOOD CONTROL: ^7message too long, trimmed\n");
+ sourcemsgstr = strcat(msgstr, "\n^3CHAT FLOOD CONTROL: ^7message too long, trimmed\n");
sourcecmsgstr = "";
}
else
{
if (autocvar_g_chat_flood_notify_flooder)
{
- sprint(source, strcat("^3FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
+ sprint(source, strcat("^3CHAT FLOOD CONTROL: ^7wait ^1", ftos(source.(flood_field) - time), "^3 seconds\n"));
ret = 0;
}
else
setcolor(player, stof(autocvar_sv_defaultplayercolors));
}
+void GiveWarmupResources(entity this)
+{
+ SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
+ SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
+ SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
+ SetResource(this, RES_CELLS, warmup_start_ammo_cells);
+ SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
+ SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
+ SetResource(this, RES_HEALTH, warmup_start_health);
+ SetResource(this, RES_ARMOR, warmup_start_armorvalue);
+ STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
+}
+
void PutPlayerInServer(entity this)
{
if (this.vehicle) vehicles_exit(this.vehicle, VHEF_RELEASE);
this.takedamage = DAMAGE_AIM;
this.effects = EF_TELEPORT_BIT | EF_RESTARTANIM_BIT;
- if (warmup_stage) {
- SetResource(this, RES_SHELLS, warmup_start_ammo_shells);
- SetResource(this, RES_BULLETS, warmup_start_ammo_nails);
- SetResource(this, RES_ROCKETS, warmup_start_ammo_rockets);
- SetResource(this, RES_CELLS, warmup_start_ammo_cells);
- SetResource(this, RES_PLASMA, warmup_start_ammo_plasma);
- SetResource(this, RES_FUEL, warmup_start_ammo_fuel);
- SetResource(this, RES_HEALTH, warmup_start_health);
- SetResource(this, RES_ARMOR, warmup_start_armorvalue);
- STAT(WEAPONS, this) = WARMUP_START_WEAPONS;
- } else {
+ if (warmup_stage)
+ GiveWarmupResources(this);
+ else
+ {
SetResource(this, RES_SHELLS, start_ammo_shells);
SetResource(this, RES_BULLETS, start_ammo_nails);
SetResource(this, RES_ROCKETS, start_ammo_rockets);
antilag_clear(this, CS(this));
- if (warmup_stage == -1)
+ if (warmup_stage < 0 || warmup_stage > 1)
ReadyCount();
}
WriteString(msg_type, autocvar_g_xonoticversion);
WriteByte(msg_type, CS(this).version_mismatch);
WriteByte(msg_type, (CS(this).version < autocvar_gameversion));
- WriteByte(msg_type, map_minplayers);
+ WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
WriteByte(msg_type, GetPlayerLimit());
MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
ATTRIB(Client, specialcommand_pos, int, this.specialcommand_pos);
ATTRIB(Client, hitplotfh, int, this.hitplotfh);
ATTRIB(Client, clientdata, entity, this.clientdata);
- ATTRIB(Client, cmd_floodcount, int, this.cmd_floodcount);
ATTRIB(Client, cmd_floodtime, float, this.cmd_floodtime);
ATTRIB(Client, wasplayer, bool, this.wasplayer);
ATTRIB(Client, weaponorder_byimpulse, string, this.weaponorder_byimpulse);
void FixPlayermodel(entity player);
+void GiveWarmupResources(entity this);
+
void ClientInit_misc(entity this);
int GetPlayerLimit();
// Last updated: December 28th, 2011
// =========================================================
-bool SV_ParseClientCommand_floodcheck(entity this)
-{
- entity store = IS_CLIENT(this) ? CS(this) : this; // unfortunately, we need to store these on the client initially
-
- if (!timeout_status) // not while paused
- {
- if (time <= (store.cmd_floodtime + autocvar_sv_clientcommand_antispam_time))
- {
- store.cmd_floodcount += 1;
- if (store.cmd_floodcount > autocvar_sv_clientcommand_antispam_count) return false; // too much spam, halt
- }
- else
- {
- store.cmd_floodtime = time;
- store.cmd_floodcount = 1;
- }
- }
- return true; // continue, as we're not flooding yet
-}
-
// =======================
// Command Sub-Functions
{
case CMD_REQUEST_COMMAND:
{
- if (IS_CLIENT(caller) && caller.last_ready < time - 3)
+ if (warmup_stage || g_race_qualifying == 2)
+ if (IS_PLAYER(caller) || INGAME_JOINED(caller))
{
- if (warmup_stage || g_race_qualifying == 2)
+ if (caller.ready) // toggle
{
- if (time < game_starttime) // game is already restarting
- return;
- if (caller.ready) // toggle
- {
- caller.ready = false;
- if (IS_PLAYER(caller) || INGAME_JOINED(caller))
- bprint(playername(caller.netname, caller.team, false), "^2 is ^1NOT^2 ready\n");
- }
- else
- {
- caller.ready = true;
- if (IS_PLAYER(caller) || INGAME_JOINED(caller))
- bprint(playername(caller.netname, caller.team, false), "^2 is ready\n");
- }
-
- caller.last_ready = time;
- ReadyCount();
+ caller.ready = false;
+ bprint(playername(caller.netname, caller.team, false), "^2 is ^1NOT^2 ready\n");
}
+ else
+ {
+ caller.ready = true;
+ bprint(playername(caller.netname, caller.team, false), "^2 is ready\n");
+ }
+ ReadyCount();
}
return; // never fall through to usage
}
case "prespawn": break; // handled by engine in host_cmd.c
case "sentcvar": break; // handled by server in this file
case "spawn": break; // handled by engine in host_cmd.c
+ case "say": case "say_team": case "tell": break; // chat has its own flood control in chat.qc
case "color": case "topcolor": case "bottomcolor": // handled by engine in host_cmd.c
if(!IS_CLIENT(this)) // on connection
{
break;
case "c2s": Net_ClientCommand(this, command); return; // handled by net.qh
+ // on connection, client sends all of these
+ case "name": case "rate": case "rate_burstsize": case "playermodel": case "playerskin": case "clientversion":
+ if(!IS_CLIENT(this)) break;
+ // else fall through to default: flood control
default:
- if (SV_ParseClientCommand_floodcheck(this)) break; // "true": continue, as we're not flooding yet
- else return; // "false": not allowed to continue, halt // print("^1ERROR: ^7ANTISPAM CAUGHT: ", command, ".\n");
+ if (!timeout_status) // not while paused
+ {
+ entity store = IS_CLIENT(this) ? CS(this) : this; // unfortunately, we need to store these on the client initially
+ // this is basically the same as the chat flood control
+ if (time < store.cmd_floodtime)
+ {
+ sprint(this, strcat("^3CMD FLOOD CONTROL: wait ^1", ftos(store.cmd_floodtime - time), "^3 seconds, command was: ", command, "\n"));
+ return; // too much spam, halt
+ }
+ else
+ store.cmd_floodtime = max(time - autocvar_sv_clientcommand_antispam_count * autocvar_sv_clientcommand_antispam_time, store.cmd_floodtime) + autocvar_sv_clientcommand_antispam_time;
+ }
+ break; // continue, as we're not flooding yet
}
/* NOTE: should this be disabled? It can be spammy perhaps, but hopefully it's okay for now */
int autocvar_sv_clientcommand_antispam_count;
.float cmd_floodtime;
-.float cmd_floodcount;
string MapVote_Suggest(entity this, string m);
// Nagger for players to know status of voting
bool Nagger_SendEntity(entity this, entity to, float sendflags)
{
- int nags, i, f, b;
- entity e;
+ int nags = 0;
WriteHeader(MSG_ENTITY, ENT_CLIENT_NAGGER);
// bits:
// 64 = vote counts
// 128 = vote string
- nags = 0;
- if (readycount)
+ if (warmup_stage)
{
- nags |= BIT(0);
- if (to.ready == 0) nags |= BIT(1);
+ if (readycount)
+ {
+ nags |= BIT(0);
+ if (!to.ready) nags |= BIT(1);
+ }
+ nags |= BIT(4);
}
+
if (vote_called)
{
nags |= BIT(2);
if (to.vote_selection == 0) nags |= BIT(3);
+ nags |= sendflags & BIT(6);
+ nags |= sendflags & BIT(7);
}
- if (warmup_stage) nags |= BIT(4);
-
- if (sendflags & BIT(6)) nags |= BIT(6);
-
- if (sendflags & BIT(7)) nags |= BIT(7);
-
- if (!(nags & 4)) // no vote called? send no string
- nags &= ~(BIT(6) | BIT(7));
WriteByte(MSG_ENTITY, nags);
if (nags & BIT(7)) WriteString(MSG_ENTITY, vote_called_display);
- if (nags & 1)
+ if (nags & BIT(0))
{
- for (i = 1; i <= maxclients; i += 8)
+ for (int i = 1; i <= maxclients;)
{
- for (f = 0, e = edict_num(i), b = BIT(0); b < BIT(8); b <<= 1, e = nextent(e))
- if (!IS_REAL_CLIENT(e) || e.ready)
- f |= b;
+ int f = 0;
+ for (int b = 0; b < 8 && i <= maxclients; ++b, ++i)
+ if (edict_num(i).ready)
+ f |= BIT(b);
WriteByte(MSG_ENTITY, f);
}
}
{
bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ^1", vote_called_display, "^2 was accepted\n");
- if ((vote_called == VOTE_MASTER) && vote_caller) vote_caller.vote_master = 1;
+ if ((vote_called == VOTE_MASTER) && vote_caller) vote_caller.vote_master = true;
else localcmd(strcat(vote_called_command, "\n"));
if (vote_caller) vote_caller.vote_waittime = 0; // people like your votes, you don't need to wait to vote again
// Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set)
void ReadyRestart_think(entity this)
{
- reset_map(true, false);
+ if (!warmup_stage) // if the countdown was not aborted
+ reset_map(true, false);
delete(this);
}
{
if (time <= game_starttime && game_stopped)
return;
- if (!is_fake_round_start)
+ if (!is_fake_round_start && !autocvar_g_campaign)
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_COUNTDOWN_RESTART);
VoteReset();
if(!is_fake_round_start && !warmup_stage)
localcmd("\nsv_hook_warmupend\n");
- // reset the .ready status of all players (also spectators)
- FOREACH_CLIENT(IS_REAL_CLIENT(it), { it.ready = false; });
+ // reset the .ready status of all clients (including spectators and bots)
+ FOREACH_CLIENT(true, { it.ready = false; });
readycount = 0;
Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
if(forceWarmupEnd || autocvar_g_campaign)
warmup_stage = 0; // forcefully end warmup and go to match stage
else
- warmup_stage = cvar("g_warmup"); // go into warmup if it's enabled, otherwise restart into match stage
+ warmup_stage = autocvar_g_warmup; // go into warmup if it's enabled, otherwise restart into match stage
ReadyRestart_force(false);
}
// cannot reset the game while a timeout is active or pending
if (timeout_status) return;
- float ready_needed_factor, ready_needed_count;
- float t_players = 0;
+ int total_players = 0, human_players = 0, humans_ready = 0;
readycount = 0;
- FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || INGAME_JOINED(it)), {
- ++t_players;
+ FOREACH_CLIENT(IS_PLAYER(it) || INGAME_JOINED(it), {
+ ++total_players;
if (it.ready) ++readycount;
+ if (IS_REAL_CLIENT(it))
+ {
+ ++human_players;
+ if (it.ready) ++humans_ready;
+ }
});
Nagger_ReadyCounted();
- if (t_players < map_minplayers) // map_minplayers will only be set if g_warmup -1 at worldspawn
+ // can't read warmup_stage here as it could have been set to 0 by ReadyRestart()
+ // and we need to use this when checking if we should abort the countdown
+ // map_minplayers can only be > 0 if g_warmup was -1 at worldspawn
+ int minplayers = autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers;
+
+ if (total_players < minplayers)
{
if (game_starttime > time) // someone bailed during countdown, back to warmup
{
- warmup_stage = -1; // CAN change it AFTER calling Nagger_ReadyCounted() this frame
+ warmup_stage = autocvar_g_warmup; // CAN change it AFTER calling Nagger_ReadyCounted() this frame
game_starttime = time;
- Send_Notification(NOTIF_ALL, NULL, MSG_MULTI, COUNTDOWN_STOP, map_minplayers);
+ Send_Notification(NOTIF_ALL, NULL, MSG_MULTI, COUNTDOWN_STOP, minplayers);
+ if (!sv_ready_restart_after_countdown) // if we ran reset_map() at start of countdown
+ FOREACH_CLIENT(IS_PLAYER(it), { GiveWarmupResources(it); });
}
if (warmup_limit > 0)
warmup_limit = -1;
return; // don't ReadyRestart if players are ready but too few
}
- else if (map_minplayers && warmup_limit <= 0)
+ else if (minplayers && warmup_limit <= 0)
{
// there's enough players now but we're still in infinite warmup
warmup_limit = cvar("g_warmup_limit");
// warmup continues until enough players AND enough RUPs (no time limit)
}
- ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 1);
- ready_needed_count = ceil(t_players * ready_needed_factor);
-
- if (readycount >= ready_needed_count) ReadyRestart(true);
+ if (humans_ready && humans_ready >= rint(human_players * bound(0.5, cvar("g_warmup_majority_factor"), 1)))
+ ReadyRestart(true);
}
// global vote information declarations
entity vote_caller; // original caller of the current vote
string vote_caller_name; // name of the vote caller
-float vote_called; // stores status of current vote (See VOTE_*)
+int vote_called; // stores status of current vote (See VOTE_*)
float vote_endtime; // time when the vote is finished
-float vote_accept_count; // total amount of players who accept the vote (counted by VoteCount() function)
-float vote_reject_count; // same as above, but rejected
-float vote_abstain_count; // same as above, but abstained
-float vote_needed_overall; // total amount of players NEEDED for a vote to pass (based on sv_vote_majority_factor)
-.float vote_master; // flag for if the player has vote master privelages
+int vote_accept_count; // total amount of players who accept the vote (counted by VoteCount() function)
+int vote_reject_count; // same as above, but rejected
+int vote_abstain_count; // same as above, but abstained
+int vote_needed_overall; // total amount of players NEEDED for a vote to pass (based on sv_vote_majority_factor)
+.bool vote_master; // flag for if the player has vote master privileges
.float vote_waittime; // flag for how long the player must wait before they can vote again
-.float vote_selection; // flag for which vote selection the player has made (See VOTE_SELECT_*)
+.int vote_selection; // flag for which vote selection the player has made (See VOTE_SELECT_*)
string vote_called_command; // command sent by client
string vote_called_display; // visual string of command sent by client
string vote_parsed_command; // command which is fixed after being parsed
const float RESTART_COUNTDOWN = 10;
entity nagger;
int readycount; // amount of players who are ready
-.float ready; // flag for if a player is ready
-.float last_ready; // last ready time for anti-spam
+.bool ready; // flag for if a player is ready
.int team_saved; // team number to restore upon map reset
.void(entity this) reset; // if set, an entity is reset using this
.void(entity this) reset2; // if set, an entity is reset using this (after calling ALL the reset functions for other entities)
int u = AVAILABLE_TEAMS - d;
map_minplayers += (u < d && u + map_minplayers <= m) ? u : -d;
}
- warmup_limit = -1;
}
else
- map_minplayers = 0; // don't display a minimum if it's not used
+ map_minplayers = 0; // don't display a minimum if it's not used (g_maxplayers < 0 && g_warmup >= 0)
}
void InitGameplayMode()
GameRules_limit_fallbacks();
- if(warmup_limit == 0)
- warmup_limit = autocvar_timelimit * 60;
-
player_count = 0;
bot_waypoints_for_items = autocvar_g_waypoints_for_items;
if(bot_waypoints_for_items == 1)
sv_ready_restart_after_countdown = cvar("sv_ready_restart_after_countdown");
- warmup_stage = cvar("g_warmup");
- warmup_limit = cvar("g_warmup_limit");
-
if(cvar("g_campaign"))
warmup_stage = 0; // no warmup during campaign
+ else
+ {
+ warmup_stage = autocvar_g_warmup;
+ if (warmup_stage < 0 || warmup_stage > 1)
+ warmup_limit = -1; // don't start until there's enough players
+ else if (warmup_stage == 1)
+ {
+ // this code is duplicated in ReadyCount()
+ warmup_limit = cvar("g_warmup_limit");
+ if(warmup_limit == 0)
+ warmup_limit = autocvar_timelimit * 60;
+ }
+ }
g_pickup_respawntime_weapon = cvar("g_pickup_respawntime_weapon");
g_pickup_respawntime_superweapon = cvar("g_pickup_respawntime_superweapon");
bool autocvar__endmatch;
bool autocvar_g_use_ammunition;
bool autocvar_g_jetpack;
+int autocvar_g_warmup;
bool autocvar_g_warmup_allguns;
bool autocvar_g_warmup_allow_timeout;
#define autocvar_g_weaponarena cvar_string("g_weaponarena")
set g_maxplayers_spectator_blocktime 5 "if the players voted for the \"nospectators\" command, this setting defines the number of seconds a observer/spectator has time to join the game before they get kicked"
// tournament mod
-set g_warmup 0 "split the game into a warmup- and match-stage, -1 means stay in warmup until enough (set by map, lower bound of 2 or 2 per team) players join, then g_warmup_limit and readiness apply"
+set g_warmup 0 "splits the game into warmup and match stages, 1 means the match starts when g_warmup_majority_factor of players are ready OR g_warmup_limit is hit, >1 also requires at least g_warmup players (including bots) to join, -1 means that minimum player requrement is set by the map (lower bound of 2 or 2 per team)"
set g_warmup_limit 180 "limit warmup-stage to this time (in seconds); if set to -1 the warmup-stage is not affected by any timelimit, if set to 0 the usual timelimit also affects warmup-stage"
set g_warmup_allow_timeout 0 "allow calling timeouts in the warmup-stage (if sv_timeout is set to 1)"
set g_warmup_allguns 1 "provide more weapons on start while in warmup: 0 = normal start weapons, 1 = all guns available on the map, 2 = all normal weapons"
-set g_warmup_majority_factor 0.8 "fraction of joined players sufficient to end warmup before g_warmup_limit by readying up"
+set g_warmup_majority_factor 0.8 "fraction of joined players (not including bots) sufficient to end warmup before g_warmup_limit by readying up"
alias sv_hook_warmupend