3 #include "../../common/counting.qh"
6 // ====================================================
7 // Shared code for server commands, written by Samual
8 // Last updated: December 27th, 2011
9 // ====================================================
11 // select the proper prefix for usage and other messages
12 string GetCommandPrefix(entity caller)
20 // if client return player nickname, or if server return admin nickname
21 string GetCallerName(entity caller)
24 return caller.netname;
26 return admin_name(); //((autocvar_sv_adminnick != "") ? autocvar_sv_adminnick : autocvar_hostname);
29 // verify that the client provided is acceptable for use
30 float VerifyClientEntity(entity client, float must_be_real, float must_be_bots)
32 if (!IS_CLIENT(client))
33 return CLIENT_DOESNT_EXIST;
34 else if(must_be_real && !IS_REAL_CLIENT(client))
35 return CLIENT_NOT_REAL;
36 else if(must_be_bots && !IS_BOT_CLIENT(client))
37 return CLIENT_NOT_BOT;
39 return CLIENT_ACCEPTABLE;
42 // if the client is not acceptable, return a string to be used for error messages
43 string GetClientErrorString(float clienterror, string original_input)
47 case CLIENT_DOESNT_EXIST: { return strcat("Client '", original_input, "' doesn't exist"); }
48 case CLIENT_NOT_REAL: { return strcat("Client '", original_input, "' is not real"); }
49 case CLIENT_NOT_BOT: { return strcat("Client '", original_input, "' is not a bot"); }
50 default: { return "Incorrect usage of GetClientErrorString"; }
54 // is this entity number even in the possible range of entities?
55 float VerifyClientNumber(float tmp_number)
57 if((tmp_number < 1) || (tmp_number > maxclients))
63 entity GetIndexedEntity(float argc, float start_index)
65 entity tmp_player, selection;
66 float tmp_number, index;
73 if(argc > start_index)
75 if(substring(argv(index), 0, 1) == "#")
77 tmp_string = substring(argv(index), 1, -1);
80 if(tmp_string != "") // is it all one token? like #1
82 tmp_number = stof(tmp_string);
84 else if(argc > index) // no, it's two tokens? # 1
86 tmp_number = stof(argv(index));
92 else // maybe it's ONLY a number?
94 tmp_number = stof(argv(index));
98 if(VerifyClientNumber(tmp_number))
100 selection = edict_num(tmp_number); // yes, it was a number
102 else // no, maybe it's a name?
104 FOR_EACH_CLIENT(tmp_player)
105 if (strdecolorize(tmp_player.netname) == strdecolorize(argv(start_index)))
106 selection = tmp_player;
108 index = (start_index + 1);
113 //print(strcat("start_index: ", ftos(start_index), ", next_token: ", ftos(next_token), ", edict: ", ftos(num_for_edict(selection)), ".\n"));
117 // find a player which matches the input string, and return their entity
118 entity GetFilteredEntity(string input)
120 entity tmp_player, selection;
123 if(substring(input, 0, 1) == "#")
124 tmp_number = stof(substring(input, 1, -1));
126 tmp_number = stof(input);
128 if(VerifyClientNumber(tmp_number))
130 selection = edict_num(tmp_number);
135 FOR_EACH_CLIENT(tmp_player)
136 if (strdecolorize(tmp_player.netname) == strdecolorize(input))
137 selection = tmp_player;
143 // same thing, but instead return their edict number
144 float GetFilteredNumber(string input)
146 entity selection = GetFilteredEntity(input);
149 output = num_for_edict(selection);
154 // switch between sprint and print depending on whether the receiver is the server or a player
155 void print_to(entity to, string input)
158 sprint(to, strcat(input, "\n"));
163 // ==========================================
164 // Supporting functions for common commands
165 // ==========================================
167 // used by CommonCommand_timeout() and CommonCommand_timein() to handle game pausing and messaging and such.
168 void timeout_handler_reset()
170 timeout_caller = world;
172 timeout_leadtime = 0;
177 void timeout_handler_think()
181 switch(timeout_status)
185 if(timeout_time > 0) // countdown is still going
187 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TIMEOUT_ENDING, timeout_time);
189 if(timeout_time == autocvar_sv_timeout_resumetime) // play a warning sound when only <sv_timeout_resumetime> seconds are left
190 Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_PREPARE);
192 self.nextthink = time + TIMEOUT_SLOWMO_VALUE; // think again in one second
193 timeout_time -= 1; // decrease the time counter
195 else // time to end the timeout
197 timeout_status = TIMEOUT_INACTIVE;
199 // reset the slowmo value back to normal
200 cvar_set("slowmo", ftos(orig_slowmo));
202 // unlock the view for players so they can move around again
203 FOR_EACH_REALPLAYER(tmp_player)
204 tmp_player.fixangle = false;
206 timeout_handler_reset();
212 case TIMEOUT_LEADTIME:
214 if(timeout_leadtime > 0) // countdown is still going
216 Send_Notification(NOTIF_ALL, world, MSG_CENTER, CENTER_TIMEOUT_BEGINNING, timeout_leadtime);
218 self.nextthink = time + 1; // think again in one second
219 timeout_leadtime -= 1; // decrease the time counter
221 else // time to begin the timeout
223 timeout_status = TIMEOUT_ACTIVE;
225 // set the slowmo value to the timeout default slowmo value
226 cvar_set("slowmo", ftos(TIMEOUT_SLOWMO_VALUE));
228 // reset all the flood variables
229 FOR_EACH_CLIENT(tmp_player)
230 tmp_player.nickspamcount = tmp_player.nickspamtime = tmp_player.floodcontrol_chat =
231 tmp_player.floodcontrol_chatteam = tmp_player.floodcontrol_chattell =
232 tmp_player.floodcontrol_voice = tmp_player.floodcontrol_voiceteam = 0;
234 // copy .v_angle to .lastV_angle for every player in order to fix their view during pause (see PlayerPreThink)
235 FOR_EACH_REALPLAYER(tmp_player)
236 tmp_player.lastV_angle = tmp_player.v_angle;
238 self.nextthink = time; // think again next frame to handle it under TIMEOUT_ACTIVE code
245 case TIMEOUT_INACTIVE:
248 timeout_handler_reset();
256 // ===================================================
257 // Common commands used in both sv_cmd.qc and cmd.qc
258 // ===================================================
260 void CommonCommand_cvar_changes(float request, entity caller)
264 case CMD_REQUEST_COMMAND:
266 print_to(caller, cvar_changes);
267 return; // never fall through to usage
271 case CMD_REQUEST_USAGE:
273 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_changes"));
274 print_to(caller, " No arguments required.");
275 print_to(caller, "See also: ^2cvar_purechanges^7");
281 void CommonCommand_cvar_purechanges(float request, entity caller)
285 case CMD_REQUEST_COMMAND:
287 print_to(caller, cvar_purechanges);
288 return; // never fall through to usage
292 case CMD_REQUEST_USAGE:
294 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " cvar_purechanges"));
295 print_to(caller, " No arguments required.");
296 print_to(caller, "See also: ^2cvar_changes^7");
302 void CommonCommand_info(float request, entity caller, float argc)
306 case CMD_REQUEST_COMMAND:
308 string command = builtin_cvar_string(strcat("sv_info_", argv(1)));
311 wordwrap_sprint(command, 1000);
313 print_to(caller, "ERROR: unsupported info command");
315 return; // never fall through to usage
319 case CMD_REQUEST_USAGE:
321 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " info request"));
322 print_to(caller, " Where 'request' is the suffixed string appended onto the request for cvar.");
328 void CommonCommand_ladder(float request, entity caller)
332 case CMD_REQUEST_COMMAND:
334 print_to(caller, ladder_reply);
335 return; // never fall through to usage
339 case CMD_REQUEST_USAGE:
341 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " ladder"));
342 print_to(caller, " No arguments required.");
348 void CommonCommand_lsmaps(float request, entity caller)
352 case CMD_REQUEST_COMMAND:
354 print_to(caller, lsmaps_reply);
355 return; // never fall through to usage
359 case CMD_REQUEST_USAGE:
361 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " lsmaps"));
362 print_to(caller, " No arguments required.");
368 void CommonCommand_printmaplist(float request, entity caller)
372 case CMD_REQUEST_COMMAND:
374 print_to(caller, maplist_reply);
375 return; // never fall through to usage
379 case CMD_REQUEST_USAGE:
381 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " printmaplist"));
382 print_to(caller, " No arguments required.");
388 void CommonCommand_rankings(float request, entity caller)
392 case CMD_REQUEST_COMMAND:
394 print_to(caller, rankings_reply);
395 return; // never fall through to usage
399 case CMD_REQUEST_USAGE:
401 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " rankings"));
402 print_to(caller, " No arguments required.");
408 void CommonCommand_records(float request, entity caller)
412 case CMD_REQUEST_COMMAND:
414 for(int i = 0; i < 10; ++i)
415 if(records_reply[i] != "")
416 print_to(caller, records_reply[i]);
418 return; // never fall through to usage
422 case CMD_REQUEST_USAGE:
424 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " records"));
425 print_to(caller, " No arguments required.");
431 void CommonCommand_teamstatus(float request, entity caller)
435 case CMD_REQUEST_COMMAND:
437 Score_NicePrint(caller);
438 return; // never fall through to usage
442 case CMD_REQUEST_USAGE:
444 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " teamstatus"));
445 print_to(caller, " No arguments required.");
451 void CommonCommand_time(float request, entity caller)
455 case CMD_REQUEST_COMMAND:
457 print_to(caller, strcat("time = ", ftos(time)));
458 print_to(caller, strcat("frame start = ", ftos(gettime(GETTIME_FRAMESTART))));
459 print_to(caller, strcat("realtime = ", ftos(gettime(GETTIME_REALTIME))));
460 print_to(caller, strcat("hires = ", ftos(gettime(GETTIME_HIRES))));
461 print_to(caller, strcat("uptime = ", ftos(gettime(GETTIME_UPTIME))));
462 print_to(caller, strcat("localtime = ", strftime(true, "%a %b %e %H:%M:%S %Z %Y")));
463 print_to(caller, strcat("gmtime = ", strftime(false, "%a %b %e %H:%M:%S %Z %Y")));
468 case CMD_REQUEST_USAGE:
470 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " time"));
471 print_to(caller, " No arguments required.");
477 void CommonCommand_timein(float request, entity caller)
481 case CMD_REQUEST_COMMAND:
483 if(!caller || autocvar_sv_timeout)
485 if (!timeout_status) { print_to(caller, "^7Error: There is no active timeout called."); }
486 else if(caller && (caller != timeout_caller)) { print_to(caller, "^7Error: You are not allowed to stop the active timeout."); }
488 else // everything should be okay, continue aborting timeout
490 switch(timeout_status)
492 case TIMEOUT_LEADTIME:
494 timeout_status = TIMEOUT_INACTIVE;
496 timeout_handler.nextthink = time; // timeout_handler has to take care of it immediately
497 bprint(strcat("^7The timeout was aborted by ", GetCallerName(caller), " !\n"));
503 timeout_time = autocvar_sv_timeout_resumetime;
504 timeout_handler.nextthink = time; // timeout_handler has to take care of it immediately
505 bprint(strcat("^1Attention: ^7", GetCallerName(caller), " resumed the game! Prepare for battle!\n"));
509 default: dprint("timeout status was inactive, but this code was executed anyway?"); return;
513 else { print_to(caller, "^1Timeins are not allowed to be called, enable them with sv_timeout 1.\n"); }
515 return; // never fall through to usage
519 case CMD_REQUEST_USAGE:
521 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timein"));
522 print_to(caller, " No arguments required.");
528 void CommonCommand_timeout(float request, entity caller) // DEAR GOD THIS COMMAND IS TERRIBLE.
532 case CMD_REQUEST_COMMAND:
534 if(!caller || autocvar_sv_timeout)
536 float last_possible_timeout = ((autocvar_timelimit * 60) - autocvar_sv_timeout_leadtime - 1);
538 if(timeout_status) { print_to(caller, "^7Error: A timeout is already active."); }
539 else if(vote_called) { print_to(caller, "^7Error: You can not call a timeout while a vote is active."); }
540 else if(warmup_stage && !g_warmup_allow_timeout) { print_to(caller, "^7Error: You can not call a timeout in warmup-stage."); }
541 else if(time < game_starttime) { print_to(caller, "^7Error: You can not call a timeout while the map is being restarted."); }
542 else if(caller && (caller.allowed_timeouts < 1)) { print_to(caller, "^7Error: You already used all your timeout calls for this map."); }
543 else if(caller && !IS_PLAYER(caller)) { print_to(caller, "^7Error: You must be a player to call a timeout."); }
544 else if((autocvar_timelimit) && (last_possible_timeout < time - game_starttime)) { print_to(caller, "^7Error: It is too late to call a timeout now!"); }
546 else // everything should be okay, proceed with starting the timeout
548 if(caller) { caller.allowed_timeouts -= 1; }
550 bprint(GetCallerName(caller), " ^7called a timeout", (caller ? strcat(" (", ftos(caller.allowed_timeouts), " timeout(s) left)") : ""), "!\n"); // write a bprint who started the timeout (and how many they have left)
552 timeout_status = TIMEOUT_LEADTIME;
553 timeout_caller = caller;
554 timeout_time = autocvar_sv_timeout_length;
555 timeout_leadtime = autocvar_sv_timeout_leadtime;
557 timeout_handler = spawn();
558 timeout_handler.think = timeout_handler_think;
559 timeout_handler.nextthink = time; // always let the entity think asap
561 Send_Notification(NOTIF_ALL, world, MSG_ANNCE, ANNCE_TIMEOUT);
564 else { print_to(caller, "^1Timeouts are not allowed to be called, enable them with sv_timeout 1.\n"); }
566 return; // never fall through to usage
570 case CMD_REQUEST_USAGE:
572 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " timeout"));
573 print_to(caller, " No arguments required.");
579 void CommonCommand_who(float request, entity caller, float argc)
583 case CMD_REQUEST_COMMAND:
585 float total_listed_players, is_bot;
588 float privacy = (caller && autocvar_sv_status_privacy);
589 string separator = strreplace("%", " ", strcat((argv(1) ? argv(1) : " "), "^7"));
590 string tmp_netaddress, tmp_crypto_idfp;
592 print_to(caller, strcat("List of client information", (privacy ? " (some data is hidden for privacy)" : ""), ":"));
593 print_to(caller, sprintf(strreplace(" ", separator, " %-4s %-20s %-5s %-3s %-9s %-16s %s "),
594 "ent", "nickname", "ping", "pl", "time", "ip", "crypto_id"));
596 total_listed_players = 0;
597 FOR_EACH_CLIENT(tmp_player)
599 is_bot = (IS_BOT_CLIENT(tmp_player));
603 tmp_netaddress = "null/botclient";
604 tmp_crypto_idfp = "null/botclient";
608 tmp_netaddress = "hidden";
609 tmp_crypto_idfp = "hidden";
613 tmp_netaddress = tmp_player.netaddress;
614 tmp_crypto_idfp = tmp_player.crypto_idfp;
617 print_to(caller, sprintf(strreplace(" ", separator, " #%-3d %-20.20s %-5d %-3d %-9s %-16s %s "),
618 num_for_edict(tmp_player),
621 tmp_player.ping_packetloss,
622 process_time(1, time - tmp_player.jointime),
626 ++total_listed_players;
629 print_to(caller, strcat("Finished listing ", ftos(total_listed_players), " client(s) out of ", ftos(maxclients), " slots."));
631 return; // never fall through to usage
635 case CMD_REQUEST_USAGE:
637 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " who [separator]"));
638 print_to(caller, " Where 'separator' is the optional string to separate the values with, default is a space.");
644 /* use this when creating a new command, making sure to place it in alphabetical order... also,
645 ** ADD ALL NEW COMMANDS TO commands.cfg WITH PROPER ALIASES IN THE SAME FASHION!
646 void CommonCommand_(float request, entity caller)
650 case CMD_REQUEST_COMMAND:
653 return; // never fall through to usage
657 case CMD_REQUEST_USAGE:
659 print_to(caller, strcat("\nUsage:^3 ", GetCommandPrefix(caller), " "));
660 print_to(caller, " No arguments required.");