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 }