#include <server/antilag.qh>
#include <server/bot/api.qh>
#include <server/bot/default/cvars.qh>
+#include <server/bot/default/waypoints.qh>
#include <server/campaign.qh>
#include <server/chat.qh>
#include <server/cheats.qh>
if (CS(e).race_completed) sf |= BIT(0); // forced scoreboard
if (CS(to).spectatee_status) sf |= BIT(1); // spectator ent number follows
if (CS(e).zoomstate) sf |= BIT(2); // zoomed
- if (autocvar_sv_showspectators) sf |= BIT(4); // show spectators
+ if (observe_blocked_if_eliminated && INGAME(to))
+ sf |= BIT(3); // observing blocked
+ if (autocvar_sv_showspectators == 1 || (autocvar_sv_showspectators && IS_SPEC(to)))
+ sf |= BIT(4); // show spectators
WriteHeader(MSG_ENTITY, ENT_CLIENT_CLIENTDATA);
WriteByte(MSG_ENTITY, sf);
{
if (vote_called) { VoteCount(false); }
this.ready = false;
- recount_ready = true;
+ if (warmup_stage || game_starttime > time) recount_ready = true;
}
entcs_update_players(this);
}
TRANSMUTE(Observer, this);
- if(recount_ready) ReadyCount();
+ if(recount_ready) ReadyCount(); // FIXME: please add comment about why this is delayed
WaypointSprite_PlayerDead(this);
accuracy_resend(this);
}
else
{
- SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+ SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR); // clears scores too in game modes without teams
this.frags = FRAGS_SPECTATOR;
}
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);
PlayerState_attach(this);
accuracy_resend(this);
+ if (teamplay && this.bot_forced_team)
+ SetPlayerTeam(this, this.bot_forced_team, TEAM_CHANGE_MANUAL);
+
if (this.team < 0)
TeamBalance_JoinBestTeam(this);
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);
Unfreeze(this, false);
MUTATOR_CALLHOOK(PlayerSpawn, this, spot);
-
{
string s = spot.target;
if(g_assault || g_race) // TODO: make targeting work in assault & race without this hack
this.alivetime = time;
antilag_clear(this, CS(this));
+
+ if (warmup_stage < 0 || warmup_stage > 1)
+ ReadyCount();
}
/** Called when a client spawns in the server */
void PutClientInServer(entity this)
{
- if (IS_BOT_CLIENT(this)) {
- TRANSMUTE(Player, this);
- } else if (IS_REAL_CLIENT(this)) {
+ if (IS_REAL_CLIENT(this)) {
msg_entity = this;
WriteByte(MSG_ONE, SVC_SETVIEW);
WriteEntity(MSG_ONE, this);
}
#endif
-string GetClientVersionMessage(entity this)
-{
- if (CS(this).version_mismatch) {
- if(CS(this).version < autocvar_gameversion) {
- return strcat("This is Xonotic ", autocvar_g_xonoticversion,
- "\n^3Your client version is outdated.\n\n\n### YOU WON'T BE ABLE TO PLAY ON THIS SERVER ###\n\n\nPlease update!!!^8");
- } else {
- return strcat("This is Xonotic ", autocvar_g_xonoticversion,
- "\n^3This server is using an outdated Xonotic version.\n\n\n ### THIS SERVER IS INCOMPATIBLE AND THUS YOU CANNOT JOIN ###.^8");
- }
- } else {
- return strcat("Welcome to Xonotic ", autocvar_g_xonoticversion);
- }
-}
-
-void SendWelcomemessage(entity this, bool force_centerprint)
+// NOTE csqc uses the active mutators list sent by this function
+// to understand which mutators are enabled
+// also note that they aren't all registered mutators, e.g. jetpack, low gravity
+void SendWelcomeMessage(entity this, int msg_type)
{
- msg_entity = this;
- WriteHeader(MSG_ONE, TE_CSQC_SERVERWELCOME);
- WriteByte(MSG_ONE, boolean(autocvar_g_campaign));
+ WriteByte(msg_type, boolean(autocvar_g_campaign));
if (boolean(autocvar_g_campaign))
{
- WriteString(MSG_ONE, Campaign_GetTitle());
- WriteByte(MSG_ONE, Campaign_GetLevelNum());
- WriteString(MSG_ONE, Campaign_GetMessage());
+ WriteString(msg_type, Campaign_GetTitle());
+ WriteByte(msg_type, Campaign_GetLevelNum());
+ WriteString(msg_type, Campaign_GetMessage());
return;
}
- WriteByte(MSG_ONE, force_centerprint);
- WriteString(MSG_ONE, autocvar_hostname);
- WriteString(MSG_ONE, GetClientVersionMessage(this));
+ WriteString(msg_type, autocvar_hostname);
+ WriteString(msg_type, autocvar_g_xonoticversion);
+ WriteByte(msg_type, CS(this).version_mismatch);
+ WriteByte(msg_type, (CS(this).version < autocvar_gameversion));
+ WriteByte(msg_type, autocvar_g_warmup > 1 ? autocvar_g_warmup : map_minplayers);
+ WriteByte(msg_type, GetPlayerLimit());
MUTATOR_CALLHOOK(BuildMutatorsPrettyString, "");
string modifications = M_ARGV(0, string);
- if(g_weaponarena)
- {
- if(g_weaponarena_random)
- modifications = strcat(modifications, ", ", ftos(g_weaponarena_random), " of ", g_weaponarena_list, " Arena");
- else
- modifications = strcat(modifications, ", ", g_weaponarena_list, " Arena");
- }
- else if(cvar("g_balance_blaster_weaponstartoverride") == 0)
+ if (!g_weaponarena && cvar("g_balance_blaster_weaponstartoverride") == 0)
modifications = strcat(modifications, ", No start weapons");
if(cvar("sv_gravity") < stof(cvar_defstring("sv_gravity")))
modifications = strcat(modifications, ", Low gravity");
if(g_weapon_stay && !g_cts)
modifications = strcat(modifications, ", Weapons stay");
if(autocvar_g_jetpack)
- modifications = strcat(modifications, ", Jet pack");
+ modifications = strcat(modifications, ", Jetpack");
modifications = substring(modifications, 2, strlen(modifications) - 2);
- WriteString(MSG_ONE, modifications);
+ WriteString(msg_type, modifications);
+
+ WriteString(msg_type, g_weaponarena_list);
if(cache_lastmutatormsg != autocvar_g_mutatormsg)
{
strcpy(cache_mutatormsg, cache_lastmutatormsg);
}
- WriteString(MSG_ONE, cache_mutatormsg);
+ WriteString(msg_type, cache_mutatormsg);
- string mutator_msg = "";
- MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
- mutator_msg = M_ARGV(0, string);
-
- WriteString(MSG_ONE, mutator_msg); // trust that the mutator will do proper formatting
- WriteString(MSG_ONE, strreplace("\\n", "\n", autocvar_sv_motd));
-}
-
-void SendWelcomemessage_onConnection_think(entity this)
-{
- SendWelcomemessage(this, false);
-}
-
-void SendWelcomemessage_onConnection(entity this)
-{
- // give the client time to sent its version
- defer(this, 0.5, SendWelcomemessage_onConnection_think);
+ WriteString(msg_type, strreplace("\\n", "\n", autocvar_sv_motd));
}
/**
{
if (g_weaponarena_weapons == WEPSET(TUBA))
stuffcmd(this, "cl_cmd settemp chase_active 1\n");
+ // quickmenu file must be put in a subfolder with an unique name
+ // to reduce chances of overriding custom client quickmenus
+ if (waypointeditor_enabled)
+ stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", "wpeditor.txt"));
+ else if (autocvar_sv_quickmenu_file != "" && strstrofs(autocvar_sv_quickmenu_file, "/", 0) && fexists(autocvar_sv_quickmenu_file))
+ stuffcmd(this, sprintf("cl_cmd settemp _hud_panel_quickmenu_file_from_server %s\n", autocvar_sv_quickmenu_file));
}
if (!autocvar_sv_foginterval && world.fog != "")
stuffcmd(this, strcat("\nfog ", world.fog, "\nr_fog_exp2 0\nr_drawfog 1\n"));
- if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AvailableTeams() == 2))
+ if (autocvar_sv_teamnagger && !(autocvar_bot_vs_human && AVAILABLE_TEAMS == 2))
if(!MUTATOR_CALLHOOK(HideTeamNagger, this))
send_CSQC_teamnagger();
CS(this).model_randomizer = random();
if (IS_REAL_CLIENT(this))
- {
- if (!autocvar_g_campaign)
- SendWelcomemessage_onConnection(this);
sv_notice_join(this);
- }
- this.move_qcphysics = autocvar_sv_qcphysics;
+ this.move_qcphysics = true;
// update physics stats (players can spawn before physics runs)
Physics_UpdateStats(this);
MUTATOR_CALLHOOK(ClientConnect, this);
- if (IS_REAL_CLIENT(this) && !IS_PLAYER(this) && !autocvar_g_campaign)
- CS(this).motd_actived_time = -1; // the welcome message is shown by the client
+ if (player_count == 1)
+ localcmd("\nsv_hook_firstjoin\n");
}
/*
=============
if (this.personal) delete(this.personal);
this.playerid = 0;
- ReadyCount();
+ if (warmup_stage || game_starttime > time) ReadyCount();
if (vote_called && IS_REAL_CLIENT(this)) VoteCount(false);
player_powerups_remove_all(this); // stop powerup sound
ONREMOVE(this);
+
+ if (player_count == 0)
+ localcmd("\nsv_hook_lastleave\n");
}
void ChatBubbleThink(entity this)
{
if (!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || Player_HasRealForcedTeam(this))
return false;
- stuffcmd(this, "menu_showteamselect\n");
+ if (frametime) // once per frame is more than enough
+ stuffcmd(this, "_scoreboard_team_selection 1\n");
return true;
}
void Join(entity this)
{
+ if (autocvar_g_campaign && !campaign_bots_may_start && !game_stopped && time >= game_starttime)
+ ReadyRestart(true);
+
TRANSMUTE(Player, this);
if(!this.team_selected)
{
if(g_duel)
return 2; // TODO: this workaround is needed since the mutator hook from duel can't be activated before the gametype is loaded (e.g. switching modes via gametype vote screen)
- int player_limit = autocvar_g_maxplayers;
+ // don't return map_maxplayers during intermission as it would interfere with MapHasRightSize()
+ int player_limit = (autocvar_g_maxplayers >= 0 || intermission_running) ? autocvar_g_maxplayers : map_maxplayers;
MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
player_limit = M_ARGV(0, int);
- return player_limit;
+ return player_limit < maxclients ? player_limit : 0;
}
/**
static float msg_time = 0;
if(this && !INGAME(this) && ignore && !free_slots && time > msg_time)
{
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT);
+ Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_JOIN_PREVENT, player_limit);
msg_time = time + 0.5;
}
return free_slots;
}
-void PrintWelcomeMessage(entity this)
-{
- if(CS(this).motd_actived_time == 0)
- {
- if (autocvar_g_campaign) {
- if ((IS_PLAYER(this) && PHYS_INPUT_BUTTON_INFO(this)) || (!IS_PLAYER(this))) {
- CS(this).motd_actived_time = time;
- SendWelcomemessage(this, false);
- }
- } else {
- if (PHYS_INPUT_BUTTON_INFO(this)) {
- CS(this).motd_actived_time = time;
- SendWelcomemessage(this, true);
- }
- }
- }
- else if(CS(this).motd_actived_time > 0) // showing MOTD or campaign message
- {
- if (autocvar_g_campaign) {
- if (PHYS_INPUT_BUTTON_INFO(this))
- CS(this).motd_actived_time = time;
- else if ((time - CS(this).motd_actived_time > 2) && IS_PLAYER(this)) { // hide it some seconds after BUTTON_INFO has been released
- CS(this).motd_actived_time = 0;
- Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
- }
- } else {
- if (PHYS_INPUT_BUTTON_INFO(this))
- CS(this).motd_actived_time = time;
- else if (time - CS(this).motd_actived_time > 2) { // hide it some seconds after BUTTON_INFO has been released
- CS(this).motd_actived_time = 0;
- Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
- }
- }
- }
- else //if(CS(this).motd_actived_time < 0) // just connected, motd is active
- {
- if(PHYS_INPUT_BUTTON_INFO(this)) // BUTTON_INFO hides initial MOTD
- CS(this).motd_actived_time = -2; // wait until BUTTON_INFO gets released
- else if (CS(this).motd_actived_time == -2)
- {
- // instantly hide MOTD
- CS(this).motd_actived_time = 0;
- Kill_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CPID_MOTD);
- }
- else if (IS_PLAYER(this) || IS_SPEC(this))
- {
- // FIXME occasionally for some reason MOTD never goes away
- // delay MOTD removal a little bit in the hope it fixes this bug
- if (CS(this).motd_actived_time == -1) // MOTD marked to fade away as soon as client becomes player or spectator
- CS(this).motd_actived_time = -(5 + floor(random() * 10)); // add small delay
- else //if (CS(this).motd_actived_time < -2)
- CS(this).motd_actived_time++;
- }
- }
-}
-
bool joinAllowed(entity this)
{
if (CS(this).version_mismatch) return false;
}
}
+ if (IS_BOT_CLIENT(this) && !CS(this).autojoin_checked)
+ {
+ CS(this).autojoin_checked = true;
+ TRANSMUTE(Player, this);
+ PutClientInServer(this);
+ return;
+ }
+
if (this.flags & FL_JUMPRELEASED) {
if (PHYS_INPUT_BUTTON_JUMP(this) && (joinAllowed(this) || time < CS(this).jointime + MIN_SPEC_TIME)) {
this.flags &= ~FL_JUMPRELEASED;
}
CS(this).impulse = 0;
} else if(PHYS_INPUT_BUTTON_ATCK2(this)) {
- this.would_spectate = false;
- this.flags &= ~FL_JUMPRELEASED;
- TRANSMUTE(Observer, this);
- PutClientInServer(this);
+ if(!observe_blocked_if_eliminated || !INGAME(this)) {
+ this.would_spectate = false;
+ this.flags &= ~FL_JUMPRELEASED;
+ TRANSMUTE(Observer, this);
+ PutClientInServer(this);
+ }
} else if(!SpectateUpdate(this) && !SpectateNext(this)) {
PutObserverInServer(this, false, true);
this.would_spectate = true;
PlayerUseKey(this);
CS(this).usekeypressed = PHYS_INPUT_BUTTON_USE(this);
- if (IS_REAL_CLIENT(this))
- PrintWelcomeMessage(this);
-
if (IS_PLAYER(this)) {
if (IS_REAL_CLIENT(this) && time < CS(this).jointime + MIN_SPEC_TIME)
error("Client can't be spawned as player on connection!");
|| (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
&& (!teamplay || autocvar_g_balance_teams)))
{
- campaign_bots_may_start = true;
if(joinAllowed(this))
Join(this);
return;