Crossfire Client, Trunk  R18666
/home/leaf/crossfire/client/trunk/common/p_cmd.c
Go to the documentation of this file.
00001 /*
00002     Crossfire client, a client program for the crossfire program.
00003 
00004     Copyright (C) 2005-2010 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 
00028 #ifndef CPROTO
00029 /* use declarations from p_cmd.h instead of doing make proto on this file */
00030 
00031 #include <client.h>
00032 #include <external.h>
00033 #include <script.h>
00034 #include <p_cmd.h>
00035 
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_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, a)
00063 #define H2(a) draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, a)
00064 #define LINE(a) draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, a)
00065 #endif
00066 
00067 #define assumed_wrap get_info_width()
00068 
00069 /* TODO Help topics other than commands? Refer to other documents? */
00070 
00074 static void do_clienthelp_list(void) {
00075     ConsoleCommand ** commands_array;
00076     ConsoleCommand * commands_copy;
00077     int i;
00078     CommCat current_cat = COMM_CAT_MISC;
00079 #ifndef CLIENTHELP_LONG_LIST
00080     char line_buf[MAX_BUF];
00081     size_t line_len = 0;
00082 
00083     line_buf[0] = '\0';
00084 #endif
00085 
00086     commands_array = get_cat_sorted_commands();
00087 
00088     /* Now we have a nice sorted list. */
00089 
00090     H1(" === Client Side Commands === ");
00091 
00092     for (i = 0; i < get_num_commands(); i++) {
00093         commands_copy = commands_array[i];
00094 
00095         /* Should be LOG_SPAM but I'm too lazy to tweak it. */
00096         /* LOG(LOG_INFO, "p_cmd::do_clienthelp_list", "%s Command %s", get_category_name(commands_copy->cat), commands_copy->name); */
00097 
00098         if (commands_copy->cat != current_cat) {
00099             char buf[MAX_BUF];
00100 
00101 #ifndef CLIENTHELP_LONG_LIST
00102             if (line_len > 0) {
00103                 LINE(line_buf);
00104                 line_buf[0] = '\0';
00105                 line_len = 0;
00106             }
00107 #endif
00108 
00109 #ifdef HELP_USE_COLOR
00110             snprintf(buf, MAX_BUF - 1, "%s Commands:", get_category_name(commands_copy->cat));
00111 #else
00112             snprintf(buf, MAX_BUF - 1, " --- %s Commands --- ", get_category_name(commands_copy->cat));
00113 #endif
00114 
00115             H2(buf);
00116             current_cat = commands_copy->cat;
00117         }
00118 
00119 #ifdef CLIENTHELP_LONG_LIST
00120         if (commands_copy->desc != NULL) {
00121             char buf[MAX_BUF];
00122             snprintf(buf, MAX_BUF - 1, "%s - %s", commands_copy->name, commands_copy->desc);
00123             LINE(buf);
00124         } else {
00125             LINE(commands_copy->name);
00126         }
00127 #else
00128         {
00129             size_t name_len;
00130 
00131             name_len = strlen(commands_copy->name);
00132 
00133             if (strlen(commands_copy->name) > MAX_BUF) {
00134                 LINE(commands_copy->name);
00135             } else if (name_len > assumed_wrap) {
00136                 LINE(line_buf);
00137                 LINE(commands_copy->name);
00138                 line_len = 0;
00139             } else if (line_len + name_len > assumed_wrap) {
00140                 LINE(line_buf);
00141                 strncpy(line_buf, commands_copy->name, name_len + 1);
00142                 line_len = name_len;
00143             } else {
00144                 if (line_len > 0) {
00145                     strncat(line_buf, " ", 2);
00146                     line_len += 1;
00147                 }
00148                 strncat(line_buf, commands_copy->name, name_len + 1);
00149                 line_len += name_len;
00150             }
00151         }
00152 #endif
00153     }
00154 
00155 #ifndef CLIENTHELP_LONG_LIST
00156     if (line_len) {
00157         LINE(line_buf);
00158     }
00159 #endif
00160 }
00161 
00166 static void show_help(const ConsoleCommand * cc) {
00167     {
00168         char buf[MAX_BUF];
00169         snprintf(buf, MAX_BUF - 1, "%s Command help:", get_category_name(cc->cat));
00170         H1(buf);
00171     }
00172 
00173     if (cc->desc != NULL) {
00174         char buf[MAX_BUF];
00175         snprintf(buf, MAX_BUF - 1, "%s - %s", cc->name, cc->desc);
00176         H2(buf);
00177     } else {
00178         H2(cc->name);
00179     }
00180 
00181     if (cc->helpfunc != NULL) {
00182         const char * long_help = NULL;
00183 
00184         long_help = cc->helpfunc();
00185 
00186         if (long_help != NULL) {
00187             /* For a test, let's watch draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT,
00188              * MSG_TYPE_CLIENT_NOTICE, ) choke on newlines. */
00189             /* TODO C line wrapping (get_info_width()), argh. Or move it to UI? */
00190             LINE(long_help);
00191         } else {
00192             LINE("This command's documentation is bugged!");
00193         }
00194     } else {
00195         LINE("This command has no extended documentation. :(");
00196     }
00197 }
00198 
00203 static void do_clienthelp(const char * arg) {
00204     const ConsoleCommand * cc;
00205 
00206     if (!arg || !strlen(arg)) {
00207         do_clienthelp_list();
00208         return;
00209     }
00210 
00211     cc = find_command(arg);
00212 
00213     if (cc == NULL) {
00214         char buf[MAX_BUF];
00215         snprintf(buf, MAX_BUF - 1, "clienthelp: Unknown command %s.", arg);
00216         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, buf);
00217         return;
00218     }
00219 
00220     show_help(cc);
00221 
00222 }
00223 
00227 static const char * help_clienthelp(void) {
00228     return
00229         "Syntax:\n"
00230         "\n"
00231         "    clienthelp\n"
00232         "    clienthelp <command>\n"
00233         "\n"
00234         "Without any arguments, displays a list of client-side "
00235         "commands.\n"
00236         "\n"
00237         "With arguments, displays the help for the client-side "
00238         "command <command>.\n"
00239         "\n"
00240         "See also: serverhelp, help.";
00241 }
00242 
00247 static void do_serverhelp(const char * arg) {
00248 
00249     if (arg) {
00250         char buf[MAX_BUF];
00251         snprintf(buf, sizeof(buf), "help %s", arg);
00252         /* maybe not a must send, but we probably don't want to drop it */
00253         send_command(buf, -1, 1);
00254     } else {
00255         send_command("help", -1, 1); /* TODO make install in server branch doesn't install def_help. */
00256     }
00257 }
00258 
00262 static const char * help_serverhelp(void) {
00263     return
00264         "Syntax:\n"
00265         "\n"
00266         "    serverhelp\n"
00267         "    serverhelp <command>\n"
00268         "\n"
00269         "Fetches help from the server.\n"
00270         "\n"
00271         "Note that currently nothing can be done (without a recompile) if a "
00272         "client command masks a server command.\n"
00273         "\n"
00274         "See also: clienthelp, help.";
00275 }
00276 
00281 static void command_help(const char *cpnext) {
00282     if (cpnext) {
00283         const ConsoleCommand * cc;
00284         char buf[MAX_BUF];
00285 
00286         cc = find_command(cpnext);
00287         if (cc != NULL) {
00288             show_help(cc);
00289         } else  {
00290             snprintf(buf, sizeof(buf), "help %s", cpnext);
00291             /* maybe not a must send, but we probably don't want to drop it */
00292             send_command(buf, -1, 1);
00293         }
00294     } else {
00295         do_clienthelp_list();
00296         /* Now fetch (in theory) command list from the server.
00297         TODO Protocol command - feed it to the tab completer.
00298 
00299         Nope! It effectivey fetches '/help commands for commands'.
00300         */
00301         send_command("help", -1, 1); /* TODO make install in server branch doesn't install def_help. */
00302     }
00303 }
00304 
00308 static const char * help_help(void) {
00309     return
00310         "Syntax:\n"
00311         "\n"
00312         "    help\n"
00313         "    help <topic>\n"
00314         "\n"
00315         "Without any arguments, displays a list of client-side "
00316         "commands, and fetches the without-arguments help from "
00317         "the server.\n"
00318         "\n"
00319         "With arguments, first checks if there's a client command "
00320         "named <topic>. If there is, display it's help. If there "
00321         "isn't, send the topic to the server.\n"
00322         "\n"
00323         "See also: clienthelp, serverhelp.";
00324 }
00325  /* EndOf PCmdHelpCommands
00328  */
00329 
00330 /*
00331  * Other commands.
00332  */
00333 
00338 static void set_command_window(const char *cpnext)
00339 {
00340     if (!cpnext) {
00341         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00342             "cwindow command requires a number parameter");
00343     } else {
00344         want_config[CONFIG_CWINDOW] = atoi(cpnext);
00345         if (want_config[CONFIG_CWINDOW]<1 || want_config[CONFIG_CWINDOW]>127)
00346             want_config[CONFIG_CWINDOW]=COMMAND_WINDOW;
00347         else
00348             use_config[CONFIG_CWINDOW] = want_config[CONFIG_CWINDOW];
00349     }
00350 }
00351 
00356 static void command_foodbeep(const char *cpnext)
00357 {
00358    (void)cpnext; /* __UNUSED__ */
00359     if (want_config[CONFIG_FOODBEEP]) {
00360         want_config[CONFIG_FOODBEEP]=0;
00361         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00362             "Warning bell when low on food disabled");
00363     } else {
00364         want_config[CONFIG_FOODBEEP]=1;
00365         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00366             "Warning bell when low on food enabled");
00367     }
00368     use_config[CONFIG_FOODBEEP] = want_config[CONFIG_FOODBEEP];
00369 }
00370 
00375 const char * get_category_name(CommCat cat) {
00376     const char * cat_name;
00377 
00378     /* HACK Need to keep this in sync. with player.h */
00379     switch(cat) {
00380         case COMM_CAT_MISC: cat_name = "Miscellaneous"; break;
00381         case COMM_CAT_HELP: cat_name = "Help"; break;
00382         case COMM_CAT_INFO: cat_name = "Informational"; break;
00383         case COMM_CAT_SETUP: cat_name = "Configuration"; break;
00384         case COMM_CAT_SCRIPT: cat_name = "Scripting"; break;
00385         case COMM_CAT_DEBUG: cat_name = "Debugging"; break;
00386         default: cat_name = "PROGRAMMER ERROR"; break;
00387     }
00388 
00389     return cat_name;
00390 }
00391 
00392 /*
00393  * Command table.
00394  *
00395  * Implementation basically stolen verbatim from the server.
00396  */
00397 
00398 /* "Typecasters" (and some forwards) */
00399 
00404 static void do_script_list(const char * ignored) { script_list(); }
00405 
00410 static void do_clearinfo(const char * ignored) { menu_clear(); }
00411 
00416 static void do_disconnect(const char * ignored) {
00417     close_server_connection();
00418 
00419         /* the gtk clients need to do some cleanup logic - otherwise,
00420          * they start hogging CPU.
00421          */
00422         cleanup_connection();
00423         return;
00424 }
00425 
00426 #ifdef HAVE_DMALLOC_H
00427 #ifndef DMALLOC_VERIFY_NOERROR
00428   #define DMALLOC_VERIFY_NOERROR  1
00429 #endif
00430 
00434 static void do_dmalloc(const char * ignored) {
00435         if (dmalloc_verify(NULL)==DMALLOC_VERIFY_NOERROR)
00436             draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00437                 "Heap checks out OK");
00438         else
00439             draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_ERROR,
00440                 "Heap corruption detected");
00441 }
00442 #endif
00443 
00448 static void do_inv(const char * ignored) { print_inventory (cpl.ob); }
00449 
00450 static void do_magicmap(const char * ignored) {
00451         cpl.showmagic=1;
00452         draw_magic_map();
00453 }
00454 
00459 static void do_metaserver(const char * ignored) {
00460         if (!metaserver_get_info(meta_server, meta_port))
00461             metaserver_show(FALSE);
00462         else
00463             draw_ext_info(
00464                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_METASERVER,
00465                     "Unable to get metaserver information.");
00466 }
00467 
00472 static void do_savedefaults(const char * ignored) { save_defaults(); }
00473 
00478 static void do_savewinpos(const char * ignored) { save_winpos(); }
00479 
00484 static void do_take(const char * used) { command_take("take", used); /* I dunno why they want it. */ }
00485 
00490 static void do_num_free_items(const char * ignored) {
00491     LOG(LOG_INFO,"common::extended_command","num_free_items=%d", num_free_items());
00492 }
00493 
00494 static void do_clienthelp(const char * arg); /* Forward. */
00495 
00496 /* Help "typecasters". */
00497 #include "../help/chelp.h"
00498 
00502 static const char * help_bind(void) { return HELP_BIND_LONG; }
00503 
00507 static const char * help_unbind(void) { return HELP_UNBIND_LONG; }
00508 
00512 static const char * help_magicmap(void) { return HELP_MAGICMAP_LONG; }
00513 
00517 static const char * help_inv(void) { return HELP_INV_LONG; }
00518 
00522 static const char * help_cwindow(void) {
00523     return
00524         "Syntax:\n"
00525         "\n"
00526         "    cwindow <val>\n"
00527         "\n"
00528         "set size of command"
00529         "window (if val is exceeded"
00530         "client won't send new"
00531         "commands to server\n\n"
00532         "(What does this mean, 'put a lid on it'?) TODO";
00533 }
00534 
00538 static const char * help_script(void) {
00539     return
00540         "Syntax:\n"
00541         "\n"
00542         "    script <pathname>\n"
00543         "\n"
00544         "Run the program at path <name>"
00545         "as a Crossfire client script."
00546         "See doc/Script.html";
00547 }
00548 
00552 static const char * help_scripttell(void) {
00553     return
00554         "Syntax:\n"
00555         "\n"
00556         "    scripttell <yourname> <data>\n"
00557         "\n"
00558         "?";
00559 }
00560 
00561 /* Toolkit-dependent. */
00562 
00566 static const char * help_savewinpos(void) {
00567     return
00568         "Syntax:\n"
00569         "\n"
00570         "    savewinpos\n"
00571         "\n"
00572         "save window positions - split windows mode only.";
00573 }
00574 
00578 static const char * help_metaserver(void) {
00579     /* TODO Add command_escape() where appropriate. On the other
00580     hand, that can lead to a meaningless syntax-display API.*/
00581 
00582     return
00583         "Syntax:\n"
00584         "\n"
00585         "    metaserver\n"
00586         "\n"
00587         "Get updated list of servers "
00588         "from the metaserver and show it."
00589         "This is the same information that the client "
00590         "uses to show a list of servers when it starts.\n"
00591         "\n"
00592         "Warning: This command may freeze the client until it gets the list.";
00593 }
00594 
00598 static const char * help_scriptkill(void) {
00599     return
00600         "Syntax:\n"
00601         "\n"
00602         "    scriptkill <name>\n"
00603         "\n"
00604         "Stop scripts named <name>.\n"
00605         "(Not guaranteed to work?)";
00606 }
00607 
00611 static const char * help_showweight(void) {
00612     return
00613         "Syntax:\n"
00614         "\n"
00615         "    showweight\n"
00616         "    showweight inventory\n"
00617         "    showweight look\n"
00618         "\n"
00619         "(Or any prefix of the arguments.)"
00620         "Toggles if you see the weight of"
00621         "items in your inventory (also if"
00622         "no argument given) or your"
00623         "look-window.";
00624 }
00625 
00626 /*
00627 *       draw_ext_info(NDI_NAVY, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00628             "Information Commands");*
00629         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00630             " inv         - *recursively* print your");
00631         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00632             "               inventory - includes containers.");
00633         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00634             " mapredraw, showinfo, take");
00635         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00636             " help        - show this message");
00637         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00638             " help <command> - get more information on a");
00639         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00640             "                command (Server command only?)");
00641         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00642             " showicon    - draw status icons in");
00643         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00644             "               inventory window");
00645         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00646             " showweight  - show weight in inventory");
00647         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00648             "               look windows");
00649         draw_ext_info(NDI_NAVY, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00650             "Scripting Commands");
00651         draw_ext_info(NDI_NAVY, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00652             "Client Side Debugging Commands");
00653 #ifdef HAVE_DMALLOC_H
00654         draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
00655             " dmalloc     - Check heap?");
00656 #endif
00657 */
00658 
00659 /* TODO Wrap these? Um. */
00660 static ConsoleCommand CommonCommands[] = {
00661     /* From player.h:
00662         name, cat,
00663         func, helpfunc,
00664         long_desc
00665     */
00666 
00667     {
00668         "autorepeat", COMM_CAT_MISC,
00669         set_autorepeat, NULL,
00670         "toggle autorepeat" /* XXX Eh? */
00671     },
00672 
00673     {
00674         "bind", COMM_CAT_SETUP,
00675         bind_key, help_bind,
00676         HELP_BIND_SHORT
00677     },
00678 
00679     {
00680         "script", COMM_CAT_SCRIPT,
00681         script_init, help_script,
00682         NULL
00683     },
00684 #ifdef HAVE_LUA
00685     {   "lua_load", COMM_CAT_SCRIPT,
00686     script_lua_load, NULL, NULL
00687     },
00688 
00689     {   "lua_list", COMM_CAT_SCRIPT,
00690     script_lua_list, NULL, NULL
00691     },
00692 
00693     {   "lua_kill", COMM_CAT_SCRIPT,
00694     script_lua_kill, NULL, NULL
00695     },
00696 #endif
00697     {
00698         "scripts", COMM_CAT_SCRIPT,
00699         do_script_list, NULL,
00700         "List the running scripts(?)"
00701     },
00702 
00703     {
00704         "scriptkill", COMM_CAT_SCRIPT,
00705         script_kill, help_scriptkill,
00706         NULL
00707     },
00708 
00709     {
00710         "scripttell", COMM_CAT_SCRIPT,
00711         script_tell, help_scripttell,
00712         NULL
00713     },
00714 
00715     {
00716         "clearinfo", COMM_CAT_MISC,
00717         do_clearinfo, NULL,
00718         "clear the info window"
00719     },
00720 
00721     {
00722         "cwindow", COMM_CAT_SETUP,
00723         set_command_window, help_cwindow,
00724         NULL
00725     },
00726 
00727     {
00728         "disconnect", COMM_CAT_MISC,
00729         do_disconnect, NULL,
00730         "close connection to server"
00731     },
00732 
00733 
00734 #ifdef HAVE_DMALLOC_H
00735     {
00736         "dmalloc", COMM_CAT_DEBUG,
00737         do_dmalloc, NULL,
00738         NULL
00739     },
00740 #endif
00741 
00742     {
00743         "foodbeep", COMM_CAT_SETUP,
00744         command_foodbeep, NULL,
00745         "toggle audible low on food warning"
00746 
00747     },
00748 
00749     {
00750         "help", COMM_CAT_HELP,
00751         command_help, help_help,
00752         NULL
00753     },
00754 
00755     {
00756         "clienthelp", COMM_CAT_HELP,
00757         do_clienthelp, help_clienthelp,
00758         "Client-side command information"
00759     },
00760 
00761     {
00762         "serverhelp", COMM_CAT_HELP,
00763         do_serverhelp, help_serverhelp,
00764         "Server-side command information"
00765     },
00766 
00767     {
00768         "inv", COMM_CAT_DEBUG,
00769         do_inv, help_inv,
00770         HELP_INV_SHORT
00771     },
00772 
00773     {
00774         "magicmap", COMM_CAT_MISC,
00775         do_magicmap, help_magicmap,
00776         HELP_MAGICMAP_SHORT
00777     },
00778 
00779     {
00780         "metaserver", COMM_CAT_INFO,
00781         do_metaserver, help_metaserver,
00782         "Print 'metaserver information'. Warning - your client will pause."
00783     },
00784 
00785     {
00786         "savedefaults", COMM_CAT_SETUP,
00787         do_savedefaults, NULL,
00788         HELP_SAVEDEFAULTS_SHORT /* How do we make sure showicons stays on? */
00789     },
00790 
00791     {
00792         "savewinpos", COMM_CAT_SETUP,
00793         do_savewinpos, help_savewinpos,
00794         "Saves the position and sizes of windows." /* Panes? */
00795     },
00796 
00797     {
00798         "scroll", COMM_CAT_SETUP,
00799         set_scroll, NULL,
00800         "toggle scroll/wrap mode in info window"
00801     },
00802 
00803     {
00804         "showicon", COMM_CAT_SETUP,
00805         set_show_icon, NULL,
00806         "Toggles if you see the worn, locked, cursed etc state in the inventory pane."
00807     },
00808 
00809     {
00810         "showweight", COMM_CAT_SETUP,
00811         set_show_weight, help_showweight,
00812         "Toggles if you see item weights in inventory look windows."
00813     },
00814 
00815     {
00816         "take", COMM_CAT_MISC,
00817         do_take, NULL,
00818         NULL
00819     },
00820 
00821     {
00822         "unbind", COMM_CAT_SETUP,
00823         unbind_key, help_unbind,
00824         NULL
00825     },
00826 
00827     {
00828         "num_free_items", COMM_CAT_DEBUG,
00829         do_num_free_items, NULL,
00830         "log the number of free items?"
00831     },
00832     {
00833         "show", COMM_CAT_SETUP,
00834         command_show, NULL,
00835         "Change what items to show in inventory"
00836     },
00837 
00838 };
00839 
00840 const int CommonCommandsSize = sizeof(CommonCommands) / sizeof(ConsoleCommand);
00841 
00842 #ifdef TOOLKIT_COMMANDS
00843 extern ConsoleCommand ToolkitCommands[];
00844 
00845 extern const int ToolkitCommandsSize;
00846 #endif
00847 
00848 /* ------------------------------------------------------------------ */
00849 
00850 int num_commands;
00851 
00855 int get_num_commands(void) { return num_commands; }
00856 
00857 static ConsoleCommand ** name_sorted_commands;
00858 
00864 static int sort_by_name(const void * a_, const void * b_)
00865 {
00866     ConsoleCommand * a = *((ConsoleCommand **)a_);
00867     ConsoleCommand * b = *((ConsoleCommand **)b_);
00868 
00869     return strcmp(a->name, b->name);
00870 }
00871 
00872 static ConsoleCommand ** cat_sorted_commands;
00873 
00874 /* Sort by category, then by name. */
00875 
00881 static int sort_by_category(const void *a_, const void *b_)
00882 {
00883     /* Typecasts, so it goes. */
00884     ConsoleCommand * a = *((ConsoleCommand **)a_);
00885     ConsoleCommand * b = *((ConsoleCommand **)b_);
00886 
00887     if (a->cat == b->cat) {
00888         return strcmp(a->name, b->name);
00889     }
00890 
00891     return a->cat - b->cat;
00892 }
00893 
00897 void init_commands(void) {
00898     int i;
00899 
00900 #ifdef TOOLKIT_COMMANDS
00901     init_toolkit_commands();
00902 
00903     /* TODO I dunno ... go through the list and print commands without helps? */
00904     num_commands = CommonCommandsSize + ToolkitCommandsSize;
00905 #else
00906     num_commands = CommonCommandsSize;
00907 #endif
00908 
00909     /* Make a list of (pointers to statically allocated!) all the commands.
00910        We have a list; the toolkit has a
00911        ToolkitCommands and ToolkitCommandsSize, initialized before calling this.
00912     */
00913 
00914     /* XXX Leak! */
00915     name_sorted_commands = malloc(sizeof(ConsoleCommand *) * num_commands);
00916 
00917     for (i = 0; i < CommonCommandsSize; i++) {
00918         name_sorted_commands[i] = &CommonCommands[i];
00919     }
00920 
00921 #ifdef TOOLKIT_COMMANDS
00922     for(i = 0; i < ToolkitCommandsSize; i++) {
00923         name_sorted_commands[CommonCommandsSize + i] = &ToolkitCommands[i];
00924     }
00925 #endif
00926 
00927     /* Sort them. */
00928     qsort(name_sorted_commands, num_commands, sizeof(ConsoleCommand *), sort_by_name);
00929 
00930     /* Copy the list, then sort it by category. */
00931     cat_sorted_commands = malloc(sizeof(ConsoleCommand *) * num_commands);
00932 
00933     memcpy(cat_sorted_commands, name_sorted_commands, sizeof(ConsoleCommand *) * num_commands);
00934 
00935     qsort(cat_sorted_commands, num_commands, sizeof(ConsoleCommand *), sort_by_category);
00936 
00937     /* TODO Add to the list of tab-completion items. */
00938 }
00939 
00940 #ifndef tolower
00941 #define tolower(C)      (((C) >= 'A' && (C) <= 'Z')? (C) - 'A' + 'a': (C))
00942 #endif
00943 
00948 const ConsoleCommand * find_command(const char * cmd) {
00949   ConsoleCommand ** asp_p = NULL, dummy;
00950   ConsoleCommand * dummy_p;
00951   ConsoleCommand * asp;
00952   char *cp, *cmd_cpy;
00953   cmd_cpy = strdup(cmd);
00954 
00955   for (cp=cmd_cpy; *cp; cp++) {
00956     *cp =tolower(*cp);
00957   }
00958 
00959   dummy.name = cmd_cpy;
00960   dummy_p = &dummy;
00961   asp_p = bsearch(
00962      (void *)&dummy_p,
00963      (void *)name_sorted_commands,
00964      num_commands,
00965      sizeof(ConsoleCommand *),
00966      sort_by_name);
00967 
00968   if (asp_p == NULL)
00969   {
00970       free(cmd_cpy);
00971       return NULL;
00972   }
00973 
00974   asp = *asp_p;
00975 
00976   /* TODO The server's find_command() searches first the commands,
00977   then the emotes. We might have to do something similar someday, too. */
00978   /* if (asp == NULL) search something else? */
00979 
00980   free(cmd_cpy);
00981 
00982   return asp;
00983 }
00984 
00989 ConsoleCommand ** get_cat_sorted_commands(void) {
00990     return cat_sorted_commands;
00991 }
00992 
01002 int handle_local_command(const char* cp, const char * cpnext) {
01003     const ConsoleCommand * cc = NULL;
01004 
01005     cc = find_command(cp);
01006 
01007     if (cc == NULL) {
01008         return FALSE;
01009     }
01010 
01011     if (cc->dofunc == NULL) {
01012         char buf[MAX_BUF];
01013 
01014         snprintf(buf, MAX_BUF - 1, "Client command %s has no implementation!", cc->name);
01015         draw_ext_info(NDI_RED, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, buf);
01016 
01017         return FALSE;
01018     }
01019 
01020     cc->dofunc(cpnext);
01021 
01022     return TRUE;
01023 }
01024 
01036 void extended_command(const char *ocommand) {
01037     const char *cp = ocommand;
01038     char *cpnext, command[MAX_BUF];
01039 
01040     if ((cpnext = strchr(cp, ' '))!=NULL) {
01041         int len = cpnext - ocommand;
01042         if (len > (MAX_BUF -1 )) len = MAX_BUF-1;
01043 
01044         strncpy(command, ocommand, len);
01045         command[len] = '\0';
01046         cp = command;
01047         while (*cpnext == ' ')
01048             cpnext++;
01049         if (*cpnext == 0)
01050             cpnext = NULL;
01051     }
01052 
01053     /* cp now contains the command (everything before first space),
01054      * and cpnext contains everything after that first space.  cpnext
01055      * could be NULL.
01056      */
01057 #ifdef HAVE_LUA
01058     if ( script_lua_command(cp, cpnext) )
01059         return;
01060 #endif
01061 
01062     /* If this isn't a client-side command, send it to the server. */
01063     if (!handle_local_command(cp, cpnext)) {
01064         /* just send the command(s)  (if `ocommand' is a compound command */
01065         /* then split it and send each part seperately */
01066         /* TODO Remove this from the server; end of commands.c. */
01067         strncpy(command, ocommand, MAX_BUF-1);
01068         command[MAX_BUF-1]=0;
01069         cp = strtok(command, ";");
01070         while ( cp ) {
01071           while( *cp == ' ' ) cp++; /* throw out leading spaces; server
01072                                        does not like them */
01073           send_command(cp, cpl.count, 0);
01074           cp = strtok(NULL, ";");
01075         }
01076     }
01077 }
01078 
01079 /* ------------------------------------------------------------------ */
01080 
01081 /* This list is used for the 'tab' completion, and nothing else.
01082  * Therefore, if it is out of date, it isn't that terrible, but
01083  * ideally it should stay somewhat up to date with regards to
01084  * the commands the server supports.
01085  */
01086 
01087 /* TODO Dynamically generate. */
01088 
01089 static const char *const commands[] = {
01090 "accuse", "afk", "apply", "applymode", "archs", "beg", "bleed", "blush",
01091 "body", "bounce", "bow", "bowmode", "brace", "build", "burp", "cackle", "cast",
01092 "chat", "chuckle", "clap", "cointoss", "cough", "cringe", "cry", "dance",
01093 "disarm", "dm", "dmhide", "drop", "dropall", "east", "examine", "explore",
01094 "fire", "fire_stop", "fix_me", "flip", "frown", "gasp", "get", "giggle",
01095 "glare", "grin", "groan", "growl", "gsay", "help", "hiccup", "hiscore", "hug",
01096 "inventory", "invoke", "killpets", "kiss", "laugh", "lick", "listen", "logs",
01097 "mapinfo", "maps", "mark", "me", "motd", "nod", "north", "northeast",
01098 "northwest", "orcknuckle", "output-count", "output-sync", "party", "peaceful",
01099 "petmode", "pickup", "players", "poke", "pout", "prepare", "printlos", "puke",
01100 "quests", "quit", "ready_skill", "rename", "reply", "resistances",
01101 "rotateshoottype", "run", "run_stop", "save", "say", "scream", "search",
01102 "search-items", "shake", "shiver", "shout", "showpets", "shrug", "shutdown",
01103 "sigh", "skills", "slap", "smile", "smirk", "snap", "sneeze", "snicker",
01104 "sniff", "snore", "sound", "south", "southeast", "southwest", "spit",
01105 "statistics", "stay", "strings", "strut", "sulk", "take", "tell", "thank",
01106 "think", "throw", "time", "title", "twiddle", "use_skill", "usekeys",
01107 "version", "wave", "weather", "west", "whereabouts", "whereami", "whistle",
01108 "who", "wimpy", "wink", "yawn",
01109 };
01110 #define NUM_COMMANDS ((int)(sizeof(commands) / sizeof(char*)))
01111 
01119 const char * complete_command(const char *command)
01120 {
01121     int i, len, display;
01122     const char *match;
01123     static char result[64];
01124     char list[500];
01125 
01126     len = strlen(command);
01127 
01128     if (len == 0)
01129         return NULL;
01130 
01131     display = 0;
01132     strcpy(list, "Matching commands:");
01133 
01134     /* TODO Partial match, e.g.:
01135          If the completion list was:
01136            wear
01137            wet #?
01138 
01139          If we type 'w' then hit tab, put in the e.
01140 
01141        Basically part of bash (readline?)'s behaviour.
01142     */
01143 
01144     match = NULL;
01145 
01146     /* check server side commands */
01147     for (i=0; i<NUM_COMMANDS; i++) {
01148         if (!strncmp(command, commands[i], len)) {
01149             if (display) {
01150                 snprintf(list + strlen(list), 499 - strlen(list), " %s", commands[i]);
01151             } else if (match != NULL) {
01152                 display = 1;
01153                 snprintf(list + strlen(list), 499 - strlen(list), " %s %s", match, commands[i]);
01154                 match = NULL;
01155             } else
01156                 match = commands[i];
01157         }
01158     }
01159 
01160     /* check client side commands */
01161     for (i=0; i<CommonCommandsSize; i++) {
01162         if (!strncmp(command, CommonCommands[i].name, len)) {
01163             if (display) {
01164                 snprintf(list + strlen(list), 499 - strlen(list), " %s", CommonCommands[i].name);
01165             } else if (match != NULL) {
01166                 display = 1;
01167                 snprintf(list + strlen(list), 499 - strlen(list), " %s %s", match, CommonCommands[i].name);
01168                 match = NULL;
01169             } else
01170                 match = CommonCommands[i].name;
01171         }
01172     }
01173 
01174     if (match == NULL) {
01175         if (display) {
01176             strncat(list, "\n", 499 - strlen(list));
01177             draw_ext_info(
01178                 NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE, list);
01179         }
01180         else
01181             draw_ext_info(NDI_BLACK, MSG_TYPE_CLIENT, MSG_TYPE_CLIENT_NOTICE,
01182                 "No matching command.\n");
01183         /* No match. */
01184         return NULL;
01185     }
01186 
01187     /*
01188      * Append a space to allow typing arguments. For commands without arguments
01189      * the excess space should be stripped off automatically.
01190      */
01191     snprintf(result, sizeof(result), "%s ", match);
01192 
01193     return result;
01194 }
01195 
01196 #endif /* CPROTO */