1 float Nagger_SendEntity(entity to, float sendflags)
5 WriteByte(MSG_ENTITY, ENT_CLIENT_NAGGER);
9 // 2 = player needs to ready up
11 // 8 = player needs to vote
39 if(!(nags & 4)) // no vote called? send no string
42 WriteByte(MSG_ENTITY, nags);
46 WriteByte(MSG_ENTITY, vote_yescount);
47 WriteByte(MSG_ENTITY, vote_nocount);
48 WriteByte(MSG_ENTITY, vote_needed_absolute);
49 WriteChar(MSG_ENTITY, to.vote_vote);
53 WriteString(MSG_ENTITY, votecalledvote_display);
57 for(i = 1; i <= maxclients; i += 8)
59 for(f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e))
60 if(clienttype(e) != CLIENTTYPE_REAL || e.ready)
62 WriteByte(MSG_ENTITY, f);
70 Net_LinkEntity(nagger = spawn(), FALSE, 0, Nagger_SendEntity);
72 void Nagger_VoteChanged()
75 nagger.SendFlags |= 128;
77 void Nagger_VoteCountChanged()
80 nagger.SendFlags |= 64;
82 void Nagger_ReadyCounted()
85 nagger.SendFlags |= 1;
88 void ReadyRestartForce()
92 bprint("^1Server is restarting...\n");
97 if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) {
98 //we have to decrease timelimit to its original value again!!
100 newTL = autocvar_timelimit;
101 newTL -= checkrules_overtimesadded * autocvar_timelimit_overtime;
102 cvar_set("timelimit", ftos(newTL));
105 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0;
108 readyrestart_happened = 1;
109 game_starttime = time;
110 if(!g_ca && !g_arena)
111 game_starttime += RESTART_COUNTDOWN;
112 restart_mapalreadyrestarted = 0; //reset this var, needed when cvar sv_ready_restart_repeatable is in use
114 inWarmupStage = 0; //once the game is restarted the game is in match stage
116 //reset the .ready status of all players (also spectators)
117 FOR_EACH_CLIENTSLOT(e)
120 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client
122 if(autocvar_teamplay_lockonrestart && teamplay) {
124 bprint("^1The teams are now locked.\n");
127 //initiate the restart-countdown-announcer entity
128 if(autocvar_sv_ready_restart_after_countdown && !g_ca && !g_arena)
130 restartTimer = spawn();
131 restartTimer.think = restartTimer_Think;
132 restartTimer.nextthink = game_starttime;
135 //after a restart every players number of allowed timeouts gets reset, too
136 if(autocvar_sv_timeout)
138 FOR_EACH_REALPLAYER(e)
139 e.allowedTimeouts = autocvar_sv_timeout_number;
142 //reset map immediately if this cvar is not set
143 if (!autocvar_sv_ready_restart_after_countdown)
146 if(autocvar_sv_eventlog)
147 GameLogEcho(":restart");
152 // no arena, assault support yet...
153 if(g_arena | g_assault | gameover | intermission_running | race_completing)
154 localcmd("restart\n");
156 localcmd("\nsv_hook_gamerestart\n");
160 // reset ALL scores, but only do that at the beginning
161 //of the countdown if sv_ready_restart_after_countdown is off!
162 //Otherwise scores could be manipulated during the countdown!
163 if (!autocvar_sv_ready_restart_after_countdown)
168 * Counts how many players are ready. If not enough players are ready, the function
169 * does nothing. If all players are ready, the timelimit will be extended and the
170 * restart_countdown variable is set to allow other functions like PlayerPostThink
171 * to detect that the countdown is now active. If the cvar sv_ready_restart_after_countdown
172 * is not set the map will be resetted.
174 * Function is called after the server receives a 'ready' sign from a player.
183 FOR_EACH_REALPLAYER(e)
192 Nagger_ReadyCounted();
194 if(r) // at least one is ready
195 if(r == p) // and, everyone is ready
200 * Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown
203 void restartTimer_Think() {
204 restart_mapalreadyrestarted = 1;
211 float VoteCheckNasty(string cmd)
213 if(strstrofs(cmd, ";", 0) >= 0)
215 if(strstrofs(cmd, "\n", 0) >= 0)
217 if(strstrofs(cmd, "\r", 0) >= 0)
219 if(strstrofs(cmd, "$", 0) >= 0)
224 string GetKickVoteVictim_newcommand;
225 string GetKickVoteVictim_reason;
227 entity GetKickVoteVictim(string vote, string cmd, entity caller)
234 tokens = tokenize_console(vote);
237 e = GetCommandPlayerSlotTargetFromTokenizedCommand(tokens, 1);
240 if(ParseCommandPlayerSlotTarget_firsttoken < tokens)
241 GetKickVoteVictim_reason = substring(vote, argv_start_index(ParseCommandPlayerSlotTarget_firsttoken), argv_end_index(-1) - argv_start_index(ParseCommandPlayerSlotTarget_firsttoken));
243 GetKickVoteVictim_reason = "";
246 if(cmd != "vdo" || GetKickVoteVictim_reason == "")
247 reason = "~"; // by convention, ~ prefixes a "unverified" kickban which will not be networked
249 if(substring(GetKickVoteVictim_reason, 0, 1) == "~")
252 GetKickVoteVictim_reason = substring(GetKickVoteVictim_reason, 1, strlen(GetKickVoteVictim_reason) - 1);
256 reason = strcat(reason, "player ", strdecolorize(caller.netname));
258 reason = strcat(reason, "console vote");
259 if(GetKickVoteVictim_reason != "")
260 reason = strcat(reason, ": ", strdecolorize(GetKickVoteVictim_reason));
262 if not(cvar_value_issafe(reason))
263 reason = uri_escape(reason);
265 GetKickVoteVictim_newcommand = strcat(argv(0), " # ", ftos(num_for_edict(e)));
266 if(argv(0) == "kickban")
268 GetKickVoteVictim_newcommand = strcat(GetKickVoteVictim_newcommand, " ", ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ", reason);
270 else if(argv(0) == "kick")
272 GetKickVoteVictim_newcommand = strcat(GetKickVoteVictim_newcommand, " ", reason);
277 print_to(caller, strcat("Usage: ", cmd, " ", argv(0), " #playernumber (as in \"status\")\n"));
281 string RemapVote_display;
282 string RemapVote_vote;
283 float RemapVote(string vote, string cmd, entity e)
287 vote_argc = tokenize_console(vote);
289 if(!VoteAllowed(argv(0), cmd))
292 // VoteAllowed tokenizes!
293 vote_argc = tokenize_console(vote);
295 // remap chmap to gotomap (forces intermission)
297 if(argv(0) == "chmap" || argv(0) == "gotomap" || argv(0) == "kick" || argv(0) == "kickban") // won't work without arguments
299 if(argv(0) == "chmap")
301 vote = strcat("gotomap ", substring(vote, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)));
302 vote_argc = tokenize_console(vote);
304 if(argv(0) == "gotomap")
306 if(!(vote = ValidateMap(substring(vote, argv_start_index(1), argv_end_index(-1) - argv_start_index(1)), e)))
308 vote = strcat("gotomap ", vote);
309 vote_argc = tokenize_console(vote); // ValidateMap may have done some stuff to it
312 // make kick and kickban votes a bit nicer (and reject them if formatted badly)
313 if(argv(0) == "kick" || argv(0) == "kickban")
315 if(!(victim = GetKickVoteVictim(vote, cmd, e)))
317 RemapVote_vote = GetKickVoteVictim_newcommand;
318 RemapVote_display = strcat("^1", vote, " (^7", victim.netname, "^1): ", GetKickVoteVictim_reason);
322 RemapVote_vote = vote;
323 RemapVote_display = strzone(strcat("^1", vote));
329 float GameCommand_Vote(string s, entity e) {
332 argc = tokenize_console(s);
333 if(argv(0) == "help") {
334 print_to(e, " vote COMMANDS ARGUMENTS. See 'vhelp' for more info.");
336 } else if(argv(0) == "vote") {
338 print_to(e, "^1You have to supply a vote command. See 'vhelp' for more info.");
339 } else if(argv(1) == "help") {
341 } else if(argv(1) == "status") {
343 print_to(e, strcat("^7Vote for ", votecalledvote_display, "^7 called by ^7", VoteNetname(votecaller), "^7."));
345 print_to(e, "^1No vote called.");
347 } else if(argv(1) == "call") {
348 if(!e || autocvar_sv_vote_call) {
349 if(autocvar_sv_vote_nospectators && e && e.classname != "player") {
350 print_to(e, "^1Error: Only players can call a vote."); // TODO invent a cvar name for allowing votes by spectators during warmup anyway
352 else if(timeoutStatus) { //don't allow a vote call during a timeout
353 print_to(e, "^1Error: You can not call a vote while a timeout is active.");
355 else if(votecalled) {
356 print_to(e, "^1There is already a vote called.");
359 vote = VoteParse(s, argc);
361 print_to(e, "^1Your vote is empty. See 'vhelp' for more info.");
363 && time < e.vote_next) {
364 print_to(e, strcat("^1You have to wait ^2", ftos(ceil(e.vote_next - time)), "^1 seconds before you can again call a vote."));
365 } else if(VoteCheckNasty(vote)) {
366 print_to(e, "Syntax error in command. See 'vhelp' for more info.");
367 } else if(RemapVote(vote, "vcall", e)) {
368 votecalledvote = strzone(RemapVote_vote);
369 votecalledvote_display = strzone(RemapVote_display);
371 votecalledmaster = FALSE;
372 votefinished = time + autocvar_sv_vote_timeout;
373 votecaller = e; // remember who called the vote
375 e.vote_vote = 1; // of course you vote yes
376 e.vote_next = time + autocvar_sv_vote_wait;
378 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote for ", votecalledvote_display, "\n");
379 if(autocvar_sv_eventlog)
380 GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
381 Nagger_VoteChanged();
382 VoteCount(); // needed if you are the only one
386 FOR_EACH_REALCLIENT(player)
390 if(playercount > 1) // don't announce a "vote now" sound if player is alone
391 Announce("votecall");
393 print_to(e, "^1This vote is not ok. See 'vhelp' for more info.");
397 print_to(e, "^1Vote calling is NOT allowed.");
399 } else if(argv(1) == "stop") {
401 print_to(e, "^1No vote called.");
402 } else if(e == votecaller) { // the votecaller can stop a vote
404 } else if(!e) { // server admin / console can too
406 } else if(e.vote_master) { // masters can too
409 print_to(e, "^1You are not allowed to stop that Vote.");
411 } else if(argv(1) == "master") {
412 if(autocvar_sv_vote_master) {
414 print_to(e, "^1There is already a vote called.");
417 votecalledmaster = TRUE;
418 votecalledvote = strzone("XXX");
419 votecalledvote_display = strzone("^3master");
420 votefinished = time + autocvar_sv_vote_timeout;
421 votecaller = e; // remember who called the vote
423 e.vote_vote = 1; // of course you vote yes
424 e.vote_next = time + autocvar_sv_vote_wait;
426 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2 calls a vote to become ^3master^2.\n");
427 if(autocvar_sv_eventlog)
428 GameLogEcho(strcat(":vote:vcall:", ftos(votecaller.playerid), ":", votecalledvote_display));
429 Nagger_VoteChanged();
430 VoteCount(); // needed if you are the only one
433 print_to(e, "^1Vote to become master is NOT allowed.");
435 } else if(argv(1) == "do") {
436 if(!e || e.vote_master) {
438 dovote = VoteParse(s, argc);
440 print_to(e, "^1Your command was empty. See 'vhelp' for more info.");
441 } else if(VoteCheckNasty(dovote)) {
442 print_to(e, "Syntax error in command. See 'vhelp' for more info.");
443 } else if(RemapVote(dovote, "vdo", e)) { // strcat seems to be necessary
444 bprint("\{1}^2* ^3", VoteNetname(e), "^2 used their ^3master^2 status to do \"^2", RemapVote_display, "^2\".\n");
445 if(autocvar_sv_eventlog)
446 GameLogEcho(strcat(":vote:vdo:", ftos(e.playerid), ":", RemapVote_display));
447 localcmd(strcat(RemapVote_vote, "\n"));
449 print_to(e, "^1This command is not ok. See 'vhelp' for more info.");
452 print_to(e, "^1You are NOT a master. You might need to login or vote to become master first. See 'vhelp' for more info.");
454 } else if(argv(1) == "login") {
456 masterpwd = autocvar_sv_vote_master_password;
457 if(masterpwd != "") {
459 granted = (masterpwd == argv(2));
461 e.vote_master = granted;
463 print("Accepted master login from ", VoteNetname(e), "\n");
464 bprint("\{1}^2* ^3", VoteNetname(e), "^2 logged in as ^3master^2\n");
465 if(autocvar_sv_eventlog)
466 GameLogEcho(strcat(":vote:vlogin:", ftos(e.playerid)));
469 print("REJECTED master login from ", VoteNetname(e), "\n");
472 print_to(e, "^1Login to become master is NOT allowed.");
473 } else if(argv(1) == "yes") {
475 print_to(e, "^1No vote called.");
477 print_to(e, "^1You can't vote from the server console.");
478 } else if(e.vote_vote == 0
479 || autocvar_sv_vote_change) {
481 print_to(e, "^1You accepted the vote.");
483 if(!autocvar_sv_vote_singlecount) {
487 print_to(e, "^1You have already voted.");
489 } else if(argv(1) == "no") {
491 print_to(e, "^1No vote called.");
493 print_to(e, "^1You can't vote from the server console.");
494 } else if(e.vote_vote == 0
495 || autocvar_sv_vote_change) {
497 print_to(e, "^1You rejected the vote.");
499 if(!autocvar_sv_vote_singlecount) {
503 print_to(e, "^1You have already voted.");
505 } else if(argv(1) == "abstain" || argv(1) == "dontcare") {
507 print_to(e, "^1No vote called.");
509 print_to(e, "^1You can't vote from the server console.");
510 } else if(e.vote_vote == 0
511 || autocvar_sv_vote_change) {
513 print_to(e, "^1You abstained from your vote.");
515 if(!autocvar_sv_vote_singlecount) {
519 print_to(e, "^1You have already voted.");
523 print_to(e, "^1Unknown vote command.");
530 void VoteHelp(entity e) {
532 if(!autocvar_sv_vote_master) {
533 vmasterdis = " ^1(disabled)";
537 if("" == autocvar_sv_vote_master_password) {
538 vlogindis = " ^1(disabled)";
542 if(!autocvar_sv_vote_call) {
543 vcalldis = " ^1(disabled)";
546 print_to(e, "^7You can use voting with \"^2cmd vote help^7\" \"^2cmd vote status^7\" \"^2cmd vote call ^3COMMAND ARGUMENTS^7\" \"^2cmd vote stop^7\" \"^2cmd vote master^7\" \"^2cmd vote login^7\" \"^2cmd vote do ^3COMMAND ARGUMENTS^7\" \"^2cmd vote yes^7\" \"^2cmd vote no^7\" \"^2cmd vote abstain^7\" \"^2cmd vote dontcare^7\".");
547 print_to(e, "^7Or if your version is up to date you can use these aliases \"^2vhelp^7\" \"^2vstatus^7\" \"^2vcall ^3COMMAND ARGUMENTS^7\" \"^2vstop^7\" \"^2vmaster^7\" \"^2vlogin^7\" \"^2vdo ^3COMMAND ARGUMENTS^7\" \"^2vyes^7\" \"^2vno^7\" \"^2abstain^7\" \"^2vdontcare^7\".");
548 print_to(e, "^7\"^2help^7\" shows this info.");
549 print_to(e, "^7\"^2status^7\" shows if there is a vote called and who called it.");
550 print_to(e, strcat("^7\"^2call^7\" is used to call a vote. See the list of allowed commands.", vcalldis, "^7"));
551 print_to(e, "^7\"^2stop^7\" can be used by the vote caller or an admin to stop a vote and maybe correct it.");
552 print_to(e, strcat("^7\"^2master^7\" call a vote to become master who can execute commands without a vote", vmasterdis, "^7"));
553 print_to(e, strcat("^7\"^2login^7\" login to become master who can execute commands without a vote.", vlogindis, "^7"));
554 print_to(e, "^7\"^2do^7\" executes a command if you are a master. See the list of allowed commands.");
555 print_to(e, "^7\"^2yes^7\", \"^2no^7\", \"^2abstain^7\" and \"^2dontcare^7\" to make your vote.");
556 print_to(e, "^7If enough of the players vote yes the vote is accepted.");
557 print_to(e, "^7If enough of the players vote no the vote is rejected.");
558 print_to(e, strcat("^7If neither the vote will timeout after ", ftos(autocvar_sv_vote_timeout), "^7 seconds."));
559 print_to(e, "^7You can call a vote for or execute these commands:");
560 print_to(e, strcat("^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7"));
563 string VoteNetname(entity e)
568 if(autocvar_sv_adminnick != "") {
569 return autocvar_sv_adminnick;
571 return autocvar_hostname;
576 string ValidateMap(string m, entity e)
578 m = MapInfo_FixName(m);
581 print_to(e, "This map is not available on this server.");
584 if(!autocvar_sv_vote_override_mostrecent)
587 print_to(e, "This server does not allow for recent maps to be played again. Please be patient for some rounds.");
590 if(!MapInfo_CheckMap(m))
592 print_to(e, strcat("^1Invalid mapname, \"^3", m, "^1\" does not support the current game mode."));
601 if(votefinished > 0) // a vote was called
602 if(time > votefinished) // time is up
608 string VoteParse(string all, float argc) {
611 return substring(all, argv_start_index(2), argv_end_index(-1) - argv_start_index(2));
614 float VoteCommandInList(string votecommand, string list)
617 l = strcat(" ", list, " ");
619 if(strstrofs(l, strcat(" ", votecommand, " "), 0) >= 0)
622 // if gotomap is allowed, chmap is too, and vice versa
623 if(votecommand == "gotomap")
624 if(strstrofs(l, " chmap ", 0) >= 0)
626 if(votecommand == "chmap")
627 if(strstrofs(l, " gotomap ", 0) >= 0)
633 float VoteAllowed(string votecommand, string cmd) {
634 if(VoteCommandInList(votecommand, autocvar_sv_vote_commands))
639 if(VoteCommandInList(votecommand, autocvar_sv_vote_master_commands))
644 if(VoteCommandInList(votecommand, autocvar_sv_vote_only_commands))
654 FOR_EACH_CLIENT(player)
656 player.vote_vote = 0;
661 strunzone(votecalledvote);
662 strunzone(votecalledvote_display);
666 votecalledmaster = FALSE;
668 votecalledvote = string_null;
669 votecalledvote_display = string_null;
671 Nagger_VoteChanged();
675 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ^1", votecalledvote_display, "^2 was accepted\n");
679 votecaller.vote_master = 1;
682 localcmd(strcat(votecalledvote, "\n"));
685 votecaller.vote_next = 0; // people like your votes,
686 // no wait for next vote
689 Announce("voteaccept");
693 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 was rejected\n");
695 Announce("votefail");
699 bprint("\{1}^2* ^3", VoteNetname(votecaller), "^2's vote for ", votecalledvote_display, "^2 timed out\n");
701 Announce("votefail");
704 void VoteStop(entity stopper) {
705 bprint("\{1}^2* ^3", VoteNetname(stopper), "^2 stopped ^3", VoteNetname(votecaller), "^2's vote\n");
706 if(autocvar_sv_eventlog)
707 GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid)));
708 if(stopper == votecaller) {
709 // no wait for next vote so you can correct your vote
711 votecaller.vote_next = time + autocvar_sv_vote_stop;
717 void VoteSpam(float notvoters, float mincount, string result)
722 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
723 s = strcat(s, ftos(vote_nocount), "^2 (^1");
724 s = strcat(s, ftos(mincount), "^2 needed), ^1");
725 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
726 s = strcat(s, ftos(notvoters), "^2 didn't vote\n");
730 s = strcat("\{1}^2* vote results: ^1", ftos(vote_yescount), "^2:^1");
731 s = strcat(s, ftos(vote_nocount), "^2, ^1");
732 s = strcat(s, ftos(vote_abstaincount), "^2 didn't care, ^1");
733 s = strcat(s, ftos(notvoters), "^2 didn't have to vote\n");
736 if(autocvar_sv_eventlog)
738 s = strcat(":vote:v", result, ":", ftos(vote_yescount));
739 s = strcat(s, ":", ftos(vote_nocount));
740 s = strcat(s, ":", ftos(vote_abstaincount));
741 s = strcat(s, ":", ftos(notvoters));
742 s = strcat(s, ":", ftos(mincount));
752 vote_abstaincount = 0;
754 //same for real players
755 float realplayercount;
756 float realplayeryescount;
757 float realplayernocount;
758 float realplayerabstaincount;
759 realplayercount = realplayernocount = realplayerabstaincount = realplayeryescount = 0;
761 Nagger_VoteCountChanged();
763 FOR_EACH_REALCLIENT(player)
765 if(player.vote_vote == -1) {
767 } else if(player.vote_vote == 1) {
769 } else if(player.vote_vote == -2) {
773 //do the same for real players
774 if(player.classname == "player") {
775 if(player.vote_vote == -1) {
777 } else if(player.vote_vote == 1) {
778 ++realplayeryescount;
779 } else if(player.vote_vote == -2) {
780 ++realplayerabstaincount;
786 //in tournament mode, if we have at least one player then don't make the vote dependent on spectators (so specs don't have to press F1)
787 if(autocvar_sv_vote_nospectators)
788 if(realplayercount > 0) {
789 vote_yescount = realplayeryescount;
790 vote_nocount = realplayernocount;
791 vote_abstaincount = realplayerabstaincount;
792 playercount = realplayercount;
795 float votefactor, simplevotefactor;
796 votefactor = bound(0.5, autocvar_sv_vote_majority_factor, 0.999);
797 simplevotefactor = autocvar_sv_vote_simple_majority_factor;
799 // FIXME this number is a guess
800 vote_needed_absolute = floor((playercount - vote_abstaincount) * votefactor) + 1;
803 simplevotefactor = bound(votefactor, simplevotefactor, 0.999);
804 vote_needed_simple = floor((vote_yescount + vote_nocount) * simplevotefactor) + 1;
807 vote_needed_simple = 0;
810 && playercount == 1) {
811 // if only one player is on the server becoming vote
812 // master is not allowed. This could be used for
813 // trolling or worse. 'self' is the user who has
814 // called the vote because this function is called
815 // by SV_ParseClientCommand. Maybe all voting should
816 // be disabled for a single player?
817 print_to(votecaller, "^1You are the only player on this server so you can not become vote master.");
819 votecaller.vote_next = 0;
823 if(vote_yescount >= vote_needed_absolute)
825 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "yes");
828 else if(vote_nocount > playercount - vote_abstaincount - vote_needed_absolute) // that means, vote_yescount cannot reach vote_needed_absolute any more
830 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, -1, "no");
833 else if(time > votefinished)
838 if(vote_yescount >= vote_needed_simple)
840 else if(vote_yescount + vote_nocount > 0)
844 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, min(vote_needed_absolute, vote_needed_simple), result);
847 else if(result == "no")
854 VoteSpam(playercount - vote_yescount - vote_nocount - vote_abstaincount, vote_needed_absolute, "timeout");