Crossfire Client, Trunk  R19307
p_cmd.c
Go to the documentation of this file.
00001 /*
00002  * Crossfire -- cooperative multi-player graphical RPG and adventure game
00003  *
00004  * Copyright (c) 1999-2013 Mark Wedel and the Crossfire Development Team
00005  * Copyright (c) 1992 Frank Tore Johansen
00006  *
00007  * Crossfire is free software and comes with ABSOLUTELY NO WARRANTY. You are
00008  * welcome to redistribute it under certain conditions. For details, please
00009  * see COPYING and LICENSE.
00010  *
00011  * The authors can be reached via e-mail at <crossfire@metalforge.org>.
00012  */
00013 
00019 #ifndef CPROTO
00020 /* use declarations from p_cmd.h instead of doing make proto on this file */
00021 
00022 #include <client.h>
00023 #include <external.h>
00024 #include <script.h>
00025 #include <p_cmd.h>
00026 
00032 /* TODO This should really be under /help commands or something... */
00033 
00034 /* This dynamically generates a list from the ConsoleCommand list. */
00035 #undef CLIENTHELP_LONG_LIST
00036 
00037 /*
00038 long-list:
00039 category
00040 name - description
00041 name - description
00042 ...
00043 
00044 not long list:
00045 category
00046 name name name ...
00047 */
00048 
00049 #undef HELP_USE_COLOR
00050 #ifdef HELP_USE_COLOR
00051 #error Oops, need to put them back.
00052 #else
00053 #define H1(a) draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, a)
00054 #define H2(a) draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, a)
00055 #define LINE(a) draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, a)
00056 #endif
00057 
00058 #define assumed_wrap get_info_width()
00059 
00060 /* TODO Help topics other than commands? Refer to other documents? */
00061 
00065 static void do_clienthelp_list(void)
00066 {
00067     ConsoleCommand ** commands_array;
00068     ConsoleCommand * commands_copy;
00069     int i;
00070     CommCat current_cat = COMM_CAT_MISC;
00071 #ifndef CLIENTHELP_LONG_LIST
00072     char line_buf[MAX_BUF];
00073     size_t line_len = 0;
00074 
00075     line_buf[0] = '\0';
00076 #endif
00077 
00078     commands_array = get_cat_sorted_commands();
00079 
00080     /* Now we have a nice sorted list. */
00081 
00082     H1(" === Client Side Commands === ");
00083 
00084     for (i = 0; i < get_num_commands(); i++) {
00085         commands_copy = commands_array[i];
00086 
00087         /* Should be LOG_SPAM but I'm too lazy to tweak it. */
00088         /* LOG(LOG_INFO, "p_cmd::do_clienthelp_list", "%s Command %s", get_category_name(commands_copy->cat), commands_copy->name); */
00089 
00090         if (commands_copy->cat != current_cat) {
00091             char buf[MAX_BUF];
00092 
00093 #ifndef CLIENTHELP_LONG_LIST
00094             if (line_len > 0) {
00095                 LINE(line_buf);
00096                 line_buf[0] = '\0';
00097                 line_len = 0;
00098             }
00099 #endif
00100 
00101 #ifdef HELP_USE_COLOR
00102             snprintf(buf, MAX_BUF - 1, "%s Commands:", get_category_name(commands_copy->cat));
00103 #else
00104             snprintf(buf, MAX_BUF - 1, " --- %s Commands --- ", get_category_name(commands_copy->cat));
00105 #endif
00106 
00107             H2(buf);
00108             current_cat = commands_copy->cat;
00109         }
00110 
00111 #ifdef CLIENTHELP_LONG_LIST
00112         if (commands_copy->desc != NULL) {
00113             char buf[MAX_BUF];
00114             snprintf(buf, MAX_BUF - 1, "%s - %s", commands_copy->name, commands_copy->desc);
00115             LINE(buf);
00116         } else {
00117             LINE(commands_copy->name);
00118         }
00119 #else
00120         {
00121             size_t name_len;
00122 
00123             name_len = strlen(commands_copy->name);
00124 
00125             if (strlen(commands_copy->name) > MAX_BUF) {
00126                 LINE(commands_copy->name);
00127             } else if (name_len > assumed_wrap) {
00128                 LINE(line_buf);
00129                 LINE(commands_copy->name);
00130                 line_len = 0;
00131             } else if (line_len + name_len > assumed_wrap) {
00132                 LINE(line_buf);
00133                 strncpy(line_buf, commands_copy->name, name_len + 1);
00134                 line_len = name_len;
00135             } else {
00136                 if (line_len > 0) {
00137                     strncat(line_buf, " ", 2);
00138                     line_len += 1;
00139                 }
00140                 strncat(line_buf, commands_copy->name, name_len + 1);
00141                 line_len += name_len;
00142             }
00143         }
00144 #endif
00145     }
00146 
00147 #ifndef CLIENTHELP_LONG_LIST
00148     if (line_len) {
00149         LINE(line_buf);
00150     }
00151 #endif
00152 }
00153 
00158 static void show_help(const ConsoleCommand *cc) {
00159     char buf[MAX_BUF];
00160 
00161     if (cc->desc != NULL) {
00162         snprintf(buf, MAX_BUF - 1, "%s - %s", cc->name, cc->desc);
00163         H2(buf);
00164     } else {
00165         snprintf(buf, MAX_BUF - 1, "Help for '%s':", cc->name);
00166         H2(buf);
00167     }
00168 
00169     if (cc->helpfunc != NULL) {
00170         const char *long_help = NULL;
00171         long_help = cc->helpfunc();
00172 
00173         if (long_help != NULL) {
00174             LINE(long_help);
00175         } else {
00176             LINE("Extended help for this command is broken.");
00177         }
00178     } else {
00179         LINE("No extended help is available for this command.");
00180     }
00181 }
00182 
00187 static void do_clienthelp(const char * arg)
00188 {
00189     const ConsoleCommand * cc;
00190 
00191     if (!arg || !strlen(arg)) {
00192         do_clienthelp_list();
00193         return;
00194     }
00195 
00196     cc = find_command(arg);
00197 
00198     if (cc == NULL) {
00199         char buf[MAX_BUF];
00200         snprintf(buf, MAX_BUF - 1, "clienthelp: Unknown command %s.", arg);
00201         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, buf);
00202         return;
00203     }
00204 
00205     show_help(cc);
00206 
00207 }
00208 
00212 static const char * help_clienthelp(void)
00213 {
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 
00233 static void do_serverhelp(const char * arg)
00234 {
00235 
00236     if (arg) {
00237         char buf[MAX_BUF];
00238         snprintf(buf, sizeof(buf), "help %s", arg);
00239         /* maybe not a must send, but we probably don't want to drop it */
00240         send_command(buf, -1, 1);
00241     } else {
00242         send_command("help", -1, 1); /* TODO make install in server branch doesn't install def_help. */
00243     }
00244 }
00245 
00249 static const char * help_serverhelp(void)
00250 {
00251     return
00252         "Syntax:\n"
00253         "\n"
00254         "    serverhelp\n"
00255         "    serverhelp <command>\n"
00256         "\n"
00257         "Fetches help from the server.\n"
00258         "\n"
00259         "Note that currently nothing can be done (without a recompile) if a "
00260         "client command masks a server command.\n"
00261         "\n"
00262         "See also: clienthelp, help.";
00263 }
00264 
00269 static void command_help(const char *cpnext)
00270 {
00271     if (cpnext) {
00272         const ConsoleCommand * cc;
00273         char buf[MAX_BUF];
00274 
00275         cc = find_command(cpnext);
00276         if (cc != NULL) {
00277             show_help(cc);
00278         } else  {
00279             snprintf(buf, sizeof(buf), "help %s", cpnext);
00280             /* maybe not a must send, but we probably don't want to drop it */
00281             send_command(buf, -1, 1);
00282         }
00283     } else {
00284         do_clienthelp_list();
00285         /* Now fetch (in theory) command list from the server.
00286         TODO Protocol command - feed it to the tab completer.
00287 
00288         Nope! It effectivey fetches '/help commands for commands'.
00289         */
00290         send_command("help", -1, 1); /* TODO make install in server branch doesn't install def_help. */
00291     }
00292 }
00293 
00297 static const char * help_help(void)
00298 {
00299     return
00300         "Syntax:\n"
00301         "\n"
00302         "    help\n"
00303         "    help <topic>\n"
00304         "\n"
00305         "Without any arguments, displays a list of client-side "
00306         "commands, and fetches the without-arguments help from "
00307         "the server.\n"
00308         "\n"
00309         "With arguments, first checks if there's a client command "
00310         "named <topic>. If there is, display it's help. If there "
00311         "isn't, send the topic to the server.\n"
00312         "\n"
00313         "See also: clienthelp, serverhelp.";
00314 }
00315  /* EndOf PCmdHelpCommands
00318  */
00319 
00320 /*
00321  * Other commands.
00322  */
00323 
00328 static void set_command_window(const char *cpnext)
00329 {
00330     if (!cpnext) {
00331         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00332                       "cwindow command requires a number parameter");
00333     } else {
00334         want_config[CONFIG_CWINDOW] = atoi(cpnext);
00335         if (want_config[CONFIG_CWINDOW]<1 || want_config[CONFIG_CWINDOW]>127) {
00336             want_config[CONFIG_CWINDOW]=COMMAND_WINDOW;
00337         } else {
00338             use_config[CONFIG_CWINDOW] = want_config[CONFIG_CWINDOW];
00339         }
00340     }
00341 }
00342 
00347 static void command_foodbeep(const char *cpnext)
00348 {
00349     (void)cpnext; /* __UNUSED__ */
00350     if (want_config[CONFIG_FOODBEEP]) {
00351         want_config[CONFIG_FOODBEEP]=0;
00352         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00353                       "Warning bell when low on food disabled");
00354     } else {
00355         want_config[CONFIG_FOODBEEP]=1;
00356         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00357                       "Warning bell when low on food enabled");
00358     }
00359     use_config[CONFIG_FOODBEEP] = want_config[CONFIG_FOODBEEP];
00360 }
00361 
00366 const char * get_category_name(CommCat cat)
00367 {
00368     const char * cat_name;
00369 
00370     /* HACK Need to keep this in sync. with player.h */
00371     switch(cat) {
00372     case COMM_CAT_MISC:
00373         cat_name = "Miscellaneous";
00374         break;
00375     case COMM_CAT_HELP:
00376         cat_name = "Help";
00377         break;
00378     case COMM_CAT_INFO:
00379         cat_name = "Informational";
00380         break;
00381     case COMM_CAT_SETUP:
00382         cat_name = "Configuration";
00383         break;
00384     case COMM_CAT_SCRIPT:
00385         cat_name = "Scripting";
00386         break;
00387     case COMM_CAT_DEBUG:
00388         cat_name = "Debugging";
00389         break;
00390     default:
00391         cat_name = "PROGRAMMER ERROR";
00392         break;
00393     }
00394 
00395     return cat_name;
00396 }
00397 
00398 /*
00399  * Command table.
00400  *
00401  * Implementation basically stolen verbatim from the server.
00402  */
00403 
00404 /* "Typecasters" (and some forwards) */
00405 
00410 static void do_script_list(const char * ignored)
00411 {
00412     script_list();
00413 }
00414 
00419 static void do_clearinfo(const char * ignored)
00420 {
00421     menu_clear();
00422 }
00423 
00428 static void do_disconnect(const char * ignored)
00429 {
00430     close_server_connection();
00431 
00432     /* the gtk clients need to do some cleanup logic - otherwise,
00433      * they start hogging CPU.
00434      */
00435     cleanup_connection();
00436     return;
00437 }
00438 
00439 #ifdef HAVE_DMALLOC_H
00440 #ifndef DMALLOC_VERIFY_NOERROR
00441 #define DMALLOC_VERIFY_NOERROR  1
00442 #endif
00443 
00447 static void do_dmalloc(const char * ignored)
00448 {
00449     if (dmalloc_verify(NULL)==DMALLOC_VERIFY_NOERROR)
00450         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00451                       "Heap checks out OK");
00452     else
00453         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR,
00454                       "Heap corruption detected");
00455 }
00456 #endif
00457 
00462 static void do_inv(const char * ignored)
00463 {
00464     print_inventory (cpl.ob);
00465 }
00466 
00467 static void do_magicmap(const char * ignored)
00468 {
00469     cpl.showmagic=1;
00470     draw_magic_map();
00471 }
00472 
00477 static void do_metaserver(const char * ignored)
00478 {
00479     if (!metaserver_get_info(meta_server, meta_port)) {
00480         metaserver_show(FALSE);
00481     } else
00482         draw_ext_info(
00483             NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00484             "Unable to get metaserver information.");
00485 }
00486 
00491 static void do_savedefaults(const char * ignored)
00492 {
00493     save_defaults();
00494 }
00495 
00500 static void do_savewinpos(const char * ignored)
00501 {
00502     save_winpos();
00503 }
00504 
00509 static void do_take(const char * used)
00510 {
00511     command_take("take", used); /* I dunno why they want it. */
00512 }
00513 
00518 static void do_num_free_items(const char * ignored)
00519 {
00520     LOG(LOG_INFO,"common::extended_command","num_free_items=%d", num_free_items());
00521 }
00522 
00523 static void do_clienthelp(const char * arg); /* Forward. */
00524 
00525 /* Help "typecasters". */
00526 #include "chelp.h"
00527 
00531 static const char * help_bind(void)
00532 {
00533     return HELP_BIND_LONG;
00534 }
00535 
00539 static const char * help_unbind(void)
00540 {
00541     return HELP_UNBIND_LONG;
00542 }
00543 
00547 static const char * help_magicmap(void)
00548 {
00549     return HELP_MAGICMAP_LONG;
00550 }
00551 
00555 static const char * help_inv(void)
00556 {
00557     return HELP_INV_LONG;
00558 }
00559 
00563 static const char * help_cwindow(void)
00564 {
00565     return
00566         "Syntax:\n"
00567         "\n"
00568         "    cwindow <val>\n"
00569         "\n"
00570         "set size of command"
00571         "window (if val is exceeded"
00572         "client won't send new"
00573         "commands to server\n\n"
00574         "(What does this mean, 'put a lid on it'?) TODO";
00575 }
00576 
00580 static const char * help_script(void) {
00581     return
00582         "Syntax: script <path>\n\n"
00583         "Start an executable client script located at <path>. For details on "
00584         "client-side scripting, please see the Crossfire Wiki.";
00585 }
00586 
00590 static const char * help_scripttell(void)
00591 {
00592     return
00593         "Syntax:\n"
00594         "\n"
00595         "    scripttell <yourname> <data>\n"
00596         "\n"
00597         "?";
00598 }
00599 
00600 /* Toolkit-dependent. */
00601 
00605 static const char * help_savewinpos(void)
00606 {
00607     return
00608         "Syntax:\n"
00609         "\n"
00610         "    savewinpos\n"
00611         "\n"
00612         "save window positions - split windows mode only.";
00613 }
00614 
00618 static const char * help_metaserver(void)
00619 {
00620     /* TODO Add command_escape() where appropriate. On the other
00621     hand, that can lead to a meaningless syntax-display API.*/
00622 
00623     return
00624         "Syntax:\n"
00625         "\n"
00626         "    metaserver\n"
00627         "\n"
00628         "Get updated list of servers "
00629         "from the metaserver and show it."
00630         "This is the same information that the client "
00631         "uses to show a list of servers when it starts.\n"
00632         "\n"
00633         "Warning: This command may freeze the client until it gets the list.";
00634 }
00635 
00639 static const char * help_scriptkill(void)
00640 {
00641     return
00642         "Syntax:\n"
00643         "\n"
00644         "    scriptkill <name>\n"
00645         "\n"
00646         "Stop scripts named <name>.\n"
00647         "(Not guaranteed to work?)";
00648 }
00649 
00653 static const char * help_showweight(void)
00654 {
00655     return
00656         "Syntax:\n"
00657         "\n"
00658         "    showweight\n"
00659         "    showweight inventory\n"
00660         "    showweight look\n"
00661         "\n"
00662         "(Or any prefix of the arguments.)"
00663         "Toggles if you see the weight of"
00664         "items in your inventory (also if"
00665         "no argument given) or your"
00666         "look-window.";
00667 }
00668 
00669 /*
00670 *       draw_ext_info(NDI_NAVY, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00671             "Information Commands");*
00672         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00673             " inv         - *recursively* print your");
00674         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00675             "               inventory - includes containers.");
00676         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00677             " mapredraw, showinfo, take");
00678         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00679             " help        - show this message");
00680         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00681             " help <command> - get more information on a");
00682         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00683             "                command (Server command only?)");
00684         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00685             " showicon    - draw status icons in");
00686         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00687             "               inventory window");
00688         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00689             " showweight  - show weight in inventory");
00690         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00691             "               look windows");
00692         draw_ext_info(NDI_NAVY, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00693             "Scripting Commands");
00694         draw_ext_info(NDI_NAVY, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00695             "Client Side Debugging Commands");
00696 #ifdef HAVE_DMALLOC_H
00697         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00698             " dmalloc     - Check heap?");
00699 #endif
00700 */
00701 
00702 /* TODO Wrap these? Um. */
00703 static ConsoleCommand CommonCommands[] = {
00704     /* From player.h:
00705         name, cat,
00706         func, helpfunc,
00707         long_desc
00708     */
00709 
00710     {
00711         "autorepeat", COMM_CAT_MISC,
00712         set_autorepeat, NULL,
00713         "toggle autorepeat" /* XXX Eh? */
00714     },
00715 
00716     {
00717         "bind", COMM_CAT_SETUP,
00718         bind_key, help_bind,
00719         HELP_BIND_SHORT
00720     },
00721 
00722     {
00723         "script", COMM_CAT_SCRIPT,
00724         script_init, help_script,
00725         NULL
00726     },
00727 #ifdef HAVE_LUA
00728     {
00729         "lua_load", COMM_CAT_SCRIPT,
00730         script_lua_load, NULL, NULL
00731     },
00732 
00733     {
00734         "lua_list", COMM_CAT_SCRIPT,
00735         script_lua_list, NULL, NULL
00736     },
00737 
00738     {
00739         "lua_kill", COMM_CAT_SCRIPT,
00740         script_lua_kill, NULL, NULL
00741     },
00742 #endif
00743     {
00744         "scripts", COMM_CAT_SCRIPT,
00745         do_script_list, NULL,
00746         "List the running scripts(?)"
00747     },
00748 
00749     {
00750         "scriptkill", COMM_CAT_SCRIPT,
00751         script_kill, help_scriptkill,
00752         NULL
00753     },
00754 
00755     {
00756         "scripttell", COMM_CAT_SCRIPT,
00757         script_tell, help_scripttell,
00758         NULL
00759     },
00760 
00761     {
00762         "clearinfo", COMM_CAT_MISC,
00763         do_clearinfo, NULL,
00764         "clear the info window"
00765     },
00766 
00767     {
00768         "cwindow", COMM_CAT_SETUP,
00769         set_command_window, help_cwindow,
00770         NULL
00771     },
00772 
00773     {
00774         "disconnect", COMM_CAT_MISC,
00775         do_disconnect, NULL,
00776         "close connection to server"
00777     },
00778 
00779 
00780 #ifdef HAVE_DMALLOC_H
00781     {
00782         "dmalloc", COMM_CAT_DEBUG,
00783         do_dmalloc, NULL,
00784         NULL
00785     },
00786 #endif
00787 
00788     {
00789         "foodbeep", COMM_CAT_SETUP,
00790         command_foodbeep, NULL,
00791         "toggle audible low on food warning"
00792 
00793     },
00794 
00795     {
00796         "help", COMM_CAT_HELP,
00797         command_help, help_help,
00798         NULL
00799     },
00800 
00801     {
00802         "clienthelp", COMM_CAT_HELP,
00803         do_clienthelp, help_clienthelp,
00804         "Client-side command information"
00805     },
00806 
00807     {
00808         "serverhelp", COMM_CAT_HELP,
00809         do_serverhelp, help_serverhelp,
00810         "Server-side command information"
00811     },
00812 
00813     {
00814         "inv", COMM_CAT_DEBUG,
00815         do_inv, help_inv,
00816         HELP_INV_SHORT
00817     },
00818 
00819     {
00820         "magicmap", COMM_CAT_MISC,
00821         do_magicmap, help_magicmap,
00822         HELP_MAGICMAP_SHORT
00823     },
00824 
00825     {
00826         "metaserver", COMM_CAT_INFO,
00827         do_metaserver, help_metaserver,
00828         "Print 'metaserver information'. Warning - your client will pause."
00829     },
00830 
00831     {
00832         "savedefaults", COMM_CAT_SETUP,
00833         do_savedefaults, NULL,
00834         HELP_SAVEDEFAULTS_SHORT /* How do we make sure showicons stays on? */
00835     },
00836 
00837     {
00838         "savewinpos", COMM_CAT_SETUP,
00839         do_savewinpos, help_savewinpos,
00840         "Saves the position and sizes of windows." /* Panes? */
00841     },
00842 
00843     {
00844         "scroll", COMM_CAT_SETUP,
00845         set_scroll, NULL,
00846         "toggle scroll/wrap mode in info window"
00847     },
00848 
00849     {
00850         "showicon", COMM_CAT_SETUP,
00851         set_show_icon, NULL,
00852         "Toggles if you see the worn, locked, cursed etc state in the inventory pane."
00853     },
00854 
00855     {
00856         "showweight", COMM_CAT_SETUP,
00857         set_show_weight, help_showweight,
00858         "Toggles if you see item weights in inventory look windows."
00859     },
00860 
00861     {
00862         "take", COMM_CAT_MISC,
00863         do_take, NULL,
00864         NULL
00865     },
00866 
00867     {
00868         "unbind", COMM_CAT_SETUP,
00869         unbind_key, help_unbind,
00870         NULL
00871     },
00872 
00873     {
00874         "num_free_items", COMM_CAT_DEBUG,
00875         do_num_free_items, NULL,
00876         "log the number of free items?"
00877     },
00878     {
00879         "show", COMM_CAT_SETUP,
00880         command_show, NULL,
00881         "Change what items to show in inventory"
00882     },
00883 
00884 };
00885 
00886 const int CommonCommandsSize = sizeof(CommonCommands) / sizeof(ConsoleCommand);
00887 
00888 #ifdef TOOLKIT_COMMANDS
00889 extern ConsoleCommand ToolkitCommands[];
00890 
00891 extern const int ToolkitCommandsSize;
00892 #endif
00893 
00894 /* ------------------------------------------------------------------ */
00895 
00896 int num_commands;
00897 
00901 int get_num_commands(void)
00902 {
00903     return num_commands;
00904 }
00905 
00906 static ConsoleCommand ** name_sorted_commands;
00907 
00913 static int sort_by_name(const void * a_, const void * b_)
00914 {
00915     ConsoleCommand * a = *((ConsoleCommand **)a_);
00916     ConsoleCommand * b = *((ConsoleCommand **)b_);
00917 
00918     return strcmp(a->name, b->name);
00919 }
00920 
00921 static ConsoleCommand ** cat_sorted_commands;
00922 
00923 /* Sort by category, then by name. */
00924 
00930 static int sort_by_category(const void *a_, const void *b_)
00931 {
00932     /* Typecasts, so it goes. */
00933     ConsoleCommand * a = *((ConsoleCommand **)a_);
00934     ConsoleCommand * b = *((ConsoleCommand **)b_);
00935 
00936     if (a->cat == b->cat) {
00937         return strcmp(a->name, b->name);
00938     }
00939 
00940     return a->cat - b->cat;
00941 }
00942 
00946 void init_commands(void)
00947 {
00948     int i;
00949 
00950 #ifdef TOOLKIT_COMMANDS
00951     init_toolkit_commands();
00952 
00953     /* TODO I dunno ... go through the list and print commands without helps? */
00954     num_commands = CommonCommandsSize + ToolkitCommandsSize;
00955 #else
00956     num_commands = CommonCommandsSize;
00957 #endif
00958 
00959     /* Make a list of (pointers to statically allocated!) all the commands.
00960        We have a list; the toolkit has a
00961        ToolkitCommands and ToolkitCommandsSize, initialized before calling this.
00962     */
00963 
00964     /* XXX Leak! */
00965     name_sorted_commands = malloc(sizeof(ConsoleCommand *) * num_commands);
00966 
00967     for (i = 0; i < CommonCommandsSize; i++) {
00968         name_sorted_commands[i] = &CommonCommands[i];
00969     }
00970 
00971 #ifdef TOOLKIT_COMMANDS
00972     for(i = 0; i < ToolkitCommandsSize; i++) {
00973         name_sorted_commands[CommonCommandsSize + i] = &ToolkitCommands[i];
00974     }
00975 #endif
00976 
00977     /* Sort them. */
00978     qsort(name_sorted_commands, num_commands, sizeof(ConsoleCommand *), sort_by_name);
00979 
00980     /* Copy the list, then sort it by category. */
00981     cat_sorted_commands = malloc(sizeof(ConsoleCommand *) * num_commands);
00982 
00983     memcpy(cat_sorted_commands, name_sorted_commands, sizeof(ConsoleCommand *) * num_commands);
00984 
00985     qsort(cat_sorted_commands, num_commands, sizeof(ConsoleCommand *), sort_by_category);
00986 
00987     /* TODO Add to the list of tab-completion items. */
00988 }
00989 
00990 #ifndef tolower
00991 #define tolower(C)      (((C) >= 'A' && (C) <= 'Z')? (C) - 'A' + 'a': (C))
00992 #endif
00993 
00998 const ConsoleCommand * find_command(const char * cmd)
00999 {
01000     ConsoleCommand ** asp_p = NULL, dummy;
01001     ConsoleCommand * dummy_p;
01002     ConsoleCommand * asp;
01003     char *cp, *cmd_cpy;
01004     cmd_cpy = strdup(cmd);
01005 
01006     for (cp=cmd_cpy; *cp; cp++) {
01007         *cp =tolower(*cp);
01008     }
01009 
01010     dummy.name = cmd_cpy;
01011     dummy_p = &dummy;
01012     asp_p = bsearch(
01013                 (void *)&dummy_p,
01014                 (void *)name_sorted_commands,
01015                 num_commands,
01016                 sizeof(ConsoleCommand *),
01017                 sort_by_name);
01018 
01019     if (asp_p == NULL) {
01020         free(cmd_cpy);
01021         return NULL;
01022     }
01023 
01024     asp = *asp_p;
01025 
01026     /* TODO The server's find_command() searches first the commands,
01027     then the emotes. We might have to do something similar someday, too. */
01028     /* if (asp == NULL) search something else? */
01029 
01030     free(cmd_cpy);
01031 
01032     return asp;
01033 }
01034 
01039 ConsoleCommand ** get_cat_sorted_commands(void)
01040 {
01041     return cat_sorted_commands;
01042 }
01043 
01053 int handle_local_command(const char* cp, const char * cpnext)
01054 {
01055     const ConsoleCommand * cc = NULL;
01056 
01057     cc = find_command(cp);
01058 
01059     if (cc == NULL) {
01060         return FALSE;
01061     }
01062 
01063     if (cc->dofunc == NULL) {
01064         char buf[MAX_BUF];
01065 
01066         snprintf(buf, MAX_BUF - 1, "Client command %s has no implementation!", cc->name);
01067         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, buf);
01068 
01069         return FALSE;
01070     }
01071 
01072     cc->dofunc(cpnext);
01073 
01074     return TRUE;
01075 }
01076 
01088 void extended_command(const char *ocommand)
01089 {
01090     const char *cp = ocommand;
01091     char *cpnext, command[MAX_BUF];
01092 
01093     if ((cpnext = strchr(cp, ' '))!=NULL) {
01094         int len = cpnext - ocommand;
01095         if (len > (MAX_BUF -1 )) {
01096             len = MAX_BUF-1;
01097         }
01098 
01099         strncpy(command, ocommand, len);
01100         command[len] = '\0';
01101         cp = command;
01102         while (*cpnext == ' ') {
01103             cpnext++;
01104         }
01105         if (*cpnext == 0) {
01106             cpnext = NULL;
01107         }
01108     }
01109 
01110     /* cp now contains the command (everything before first space),
01111      * and cpnext contains everything after that first space.  cpnext
01112      * could be NULL.
01113      */
01114 #ifdef HAVE_LUA
01115     if ( script_lua_command(cp, cpnext) ) {
01116         return;
01117     }
01118 #endif
01119 
01120     /* If this isn't a client-side command, send it to the server. */
01121     if (!handle_local_command(cp, cpnext)) {
01122         /* just send the command(s)  (if `ocommand' is a compound command */
01123         /* then split it and send each part seperately */
01124         /* TODO Remove this from the server; end of commands.c. */
01125         strncpy(command, ocommand, MAX_BUF-1);
01126         command[MAX_BUF-1]=0;
01127         cp = strtok(command, ";");
01128         while ( cp ) {
01129             while( *cp == ' ' ) {
01130                 cp++;
01131             } /* throw out leading spaces; server
01132                                        does not like them */
01133             send_command(cp, cpl.count, 0);
01134             cp = strtok(NULL, ";");
01135         }
01136     }
01137 }
01138 
01139 /* ------------------------------------------------------------------ */
01140 
01141 /* This list is used for the 'tab' completion, and nothing else.
01142  * Therefore, if it is out of date, it isn't that terrible, but
01143  * ideally it should stay somewhat up to date with regards to
01144  * the commands the server supports.
01145  */
01146 
01147 /* TODO Dynamically generate. */
01148 
01149 static const char *const commands[] = {
01150     "accuse", "afk", "apply", "applymode", "archs", "beg", "bleed", "blush",
01151     "body", "bounce", "bow", "bowmode", "brace", "build", "burp", "cackle", "cast",
01152     "chat", "chuckle", "clap", "cointoss", "cough", "cringe", "cry", "dance",
01153     "disarm", "dm", "dmhide", "drop", "dropall", "east", "examine", "explore",
01154     "fire", "fire_stop", "fix_me", "flip", "frown", "gasp", "get", "giggle",
01155     "glare", "grin", "groan", "growl", "gsay", "help", "hiccup", "hiscore", "hug",
01156     "inventory", "invoke", "killpets", "kiss", "laugh", "lick", "listen", "logs",
01157     "mapinfo", "maps", "mark", "me", "motd", "nod", "north", "northeast",
01158     "northwest", "orcknuckle", "output-count", "output-sync", "party", "peaceful",
01159     "petmode", "pickup", "players", "poke", "pout", "prepare", "printlos", "puke",
01160     "quests", "quit", "ready_skill", "rename", "reply", "resistances",
01161     "rotateshoottype", "run", "run_stop", "save", "say", "scream", "search",
01162     "search-items", "shake", "shiver", "shout", "showpets", "shrug", "shutdown",
01163     "sigh", "skills", "slap", "smile", "smirk", "snap", "sneeze", "snicker",
01164     "sniff", "snore", "sound", "south", "southeast", "southwest", "spit",
01165     "statistics", "stay", "strings", "strut", "sulk", "take", "tell", "thank",
01166     "think", "throw", "time", "title", "twiddle", "use_skill", "usekeys",
01167     "version", "wave", "weather", "west", "whereabouts", "whereami", "whistle",
01168     "who", "wimpy", "wink", "yawn",
01169 };
01170 #define NUM_COMMANDS ((int)(sizeof(commands) / sizeof(char*)))
01171 
01179 const char * complete_command(const char *command)
01180 {
01181     int i, len, display;
01182     const char *match;
01183     static char result[64];
01184     char list[500];
01185 
01186     len = strlen(command);
01187 
01188     if (len == 0) {
01189         return NULL;
01190     }
01191 
01192     display = 0;
01193     strcpy(list, "Matching commands:");
01194 
01195     /* TODO Partial match, e.g.:
01196          If the completion list was:
01197            wear
01198            wet #?
01199 
01200          If we type 'w' then hit tab, put in the e.
01201 
01202        Basically part of bash (readline?)'s behaviour.
01203     */
01204 
01205     match = NULL;
01206 
01207     /* check server side commands */
01208     for (i=0; i<NUM_COMMANDS; i++) {
01209         if (!strncmp(command, commands[i], len)) {
01210             if (display) {
01211                 snprintf(list + strlen(list), 499 - strlen(list), " %s", commands[i]);
01212             } else if (match != NULL) {
01213                 display = 1;
01214                 snprintf(list + strlen(list), 499 - strlen(list), " %s %s", match, commands[i]);
01215                 match = NULL;
01216             } else {
01217                 match = commands[i];
01218             }
01219         }
01220     }
01221 
01222     /* check client side commands */
01223     for (i=0; i<CommonCommandsSize; i++) {
01224         if (!strncmp(command, CommonCommands[i].name, len)) {
01225             if (display) {
01226                 snprintf(list + strlen(list), 499 - strlen(list), " %s", CommonCommands[i].name);
01227             } else if (match != NULL) {
01228                 display = 1;
01229                 snprintf(list + strlen(list), 499 - strlen(list), " %s %s", match, CommonCommands[i].name);
01230                 match = NULL;
01231             } else {
01232                 match = CommonCommands[i].name;
01233             }
01234         }
01235     }
01236 
01237     if (match == NULL) {
01238         if (display) {
01239             strncat(list, "\n", 499 - strlen(list));
01240             draw_ext_info(
01241                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, list);
01242         } else
01243             draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
01244                           "No matching command.\n");
01245         /* No match. */
01246         return NULL;
01247     }
01248 
01249     /*
01250      * Append a space to allow typing arguments. For commands without arguments
01251      * the excess space should be stripped off automatically.
01252      */
01253     snprintf(result, sizeof(result), "%s ", match);
01254 
01255     return result;
01256 }
01257 
01258 #endif /* CPROTO */