Crossfire Client, Branch  R11627
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,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 */