1#include "vote.qh" 2#include <common/command/_mod.qh> 3#include "vote.qh" 4 5#include "common.qh" 6 7#include "../g_damage.qh" 8#include "../g_world.qh" 9#include "../race.qh" 10#include "../round_handler.qh" 11#include "../scores.qh" 12 13#include "../mutators/_mod.qh" 14 15#include <common/constants.qh> 16#include <common/net_linked.qh> 17#include <common/mapinfo.qh> 18#include <common/notifications/all.qh> 19#include <common/playerstats.qh> 20#include <common/util.qh> 21 22// ============================================= 23// Server side voting code, reworked by Samual 24// Last updated: December 27th, 2011 25// ============================================= 26 27// Nagger for players to know status of voting 28bool Nagger_SendEntity(entity this, entity to, float sendflags) 29{ 30 int nags, i, f, b; 31 entity e; 32 WriteHeader(MSG_ENTITY, ENT_CLIENT_NAGGER); 33 34 // bits: 35 // 1 = ready 36 // 2 = player needs to ready up 37 // 4 = vote 38 // 8 = player needs to vote 39 // 16 = warmup 40 // sendflags: 41 // 64 = vote counts 42 // 128 = vote string 43 44 nags = 0; 45 if (readycount) 46 { 47 nags |= BIT(0); 48 if (to.ready == 0) nags |= BIT(1); 49 } 50 if (vote_called) 51 { 52 nags |= BIT(2); 53 if (to.vote_selection == 0) nags |= BIT(3); 54 } 55 if (warmup_stage) nags |= BIT(4); 56 57 if (sendflags & BIT(6)) nags |= BIT(6); 58 59 if (sendflags & BIT(7)) nags |= BIT(7); 60 61 if (!(nags & 4)) // no vote called? send no string 62 nags &= ~(BIT(6) | BIT(7)); 63 64 WriteByte(MSG_ENTITY, nags); 65 66 if (nags & BIT(6)) 67 { 68 WriteByte(MSG_ENTITY, vote_accept_count); 69 WriteByte(MSG_ENTITY, vote_reject_count); 70 WriteByte(MSG_ENTITY, vote_needed_overall); 71 WriteChar(MSG_ENTITY, to.vote_selection); 72 } 73 74 if (nags & BIT(7)) WriteString(MSG_ENTITY, vote_called_display); 75 76 if (nags & 1) 77 { 78 for (i = 1; i <= maxclients; i += 8) 79 { 80 for (f = 0, e = edict_num(i), b = 1; b < 256; b *= 2, e = nextent(e)) 81 if (!IS_REAL_CLIENT(e) || e.ready) f |= b; 82 WriteByte(MSG_ENTITY, f); 83 } 84 } 85 86 return true; 87} 88 89void Nagger_Init() 90{ 91 Net_LinkEntity(nagger = new_pure(nagger), false, 0, Nagger_SendEntity); 92} 93 94void Nagger_VoteChanged() 95{ 96 if (nagger) nagger.SendFlags |= BIT(7); 97} 98 99void Nagger_VoteCountChanged() 100{ 101 if (nagger) nagger.SendFlags |= BIT(6); 102} 103 104void Nagger_ReadyCounted() 105{ 106 if (nagger) nagger.SendFlags |= BIT(0); 107} 108 109// If the vote_caller is still here, return their name, otherwise vote_caller_name 110string OriginalCallerName() 111{ 112 if (IS_REAL_CLIENT(vote_caller)) return playername(vote_caller, false); 113 return vote_caller_name; 114} 115 116// ======================= 117// Game logic for voting 118// ======================= 119 120void VoteReset() 121{ 122 FOREACH_CLIENT(true, LAMBDA(it.vote_selection = 0)); 123 124 if (vote_called) 125 { 126 strunzone(vote_called_command); 127 strunzone(vote_called_display); 128 strunzone(vote_caller_name); 129 } 130 131 vote_called = VOTE_NULL; 132 vote_caller = NULL; 133 vote_caller_name = string_null; 134 vote_endtime = 0; 135 136 vote_called_command = string_null; 137 vote_called_display = string_null; 138 139 vote_parsed_command = string_null; 140 vote_parsed_display = string_null; 141 142 Nagger_VoteChanged(); 143} 144 145void VoteStop(entity stopper) 146{ 147 bprint("\{1}^2* ^3", GetCallerName(stopper), "^2 stopped ^3", OriginalCallerName(), "^2's vote\n"); 148 if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vstop:", ftos(stopper.playerid))); 149 // Don't force them to wait for next vote, this way they can e.g. correct their vote. 150 if ((vote_caller) && (stopper == vote_caller)) vote_caller.vote_waittime = time + autocvar_sv_vote_stop; 151 VoteReset(); 152} 153 154void VoteAccept() 155{ 156 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ^1", vote_called_display, "^2 was accepted\n"); 157 158 if ((vote_called == VOTE_MASTER) && vote_caller) vote_caller.vote_master = 1; 159 else localcmd(strcat(vote_called_command, "\n")); 160 161 if (vote_caller) vote_caller.vote_waittime = 0; // people like your votes, you don't need to wait to vote again 162 163 VoteReset(); 164 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_ACCEPT); 165} 166 167void VoteReject() 168{ 169 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 was rejected\n"); 170 VoteReset(); 171 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL); 172} 173 174void VoteTimeout() 175{ 176 bprint("\{1}^2* ^3", OriginalCallerName(), "^2's vote for ", vote_called_display, "^2 timed out\n"); 177 VoteReset(); 178 Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_FAIL); 179} 180 181void VoteSpam(float notvoters, float mincount, string result) 182{ 183 bprint(strcat( 184 strcat("\{1}^2* vote results: ^1", ftos(vote_accept_count)), 185 strcat("^2:^1", ftos(vote_reject_count)), 186 ((mincount >= 0) ? strcat("^2 (^1", ftos(mincount), "^2 needed)") : "^2"), 187 strcat(", ^1", ftos(vote_abstain_count), "^2 didn't care"), 188 strcat(", ^1", ftos(notvoters), strcat("^2 didn't ", ((mincount >= 0) ? "" : "have to "), "vote\n")))); 189 190 if (autocvar_sv_eventlog) 191 { 192 GameLogEcho(strcat( 193 strcat(":vote:v", result, ":", ftos(vote_accept_count)), 194 strcat(":", ftos(vote_reject_count)), 195 strcat(":", ftos(vote_abstain_count)), 196 strcat(":", ftos(notvoters)), 197 strcat(":", ftos(mincount)))); 198 } 199} 200 201#define spectators_allowed (!autocvar_sv_vote_nospectators || (autocvar_sv_vote_nospectators == 1 && (warmup_stage || intermission_running))) 202 203void VoteCount(float first_count) 204{ 205 // declarations 206 vote_accept_count = vote_reject_count = vote_abstain_count = 0; 207 208 float vote_player_count = 0, notvoters = 0; 209 float vote_real_player_count = 0, vote_real_accept_count = 0; 210 float vote_real_reject_count = 0, vote_real_abstain_count = 0; 211 float vote_needed_of_voted, final_needed_votes; 212 float vote_factor_overall, vote_factor_of_voted; 213 214 Nagger_VoteCountChanged(); 215 216 // add up all the votes from each connected client 217 FOREACH_CLIENT(IS_REAL_CLIENT(it) && IS_CLIENT(it), LAMBDA( 218 ++vote_player_count; 219 if (IS_PLAYER(it)) ++vote_real_player_count; 220 switch (it.vote_selection) 221 { 222 case VOTE_SELECT_REJECT: 223 { ++vote_reject_count; 224 { if (IS_PLAYER(it)) ++vote_real_reject_count; } break; 225 } 226 case VOTE_SELECT_ACCEPT: 227 { ++vote_accept_count; 228 { if (IS_PLAYER(it)) ++vote_real_accept_count; } break; 229 } 230 case VOTE_SELECT_ABSTAIN: 231 { ++vote_abstain_count; 232 { if (IS_PLAYER(it)) ++vote_real_abstain_count; } break; 233 } 234 default: break; 235 } 236 )); 237 238 // Check to see if there are enough players on the server to allow master voting... otherwise, vote master could be used for evil. 239 if ((vote_called == VOTE_MASTER) && autocvar_sv_vote_master_playerlimit > vote_player_count) 240 { 241 if (vote_caller) vote_caller.vote_waittime = 0; 242 print_to(vote_caller, "^1There are not enough players on this server to allow you to become vote master."); 243 VoteReset(); 244 return; 245 } 246 247 // if spectators aren't allowed to vote and there are players in a match, then only count the players in the vote and ignore spectators. 248 if (!spectators_allowed && (vote_real_player_count > 0)) 249 { 250 vote_accept_count = vote_real_accept_count; 251 vote_reject_count = vote_real_reject_count; 252 vote_abstain_count = vote_real_abstain_count; 253 vote_player_count = vote_real_player_count; 254 } 255 256 // people who have no opinion in any way :D 257 notvoters = (vote_player_count - vote_accept_count - vote_reject_count - vote_abstain_count); 258 259 // determine the goal for the vote to be passed or rejected normally 260 vote_factor_overall = bound(0.5, autocvar_sv_vote_majority_factor, 0.999); 261 vote_needed_overall = floor((vote_player_count - vote_abstain_count) * vote_factor_overall) + 1; 262 263 // if the vote times out, determine the amount of votes needed of the people who actually already voted 264 vote_factor_of_voted = bound(0.5, autocvar_sv_vote_majority_factor_of_voted, 0.999); 265 vote_needed_of_voted = floor((vote_accept_count + vote_reject_count) * vote_factor_of_voted) + 1; 266 267 // are there any players at all on the server? it could be an admin vote 268 if (vote_player_count == 0 && first_count) 269 { 270 VoteSpam(0, -1, "yes"); // no players at all, just accept it 271 VoteAccept(); 272 return; 273 } 274 275 // since there ARE players, finally calculate the result of the vote 276 if (vote_accept_count >= vote_needed_overall) 277 { 278 VoteSpam(notvoters, -1, "yes"); // there is enough acceptions to pass the vote 279 VoteAccept(); 280 return; 281 } 282 283 if (vote_reject_count > vote_player_count - vote_abstain_count - vote_needed_overall) 284 { 285 VoteSpam(notvoters, -1, "no"); // there is enough rejections to deny the vote 286 VoteReject(); 287 return; 288 } 289 290 // there is not enough votes in either direction, now lets just calculate what the voters have said 291 if (time > vote_endtime) 292 { 293 final_needed_votes = vote_needed_overall; 294 295 if (autocvar_sv_vote_majority_factor_of_voted) 296 { 297 if (vote_accept_count >= vote_needed_of_voted) 298 { 299 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "yes"); 300 VoteAccept(); 301 return; 302 } 303 304 if (vote_accept_count + vote_reject_count > 0) 305 { 306 VoteSpam(notvoters, min(vote_needed_overall, vote_needed_of_voted), "no"); 307 VoteReject(); 308 return; 309 } 310 311 final_needed_votes = min(vote_needed_overall, vote_needed_of_voted); 312 } 313 314 // it didn't pass or fail, so not enough votes to even make a decision. 315 VoteSpam(notvoters, final_needed_votes, "timeout"); 316 VoteTimeout(); 317 } 318} 319 320void VoteThink() 321{ 322 if (vote_endtime > 0) // a vote was called 323 { 324 if (time > vote_endtime) // time is up 325 VoteCount(false); 326 } 327} 328 329 330// ======================= 331// Game logic for warmup 332// ======================= 333 334// Resets the state of all clients, items, weapons, waypoints, ... of the map. 335void reset_map(bool dorespawn) 336{ 337 if (time <= game_starttime) 338 { 339 if (game_stopped) 340 return; 341 if (round_handler_IsActive()) 342 round_handler_Reset(game_starttime); 343 } 344 345 MUTATOR_CALLHOOK(reset_map_global); 346 347 FOREACH_ENTITY_ORDERED(IS_NOT_A_CLIENT(it), { 348 if (it.reset) 349 { 350 it.reset(it); 351 continue; 352 } 353 if (it.team_saved) it.team = it.team_saved; 354 if (it.flags & FL_PROJECTILE) delete(it); // remove any projectiles left 355 }); 356 357 // Waypoints and assault start come LAST 358 FOREACH_ENTITY_ORDERED(IS_NOT_A_CLIENT(it), { 359 if (it.reset2) it.reset2(it); 360 }); 361 362 FOREACH_CLIENT(IS_PLAYER(it) && STAT(FROZEN, it), LAMBDA(Unfreeze(it))); 363 364 // Moving the player reset code here since the player-reset depends 365 // on spawnpoint entities which have to be reset first --blub 366 if (dorespawn) 367 { 368 if (!MUTATOR_CALLHOOK(reset_map_players)) 369 { 370 if (restart_mapalreadyrestarted || (time < game_starttime)) 371 { 372 FOREACH_CLIENT(IS_PLAYER(it), 373 { 374 /* 375 only reset players if a restart countdown is active 376 this can either be due to cvar sv_ready_restart_after_countdown having set 377 restart_mapalreadyrestarted to 1 after the countdown ended or when 378 sv_ready_restart_after_countdown is not used and countdown is still running 379 */ 380 // NEW: changed behaviour so that it prevents that previous spectators/observers suddenly spawn as players 381 // PlayerScore_Clear(it); 382 it.killcount = 0; 383 // stop the player from moving so that he stands still once he gets respawned 384 it.velocity = '0 0 0'; 385 it.avelocity = '0 0 0'; 386 it.movement = '0 0 0'; 387 PutClientInServer(it); 388 }); 389 } 390 } 391 } 392} 393 394// Restarts the map after the countdown is over (and cvar sv_ready_restart_after_countdown is set) 395void ReadyRestart_think(entity this) 396{ 397 restart_mapalreadyrestarted = true; 398 reset_map(true); 399 Score_ClearAll(); 400 delete(this); 401} 402 403// Forces a restart of the game without actually reloading the map // this is a mess... 404void ReadyRestart_force() 405{ 406 if (time <= game_starttime && game_stopped) 407 return; 408 409 bprint("^1Server is restarting...\n"); 410 411 VoteReset(); 412 413 // clear overtime, we have to decrease timelimit to its original value again. 414 if (checkrules_overtimesadded > 0 && g_race_qualifying != 2) 415 cvar_set("timelimit", ftos(autocvar_timelimit - (checkrules_overtimesadded * autocvar_timelimit_overtime))); 416 checkrules_suddendeathend = checkrules_overtimesadded = checkrules_suddendeathwarning = 0; 417 418 readyrestart_happened = true; 419 game_starttime = time + RESTART_COUNTDOWN; 420 421 // clear player attributes 422 FOREACH_CLIENT(IS_PLAYER(it), LAMBDA( 423 it.alivetime = 0; 424 it.killcount = 0; 425 PS_GR_P_ADDVAL(it, PLAYERSTATS_ALIVETIME, -PS_GR_P_ADDVAL(it, PLAYERSTATS_ALIVETIME, 0)); 426 )); 427 428 restart_mapalreadyrestarted = false; // reset this var, needed when cvar sv_ready_restart_repeatable is in use 429 430 // disable the warmup global for the server 431 warmup_stage = 0; // once the game is restarted the game is in match stage 432 433 // reset the .ready status of all players (also spectators) 434 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(it.ready = false)); 435 readycount = 0; 436 Nagger_ReadyCounted(); // NOTE: this causes a resend of that entity, and will also turn off warmup state on the client 437 438 // lock teams with lockonrestart 439 if (autocvar_teamplay_lockonrestart && teamplay) 440 { 441 lockteams = true; 442 bprint("^1The teams are now locked.\n"); 443 } 444 445 // initiate the restart-countdown-announcer entity 446 if (autocvar_sv_ready_restart_after_countdown) 447 { 448 entity restart_timer = new_pure(restart_timer); 449 setthink(restart_timer, ReadyRestart_think); 450 restart_timer.nextthink = game_starttime; 451 } 452 453 // after a restart every players number of allowed timeouts gets reset, too 454 if (autocvar_sv_timeout) 455 { 456 FOREACH_CLIENT(IS_PLAYER(it) && IS_REAL_CLIENT(it), LAMBDA(it.allowed_timeouts = autocvar_sv_timeout_number)); 457 } 458 // reset map immediately if this cvar is not set 459 if (!autocvar_sv_ready_restart_after_countdown) reset_map(true); 460 if (autocvar_sv_eventlog) GameLogEcho(":restart"); 461} 462 463void ReadyRestart() 464{ 465 if (MUTATOR_CALLHOOK(ReadyRestart_Deny) || game_stopped || race_completing) localcmd("restart\n"); 466 else localcmd("\nsv_hook_gamerestart\n"); 467 468 // Reset ALL scores, but only do that at the beginning of the countdown if sv_ready_restart_after_countdown is off! 469 // Otherwise scores could be manipulated during the countdown. 470 if (!autocvar_sv_ready_restart_after_countdown) Score_ClearAll(); 471 ReadyRestart_force(); 472} 473 474// Count the players who are ready and determine whether or not to restart the match 475void ReadyCount() 476{ 477 float ready_needed_factor, ready_needed_count; 478 float t_ready = 0, t_players = 0; 479 480 FOREACH_CLIENT(IS_REAL_CLIENT(it) && (IS_PLAYER(it) || it.caplayer == 1), LAMBDA( 481 ++t_players; 482 if (it.ready) ++t_ready; 483 )); 484 485 readycount = t_ready; 486 487 Nagger_ReadyCounted(); 488 489 ready_needed_factor = bound(0.5, cvar("g_warmup_majority_factor"), 0.999); 490 ready_needed_count = floor(t_players * ready_needed_factor) + 1; 491 492 if (readycount >= ready_needed_count) ReadyRestart(); 493} 494 495 496// ====================================== 497// Supporting functions for VoteCommand 498// ====================================== 499 500float Votecommand_check_assignment(entity caller, float assignment) 501{ 502 float from_server = (!caller); 503 504 if ((assignment == VC_ASGNMNT_BOTH) 505 || ((!from_server && assignment == VC_ASGNMNT_CLIENTONLY) 506 || (from_server && assignment == VC_ASGNMNT_SERVERONLY))) return true; 507 508 return false; 509} 510 511string VoteCommand_extractcommand(string input, float startpos, float argc) 512{ 513 string output; 514 515 if ((argc - 1) < startpos) output = ""; 516 else output = substring(input, argv_start_index(startpos), argv_end_index(-1) - argv_start_index(startpos)); 517 518 return output; 519} 520 521float VoteCommand_checknasty(string vote_command) 522{ 523 if ((strstrofs(vote_command, ";", 0) >= 0) 524 || (strstrofs(vote_command, "\n", 0) >= 0) 525 || (strstrofs(vote_command, "\r", 0) >= 0) 526 || (strstrofs(vote_command, "$", 0) >= 0)) return false; 527 528 return true; 529} 530 531float VoteCommand_checkinlist(string vote_command, string list) 532{ 533 string l = strcat(" ", list, " "); 534 535 if (strstrofs(l, strcat(" ", vote_command, " "), 0) >= 0) return true; 536 537 return false; 538} 539 540string ValidateMap(string validated_map, entity caller) 541{ 542 validated_map = MapInfo_FixName(validated_map); 543 544 if (!validated_map) 545 { 546 print_to(caller, "This map is not available on this server."); 547 return string_null; 548 } 549 550 if (!autocvar_sv_vote_override_mostrecent && caller) 551 { 552 if (Map_IsRecent(validated_map)) 553 { 554 print_to(caller, "This server does not allow for recent maps to be played again. Please be patient for some rounds."); 555 return string_null; 556 } 557 } 558 559 if (!MapInfo_CheckMap(validated_map)) 560 { 561 print_to(caller, strcat("^1Invalid mapname, \"^3", validated_map, "^1\" does not support the current game mode.")); 562 return string_null; 563 } 564 565 return validated_map; 566} 567 568float VoteCommand_checkargs(float startpos, float argc) 569{ 570 float p, q, check, minargs; 571 string cvarname = strcat("sv_vote_command_restriction_", argv(startpos)); 572 string cmdrestriction = ""; // No we don't. 573 string charlist, arg; 574 float checkmate; 575 576 if(cvar_type(cvarname) & CVAR_TYPEFLAG_EXISTS) 577 cmdrestriction = cvar_string(cvarname); 578 else 579 LOG_INFO("NOTE: ", cvarname, " does not exist, no restrictions will be applied.\n"); 580 581 if (cmdrestriction == "") return true; 582 583 ++startpos; // skip command name 584 585 // check minimum arg count 586 587 // 0 args: argc == startpos 588 // 1 args: argc == startpos + 1 589 // ... 590 591 minargs = stof(cmdrestriction); 592 if (argc - startpos < minargs) return false; 593 594 p = strstrofs(cmdrestriction, ";", 0); // find first semicolon 595 596 for ( ; ; ) 597 { 598 // we know that at any time, startpos <= argc - minargs 599 // so this means: argc-minargs >= startpos >= argc, thus 600 // argc-minargs >= argc, thus minargs <= 0, thus all minargs 601 // have been seen already 602 603 if (startpos >= argc) // all args checked? GOOD 604 break; 605 606 if (p < 0) // no more args? FAIL 607 { 608 // exception: exactly minargs left, this one included 609 if (argc - startpos == minargs) break; 610 611 // otherwise fail 612 return false; 613 } 614 615 // cut to next semicolon 616 q = strstrofs(cmdrestriction, ";", p + 1); // find next semicolon 617 if (q < 0) charlist = substring(cmdrestriction, p + 1, -1); 618 else charlist = substring(cmdrestriction, p + 1, q - (p + 1)); 619 620 // in case we ever want to allow semicolons in VoteCommand_checknasty 621 // charlist = strreplace("^^", ";", charlist); 622 623 if (charlist != "") 624 { 625 // verify the arg only contains allowed chars 626 arg = argv(startpos); 627 checkmate = strlen(arg); 628 for (check = 0; check < checkmate; ++check) 629 if (strstrofs(charlist, substring(arg, check, 1), 0) < 0) return false; 630 // not allowed character 631 // all characters are allowed. FINE. 632 } 633 634 ++startpos; 635 --minargs; 636 p = q; 637 } 638 639 return true; 640} 641 642float VoteCommand_parse(entity caller, string vote_command, string vote_list, float startpos, float argc) 643{ 644 string first_command; 645 646 first_command = argv(startpos); 647 648 /*printf("VoteCommand_parse(): Command: '%s', Length: %f.\n", 649 substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos)), 650 strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos))) 651 );*/ 652 653 if ( 654 (autocvar_sv_vote_limit > 0) 655 && 656 (strlen(substring(vote_command, argv_start_index(startpos), strlen(vote_command) - argv_start_index(startpos))) > autocvar_sv_vote_limit) 657 ) return false; 658 659 if (!VoteCommand_checkinlist(first_command, vote_list)) return false; 660 661 if (!VoteCommand_checkargs(startpos, argc)) return false; 662 663 switch (first_command) // now go through and parse the proper commands to adjust as needed. 664 { 665 case "kick": 666 case "kickban": // catch all kick/kickban commands 667 { 668 entity victim = GetIndexedEntity(argc, (startpos + 1)); 669 float accepted = VerifyClientEntity(victim, true, false); 670 671 if (accepted > 0) 672 { 673 string reason = ((argc > next_token) ? substring(vote_command, argv_start_index(next_token), strlen(vote_command) - argv_start_index(next_token)) : "No reason provided"); 674 string command_arguments; 675 676 if (first_command == "kickban") command_arguments = strcat(ftos(autocvar_g_ban_default_bantime), " ", ftos(autocvar_g_ban_default_masksize), " ~"); 677 else command_arguments = reason; 678 679 vote_parsed_command = strcat(first_command, " # ", ftos(etof(victim)), " ", command_arguments); 680 vote_parsed_display = strcat("^1", vote_command, " (^7", victim.netname, "^1): ", reason); 681 } 682 else { print_to(caller, strcat("vcall: ", GetClientErrorString(accepted, argv(startpos + 1)), ".\n")); return false; } 683 684 break; 685 } 686 687 case "map": 688 case "chmap": 689 case "gotomap": // re-direct all map selection commands to gotomap 690 { 691 vote_command = ValidateMap(argv(startpos + 1), caller); 692 if (!vote_command) return false; 693 vote_parsed_command = strcat("gotomap ", vote_command); 694 vote_parsed_display = strzone(strcat("^1", vote_parsed_command)); 695 696 break; 697 } 698 699 default: 700 { 701 vote_parsed_command = vote_command; 702 vote_parsed_display = strzone(strcat("^1", vote_command)); 703 704 break; 705 } 706 } 707 708 return true; 709} 710 711 712// ======================= 713// Command Sub-Functions 714// ======================= 715 716void VoteCommand_abstain(float request, entity caller) // CLIENT ONLY 717{ 718 switch (request) 719 { 720 case CMD_REQUEST_COMMAND: 721 { 722 if (!vote_called) { print_to(caller, "^1No vote called."); } 723 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change) 724 { 725 print_to(caller, "^1You have already voted."); 726 } 727 728 else // everything went okay, continue changing vote 729 { 730 print_to(caller, "^1You abstained from your vote."); 731 caller.vote_selection = VOTE_SELECT_ABSTAIN; 732 msg_entity = caller; 733 if (!autocvar_sv_vote_singlecount) VoteCount(false); } 734 735 return; 736 } 737 738 default: 739 case CMD_REQUEST_USAGE: 740 { 741 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote abstain")); 742 print_to(caller, " No arguments required."); 743 return; 744 } 745 } 746} 747 748void VoteCommand_call(float request, entity caller, float argc, string vote_command) // BOTH 749{ 750 switch (request) 751 { 752 case CMD_REQUEST_COMMAND: 753 { 754 float tmp_playercount = 0; 755 756 vote_command = VoteCommand_extractcommand(vote_command, 2, argc); 757 758 if (!autocvar_sv_vote_call && caller) { print_to(caller, "^1Vote calling is not allowed."); } 759 else if (!autocvar_sv_vote_gamestart && time < game_starttime) 760 { 761 print_to(caller, "^1Vote calling is not allowed before the match has started."); 762 } 763 else if (vote_called) 764 { 765 print_to(caller, "^1There is already a vote called."); 766 } 767 else if (!spectators_allowed && (caller && !IS_PLAYER(caller))) 768 { 769 print_to(caller, "^1Only players can call a vote."); 770 } 771 else if (caller && !IS_CLIENT(caller)) 772 { 773 print_to(caller, "^1Only connected clients can vote."); 774 } 775 else if (timeout_status) 776 { 777 print_to(caller, "^1You can not call a vote while a timeout is active."); 778 } 779 else if (caller && (time < caller.vote_waittime)) 780 { 781 print_to(caller, strcat("^1You have to wait ^2", ftos(ceil(caller.vote_waittime - time)), "^1 seconds before you can again call a vote.")); 782 } 783 else if (!VoteCommand_checknasty(vote_command)) 784 { 785 print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); 786 } 787 else if (!VoteCommand_parse(caller, vote_command, autocvar_sv_vote_commands, 2, argc)) 788 { 789 print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); 790 } 791 792 else // everything went okay, continue with calling the vote 793 { 794 vote_caller = caller; // remember who called the vote 795 vote_caller_name = strzone(GetCallerName(vote_caller)); 796 vote_called = VOTE_NORMAL; 797 vote_called_command = strzone(vote_parsed_command); 798 vote_called_display = strzone(vote_parsed_display); 799 vote_endtime = time + autocvar_sv_vote_timeout; 800 801 if (caller) 802 { 803 caller.vote_selection = VOTE_SELECT_ACCEPT; 804 caller.vote_waittime = time + autocvar_sv_vote_wait; 805 msg_entity = caller; 806 } 807 808 FOREACH_CLIENT(IS_REAL_CLIENT(it), LAMBDA(++tmp_playercount)); 809 if (tmp_playercount > 1) Send_Notification(NOTIF_ALL, NULL, MSG_ANNCE, ANNCE_VOTE_CALL); // don't announce a "vote now" sound if player is alone 810 811 bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote for ", vote_called_display, "\n"); 812 if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display)); 813 Nagger_VoteChanged(); 814 VoteCount(true); // needed if you are the only one 815 } 816 817 return; 818 } 819 820 default: 821 case CMD_REQUEST_USAGE: 822 { 823 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote call command")); 824 print_to(caller, " Where 'command' is the command to request a vote upon."); 825 print_to(caller, strcat("Examples: ", GetCommandPrefix(caller), " vote call gotomap dance")); 826 print_to(caller, strcat(" ", GetCommandPrefix(caller), " vote call endmatch")); 827 return; 828 } 829 } 830} 831 832void VoteCommand_master(float request, entity caller, float argc, string vote_command) // CLIENT ONLY 833{ 834 switch (request) 835 { 836 case CMD_REQUEST_COMMAND: 837 { 838 if (autocvar_sv_vote_master) 839 { 840 switch (strtolower(argv(2))) 841 { 842 case "do": 843 { 844 vote_command = VoteCommand_extractcommand(vote_command, 3, argc); 845 846 if (!caller.vote_master) { print_to(caller, "^1You do not have vote master privelages."); } 847 else if (!VoteCommand_checknasty(vote_command)) 848 { 849 print_to(caller, "^1Syntax error in command, see 'vhelp' for more info."); 850 } 851 else if (!VoteCommand_parse(caller, vote_command, strcat(autocvar_sv_vote_commands, " ", autocvar_sv_vote_master_commands), 3, argc)) 852 { 853 print_to(caller, "^1This command is not acceptable, see 'vhelp' for more info."); 854 } 855 856 else // everything went okay, proceed with command 857 { 858 localcmd(strcat(vote_parsed_command, "\n")); 859 print_to(caller, strcat("Executing command '", vote_parsed_display, "' on server.")); 860 bprint("\{1}^2* ^3", GetCallerName(caller), "^2 used their ^3master^2 status to do \"^2", vote_parsed_display, "^2\".\n"); 861 if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vdo:", ftos(caller.playerid), ":", vote_parsed_display)); } 862 863 return; 864 } 865 866 case "login": 867 { 868 if (autocvar_sv_vote_master_password == "") { print_to(caller, "^1Login to vote master is not allowed."); } 869 else if (caller.vote_master) 870 { 871 print_to(caller, "^1You are already logged in as vote master."); 872 } 873 else if (autocvar_sv_vote_master_password != argv(3)) 874 { 875 print_to(caller, strcat("Rejected vote master login from ", GetCallerName(caller))); 876 } 877 878 else // everything went okay, proceed with giving this player master privilages 879 { 880 caller.vote_master = true; 881 print_to(caller, strcat("Accepted vote master login from ", GetCallerName(caller))); 882 bprint("\{1}^2* ^3", GetCallerName(caller), "^2 logged in as ^3master^2\n"); 883 if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vlogin:", ftos(caller.playerid))); } 884 885 return; 886 } 887 888 default: // calling a vote for master 889 { 890 if (!autocvar_sv_vote_master_callable) { print_to(caller, "^1Vote to become vote master is not allowed."); } 891 else if (vote_called) 892 { 893 print_to(caller, "^1There is already a vote called."); 894 } 895 else if (!spectators_allowed && (caller && !IS_PLAYER(caller))) 896 { 897 print_to(caller, "^1Only players can call a vote."); 898 } 899 else if (timeout_status) 900 { 901 print_to(caller, "^1You can not call a vote while a timeout is active."); 902 } 903 904 else // everything went okay, continue with creating vote 905 { 906 vote_caller = caller; 907 vote_caller_name = strzone(GetCallerName(vote_caller)); 908 vote_called = VOTE_MASTER; 909 vote_called_command = strzone("XXX"); 910 vote_called_display = strzone("^3master"); 911 vote_endtime = time + autocvar_sv_vote_timeout; 912 913 caller.vote_selection = VOTE_SELECT_ACCEPT; 914 caller.vote_waittime = time + autocvar_sv_vote_wait; 915 916 bprint("\{1}^2* ^3", OriginalCallerName(), "^2 calls a vote to become ^3master^2.\n"); 917 if (autocvar_sv_eventlog) GameLogEcho(strcat(":vote:vcall:", ftos(vote_caller.playerid), ":", vote_called_display)); 918 Nagger_VoteChanged(); 919 VoteCount(true); // needed if you are the only one 920 } 921 922 return; 923 } 924 } 925 } 926 else { print_to(caller, "^1Master control of voting is not allowed."); } 927 928 return; 929 } 930 931 default: 932 case CMD_REQUEST_USAGE: 933 { 934 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote master [action [command | password]]")); 935 print_to(caller, " If action is left blank, it calls a vote for you to become master."); 936 print_to(caller, " Otherwise the actions are either 'do' a command or 'login' as master."); 937 return; 938 } 939 } 940} 941 942void VoteCommand_no(float request, entity caller) // CLIENT ONLY 943{ 944 switch (request) 945 { 946 case CMD_REQUEST_COMMAND: 947 { 948 if (!vote_called) { print_to(caller, "^1No vote called."); } 949 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change) 950 { 951 print_to(caller, "^1You have already voted."); 952 } 953 else if (((caller == vote_caller) || caller.vote_master) && autocvar_sv_vote_no_stops_vote) 954 { 955 VoteStop(caller); 956 } 957 958 else // everything went okay, continue changing vote 959 { 960 print_to(caller, "^1You rejected the vote."); 961 caller.vote_selection = VOTE_SELECT_REJECT; 962 msg_entity = caller; 963 if (!autocvar_sv_vote_singlecount) VoteCount(false); } 964 965 return; 966 } 967 968 default: 969 case CMD_REQUEST_USAGE: 970 { 971 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote no")); 972 print_to(caller, " No arguments required."); 973 return; 974 } 975 } 976} 977 978void VoteCommand_status(float request, entity caller) // BOTH 979{ 980 switch (request) 981 { 982 case CMD_REQUEST_COMMAND: 983 { 984 if (vote_called) print_to(caller, strcat("^7Vote for ", vote_called_display, "^7 called by ^7", OriginalCallerName(), "^7.")); 985 else print_to(caller, "^1No vote called."); 986 987 return; 988 } 989 990 default: 991 case CMD_REQUEST_USAGE: 992 { 993 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote status")); 994 print_to(caller, " No arguments required."); 995 return; 996 } 997 } 998} 999 1000void VoteCommand_stop(float request, entity caller) // BOTH 1001{ 1002 switch (request) 1003 { 1004 case CMD_REQUEST_COMMAND: 1005 { 1006 if (!vote_called) print_to(caller, "^1No vote called."); 1007 else if ((caller == vote_caller) || !caller || caller.vote_master) VoteStop(caller); 1008 else print_to(caller, "^1You are not allowed to stop that vote."); 1009 return; 1010 } 1011 1012 default: 1013 case CMD_REQUEST_USAGE: 1014 { 1015 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote stop")); 1016 print_to(caller, " No arguments required."); 1017 return; 1018 } 1019 } 1020} 1021 1022void VoteCommand_yes(float request, entity caller) // CLIENT ONLY 1023{ 1024 switch (request) 1025 { 1026 case CMD_REQUEST_COMMAND: 1027 { 1028 if (!vote_called) { print_to(caller, "^1No vote called."); } 1029 else if (caller.vote_selection != VOTE_SELECT_NULL && !autocvar_sv_vote_change) 1030 { 1031 print_to(caller, "^1You have already voted."); 1032 } 1033 1034 else // everything went okay, continue changing vote 1035 { 1036 print_to(caller, "^1You accepted the vote."); 1037 caller.vote_selection = VOTE_SELECT_ACCEPT; 1038 msg_entity = caller; 1039 if (!autocvar_sv_vote_singlecount) VoteCount(false); } 1040 1041 return; 1042 } 1043 1044 default: 1045 case CMD_REQUEST_USAGE: 1046 { 1047 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote yes")); 1048 print_to(caller, " No arguments required."); 1049 return; 1050 } 1051 } 1052} 1053 1054/* use this when creating a new command, making sure to place it in alphabetical order... also, 1055** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION! 1056void VoteCommand_(float request) 1057{ 1058 switch(request) 1059 { 1060 case CMD_REQUEST_COMMAND: 1061 { 1062 1063 return; 1064 } 1065 1066 default: 1067 case CMD_REQUEST_USAGE: 1068 { 1069 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " vote "); 1070 print_to(caller, " No arguments required."); 1071 return; 1072 } 1073 } 1074} 1075*/ 1076 1077 1078// ================================ 1079// Macro system for vote commands 1080// ================================ 1081 1082// Do not hard code aliases for these, instead create them in commands.cfg... also: keep in alphabetical order, please ;) 1083#define VOTE_COMMANDS(request, caller, arguments, command) \ 1084 VOTE_COMMAND("abstain", VoteCommand_abstain(request, caller), "Abstain your vote in current vote", VC_ASGNMNT_CLIENTONLY) \ 1085 VOTE_COMMAND("call", VoteCommand_call(request, caller, arguments, command), "Create a new vote for players to decide on", VC_ASGNMNT_BOTH) \ 1086 VOTE_COMMAND("help", VoteCommand_macro_help(caller, arguments), "Shows this information", VC_ASGNMNT_BOTH) \ 1087 VOTE_COMMAND("master", VoteCommand_master(request, caller, arguments, command), "Full control over all voting and vote commands", VC_ASGNMNT_CLIENTONLY) \ 1088 VOTE_COMMAND("no", VoteCommand_no(request, caller), "Select no in current vote", VC_ASGNMNT_CLIENTONLY) \ 1089 VOTE_COMMAND("status", VoteCommand_status(request, caller), "Prints information about current vote", VC_ASGNMNT_BOTH) \ 1090 VOTE_COMMAND("stop", VoteCommand_stop(request, caller), "Immediately end a vote", VC_ASGNMNT_BOTH) \ 1091 VOTE_COMMAND("yes", VoteCommand_yes(request, caller), "Select yes in current vote", VC_ASGNMNT_CLIENTONLY) \ 1092 /* nothing */ 1093 1094void VoteCommand_macro_help(entity caller, float argc) 1095{ 1096 string command_origin = GetCommandPrefix(caller); 1097 1098 if (argc == 2 || argv(2) == "help") // help display listing all commands 1099 { 1100 print_to(caller, "\nVoting commands:\n"); 1101 #define VOTE_COMMAND(name, function, description, assignment) \ 1102 { if (Votecommand_check_assignment(caller, assignment)) { print_to(caller, strcat(" ^2", name, "^7: ", description)); } } 1103 1104 VOTE_COMMANDS(0, caller, 0, ""); 1105#undef VOTE_COMMAND 1106 1107 print_to(caller, strcat("\nUsage:^3 ", command_origin, " vote COMMAND...^7, where possible commands are listed above.\n")); 1108 print_to(caller, strcat("For help about a specific command, type ", command_origin, " vote help COMMAND")); 1109 print_to(caller, strcat("\n^7You can call a vote for or execute these commands: ^3", autocvar_sv_vote_commands, "^7 and maybe further ^3arguments^7")); 1110 } 1111 else // usage for individual command 1112 { 1113 #define VOTE_COMMAND(name, function, description, assignment) \ 1114 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(2))) { function; return; } } } 1115 1116 VOTE_COMMANDS(CMD_REQUEST_USAGE, caller, argc, ""); 1117#undef VOTE_COMMAND 1118 } 1119} 1120 1121float VoteCommand_macro_command(entity caller, float argc, string vote_command) 1122{ 1123 #define VOTE_COMMAND(name, function, description, assignment) \ 1124 { if (Votecommand_check_assignment(caller, assignment)) { if (name == strtolower(argv(1))) { function; return true; } } } 1125 1126 VOTE_COMMANDS(CMD_REQUEST_COMMAND, caller, argc, vote_command); 1127#undef VOTE_COMMAND 1128 1129 return false; 1130} 1131 1132 1133// ====================================== 1134// Main function handling vote commands 1135// ====================================== 1136 1137void VoteCommand(float request, entity caller, float argc, string vote_command) 1138{ 1139 // Guide for working with argc arguments by example: 1140 // argc: 1 - 2 - 3 - 4 1141 // argv: 0 - 1 - 2 - 3 1142 // cmd vote - master - login - password 1143 1144 switch (request) 1145 { 1146 case CMD_REQUEST_COMMAND: 1147 { 1148 if (VoteCommand_macro_command(caller, argc, vote_command)) return; 1149 } 1150 1151 default: 1152 print_to(caller, strcat(((argv(1) != "") ? strcat("Unknown vote command \"", argv(1), "\"") : "No command provided"), ". For a list of supported commands, try ", GetCommandPrefix(caller), " vote help.\n")); 1153 case CMD_REQUEST_USAGE: 1154 { 1155 VoteCommand_macro_help(caller, argc); 1156 return; 1157 } 1158 } 1159} 1160