Crossfire Client, Branch
R11627
|
00001 /* 00002 Crossfire client, a client program for the crossfire program. 00003 00004 Copyright (C) 2005,2006 Mark Wedel & Crossfire Development Team 00005 00006 This program is free software; you can redistribute it and/or modify 00007 it under the terms of the GNU General Public License as published by 00008 the Free Software Foundation; either version 2 of the License, or 00009 (at your option) any later version. 00010 00011 This program is distributed in the hope that it will be useful, 00012 but WITHOUT ANY WARRANTY; without even the implied warranty of 00013 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 00014 GNU General Public License for more details. 00015 00016 You should have received a copy of the GNU General Public License 00017 along with this program; if not, write to the Free Software 00018 Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. 00019 00020 The author can be reached via e-mail to crossfire-devel@real-time.com 00021 */ 00022 00023 /* 00024 * Contains a lot about the commands typed into the client. 00025 */ 00026 00027 #ifndef CPROTO 00028 /* use declartions from p_cmd.h instead of doing make proto on this file */ 00029 00030 #include <client.h> 00031 #include <external.h> 00032 #include <script.h> 00033 #include <p_cmd.h> 00034 00035 /* 00036 * 00037 * Help commands. 00038 * 00039 */ 00040 00041 /* TODO This should really be under /help commands or something... */ 00042 00043 /* This dynamically generates a list from the ConsoleCommand list. */ 00044 #undef CLIENTHELP_LONG_LIST 00045 00046 /* 00047 long-list: 00048 category 00049 name - description 00050 name - description 00051 ... 00052 00053 not long list: 00054 category 00055 name name name ... 00056 */ 00057 00058 #undef HELP_USE_COLOR 00059 #ifdef HELP_USE_COLOR 00060 #error Oops, need to put them back. 00061 #else 00062 #define H1(a) draw_info(a, NDI_BLACK) 00063 #define H2(a) draw_info(a, NDI_BLACK) 00064 #define LINE(a) draw_info(a, NDI_BLACK) 00065 #endif 00066 00067 #define assumed_wrap get_info_width() 00068 00069 00070 /* TODO Help topics other than commands? Refer to other documents? */ 00071 static void do_clienthelp_list(void) { 00072 ConsoleCommand ** commands_array; 00073 ConsoleCommand * commands_copy; 00074 int i; 00075 CommCat current_cat = COMM_CAT_MISC; 00076 #ifndef CLIENTHELP_LONG_LIST 00077 char line_buf[MAX_BUF]; 00078 size_t line_len = 0; 00079 00080 line_buf[0] = '\0'; 00081 #endif 00082 00083 commands_array = get_cat_sorted_commands(); 00084 00085 /* Now we have a nice sorted list. */ 00086 00087 H1(" === Client Side Commands === "); 00088 00089 for (i = 0; i < get_num_commands(); i++) { 00090 commands_copy = commands_array[i]; 00091 00092 /* Should be LOG_SPAM but I'm too lazy to tweak it. */ 00093 /* LOG(LOG_INFO, "p_cmd::do_clienthelp_list", "%s Command %s", get_category_name(commands_copy->cat), commands_copy->name); */ 00094 00095 if (commands_copy->cat != current_cat) { 00096 char buf[MAX_BUF]; 00097 00098 #ifndef CLIENTHELP_LONG_LIST 00099 if (line_len > 0) { 00100 LINE(line_buf); 00101 line_buf[0] = '\0'; 00102 line_len = 0; 00103 } 00104 #endif 00105 00106 #ifdef HELP_USE_COLOR 00107 snprintf(buf, MAX_BUF - 1, "%s Commands:", get_category_name(commands_copy->cat)); 00108 #else 00109 snprintf(buf, MAX_BUF - 1, " --- %s Commands --- ", get_category_name(commands_copy->cat)); 00110 #endif 00111 00112 H2(buf); 00113 current_cat = commands_copy->cat; 00114 } 00115 00116 #ifdef CLIENTHELP_LONG_LIST 00117 if (commands_copy->desc != NULL) { 00118 char buf[MAX_BUF]; 00119 snprintf(buf, MAX_BUF - 1, "%s - %s", commands_copy->name, commands_copy->desc); 00120 LINE(buf); 00121 } else { 00122 LINE(commands_copy->name); 00123 } 00124 #else 00125 { 00126 size_t name_len; 00127 00128 name_len = strlen(commands_copy->name); 00129 00130 if (strlen(commands_copy->name) > MAX_BUF) { 00131 LINE(commands_copy->name); 00132 } else if (name_len > assumed_wrap) { 00133 LINE(line_buf); 00134 LINE(commands_copy->name); 00135 line_len = 0; 00136 } else if (line_len + name_len > assumed_wrap) { 00137 LINE(line_buf); 00138 strncpy(line_buf, commands_copy->name, name_len + 1); 00139 line_len = name_len; 00140 } else { 00141 if (line_len > 0) { 00142 strncat(line_buf, " ", 2); 00143 line_len += 1; 00144 } 00145 strncat(line_buf, commands_copy->name, name_len + 1); 00146 line_len += name_len; 00147 } 00148 } 00149 #endif 00150 } 00151 00152 #ifndef CLIENTHELP_LONG_LIST 00153 if (line_len) { 00154 LINE(line_buf); 00155 } 00156 #endif 00157 } 00158 00159 00160 static void show_help(const ConsoleCommand * cc) { 00161 { 00162 char buf[MAX_BUF]; 00163 snprintf(buf, MAX_BUF - 1, "%s Command help:", get_category_name(cc->cat)); 00164 H1(buf); 00165 } 00166 00167 if (cc->desc != NULL) { 00168 char buf[MAX_BUF]; 00169 snprintf(buf, MAX_BUF - 1, "%s - %s", cc->name, cc->desc); 00170 H2(buf); 00171 } else { 00172 H2(cc->name); 00173 } 00174 00175 if (cc->helpfunc != NULL) { 00176 const char * long_help = NULL; 00177 00178 long_help = cc->helpfunc(); 00179 00180 if (long_help != NULL) { 00181 /* For a test, let's watch draw_info() choke on newlines. */ 00182 /* TODO C line wrapping (get_info_width()), argh. Or move it to UI? */ 00183 LINE(long_help); 00184 } else { 00185 LINE("This command's documentation is bugged!"); 00186 } 00187 } else { 00188 LINE("This command has no extended documentation. :("); 00189 } 00190 } 00191 00192 static void do_clienthelp(const char * arg) { 00193 const ConsoleCommand * cc; 00194 00195 if (!arg || !strlen(arg)) { 00196 do_clienthelp_list(); 00197 return; 00198 } 00199 00200 cc = find_command(arg); 00201 00202 if (cc == NULL) { 00203 char buf[MAX_BUF]; 00204 snprintf(buf, MAX_BUF - 1, "clienthelp: Unknown command %s.", arg); 00205 draw_info(buf, NDI_BLACK); 00206 return; 00207 } 00208 00209 show_help(cc); 00210 00211 } 00212 00213 static const char * help_clienthelp(void) { 00214 return 00215 "Syntax:\n" 00216 "\n" 00217 " clienthelp\n" 00218 " clienthelp <command>\n" 00219 "\n" 00220 "Without any arguments, displays a list of client-side " 00221 "commands.\n" 00222 "\n" 00223 "With arguments, displays the help for the client-side " 00224 "command <command>.\n" 00225 "\n" 00226 "See also: serverhelp, help."; 00227 } 00228 00229 static void do_serverhelp(const char * arg) { 00230 00231 if (arg) { 00232 char buf[MAX_BUF]; 00233 snprintf(buf, sizeof(buf), "help %s", arg); 00234 /* maybe not a must send, but we probably don't want to drop it */ 00235 send_command(buf, -1, 1); 00236 } else { 00237 send_command("help", -1, 1); /* TODO make install in server branch doesn't install def_help. */ 00238 } 00239 } 00240 00241 static const char * help_serverhelp(void) { 00242 return 00243 "Syntax:\n" 00244 "\n" 00245 " serverhelp\n" 00246 " serverhelp <command>\n" 00247 "\n" 00248 "Fetches help from the server.\n" 00249 "\n" 00250 "Note that currently nothing can be done (without a recompile) if a " 00251 "client command masks a server command.\n" 00252 "\n" 00253 "See also: clienthelp, help."; 00254 } 00255 00256 00257 static void command_help(const char *cpnext) { 00258 if (cpnext) { 00259 const ConsoleCommand * cc; 00260 char buf[MAX_BUF]; 00261 00262 cc = find_command(cpnext); 00263 if (cc != NULL) { 00264 show_help(cc); 00265 } else { 00266 snprintf(buf, sizeof(buf), "help %s", cpnext); 00267 /* maybe not a must send, but we probably don't want to drop it */ 00268 send_command(buf, -1, 1); 00269 } 00270 } else { 00271 do_clienthelp_list(); 00272 /* Now fetch (in theory) command list from the server. 00273 TODO Protocol command - feed it to the tab completer. 00274 00275 Nope! It effectivey fetches '/help commands for commands'. 00276 */ 00277 send_command("help", -1, 1); /* TODO make install in server branch doesn't install def_help. */ 00278 } 00279 } 00280 00281 static const char * help_help(void) { 00282 return 00283 "Syntax:\n" 00284 "\n" 00285 " help\n" 00286 " help <topic>\n" 00287 "\n" 00288 "Without any arguments, displays a list of client-side " 00289 "commands, and fetches the without-arguments help from " 00290 "the server.\n" 00291 "\n" 00292 "With arguments, first checks if there's a client command " 00293 "named <topic>. If there is, display it's help. If there " 00294 "isn't, send the topic to the server.\n" 00295 "\n" 00296 "See also: clienthelp, serverhelp."; 00297 } 00298 00299 00300 /* 00301 * 00302 * Other commands. 00303 * 00304 */ 00305 00306 static void set_command_window(const char *cpnext) 00307 { 00308 if (!cpnext) { 00309 draw_info("cwindow command requires a number parameter", NDI_BLACK); 00310 } else { 00311 want_config[CONFIG_CWINDOW] = atoi(cpnext); 00312 if (want_config[CONFIG_CWINDOW]<1 || want_config[CONFIG_CWINDOW]>127) 00313 want_config[CONFIG_CWINDOW]=COMMAND_WINDOW; 00314 else 00315 use_config[CONFIG_CWINDOW] = want_config[CONFIG_CWINDOW]; 00316 } 00317 } 00318 00319 static void command_foodbep(const char *cpnext) 00320 { 00321 (void)cpnext; /* __UNUSED__ */ 00322 if (want_config[CONFIG_FOODBEEP]) { 00323 want_config[CONFIG_FOODBEEP]=0; 00324 draw_info("Warning bell when low on food disabled", NDI_BLACK); 00325 } else { 00326 want_config[CONFIG_FOODBEEP]=1; 00327 draw_info("Warning bell when low on food enabled", NDI_BLACK); 00328 } 00329 use_config[CONFIG_FOODBEEP] = want_config[CONFIG_FOODBEEP]; 00330 } 00331 00332 00333 00334 00335 const char * get_category_name(CommCat cat) { 00336 const char * cat_name; 00337 00338 /* HACK Need to keep this in sync. with player.h */ 00339 switch(cat) { 00340 case COMM_CAT_MISC: cat_name = "Miscellaneous"; break; 00341 case COMM_CAT_HELP: cat_name = "Help"; break; 00342 case COMM_CAT_INFO: cat_name = "Informational"; break; 00343 case COMM_CAT_SETUP: cat_name = "Configuration"; break; 00344 case COMM_CAT_SCRIPT: cat_name = "Scripting"; break; 00345 case COMM_CAT_DEBUG: cat_name = "Debugging"; break; 00346 default: cat_name = "PROGRAMMER ERROR"; break; 00347 } 00348 00349 return cat_name; 00350 } 00351 00352 00353 /* 00354 * Command table. 00355 * 00356 * Implementation basically stolen verbatim from the server. 00357 */ 00358 00359 /* "Typecasters" (and some forwards) */ 00360 static void do_script_list(const char * ignored) { script_list(); } 00361 static void do_clearinfo(const char * ignored) { menu_clear(); } 00362 00363 static void do_disconnect(const char * ignored) { 00364 #ifdef WIN32 00365 closesocket(csocket.fd); 00366 #else 00367 close(csocket.fd); 00368 #endif 00369 csocket.fd=-1; 00370 00371 /* the gtk clients need to do some cleanup logic - otherwise, 00372 * they start hogging CPU. 00373 */ 00374 cleanup_connection(); 00375 return; 00376 } 00377 00378 #ifdef HAVE_DMALLOC_H 00379 #ifndef DMALLOC_VERIFY_NOERROR 00380 #define DMALLOC_VERIFY_NOERROR 1 00381 #endif 00382 static void do_dmalloc(const char * ignored) { 00383 if (dmalloc_verify(NULL)==DMALLOC_VERIFY_NOERROR) 00384 draw_info("Heap checks out OK", NDI_BLACK); 00385 else 00386 draw_info("Heap corruption detected", NDI_RED); 00387 } 00388 #endif 00389 00390 static void do_inv(const char * ignored) { print_inventory (cpl.ob); } 00391 00392 static void do_magicmap(const char * ignored) { 00393 cpl.showmagic=1; 00394 draw_magic_map(); 00395 } 00396 00397 static void do_metaserver(const char * ignored) { 00398 if (!metaserver_get_info(meta_server, meta_port)) 00399 metaserver_show(FALSE); 00400 else 00401 draw_info("Unable to get metaserver information.", NDI_BLACK); 00402 } 00403 00404 static void do_savedefaults(const char * ignored) { save_defaults(); } 00405 00406 static void do_savewinpos(const char * ignored) { save_winpos(); } 00407 00408 static void do_take(const char * used) { command_take("take", used); /* I dunno why they want it. */ } 00409 00410 static void do_num_free_items(const char * ignored) { 00411 LOG(LOG_INFO,"common::extended_command","num_free_items=%d", num_free_items()); 00412 } 00413 00414 static void do_clienthelp(const char * arg); /* Forward. */ 00415 00416 /* Help "typecasters". */ 00417 #include "../help/chelp.h" 00418 00419 static const char * help_bind(void) { return HELP_BIND_LONG; } 00420 00421 static const char * help_unbind(void) { return HELP_UNBIND_LONG; } 00422 00423 static const char * help_magicmap(void) { return HELP_MAGICMAP_LONG; } 00424 00425 static const char * help_inv(void) { return HELP_INV_LONG; } 00426 00427 static const char * help_cwindow(void) { 00428 return 00429 "Syntax:\n" 00430 "\n" 00431 " cwindow <val>\n" 00432 "\n" 00433 "set size of command" 00434 "window (if val is exceeded" 00435 "client won't send new" 00436 "commands to server\n\n" 00437 "(What does this mean, 'put a lid on it'?) TODO"; 00438 } 00439 00440 static const char * help_script(void) { 00441 return 00442 "Syntax:\n" 00443 "\n" 00444 " script <pathname>\n" 00445 "\n" 00446 "Run the program at path <name>" 00447 "as a Crossfire client script." 00448 "See Documentation/Script.html"; 00449 } 00450 00451 static const char * help_scripttell(void) { 00452 return 00453 "Syntax:\n" 00454 "\n" 00455 " scripttell <yourname> <data>\n" 00456 "\n" 00457 "?"; 00458 } 00459 00460 /* Toolkit-dependent. */ 00461 static const char * help_savewinpos(void) { 00462 return 00463 "Syntax:\n" 00464 "\n" 00465 " savewinpos\n" 00466 "\n" 00467 "save window positions - split windows mode only."; 00468 } 00469 00470 static const char * help_metaserver(void) { 00471 /* TODO Add command_escape() where appropriate. On the other 00472 hand, that can lead to a meaningless syntax-display API.*/ 00473 00474 return 00475 "Syntax:\n" 00476 "\n" 00477 " metaserver\n" 00478 "\n" 00479 "Get updated list of servers " 00480 "from the metaserver and show it." 00481 "This is the same information that the client " 00482 "uses to show a list of servers when it starts.\n" 00483 "\n" 00484 "Warning: This command may freeze the client until it gets the list."; 00485 } 00486 00487 static const char * help_scriptkill(void) { 00488 return 00489 "Syntax:\n" 00490 "\n" 00491 " scriptkill <name>\n" 00492 "\n" 00493 "Stop scripts named <name>.\n" 00494 "(Not guaranteed to work?)"; 00495 } 00496 00497 static const char * help_showweight(void) { 00498 return 00499 "Syntax:\n" 00500 "\n" 00501 " showweight\n" 00502 " showweight inventory\n" 00503 " showweight look\n" 00504 "\n" 00505 "(Or any prefix of the arguments.)" 00506 "Toggles if you see the weight of" 00507 "items in your inventory (also if" 00508 "no argument given) or your" 00509 "look-window."; 00510 } 00511 00512 /* 00513 * draw_info("Information Commands", NDI_NAVY);* 00514 draw_info(" inv - *recursively* print your", NDI_BLACK); 00515 draw_info(" inventory - includes containers.", NDI_BLACK); 00516 draw_info(" mapredraw, showinfo, take", NDI_BLACK); 00517 draw_info(" help - show this message", NDI_BLACK); 00518 draw_info(" help <command> - get more information on a", NDI_BLACK); 00519 draw_info(" command (Server command only?)", NDI_BLACK); 00520 draw_info(" showicon - draw status icons in", NDI_BLACK); 00521 draw_info(" inventory window", NDI_BLACK); 00522 draw_info(" showweight - show weight in inventory", NDI_BLACK); 00523 draw_info(" look windows", NDI_BLACK); 00524 draw_info("Scripting Commands", NDI_NAVY); 00525 draw_info("Client Side Debugging Commands", NDI_NAVY); 00526 #ifdef HAVE_DMALLOC_H 00527 draw_info(" dmalloc - Check heap?", NDI_BLACK); 00528 #endif 00529 */ 00530 00531 /* Forward. */ 00532 static void do_clienthelp(const char * currently_ignored); 00533 00534 /* TODO Wrap these? Um. */ 00535 static ConsoleCommand CommonCommands[] = { 00536 /* From player.h: 00537 name, cat, 00538 func, helpfunc, 00539 long_desc 00540 */ 00541 00542 { 00543 "autorepeat", COMM_CAT_MISC, 00544 set_autorepeat, NULL, 00545 "toggle autorepeat" /* XXX Eh? */ 00546 }, 00547 00548 { 00549 "bind", COMM_CAT_SETUP, 00550 bind_key, help_bind, 00551 HELP_BIND_SHORT 00552 }, 00553 00554 { 00555 "script", COMM_CAT_SCRIPT, 00556 script_init, help_script, 00557 NULL 00558 }, 00559 #ifdef HAVE_LUA 00560 { "lua_load", COMM_CAT_SCRIPT, 00561 script_lua_load, NULL, NULL 00562 }, 00563 00564 { "lua_list", COMM_CAT_SCRIPT, 00565 script_lua_list, NULL, NULL 00566 }, 00567 00568 { "lua_kill", COMM_CAT_SCRIPT, 00569 script_lua_kill, NULL, NULL 00570 }, 00571 #endif 00572 { 00573 "scripts", COMM_CAT_SCRIPT, 00574 do_script_list, NULL, 00575 "List the running scripts(?)" 00576 }, 00577 00578 { 00579 "scriptkill", COMM_CAT_SCRIPT, 00580 script_kill, help_scriptkill, 00581 NULL 00582 }, 00583 00584 { 00585 "scripttell", COMM_CAT_SCRIPT, 00586 script_tell, help_scripttell, 00587 NULL 00588 }, 00589 00590 { 00591 "clearinfo", COMM_CAT_MISC, 00592 do_clearinfo, NULL, 00593 "clear the info window" 00594 }, 00595 00596 { 00597 "cwindow", COMM_CAT_SETUP, 00598 set_command_window, help_cwindow, 00599 NULL 00600 }, 00601 00602 { 00603 "disconnect", COMM_CAT_MISC, 00604 do_disconnect, NULL, 00605 "close connection to server" 00606 }, 00607 00608 00609 #ifdef HAVE_DMALLOC_H 00610 { 00611 "dmalloc", COMM_CAT_DEBUG, 00612 do_dmalloc, NULL, 00613 NULL 00614 }, 00615 #endif 00616 00617 { 00618 "foodbeep", COMM_CAT_SETUP, 00619 command_foodbep, NULL, 00620 "toggle audible low on food warning" 00621 00622 }, 00623 00624 { 00625 "help", COMM_CAT_HELP, 00626 command_help, help_help, 00627 NULL 00628 }, 00629 00630 { 00631 "clienthelp", COMM_CAT_HELP, 00632 do_clienthelp, help_clienthelp, 00633 "Client-side command information" 00634 }, 00635 00636 { 00637 "serverhelp", COMM_CAT_HELP, 00638 do_serverhelp, help_serverhelp, 00639 "Server-side command information" 00640 }, 00641 00642 { 00643 "inv", COMM_CAT_DEBUG, 00644 do_inv, help_inv, 00645 HELP_INV_SHORT 00646 }, 00647 00648 { 00649 "magicmap", COMM_CAT_MISC, 00650 do_magicmap, help_magicmap, 00651 HELP_MAGICMAP_SHORT 00652 }, 00653 00654 { 00655 "metaserver", COMM_CAT_INFO, 00656 do_metaserver, help_metaserver, 00657 "Print 'metaserver information'. Warning - your client will pause." 00658 }, 00659 00660 { 00661 "savedefaults", COMM_CAT_SETUP, 00662 do_savedefaults, NULL, 00663 HELP_SAVEDEFAULTS_SHORT /* How do we make sure showicons stays on? */ 00664 }, 00665 00666 { 00667 "savewinpos", COMM_CAT_SETUP, 00668 do_savewinpos, help_savewinpos, 00669 "Saves the position and sizes of windows." /* Panes? */ 00670 }, 00671 00672 { 00673 "scroll", COMM_CAT_SETUP, 00674 set_scroll, NULL, 00675 "toggle scroll/wrap mode in info window" 00676 }, 00677 00678 { 00679 "showicon", COMM_CAT_SETUP, 00680 set_show_icon, NULL, 00681 "Toggles if you see the worn, locked, cursed etc state in the inventory pane." 00682 }, 00683 00684 { 00685 "showweight", COMM_CAT_SETUP, 00686 set_show_weight, help_showweight, 00687 "Toggles if you see item weights in inventory look windows." 00688 }, 00689 00690 { 00691 "take", COMM_CAT_MISC, 00692 do_take, NULL, 00693 NULL 00694 }, 00695 00696 { 00697 "unbind", COMM_CAT_SETUP, 00698 unbind_key, help_unbind, 00699 NULL 00700 }, 00701 00702 { 00703 "num_free_items", COMM_CAT_DEBUG, 00704 do_num_free_items, NULL, 00705 "log the number of free items?" 00706 }, 00707 { 00708 "show", COMM_CAT_SETUP, 00709 command_show, NULL, 00710 "Change what items to show in inventory" 00711 }, 00712 00713 }; 00714 00715 const int CommonCommandsSize = sizeof(CommonCommands) / sizeof(ConsoleCommand); 00716 00717 #ifdef TOOLKIT_COMMANDS 00718 extern ConsoleCommand ToolkitCommands[]; 00719 00720 extern const int ToolkitCommandsSize; 00721 #endif 00722 00723 /* ------------------------------------------------------------------ */ 00724 00725 int num_commands; 00726 00727 int get_num_commands(void) { return num_commands; } 00728 00729 static ConsoleCommand ** name_sorted_commands; 00730 00731 static int sort_by_name(const void * a_, const void * b_) 00732 { 00733 ConsoleCommand * a = *((ConsoleCommand **)a_); 00734 ConsoleCommand * b = *((ConsoleCommand **)b_); 00735 00736 return strcmp(a->name, b->name); 00737 } 00738 00739 static ConsoleCommand ** cat_sorted_commands; 00740 00741 /* Sort by category, then by name. */ 00742 static int sort_by_category(const void *a_, const void *b_) 00743 { 00744 /* Typecasts, so it goes. */ 00745 ConsoleCommand * a = *((ConsoleCommand **)a_); 00746 ConsoleCommand * b = *((ConsoleCommand **)b_); 00747 00748 if (a->cat == b->cat) { 00749 return strcmp(a->name, b->name); 00750 } 00751 00752 return a->cat - b->cat; 00753 } 00754 00755 void init_commands(void) { 00756 int i; 00757 00758 #ifdef TOOLKIT_COMMANDS 00759 init_toolkit_commands(); 00760 00761 /* TODO I dunno ... go through the list and print commands without helps? */ 00762 num_commands = CommonCommandsSize + ToolkitCommandsSize; 00763 #else 00764 num_commands = CommonCommandsSize; 00765 #endif 00766 00767 /* Make a list of (pointers to statically allocated!) all the commands. 00768 We have a list; the toolkit has a 00769 ToolkitCommands and ToolkitCommandsSize, initialized before calling this. 00770 */ 00771 00772 /* XXX Leak! */ 00773 name_sorted_commands = malloc(sizeof(ConsoleCommand *) * num_commands); 00774 00775 for (i = 0; i < CommonCommandsSize; i++) { 00776 name_sorted_commands[i] = &CommonCommands[i]; 00777 } 00778 00779 #ifdef TOOLKIT_COMMANDS 00780 for(i = 0; i < ToolkitCommandsSize; i++) { 00781 name_sorted_commands[CommonCommandsSize + i] = &ToolkitCommands[i]; 00782 } 00783 #endif 00784 00785 /* Sort them. */ 00786 qsort(name_sorted_commands, num_commands, sizeof(ConsoleCommand *), sort_by_name); 00787 00788 /* Copy the list, then sort it by category. */ 00789 cat_sorted_commands = malloc(sizeof(ConsoleCommand *) * num_commands); 00790 00791 memcpy(cat_sorted_commands, name_sorted_commands, sizeof(ConsoleCommand *) * num_commands); 00792 00793 qsort(cat_sorted_commands, num_commands, sizeof(ConsoleCommand *), sort_by_category); 00794 00795 /* TODO Add to the list of tab-completion items. */ 00796 } 00797 00798 #ifndef tolower 00799 #define tolower(C) (((C) >= 'A' && (C) <= 'Z')? (C) - 'A' + 'a': (C)) 00800 #endif 00801 00802 const ConsoleCommand * find_command(const char * cmd) { 00803 ConsoleCommand ** asp_p = NULL, dummy; 00804 ConsoleCommand * dummy_p; 00805 ConsoleCommand * asp; 00806 char *cp, *cmd_cpy; 00807 cmd_cpy = strdup(cmd); 00808 00809 for (cp=cmd_cpy; *cp; cp++) { 00810 *cp =tolower(*cp); 00811 } 00812 00813 dummy.name = cmd_cpy; 00814 dummy_p = &dummy; 00815 asp_p = bsearch( 00816 (void *)&dummy_p, 00817 (void *)name_sorted_commands, 00818 num_commands, 00819 sizeof(ConsoleCommand *), 00820 sort_by_name); 00821 00822 if (asp_p == NULL) 00823 { 00824 free(cmd_cpy); 00825 return NULL; 00826 } 00827 00828 asp = *asp_p; 00829 00830 /* TODO The server's find_command() searches first the commands, 00831 then the emotes. We might have to do something similar someday, too. */ 00832 /* if (asp == NULL) search something else? */ 00833 00834 free(cmd_cpy); 00835 00836 return asp; 00837 } 00838 00839 00846 ConsoleCommand ** get_cat_sorted_commands(void) { 00847 return cat_sorted_commands; 00848 } 00849 00850 00851 /* Tries to handle command cp (with optional params in cpnext, which may be null) 00852 * as a local command. If this was a local command, returns true to indicate 00853 * command was handled. 00854 * This code was moved from extended_command so scripts ca issue local commands 00855 * to handle keybindings or anything else. 00856 */ 00857 00858 int handle_local_command(const char* cp, const char * cpnext) { 00859 const ConsoleCommand * cc = NULL; 00860 00861 cc = find_command(cp); 00862 00863 if (cc == NULL) { 00864 return FALSE; 00865 } 00866 00867 if (cc->dofunc == NULL) { 00868 char buf[MAX_BUF]; 00869 00870 snprintf(buf, MAX_BUF - 1, "Client command %s has no implementation!", cc->name); 00871 draw_info(buf, NDI_RED); 00872 00873 return FALSE; 00874 } 00875 00876 cc->dofunc(cpnext); 00877 00878 return TRUE; 00879 } 00880 00881 /* This is an extended command (ie, 'who, 'whatever, etc). In general, 00882 * we just send the command to the server, but there are a few that 00883 * we care about (bind, unbind) 00884 * 00885 * The command passed to us can not be modified - if it is a keybinding, 00886 * we get passed the string that is that binding - modifying it effectively 00887 * changes the binding. 00888 */ 00889 00890 void extended_command(const char *ocommand) { 00891 const char *cp = ocommand; 00892 char *cpnext, command[MAX_BUF]; 00893 00894 if ((cpnext = strchr(cp, ' '))!=NULL) { 00895 int len = cpnext - ocommand; 00896 if (len > (MAX_BUF -1 )) len = MAX_BUF-1; 00897 00898 strncpy(command, ocommand, len); 00899 command[len] = '\0'; 00900 cp = command; 00901 while (*cpnext == ' ') 00902 cpnext++; 00903 if (*cpnext == 0) 00904 cpnext = NULL; 00905 } 00906 00907 /* cp now contains the command (everything before first space), 00908 * and cpnext contains everything after that first space. cpnext 00909 * could be NULL. 00910 */ 00911 #ifdef HAVE_LUA 00912 if ( script_lua_command(cp, cpnext) ) 00913 return; 00914 #endif 00915 00916 /* If this isn't a client-side command, send it to the server. */ 00917 if (!handle_local_command(cp, cpnext)) { 00918 /* just send the command(s) (if `ocommand' is a compound command */ 00919 /* then split it and send each part seperately */ 00920 /* TODO Remove this from the server; end of commands.c. */ 00921 strncpy(command, ocommand, MAX_BUF-1); 00922 command[MAX_BUF-1]=0; 00923 cp = strtok(command, ";"); 00924 while ( cp ) { 00925 while( *cp == ' ' ) cp++; /* throw out leading spaces; server 00926 does not like them */ 00927 send_command(cp, cpl.count, 0); 00928 cp = strtok(NULL, ";"); 00929 } 00930 } 00931 } 00932 00933 00934 /* ------------------------------------------------------------------ */ 00935 00936 /* This list is used for the 'tab' completion, and nothing else. 00937 * Therefore, if it is out of date, it isn't that terrible, but 00938 * ideally it should stay somewhat up to date with regards to 00939 * the commands the server supports. 00940 */ 00941 00942 /* TODO Dynamically generate. */ 00943 00944 static const char *const commands[] = { 00945 "accuse", "afk", "apply", "applymode", "archs", "beg", "bleed", "blush", 00946 "body", "bounce", "bow", "bowmode", "brace", "build", "burp", "cackle", "cast", 00947 "chat", "chuckle", "clap", "cointoss", "cough", "cringe", "cry", "dance", 00948 "disarm", "dm", "dmhide", "drop", "dropall", "east", "examine", "explore", 00949 "fire", "fire_stop", "fix_me", "flip", "frown", "gasp", "get", "giggle", 00950 "glare", "grin", "groan", "growl", "gsay", "help", "hiccup", "hiscore", "hug", 00951 "inventory", "invoke", "killpets", "kiss", "laugh", "lick", "listen", "logs", 00952 "mapinfo", "maps", "mark", "me", "motd", "nod", "north", "northeast", 00953 "northwest", "orcknuckle", "output-count", "output-sync", "party", "peaceful", 00954 "petmode", "pickup", "players", "poke", "pout", "prepare", "printlos", "puke", 00955 "quests", "quit", "ready_skill", "rename", "reply", "resistances", 00956 "rotateshoottype", "run", "run_stop", "save", "say", "scream", "search", 00957 "search-items", "shake", "shiver", "shout", "showpets", "shrug", "shutdown", 00958 "sigh", "skills", "slap", "smile", "smirk", "snap", "sneeze", "snicker", 00959 "sniff", "snore", "sound", "south", "southeast", "southwest", "spit", 00960 "statistics", "stay", "strings", "strut", "sulk", "take", "tell", "thank", 00961 "think", "throw", "time", "title", "twiddle", "use_skill", "usekeys", 00962 "version", "wave", "weather", "west", "whereabouts", "whereami", "whistle", 00963 "who", "wimpy", "wink", "yawn", 00964 }; 00965 #define NUM_COMMANDS ((int)(sizeof(commands) / sizeof(char*))) 00966 00967 /* Player has entered 'command' and hit tab to complete it. 00968 * See if we can find a completion. Returns matching 00969 * command. Returns NULL if no command matches. 00970 */ 00971 00972 const char * complete_command(const char *command) 00973 { 00974 int i, len, display; 00975 const char *match; 00976 static char result[64]; 00977 char list[500]; 00978 00979 len = strlen(command); 00980 00981 if (len == 0) 00982 return NULL; 00983 00984 display = 0; 00985 strcpy(list, "Matching commands:"); 00986 00987 /* TODO Partial match, e.g.: 00988 If the completion list was: 00989 wear 00990 wet #? 00991 00992 If we type 'w' then hit tab, put in the e. 00993 00994 Basically part of bash (readline?)'s behaviour. 00995 */ 00996 00997 match = NULL; 00998 00999 /* check server side commands */ 01000 for (i=0; i<NUM_COMMANDS; i++) { 01001 if (!strncmp(command, commands[i], len)) { 01002 if (display) { 01003 snprintf(list + strlen(list), 499 - strlen(list), " %s", commands[i]); 01004 } else if (match != NULL) { 01005 display = 1; 01006 snprintf(list + strlen(list), 499 - strlen(list), " %s %s", match, commands[i]); 01007 match = NULL; 01008 } else 01009 match = commands[i]; 01010 } 01011 } 01012 01013 /* check client side commands */ 01014 for (i=0; i<CommonCommandsSize; i++) { 01015 if (!strncmp(command, CommonCommands[i].name, len)) { 01016 if (display) { 01017 snprintf(list + strlen(list), 499 - strlen(list), " %s", CommonCommands[i].name); 01018 } else if (match != NULL) { 01019 display = 1; 01020 snprintf(list + strlen(list), 499 - strlen(list), " %s %s", match, CommonCommands[i].name); 01021 match = NULL; 01022 } else 01023 match = CommonCommands[i].name; 01024 } 01025 } 01026 01027 if (match == NULL) { 01028 if (display) { 01029 strncat(list, "\n", 499 - strlen(list)); 01030 draw_info(list, NDI_BLACK); 01031 } 01032 else 01033 draw_info("No matching command.\n", NDI_BLACK); 01034 /* No match. */ 01035 return NULL; 01036 } 01037 01038 /* 01039 * Append a space to allow typing arguments. For commands without arguments 01040 * the excess space should be stripped off automatically. 01041 */ 01042 snprintf(result, sizeof(result), "%s ", match); 01043 01044 return result; 01045 } 01046 01047 #endif /* CPROTO */