URI: 
       tcurses_client.c - vaccinewars - be a doctor and try to vaccinate the world
  HTML git clone git://src.adamsgaard.dk/vaccinewars
   DIR Log
   DIR Files
   DIR Refs
   DIR README
   DIR LICENSE
       ---
       tcurses_client.c (82474B)
       ---
            1 /************************************************************************
            2  * curses_client.c  dopewars client using the (n)curses console library *
            3  * Copyright (C)  1998-2021  Ben Webb                                   *
            4  *                Email: benwebb@users.sf.net                           *
            5  *                WWW: https://dopewars.sourceforge.io/                 *
            6  *                                                                      *
            7  * This program is free software; you can redistribute it and/or        *
            8  * modify it under the terms of the GNU General Public License          *
            9  * as published by the Free Software Foundation; either version 2       *
           10  * of the License, or (at your option) any later version.               *
           11  *                                                                      *
           12  * This program is distributed in the hope that it will be useful,      *
           13  * but WITHOUT ANY WARRANTY; without even the implied warranty of       *
           14  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the        *
           15  * GNU General Public License for more details.                         *
           16  *                                                                      *
           17  * You should have received a copy of the GNU General Public License    *
           18  * along with this program; if not, write to the Free Software          *
           19  * Foundation, Inc., 59 Temple Place - Suite 330, Boston,               *
           20  *                   MA  02111-1307, USA.                               *
           21  ************************************************************************/
           22 
           23 #ifdef HAVE_CONFIG_H
           24 #include <config.h>
           25 #endif
           26 
           27 #include <string.h>
           28 #include <stdlib.h>
           29 #include <sys/types.h>
           30 #ifdef HAVE_UNISTD_H
           31 #include <unistd.h>
           32 #endif
           33 #include <ctype.h>
           34 #include <signal.h>
           35 #include <assert.h>
           36 #include <errno.h>
           37 #include <glib.h>
           38 #include "configfile.h"
           39 #include "curses_client.h"
           40 #include "cursesport/cursesport.h"
           41 #include "dopewars.h"
           42 #include "message.h"
           43 #include "nls.h"
           44 #include "serverside.h"
           45 #include "sound.h"
           46 #include "tstring.h"
           47 
           48 static int ResizedFlag;
           49 static SCREEN *cur_screen;
           50 
           51 #define PromptAttr   (COLOR_PAIR(1))
           52 #define TextAttr     (COLOR_PAIR(2))
           53 #define LocationAttr (COLOR_PAIR(3)|A_BOLD)
           54 #define TitleAttr    (COLOR_PAIR(4))
           55 #define StatsAttr    (COLOR_PAIR(5))
           56 #define DebtAttr     (COLOR_PAIR(6))
           57 
           58 /* Current size of the screen */
           59 static int Width, Depth;
           60 
           61 /* Maximum number of messages to store (for scrollback etc.) */
           62 const static int MaxMessages = 1000;
           63 
           64 #ifdef NETWORKING
           65 /* Data waiting to be sent to/read from the metaserver */
           66 static CurlConnection MetaConn;
           67 
           68 static enum {
           69   CM_SERVER, CM_PROMPT, CM_META, CM_SINGLE
           70 } ConnectMethod = CM_SERVER;
           71 #endif
           72 
           73 static gboolean CanFire = FALSE, RunHere = FALSE;
           74 static FightPoint fp;
           75 
           76 /* Function definitions; make them static so as not to clash with
           77  * functions of the same name in different clients */
           78 static void display_intro(void);
           79 static void ResizeHandle(int sig);
           80 static void CheckForResize(Player *Play);
           81 static int GetKey(const char *orig_allowed, gboolean AllowOther,
           82                   gboolean PrintAllowed, gboolean ExpandOut);
           83 static void clear_bottom(void), clear_screen(void);
           84 static void clear_line(int line), clear_exceptfor(int skip);
           85 static void nice_wait(void);
           86 static void DisplayFightMessage(Player *Play, char *text);
           87 static void DisplaySpyReports(char *Data, Player *From, Player *To);
           88 static void display_message(const char *buf);
           89 static void print_location(char *text);
           90 static void print_status(Player *Play, gboolean DispDrug);
           91 static char *nice_input(char *prompt, int sy, int sx, gboolean digitsonly,
           92                         char *displaystr, char passwdchar);
           93 static Player *ListPlayers(Player *Play, gboolean Select, char *Prompt);
           94 static void HandleClientMessage(char *buf, Player *Play);
           95 static void PrintMessage(const gchar *text);
           96 static void GunShop(Player *Play);
           97 static void LoanShark(Player *Play);
           98 static void Bank(Player *Play);
           99 static void PrepareHighScoreScreen(void);
          100 static void PrintHighScore(char *Data);
          101 static void scroll_msg_area_up(void);
          102 static void scroll_msg_area_down(void);
          103 
          104 
          105 #ifdef NETWORKING
          106 static void SocksAuthFunc(NetworkBuffer *netbuf, gpointer data);
          107 #endif
          108 
          109 static DispMode DisplayMode;
          110 static gboolean QuitRequest, WantColor = TRUE, WantNetwork = TRUE;
          111 
          112 /* 
          113  * Initializes the curses library for accessing the screen.
          114  */
          115 static void start_curses(void)
          116 {
          117   cur_screen = newterm(NULL, stdout, stdin);
          118   if (WantColor) {
          119     start_color();
          120     init_pair(1, COLOR_MAGENTA, COLOR_WHITE);
          121     init_pair(2, COLOR_BLACK, COLOR_WHITE);
          122     init_pair(3, COLOR_BLACK, COLOR_WHITE);
          123     init_pair(4, COLOR_BLUE, COLOR_WHITE);
          124     init_pair(5, COLOR_WHITE, COLOR_BLUE);
          125     init_pair(6, COLOR_RED, COLOR_WHITE);
          126   }
          127   cbreak();
          128   noecho();
          129   nodelay(stdscr, FALSE);
          130   keypad(stdscr, TRUE);
          131   curs_set(0);
          132 }
          133 
          134 /* 
          135  * Shuts down the curses screen library.
          136  */
          137 static void end_curses(void)
          138 {
          139   keypad(stdscr, FALSE);
          140   curs_set(1);
          141   erase();
          142   refresh();
          143   endwin();
          144 }
          145 
          146 /* 
          147  * Handles a SIGWINCH signal, which is sent to indicate that the
          148  * size of the curses screen has changed.
          149  */
          150 void ResizeHandle(int sig)
          151 {
          152   ResizedFlag = 1;
          153 }
          154 
          155 /*
          156  * Returns the topmost row of the message area
          157  */
          158 static int get_msg_area_top(void)
          159 {
          160   return 10;
          161 }
          162 
          163 static int get_separator_line(void)
          164 {
          165   if (!Network) {
          166     return 14;
          167   } else if (Depth <= 20) {
          168     return 11;
          169   } else if (Depth <= 24) {
          170     return Depth - 9;
          171   } else {
          172     return 11 + (Depth - 20) * 2 / 3;
          173   }
          174 }
          175 
          176 /*
          177  * Returns the bottommost row of the message area
          178  */
          179 static int get_msg_area_bottom(void)
          180 {
          181   return get_separator_line() - 1;
          182 }
          183 
          184 static int get_ui_area_top(void)
          185 {
          186   return get_separator_line() + (Network ? 1 : 2);
          187 }
          188 
          189 static int get_ui_area_bottom(void)
          190 {
          191   return Depth - 1;
          192 }
          193 
          194 static int get_prompt_line(void)
          195 {
          196   return Depth - 2;
          197 }
          198 
          199 /* 
          200  * Checks to see if the curses window needs to be resized - i.e. if a
          201  * SIGWINCH signal has been received.
          202  */
          203 void CheckForResize(Player *Play)
          204 {
          205 #ifdef CYGWIN
          206   int sigset;
          207 #else
          208   sigset_t sigset;
          209 #endif
          210 
          211   sigemptyset(&sigset);
          212   sigaddset(&sigset, SIGWINCH);
          213   sigprocmask(SIG_BLOCK, &sigset, NULL);
          214   if (ResizedFlag) {
          215     ResizedFlag = 0;
          216     end_curses();
          217     start_curses();
          218     Width = COLS;
          219     Depth = LINES;
          220     attrset(TextAttr);
          221     clear_screen();
          222     display_message("");
          223     DisplayFightMessage(Play, "");
          224     print_status(Play, TRUE);
          225   }
          226   sigprocmask(SIG_UNBLOCK, &sigset, NULL);
          227 }
          228 
          229 static void LogMessage(const gchar *log_domain, GLogLevelFlags log_level,
          230                        const gchar *message, gpointer user_data)
          231 {
          232   attrset(TextAttr);
          233   clear_bottom();
          234   PrintMessage(message);
          235   nice_wait();
          236   attrset(TextAttr);
          237   clear_bottom();
          238 }
          239 
          240 /* Return length of string in characters (not bytes, like strlen) */
          241 static int strcharlen(const char *str)
          242 {
          243   return LocaleIsUTF8 ? g_utf8_strlen(str, -1) : strlen(str);
          244 }
          245 
          246 /* Displays a string right-aligned at the given position (the last character
          247    in the string will be at the given row and column)
          248  */
          249 static void mvaddrightstr(int row, int col, const gchar *str)
          250 {
          251   int len = strcharlen(str);
          252   mvaddstr(row, MAX(col - len + 1, 0), str);
          253 }
          254 
          255 /* Append len spaces to the end of text */
          256 static void g_string_pad(GString *text, int len)
          257 {
          258   int curlen = text->len;
          259   g_string_set_size(text, curlen + len);
          260   memset(text->str + curlen, ' ', len);
          261 }
          262 
          263 /* Make the string the given number of characters, either by adding spaces
          264    to the end, or by truncating it */
          265 static void g_string_pad_or_truncate_to_charlen(GString *text, int len)
          266 {
          267   int curlen = strcharlen(text->str);
          268   if (len < 0) {
          269     return;
          270   }
          271   if (curlen < len) {
          272     g_string_pad(text, len - curlen);
          273   } else if (curlen > len) {
          274     if (LocaleIsUTF8) {
          275       /* Convert from number of characters to bytes */
          276       len = g_utf8_offset_to_pointer(text->str, len) - text->str;
          277     }
          278     g_string_truncate(text, len);
          279   }
          280 }
          281 
          282 /*
          283  * Displays a string, horizontally centred on the given row
          284  */
          285 static void mvaddcentstr(const int row, const gchar *str)
          286 {
          287   guint col, len;
          288 
          289   len = strcharlen(str);
          290   col = (len > (guint)Width ? 0 : ((guint)Width - len) / 2);
          291   mvaddstr(row, col, str);
          292 }
          293 
          294 /*
          295  * Displays a string at the given coordinates and with the given
          296  * attributes. If the string is longer than "wid" characters, it is truncated,
          297  * and if shorter, it is padded with spaces.
          298  */
          299 static void mvaddfixwidstr(const int row, const int col, const int wid,
          300                            const gchar *str, const int attrs)
          301 {
          302   int strwidch = str ? strcharlen(str) : 0;
          303   int i, strwidbyte;
          304 
          305   strwidch = MIN(strwidch, wid);
          306   if (LocaleIsUTF8) {
          307     strwidbyte = g_utf8_offset_to_pointer(str, strwidch) - str;
          308   } else {
          309     strwidbyte = strwidch;
          310   }
          311 
          312   move(row, col);
          313   for (i = 0; i < strwidbyte; ++i) {
          314     addch((guchar)str[i] | attrs);
          315   }
          316   for (i = strwidch; i < wid; ++i) {
          317     addch((guchar)' ' | attrs);
          318   }
          319 }
          320 
          321 /* 
          322  * Displays a dopewars introduction screen.
          323  */
          324 void display_intro(void)
          325 {
          326   const gchar *translation = N_("English Translation           Ben Webb");
          327   GString *text;
          328 
          329   attrset(TextAttr);
          330   clear_screen();
          331   attrset(TitleAttr);
          332 
          333   /* Curses client introduction screen */
          334   text = g_string_new(_("V A C C I N E W A R S"));
          335   mvaddcentstr(0, text->str);
          336 
          337   attrset(TextAttr);
          338 
          339   mvaddcentstr(2, _("Based on John E. Dell's old Drug Wars game, vaccinewars "
          340                     "is a simulation of an"));
          341   mvaddcentstr(3, _("imaginary vaccine market.  vaccinewars is a "
          342                     "game which features"));
          343   mvaddcentstr(4, _("buying, selling, and trying to get past the antivaxxers!"));
          344 
          345   mvaddcentstr(6, _("The first thing you need to do is pay off your "
          346                     "debt to your dad. After"));
          347   mvaddcentstr(7, _("that, your goal is to make as much money as "
          348                     "possible (and stay alive)!"));
          349   mvaddcentstr(8, _("You have one month of game time to make your fortune and vaccinate the world."));
          350 
          351   g_string_printf(text, _("Version %-8s Copyright (C) 2022 Anders Damsgaard, (C) 1998-2021 Ben Webb "
          352                            "benwebb@users.sf.net"), VERSION);
          353   mvaddcentstr(10, text->str);
          354   g_string_assign(text, _("vaccinewars is released under the GNU "
          355                           "General Public License"));
          356   mvaddcentstr(11, text->str);
          357 
          358   g_string_assign(text, _(translation));
          359   if (strcmp(text->str, translation) != 0) {
          360     mvaddstr(12, 7, text->str);
          361   }
          362   mvaddstr(13, 7, _("Icons and Graphics            Ocelot Mantis"));
          363   mvaddstr(14, 7, _("Sounds                        Robin Kohli, 19.5degs.com"));
          364   mvaddstr(15, 7, _("Drug Dealing and Research     Dan Wolf"));
          365   mvaddstr(16, 7, _("Play Testing                  Phil Davis           "
          366                     "Owen Walsh"));
          367   mvaddstr(17, 7, _("Extensive Play Testing        Katherine Holt       "
          368                     "Caroline Moore"));
          369   mvaddstr(18, 7, _("Constructive Criticism        Andrea Elliot-Smith  "
          370                     "Pete Winn"));
          371   mvaddstr(19, 7, _("Unconstructive Criticism      James Matthews"));
          372 
          373   mvaddcentstr(21, _("For information on the command line options, type "
          374                      "vaccinewars -h at your"));
          375   mvaddcentstr(22, _("Unix prompt. This will display a help screen, listing "
          376                      "the available options."));
          377 
          378   g_string_free(text, TRUE);
          379   nice_wait();
          380   attrset(TextAttr);
          381   clear_screen();
          382   refresh();
          383 }
          384 
          385 #ifdef NETWORKING
          386 /* 
          387  * Prompts the user to enter a server name and port to connect to.
          388  */
          389 static void SelectServerManually(void)
          390 {
          391   gchar *text, *PortText;
          392   int top = get_ui_area_top();
          393 
          394   if (ServerName[0] == '(')
          395     AssignName(&ServerName, "localhost");
          396   attrset(TextAttr);
          397   clear_bottom();
          398   mvaddstr(top + 1, 1,
          399            /* Prompts for hostname and port when selecting a server
          400               manually */
          401            _("Please enter the hostname and port of a dopewars server:-"));
          402   text = nice_input(_("Hostname: "), top + 2, 1, FALSE, ServerName, '\0');
          403   AssignName(&ServerName, text);
          404   g_free(text);
          405   PortText = g_strdup_printf("%d", Port);
          406   text = nice_input(_("Port: "), top + 3, 1, TRUE, PortText, '\0');
          407   Port = atoi(text);
          408   g_free(text);
          409   g_free(PortText);
          410 }
          411 
          412 /* 
          413  * Contacts the dopewars metaserver, and obtains a list of valid
          414  * server/port pairs, one of which the user should select.
          415  * Returns TRUE on success; on failure FALSE is returned, and
          416  * errstr is assigned an error message.
          417  */
          418 static gboolean SelectServerFromMetaServer(Player *Play, GString *errstr)
          419 {
          420   int c;
          421   GError *tmp_error = NULL;
          422   GSList *ListPt;
          423   ServerData *ThisServer;
          424   GString *text;
          425   gint index;
          426   fd_set readfds, writefds, errorfds;
          427   int maxsock;
          428   int top = get_ui_area_top();
          429 
          430   attrset(TextAttr);
          431   clear_bottom();
          432   mvaddstr(top + 1, 1, _("Please wait... attempting to contact metaserver..."));
          433   refresh();
          434 
          435   if (!OpenMetaHttpConnection(&MetaConn, &tmp_error)) {
          436     g_string_assign(errstr, tmp_error->message);
          437     g_error_free(tmp_error);
          438     return FALSE;
          439   }
          440 
          441   ClearServerList(&ServerList);
          442 
          443   while(TRUE) {
          444     long mintime;
          445     struct timeval timeout;
          446     int still_running;
          447     FD_ZERO(&readfds);
          448     FD_ZERO(&writefds);
          449     FD_ZERO(&errorfds);
          450     FD_SET(0, &readfds);
          451     curl_multi_fdset(MetaConn.multi, &readfds, &writefds, &errorfds, &maxsock);
          452     curl_multi_timeout(MetaConn.multi, &mintime);
          453     timeout.tv_sec = mintime < 0 ? 5 : mintime;
          454     timeout.tv_usec = 0;
          455     maxsock = MAX(maxsock+1, 1);
          456 
          457     if (bselect(maxsock, &readfds, &writefds, &errorfds, &timeout) == -1) {
          458       if (errno == EINTR) {
          459         CheckForResize(Play);
          460         continue;
          461       }
          462       perror("bselect");
          463       exit(EXIT_FAILURE);
          464     }
          465     if (FD_ISSET(0, &readfds)) {
          466       /* So that Ctrl-L works */
          467       c = getch();
          468       if (c == '\f')
          469         wrefresh(curscr);
          470     }
          471     if (!CurlConnectionPerform(&MetaConn, &still_running, &tmp_error)) {
          472       g_string_assign(errstr, tmp_error->message);
          473       g_error_free(tmp_error);
          474       return FALSE;
          475     } else if (still_running == 0) {
          476       if (!HandleWaitingMetaServerData(&MetaConn, &ServerList, &tmp_error)) {
          477         CloseCurlConnection(&MetaConn);
          478         g_string_assign(errstr, tmp_error->message);
          479         g_error_free(tmp_error);
          480         return FALSE;
          481       }
          482       CloseCurlConnection(&MetaConn);
          483       break;
          484     }
          485   }
          486 
          487   text = g_string_new("");
          488 
          489   ListPt = ServerList;
          490   while (ListPt) {
          491     ThisServer = (ServerData *)(ListPt->data);
          492     attrset(TextAttr);
          493     clear_bottom();
          494     /* Printout of metaserver information in curses client */
          495     g_string_printf(text, _("Server : %s"), ThisServer->Name);
          496     mvaddstr(top + 1, 1, text->str);
          497     g_string_printf(text, _("Port   : %d"), ThisServer->Port);
          498     mvaddstr(top + 2, 1, text->str);
          499     g_string_printf(text, _("Version    : %s"), ThisServer->Version);
          500     mvaddstr(top + 2, 40, text->str);
          501     if (ThisServer->CurPlayers == -1) {
          502       g_string_printf(text, _("Players: -unknown- (maximum %d)"),
          503                        ThisServer->MaxPlayers);
          504     } else {
          505       g_string_printf(text, _("Players: %d (maximum %d)"),
          506                        ThisServer->CurPlayers, ThisServer->MaxPlayers);
          507     }
          508     mvaddstr(top + 3, 1, text->str);
          509     g_string_printf(text, _("Up since   : %s"), ThisServer->UpSince);
          510     mvaddstr(top + 3, 40, text->str);
          511     g_string_printf(text, _("Comment: %s"), ThisServer->Comment);
          512     mvaddstr(top + 4, 1, text->str);
          513     attrset(PromptAttr);
          514     mvaddstr(top + 5, 1,
          515              _("N>ext server; P>revious server; S>elect this server... "));
          516 
          517     /* The three keys that are valid responses to the previous question -
          518        if you translate them, keep the keys in the same order (N>ext,
          519        P>revious, S>elect) as they are here, otherwise they'll do the
          520        wrong things. */
          521     c = GetKey(N_("NPS"), FALSE, FALSE, FALSE);
          522     switch (c) {
          523     case 'S':
          524       AssignName(&ServerName, ThisServer->Name);
          525       Port = ThisServer->Port;
          526       ListPt = NULL;
          527       break;
          528     case 'N':
          529       ListPt = g_slist_next(ListPt);
          530       if (!ListPt)
          531         ListPt = ServerList;
          532       break;
          533     case 'P':
          534       index = g_slist_position(ServerList, ListPt) - 1;
          535       if (index >= 0)
          536         ListPt = g_slist_nth(ServerList, (guint)index);
          537       else
          538         ListPt = g_slist_last(ListPt);
          539       break;
          540     }
          541   }
          542   clear_line(top + 1);
          543   refresh();
          544   g_string_free(text, TRUE);
          545   return TRUE;
          546 }
          547 
          548 static void DisplayConnectStatus(NetworkBuffer *netbuf,
          549                                  NBStatus oldstatus,
          550                                  NBSocksStatus oldsocks)
          551 {
          552   NBStatus status;
          553   NBSocksStatus sockstat;
          554   GString *text;
          555 
          556   status = netbuf->status;
          557   sockstat = netbuf->sockstat;
          558 
          559   if (oldstatus == status && oldsocks == sockstat)
          560     return;
          561 
          562   text = g_string_new("");
          563 
          564   switch (status) {
          565   case NBS_PRECONNECT:
          566     break;
          567   case NBS_SOCKSCONNECT:
          568     switch (sockstat) {
          569     case NBSS_METHODS:
          570       g_string_printf(text, _("Connected to SOCKS server %s..."),
          571                        Socks.name);
          572       break;
          573     case NBSS_USERPASSWD:
          574       g_string_assign(text, _("Authenticating with SOCKS server"));
          575       break;
          576     case NBSS_CONNECT:
          577       g_string_printf(text, _("Asking SOCKS for connect to %s..."),
          578                        ServerName);
          579       break;
          580     }
          581     break;
          582   case NBS_CONNECTED:
          583     break;
          584   }
          585   if (text->str[0]) {
          586     mvaddstr(17, 1, text->str);
          587     refresh();
          588   }
          589   g_string_free(text, TRUE);
          590 }
          591 
          592 void SocksAuthFunc(NetworkBuffer *netbuf, gpointer data)
          593 {
          594   gchar *user, *password = NULL;
          595 
          596   attrset(TextAttr);
          597   clear_bottom();
          598   mvaddstr(17, 1, _("SOCKS authentication required (enter a blank "
          599                     "username to cancel)"));
          600 
          601   user = nice_input(_("User name: "), 18, 1, FALSE, NULL, '\0');
          602   if (user && user[0]) {
          603     password = nice_input(_("Password: "), 19, 1, FALSE, NULL, '*');
          604   }
          605 
          606   SendSocks5UserPasswd(netbuf, user, password);
          607   g_free(user);
          608   g_free(password);
          609 }
          610 
          611 static gboolean DoConnect(Player *Play, GString *errstr)
          612 {
          613   NetworkBuffer *netbuf;
          614   fd_set readfds, writefds, errorfds;
          615   int maxsock, c;
          616   gboolean doneOK = TRUE;
          617   NBStatus oldstatus;
          618   NBSocksStatus oldsocks;
          619 
          620   netbuf = &Play->NetBuf;
          621   oldstatus = netbuf->status;
          622   oldsocks = netbuf->sockstat;
          623 
          624   if (!StartNetworkBufferConnect(netbuf, NULL, ServerName, Port)) {
          625     doneOK = FALSE;
          626   } else {
          627     SetNetworkBufferUserPasswdFunc(netbuf, SocksAuthFunc, NULL);
          628     doneOK = TRUE;
          629     while (netbuf->status != NBS_CONNECTED && doneOK) {
          630       DisplayConnectStatus(netbuf, oldstatus, oldsocks);
          631       oldstatus = netbuf->status;
          632       oldsocks = netbuf->sockstat;
          633       FD_ZERO(&readfds);
          634       FD_ZERO(&writefds);
          635       FD_ZERO(&errorfds);
          636       FD_SET(0, &readfds);
          637       maxsock = 1;
          638       SetSelectForNetworkBuffer(netbuf, &readfds, &writefds, &errorfds,
          639                                 &maxsock);
          640       if (bselect(maxsock, &readfds, &writefds, &errorfds, NULL) == -1) {
          641         if (errno == EINTR) {
          642           CheckForResize(Play);
          643           continue;
          644         }
          645         perror("bselect");
          646         exit(EXIT_FAILURE);
          647       }
          648       if (FD_ISSET(0, &readfds)) {
          649         /* So that Ctrl-L works */
          650         c = getch();
          651 #ifndef CYGWIN
          652         if (c == '\f')
          653           wrefresh(curscr);
          654 #endif
          655       }
          656       RespondToSelect(netbuf, &readfds, &writefds, &errorfds, &doneOK);
          657     }
          658   }
          659 
          660   if (!doneOK)
          661     g_string_assign_error(errstr, netbuf->error);
          662   return doneOK;
          663 }
          664 
          665 /* 
          666  * Connects to a dopewars server. Prompts the user to select a server
          667  * if necessary. Returns TRUE, unless the user elected to quit the
          668  * program rather than choose a valid server.
          669  */
          670 static gboolean ConnectToServer(Player *Play)
          671 {
          672   gboolean MetaOK = TRUE, NetOK = TRUE, firstrun = FALSE;
          673   GString *errstr;
          674   gchar *text;
          675   int c, top = get_ui_area_top();
          676 
          677   errstr = g_string_new("");
          678 
          679   if (g_ascii_strncasecmp(ServerName, SN_META, strlen(SN_META)) == 0 || ConnectMethod == CM_META) {
          680     ConnectMethod = CM_META;
          681     MetaOK = SelectServerFromMetaServer(Play, errstr);
          682   } else if (g_ascii_strncasecmp(ServerName, SN_PROMPT, strlen(SN_PROMPT)) == 0 ||
          683              ConnectMethod == CM_PROMPT) {
          684     ConnectMethod = CM_PROMPT;
          685     SelectServerManually();
          686   } else if (g_ascii_strncasecmp(ServerName, SN_SINGLE, strlen(SN_SINGLE)) == 0 ||
          687              ConnectMethod == CM_SINGLE) {
          688     ConnectMethod = CM_SINGLE;
          689     g_string_free(errstr, TRUE);
          690     return TRUE;
          691   } else
          692     firstrun = TRUE;
          693 
          694   while (1) {
          695     attrset(TextAttr);
          696     clear_bottom();
          697     if (MetaOK && !firstrun) {
          698       mvaddstr(top + 1, 1, _("Please wait... attempting to contact "
          699                              "dopewars server..."));
          700       refresh();
          701       NetOK = DoConnect(Play, errstr);
          702     }
          703     if (!NetOK || !MetaOK || firstrun) {
          704       firstrun = FALSE;
          705       clear_line(top);
          706       clear_line(top + 1);
          707       if (!MetaOK) {
          708         /* Display of an error while contacting the metaserver */
          709         mvaddstr(top, 1, _("Cannot get metaserver details"));
          710         text = g_strdup_printf("   (%s)", errstr->str);
          711         mvaddstr(top + 1, 1, text);
          712         g_free(text);
          713       } else if (!NetOK) {
          714         /* Display of an error message while trying to contact a dopewars
          715            server (the error message itself is displayed on the next
          716            screen line) */
          717         mvaddstr(top, 1, _("Could not start multiplayer dopewars"));
          718         text = g_strdup_printf("   (%s)",
          719                                errstr->str[0] ? errstr->str
          720                                   : _("connection to server failed"));
          721         mvaddstr(top + 1, 1, text);
          722         g_free(text);
          723       }
          724       MetaOK = NetOK = TRUE;
          725       attrset(PromptAttr);
          726       mvaddstr(top + 2, 1,
          727                _("Will you... C>onnect to a named dopewars server"));
          728       mvaddstr(top + 3, 1,
          729                _("            L>ist the servers on the metaserver, and "
          730                  "select one"));
          731       mvaddstr(top + 4, 1,
          732                _("            Q>uit (where you can start a server "
          733                  "by typing \"dopewars -s\")"));
          734       mvaddstr(top + 5, 1, _("         or P>lay single-player ? "));
          735       attrset(TextAttr);
          736 
          737       /* Translate these 4 keys in line with the above options, keeping
          738          the order the same (C>onnect, L>ist, Q>uit, P>lay single-player) */
          739       c = GetKey(N_("CLQP"), FALSE, FALSE, FALSE);
          740       switch (c) {
          741       case 'Q':
          742         g_string_free(errstr, TRUE);
          743         return FALSE;
          744       case 'P':
          745         g_string_free(errstr, TRUE);
          746         return TRUE;
          747       case 'L':
          748         MetaOK = SelectServerFromMetaServer(Play, errstr);
          749         break;
          750       case 'C':
          751         SelectServerManually();
          752         break;
          753       }
          754     } else
          755       break;
          756   }
          757   g_string_free(errstr, TRUE);
          758   Client = Network = TRUE;
          759   return TRUE;
          760 }
          761 #endif /* NETWORKING */
          762 
          763 /*
          764  * Displays the list of null-terminated names given by the "names"
          765  * parameter in the bottom part of the screen. The names are spaced
          766  * in columns so as to attempt to make best use of the available space.
          767  * After displaying the names, the list is freed.
          768  */
          769 void display_select_list(GSList *names)
          770 {
          771   int top = get_ui_area_top() + 1, maxrows;
          772   guint maxlen = 0;
          773   int numcols, numrows, xoff, count, numlist;
          774   GSList *listpt;
          775 
          776   maxrows = get_prompt_line() - top;
          777 
          778   for (listpt = names, numlist = 0; listpt;
          779        listpt = g_slist_next(listpt), numlist++) {
          780     maxlen = MAX(maxlen, strcharlen(listpt->data));
          781   }
          782 
          783   maxlen += 3;
          784   numcols = Width / maxlen;
          785   numcols = MAX(numcols, 1);
          786 
          787   /* Try and make the list reasonably "square" */
          788   while(numcols > 1) {
          789     numrows = (numlist + numcols - 2) / (numcols - 1);
          790     if (numrows <= maxrows && numrows <= numcols && numcols > 1) {
          791       numcols--;
          792     } else {
          793       break;
          794     }
          795   }
          796 
          797   xoff = (Width - numcols * maxlen + 3) / 2;
          798   xoff = MAX(xoff, 0);
          799 
          800   count = 0;
          801   for (listpt = names; listpt; listpt = g_slist_next(listpt), count++) {
          802     mvaddstr(top + count / numcols, xoff + maxlen * (count % numcols),
          803              listpt->data);
          804     g_free(listpt->data);
          805   }
          806   g_slist_free(names);
          807 }
          808 
          809 /* 
          810  * Displays the list of locations and prompts the user to select one.
          811  * If "AllowReturn" is TRUE, then if the current location is selected
          812  * simply drop back to the main game loop, otherwise send a request
          813  * to the server to move to the new location. If FALSE, the user MUST
          814  * choose a new location to move to. The active client player is
          815  * passed in "Play".
          816  * N.B. May set the global variable DisplayMode.
          817  * Returns: TRUE if the user chose to jet to a new location,
          818  *          FALSE if the action was canceled instead.
          819  */
          820 static gboolean jet(Player *Play, gboolean AllowReturn)
          821 {
          822   int i, c;
          823   GSList *names = NULL;
          824   GString *str;
          825 
          826   str = g_string_new("");
          827   attrset(TextAttr);
          828   clear_bottom();
          829   for (i = 0; i < NumLocation; i++) {
          830     gchar *text;
          831     /* Display of shortcut keys and locations to jet to */
          832     text = dpg_strdup_printf(_("%d. %tde"), i + 1, Location[i].Name);
          833     names = g_slist_append(names, text);
          834   }
          835   display_select_list(names);
          836   attrset(PromptAttr);
          837 
          838   /* Prompt when the player chooses to "jet" to a new location */
          839   mvaddstr(get_prompt_line(), 22, _("Where to, doctor ? "));
          840   attrset(TextAttr);
          841   curs_set(1);
          842   do {
          843     c = bgetch();
          844     if (c >= '1' && c < '1' + NumLocation) {
          845       dpg_string_printf(str, _("%/Location display/%tde"),
          846                          Location[c - '1'].Name);
          847       addstr(str->str);
          848       if (Play->IsAt != c - '1') {
          849         g_string_printf(str, "%d", c - '1');
          850         DisplayMode = DM_NONE;
          851         SendClientMessage(Play, C_NONE, C_REQUESTJET, NULL, str->str);
          852       } else {
          853         c = 0;
          854       }
          855     } else {
          856       c = 0;
          857     }
          858   } while (c == 0 && !AllowReturn);
          859 
          860   curs_set(0);
          861   g_string_free(str, TRUE);
          862 
          863   return (c != 0);
          864 }
          865 
          866 /* 
          867  * Prompts the user "Play" to drop some of the currently carried drugs.
          868  */
          869 static void DropDrugs(Player *Play)
          870 {
          871   int i, c, num, NumDrugs, top = get_ui_area_top();
          872   GString *text;
          873   gchar *buf;
          874 
          875   attrset(TextAttr);
          876   clear_bottom();
          877   text = g_string_new("");
          878   dpg_string_printf(text,
          879                      /* List of drugs that you can drop (%tde = "drugs" by 
          880                         default) */
          881                      _("You can\'t get any cash for the following "
          882                        "carried %tde :"), Names.Drugs);
          883   mvaddstr(top, 1, text->str);
          884   NumDrugs = 0;
          885   for (i = 0; i < NumDrug; i++) {
          886     if (Play->Drugs[i].Carried > 0 && Play->Drugs[i].Price == 0) {
          887       g_string_printf(text, "%c. %-10s %-8d", NumDrugs + 'A',
          888                        Drug[i].Name, Play->Drugs[i].Carried);
          889       mvaddstr(top + NumDrugs / 3, (NumDrugs % 3) * 25 + 4, text->str);
          890       NumDrugs++;
          891     }
          892   }
          893   attrset(PromptAttr);
          894   mvaddstr(get_prompt_line(), 20, _("What do you want to drop? "));
          895   curs_set(1);
          896   attrset(TextAttr);
          897   c = bgetch();
          898   c = toupper(c);
          899   for (i = 0; c >= 'A' && c < 'A' + NumDrugs && i < NumDrug; i++) {
          900     if (Play->Drugs[i].Carried > 0 && Play->Drugs[i].Price == 0) {
          901       c--;
          902       if (c < 'A') {
          903         addstr(Drug[i].Name);
          904         buf = nice_input(_("How many do you drop? "), get_prompt_line() + 1,
          905                          8, TRUE, NULL, '\0');
          906         num = atoi(buf);
          907         g_free(buf);
          908         if (num > 0) {
          909           g_string_printf(text, "drug^%d^%d", i, -num);
          910           SendClientMessage(Play, C_NONE, C_BUYOBJECT, NULL, text->str);
          911         }
          912       }
          913     }
          914   }
          915   g_string_free(text, TRUE);
          916 }
          917 
          918 /* 
          919  * Prompts the user (i.e. the owner of client "Play") to buy drugs if
          920  * "Buy" is TRUE, or to sell drugs otherwise. A list of available drugs
          921  * is displayed, and on receiving the selection, the user is prompted
          922  * for the number of drugs desired. Finally a message is sent to the
          923  * server to buy or sell the required quantity.
          924  */
          925 static void DealDrugs(Player *Play, gboolean Buy)
          926 {
          927   int i, c, NumDrugsHere;
          928   gchar *text, *input;
          929   int DrugNum, CanCarry, CanAfford;
          930 
          931   NumDrugsHere = 0;
          932   for (c = 0; c < NumDrug; c++)
          933     if (Play->Drugs[c].Price > 0)
          934       NumDrugsHere++;
          935 
          936   clear_line(get_prompt_line());
          937   attrset(PromptAttr);
          938   if (Buy) {
          939     /* Buy and sell prompts for dealing drugs or guns */
          940     mvaddstr(get_prompt_line(), 20, _("What do you wish to buy? "));
          941   } else {
          942     mvaddstr(get_prompt_line(), 20, _("What do you wish to sell? "));
          943   }
          944   curs_set(1);
          945   attrset(TextAttr);
          946   c = bgetch();
          947   c = toupper(c);
          948   if (c >= 'A' && c < 'A' + NumDrugsHere) {
          949     DrugNum = -1;
          950     c -= 'A';
          951     for (i = 0; i <= c; i++)
          952       DrugNum = GetNextDrugIndex(DrugNum, Play);
          953     addstr(Drug[DrugNum].Name);
          954     CanCarry = Play->CoatSize;
          955     CanAfford = Play->Cash / Play->Drugs[DrugNum].Price;
          956 
          957     if (Buy) {
          958       /* Display of number of drugs you could buy and/or carry, when
          959          buying drugs */
          960       text = g_strdup_printf(_("You can afford %d, and can carry %d. "),
          961                              CanAfford, CanCarry);
          962       mvaddstr(get_prompt_line() + 1, 2, text);
          963       input = nice_input(_("How many do you buy? "), get_prompt_line() + 1,
          964                          2 + strcharlen(text), TRUE, NULL, '\0');
          965       c = atoi(input);
          966       g_free(input);
          967       g_free(text);
          968       if (c >= 0) {
          969         text = g_strdup_printf("drug^%d^%d", DrugNum, c);
          970         SendClientMessage(Play, C_NONE, C_BUYOBJECT, NULL, text);
          971         g_free(text);
          972       }
          973     } else {
          974       /* Display of number of drugs you have, when selling drugs */
          975       text =
          976           g_strdup_printf(_("You have %d. "),
          977                           Play->Drugs[DrugNum].Carried);
          978       mvaddstr(get_prompt_line() + 1, 2, text);
          979       input = nice_input(_("How many do you sell? "), get_prompt_line() + 1,
          980                          2 + strcharlen(text), TRUE, NULL, '\0');
          981       c = atoi(input);
          982       g_free(input);
          983       g_free(text);
          984       if (c >= 0) {
          985         text = g_strdup_printf("drug^%d^%d", DrugNum, -c);
          986         SendClientMessage(Play, C_NONE, C_BUYOBJECT, NULL, text);
          987         g_free(text);
          988       }
          989     }
          990   }
          991   curs_set(0);
          992 }
          993 
          994 /* 
          995  * Prompts the user (player "Play") to give an errand to one of his/her
          996  * bitches. The decision is relayed to the server for implementation.
          997  */
          998 static void GiveErrand(Player *Play)
          999 {
         1000   int c, y;
         1001   GString *text;
         1002   Player *To;
         1003 
         1004   text = g_string_new("");
         1005   attrset(TextAttr);
         1006   clear_bottom();
         1007   y = get_ui_area_top() + 1;
         1008 
         1009   /* Prompt for sending your bitches out to spy etc. (%tde = "bitches" by
         1010    * default) */
         1011   dpg_string_printf(text,
         1012                      _("Choose an errand to give one of your %tde..."),
         1013                      Names.Bitches);
         1014   mvaddstr(y++, 1, text->str);
         1015   attrset(PromptAttr);
         1016   if (Play->Bitches.Carried > 0) {
         1017     dpg_string_printf(text,
         1018                        _("   S>py on another dealer                  "
         1019                          "(cost: %P)"), Prices.Spy);
         1020     mvaddstr(y++, 2, text->str);
         1021     dpg_string_printf(text,
         1022                        _("   T>ip off the cops to another dealer     "
         1023                          "(cost: %P)"), Prices.Tipoff);
         1024     mvaddstr(y++, 2, text->str);
         1025     mvaddstr(y++, 2, _("   G>et stuffed"));
         1026   }
         1027   if (Play->Flags & SPYINGON) {
         1028     mvaddstr(y++, 2, _("or C>ontact your spies and receive reports"));
         1029   }
         1030   mvaddstr(y++, 2, _("or N>o errand ? "));
         1031   curs_set(1);
         1032   attrset(TextAttr);
         1033 
         1034   /* Translate these 5 keys to match the above options, keeping the
         1035      original order the same (S>py, T>ip off, G>et stuffed, C>ontact spy,
         1036      N>o errand) */
         1037   c = GetKey(N_("STGCN"), TRUE, FALSE, FALSE);
         1038 
         1039   if (Play->Bitches.Carried > 0 || c == 'C')
         1040     switch (c) {
         1041     case 'S':
         1042       To = ListPlayers(Play, TRUE, _("Whom do you want to spy on? "));
         1043       if (To)
         1044         SendClientMessage(Play, C_NONE, C_SPYON, To, NULL);
         1045       break;
         1046     case 'T':
         1047       To = ListPlayers(Play, TRUE,
         1048                        _("Whom do you want to tip the cops off to? "));
         1049       if (To)
         1050         SendClientMessage(Play, C_NONE, C_TIPOFF, To, NULL);
         1051       break;
         1052     case 'G':
         1053       attrset(PromptAttr);
         1054       /* Prompt for confirmation of sacking a bitch */
         1055       addstr(_(" Are you sure? "));
         1056 
         1057       /* The two keys that are valid for answering Yes/No - if you
         1058          translate them, keep them in the same order - i.e. "Yes" before
         1059          "No" */
         1060       c = GetKey(N_("YN"), FALSE, TRUE, FALSE);
         1061 
         1062       if (c == 'Y')
         1063         SendClientMessage(Play, C_NONE, C_SACKBITCH, NULL, NULL);
         1064       break;
         1065     case 'C':
         1066       if (Play->Flags & SPYINGON) {
         1067         SendClientMessage(Play, C_NONE, C_CONTACTSPY, NULL, NULL);
         1068       }
         1069       break;
         1070     }
         1071 }
         1072 
         1073 /* 
         1074  * Asks the user if he/she _really_ wants to quit dopewars.
         1075  */
         1076 static int want_to_quit(void)
         1077 {
         1078   attrset(TextAttr);
         1079   clear_line(get_prompt_line());
         1080   attrset(PromptAttr);
         1081   mvaddstr(get_prompt_line(), 1, _("Are you sure you want to quit? "));
         1082   attrset(TextAttr);
         1083   return (GetKey(N_("YN"), FALSE, TRUE, FALSE) != 'N');
         1084 }
         1085 
         1086 /* 
         1087  * Prompts the user to change his or her name, and notifies the server.
         1088  */
         1089 static void change_name(Player *Play, gboolean nullname)
         1090 {
         1091   gchar *NewName;
         1092 
         1093   /* Prompt for player to change his/her name */
         1094   NewName = nice_input(_("New name: "), get_prompt_line() + 1, 0, FALSE,
         1095                        NULL, '\0');
         1096 
         1097   if (NewName[0]) {
         1098     StripTerminators(NewName);
         1099     if (nullname) {
         1100       SendNullClientMessage(Play, C_NONE, C_NAME, NULL, NewName);
         1101     } else {
         1102       SendClientMessage(Play, C_NONE, C_NAME, NULL, NewName);
         1103     }
         1104     SetPlayerName(Play, NewName);
         1105   }
         1106   g_free(NewName);
         1107 }
         1108 
         1109 /* 
         1110  * Given a message "Message" coming in for player "Play", performs
         1111  * processing and reacts properly; if a message indicates the end of the
         1112  * game, the global variable QuitRequest is set. The global variable
         1113  * DisplayMode may also be changed by this routine as a result of network
         1114  * traffic.
         1115  */
         1116 void HandleClientMessage(char *Message, Player *Play)
         1117 {
         1118   char *pt, *Data, *wrd;
         1119   AICode AI;
         1120   MsgCode Code;
         1121   Player *From, *tmp;
         1122   GSList *list;
         1123   gchar *text;
         1124   int i;
         1125   gboolean Handled;
         1126 
         1127   /* Ignore To: field - all messages will be for Player "Play" */
         1128   if (ProcessMessage(Message, Play, &From, &AI, &Code, &Data, FirstClient)
         1129       == -1) {
         1130     return;
         1131   }
         1132 
         1133   Handled =
         1134       HandleGenericClientMessage(From, AI, Code, Play, Data, &DisplayMode);
         1135   switch (Code) {
         1136   case C_ENDLIST:
         1137     if (FirstClient && g_slist_next(FirstClient)) {
         1138       ListPlayers(Play, FALSE, NULL);
         1139     }
         1140     break;
         1141   case C_STARTHISCORE:
         1142     PrepareHighScoreScreen();
         1143     break;
         1144   case C_HISCORE:
         1145     PrintHighScore(Data);
         1146     break;
         1147   case C_ENDHISCORE:
         1148     if (strcmp(Data, "end") == 0) {
         1149       QuitRequest = TRUE;
         1150     } else {
         1151       nice_wait();
         1152       clear_screen();
         1153       display_message("");
         1154       print_status(Play, TRUE);
         1155       refresh();
         1156     }
         1157     break;
         1158   case C_PUSH:
         1159     attrset(TextAttr);
         1160     clear_line(get_prompt_line());
         1161     mvaddstr(get_prompt_line(), 0, _("You have been pushed from the server. "
         1162                                      "Reverting to single player mode."));
         1163     nice_wait();
         1164     SwitchToSinglePlayer(Play);
         1165     print_status(Play, TRUE);
         1166     break;
         1167   case C_QUIT:
         1168     attrset(TextAttr);
         1169     clear_line(get_prompt_line());
         1170     mvaddstr(get_prompt_line(), 0,
         1171              _("The server has terminated. Reverting to "
         1172                "single player mode."));
         1173     nice_wait();
         1174     SwitchToSinglePlayer(Play);
         1175     print_status(Play, TRUE);
         1176     break;
         1177   case C_MSG:
         1178     text = g_strdup_printf("%s: %s", GetPlayerName(From), Data);
         1179     display_message(text);
         1180     g_free(text);
         1181     SoundPlay(Sounds.TalkToAll);
         1182     break;
         1183   case C_MSGTO:
         1184     text = g_strdup_printf("%s->%s: %s", GetPlayerName(From),
         1185                            GetPlayerName(Play), Data);
         1186     display_message(text);
         1187     g_free(text);
         1188     SoundPlay(Sounds.TalkPrivate);
         1189     break;
         1190   case C_JOIN:
         1191     text = g_strdup_printf(_("%s joins the game!"), Data);
         1192     display_message(text);
         1193     g_free(text);
         1194     SoundPlay(Sounds.JoinGame);
         1195     break;
         1196   case C_LEAVE:
         1197     if (From != &Noone) {
         1198       text = g_strdup_printf(_("%s has left the game."), Data);
         1199       display_message(text);
         1200       g_free(text);
         1201       SoundPlay(Sounds.LeaveGame);
         1202     }
         1203     break;
         1204   case C_RENAME:
         1205     /* Displayed when a player changes his/her name */
         1206     text = g_strdup_printf(_("%s will now be known as %s."),
         1207                            GetPlayerName(From), Data);
         1208     SetPlayerName(From, Data);
         1209     mvaddstr(get_prompt_line(), 0, text);
         1210     g_free(text);
         1211     nice_wait();
         1212     break;
         1213   case C_PRINTMESSAGE:
         1214     PrintMessage(Data);
         1215     nice_wait();
         1216     break;
         1217   case C_FIGHTPRINT:
         1218     DisplayFightMessage(Play, Data);
         1219     break;
         1220   case C_SUBWAYFLASH:
         1221     DisplayFightMessage(Play, NULL);
         1222     for (list = FirstClient; list; list = g_slist_next(list)) {
         1223       tmp = (Player *)list->data;
         1224       tmp->Flags &= ~FIGHTING;
         1225     }
         1226     SoundPlay(Sounds.Jet);
         1227     for (i = 0; i < 4; i++) {
         1228       print_location(_("S U B W A Y"));
         1229       refresh();
         1230       MicroSleep(100000);
         1231       print_location("");
         1232       refresh();
         1233       MicroSleep(100000);
         1234     }
         1235     text = dpg_strdup_printf(_("%/Current location/%tde"),
         1236                              Location[Play->IsAt].Name);
         1237     print_location(text);
         1238     g_free(text);
         1239     break;
         1240   case C_QUESTION:
         1241     pt = Data;
         1242     wrd = GetNextWord(&pt, "");
         1243     PrintMessage(pt);
         1244     addch(' ');
         1245     i = GetKey(wrd, FALSE, TRUE, TRUE);
         1246     wrd = g_strdup_printf("%c", i);
         1247     SendClientMessage(Play, C_NONE, C_ANSWER,
         1248                       From == &Noone ? NULL : From, wrd);
         1249     g_free(wrd);
         1250     break;
         1251   case C_LOANSHARK:
         1252     LoanShark(Play);
         1253     SendClientMessage(Play, C_NONE, C_DONE, NULL, NULL);
         1254     break;
         1255   case C_BANK:
         1256     Bank(Play);
         1257     SendClientMessage(Play, C_NONE, C_DONE, NULL, NULL);
         1258     break;
         1259   case C_GUNSHOP:
         1260     GunShop(Play);
         1261     SendClientMessage(Play, C_NONE, C_DONE, NULL, NULL);
         1262     break;
         1263   case C_UPDATE:
         1264     if (From == &Noone) {
         1265       ReceivePlayerData(Play, Data, Play);
         1266       print_status(Play, TRUE);
         1267       refresh();
         1268     } else {
         1269       DisplaySpyReports(Data, From, Play);
         1270     }
         1271     break;
         1272   case C_NEWNAME:
         1273     clear_line(get_prompt_line());
         1274     clear_line(get_prompt_line() + 1);
         1275     attrset(TextAttr);
         1276     mvaddstr(get_prompt_line(), 0,
         1277              _("Unfortunately, somebody else is already "
         1278                "using \"your\" name. Please change it."));
         1279     change_name(Play, TRUE);
         1280     break;
         1281   default:
         1282     if (!Handled) {
         1283       text = g_strdup_printf("%s^%c^%s^%s", GetPlayerName(From), Code,
         1284                              GetPlayerName(Play), Data);
         1285       mvaddstr(get_prompt_line(), 0, text);
         1286       g_free(text);
         1287       nice_wait();
         1288     }
         1289     break;
         1290   }
         1291 }
         1292 
         1293 /* 
         1294  * Responds to a "starthiscore" message by clearing the screen and
         1295  * displaying the title for the high scores screen.
         1296  */
         1297 void PrepareHighScoreScreen(void)
         1298 {
         1299   char *text;
         1300 
         1301   attrset(TextAttr);
         1302   clear_screen();
         1303   attrset(TitleAttr);
         1304   text = _("H I G H   S C O R E S");
         1305   mvaddstr(0, (Width - strcharlen(text)) / 2, text);
         1306   attrset(TextAttr);
         1307 }
         1308 
         1309 /* 
         1310  * Prints a high score coded in "Data"; first word is the index of the
         1311  * score (i.e. y screen coordinate), second word is the text, the first
         1312  * letter of which identifies whether it's to be printed bold or not.
         1313  */
         1314 void PrintHighScore(char *Data)
         1315 {
         1316   char *cp;
         1317   int index;
         1318 
         1319   cp = Data;
         1320   index = GetNextInt(&cp, 0);
         1321   if (!cp || strlen(cp) < 2)
         1322     return;
         1323   move(index + 2, 0);
         1324   attrset(TextAttr);
         1325   if (cp[0] == 'B')
         1326     standout();
         1327   addstr(&cp[1]);
         1328   if (cp[0] == 'B')
         1329     standend();
         1330 }
         1331 
         1332 /* 
         1333  * Prints a message "text" received via. a "printmessage" message in the
         1334  * bottom part of the screen.
         1335  */
         1336 void PrintMessage(const gchar *text)
         1337 {
         1338   guint i, line;
         1339 
         1340   attrset(TextAttr);
         1341   clear_line(get_ui_area_top());
         1342 
         1343   line = 1;
         1344   for (i = 0; i < strlen(text) && (text[i] == '^' || text[i] == '\n'); i++)
         1345     line++;
         1346   clear_exceptfor(line);
         1347 
         1348   line = get_ui_area_top() + 1;
         1349   move(line, 1);
         1350   for (i = 0; i < strlen(text); i++) {
         1351     if (text[i] == '^' || text[i] == '\n') {
         1352       line++;
         1353       move(line, 1);
         1354     } else if (text[i] != '\r')
         1355       addch((guchar)text[i]);
         1356   }
         1357 }
         1358 
         1359 static void SellGun(Player *Play)
         1360 {
         1361   gchar *text;
         1362   gint gunind;
         1363 
         1364   clear_line(get_prompt_line());
         1365   if (TotalGunsCarried(Play) == 0) {
         1366     /* Error - player tried to sell guns that he/she doesn't have
         1367        (%tde="guns" by default) */
         1368     text = dpg_strdup_printf(_("You don't have any %tde to sell!"),
         1369                              Names.Guns);
         1370     mvaddcentstr(get_prompt_line(), text);
         1371     g_free(text);
         1372     nice_wait();
         1373     clear_line(get_prompt_line() + 1);
         1374   } else {
         1375     attrset(PromptAttr);
         1376     mvaddstr(get_prompt_line(), 20, _("What do you wish to sell? "));
         1377     curs_set(1);
         1378     attrset(TextAttr);
         1379     gunind = bgetch();
         1380     gunind = toupper(gunind);
         1381     if (gunind >= 'A' && gunind < 'A' + NumGun) {
         1382       gunind -= 'A';
         1383       addstr(Gun[gunind].Name);
         1384       if (Play->Guns[gunind].Carried == 0) {
         1385         clear_line(get_prompt_line());
         1386         /* Error - player tried to sell some guns that he/she doesn't have */
         1387         mvaddstr(get_prompt_line(), 10, _("You don't have any to sell!"));
         1388         nice_wait();
         1389         clear_line(get_prompt_line() + 1);
         1390       } else {
         1391         Play->Cash += Gun[gunind].Price;
         1392         Play->CoatSize += Gun[gunind].Space;
         1393         Play->Guns[gunind].Carried--;
         1394         text = g_strdup_printf("gun^%d^-1", gunind);
         1395         SendClientMessage(Play, C_NONE, C_BUYOBJECT, NULL, text);
         1396         g_free(text);
         1397         print_status(Play, FALSE);
         1398       }
         1399     }
         1400   }
         1401 }
         1402 
         1403 static void BuyGun(Player *Play)
         1404 {
         1405   gchar *text;
         1406   gint gunind;
         1407 
         1408   clear_line(get_prompt_line());
         1409   if (TotalGunsCarried(Play) >= Play->Bitches.Carried + 2) {
         1410     text = dpg_strdup_printf(
         1411                               /* Error - player tried to buy more guns
         1412                                  than his/her bitches can carry (1st
         1413                                  %tde="bitches", 2nd %tde="guns" by
         1414                                  default) */
         1415                               _("You'll need more %tde to carry "
         1416                                 "any more %tde!"),
         1417                               Names.Bitches, Names.Guns);
         1418     mvaddcentstr(get_prompt_line(), text);
         1419     g_free(text);
         1420     nice_wait();
         1421     clear_line(get_prompt_line() + 1);
         1422   } else {
         1423     attrset(PromptAttr);
         1424     mvaddstr(get_prompt_line(), 20, _("What do you wish to buy? "));
         1425     curs_set(1);
         1426     attrset(TextAttr);
         1427     gunind = bgetch();
         1428     gunind = toupper(gunind);
         1429     if (gunind >= 'A' && gunind < 'A' + NumGun) {
         1430       gunind -= 'A';
         1431       addstr(Gun[gunind].Name);
         1432       if (Gun[gunind].Space > Play->CoatSize) {
         1433         clear_line(get_prompt_line());
         1434         /* Error - player tried to buy a gun that he/she doesn't have
         1435            space for (%tde="gun" by default) */
         1436         text = dpg_strdup_printf(_("You don't have enough space to "
         1437                                    "carry that %tde!"), Names.Gun);
         1438         mvaddcentstr(get_prompt_line(), text);
         1439         g_free(text);
         1440         nice_wait();
         1441         clear_line(get_prompt_line() + 1);
         1442       } else if (Gun[gunind].Price > Play->Cash) {
         1443         clear_line(get_prompt_line());
         1444         /* Error - player tried to buy a gun that he/she can't afford
         1445            (%tde="gun" by default) */
         1446         text = dpg_strdup_printf(_("You don't have enough cash to buy "
         1447                                    "that %tde!"), Names.Gun);
         1448         mvaddcentstr(get_prompt_line(), text);
         1449         g_free(text);
         1450         nice_wait();
         1451         clear_line(get_prompt_line() + 1);
         1452       } else {
         1453         Play->Cash -= Gun[gunind].Price;
         1454         Play->CoatSize -= Gun[gunind].Space;
         1455         Play->Guns[gunind].Carried++;
         1456         text = g_strdup_printf("gun^%d^1", gunind);
         1457         SendClientMessage(Play, C_NONE, C_BUYOBJECT, NULL, text);
         1458         g_free(text);
         1459         print_status(Play, FALSE);
         1460       }
         1461     }
         1462   }
         1463 }
         1464 
         1465 /* 
         1466  * Allows player "Play" to buy and sell guns interactively. Passes the
         1467  * decisions on to the server for sanity checking and implementation.
         1468  */
         1469 void GunShop(Player *Play)
         1470 {
         1471   int i, action;
         1472   GSList *names = NULL;
         1473   gchar *text;
         1474 
         1475   print_status(Play, FALSE);
         1476   attrset(TextAttr);
         1477   clear_bottom();
         1478   for (i = 0; i < NumGun; i++) {
         1479     text = dpg_strdup_printf("%c. %-22tde %12P", 'A' + i, Gun[i].Name,
         1480                              Gun[i].Price);
         1481     names = g_slist_append(names, text);
         1482   }
         1483   display_select_list(names);
         1484   do {
         1485     /* Prompt for actions in the gun shop */
         1486     text = _("Will you B>uy, S>ell, or L>eave? ");
         1487     attrset(PromptAttr);
         1488     clear_line(get_prompt_line());
         1489     mvaddcentstr(get_prompt_line(), text);
         1490     attrset(TextAttr);
         1491 
         1492     /* Translate these three keys in line with the above options, keeping
         1493        the order (B>uy, S>ell, L>eave) the same - you can change the
         1494        wording of the prompt, but if you change the order in this key
         1495        list, the keys will do the wrong things! */
         1496     action = GetKey(N_("BSL"), FALSE, FALSE, FALSE);
         1497     if (action == 'S')
         1498       SellGun(Play);
         1499     else if (action == 'B')
         1500       BuyGun(Play);
         1501   } while (action != 'L');
         1502   print_status(Play, TRUE);
         1503 }
         1504 
         1505 /* 
         1506  * Allows player "Play" to pay off loans interactively.
         1507  */
         1508 void LoanShark(Player *Play)
         1509 {
         1510   gchar *text, *prstr;
         1511   price_t money;
         1512 
         1513   do {
         1514     clear_bottom();
         1515     attrset(PromptAttr);
         1516 
         1517     /* Prompt for paying back loans from the loan shark */
         1518     text =
         1519         nice_input(_("How much money do you pay back? "), 19, 1, TRUE,
         1520                    NULL, '\0');
         1521     attrset(TextAttr);
         1522     money = strtoprice(text);
         1523     g_free(text);
         1524     if (money < 0)
         1525       money = 0;
         1526     if (money > Play->Debt)
         1527       money = Play->Debt;
         1528     if (money > Play->Cash) {
         1529       /* Error - player doesn't have enough money to pay back the loan */
         1530       mvaddstr(20, 1, _("You don't have that much money!"));
         1531       nice_wait();
         1532     } else {
         1533       SendClientMessage(Play, C_NONE, C_PAYLOAN, NULL,
         1534                         (prstr = pricetostr(money)));
         1535       g_free(prstr);
         1536       money = 0;
         1537     }
         1538   } while (money != 0);
         1539 }
         1540 
         1541 /* 
         1542  * Allows player "Play" to pay in or withdraw money from the bank
         1543  * interactively.
         1544  */
         1545 void Bank(Player *Play)
         1546 {
         1547   gchar *text, *prstr;
         1548   price_t money = 0;
         1549   int action;
         1550 
         1551   do {
         1552     clear_bottom();
         1553     attrset(PromptAttr);
         1554     /* Prompt for dealing with the bank in the curses client */
         1555     mvaddstr(18, 1, _("Do you want to D>eposit money, W>ithdraw money, "
         1556                       "or L>eave ? "));
         1557     attrset(TextAttr);
         1558 
         1559     /* Make sure you keep the order the same if you translate these keys!
         1560        (D>eposit, W>ithdraw, L>eave) */
         1561     action = GetKey(N_("DWL"), FALSE, FALSE, FALSE);
         1562 
         1563     if (action == 'D' || action == 'W') {
         1564       /* Prompt for putting money in or taking money out of the bank */
         1565       text = nice_input(_("How much money? "), 19, 1, TRUE, NULL, '\0');
         1566 
         1567       money = strtoprice(text);
         1568       g_free(text);
         1569       if (money < 0)
         1570         money = 0;
         1571       if (action == 'W')
         1572         money = -money;
         1573       if (money > Play->Cash) {
         1574         /* Error - player has tried to put more money into the bank than
         1575            he/she has */
         1576         mvaddstr(20, 1, _("You don't have that much money!"));
         1577         nice_wait();
         1578       } else if (-money > Play->Bank) {
         1579         /* Error - player has tried to withdraw more money from the bank
         1580            than there is in the account */
         1581         mvaddstr(20, 1, _("There isn't that much money in the bank..."));
         1582         nice_wait();
         1583       } else if (money != 0) {
         1584         SendClientMessage(Play, C_NONE, C_DEPOSIT, NULL,
         1585                           (prstr = pricetostr(money)));
         1586         g_free(prstr);
         1587         money = 0;
         1588       }
         1589     }
         1590   } while (action != 'L' && money != 0);
         1591 }
         1592 
         1593 /* Output a single Unicode character */
         1594 static void addunich(gunichar ch, int attr)
         1595 {
         1596   if (LocaleIsUTF8) {
         1597     char utf8buf[20];
         1598     gint utf8len = g_unichar_to_utf8(ch, utf8buf);
         1599     utf8buf[utf8len] = '\0';
         1600     attrset(attr);
         1601     addstr(utf8buf);
         1602   } else {
         1603     addch((guchar)ch | attr);
         1604   }
         1605 }
         1606 
         1607 #define MAX_GET_KEY 30
         1608 
         1609 /* 
         1610  * Waits for keyboard input; will only accept a key listed in the
         1611  * translated form of the "orig_allowed" string.
         1612  * Returns the untranslated key corresponding to the key pressed
         1613  * (e.g. if translated(orig_allowed[2]) is pressed, orig_allowed[2] is returned)
         1614  * Case insensitive. If "AllowOther" is TRUE, keys other than the
         1615  * given selection are allowed, and cause a zero return value.
         1616  * If "PrintAllowed" is TRUE, the allowed keys are printed after
         1617  * the prompt. If "ExpandOut" is also TRUE, the full words for
         1618  * the commands, rather than just their first letters, are displayed.
         1619  */
         1620 int GetKey(const char *orig_allowed, gboolean AllowOther,
         1621            gboolean PrintAllowed, gboolean ExpandOut)
         1622 {
         1623   int ch, i, num_allowed;
         1624   guint AllowInd, WordInd;
         1625   gunichar allowed[MAX_GET_KEY];
         1626 
         1627   /* Expansions of the single-letter keypresses for the benefit of the
         1628      user. i.e. "Yes" is printed for the key "Y" etc. You should indicate
         1629      to the user which letter in the word corresponds to the keypress, by
         1630      capitalising it or similar. */
         1631   gchar *Words[] = { N_("Y:Yes"), N_("N:No"), N_("R:Run"),
         1632     N_("F:Fight"), N_("A:Attack"), N_("E:Evade")
         1633   };
         1634   guint numWords = sizeof(Words) / sizeof(Words[0]);
         1635   gchar *trWord;
         1636 
         1637   /* Translate allowed keys
         1638    * Note that allowed is in the locale encoding, usually UTF-8, while
         1639    * orig_allowed is plain ASCII */
         1640   char *allowed_str = _(orig_allowed);
         1641 
         1642   num_allowed = strlen(orig_allowed);
         1643   assert(num_allowed <= MAX_GET_KEY);
         1644   assert(strcharlen(allowed_str) == num_allowed);
         1645 
         1646   if (num_allowed == 0)
         1647     return 0;
         1648 
         1649   /* Get Unicode allowed keys */
         1650   if (LocaleIsUTF8) {
         1651     const char *pt;
         1652     for (pt = allowed_str, i = 0; pt && *pt; pt = g_utf8_next_char(pt), ++i) {
         1653       allowed[i] = g_utf8_get_char(pt);
         1654     }
         1655   } else {
         1656     for (i = 0; i < num_allowed; ++i) {
         1657       allowed[i] = (guchar)allowed_str[i];
         1658     }
         1659   }
         1660 
         1661   curs_set(1);
         1662   ch = '\0';
         1663 
         1664   if (PrintAllowed) {
         1665     addch('[' | TextAttr);
         1666     for (AllowInd = 0; AllowInd < num_allowed; AllowInd++) {
         1667       if (AllowInd > 0)
         1668         addch('/' | TextAttr);
         1669       WordInd = 0;
         1670       while (WordInd < numWords &&
         1671              orig_allowed[AllowInd] != Words[WordInd][0])
         1672         WordInd++;
         1673 
         1674       if (ExpandOut && WordInd < numWords) {
         1675         trWord = strchr(_(Words[WordInd]), ':');
         1676         /* Print everything after the colon */
         1677         if (*trWord) trWord++;
         1678         attrset(TextAttr);
         1679         addstr(trWord);
         1680       } else {
         1681         addunich(allowed[AllowInd], TextAttr);
         1682       }
         1683     }
         1684     addch(']' | TextAttr);
         1685     addch(' ' | TextAttr);
         1686   }
         1687 
         1688   do {
         1689     ch = bgetch();
         1690     ch = LocaleIsUTF8 ? g_unichar_toupper(ch) : toupper(ch);
         1691     /* Handle scrolling of message window */
         1692     if (ch == '-') {
         1693       scroll_msg_area_up();
         1694       continue;
         1695     } else if (ch == '+') {
         1696       scroll_msg_area_down();
         1697       continue;
         1698     }
         1699     for (AllowInd = 0; AllowInd < num_allowed; AllowInd++) {
         1700       if (allowed[AllowInd] == ch) {
         1701         addunich(allowed[AllowInd], TextAttr);
         1702         curs_set(0);
         1703         return orig_allowed[AllowInd];
         1704       }
         1705     }
         1706   } while (!AllowOther);
         1707 
         1708   curs_set(0);
         1709   return 0;
         1710 }
         1711 
         1712 /* 
         1713  * Clears one whole line on the curses screen.
         1714  */
         1715 void clear_line(int line)
         1716 {
         1717   int i;
         1718 
         1719   move(line, 0);
         1720   for (i = 0; i < Width; i++)
         1721     addch(' ');
         1722 }
         1723 
         1724 /* 
         1725  * Clears the bottom of the screen (the user interface)
         1726  * except for the top "skip" lines.
         1727  */
         1728 void clear_exceptfor(int skip)
         1729 {
         1730   int i, from = get_ui_area_top() + skip, to  = get_ui_area_bottom();
         1731 
         1732   for (i = from; i <= to; i++) {
         1733     clear_line(i);
         1734   }
         1735 }
         1736 
         1737 
         1738 /* 
         1739  * Clears the bottom part of the screen (the user interface)
         1740  */
         1741 void clear_bottom(void)
         1742 {
         1743   int i, from = get_ui_area_top(), to = get_ui_area_bottom();
         1744 
         1745   for (i = from; i <= to; i++) {
         1746     clear_line(i);
         1747   }
         1748 }
         1749 
         1750 /* 
         1751  * Clears the entire screen
         1752  */
         1753 void clear_screen(void)
         1754 {
         1755   int i;
         1756 
         1757   for (i = 0; i < Depth; i++) {
         1758     clear_line(i);
         1759   }
         1760 }
         1761 
         1762 /* 
         1763  * Displays a prompt on the bottom screen line and waits for the user
         1764  * to press a key.
         1765  */
         1766 void nice_wait()
         1767 {
         1768   attrset(PromptAttr);
         1769   mvaddcentstr(get_prompt_line() + 1, _("Press any key..."));
         1770   bgetch();
         1771   attrset(TextAttr);
         1772 }
         1773 
         1774 /* 
         1775  * Handles the display of messages pertaining to player-player fights
         1776  * in the lower part of screen (fighting sub-screen). Adds the new line
         1777  * of text in "text" and scrolls up previous messages if necessary
         1778  * If "text" is NULL, initializes the area
         1779  * If "text" is a blank string, redisplays the message area
         1780  * Messages are displayed from lines 16 to 20; line 22 is used for
         1781  * the prompt for the user.
         1782  */
         1783 void DisplayFightMessage(Player *Play, char *text)
         1784 {
         1785   static GList *msgs = NULL;
         1786   static int num_msgs = 0;
         1787   gchar *textpt;
         1788   gchar *AttackName, *DefendName, *BitchName;
         1789   gint y, DefendHealth, DefendBitches, BitchesKilled, ArmPercent;
         1790   gboolean Loot;
         1791   int top = get_ui_area_top(), bottom = get_ui_area_bottom() - 4;
         1792 
         1793   if (text == NULL) {
         1794     GList *pt;
         1795     for (pt = msgs; pt; pt = g_list_next(pt)) {
         1796       g_free(pt->data);
         1797     }
         1798     g_list_free(msgs);
         1799     msgs = NULL;
         1800     num_msgs = 0;
         1801   } else {
         1802     GList *pt;
         1803     if (text[0]) {
         1804       if (HaveAbility(Play, A_NEWFIGHT)) {
         1805         ReceiveFightMessage(text, &AttackName, &DefendName, &DefendHealth,
         1806                             &DefendBitches, &BitchName, &BitchesKilled,
         1807                             &ArmPercent, &fp, &RunHere, &Loot, &CanFire,
         1808                             &textpt);
         1809       } else {
         1810         textpt = text;
         1811         if (Play->Flags & FIGHTING)
         1812           fp = F_MSG;
         1813         else
         1814           fp = F_LASTLEAVE;
         1815         CanFire = (Play->Flags & CANSHOOT);
         1816         RunHere = FALSE;
         1817       }
         1818       msgs = g_list_append(msgs, g_strdup(textpt));
         1819       num_msgs++;
         1820     }
         1821     attrset(TextAttr);
         1822     clear_bottom();
         1823     pt = g_list_last(msgs);
         1824     y = bottom;
         1825     while (pt && g_list_previous(pt) && y > top) {
         1826       y--;
         1827       pt = g_list_previous(pt);
         1828     }
         1829     y = top;
         1830     while (pt) {
         1831       mvaddstr(y++, 1, pt->data);
         1832       pt = g_list_next(pt);
         1833     }
         1834   }
         1835 }
         1836 
         1837 /* Number of lines that the message window is scrolled back by */
         1838 static int scrollpos = 0;
         1839 
         1840 /*
         1841  * Scrolls the message area up a page
         1842  */
         1843 static void scroll_msg_area_up(void)
         1844 {
         1845   scrollpos += (get_msg_area_bottom() - get_msg_area_top() + 1);
         1846   display_message("");
         1847 }
         1848 
         1849 /*
         1850  * Scrolls the message area down a page
         1851  */
         1852 static void scroll_msg_area_down(void)
         1853 {
         1854   scrollpos -= (get_msg_area_bottom() - get_msg_area_top() + 1);
         1855   display_message("");
         1856 }
         1857 
         1858 /* 
         1859  * Displays a network message "buf" in the message area
         1860  * scrolling previous messages up.
         1861  * If "buf" is NULL, clears the message area
         1862  * If "buf" is a blank string, redisplays the message area
         1863  */
         1864 void display_message(const char *buf)
         1865 {
         1866   guint y, top, depth;
         1867   guint wid;
         1868   static GList *msgs = NULL;
         1869   static int num_msgs = 0;
         1870 
         1871   top = get_msg_area_top();
         1872   depth = get_msg_area_bottom() - top + 1;
         1873   wid = Width - 4;
         1874 
         1875   if (wid < 0 || depth < 0 || !Network) {
         1876     return;
         1877   }
         1878 
         1879   if (!buf) {
         1880     GList *pt;
         1881     for (pt = msgs; pt; pt = g_list_next(pt)) {
         1882       g_free(pt->data);
         1883     }
         1884     g_list_free(msgs);
         1885     msgs = NULL;
         1886     num_msgs = 0;
         1887     scrollpos = 0;
         1888     /* Display a blank message area */
         1889     if (Network) {
         1890       for (y = 0; y < depth; y++) {
         1891         mvaddfixwidstr(y + top, 2, wid, NULL, StatsAttr);
         1892       }
         1893     }
         1894   } else if (Network) {
         1895     GList *pt, *nextpt;
         1896     gchar *data;
         1897     if (buf[0]) {
         1898       /* Remove the first message if we've got to the limit */
         1899       if (num_msgs == MaxMessages && msgs) {
         1900         g_free(msgs->data);
         1901         msgs = g_list_remove(msgs, msgs->data);
         1902         --num_msgs;
         1903       }
         1904       msgs = g_list_append(msgs, g_strdup(buf));
         1905       ++num_msgs;
         1906     }
         1907 
         1908     nextpt = g_list_last(msgs);
         1909     pt = NULL;
         1910     data = NULL;
         1911     if (nextpt) {
         1912       int lines = 0, displines, total_lines = 0;
         1913       
         1914       /* Correct for having scrolled down too far */
         1915       if (scrollpos < 0) {
         1916         scrollpos = 0;
         1917       }
         1918 
         1919       displines = depth + scrollpos;
         1920       /* Find the message to display at the top of the message area */
         1921       do {
         1922         displines -= lines;
         1923         pt = nextpt;
         1924         nextpt = g_list_previous(pt);
         1925         data = pt->data;
         1926         lines = (strlen(data) + wid - 1) / wid;
         1927         total_lines += lines;
         1928       } while (displines > lines && nextpt);
         1929       
         1930       /* Correct for having scrolled up too far */
         1931       if ((depth + scrollpos) > total_lines && total_lines > depth) {
         1932         scrollpos = total_lines - depth;
         1933       }
         1934 
         1935       /* Correct for the first line starting partway through a message */
         1936       if (displines < lines) {
         1937         data += wid * (lines - displines);
         1938       }
         1939     }
         1940 
         1941     /* Display the relevant messages, line by line */
         1942     y = 0;
         1943     while (y < depth && pt) {
         1944       mvaddfixwidstr(y + top, 2, wid, data, StatsAttr);
         1945       ++y;
         1946       if (strlen(data) > wid) {
         1947         data += wid;
         1948       } else {
         1949         pt = g_list_next(pt);
         1950         if (pt) {
         1951           data = pt->data;
         1952         }
         1953       }
         1954     }
         1955 
         1956     /* Blank out any remaining lines in the message area */
         1957     for (; y < depth; ++y) {
         1958       mvaddfixwidstr(y + top, 2, wid, NULL, StatsAttr);
         1959     }
         1960     refresh();
         1961   }
         1962 }
         1963 
         1964 /* 
         1965  * Displays the string "text" at the top of the screen. Usually used for
         1966  * displaying the current location or the "Subway" flash.
         1967  */
         1968 void print_location(char *text)
         1969 {
         1970   int i;
         1971 
         1972   if (!text)
         1973     return;
         1974   attrset(LocationAttr);
         1975   move(0, Width / 2 - 9);
         1976   for (i = 0; i < 18; i++)
         1977     addch(' ');
         1978   mvaddcentstr(0, text);
         1979   attrset(TextAttr);
         1980 }
         1981 
         1982 /* 
         1983  * Displays the status of player "Play" - i.e. the current turn, the
         1984  * location, bitches, available space, cash, guns, health and bank
         1985  * details. If "DispDrugs" is TRUE, displays the carried drugs on the
         1986  * right hand side of the screen; if FALSE, displays the carried guns.
         1987  */
         1988 void print_status(Player *Play, gboolean DispDrug)
         1989 {
         1990   int i, c;
         1991   char *p;
         1992   GString *text;
         1993 
         1994   text = g_string_new(NULL);
         1995   attrset(TitleAttr);
         1996   clear_line(0);
         1997   GetDateString(text, Play);
         1998   mvaddstr(0, 3, text->str);
         1999 
         2000   attrset(StatsAttr);
         2001   for (i = get_separator_line() - 1; i >= 2; i--) {
         2002     mvaddch(i, 1, ACS_VLINE);
         2003     mvaddch(i, Width - 2, ACS_VLINE);
         2004   }
         2005   mvaddch(1, 1, ACS_ULCORNER);
         2006   for (i = 0; i < Width - 4; i++)
         2007     addch(ACS_HLINE);
         2008   addch(ACS_URCORNER);
         2009 
         2010   mvaddch(1, Width / 2, ACS_TTEE);
         2011   for (i = 2; i <= (Network ? 8 : 13); i++) {
         2012     move(i, 2);
         2013     for (c = 2; c < Width / 2; c++)
         2014       addch(' ');
         2015     addch(ACS_VLINE);
         2016     for (c = Width / 2 + 1; c < Width - 2; c++)
         2017       addch(' ');
         2018   }
         2019   if (!Network) {
         2020     mvaddch(get_separator_line(), 1, ACS_LLCORNER);
         2021     for (i = 0; i < Width - 4; i++)
         2022       addch(ACS_HLINE);
         2023     addch(ACS_LRCORNER);
         2024     mvaddch(get_separator_line(), Width / 2, ACS_BTEE);
         2025   } else {
         2026     mvaddch(9, 1, ACS_LTEE);
         2027     for (i = 0; i < Width - 4; i++)
         2028       addch(ACS_HLINE);
         2029     addch(ACS_RTEE);
         2030     mvaddch(9, Width / 2, ACS_BTEE);
         2031 
         2032     /* Title of the "Messages" window in the curses client */
         2033     mvaddstr(9, 9, _("Messages (-/+ scrolls up/down)"));
         2034 
         2035     mvaddch(get_separator_line(), 1, ACS_LLCORNER);
         2036     for (i = 0; i < Width - 4; i++) {
         2037       addch(ACS_HLINE);
         2038     }
         2039     addch(ACS_LRCORNER);
         2040   }
         2041 
         2042   /* Title of the "Stats" window in the curses client */
         2043   mvaddstr(1, Width / 4 - 2, _("Stats"));
         2044 
         2045   attrset(StatsAttr);
         2046 
         2047   /* Display of the player's cash in the stats window */
         2048   mvaddstr(3, 9, _("Cash"));
         2049   p = FormatPrice(Play->Cash);
         2050   mvaddrightstr(3, 30, p);
         2051   g_free(p);
         2052 
         2053   /* Display of the total number of guns carried (%Tde="Guns" by default) */
         2054   dpg_string_printf(text, _("%/Stats: Guns/%Tde"), Names.Guns);
         2055   mvaddstr(Network ? 4 : 5, 9, text->str);
         2056   dpg_string_printf(text, "%d", TotalGunsCarried(Play));
         2057   mvaddrightstr(Network ? 4 : 5, 30, text->str);
         2058 
         2059   /* Display of the player's health */
         2060   mvaddstr(Network ? 5 : 7, 9, _("Health"));
         2061   dpg_string_printf(text, "%d", Play->Health);
         2062   mvaddrightstr(Network ? 5 : 7, 30, text->str);
         2063 
         2064   /* Display of the player's bank balance */
         2065   mvaddstr(Network ? 6 : 9, 9, _("Bank"));
         2066   p = FormatPrice(Play->Bank);
         2067   mvaddrightstr(Network ? 6 : 9, 30, p);
         2068   g_free(p);
         2069 
         2070   if (Play->Debt > 0)
         2071     attrset(DebtAttr);
         2072   /* Display of the player's debt */
         2073   g_string_assign(text, _("Debt"));
         2074   p = FormatPrice(Play->Debt);
         2075   /* Put in one big string otherwise the DebtAttr won't be applied to the
         2076      space between "Debt" and the price */
         2077   g_string_pad(text, 22 - strcharlen(text->str) - strcharlen(p));
         2078   g_string_append(text, p);
         2079   g_free(p);
         2080   mvaddstr(Network ? 7 : 11, 9, text->str);
         2081   attrset(TitleAttr);
         2082 
         2083   /* Display of the player's trenchcoat size (antique mode only) */
         2084   if (WantAntique)
         2085     g_string_printf(text, _("Space %6d"), Play->CoatSize);
         2086   else {
         2087     /* Display of the player's number of bitches, and available space
         2088        (%Tde="Bitches" by default) */
         2089     dpg_string_printf(text, _("%Tde %3d  Space %6d"), Names.Bitches,
         2090                        Play->Bitches.Carried, Play->CoatSize);
         2091   }
         2092   mvaddrightstr(0, Width - 3, text->str);
         2093   dpg_string_printf(text, _("%/Current location/%tde"),
         2094                      Location[Play->IsAt].Name);
         2095   print_location(text->str);
         2096   attrset(StatsAttr);
         2097 
         2098   c = 0;
         2099   if (DispDrug) {
         2100     /* Title of the "trenchcoat" window (antique mode only) */
         2101     if (WantAntique)
         2102       mvaddstr(1, Width * 3 / 4 - 5, _("Trenchcoat"));
         2103     else {
         2104       /* Title of the "drugs" window (the only important bit in this
         2105          string is the "%Tde" which is "Drugs" by default; the %/.../ part
         2106          is ignored, so you don't need to translate it; see doc/i18n.html)
         2107        */
         2108       dpg_string_printf(text, _("%/Stats: Drugs/%Tde"), Names.Drugs);
         2109       mvaddstr(1, Width * 3 / 4 - strcharlen(text->str) / 2, text->str);
         2110     }
         2111     for (i = 0; i < NumDrug; i++) {
         2112       if (Play->Drugs[i].Carried > 0) {
         2113         /* Display of carried drugs with price (%tde="Opium", etc. by
         2114            default) */
         2115         if (HaveAbility(Play, A_DRUGVALUE)) {
         2116           dpg_string_printf(text, _("%-7tde  %3d @ %P"), Drug[i].Name,
         2117                              Play->Drugs[i].Carried,
         2118                              Play->Drugs[i].TotalValue /
         2119                              Play->Drugs[i].Carried);
         2120           mvaddstr(3 + c, Width / 2 + 3, text->str);
         2121         } else {
         2122           /* Display of carried drugs (%tde="Opium", etc. by default) */
         2123           dpg_string_printf(text, _("%-7tde  %3d"), Drug[i].Name,
         2124                              Play->Drugs[i].Carried);
         2125           mvaddstr(3 + c / 2, Width / 2 + 3 + (c % 2) * 17, text->str);
         2126         }
         2127         c++;
         2128       }
         2129     }
         2130   } else {
         2131     /* Title of the "guns" window (the only important bit in this string
         2132        is the "%Tde" which is "Guns" by default) */
         2133     dpg_string_printf(text, _("%/Stats: Guns/%Tde"), Names.Guns);
         2134     mvaddstr(1, Width * 3 / 4 - strcharlen(text->str) / 2, text->str);
         2135     for (i = 0; i < NumGun; i++) {
         2136       if (Play->Guns[i].Carried > 0) {
         2137         /* Display of carried guns (%tde="Baretta", etc. by default) */
         2138         dpg_string_printf(text, _("%-22tde %3d"), Gun[i].Name,
         2139                            Play->Guns[i].Carried);
         2140         mvaddstr(3 + c, Width / 2 + 3, text->str);
         2141         c++;
         2142       }
         2143     }
         2144   }
         2145   attrset(TextAttr);
         2146   if (!Network)
         2147     clear_line(get_separator_line() + 1);
         2148   refresh();
         2149   g_string_free(text, TRUE);
         2150 }
         2151 
         2152 /* 
         2153  * Parses details about player "From" from string "Data" and then
         2154  * displays the lot, drugs and guns.
         2155  */
         2156 void DisplaySpyReports(char *Data, Player *From, Player *To)
         2157 {
         2158   gchar *text;
         2159 
         2160   ReceivePlayerData(To, Data, From);
         2161 
         2162   clear_bottom();
         2163   text = g_strdup_printf(_("Spy reports for %s"), GetPlayerName(From));
         2164   mvaddstr(17, 1, text);
         2165   g_free(text);
         2166 
         2167   /* Message displayed with a spy's list of drugs (%Tde="Drugs" by
         2168      default) */
         2169   text = dpg_strdup_printf(_("%/Spy: Drugs/%Tde..."), Names.Drugs);
         2170   mvaddstr(19, 20, text);
         2171   g_free(text);
         2172   print_status(From, TRUE);
         2173   nice_wait();
         2174   clear_line(19);
         2175 
         2176   /* Message displayed with a spy's list of guns (%Tde="Guns" by default) */
         2177   text = dpg_strdup_printf(_("%/Spy: Guns/%Tde..."), Names.Guns);
         2178   mvaddstr(19, 20, text);
         2179   g_free(text);
         2180   print_status(From, FALSE);
         2181   nice_wait();
         2182 
         2183   print_status(To, TRUE);
         2184   refresh();
         2185 }
         2186 
         2187 /* 
         2188  * Displays the "Prompt" if non-NULL, and then lists all clients
         2189  * currently playing dopewars, other than the current player "Play".
         2190  * If "Select" is TRUE, gives each player a letter and asks the user
         2191  * to select one, which is returned by the function.
         2192  */
         2193 Player *ListPlayers(Player *Play, gboolean Select, char *Prompt)
         2194 {
         2195   Player *tmp = NULL;
         2196   GSList *list;
         2197   int i, c, top = get_ui_area_top(), bottom = get_ui_area_bottom();
         2198   gchar *text;
         2199   GSList *names = NULL;
         2200 
         2201   attrset(TextAttr);
         2202   clear_bottom();
         2203   if (!FirstClient || (!g_slist_next(FirstClient) &&
         2204                        FirstClient->data == Play)) {
         2205     text = _("No other players are currently logged on!");
         2206     mvaddcentstr((top + bottom) / 2, text);
         2207     nice_wait();
         2208     return 0;
         2209   }
         2210   mvaddstr(top, 1, _("Players currently logged on:-"));
         2211 
         2212   i = 0;
         2213   for (list = FirstClient; list; list = g_slist_next(list)) {
         2214     tmp = (Player *)list->data;
         2215     if (strcmp(GetPlayerName(tmp), GetPlayerName(Play)) == 0)
         2216       continue;
         2217     if (Select) {
         2218       text = g_strdup_printf("%c. %s", 'A' + i, GetPlayerName(tmp));
         2219     } else {
         2220       text = g_strdup(GetPlayerName(tmp));
         2221     }
         2222     names = g_slist_append(names, text);
         2223     i++;
         2224   }
         2225   display_select_list(names);
         2226 
         2227   if (Prompt) {
         2228     attrset(PromptAttr);
         2229     mvaddstr(get_prompt_line(), 10, Prompt);
         2230     attrset(TextAttr);
         2231   }
         2232   if (Select) {
         2233     curs_set(1);
         2234     attrset(TextAttr);
         2235     c = 0;
         2236     while (c < 'A' || c >= 'A' + i) {
         2237       c = bgetch();
         2238       c = toupper(c);
         2239     }
         2240     if (Prompt)
         2241       addch((guint)c);
         2242     list = FirstClient;
         2243     while (c >= 'A') {
         2244       if (list != FirstClient)
         2245         list = g_slist_next(list);
         2246       tmp = (Player *)list->data;
         2247       while (strcmp(GetPlayerName(tmp), GetPlayerName(Play)) == 0) {
         2248         list = g_slist_next(list);
         2249         tmp = (Player *)list->data;
         2250       }
         2251       c--;
         2252     }
         2253     return tmp;
         2254   } else {
         2255     nice_wait();
         2256   }
         2257   return NULL;
         2258 }
         2259 
         2260 /* 
         2261  * Displays the given "prompt" (if non-NULL) at coordinates sx,sy and
         2262  * allows the user to input a string, which is returned. This is a
         2263  * dynamically allocated string, and so must be freed by the calling
         2264  * routine. If "digitsonly" is TRUE, the user will be permitted only to
         2265  * input numbers, although the suffixes m and k are allowed (the
         2266  * strtoprice routine understands this notation for a 1000000 or 1000
         2267  * multiplier) as well as a decimal point (. or ,)
         2268  * If "displaystr" is non-NULL, it is taken as a default response.
         2269  * If "passwdchar" is non-zero, it is displayed instead of the user's
         2270  * keypresses (e.g. for entering passwords)
         2271  */
         2272 char *nice_input(char *prompt, int sy, int sx, gboolean digitsonly,
         2273                  char *displaystr, char passwdchar)
         2274 {
         2275   int ibyte, ichar, x;
         2276   gunichar c;
         2277   gboolean DecimalPoint, Suffix;
         2278   GString *text;
         2279   gchar *ReturnString;
         2280 
         2281   DecimalPoint = Suffix = FALSE;
         2282 
         2283   x = sx;
         2284   move(sy, x);
         2285   if (prompt) {
         2286     attrset(PromptAttr);
         2287     addstr(prompt);
         2288     x += strcharlen(prompt);
         2289   }
         2290   attrset(TextAttr);
         2291   if (displaystr) {
         2292     if (passwdchar) {
         2293       int i;
         2294       for (i = strcharlen(displaystr); i; i--)
         2295         addch((guint)passwdchar);
         2296     } else {
         2297       addstr(displaystr);
         2298     }
         2299     ibyte = strlen(displaystr);
         2300     ichar = strcharlen(displaystr);
         2301     text = g_string_new(displaystr);
         2302   } else {
         2303     ibyte = ichar = 0;
         2304     text = g_string_new("");
         2305   }
         2306 
         2307   curs_set(1);
         2308   do {
         2309     move(sy + (x + ichar) / Width, (x + ichar) % Width);
         2310     c = bgetch();
         2311     if ((c == 8 || c == KEY_BACKSPACE || c == 127) && ichar > 0) {
         2312       move(sy + (x + ichar - 1) / Width, (x + ichar - 1) % Width);
         2313       addch(' ');
         2314       ichar--;
         2315       if (LocaleIsUTF8) {
         2316         char *p = g_utf8_find_prev_char(text->str, text->str+ibyte);
         2317         assert(p);
         2318         ibyte = p - text->str;
         2319       } else {
         2320         ibyte--;
         2321       }
         2322       if (DecimalPoint && text->str[ibyte] == '.')
         2323         DecimalPoint = FALSE;
         2324       if (Suffix)
         2325         Suffix = FALSE;
         2326       g_string_truncate(text, ibyte);
         2327     } else if (!Suffix) {
         2328       if ((digitsonly && c >= '0' && c <= '9') ||
         2329           (!digitsonly && c >= 32 && c != '^' && c != 127)) {
         2330         if (LocaleIsUTF8) {
         2331           char utf8buf[20];
         2332           gint utf8len = g_unichar_to_utf8(c, utf8buf);
         2333           utf8buf[utf8len] = '\0';
         2334           ibyte += utf8len;
         2335           g_string_append(text, utf8buf);
         2336           if (passwdchar) {
         2337             addch((guint)passwdchar);
         2338           } else {
         2339             addstr(utf8buf);
         2340           }
         2341         } else {
         2342           g_string_append_c(text, c);
         2343           ibyte++;
         2344           addch((guint)passwdchar ? passwdchar : c);
         2345         }
         2346         ichar++;
         2347       } else if (digitsonly && (c == '.' || c == ',') && !DecimalPoint) {
         2348         g_string_append_c(text, '.');
         2349         ibyte++;
         2350         ichar++;
         2351         addch((guint)passwdchar ? passwdchar : c);
         2352         DecimalPoint = TRUE;
         2353       } else if (digitsonly
         2354                  && (c == 'M' || c == 'm' || c == 'k' || c == 'K')
         2355                  && !Suffix) {
         2356         g_string_append_c(text, c);
         2357         ibyte++;
         2358         ichar++;
         2359         addch((guint)passwdchar ? passwdchar : c);
         2360         Suffix = TRUE;
         2361       }
         2362     }
         2363   } while (c != '\n' && c != KEY_ENTER);
         2364   curs_set(0);
         2365   move(sy, x);
         2366   ReturnString = text->str;
         2367   g_string_free(text, FALSE);   /* Leave the buffer to return */
         2368   return ReturnString;
         2369 }
         2370 
         2371 static void DisplayDrugsHere(Player *Play)
         2372 {
         2373   int NumDrugsHere, i, c;
         2374   gchar *text;
         2375   GSList *names = NULL;
         2376 
         2377   attrset(TextAttr);
         2378   NumDrugsHere = 0;
         2379   for (i = 0; i < NumDrug; i++) {
         2380     if (Play->Drugs[i].Price > 0) {
         2381       NumDrugsHere++;
         2382     }
         2383   }
         2384   clear_bottom();
         2385   /* Display of drug prices (%tde="drugs" by default) */
         2386   text = dpg_strdup_printf(_("Hey doctor, the prices of %tde here are:"),
         2387                            Names.Drugs);
         2388   mvaddstr(get_ui_area_top(), 1, text);
         2389   g_free(text);
         2390   for (c = 0, i = GetNextDrugIndex(-1, Play);
         2391        c < NumDrugsHere && i != -1;
         2392        c++, i = GetNextDrugIndex(i, Play)) {
         2393     char *price = FormatPrice(Play->Drugs[i].Price);
         2394     GString *str = g_string_new(NULL);
         2395     /* List of individual drug names for selection (%tde="Opium" etc.
         2396        by default) */
         2397     dpg_string_printf(str, _("%/Drug Select/%c. %tde"), 'A' + c, Drug[i].Name);
         2398     g_string_pad_or_truncate_to_charlen(str, 22 - strcharlen(price));
         2399     g_string_append(str, price);
         2400     g_free(price);
         2401     names = g_slist_append(names, g_string_free(str, FALSE));
         2402   }
         2403   display_select_list(names);
         2404   attrset(PromptAttr);
         2405 }
         2406 
         2407 /* 
         2408  * Loop which handles the user playing an interactive game (i.e. "Play"
         2409  * is a client connected to a server, either locally or remotely)
         2410  * dopewars is essentially server-driven, so this loop simply has to
         2411  * make the screen look pretty, respond to user keypresses, and react
         2412  * to messages from the server.
         2413  */
         2414 static void Curses_DoGame(Player *Play)
         2415 {
         2416   gchar *buf, *OldName, *TalkMsg;
         2417   GString *text;
         2418   int i, c;
         2419   char IsCarrying;
         2420 
         2421 #if defined(NETWORKING) || defined(HAVE_SELECT)
         2422   fd_set readfs;
         2423 #endif
         2424 #ifdef NETWORKING
         2425   fd_set writefs;
         2426   gboolean DoneOK;
         2427   gchar *pt;
         2428   gboolean justconnected = FALSE;
         2429 #endif
         2430   int MaxSock;
         2431   char HaveWorthless;
         2432   Player *tmp;
         2433   struct sigaction sact;
         2434 
         2435   DisplayMode = DM_NONE;
         2436   QuitRequest = FALSE;
         2437 
         2438   ResizedFlag = 0;
         2439   sact.sa_handler = ResizeHandle;
         2440   sact.sa_flags = 0;
         2441   sigemptyset(&sact.sa_mask);
         2442   if (sigaction(SIGWINCH, &sact, NULL) == -1) {
         2443     g_warning(_("Cannot install SIGWINCH interrupt handler!"));
         2444   }
         2445   OldName = g_strdup(GetPlayerName(Play));
         2446   attrset(TextAttr);
         2447   clear_screen();
         2448   display_message(NULL);
         2449   DisplayFightMessage(Play, NULL);
         2450   print_status(Play, TRUE);
         2451 
         2452   attrset(TextAttr);
         2453   clear_bottom();
         2454   buf = NULL;
         2455   if (PlayerName && PlayerName[0]) {
         2456     buf = g_strdup(PlayerName);
         2457   } else {
         2458     do {
         2459       g_free(buf);
         2460       buf = nice_input(_("Hey doctor, what's your name? "),
         2461                        get_ui_area_top() + 1, 1, FALSE, OldName, '\0');
         2462     } while (buf[0] == 0);
         2463   }
         2464 #ifdef NETWORKING
         2465   if (WantNetwork) {
         2466     if (!ConnectToServer(Play)) {
         2467       end_curses();
         2468       exit(1);
         2469     }
         2470     justconnected = TRUE;
         2471   }
         2472 #endif /* NETWORKING */
         2473   print_status(Play, TRUE);
         2474   display_message("");
         2475 
         2476   InitAbilities(Play);
         2477   SendAbilities(Play);
         2478   StripTerminators(buf);
         2479   SetPlayerName(Play, buf);
         2480   SendNullClientMessage(Play, C_NONE, C_NAME, NULL, buf);
         2481   g_free(buf);
         2482   g_free(OldName);
         2483   SoundPlay(Sounds.StartGame);
         2484 
         2485   text = g_string_new("");
         2486 
         2487   while (1) {
         2488     if (Play->Health == 0)
         2489       DisplayMode = DM_NONE;
         2490     HaveWorthless = 0;
         2491     IsCarrying = 0;
         2492     for (i = 0; i < NumDrug; i++) {
         2493       if (Play->Drugs[i].Carried > 0) {
         2494         IsCarrying = 1;
         2495         if (Play->Drugs[i].Price == 0) {
         2496           HaveWorthless = 1;
         2497         }
         2498       }
         2499     }
         2500     switch (DisplayMode) {
         2501     case DM_STREET:
         2502       DisplayDrugsHere(Play);
         2503       /* Prompts for "normal" actions in curses client */
         2504       g_string_assign(text, _("Will you B>uy"));
         2505       if (IsCarrying)
         2506         g_string_append(text, _(", S>ell"));
         2507       if (HaveWorthless && !WantAntique)
         2508         g_string_append(text, _(", D>rop"));
         2509       if (Network)
         2510         g_string_append(text, _(", T>alk, P>age"));
         2511       g_string_append(text, _(", L>ist"));
         2512       if (!WantAntique && (Play->Bitches.Carried > 0 ||
         2513                            Play->Flags & SPYINGON)) {
         2514         g_string_append(text, _(", G>ive"));
         2515       }
         2516       if (Play->Flags & FIGHTING) {
         2517         g_string_append(text, _(", F>ight"));
         2518       } else {
         2519         g_string_append(text, _(", J>et"));
         2520       }
         2521       g_string_append(text, _(", or Q>uit? "));
         2522       mvaddcentstr(get_prompt_line(), text->str);
         2523       attrset(TextAttr);
         2524       curs_set(1);
         2525       break;
         2526     case DM_FIGHT:
         2527       DisplayFightMessage(Play, "");
         2528       attrset(PromptAttr);
         2529       /* Prompts for actions during fights in curses client */
         2530       g_string_assign(text, _("Do you "));
         2531       if (CanFire) {
         2532         if (TotalGunsCarried(Play) > 0) {
         2533           g_string_append(text, _("F>ight, "));
         2534         } else {
         2535           g_string_append(text, _("S>tand, "));
         2536         }
         2537       }
         2538       if (fp != F_LASTLEAVE)
         2539         g_string_append(text, _("R>un, "));
         2540       if (!RunHere || fp == F_LASTLEAVE)
         2541         /* (%tde = "drugs" by default here) */
         2542         dpg_string_append_printf(text, _("D>eal %tde, "), Names.Drugs);
         2543       g_string_append(text, _("or Q>uit? "));
         2544       mvaddcentstr(get_prompt_line(), text->str);
         2545       attrset(TextAttr);
         2546       curs_set(1);
         2547       break;
         2548     case DM_DEAL:
         2549       attrset(TextAttr);
         2550       clear_bottom();
         2551       mvaddstr(16, 1, "Your trade:-");
         2552       mvaddstr(19, 1, "His trade:-");
         2553       g_string_assign(text, "Do you A>dd, R>emove, O>K, D>eal ");
         2554       g_string_append(text, Names.Drugs);
         2555       g_string_append(text, ", or Q>uit? ");
         2556       attrset(PromptAttr);
         2557       mvaddcentstr(get_prompt_line(), text->str);
         2558       attrset(TextAttr);
         2559       curs_set(1);
         2560       break;
         2561     case DM_NONE:
         2562       break;
         2563     }
         2564     refresh();
         2565 
         2566     if (QuitRequest)
         2567       return;
         2568 #ifdef NETWORKING
         2569     FD_ZERO(&readfs);
         2570     FD_ZERO(&writefs);
         2571     FD_SET(0, &readfs);
         2572     MaxSock = 1;
         2573     if (Client) {
         2574       if (justconnected) {
         2575         /* Deal with any messages that came in while we were connect()ing */
         2576         justconnected = FALSE;
         2577         while ((pt = GetWaitingPlayerMessage(Play)) != NULL) {
         2578           HandleClientMessage(pt, Play);
         2579           g_free(pt);
         2580         }
         2581         if (QuitRequest)
         2582           return;
         2583       }
         2584       SetSelectForNetworkBuffer(&Play->NetBuf, &readfs, &writefs,
         2585                                 NULL, &MaxSock);
         2586     }
         2587     if (bselect(MaxSock, &readfs, &writefs, NULL, NULL) == -1) {
         2588       if (errno == EINTR) {
         2589         CheckForResize(Play);
         2590         continue;
         2591       }
         2592       perror("bselect");
         2593       exit(1);
         2594     }
         2595     if (Client) {
         2596       if (RespondToSelect(&Play->NetBuf, &readfs, &writefs, NULL, &DoneOK)) {
         2597         while ((pt = GetWaitingPlayerMessage(Play)) != NULL) {
         2598           HandleClientMessage(pt, Play);
         2599           g_free(pt);
         2600         }
         2601         if (QuitRequest)
         2602           return;
         2603       }
         2604       if (!DoneOK) {
         2605         attrset(TextAttr);
         2606         clear_line(get_prompt_line());
         2607         mvaddstr(get_prompt_line(), 0,
         2608                  _("Connection to server lost! "
         2609                    "Reverting to single player mode"));
         2610         nice_wait();
         2611         SwitchToSinglePlayer(Play);
         2612         print_status(Play, TRUE);
         2613       }
         2614     }
         2615     if (FD_ISSET(0, &readfs)) {
         2616 #elif defined(HAVE_SELECT)
         2617     FD_ZERO(&readfs);
         2618     FD_SET(0, &readfs);
         2619     MaxSock = 1;
         2620     if (bselect(MaxSock, &readfs, NULL, NULL, NULL) == -1) {
         2621       if (errno == EINTR) {
         2622         CheckForResize(Play);
         2623         continue;
         2624       }
         2625       perror("bselect");
         2626       exit(1);
         2627     }
         2628 #endif /* NETWORKING */
         2629     if (DisplayMode == DM_STREET) {
         2630       /* N.B. You must keep the order of these keys the same as the
         2631          original when you translate (B>uy, S>ell, D>rop, T>alk, P>age,
         2632          L>ist, G>ive errand, F>ight, J>et, Q>uit) */
         2633       c = GetKey(N_("BSDTPLGFJQ"), TRUE, FALSE, FALSE);
         2634 
         2635     } else if (DisplayMode == DM_FIGHT) {
         2636       /* N.B. You must keep the order of these keys the same as the
         2637          original when you translate (D>eal drugs, R>un, F>ight, S>tand,
         2638          Q>uit) */
         2639       c = GetKey(N_("DRFSQ"), TRUE, FALSE, FALSE);
         2640 
         2641     } else
         2642       c = 0;
         2643 #if ! (defined(NETWORKING) || defined(HAVE_SELECT))
         2644     CheckForResize(Play);
         2645 #endif
         2646     if (DisplayMode == DM_STREET) {
         2647       if (c == 'J' && !(Play->Flags & FIGHTING)) {
         2648         jet(Play, TRUE);
         2649       } else if (c == 'F' && Play->Flags & FIGHTING) {
         2650         DisplayMode = DM_FIGHT;
         2651       } else if (c == 'T' && Play->Flags & TRADING) {
         2652         DisplayMode = DM_DEAL;
         2653       } else if (c == 'B') {
         2654         DealDrugs(Play, TRUE);
         2655       } else if (c == 'S' && IsCarrying) {
         2656         DealDrugs(Play, FALSE);
         2657       } else if (c == 'D' && HaveWorthless && !WantAntique) {
         2658         DropDrugs(Play);
         2659       } else if (c == 'G' && !WantAntique && Play->Bitches.Carried > 0) {
         2660         GiveErrand(Play);
         2661       } else if (c == 'Q') {
         2662         if (want_to_quit() == 1) {
         2663           DisplayMode = DM_NONE;
         2664           clear_bottom();
         2665           SendClientMessage(Play, C_NONE, C_WANTQUIT, NULL, NULL);
         2666         }
         2667       } else if (c == 'L') {
         2668         if (Network) {
         2669           attrset(PromptAttr);
         2670           mvaddstr(get_prompt_line() + 1, 20,
         2671                    _("List what? P>layers or S>cores? "));
         2672           /* P>layers, S>cores */
         2673           i = GetKey(N_("PS"), TRUE, FALSE, FALSE);
         2674           if (i == 'P') {
         2675             ListPlayers(Play, FALSE, NULL);
         2676           } else if (i == 'S') {
         2677             DisplayMode = DM_NONE;
         2678             SendClientMessage(Play, C_NONE, C_REQUESTSCORE, NULL, NULL);
         2679           }
         2680         } else {
         2681           DisplayMode = DM_NONE;
         2682           SendClientMessage(Play, C_NONE, C_REQUESTSCORE, NULL, NULL);
         2683         }
         2684       } else if (c == 'P' && Network) {
         2685         tmp = ListPlayers(Play, TRUE,
         2686                           _("Whom do you want to page "
         2687                             "(talk privately to) ? "));
         2688         if (tmp) {
         2689           attrset(TextAttr);
         2690           clear_line(get_prompt_line());
         2691           /* Prompt for sending player-player messages */
         2692           TalkMsg = nice_input(_("Talk: "), get_prompt_line(), 0, FALSE,
         2693                                NULL, '\0');
         2694           if (TalkMsg[0]) {
         2695             SendClientMessage(Play, C_NONE, C_MSGTO, tmp, TalkMsg);
         2696             buf = g_strdup_printf("%s->%s: %s", GetPlayerName(Play),
         2697                                   GetPlayerName(tmp), TalkMsg);
         2698             display_message(buf);
         2699             g_free(buf);
         2700           }
         2701           g_free(TalkMsg);
         2702         }
         2703       } else if (c == 'T' && Client) {
         2704         attrset(TextAttr);
         2705         clear_line(get_prompt_line());
         2706         TalkMsg = nice_input(_("Talk: "), get_prompt_line(), 0,
         2707                              FALSE, NULL, '\0');
         2708         if (TalkMsg[0]) {
         2709           SendClientMessage(Play, C_NONE, C_MSG, NULL, TalkMsg);
         2710           buf = g_strdup_printf("%s: %s", GetPlayerName(Play), TalkMsg);
         2711           display_message(buf);
         2712           g_free(buf);
         2713         }
         2714         g_free(TalkMsg);
         2715       }
         2716     } else if (DisplayMode == DM_FIGHT) {
         2717       switch (c) {
         2718       case 'D':
         2719         if (!RunHere || fp == F_LASTLEAVE) {
         2720           DisplayMode = DM_STREET;
         2721           if (!(Play->Flags & FIGHTING) && HaveAbility(Play, A_DONEFIGHT)) {
         2722             SendClientMessage(Play, C_NONE, C_DONE, NULL, NULL);
         2723           }
         2724         }
         2725         break;
         2726       case 'R':
         2727         if (RunHere) {
         2728           SendClientMessage(Play, C_NONE, C_FIGHTACT, NULL, "R");
         2729         } else {
         2730           jet(Play, TRUE);
         2731         }
         2732         break;
         2733       case 'F':
         2734         if (TotalGunsCarried(Play) > 0 && CanFire) {
         2735           buf = g_strdup_printf("%c", c);
         2736           Play->Flags &= ~CANSHOOT;
         2737           SendClientMessage(Play, C_NONE, C_FIGHTACT, NULL, buf);
         2738           g_free(buf);
         2739         }
         2740         break;
         2741       case 'S':
         2742         if (TotalGunsCarried(Play) == 0 && CanFire) {
         2743           buf = g_strdup_printf("%c", c);
         2744           Play->Flags &= ~CANSHOOT;
         2745           SendClientMessage(Play, C_NONE, C_FIGHTACT, NULL, buf);
         2746           g_free(buf);
         2747         }
         2748         break;
         2749       case 'Q':
         2750         if (want_to_quit() == 1) {
         2751           DisplayMode = DM_NONE;
         2752           clear_bottom();
         2753           SendClientMessage(Play, C_NONE, C_WANTQUIT, NULL, NULL);
         2754         }
         2755         break;
         2756       }
         2757     } else if (DisplayMode == DM_DEAL) {
         2758       switch (c) {
         2759       case 'D':
         2760         DisplayMode = DM_STREET;
         2761         break;
         2762       case 'Q':
         2763         if (want_to_quit() == 1) {
         2764           DisplayMode = DM_NONE;
         2765           clear_bottom();
         2766           SendClientMessage(Play, C_NONE, C_WANTQUIT, NULL, NULL);
         2767         }
         2768         break;
         2769       }
         2770     }
         2771 #ifdef NETWORKING
         2772     }
         2773 #endif
         2774     curs_set(0);
         2775   }
         2776   g_string_free(text, TRUE);
         2777 }
         2778 
         2779 void CursesLoop(struct CMDLINE *cmdline)
         2780 {
         2781   char c;
         2782   Player *Play;
         2783 
         2784 #ifdef CYGWIN
         2785   /* On Windows, force UTF-8 rather than the non-Unicode codepage */
         2786   bind_textdomain_codeset(PACKAGE, "UTF-8");
         2787   Conv_SetInternalCodeset("UTF-8");
         2788   LocaleIsUTF8 = TRUE;
         2789   WantUTF8Errors(TRUE);
         2790 #endif
         2791 
         2792   InitConfiguration(cmdline);
         2793   if (!CheckHighScoreFileConfig())
         2794     return;
         2795 
         2796   WantColor = cmdline->color;
         2797   WantNetwork = cmdline->network;
         2798 
         2799   /* Save the configuration, so we can restore those elements that get
         2800    * overwritten when we connect to a dopewars server */
         2801   BackupConfig();
         2802 
         2803   start_curses();
         2804 #ifdef NETWORKING
         2805   CurlInit(&MetaConn);
         2806 #endif
         2807   Width = COLS;
         2808   Depth = LINES;
         2809 
         2810   /* Set up message handlers */
         2811   ClientMessageHandlerPt = HandleClientMessage;
         2812 
         2813   /* Make the GLib log messages display nicely */
         2814   g_log_set_handler(NULL,
         2815                     G_LOG_LEVEL_MESSAGE | G_LOG_LEVEL_WARNING,
         2816                     LogMessage, NULL);
         2817 
         2818   SoundOpen(cmdline->plugin);
         2819   SoundEnable(UseSounds);
         2820 
         2821   display_intro();
         2822 
         2823   Play = g_new(Player, 1);
         2824   FirstClient = AddPlayer(0, Play, FirstClient);
         2825   do {
         2826     Curses_DoGame(Play);
         2827     SoundPlay(Sounds.EndGame);
         2828     ShutdownNetwork(Play);
         2829     CleanUpServer();
         2830     RestoreConfig();
         2831     attrset(TextAttr);
         2832     mvaddstr(get_prompt_line() + 1, 20, _("Play again? "));
         2833     c = GetKey(N_("YN"), TRUE, TRUE, FALSE);
         2834   } while (c == 'Y');
         2835   FirstClient = RemovePlayer(Play, FirstClient);
         2836   end_curses();
         2837 #ifdef NETWORKING
         2838   CurlCleanup(&MetaConn);
         2839 #endif
         2840 }