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)) {
103 if(self.aiment && self.enemy)
105 self.origin_x = (self.aiment.origin_x + self.enemy.origin_x) * 0.5;
106 self.origin_y = (self.aiment.origin_y + self.enemy.origin_y) * 0.5;
107 self.origin_z = (self.aiment.origin_z + self.enemy.origin_z) * 0.5;
108 self.maxs_x = fabs(self.aiment.origin_x - self.enemy.origin_x) * 0.5;
109 self.maxs_y = fabs(self.aiment.origin_y - self.enemy.origin_y) * 0.5;
110 self.maxs_z = fabs(self.aiment.origin_z - self.enemy.origin_z) * 0.5;
111 self.mins = -1 * self.maxs;
112 setorigin(self, self.origin); setsize(self, self.mins, self.maxs); // link edict
115 if(self.cnt == -1) // actually race_place -1
117 // show "10 10" for qualifying spawns
118 setmodel(self.killindicator, "models/sprites/10.spr32");
119 setmodel(self.killindicator.killindicator, "models/sprites/10.spr32");
121 else if(self.cnt == -2) // actually race_place 0
123 // show "10 0" for loser spawns
124 setmodel(self.killindicator, "models/sprites/10.spr32");
125 setmodel(self.killindicator.killindicator, "models/sprites/0.spr32");
129 setmodel(self.killindicator, strcat("models/sprites/", ftos(mod(self.cnt, 10)), ".spr32"));
130 setmodel(self.killindicator.killindicator, strcat("models/sprites/", ftos(floor(self.cnt / 10)), ".spr32"));
133 self.nextthink = time;
136 //float ctf_clientcommand();
137 float readyrestart_happened;
138 .float lms_spectate_warning;
139 void spawnfunc_func_breakable();
141 .float cmd_floodtime;
142 .float cmd_floodcount;
143 float cmd_floodcheck()
145 if (timeoutStatus != 2)
147 if(time == self.cmd_floodtime)
149 self.cmd_floodcount += 1;
150 if(self.cmd_floodcount > 8)
155 self.cmd_floodtime = time;
156 self.cmd_floodcount = 1;
162 void SV_ParseClientCommand(string s) {
164 float tokens, f, effectnum;
168 tokens = tokenize_console(s);
171 if(cmd != "reportcvar")
172 if(cmd != "sentcvar")
174 if(cmd != "prespawn")
182 if(GameCommand_Vote(s, self)) {
184 } else if(GameCommand_MapVote(argv(0))) {
186 } else if(cmd == "autoswitch") {
187 // be backwards compatible with older clients (enabled)
188 self.autoswitch = ("0" != argv(1));
189 local string autoswitchmsg;
190 if (self.autoswitch) {
191 autoswitchmsg = "on";
193 autoswitchmsg = "off";
195 sprint(self, strcat("^1autoswitch turned ", autoswitchmsg, "\n"));
196 } else if(cmd == "clientversion") {
197 if not(self.flags & FL_CLIENT)
199 if (argv(1) == "$gameversion") {
200 //versionmsg = "^1client is too old to get versioninfo.\nUPDATE!!! (http://www.nexuiz.com)^8";
201 // either that or someone wants to be funny
204 self.version = stof(argv(1));
206 if(self.version != cvar("gameversion"))
208 self.classname = "observer";
209 self.version_mismatch = 1;
211 } else if(cvar("g_campaign") || cvar("g_balance_teams") || cvar("g_balance_teams_force")) {
212 //JoinBestTeam(self, FALSE, TRUE);
213 } else if(teams_matter && !cvar("sv_spectate")) {
214 self.classname = "observer";
215 stuffcmd(self,"menu_showteamselect\n");
217 } else if(cmd == "reportcvar") { // old system
218 if(substring(argv(2), 0, 1) == "$") // undefined cvar: use the default value on the server then
220 s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
221 tokens = tokenize_console(s);
225 } else if (cmd == "uid") {
228 self.uid = strzone(argv(1));
229 self.uid_kicktime = 0;
230 print("Client ", etos(self), " has UID ", self.uid, "\n");
233 } else if(cmd == "sentcvar") { // new system
234 if(tokens == 2) // undefined cvar: use the default value on the server then
236 s = strcat(substring(s, argv_start_index(0), argv_end_index(1) - argv_start_index(0)), " \"", cvar_defstring(argv(1)), "\"");
237 tokens = tokenize_console(s);
240 } else if(cmd == "spectate") {
243 if not(self.flags & FL_CLIENT)
249 if(self.lms_spectate_warning)
251 // mark player as spectator
252 PlayerScore_Add(self, SP_LMS_RANK, 666 - PlayerScore_Add(self, SP_LMS_RANK, 0));
256 self.lms_spectate_warning = 1;
257 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");
261 if(self.classname == "player" && cvar("sv_spectate") == 1) {
263 DropFlag(self.flagcarried, world, world);
265 DropBall(self.ballcarried, self.origin, self.velocity);
266 kh_Key_DropAll(self, TRUE);
267 WaypointSprite_PlayerDead();
268 self.classname = "observer";
272 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"));
275 } else if(cmd == "join") {
276 if not(self.flags & FL_CLIENT)
279 if (self.classname != "player" && !lockteams)
281 if(isJoinAllowed()) {
282 self.classname = "player";
285 PlayerScore_Clear(self);
286 bprint ("^4", self.netname, "^4 is playing now\n");
287 self.stat_count = WEP_LAST;
289 if(cvar("g_campaign"))
290 campaign_bots_may_start = 1;
293 //player may not join because of g_maxplayers is set
294 centerprint_atprio(self, CENTERPRIO_MAPVOTE, PREVENT_JOIN_TEXT);
297 } else if( cmd == "selectteam" ) {
298 if not(self.flags & FL_CLIENT)
300 if( !teams_matter ) {
301 sprint( self, "selecteam can only be used in teamgames\n");
302 } else if(cvar("g_campaign")) {
303 //JoinBestTeam(self, 0);
304 } else if(lockteams) {
305 sprint( self, "^7The game has already begun, you must wait until the next map to be able to join a team.\n");
306 } else if( argv(1) == "red" ) {
307 DoTeamChange(COLOR_TEAM1);
308 } else if( argv(1) == "blue" ) {
309 DoTeamChange(COLOR_TEAM2);
310 } else if( argv(1) == "yellow" ) {
311 DoTeamChange(COLOR_TEAM3);
312 } else if( argv(1) == "pink" ) {
313 DoTeamChange(COLOR_TEAM4);
314 } else if( argv(1) == "auto" ) {
317 sprint( self, strcat( "selectteam none/red/blue/yellow/pink/auto - \"", argv(1), "\" not recognised\n" ) );
319 } else if(cmd == "ready") {
320 if not(self.flags & FL_CLIENT)
323 if((inWarmupStage && 0 >= g_warmup_limit) // with unlimited warmup players have to be able to restart
324 || cvar("sv_ready_restart") || g_race_qualifying == 2)
326 if(!readyrestart_happened || cvar("sv_ready_restart_repeatable"))
328 if (self.ready) // toggle
331 bprint(self.netname, "^2 is ^1NOT^2 ready\n");
336 bprint(self.netname, "^2 is ready\n");
339 // cannot reset the game while a timeout is active!
343 sprint(self, "^1Game has already been restarted\n");
346 } else if(cmd == "maplist") {
347 sprint(self, maplist_reply);
348 } else if(cmd == "lsmaps") {
349 sprint(self, lsmaps_reply);
350 } else if(cmd == "records") {
351 sprint(self, records_reply);
352 } else if(cmd == "rankings") {
353 sprint(self, rankings_reply);
354 } else if(cmd == "voice") {
356 VoiceMessage(argv(1), substring(s, argv_start_index(2), argv_end_index(-1) - argv_start_index(2)));
358 VoiceMessage(argv(1), "");
359 } else if(cmd == "say") {
361 Say(self, FALSE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
362 //clientcommand(self, formatmessage(s));
363 } else if(cmd == "say_team") {
365 Say(self, TRUE, world, substring(s, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), 1);
366 //clientcommand(self, formatmessage(s));
367 } else if(cmd == "tell") {
368 e = GetCommandPlayerSlotTargetFromTokenizedCommand(tokens, 1);
369 if(e && tokens > ParseCommandPlayerSlotTarget_firsttoken)
371 Say(self, FALSE, e, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)), TRUE);
375 if(tokens > ParseCommandPlayerSlotTarget_firsttoken)
376 trigger_magicear_processmessage_forallears(self, -1, world, substring(s, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken)));
377 sprint(self, "ERROR: usage: tell # playerid text...\n");
379 //clientcommand(self, formatmessage(s));
380 } else if(cmd == "info") {
381 cmd = cvar_string_builtin(strcat("sv_info_", argv(1))); // This needed fixed for the cvar check
383 sprint(self, "ERROR: unsupported info command\n");
385 wordwrap_sprint(cmd, 1111);
386 } else if(cmd == "suggestmap") {
387 sprint(self, strcat(MapVote_Suggest(argv(1)), "\n"));
388 } else if(cmd == "timeout") {
389 if not(self.flags & FL_CLIENT)
391 if(cvar("sv_timeout")) {
392 if(self.classname == "player") {
394 sprint(self, "^7Error: you can not call a timeout while a vote is active!\n");
399 sprint(self, "^7Error: only players can call a timeout!\n");
401 } else if(cmd == "timein") {
402 if not(self.flags & FL_CLIENT)
404 if(cvar("sv_timeout")) {
407 } else if(cmd == "teamstatus") {
408 Score_NicePrint(self);
409 } else if(cmd == "cvar_changes") {
410 sprint(self, cvar_changes);
411 } else if(cmd == "pointparticles") {
412 if((sv_cheats || self.maycheat) && tokens == 5)
416 // origin (0..1, on crosshair line)
419 effectnum = particleeffectnum(argv(1));
421 start = (1-f) * self.origin + f * self.cursor_trace_endpos;
424 pointparticles(effectnum, start, end, f);
427 sprint(self, "Usage: sv_cheats 1; restart; cmd pointparticles effectname position(0..1) velocityvector multiplier\n");
428 } else if(cmd == "trailparticles") {
429 if((sv_cheats || self.maycheat) && tokens == 2)
433 effectnum = particleeffectnum(argv(1));
434 W_SetupShot(self, FALSE, FALSE, "",0);
435 traceline(w_shotorg, w_shotorg + w_shotdir * MAX_SHOT_DISTANCE, MOVE_NORMAL, self);
436 trailparticles(self, effectnum, w_shotorg, trace_endpos);
439 sprint(self, "Usage: sv_cheats 1; restart; cmd trailparticles effectname\n");
440 } else if(cmd == "make") {
441 if((sv_cheats || self.maycheat) && tokens == 3)
446 W_SetupShot(self, FALSE, FALSE, "", 0);
447 traceline(w_shotorg, w_shotorg + w_shotdir * 2048, MOVE_NORMAL, self);
448 if((trace_dphitq3surfaceflags & Q3SURFACEFLAG_NOIMPACT) || trace_fraction == 1)
450 sprint(self, "cannot make stuff there (bad surface)\n");
456 self.model = strzone(argv(1));
457 self.mdl = "rocket_explode";
459 setorigin(self, trace_endpos);
460 self.effects = EF_NOMODELFLAGS;
463 self.angles = fixedvectoangles2(trace_plane_normal, v_forward);
464 self.angles = AnglesTransform_Multiply(self.angles, '-90 0 0'); // so unrotated models work
466 spawnfunc_func_breakable();
470 tracebox(self.origin, self.mins, self.maxs, self.origin, MOVE_NORMAL, self);
473 sprint(oldself, "cannot make stuff there (no space)\n");
481 sprint(self, "Usage: sv_cheats 1; restart; cmd make models/... 0/1/2\n");
482 } else if(cmd == "penalty") {
483 if((sv_cheats || self.maycheat) && tokens == 3)
484 race_ImposePenaltyTime(self, stof(argv(1)), argv(2));
486 sprint(self, "Usage: sv_cheats 1; restart; cmd penalty 5.0 AHAHAHAHAHAHAH))\n");
487 } else if(cmd == "dragbox_spawn") {
488 if(sv_cheats || self.maycheat)
491 e.classname = "dragbox_box";
492 e.think = DragBox_Think;
494 e.solid = -1; // black
495 setmodel(e, "null"); // network it
497 e.cnt = stof(argv(1));
499 e.cnt = max(0, drag_lastcnt);
502 e.aiment.classname = "dragbox_corner_1";
504 setmodel(e.aiment, "models/marker.md3");
506 setsize(e.aiment, '0 0 0', '0 0 0');
508 setorigin(e.aiment, stov(argv(2)));
510 setorigin(e.aiment, self.cursor_trace_endpos);
513 e.enemy.classname = "dragbox_corner_2";
515 setmodel(e.enemy, "models/marker.md3");
517 setsize(e.enemy, '0 0 0', '0 0 0');
518 end = normalize(self.cursor_trace_start - e.aiment.origin);
519 end_x = (end_x > 0) * 2 - 1;
520 end_y = (end_y > 0) * 2 - 1;
521 end_z = (end_z > 0) * 2 - 1;
523 setorigin(e.enemy, stov(argv(3)));
525 setorigin(e.enemy, e.aiment.origin + 32 * end);
527 e.killindicator = spawn();
528 e.killindicator.classname = "drag_digit";
529 e.killindicator.owner = e;
530 setattachment(e.killindicator, e, "");
531 setorigin(e.killindicator, '0 0 -8');
532 e.killindicator.killindicator = spawn();
533 e.killindicator.killindicator.classname = "drag_digit";
534 e.killindicator.killindicator.owner = e;
535 setattachment(e.killindicator.killindicator, e, "");
536 setorigin(e.killindicator.killindicator, '0 0 8');
539 sprint(self, "Usage: sv_cheats 1; r_showbboxes 1.5; restart; cmd dragbox_spawn\n");
540 } else if(cmd == "dragpoint_spawn") {
541 if(sv_cheats || self.maycheat)
544 e.classname = "dragpoint";
545 e.think = DragBox_Think;
547 e.solid = 0; // nothing special
548 setmodel(e, "models/marker.md3");
549 setsize(e, PL_MIN, PL_MAX);
552 e.cnt = stof(argv(1));
554 e.cnt = drag_lastcnt;
556 setorigin(e, stov(argv(2)));
559 setorigin(e, self.cursor_trace_endpos + normalize(self.cursor_trace_start - self.cursor_trace_endpos));
560 move_out_of_solid(e);
563 e.killindicator = spawn();
564 e.killindicator.classname = "drag_digit";
565 e.killindicator.owner = e;
566 setattachment(e.killindicator, e, "");
567 setorigin(e.killindicator, '0 0 40');
568 e.killindicator.killindicator = spawn();
569 e.killindicator.killindicator.classname = "drag_digit";
570 e.killindicator.killindicator.owner = e;
571 setattachment(e.killindicator.killindicator, e, "");
572 setorigin(e.killindicator.killindicator, '0 0 56');
575 sprint(self, "Usage: sv_cheats 1; r_showbboxes 1.5; restart; cmd dragbox_spawn\n");
576 } else if(cmd == "drag_remove") {
577 if(sv_cheats || self.maycheat)
579 RandomSelection_Init();
580 for(e = world; (e = find(e, classname, "dragbox_box")); )
581 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
582 for(e = world; (e = find(e, classname, "dragpoint")); )
583 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
584 if(RandomSelection_chosen_ent)
586 remove(RandomSelection_chosen_ent.killindicator.killindicator);
587 remove(RandomSelection_chosen_ent.killindicator);
588 if(RandomSelection_chosen_ent.aiment)
589 remove(RandomSelection_chosen_ent.aiment);
590 if(RandomSelection_chosen_ent.enemy)
591 remove(RandomSelection_chosen_ent.enemy);
592 remove(RandomSelection_chosen_ent);
596 sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_remove\n");
597 } else if(cmd == "drag_setcnt") {
598 if((sv_cheats || self.maycheat) && tokens >= 2)
600 RandomSelection_Init();
601 for(e = world; (e = find(e, classname, "dragbox_box")); )
602 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
603 for(e = world; (e = find(e, classname, "dragpoint")); )
604 RandomSelection_Add(e, 0, string_null, 1, 1 / vlen(e.origin + (e.mins + e.maxs) * 0.5 - self.cursor_trace_endpos));
605 if(RandomSelection_chosen_ent)
607 if(substring(argv(1), 0, 1) == "*")
608 RandomSelection_chosen_ent.cnt = drag_lastcnt = RandomSelection_chosen_ent.cnt + stof(substring(argv(1), 1, -1));
610 RandomSelection_chosen_ent.cnt = drag_lastcnt = stof(argv(1));
614 sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_setcnt cnt\n");
615 } else if(cmd == "drag_save") {
616 if((sv_cheats || self.maycheat) && tokens >= 2)
618 f = fopen(argv(1), FILE_WRITE);
619 fputs(f, "cmd drag_clear\n");
620 for(e = world; (e = find(e, classname, "dragbox_box")); )
622 fputs(f, strcat("cmd dragbox_spawn ", ftos(e.cnt), " \"", vtos(e.aiment.origin), "\" \"", vtos(e.enemy.origin), "\"\n"));
624 for(e = world; (e = find(e, classname, "dragpoint")); )
626 fputs(f, strcat("cmd dragpoint_spawn ", ftos(e.cnt), " \"", vtos(e.origin), "\"\n"));
631 sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_save filename\n");
632 } else if(cmd == "drag_saveraceent") {
633 if((sv_cheats || self.maycheat) && tokens >= 2)
635 f = fopen(argv(1), FILE_WRITE);
636 for(e = world; (e = find(e, classname, "dragbox_box")); )
639 fputs(f, "\"classname\" \"trigger_race_checkpoint\"\n");
640 fputs(f, strcat("\"origin\" \"", ftos(e.absmin_x), " ", ftos(e.absmin_y), " ", ftos(e.absmin_z), "\"\n"));
641 fputs(f, strcat("\"maxs\" \"", ftos(e.absmax_x - e.absmin_x), " ", ftos(e.absmax_y - e.absmin_y), " ", ftos(e.absmax_z - e.absmin_z), "\"\n"));
642 fputs(f, strcat("\"cnt\" \"", ftos(e.cnt), "\"\n"));
643 fputs(f, strcat("\"targetname\" \"checkpoint", ftos(e.cnt), "\"\n"));
646 for(e = world; (e = find(e, classname, "dragpoint")); )
650 for(oldself = world; (oldself = find(oldself, classname, "dragbox_box")); )
652 if(e.cnt <= 0 && oldself.cnt == 0 || e.cnt == oldself.cnt)
654 start = start + oldself.origin;
658 start *= 1 / effectnum;
660 fputs(f, "\"classname\" \"info_player_race\"\n");
661 fputs(f, strcat("\"angle\" \"", ftos(vectoyaw(start - e.origin)), "\"\n"));
662 fputs(f, strcat("\"origin\" \"", ftos(e.origin_x), " ", ftos(e.origin_y), " ", ftos(e.origin_z), "\"\n"));
665 fputs(f, "\"target\" \"checkpoint0\"\n");
666 fputs(f, "\"race_place\" \"0\"\n");
670 fputs(f, "\"target\" \"checkpoint0\"\n");
671 fputs(f, "\"race_place\" \"-1\"\n");
675 fputs(f, strcat("\"target\" \"checkpoint", ftos(e.cnt), "\"\n"));
678 // these need race_place
681 for(oldself = world; (oldself = find(oldself, classname, "dragpoint")); )
684 if(vlen(oldself.origin - start) < vlen(e.origin - start))
686 else if(vlen(oldself.origin - start) == vlen(e.origin - start) && num_for_edict(oldself) < num_for_edict(e))
689 fputs(f, strcat("\"race_place\" \"", ftos(effectnum), "\"\n"));
697 sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_save filename\n");
698 } else if(cmd == "drag_clear") {
699 if(sv_cheats || self.maycheat)
701 for(e = world; (e = find(e, classname, "dragbox_box")); )
703 for(e = world; (e = find(e, classname, "dragbox_corner_1")); )
705 for(e = world; (e = find(e, classname, "dragbox_corner_2")); )
707 for(e = world; (e = find(e, classname, "dragpoint")); )
709 for(e = world; (e = find(e, classname, "drag_digit")); )
713 sprint(self, "Usage: sv_cheats 1; restart; cmd dragbox_clear\n");
715 //if(ctf_clientcommand())
717 // grep for Cmd_AddCommand_WithClientCommand to find them all
725 if(cmd != "notarget")
729 //if(cmd != "say") // handled above
730 //if(cmd != "say_team") // handled above
738 if(cmd != "playermodel")
739 if(cmd != "playerskin")
740 if(cmd != "prespawn")
744 if(cmd != "sv_startdownload")
745 if(cmd != "download")
747 print("WARNING: Invalid clientcommand by ", self.netname, ": ", s, "\n");
751 if(self.jointime > 0 && time > self.jointime + 10 && time > self.nickspamtime) // allow any changes in the first 10 seconds since joining
752 if(cmd == "name" || cmd == "playermodel") // TODO also playerskin and color?
754 if(self.nickspamtime == 0 || time > self.nickspamtime + cvar("g_nick_flood_timeout"))
755 // good, no serious flood
756 self.nickspamcount = 1;
758 self.nickspamcount += 1;
759 self.nickspamtime = time + cvar("g_nick_flood_penalty");
761 if (timeoutStatus == 2) //when game is paused, no flood protection
762 self.nickspamcount = self.nickspamtime = 0;
765 clientcommand(self,s);
769 void ReadyRestartForce()
773 bprint("^1Server is restarting...\n");
778 if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
779 //we have to decrease timelimit to its original value again!!
781 newTL = cvar("timelimit");
782 newTL -= checkrules_overtimesadded * cvar("timelimit_overtime");
783 cvar_set("timelimit", ftos(newTL));
786 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
789 readyrestart_happened = 1;
790 game_starttime = time;
792 game_starttime += RESTART_COUNTDOWN;
793 restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
795 inWarmupStage = 0; //once the game is restarted the game is in match stage
797 //reset the .ready status of all players (also spectators)
798 FOR_EACH_CLIENTSLOT(e)
801 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
803 if(cvar("teamplay_lockonrestart") && teams_matter) {
805 bprint("^1The teams are now locked.\n");
808 //initiate the restart-countdown-announcer entity
809 if(cvar("sv_ready_restart_after_countdown"))
811 restartTimer = spawn();
812 restartTimer.think = restartTimer_Think;
813 restartTimer.nextthink = game_starttime;
816 //after a restart every players number of allowed timeouts gets reset, too
817 if(cvar("sv_timeout"))
819 FOR_EACH_REALPLAYER(e)
820 e.allowedTimeouts = cvar("sv_timeout_number");
823 //reset map immediately if this cvar is not set
824 if (!cvar("sv_ready_restart_after_countdown"))
827 if(cvar("sv_eventlog"))
828 GameLogEcho(":restart");
833 // no arena, assault support yet...
834 if(g_arena | g_assault | gameover | intermission_running | race_completing)
835 localcmd("restart\n");
837 localcmd("\nsv_hook_gamerestart;");
841 // reset ALL scores, but only do that at the beginning
842 //of the countdown if sv_ready_restart_after_countdown is off!
843 //Otherwise scores could be manipulated during the countdown!
844 if (!cvar("sv_ready_restart_after_countdown"))
849 * Counts how many players are ready. If not enough players are ready, the function
850 * does nothing. If all players are ready, the timelimit will be extended and the
851 * restart_countdown variable is set to allow other functions like PlayerPostThink
852 * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
853 * is not set the map will be resetted.
855 * Function is called after the server receives a 'ready' sign from a player.
864 FOR_EACH_REALPLAYER(e)
873 Nagger_ReadyCounted();
875 if(r) // at least one is ready
876 if(r == p) // and, everyone is ready
881 * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
884 void restartTimer_Think() {
885 restart_mapalreadyrestarted = 1;
893 * Checks whether the player who calls the timeout is allowed to do so.
894 * If so, it initializes the timeout countdown. It also checks whether another
895 * timeout was already running at this time and reacts correspondingly.
897 * affected globals/fields: .allowedTimeouts, remainingTimeoutTime, remainingLeadTime,
898 * timeoutInitiator, timeoutStatus, timeoutHandler
900 * This function is called when a player issues the calltimeout command.
902 void evaluateTimeout() {
903 if (inWarmupStage && !g_warmup_allow_timeout)
904 return sprint(self, "^7Error: You can not call a timeout in warmup-stage!\n");
905 if (time < game_starttime )
906 return sprint(self, "^7Error: You can not call a timeout while the map is being restarted!\n");
907 if (timeoutStatus != 2) {
908 //if the map uses a timelimit make sure that timeout cannot be called right before the map ends
909 if (cvar("timelimit")) {
910 //a timelimit was used
912 myTl = cvar("timelimit");
914 local float lastPossibleTimeout;
915 lastPossibleTimeout = (myTl*60) - cvar("sv_timeout_leadtime") - 1;
917 if (lastPossibleTimeout < time - game_starttime)
918 return sprint(self, "^7Error: It is too late to call a timeout now!\n");
921 //player may not call a timeout if he has no calls left
922 if (self.allowedTimeouts < 1)
923 return sprint(self, "^7Error: You already used all your timeout calls for this map!\n");
924 //now all required checks are passed
925 self.allowedTimeouts -= 1;
926 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)
927 remainingTimeoutTime = cvar("sv_timeout_length");
928 remainingLeadTime = cvar("sv_timeout_leadtime");
929 timeoutInitiator = self;
930 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
932 //create the timeout indicator which centerprints the information to all players and takes care of pausing/unpausing
933 timeoutHandler = spawn();
934 timeoutHandler.think = timeoutHandler_Think;
936 timeoutHandler.nextthink = time; //always let the entity think asap
938 //inform all connected clients about the timeout call
939 play2all("announcer/robotic/timeoutcalled.wav");
943 * Checks whether a player is allowed to resume the game. If he is allowed to do it,
944 * and the lead time for the timeout is still active, this countdown just will be aborted (the
945 * game will never be paused). Otherwise the remainingTimeoutTime will be set to the corresponding
946 * value of the cvar sv_timeout_resumetime.
948 * This function is called when a player issues the resumegame command.
950 void evaluateTimein() {
952 return sprint(self, "^7Error: There is no active timeout which could be aborted!\n");
953 if (self != timeoutInitiator)
954 return sprint(self, "^7Error: You may not abort the active timeout. Only the player who called it can do that!\n");
955 if (timeoutStatus == 1) {
956 remainingTimeoutTime = timeoutStatus = 0;
957 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
958 bprint(strcat("^7The timeout was aborted by ", self.netname, " !\n"));
960 else if (timeoutStatus == 2) {
961 //only shorten the remainingTimeoutTime if it makes sense
962 if( remainingTimeoutTime > (cvar("sv_timeout_resumetime") + 1) ) {
963 bprint(strcat("^1Attention: ^7", self.netname, " resumed the game! Prepare for battle!\n"));
964 remainingTimeoutTime = cvar("sv_timeout_resumetime");
965 timeoutHandler.nextthink = time; //timeoutHandler has to take care of it immediately
968 sprint(self, "^7Error: Your resumegame call was discarded!\n");