]> git.pond.sub.org Git - empserver/blob - src/lib/subs/pr.c
Document, in particular use of UTF-8. Simplify code in a couple of
[empserver] / src / lib / subs / pr.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2005, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program is free software; you can redistribute it and/or modify
7  *  it under the terms of the GNU General Public License as published by
8  *  the Free Software Foundation; either version 2 of the License, or
9  *  (at your option) any later version.
10  *
11  *  This program is distributed in the hope that it will be useful,
12  *  but WITHOUT ANY WARRANTY; without even the implied warranty of
13  *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  *  GNU General Public License for more details.
15  *
16  *  You should have received a copy of the GNU General Public License
17  *  along with this program; if not, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
23  *  related information and legal notices. It is expected that any future
24  *  projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  pr.c: Use to do output to a player
29  * 
30  *  Known contributors to this file:
31  *     Dave Pare, 1986, 1989 
32  *     Steve McClure, 1998-2000
33  */
34 /*
35  * The pr routine historically arranged for nonbuffered i/o
36  * because stdio didn't used to automatically flush stdout before
37  * it read something from stdin.  Now pr() prepends an "output id"
38  * in front of each line of text, informing the user interface
39  * what sort of item it is seeing; prompt, noecho prompt,
40  * more input data, etc.
41  */
42
43 #include <string.h>
44 #include <fcntl.h>
45 #include <ctype.h>
46 #include <stdarg.h>
47 #include "proto.h"
48 #include "misc.h"
49 #include "player.h"
50 #include "nat.h"
51 #include "empio.h"
52 #include "file.h"
53 #include "com.h"
54 #include "tel.h"
55 #include "server.h"
56 #include "prototypes.h"
57
58 static void outid(struct player *pl, int n);
59
60 /*
61  * Print to current player similar to printf().
62  * Use printf-style FORMAT with the optional arguments.
63  * Note: `to print' without further qualifications means sending
64  * C_DATA text.
65  */
66 void
67 pr(char *format, ...)
68 {
69     char buf[4096];
70     va_list ap;
71
72     va_start(ap, format);
73     (void)vsprintf(buf, format, ap);
74     va_end(ap);
75     if (player->flags & PF_UTF8)
76         /* normal text needs to be converted to user text */
77         upr_player(player, C_DATA, buf);
78     else
79         /* normal text and user text are identical */
80         pr_player(player, C_DATA, buf);
81 }
82
83 /*
84  * Print UTF-8 text BUF to current player.
85  */
86 void
87 uprnf(char *buf)
88 {
89     char *p;
90
91     if (!(player->flags & PF_UTF8)) {
92         p = malloc(strlen(buf) + 1);
93         copy_utf8_to_ascii_no_funny(p, buf);
94         pr_player(player, C_DATA, p);
95         free(p);
96     } else
97         pr_player(player, C_DATA, buf);
98 }
99
100 /*
101  * Send some text to P with id ID, line-buffered.
102  * Format text to send using printf-style FORMAT and optional
103  * arguments.  It is assumed to be already user text.  Plain ASCII and
104  * text received from the same player are fine, for anything else the
105  * caller has to deal with output filtering.
106  * If a partial line with different id is buffered, terminate it with
107  * a newline first.
108  */
109 void
110 pr_id(struct player *p, int id, char *format, ...)
111 {
112     char buf[4096];
113     va_list ap;
114
115     if (p->curid >= 0) {
116         io_puts(p->iop, "\n");
117         p->curid = -1;
118     }
119     va_start(ap, format);
120     (void)vsprintf(buf, format, ap);
121     va_end(ap);
122     pr_player(p, id, buf);
123 }
124
125 /*
126  * Send C_FLASH text to PL.
127  * Format text to send using printf-style FORMAT and optional
128  * arguments.  It is assumed to be UTF-8.
129  */
130 void
131 pr_flash(struct player *pl, char *format, ...)
132 {
133     char buf[4096];             /* UTF-8 */
134     va_list ap;
135
136     if (pl->state != PS_PLAYING)
137         return;
138     va_start(ap, format);
139     (void)vsprintf(buf, format, ap);
140     va_end(ap);
141     if (!(pl->flags & PF_UTF8))
142         copy_utf8_to_ascii_no_funny(buf, buf);
143     pr_player(pl, C_FLASH, buf);
144     io_output(pl->iop, IO_NOWAIT);
145 }
146
147 /*
148  * Send C_INFORM text to PL.
149  * Format text to send using printf-style FORMAT and optional
150  * arguments.  It is assumed to be plain ASCII.
151  */
152 void
153 pr_inform(struct player *pl, char *format, ...)
154 {
155     char buf[4096];
156     va_list ap;
157
158     if (pl->state != PS_PLAYING)
159         return;
160     va_start(ap, format);
161     (void)vsprintf(buf, format, ap);
162     va_end(ap);
163     pr_player(pl, C_INFORM, buf);
164     io_output(pl->iop, IO_NOWAIT);
165 }
166
167 /*
168  * Send C_FLASH text to everyone.
169  * Format text to send using printf-style FORMAT and optional
170  * arguments.  It is assumed to be plain ASCII.
171  */
172 void
173 pr_wall(char *format, ...)
174 {
175     char buf[4096];             /* UTF-8 */
176     struct player *p;
177     va_list ap;
178
179     va_start(ap, format);
180     (void)vsprintf(buf, format, ap);
181     va_end(ap);
182     for (p = player_next(0); p; p = player_next(p)) {
183         if (p->state != PS_PLAYING)
184             continue;
185         pr_player(p, C_FLASH, buf);
186         io_output(p->iop, IO_NOWAIT);
187     }
188 }
189
190 /*
191  * Send ID text BUF to PL, line-buffered.
192  * BUF is user text.
193  * If a partial line with different id is buffered, terminate it with
194  * a newline first.
195  */
196 void
197 pr_player(struct player *pl, int id, char *buf)
198 {
199     char *p;
200     char *bp;
201     int len;
202
203     bp = buf;
204     while (*bp != '\0') {
205         if (pl->curid != -1 && pl->curid != id) {
206             io_puts(pl->iop, "\n");
207             pl->curid = -1;
208         }
209         if (pl->curid == -1)
210             outid(pl, id);
211         p = strchr(bp, '\n');
212         if (p != NULL) {
213             len = (p - bp) + 1;
214             if (pl->command && (pl->command->c_flags & C_MOD))
215                 io_write(pl->iop, bp, len, IO_NOWAIT);
216             else
217                 io_write(pl->iop, bp, len, IO_WAIT);
218             bp += len;
219             pl->curid = -1;
220         } else {
221             len = io_puts(pl->iop, bp);
222             bp += len;
223         }
224     }
225 }
226
227 /*
228  * Send ID text BUF to PL, line-buffered.
229  * If a partial line with different id is buffered, terminate it with
230  * a newline first.
231  */
232 void
233 upr_player(struct player *pl, int id, char *buf)
234 {
235     char *bp;
236     int standout = 0;
237     char printbuf[2];
238     char ch;
239
240     printbuf[0] = '\0';
241     printbuf[1] = '\0';
242
243     bp = buf;
244     while ((ch = *bp++)) {
245         if (pl->curid != -1 && pl->curid != id) {
246             io_puts(pl->iop, "\n");
247             pl->curid = -1;
248         }
249         if (pl->curid == -1)
250             outid(pl, id);
251
252         if (ch & 0x80) {
253             if (standout == 0) {
254                 printbuf[0] = 0x0e;
255                 io_puts(pl->iop, printbuf);
256                 standout = 1;
257             }
258             ch &= 0x7f;
259         } else {
260             if (standout == 1) {
261                 printbuf[0] = 0x0f;
262                 io_puts(pl->iop, printbuf);
263                 standout = 0;
264             }
265         }
266         if (ch == '\n') {
267             if (pl->command && (pl->command->c_flags & C_MOD))
268                 io_write(pl->iop, &ch, 1, IO_NOWAIT);
269             else
270                 io_write(pl->iop, &ch, 1, IO_WAIT);
271             pl->curid = -1;
272         } else {
273             printbuf[0] = ch;
274             io_puts(pl->iop, printbuf);
275         }
276     }
277 }
278
279 /*
280  * highlighted characters have hex 80 or'ed in
281  * with them to designate their highlightedness
282  */
283 void
284 pr_hilite(s_char *buf)
285 {
286     register s_char *bp;
287     register s_char c;
288     s_char *p;
289
290     p = malloc(strlen(buf) + 1);
291     strcpy(p, buf);
292     for (bp = p; 0 != (c = *bp); bp++)
293         if (isprint(c))
294             *bp |= 0x80;
295     pr(p);
296     free(p);
297 }
298
299 /*
300  * Send id N to PL.
301  * This runs always at the beginning of a line.
302  */
303 static void
304 outid(struct player *pl, int n)
305 {
306     char buf[3];
307
308     if (CANT_HAPPEN(n > C_LAST))
309         n = C_DATA;
310
311     if (n >= 10)
312         buf[0] = 'a' - 10 + n;
313     else
314         buf[0] = '0' + n;
315     buf[1] = ' ';
316     buf[2] = '\0';
317     io_puts(pl->iop, buf);
318     pl->curid = n;
319 }
320
321 /*
322  * Send redirection request REDIR to the current player.
323  * REDIR is UTF-8, but non-ASCII characters can occur only if the
324  * player sent them.  Therefore, it is also user text.
325  */
326 void
327 prredir(char *redir)
328 {
329     pr_id(player, *redir == '>' ? C_REDIR : C_PIPE, "%s\n", redir);
330 }
331
332 /*
333  * Send script execute request FILE to the current player.
334  * REDIR is UTF-8, but non-ASCII characters can occur only if the
335  * player sent them.  Therefore, it is also user text.
336  */
337 void
338 prexec(char *file)
339 {
340     pr_id(player, C_EXECUTE, "%s\n", file);
341 }
342
343 /*
344  * Send a command prompt to the current player.
345  */
346 void
347 prprompt(int min, int btu)
348 {
349     pr_id(player, C_PROMPT, "%d %d\n", min, btu);
350 }
351
352 /*
353  * Prompt for a line of non-command input.
354  * Send C_FLUSH prompt PROMPT to the current player.
355  * Read a line of input into BUF[SIZE] and convert it to ASCII.
356  * Return number of bytes in BUF[], not counting the terminating 0,
357  * or -1 on error.
358  */
359 int
360 prmptrd(char *prompt, char *buf, int size)
361 {
362     int r;
363     char *cp;
364
365     pr_id(player, C_FLUSH, "%s\n", prompt);
366     if ((r = recvclient(buf, size)) < 0)
367         return r;
368     time(&player->curup);
369     if (*buf == 0)
370         return 1;
371     if (player->flags & PF_UTF8)
372         return copy_utf8_to_ascii_no_funny(buf, buf);
373     return copy_ascii_no_funny(buf, buf);
374 }
375
376 /*
377  * Prompt for a line of non-command, UTF-8 input.
378  * Send C_FLUSH prompt PROMPT to the current player.
379  * Read a line of input into BUF[SIZE], replacing funny characters by
380  * '?'.  The result is UTF-8.
381  * Return number of bytes in BUF[], not counting the terminating 0,
382  * or -1 on error.
383  */
384 int
385 uprmptrd(char *prompt, char *buf, int size)
386 {
387     int r;
388     char *cp;
389
390     pr_id(player, C_FLUSH, "%s\n", prompt);
391     if ((r = recvclient(buf, size)) < 0)
392         return r;
393     time(&player->curup);
394     if (*buf == 0)
395         return 1;
396     if (player->flags & PF_UTF8)
397         return copy_utf8_no_funny(buf, buf);
398     return copy_ascii_no_funny(buf, buf);
399 }
400
401 /*
402  * Print the current time in ctime() format.
403  */
404 void
405 prdate(void)
406 {
407     time_t now;
408
409     (void)time(&now);
410     pr(ctime(&now));
411 }
412
413 /*
414  * Print coordinates X, Y for COUNTRY.
415  * FORMAT must be a printf-style format string that converts exactly
416  * two int values.
417  */
418 void
419 prxy(char *format, coord x, coord y, natid country)
420 {
421     char buf[255];
422     struct natstr *np;
423
424     np = getnatp(country);
425     sprintf(buf, format, xrel(np, x), yrel(np, y));
426     pr(buf);
427 }
428
429 /*
430  * Print to country CN similar to printf().
431  * Use printf-style FORMAT with the optional arguments.
432  * Output is buffered until a newline arrives.
433  * If CN is the current player, print just like pr().
434  * Else print into a bulletin.
435  * Because printing like pr() requires normal text, and bulletins
436  * require user text, only plain ASCII is allowed.
437  */
438 void
439 PR(int cn, char *format, ...)
440 {
441     /* XXX should really do this on a per-nation basis */
442     static char longline[MAXNOC][512];
443     int newline;
444     va_list ap;
445     char buf[1024];
446
447     va_start(ap, format);
448     (void)vsprintf(buf, format, ap);
449     va_end(ap);
450     newline = strrchr(buf, '\n') ? 1 : 0;
451     strcat(longline[cn], buf);
452     if (newline) {
453         if (update_pending || (cn && cn != player->cnum))
454             typed_wu(0, cn, longline[cn], TEL_BULLETIN);
455         else
456             pr_player(player, C_DATA, longline[cn]);
457         longline[cn][0] = '\0';
458     }
459 }
460
461 /*
462  * Print the current time in ctime() format to country CN.
463  * If CN is the current player, print like prdate().
464  * Else print into a bulletin.
465  */
466 void
467 PRdate(natid cn)
468 {
469     time_t now;
470
471     (void)time(&now);
472     PR(cn, ctime(&now));
473 }
474
475 /*
476  * Sound the current player's bell.
477  */
478 void
479 pr_beep(void)
480 {
481     struct natstr *np = getnatp(player->cnum);
482
483     if (np->nat_flags & NF_BEEP)
484         pr("\07");
485 }
486
487 /*
488  * Print to country CN similar to printf().
489  * Use printf-style FORMAT with the optional arguments.
490  * If CN is the current player, print just like pr().
491  * Else print into a bulletin.
492  * Because printing like pr() requires normal text, and bulletins
493  * require user text, only plain ASCII is allowed.
494  */
495 void
496 mpr(int cn, char *format, ...)
497 {
498     char buf[4096];
499     va_list ap;
500
501     va_start(ap, format);
502     (void)vsprintf(buf, format, ap);
503     va_end(ap);
504     if (cn) {
505         if (update_pending || cn != player->cnum)
506             typed_wu(0, cn, buf, TEL_BULLETIN);
507         else
508             pr_player(player, C_DATA, buf);
509     }
510 }
511
512 /*
513  * Copy SRC without funny characters to DST.
514  * Drop control characters, except for '\t'.
515  * Replace non-ASCII characters by '?'.
516  * Return length of DST.
517  * DST must have space.  If it overlaps SRC, then DST <= SRC must
518  * hold.
519  */
520 size_t
521 copy_ascii_no_funny(char *dst, char *src)
522 {
523     char *p;
524     unsigned char ch;
525
526     p = dst;
527     while ((ch = *src++)) {
528         if ((ch < 0x20 && ch != '\t') || ch == 0x7f)
529             ;                   /* ignore control */
530         else if (ch > 0x7f)
531             *p++ = '?'; /* replace non-ASCII */
532         else
533             *p++ = ch;
534     }
535     *p = 0;
536
537     return p - dst;
538 }
539
540 /*
541  * Copy UTF-8 SRC without funny characters to DST.
542  * Drop control characters, except for '\t'.
543  * FIXME Replace malformed UTF-8 sequences by '?'.
544  * Return byte length of DST.
545  * DST must have space.  If it overlaps SRC, then DST <= SRC must
546  * hold.
547  */
548 size_t
549 copy_utf8_no_funny(char *dst, char *src)
550 {
551     char *p;
552     unsigned char ch;
553
554     p = dst;
555     while ((ch = *src++)) {
556         /* FIXME do the right thing for malformed and overlong sequences */
557         if ((ch < 0x20 && ch != '\t') || ch == 0x7f)
558             ;                   /* ignore control */
559         else
560             *p++ = ch;
561     }
562     *p = 0;
563
564     return p - dst;
565 }
566
567 /*
568  * Copy UTF-8 SRC without funny characters to ASCII DST.
569  * Drop control characters, except for '\t'.
570  * Replace non-ASCII characters by '?'.
571  * Return length of DST.
572  * DST must have space.  If it overlaps SRC, then DST <= SRC must
573  * hold.
574  */
575 size_t
576 copy_utf8_to_ascii_no_funny(char *dst, char *src)
577 {
578     char *p;
579     unsigned char ch;
580
581     p = dst;
582     while ((ch = *src++)) {
583         /* FIXME do the right thing for malformed and overlong sequences */
584         if ((ch < 0x20 && ch != '\t') || ch == 0x7f)
585             ;                   /* ignore control */
586         else if (ch > 0x7f) {
587             *p++ = '?';         /* replace non-ASCII */
588             while ((*src++ & 0xc0) == 0x80) ;
589         } else
590             *p++ = ch;
591     }
592     *p = 0;
593
594     return p - dst;
595 }
596
597 /*
598  * Return byte-index of the N-th UTF-8 character in UTF-8 string S.
599  * If S doesn't have that many characters, return its length instead.
600  */
601 int
602 ufindpfx(char *s, int n)
603 {
604     int i = 0;
605
606     while (n && s[i])
607     {
608         if ((s[i++] & 0xc0) == 0xc0)
609             while ((s[i] & 0xc0) == 0x80)
610                 i++;
611         --n;
612     }
613     return i;
614 }