Translations update
[openttd/fttd.git] / src / console.cpp
blob6bf220659244e9e4afada2db2f05bac5173e12c1
1 /* $Id$ */
3 /*
4 * This file is part of OpenTTD.
5 * OpenTTD is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2.
6 * OpenTTD is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
7 * See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with OpenTTD. If not, see <http://www.gnu.org/licenses/>.
8 */
10 /** @file console.cpp Handling of the in-game console. */
12 #include "stdafx.h"
13 #include "string.h"
14 #include "console_internal.h"
15 #include "network/network.h"
16 #include "network/network_func.h"
17 #include "network/network_admin.h"
18 #include "debug.h"
19 #include "console_func.h"
20 #include "settings_type.h"
22 #include <stdarg.h>
24 static const uint ICON_TOKEN_COUNT = 20; ///< Maximum number of tokens in one command
26 /* console parser */
27 IConsoleCmd *_iconsole_cmds; ///< list of registered commands
28 IConsoleAlias *_iconsole_aliases; ///< list of registered aliases
30 FILE *_iconsole_output_file;
32 void IConsoleInit()
34 _iconsole_output_file = NULL;
35 #ifdef ENABLE_NETWORK /* Initialize network only variables */
36 _redirect_console_to_client = INVALID_CLIENT_ID;
37 _redirect_console_to_admin = INVALID_ADMIN_ID;
38 #endif
40 IConsoleGUIInit();
42 IConsoleStdLibRegister();
45 static void IConsoleWriteToLogFile(const char *string)
47 if (_iconsole_output_file != NULL) {
48 /* if there is an console output file ... also print it there */
49 const char *header = GetLogPrefix();
50 if ((strlen(header) != 0 && fwrite(header, strlen(header), 1, _iconsole_output_file) != 1) ||
51 fwrite(string, strlen(string), 1, _iconsole_output_file) != 1 ||
52 fwrite("\n", 1, 1, _iconsole_output_file) != 1) {
53 fclose(_iconsole_output_file);
54 _iconsole_output_file = NULL;
55 IConsolePrintF(CC_DEFAULT, "cannot write to log file");
60 bool CloseConsoleLogIfActive()
62 if (_iconsole_output_file != NULL) {
63 IConsolePrintF(CC_DEFAULT, "file output complete");
64 fclose(_iconsole_output_file);
65 _iconsole_output_file = NULL;
66 return true;
69 return false;
72 void IConsoleFree()
74 IConsoleGUIFree();
75 CloseConsoleLogIfActive();
78 /**
79 * Handle the printing of text entered into the console or redirected there
80 * by any other means. Text can be redirected to other clients in a network game
81 * as well as to a logfile. If the network server is a dedicated server, all activities
82 * are also logged. All lines to print are added to a temporary buffer which can be
83 * used as a history to print them onscreen
84 * @param colour_code the colour of the command. Red in case of errors, etc.
85 * @param string the message entered or output on the console (notice, error, etc.)
87 void IConsolePrint(TextColour colour_code, const char *string)
89 assert(IsValidConsoleColour(colour_code));
91 char *str;
92 #ifdef ENABLE_NETWORK
93 if (_redirect_console_to_client != INVALID_CLIENT_ID) {
94 /* Redirect the string to the client */
95 NetworkServerSendRcon(_redirect_console_to_client, colour_code, string);
96 return;
99 if (_redirect_console_to_admin != INVALID_ADMIN_ID) {
100 NetworkServerSendAdminRcon(_redirect_console_to_admin, colour_code, string);
101 return;
103 #endif
105 /* Create a copy of the string, strip if of colours and invalid
106 * characters and (when applicable) assign it to the console buffer */
107 str = xstrdup(string);
108 str_strip_colours(str);
109 str_validate(str, str + strlen(str));
111 if (_network_dedicated) {
112 #ifdef ENABLE_NETWORK
113 NetworkAdminConsole("console", str);
114 #endif /* ENABLE_NETWORK */
115 fprintf(stdout, "%s%s\n", GetLogPrefix(), str);
116 fflush(stdout);
117 IConsoleWriteToLogFile(str);
118 free(str); // free duplicated string since it's not used anymore
119 return;
122 IConsoleWriteToLogFile(str);
123 IConsoleGUIPrint(colour_code, str);
127 * Handle the printing of text entered into the console or redirected there
128 * by any other means. Uses printf() style format, for more information look
129 * at IConsolePrint()
131 void CDECL IConsolePrintF(TextColour colour_code, const char *format, ...)
133 assert(IsValidConsoleColour(colour_code));
135 va_list va;
136 char buf[ICON_MAX_STREAMSIZE];
138 va_start(va, format);
139 bstrvfmt (buf, format, va);
140 va_end(va);
142 IConsolePrint(colour_code, buf);
146 * It is possible to print debugging information to the console,
147 * which is achieved by using this function. Can only be used by
148 * debug() in debug.cpp. You need at least a level 2 (developer) for debugging
149 * messages to show up
150 * @param dbg debugging category
151 * @param string debugging message
153 void IConsoleDebug(const char *dbg, const char *string)
155 if (_settings_client.gui.developer <= 1) return;
156 IConsolePrintF(CC_DEBUG, "dbg: [%s] %s", dbg, string);
160 * It is possible to print warnings to the console. These are mostly
161 * errors or mishaps, but non-fatal. You need at least a level 1 (developer) for
162 * debugging messages to show up
164 void IConsoleWarning(const char *string)
166 if (_settings_client.gui.developer == 0) return;
167 IConsolePrintF(CC_WARNING, "WARNING: %s", string);
171 * It is possible to print error information to the console. This can include
172 * game errors, or errors in general you would want the user to notice
174 void IConsoleError(const char *string)
176 IConsolePrintF(CC_ERROR, "ERROR: %s", string);
180 * Change a string into its number representation. Supports
181 * decimal and hexadecimal numbers as well as 'on'/'off' 'true'/'false'
182 * @param *value the variable a successful conversion will be put in
183 * @param *arg the string to be converted
184 * @return Return true on success or false on failure
186 bool GetArgumentInteger(uint32 *value, const char *arg)
188 char *endptr;
190 if (strcmp(arg, "on") == 0 || strcmp(arg, "true") == 0) {
191 *value = 1;
192 return true;
194 if (strcmp(arg, "off") == 0 || strcmp(arg, "false") == 0) {
195 *value = 0;
196 return true;
199 *value = strtoul(arg, &endptr, 0);
200 return arg != endptr;
204 * Add an item to an alphabetically sorted list.
205 * @param base first item of the list
206 * @param item_new the item to add
208 template<class T>
209 void IConsoleAddSorted(T **base, T *item_new)
211 if (*base == NULL) {
212 *base = item_new;
213 return;
216 T *item_before = NULL;
217 T *item = *base;
218 /* The list is alphabetically sorted, insert the new item at the correct location */
219 while (item != NULL) {
220 if (strcmp(item->name, item_new->name) > 0) break; // insert here
222 item_before = item;
223 item = item->next;
226 if (item_before == NULL) {
227 *base = item_new;
228 } else {
229 item_before->next = item_new;
232 item_new->next = item;
236 * Remove underscores from a string; the string will be modified!
237 * @param name The string to remove the underscores from.
238 * @return #name.
240 char *RemoveUnderscores(char *name)
242 char *q = name;
243 for (const char *p = name; *p != '\0'; p++) {
244 if (*p != '_') *q++ = *p;
246 *q = '\0';
247 return name;
251 * Register a new command to be used in the console
252 * @param name name of the command that will be used
253 * @param proc function that will be called upon execution of command
255 void IConsoleCmdRegister(const char *name, IConsoleCmdProc *proc, IConsoleHook *hook)
257 IConsoleCmd *item_new = xmalloct<IConsoleCmd>();
258 item_new->name = RemoveUnderscores(xstrdup(name));
259 item_new->next = NULL;
260 item_new->proc = proc;
261 item_new->hook = hook;
263 IConsoleAddSorted(&_iconsole_cmds, item_new);
267 * Find the command pointed to by its string
268 * @param name command to be found
269 * @return return Cmdstruct of the found command, or NULL on failure
271 IConsoleCmd *IConsoleCmdGet(const char *name)
273 IConsoleCmd *item;
275 for (item = _iconsole_cmds; item != NULL; item = item->next) {
276 if (strcmp(item->name, name) == 0) return item;
278 return NULL;
282 * Register a an alias for an already existing command in the console
283 * @param name name of the alias that will be used
284 * @param cmd name of the command that 'name' will be alias of
286 void IConsoleAliasRegister(const char *name, const char *cmd)
288 if (IConsoleAliasGet(name) != NULL) {
289 IConsoleError("an alias with this name already exists; insertion aborted");
290 return;
293 char *new_alias = RemoveUnderscores(xstrdup(name));
294 char *cmd_aliased = xstrdup(cmd);
295 IConsoleAlias *item_new = xmalloct<IConsoleAlias>();
297 item_new->next = NULL;
298 item_new->cmdline = cmd_aliased;
299 item_new->name = new_alias;
301 IConsoleAddSorted(&_iconsole_aliases, item_new);
305 * Find the alias pointed to by its string
306 * @param name alias to be found
307 * @return return Aliasstruct of the found alias, or NULL on failure
309 IConsoleAlias *IConsoleAliasGet(const char *name)
311 IConsoleAlias *item;
313 for (item = _iconsole_aliases; item != NULL; item = item->next) {
314 if (strcmp(item->name, name) == 0) return item;
317 return NULL;
320 * An alias is just another name for a command, or for more commands
321 * Execute it as well.
322 * @param *alias is the alias of the command
323 * @param tokencount the number of parameters passed
324 * @param *tokens are the parameters given to the original command (0 is the first param)
326 static void IConsoleAliasExec(const IConsoleAlias *alias, byte tokencount, char *tokens[ICON_TOKEN_COUNT])
328 sstring<ICON_MAX_STREAMSIZE> alias_buffer;
330 DEBUG(console, 6, "Requested command is an alias; parsing...");
332 for (const char *cmdptr = alias->cmdline; *cmdptr != '\0'; cmdptr++) {
333 switch (*cmdptr) {
334 case '\'': // ' will double for ""
335 alias_buffer.append ('"');
336 break;
338 case ';': // Cmd separator; execute previous and start new command
339 IConsoleCmdExec (alias_buffer.c_str());
340 alias_buffer.clear();
341 cmdptr++;
342 break;
344 case '%': // Some or all parameters
345 cmdptr++;
346 switch (*cmdptr) {
347 case '+': { // All parameters separated: "[param 1]" "[param 2]"
348 for (uint i = 0; i != tokencount; i++) {
349 if (i != 0) alias_buffer.append (' ');
350 alias_buffer.append ('"');
351 alias_buffer.append (tokens[i]);
352 alias_buffer.append ('"');
354 break;
357 case '!': { // Merge the parameters to one: "[param 1] [param 2] [param 3...]"
358 alias_buffer.append ('"');
359 for (uint i = 0; i != tokencount; i++) {
360 if (i != 0) alias_buffer.append (' ');
361 alias_buffer.append (tokens[i]);
363 alias_buffer.append ('"');
364 break;
367 default: { // One specific parameter: %A = [param 1] %B = [param 2] ...
368 int param = *cmdptr - 'A';
370 if (param < 0 || param >= tokencount) {
371 IConsoleError("too many or wrong amount of parameters passed to alias, aborting");
372 IConsolePrintF(CC_WARNING, "Usage of alias '%s': %s", alias->name, alias->cmdline);
373 return;
376 alias_buffer.append ('"');
377 alias_buffer.append (tokens[param]);
378 alias_buffer.append ('"');
379 break;
382 break;
384 default:
385 alias_buffer.append (*cmdptr);
386 break;
389 if (alias_buffer.full()) {
390 IConsoleError("Requested alias execution would overflow execution buffer");
391 return;
395 IConsoleCmdExec (alias_buffer.c_str());
399 * Execute a given command passed to us. First chop it up into
400 * individual tokens (separated by spaces), then execute it if possible
401 * @param cmdstr string to be parsed and executed
403 void IConsoleCmdExec(const char *cmdstr)
405 const char *cmdptr;
406 char *tokens[ICON_TOKEN_COUNT], tokenstream[ICON_MAX_STREAMSIZE];
407 uint t_index, tstream_i;
409 bool longtoken = false;
410 bool foundtoken = false;
412 if (cmdstr[0] == '#') return; // comments
414 for (cmdptr = cmdstr; *cmdptr != '\0'; cmdptr++) {
415 if (!IsValidChar(*cmdptr, CS_ALPHANUMERAL)) {
416 IConsoleError("command contains malformed characters, aborting");
417 IConsolePrintF(CC_ERROR, "ERROR: command was: '%s'", cmdstr);
418 return;
422 DEBUG(console, 4, "Executing cmdline: '%s'", cmdstr);
424 memset(&tokens, 0, sizeof(tokens));
425 memset(&tokenstream, 0, sizeof(tokenstream));
427 /* 1. Split up commandline into tokens, separated by spaces, commands
428 * enclosed in "" are taken as one token. We can only go as far as the amount
429 * of characters in our stream or the max amount of tokens we can handle */
430 for (cmdptr = cmdstr, t_index = 0, tstream_i = 0; *cmdptr != '\0'; cmdptr++) {
431 if (t_index >= lengthof(tokens) || tstream_i >= lengthof(tokenstream)) break;
433 switch (*cmdptr) {
434 case ' ': // Token separator
435 if (!foundtoken) break;
437 if (longtoken) {
438 tokenstream[tstream_i] = *cmdptr;
439 } else {
440 tokenstream[tstream_i] = '\0';
441 foundtoken = false;
444 tstream_i++;
445 break;
446 case '"': // Tokens enclosed in "" are one token
447 longtoken = !longtoken;
448 if (!foundtoken) {
449 tokens[t_index++] = &tokenstream[tstream_i];
450 foundtoken = true;
452 break;
453 case '\\': // Escape character for ""
454 if (cmdptr[1] == '"' && tstream_i + 1 < lengthof(tokenstream)) {
455 tokenstream[tstream_i++] = *++cmdptr;
456 break;
458 /* FALL THROUGH */
459 default: // Normal character
460 tokenstream[tstream_i++] = *cmdptr;
462 if (!foundtoken) {
463 tokens[t_index++] = &tokenstream[tstream_i - 1];
464 foundtoken = true;
466 break;
470 for (uint i = 0; tokens[i] != NULL; i++) {
471 DEBUG(console, 8, "Token %d is: '%s'", i, tokens[i]);
474 if (StrEmpty(tokens[0])) return; // don't execute empty commands
475 /* 2. Determine type of command (cmd or alias) and execute
476 * First try commands, then aliases. Execute
477 * the found action taking into account its hooking code
479 RemoveUnderscores(tokens[0]);
480 IConsoleCmd *cmd = IConsoleCmdGet(tokens[0]);
481 if (cmd != NULL) {
482 ConsoleHookResult chr = (cmd->hook == NULL ? CHR_ALLOW : cmd->hook(true));
483 switch (chr) {
484 case CHR_ALLOW:
485 if (!cmd->proc(t_index, tokens)) { // index started with 0
486 cmd->proc(0, NULL); // if command failed, give help
488 return;
490 case CHR_DISALLOW: return;
491 case CHR_HIDE: break;
495 t_index--;
496 IConsoleAlias *alias = IConsoleAliasGet(tokens[0]);
497 if (alias != NULL) {
498 IConsoleAliasExec(alias, t_index, &tokens[1]);
499 return;
502 IConsoleError("command not found");