#include "g_hook.qh"
#include "command/common.qh"
#include "command/vote.qh"
+#include "clientkill.qh"
#include "cheats.qh"
#include "g_world.qh"
#include "race.qh"
#include "../common/wepent.qh"
#include <common/state.qh>
+#include "compat/quake3.qh"
+
#include <common/effects/qc/globalsound.qh>
#include "../common/mapobjects/func/conveyor.qh"
#include "../common/mapobjects/teleporters.qh"
#include "../common/mapobjects/target/spawnpoint.qh"
+#include <common/mapobjects/trigger/counter.qh>
#include "../common/vehicles/all.qh"
WaypointSprite_PlayerDead(this);
- if (mutator_returnvalue) {
- // mutator prevents resetting teams+score
- } else {
- int oldteam = this.team;
- this.team = -1; // move this as it is needed to log the player spectating in eventlog
- MUTATOR_CALLHOOK(Player_ChangedTeam, this, oldteam, this.team);
- this.frags = FRAGS_SPECTATOR;
- PlayerScore_Clear(this); // clear scores when needed
- }
-
if (CS(this).killcount != FRAGS_SPECTATOR)
{
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_QUIT_SPECTATE, this.netname);
if(!game_stopped)
if(autocvar_g_chat_nospectators == 1 || (!warmup_stage && autocvar_g_chat_nospectators == 2))
Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_CHAT_NOSPECTATORS);
-
- if(!CS(this).just_joined)
- LogTeamchange(this.playerid, -1, 4);
- else
- CS(this).just_joined = false;
}
accuracy_resend(this);
this.strength_finished = 0;
this.invincible_finished = 0;
this.superweapons_finished = 0;
- this.dphitcontentsmask = 0;
+ //this.dphitcontentsmask = 0;
+ this.dphitcontentsmask = DPCONTENTS_SOLID;
+ if (autocvar_g_playerclip_collisions)
+ this.dphitcontentsmask |= DPCONTENTS_PLAYERCLIP;
this.pushltime = 0;
this.istypefrag = 0;
setthink(this, func_null);
if(axh.owner == this && axh != NULL && !wasfreed(axh))
delete(axh);
}
+
+ if (mutator_returnvalue)
+ {
+ // mutator prevents resetting teams+score
+ }
+ else
+ {
+ SetPlayerTeam(this, -1, TEAM_CHANGE_SPECTATOR);
+ this.frags = FRAGS_SPECTATOR;
+ }
}
int player_getspecies(entity this)
accuracy_resend(this);
if (this.team < 0)
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this);
entity spot = SelectSpawnPoint(this, false);
if (!spot) {
this.speedrunning = false;
+ this.counter_cnt = 0;
+ this.fragsfilter_cnt = 0;
+
target_voicescript_clear(this);
// reset fields the weapons may use
});
{
- string s = spot.target;
- spot.target = string_null;
+ //string s = spot.target;
+ //spot.target = string_null;
SUB_UseTargets(spot, this, NULL);
- spot.target = s;
+ //spot.target = s;
}
Unfreeze(this);
MUTATOR_CALLHOOK(DecodeLevelParms);
}
-/*
-=============
-ClientKill
-
-Called when a client types 'kill' in the console
-=============
-*/
-
-.float clientkill_nexttime;
-void ClientKill_Now_TeamChange(entity this)
-{
- if(this.killindicator_teamchange == -1)
- {
- JoinBestTeam( this, true );
- }
- else if(this.killindicator_teamchange == -2)
- {
- if(blockSpectators)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_INFO, INFO_SPECTATE_WARNING, autocvar_g_maxplayers_spectator_blocktime);
- PutObserverInServer(this);
- }
- else
- SV_ChangeTeam(this, this.killindicator_teamchange - 1);
- this.killindicator_teamchange = 0;
-}
-
-void ClientKill_Now(entity this)
-{
- if(this.vehicle)
- {
- vehicles_exit(this.vehicle, VHEF_RELEASE);
- if(!this.killindicator_teamchange)
- {
- this.vehicle_health = -1;
- Damage(this, this, this, 1 , DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
- }
- }
-
- if(this.killindicator && !wasfreed(this.killindicator))
- delete(this.killindicator);
-
- this.killindicator = NULL;
-
- if(this.killindicator_teamchange)
- ClientKill_Now_TeamChange(this);
-
- if (!IS_SPEC(this) && !IS_OBSERVER(this) && MUTATOR_CALLHOOK(ClientKill_Now, this) == false)
- {
- Damage(this, this, this, 100000, DEATH_KILL.m_id, DMG_NOWEP, this.origin, '0 0 0');
- }
-
- // now I am sure the player IS dead
-}
-void KillIndicator_Think(entity this)
-{
- if (game_stopped)
- {
- this.owner.killindicator = NULL;
- delete(this);
- return;
- }
-
- if (this.owner.alpha < 0 && !this.owner.vehicle)
- {
- this.owner.killindicator = NULL;
- delete(this);
- return;
- }
-
- if(this.cnt <= 0)
- {
- ClientKill_Now(this.owner);
- return;
- }
- else if(this.count == 1) // count == 1 means that it's silent
- {
- this.nextthink = time + 1;
- this.cnt -= 1;
- }
- else
- {
- if(this.cnt <= 10)
- setmodel(this, MDL_NUM(this.cnt));
- if(IS_REAL_CLIENT(this.owner))
- {
- if(this.cnt <= 10)
- { Send_Notification(NOTIF_ONE, this.owner, MSG_ANNCE, Announcer_PickNumber(CNT_KILL, this.cnt)); }
- }
- this.nextthink = time + 1;
- this.cnt -= 1;
- }
-}
-
-float clientkilltime;
-void ClientKill_TeamChange (entity this, float targetteam) // 0 = don't change, -1 = auto, -2 = spec
-{
- float killtime;
- float starttime;
-
- if (game_stopped)
- return;
-
- killtime = autocvar_g_balance_kill_delay;
-
- if(MUTATOR_CALLHOOK(ClientKill, this, killtime))
- return;
- killtime = M_ARGV(1, float);
-
- this.killindicator_teamchange = targetteam;
-
- if(!this.killindicator)
- {
- if(!IS_DEAD(this))
- {
- killtime = max(killtime, this.clientkill_nexttime - time);
- this.clientkill_nexttime = time + killtime + autocvar_g_balance_kill_antispam;
- }
-
- if(killtime <= 0 || !IS_PLAYER(this) || IS_DEAD(this))
- {
- ClientKill_Now(this);
- }
- else
- {
- starttime = max(time, clientkilltime);
-
- this.killindicator = spawn();
- this.killindicator.owner = this;
- this.killindicator.scale = 0.5;
- setattachment(this.killindicator, this, "");
- setorigin(this.killindicator, '0 0 52');
- setthink(this.killindicator, KillIndicator_Think);
- this.killindicator.nextthink = starttime + (this.lip) * 0.05;
- clientkilltime = max(clientkilltime, this.killindicator.nextthink + 0.05);
- this.killindicator.cnt = ceil(killtime);
- this.killindicator.count = bound(0, ceil(killtime), 10);
- //sprint(this, strcat("^1You'll be dead in ", ftos(this.killindicator.cnt), " seconds\n"));
-
- IL_EACH(g_clones, it.enemy == this && !(it.effects & CSQCMODEL_EF_RESPAWNGHOST),
- {
- it.killindicator = spawn();
- it.killindicator.owner = it;
- it.killindicator.scale = 0.5;
- setattachment(it.killindicator, it, "");
- setorigin(it.killindicator, '0 0 52');
- setthink(it.killindicator, KillIndicator_Think);
- it.killindicator.nextthink = starttime + (it.lip) * 0.05;
- //clientkilltime = max(clientkilltime, it.killindicator.nextthink + 0.05);
- it.killindicator.cnt = ceil(killtime);
- });
- this.lip = 0;
- }
- }
- if(this.killindicator)
- {
- if(targetteam == 0) // just die
- {
- this.killindicator.colormod = '0 0 0';
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SUICIDE, this.killindicator.cnt);
- }
- else if(targetteam == -1) // auto
- {
- this.killindicator.colormod = '0 1 0';
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_AUTO, this.killindicator.cnt);
- }
- else if(targetteam == -2) // spectate
- {
- this.killindicator.colormod = '0.5 0.5 0.5';
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, CENTER_TEAMCHANGE_SPECTATE, this.killindicator.cnt);
- }
- else
- {
- this.killindicator.colormod = Team_ColorRGB(targetteam);
- if(IS_REAL_CLIENT(this))
- if(this.killindicator.cnt > 0)
- Send_Notification(NOTIF_ONE_ONLY, this, MSG_CENTER, APP_TEAM_NUM(targetteam, CENTER_TEAMCHANGE), this.killindicator.cnt);
- }
- }
-
-}
-
-void ClientKill (entity this)
-{
- // TODO: once .health is removed, will need to check it here for the "already dead" message!
-
- if(game_stopped) return;
- if(this.player_blocked) return;
- if(STAT(FROZEN, this)) return;
-
- ClientKill_TeamChange(this, 0);
-}
-
void FixClientCvars(entity e)
{
// send prediction settings to the client
}
#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);
+ }
+}
+
+string getwelcomemessage(entity this)
+{
+ 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)
+ 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(g_jetpack)
+ modifications = strcat(modifications, ", Jet pack");
+ if(autocvar_g_powerups == 0)
+ modifications = strcat(modifications, ", No powerups");
+ if(autocvar_g_powerups > 0)
+ modifications = strcat(modifications, ", Powerups");
+ modifications = substring(modifications, 2, strlen(modifications) - 2);
+
+ string versionmessage = GetClientVersionMessage(this);
+ string s = strcat(versionmessage, "^8\n^8\nmatch type is ^1", gamemode_name, "^8\n");
+
+ if(modifications != "")
+ s = strcat(s, "^8\nactive modifications: ^3", modifications, "^8\n");
+
+ if(cache_lastmutatormsg != autocvar_g_mutatormsg)
+ {
+ strcpy(cache_lastmutatormsg, autocvar_g_mutatormsg);
+ strcpy(cache_mutatormsg, cache_lastmutatormsg);
+ }
+
+ if (cache_mutatormsg != "") {
+ s = strcat(s, "\n\n^8special gameplay tips: ^7", cache_mutatormsg);
+ }
+
+ string mutator_msg = "";
+ MUTATOR_CALLHOOK(BuildGameplayTipsString, mutator_msg);
+ mutator_msg = M_ARGV(0, string);
+
+ s = strcat(s, mutator_msg); // trust that the mutator will do proper formatting
+
+ string motd = autocvar_sv_motd;
+ if (motd != "") {
+ s = strcat(s, "\n\n^8MOTD: ^7", strreplace("\\n", "\n", motd));
+ }
+ return s;
+}
+
/**
=============
ClientConnect
TRANSMUTE(Client, this);
CS(this).version_nagtime = time + 10 + random() * 10;
- // identify the right forced team
- if (autocvar_g_campaign)
- {
- if (IS_REAL_CLIENT(this)) // only players, not bots
- {
- switch (autocvar_g_campaign_forceteam)
- {
- case 1: this.team_forced = NUM_TEAM_1; break;
- case 2: this.team_forced = NUM_TEAM_2; break;
- case 3: this.team_forced = NUM_TEAM_3; break;
- case 4: this.team_forced = NUM_TEAM_4; break;
- default: this.team_forced = 0;
- }
- }
- }
- else if (PlayerInList(this, autocvar_g_forced_team_red)) this.team_forced = NUM_TEAM_1;
- else if (PlayerInList(this, autocvar_g_forced_team_blue)) this.team_forced = NUM_TEAM_2;
- else if (PlayerInList(this, autocvar_g_forced_team_yellow)) this.team_forced = NUM_TEAM_3;
- else if (PlayerInList(this, autocvar_g_forced_team_pink)) this.team_forced = NUM_TEAM_4;
- else switch (autocvar_g_forced_team_otherwise)
- {
- default: this.team_forced = 0; break;
- case "red": this.team_forced = NUM_TEAM_1; break;
- case "blue": this.team_forced = NUM_TEAM_2; break;
- case "yellow": this.team_forced = NUM_TEAM_3; break;
- case "pink": this.team_forced = NUM_TEAM_4; break;
- case "spectate":
- case "spectator":
- this.team_forced = -1;
- break;
- }
- if (!teamplay && this.team_forced > 0) this.team_forced = 0;
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
+
+ bot_clientconnect(this);
- int playerid_save = this.playerid;
- this.playerid = 0; // silent
- JoinBestTeam(this, false); // if the team number is valid, keep it
- this.playerid = playerid_save;
+ Player_DetermineForcedTeam(this);
TRANSMUTE(Observer, this);
if (autocvar_sv_eventlog)
GameLogEcho(strcat(":join:", ftos(this.playerid), ":", ftos(etof(this)), ":", ((IS_REAL_CLIENT(this)) ? this.netaddress : "bot"), ":", playername(this, false)));
- LogTeamchange(this.playerid, this.team, 1);
-
CS(this).just_joined = true; // stop spamming the eventlog with additional lines when the client connects
- if(teamplay && IS_PLAYER(this))
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_CONNECT_TEAM), this.netname);
- else
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_CONNECT, this.netname);
-
stuffcmd(this, clientstuff, "\n");
stuffcmd(this, "cl_particles_reloadeffects\n"); // TODO do we still need this?
// notify about available teams
if (teamplay)
{
- CheckAllowedTeams(this);
- int t = 0;
- if (c1 >= 0) t |= BIT(0);
- if (c2 >= 0) t |= BIT(1);
- if (c3 >= 0) t |= BIT(2);
- if (c4 >= 0) t |= BIT(3);
+ entity balance = TeamBalance_CheckAllowedTeams(this);
+ int t = TeamBalance_GetAllowedTeams(balance);
+ TeamBalance_Destroy(balance);
stuffcmd(this, sprintf("set _teams_available %d\n", t));
}
else
void play_countdown(entity this, float finished, Sound samp)
{
- TC(Sound, samp);
+ TC(Sound, samp);
if(IS_REAL_CLIENT(this))
if(floor(finished - time - frametime) != floor(finished - time))
if(finished - time < 6)
if (time < this.superweapons_finished || (this.items & IT_UNLIMITED_SUPERWEAPONS))
{
this.items = this.items | IT_SUPERWEAPON;
- if(!g_cts)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
- Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
+ if(!(this.items & IT_UNLIMITED_SUPERWEAPONS))
+ {
+ if(!g_cts)
+ Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_SUPERWEAPON_PICKUP, this.netname);
+ Send_Notification(NOTIF_ONE, this, MSG_CENTER, CENTER_SUPERWEAPON_PICKUP);
+ }
}
else
{
keys = BITSET(keys, KEY_LEFT, CS(this).movement.y < 0);
keys = BITSET(keys, KEY_JUMP, PHYS_INPUT_BUTTON_JUMP(this));
- keys = BITSET(keys, KEY_CROUCH, PHYS_INPUT_BUTTON_CROUCH(this));
+ keys = BITSET(keys, KEY_CROUCH, IS_DUCKED(this)); // workaround: player can't un-crouch until their path is clear, so we keep the button held here
keys = BITSET(keys, KEY_ATCK, PHYS_INPUT_BUTTON_ATCK(this));
keys = BITSET(keys, KEY_ATCK2, PHYS_INPUT_BUTTON_ATCK2(this));
CS(this).pressedkeys = keys; // store for other users
void SpectateCopy(entity this, entity spectatee)
{
- TC(Client, this); TC(Client, spectatee);
+ TC(Client, this); TC(Client, spectatee);
MUTATOR_CALLHOOK(SpectateCopy, spectatee, this);
PS(this) = PS(spectatee);
.bool team_selected;
bool ShowTeamSelection(entity this)
{
- if(!teamplay || autocvar_g_campaign || autocvar_g_balance_teams || this.team_selected || (CS(this).wasplayer && autocvar_g_changeteam_banned) || this.team_forced > 0)
+ 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");
return true;
if(!this.team_selected)
if(autocvar_g_campaign || autocvar_g_balance_teams)
- JoinBestTeam(this, true);
+ TeamBalance_JoinBestTeam(this);
if(autocvar_g_campaign)
campaign_bots_may_start = true;
if(IS_PLAYER(this))
if(teamplay && this.team != -1)
- Send_Notification(NOTIF_ALL, NULL, MSG_INFO, APP_TEAM_NUM(this.team, INFO_JOIN_PLAY_TEAM), this.netname);
+ {
+ }
else
Send_Notification(NOTIF_ALL, NULL, MSG_INFO, INFO_JOIN_PLAY, this.netname);
this.team_selected = false;
}
+int GetPlayerLimit()
+{
+ int player_limit = autocvar_g_maxplayers;
+ MUTATOR_CALLHOOK(GetPlayerLimit, player_limit);
+ player_limit = M_ARGV(0, int);
+ return player_limit;
+}
+
/**
* Determines whether the player is allowed to join. This depends on cvar
* g_maxplayers, if it isn't used this function always return true, otherwise
return 0;
}
- if(this && this.team_forced < 0)
+ if(this && (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
return 0; // forced spectators can never join
// TODO simplify this
++currentlyPlaying;
});
+ int player_limit = GetPlayerLimit();
+
float free_slots = 0;
- if (!autocvar_g_maxplayers)
+ if (!player_limit)
free_slots = maxclients - totalClients;
- else if(currentlyPlaying < autocvar_g_maxplayers)
- free_slots = min(maxclients - totalClients, autocvar_g_maxplayers - currentlyPlaying);
+ else if(currentlyPlaying < player_limit)
+ free_slots = min(maxclients - totalClients, player_limit - currentlyPlaying);
static float join_prevent_msg_time = 0;
if(this && ignore && !free_slots && time > join_prevent_msg_time)
// don't do this in ClientConnect
// many things can go wrong if a client is spawned as player on connection
if (MUTATOR_CALLHOOK(AutoJoinOnConnection, this)
- || (!(autocvar_sv_spectate || autocvar_g_campaign || this.team_forced < 0)
+ || (!(autocvar_sv_spectate || autocvar_g_campaign || (Player_GetForcedTeamIndex(this) == TEAM_FORCE_SPECTATOR))
&& (!teamplay || autocvar_g_balance_teams)))
{
campaign_bots_may_start = true;