3 float Nagger_SendEntity(entity to, float sendflags)
7 WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
28 WriteByte(MSG_ENTITY, nags);
33 WriteString(MSG_ENTITY, votecalledvote_display);
35 WriteString(MSG_ENTITY, "");
40 for(i = 1; i <= maxclients; i += 8)
42 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
43 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
45 WriteByte(MSG_ENTITY, f);
53 Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
55 void Nagger_VoteChanged()
58 nagger.SendFlags |= 128;
60 void Nagger_VoteCountChanged()
63 nagger.SendFlags |= 1;
65 void Nagger_ReadyCounted()
68 nagger.SendFlags |= 1;
72 string MapVote_Suggest(string m);
74 entity GetPlayer(string name)
80 if(substring(name, 0, 1) == "#") {
81 num = stof(substring(name, 1, 999));
82 if(num >= 1 && num <= maxclients) {
83 for((e = world); num > 0; --num, (e = nextent(e)))
85 //if(clienttype(e) == CLIENTTYPE_REAL)
86 if(e.classname == "player")
90 ns = strdecolorize(name);
91 FOR_EACH_REALPLAYER(e) {
92 if(!strcasecmp(strdecolorize(e.netname), ns)) {
100 //float ctf_clientcommand();
101 float readyrestart_happened;
102 .float lms_spectate_warning;
103 void spawnfunc_func_breakable();
104 void SV_ParseClientCommand(string s) {
106 float tokens, f, effectnum;
110 tokens = tokenize_console(s);
112 if(GameCommand_Vote(s, self)) {
114 } else if(GameCommand_MapVote(argv(0))) {
116 } else if(argv(0) == "autoswitch") {
117 // be backwards compatible with older clients (enabled)
118 self.autoswitch = ("0" != argv(1));
119 local string autoswitchmsg;
120 if (self.autoswitch) {
121 autoswitchmsg = "on";
123 autoswitchmsg = "off";
125 sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
126 } else if(argv(0) == "clientversion") {
127 if not(self.flags & FL_CLIENT)
129 if (argv(1) == "$gameversion") {
130 //versionmsg = "^1client is too old to get versioninfo.\nUPDATE!!! (http://www.nexuiz.com)^8";
131 // either that or someone wants to be funny
134 self.version = stof(argv(1));
136 if(self.version != cvar("gameversion"))
138 self.classname = "observer";
139 self.version_mismatch = 1;
141 } else if(cvar("g_campaign") || cvar("g_balance_teams") || cvar("g_balance_teams_force")) {
142 //JoinBestTeam(self, FALSE, TRUE);
143 } else if(teams_matter && !cvar("sv_spectate")) {
144 self.classname = "observer";
145 stuffcmd(self,"menu_showteamselect\n");
147 } else if(argv(0) == "reportcvar") { // old system
148 if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
150 s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
151 tokens = tokenize_console(s);
154 } else if(argv(0) == "sentcvar") { // new system
155 if(tokens == 2) // undefined cvar: use the default value on the server then
157 s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
158 tokens = tokenize_console(s);
161 } else if(argv(0) == "spectate") {
162 if not(self.flags & FL_CLIENT)
168 if(self.lms_spectate_warning)
170 // mark player as spectator
171 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
175 self.lms_spectate_warning = 1;
176 sprint(self, "WARNING: you won't be able to enter the game again after spectating in LMS. Use the same command again to spectate anyway.\n");
180 if(self.classname == "player" && cvar("sv_spectate") == 1) {
182 DropFlag(self.flagcarried, world, world);
184 DropBall(self.ballcarried, self.origin, self.velocity);
185 kh_Key_DropAll(self, TRUE);
186 WaypointSprite_PlayerDead();
187 self.classname = "observer";
189 sprint(self, strcat("^7You have to become a player within the next ", ftos(cvar("g_maxplayers_spectator_blocktime")), " seconds, otherwise you will be kicked, because spectators aren't allowed at this time!\n"));
192 } else if(argv(0) == "join") {
193 if not(self.flags & FL_CLIENT)
196 if (self.classname != "player" && !lockteams)
198 if(isJoinAllowed()) {
199 self.classname = "player";
200 PlayerScore_Clear(self);
201 bprint ("^4", self.netname, "^4 is playing now\n");
205 //player may not join because of g_maxplayers is set
206 centerprint_atprio(self, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
209 } else if( argv(0) == "selectteam" ) {
210 if not(self.flags & FL_CLIENT)
212 if( !teams_matter ) {
213 sprint( self, "selecteam can only be used in teamgames\n");
214 } else if(cvar("g_campaign")) {
215 //JoinBestTeam(self, 0);
216 } else if(lockteams) {
217 sprint( self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
218 } else if( argv(1) == "red" ) {
219 DoTeamChange(COLOR_TEAM1);
220 } else if( argv(1) == "blue" ) {
221 DoTeamChange(COLOR_TEAM2);
222 } else if( argv(1) == "yellow" ) {
223 DoTeamChange(COLOR_TEAM3);
224 } else if( argv(1) == "pink" ) {
225 DoTeamChange(COLOR_TEAM4);
226 } else if( argv(1) == "auto" ) {
229 sprint( self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
231 } else if(argv(0) == "ready") {
232 if not(self.flags & FL_CLIENT)
235 if((inWarmupStage && 0 >= g_warmup_limit) // with unlimited warmup players have to be able to restart
236 || cvar("sv_ready_restart"))
238 if(!readyrestart_happened || cvar("sv_ready_restart_repeatable"))
240 if (self.ready) // toggle
243 bprint(self.netname, "^2 is ^1NOT^2 ready\n");
248 bprint(self.netname, "^2 is ready\n");
251 // cannot reset the game while a timeout is active!
255 sprint(self, "^1Game has already been restarted\n");
258 } else if(argv(0) == "maplist") {
259 sprint(self, maplist_reply);
260 } else if(argv(0) == "lsmaps") {
261 sprint(self, lsmaps_reply);
262 } else if(argv(0) == "records") {
263 sprint(self, records_reply);
264 } else if(argv(0) == "voice") {
266 VoiceMessage(argv(1), substring(s, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
268 VoiceMessage(argv(1), "");
269 } else if(argv(0) == "say") {
271 Say(self, FALSE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
272 //clientcommand(self, formatmessage(s));
273 } else if(argv(0) == "say_team") {
275 Say(self, TRUE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
276 //clientcommand(self, formatmessage(s));
277 } else if(argv(0) == "tell") {
278 e = GetCommandPlayerSlotTargetFromTokenizedCommand(tokens, 1);
279 if(e && tokens > ParseCommandPlayerSlotTarget_firsttoken)
280 Say(self, FALSE, e, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), 1);
282 sprint(self, "ERROR: usage: tell # playerid text...\n");
283 //clientcommand(self, formatmessage(s));
284 } else if(argv(0) == "info") {
285 cmd = cvar_string(strcat("sv_info_", argv(1)));
287 sprint(self, "ERROR: unsupported info command\n");
289 wordwrap_sprint(cmd, 1111);
290 } else if(argv(0) == "suggestmap") {
291 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
292 } else if(argv(0) == "timeout") {
293 if not(self.flags & FL_CLIENT)
295 if(cvar("sv_timeout")) {
296 if(self.classname == "player") {
298 sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
303 sprint(self, "^7Error: only players can call a timeout!\n");
305 } else if(argv(0) == "timein") {
306 if not(self.flags & FL_CLIENT)
308 if(cvar("sv_timeout")) {
311 } else if(argv(0) == "teamstatus") {
312 Score_NicePrint(self);
313 } else if(argv(0) == "cvar_changes") {
314 sprint(self, cvar_changes);
315 } else if(argv(0) == "pointparticles") {
316 if((sv_cheats || self.maycheat) && tokens == 5)
320 // origin (0..1, on crosshair line)
323 effectnum = particleeffectnum(argv(1));
325 start = (1-f) * self.origin + f * self.cursor_trace_endpos;
328 pointparticles(effectnum, start, end, f);
331 sprint(self, "Usage: sv_cheats 1; restart; cmd pointparticles effectname position(0..1) velocityvector multiplier\n");
332 } else if(argv(0) == "trailparticles") {
333 if((sv_cheats || self.maycheat) && tokens == 2)
337 effectnum = particleeffectnum(argv(1));
338 W_SetupShot(self, FALSE, FALSE, "",0);
339 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
340 trailparticles(self, effectnum, w_shotorg, trace_endpos);
343 sprint(self, "Usage: sv_cheats 1; restart; cmd trailparticles effectname\n");
344 } else if(argv(0) == "make") {
345 if((sv_cheats || self.maycheat) && tokens == 3)
350 W_SetupShot(self, FALSE, FALSE, "", 0);
351 traceline(w_shotorg, w_shotorg + w_shotdir * 2048, MOVE_NORMAL, self);
352 if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || trace_fraction == 1)
354 sprint(self, "cannot make stuff there (bad surface)\n");
360 self.model = strzone(argv(1));
361 self.mdl = "rocket_explode";
363 self.origin = trace_endpos;
364 self.effects = EF_NOMODELFLAGS;
367 self.angles = fixedvectoangles2(trace_plane_normal, v_forward);
368 self.angles = AnglesTransform_Multiply(self.angles, '-90 0 0'); // so unrotated models work
370 spawnfunc_func_breakable();
374 tracebox(self.origin, self.mins, self.maxs, self.origin, MOVE_NORMAL, self);
377 sprint(oldself, "cannot make stuff there (no space)\n");
385 sprint(self, "Usage: sv_cheats 1; restart; cmd make models/... 0/1/2\n");
387 //if(ctf_clientcommand())
390 // grep for Cmd_AddCommand_WithClientCommand to find them all
398 if(cmd != "notarget")
402 //if(cmd != "say") // handled above
403 //if(cmd != "say_team") // handled above
412 if(cmd != "playermodel")
413 if(cmd != "playerskin")
414 if(cmd != "prespawn")
418 if(cmd != "sv_startdownload")
419 if(cmd != "download")
421 print("WARNING: Invalid clientcommand by ", self.netname, ": ", s, "\n");
425 if(self.jointime > 0 && time > self.jointime + 10 && time > self.nickspamtime) // allow any changes in the first 10 seconds since joining
426 if(cmd == "name" || cmd == "playermodel") // TODO also playerskin and color?
428 if(self.nickspamtime == 0 || time > self.nickspamtime + cvar("g_nick_flood_timeout"))
429 // good, no serious flood
430 self.nickspamcount = 1;
432 self.nickspamcount += 1;
433 self.nickspamtime = time + cvar("g_nick_flood_penalty");
435 if (timeoutStatus == 2) //when game is paused, no flood protection
436 self.nickspamcount = self.nickspamtime = 0;
439 clientcommand(self,s);
443 void ReadyRestartForce()
447 bprint("^1Server is restarting...\n");
452 if (checkrules_overtimesadded > 0) {
453 //we have to decrease timelimit to its original value again!!
455 newTL = cvar("timelimit");
456 newTL -= checkrules_overtimesadded * cvar("timelimit_overtime");
457 cvar_set("timelimit", ftos(newTL));
460 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
463 readyrestart_happened = 1;
464 game_starttime = time + RESTART_COUNTDOWN;
465 restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
467 inWarmupStage = 0; //once the game is restarted the game is in match stage
469 //reset the .ready status of all players (also spectators)
470 FOR_EACH_CLIENTSLOT(e)
473 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
475 if(cvar("teamplay_lockonrestart") && teams_matter) {
477 bprint("^1The teams are now locked.\n");
480 //initiate the restart-countdown-announcer entity
481 if(cvar("sv_ready_restart_after_countdown"))
483 restartTimer = spawn();
484 restartTimer.think = restartTimer_Think;
485 restartTimer.nextthink = game_starttime;
488 //after a restart every players number of allowed timeouts gets reset, too
489 if(cvar("sv_timeout"))
491 FOR_EACH_REALPLAYER(e)
492 e.allowedTimeouts = cvar("sv_timeout_number");
495 //reset map immediately if this cvar is not set
496 if (!cvar("sv_ready_restart_after_countdown"))
499 if(cvar("sv_eventlog"))
500 GameLogEcho(":restart");
505 // no arena, assault support yet...
506 if(g_arena | g_assault | gameover | intermission_running | race_completing)
507 localcmd("restart\n");
509 // reset ALL scores, but only do that at the beginning
510 //of the countdown if sv_ready_restart_after_countdown is off!
511 //Otherwise scores could be manipulated during the countdown!
512 if (!cvar("sv_ready_restart_after_countdown"))
519 * Counts how many players are ready. If not enough players are ready, the function
520 * does nothing. If all players are ready, the timelimit will be extended and the
521 * restart_countdown variable is set to allow other functions like PlayerPostThink
522 * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
523 * is not set the map will be resetted.
525 * Function is called after the server receives a 'ready' sign from a player.
534 FOR_EACH_REALPLAYER(e)
543 Nagger_ReadyCounted();
545 if(r) // at least one is ready
546 if(r == p) // and, everyone is ready
551 * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
554 void restartTimer_Think() {
555 restart_mapalreadyrestarted = 1;
563 * Checks whether the player who calls the timeout is allowed to do so.
564 * If so, it initializes the timeout countdown. It also checks whether another
565 * timeout was already running at this time and reacts correspondingly.
567 * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
568 * timeoutInitiator, timeoutStatus, timeoutHandler
570 * This function is called when a player issues the calltimeout command.
572 void evaluateTimeout() {
573 if (inWarmupStage && !g_warmup_allow_timeout)
574 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
575 if (time < game_starttime )
576 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
577 if (timeoutStatus != 2) {
578 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
579 if (cvar("timelimit")) {
580 //a timelimit was used
582 myTl = cvar("timelimit");
584 local float lastPossibleTimeout;
585 lastPossibleTimeout = (myTl*60) - cvar("sv_timeout_leadtime") - 1;
587 if (lastPossibleTimeout < time - game_starttime)
588 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
591 //player may not call a timeout if he has no calls left
592 if (self.allowedTimeouts < 1)
593 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
594 //now all required checks are passed
595 self.allowedTimeouts -= 1;
596 bprint(self.netname, " ^7called a timeout (", ftos(self.allowedTimeouts), " timeouts left)!\n"); //write a bprint who started the timeout (and how many he has left)
597 remainingTimeoutTime = cvar("sv_timeout_length");
598 remainingLeadTime = cvar("sv_timeout_leadtime");
599 timeoutInitiator = self;
600 if (timeoutStatus == 0) { //if another timeout was already active, don't change its status (which was 1 or 2) to 1, only change it to 1 if no timeout was active yet
602 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
603 timeoutHandler = spawn();
604 timeoutHandler.think = timeoutHandler_Think;
606 timeoutHandler.nextthink = time; //always let the entity think asap
608 //inform all connected clients about the timeout call
609 play2all("announcer/robotic/timeoutcalled.wav");
613 * Checks whether a player is allowed to resume the game. If he is allowed to do it,
614 * and the lead time for the timeout is still active, this countdown just will be aborted (the
615 * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
616 * value of the cvar sv_timeout_resumetime.
618 * This function is called when a player issues the resumegame command.
620 void evaluateTimein() {
622 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
623 if (self != timeoutInitiator)
624 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
625 if (timeoutStatus == 1) {
626 remainingTimeoutTime = timeoutStatus = 0;
627 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
628 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
630 else if (timeoutStatus == 2) {
631 //only shorten the remainingTimeoutTime if it makes sense
632 if( remainingTimeoutTime > (cvar("sv_timeout_resumetime") + 1) ) {
633 bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
634 remainingTimeoutTime = cvar("sv_timeout_resumetime");
635 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
638 sprint(self, "^7Error: Your resumegame call was discarded!\n");