]> git.pond.sub.org Git - empserver/blob - src/lib/subs/pr.c
(copy_ascii_no_funny, copy_utf8_no_funny)
[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 pr_player(struct player *pl, int id, char *buf);
59 static void upr_player(struct player *pl, int id, char *buf);
60 static void outid(struct player *pl, int n);
61
62 /*
63  * Print to current player similar to printf().
64  * Use printf-style FORMAT with the optional arguments.
65  * Note: `to print' without further qualifications means sending
66  * C_DATA text.
67  */
68 void
69 pr(char *format, ...)
70 {
71     char buf[4096];
72     va_list ap;
73
74     va_start(ap, format);
75     (void)vsprintf(buf, format, ap);
76     va_end(ap);
77     if (player->flags & PF_UTF8)
78         /* normal text needs to be converted to user text */
79         upr_player(player, C_DATA, buf);
80     else
81         /* normal text and user text are identical */
82         pr_player(player, C_DATA, buf);
83 }
84
85 /*
86  * Print UTF-8 text BUF to current player.
87  */
88 void
89 uprnf(char *buf)
90 {
91     char *p;
92
93     if (!(player->flags & PF_UTF8)) {
94         p = malloc(strlen(buf) + 1);
95         copy_utf8_to_ascii_no_funny(p, buf);
96         pr_player(player, C_DATA, p);
97         free(p);
98     } else
99         pr_player(player, C_DATA, buf);
100 }
101
102 /*
103  * Send some text to P with id ID, line-buffered.
104  * Format text to send using printf-style FORMAT and optional
105  * arguments.  It is assumed to be already user text.  Plain ASCII and
106  * text received from the same player are fine, for anything else the
107  * caller has to deal with output filtering.
108  * If a partial line with different id is buffered, terminate it with
109  * a newline first.
110  */
111 void
112 pr_id(struct player *p, int id, char *format, ...)
113 {
114     char buf[4096];
115     va_list ap;
116
117     if (p->curid >= 0) {
118         io_puts(p->iop, "\n");
119         p->curid = -1;
120     }
121     va_start(ap, format);
122     (void)vsprintf(buf, format, ap);
123     va_end(ap);
124     pr_player(p, id, buf);
125 }
126
127 /*
128  * Send C_FLASH text to PL.
129  * Format text to send using printf-style FORMAT and optional
130  * arguments.  It is assumed to be UTF-8.
131  */
132 void
133 pr_flash(struct player *pl, char *format, ...)
134 {
135     char buf[4096];             /* UTF-8 */
136     va_list ap;
137
138     if (pl->state != PS_PLAYING)
139         return;
140     va_start(ap, format);
141     (void)vsprintf(buf, format, ap);
142     va_end(ap);
143     if (!(pl->flags & PF_UTF8))
144         copy_utf8_to_ascii_no_funny(buf, buf);
145     pr_player(pl, C_FLASH, buf);
146     io_output(pl->iop, IO_NOWAIT);
147 }
148
149 /*
150  * Send C_INFORM text to PL.
151  * Format text to send using printf-style FORMAT and optional
152  * arguments.  It is assumed to be plain ASCII.
153  */
154 void
155 pr_inform(struct player *pl, char *format, ...)
156 {
157     char buf[4096];
158     va_list ap;
159
160     if (pl->state != PS_PLAYING)
161         return;
162     va_start(ap, format);
163     (void)vsprintf(buf, format, ap);
164     va_end(ap);
165     pr_player(pl, C_INFORM, buf);
166     io_output(pl->iop, IO_NOWAIT);
167 }
168
169 /*
170  * Send C_FLASH text to everyone.
171  * Format text to send using printf-style FORMAT and optional
172  * arguments.  It is assumed to be plain ASCII.
173  */
174 void
175 pr_wall(char *format, ...)
176 {
177     char buf[4096];             /* UTF-8 */
178     struct player *p;
179     va_list ap;
180
181     va_start(ap, format);
182     (void)vsprintf(buf, format, ap);
183     va_end(ap);
184     for (p = player_next(0); p; p = player_next(p)) {
185         if (p->state != PS_PLAYING)
186             continue;
187         pr_player(p, C_FLASH, buf);
188         io_output(p->iop, IO_NOWAIT);
189     }
190 }
191
192 /*
193  * Send ID text BUF to PL, line-buffered.
194  * BUF is user text.
195  * If a partial line with different id is buffered, terminate it with
196  * a newline first.
197  */
198 static void
199 pr_player(struct player *pl, int id, char *buf)
200 {
201     char *p;
202     char *bp;
203     int len;
204
205     bp = buf;
206     while (*bp != '\0') {
207         if (pl->curid != -1 && pl->curid != id) {
208             io_puts(pl->iop, "\n");
209             pl->curid = -1;
210         }
211         if (pl->curid == -1)
212             outid(pl, id);
213         p = strchr(bp, '\n');
214         if (p != NULL) {
215             len = (p - bp) + 1;
216             if (pl->command && (pl->command->c_flags & C_MOD))
217                 io_write(pl->iop, bp, len, IO_NOWAIT);
218             else
219                 io_write(pl->iop, bp, len, IO_WAIT);
220             bp += len;
221             pl->curid = -1;
222         } else {
223             len = io_puts(pl->iop, bp);
224             bp += len;
225         }
226     }
227 }
228
229 /*
230  * Send ID text BUF to PL, line-buffered.
231  * If a partial line with different id is buffered, terminate it with
232  * a newline first.
233  */
234 static void
235 upr_player(struct player *pl, int id, char *buf)
236 {
237     char *bp;
238     int standout = 0;
239     char printbuf[2];
240     char ch;
241
242     printbuf[0] = '\0';
243     printbuf[1] = '\0';
244
245     bp = buf;
246     while ((ch = *bp++)) {
247         if (pl->curid != -1 && pl->curid != id) {
248             io_puts(pl->iop, "\n");
249             pl->curid = -1;
250         }
251         if (pl->curid == -1)
252             outid(pl, id);
253
254         if (ch & 0x80) {
255             if (standout == 0) {
256                 printbuf[0] = 0x0e;
257                 io_puts(pl->iop, printbuf);
258                 standout = 1;
259             }
260             ch &= 0x7f;
261         } else {
262             if (standout == 1) {
263                 printbuf[0] = 0x0f;
264                 io_puts(pl->iop, printbuf);
265                 standout = 0;
266             }
267         }
268         if (ch == '\n') {
269             if (pl->command && (pl->command->c_flags & C_MOD))
270                 io_write(pl->iop, &ch, 1, IO_NOWAIT);
271             else
272                 io_write(pl->iop, &ch, 1, IO_WAIT);
273             pl->curid = -1;
274         } else {
275             printbuf[0] = ch;
276             io_puts(pl->iop, printbuf);
277         }
278     }
279 }
280
281 /*
282  * Send id N to PL.
283  * This runs always at the beginning of a line.
284  */
285 static void
286 outid(struct player *pl, int n)
287 {
288     char buf[3];
289
290     if (CANT_HAPPEN(n > C_LAST))
291         n = C_DATA;
292
293     if (n >= 10)
294         buf[0] = 'a' - 10 + n;
295     else
296         buf[0] = '0' + n;
297     buf[1] = ' ';
298     buf[2] = '\0';
299     io_puts(pl->iop, buf);
300     pl->curid = n;
301 }
302
303 /*
304  * Send redirection request REDIR to the current player.
305  * REDIR is UTF-8, but non-ASCII characters can occur only if the
306  * player sent them.  Therefore, it is also user text.
307  */
308 void
309 prredir(char *redir)
310 {
311     pr_id(player, *redir == '>' ? C_REDIR : C_PIPE, "%s\n", redir);
312 }
313
314 /*
315  * Send script execute request FILE to the current player.
316  * REDIR is UTF-8, but non-ASCII characters can occur only if the
317  * player sent them.  Therefore, it is also user text.
318  */
319 void
320 prexec(char *file)
321 {
322     pr_id(player, C_EXECUTE, "%s\n", file);
323 }
324
325 /*
326  * Send a command prompt to the current player.
327  */
328 void
329 prprompt(int min, int btu)
330 {
331     pr_id(player, C_PROMPT, "%d %d\n", min, btu);
332 }
333
334 /*
335  * Prompt for a line of non-command input.
336  * Send C_FLUSH prompt PROMPT to the current player.
337  * Read a line of input into BUF[SIZE] and convert it to ASCII.
338  * Return number of bytes in BUF[], not counting the terminating 0,
339  * or -1 on error.
340  */
341 int
342 prmptrd(char *prompt, char *buf, int size)
343 {
344     int r;
345     char *cp;
346
347     pr_id(player, C_FLUSH, "%s\n", prompt);
348     if ((r = recvclient(buf, size)) < 0)
349         return r;
350     time(&player->curup);
351     if (*buf == 0)
352         return 1;
353     if (player->flags & PF_UTF8)
354         return copy_utf8_to_ascii_no_funny(buf, buf);
355     return copy_ascii_no_funny(buf, buf);
356 }
357
358 /*
359  * Prompt for a line of non-command, UTF-8 input.
360  * Send C_FLUSH prompt PROMPT to the current player.
361  * Read a line of input into BUF[SIZE], replacing funny characters by
362  * '?'.  The result is UTF-8.
363  * Return number of bytes in BUF[], not counting the terminating 0,
364  * or -1 on error.
365  */
366 int
367 uprmptrd(char *prompt, char *buf, int size)
368 {
369     int r;
370     char *cp;
371
372     pr_id(player, C_FLUSH, "%s\n", prompt);
373     if ((r = recvclient(buf, size)) < 0)
374         return r;
375     time(&player->curup);
376     if (*buf == 0)
377         return 1;
378     if (player->flags & PF_UTF8)
379         return copy_utf8_no_funny(buf, buf);
380     return copy_ascii_no_funny(buf, buf);
381 }
382
383 /*
384  * Print the current time in ctime() format.
385  */
386 void
387 prdate(void)
388 {
389     time_t now;
390
391     (void)time(&now);
392     pr(ctime(&now));
393 }
394
395 /*
396  * Print coordinates X, Y for COUNTRY.
397  * FORMAT must be a printf-style format string that converts exactly
398  * two int values.
399  */
400 void
401 prxy(char *format, coord x, coord y, natid country)
402 {
403     char buf[255];
404     struct natstr *np;
405
406     np = getnatp(country);
407     sprintf(buf, format, xrel(np, x), yrel(np, y));
408     pr(buf);
409 }
410
411 /*
412  * Print to country CN similar to printf().
413  * Use printf-style FORMAT with the optional arguments.
414  * Output is buffered until a newline arrives.
415  * If CN is the current player, print just like pr().
416  * Else print into a bulletin.
417  * Because printing like pr() requires normal text, and bulletins
418  * require user text, only plain ASCII is allowed.
419  */
420 void
421 PR(int cn, char *format, ...)
422 {
423     /* XXX should really do this on a per-nation basis */
424     static char longline[MAXNOC][512];
425     int newline;
426     va_list ap;
427     char buf[1024];
428
429     va_start(ap, format);
430     (void)vsprintf(buf, format, ap);
431     va_end(ap);
432     newline = strrchr(buf, '\n') ? 1 : 0;
433     strcat(longline[cn], buf);
434     if (newline) {
435         if (update_pending || (cn && cn != player->cnum))
436             typed_wu(0, cn, longline[cn], TEL_BULLETIN);
437         else
438             pr_player(player, C_DATA, longline[cn]);
439         longline[cn][0] = '\0';
440     }
441 }
442
443 /*
444  * Print the current time in ctime() format to country CN.
445  * If CN is the current player, print like prdate().
446  * Else print into a bulletin.
447  */
448 void
449 PRdate(natid cn)
450 {
451     time_t now;
452
453     (void)time(&now);
454     PR(cn, ctime(&now));
455 }
456
457 /*
458  * Sound the current player's bell.
459  */
460 void
461 pr_beep(void)
462 {
463     struct natstr *np = getnatp(player->cnum);
464
465     if (np->nat_flags & NF_BEEP)
466         pr("\07");
467 }
468
469 /*
470  * Print to country CN similar to printf().
471  * Use printf-style FORMAT with the optional arguments.
472  * If CN is the current player, print just like pr().
473  * Else print into a bulletin.
474  * Because printing like pr() requires normal text, and bulletins
475  * require user text, only plain ASCII is allowed.
476  */
477 void
478 mpr(int cn, char *format, ...)
479 {
480     char buf[4096];
481     va_list ap;
482
483     va_start(ap, format);
484     (void)vsprintf(buf, format, ap);
485     va_end(ap);
486     if (cn) {
487         if (update_pending || cn != player->cnum)
488             typed_wu(0, cn, buf, TEL_BULLETIN);
489         else
490             pr_player(player, C_DATA, buf);
491     }
492 }
493
494 /*
495  * Copy SRC without funny characters to DST.
496  * Drop control characters, except for '\t'.
497  * Replace non-ASCII characters by '?'.
498  * Return length of DST.
499  * DST must have space.  If it overlaps SRC, then DST <= SRC must
500  * hold.
501  */
502 size_t
503 copy_ascii_no_funny(char *dst, char *src)
504 {
505     char *p;
506     unsigned char ch;
507
508     p = dst;
509     while ((ch = *src++)) {
510         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
511             ;                   /* ignore funny control */
512         else if (ch > 0x7f)
513             *p++ = '?'; /* replace non-ASCII */
514         else
515             *p++ = ch;
516     }
517     *p = 0;
518
519     return p - dst;
520 }
521
522 /*
523  * Copy UTF-8 SRC without funny characters to DST.
524  * Drop control characters, except for '\t'.
525  * FIXME Replace malformed UTF-8 sequences by '?'.
526  * Return byte length of DST.
527  * DST must have space.  If it overlaps SRC, then DST <= SRC must
528  * hold.
529  */
530 size_t
531 copy_utf8_no_funny(char *dst, char *src)
532 {
533     char *p;
534     unsigned char ch;
535
536     p = dst;
537     while ((ch = *src++)) {
538         /* FIXME do the right thing for malformed and overlong sequences */
539         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
540             ;                   /* ignore funny control */
541         else
542             *p++ = ch;
543     }
544     *p = 0;
545
546     return p - dst;
547 }
548
549 /*
550  * Copy UTF-8 SRC without funny characters to ASCII DST.
551  * Drop control characters, except for '\t'.
552  * Replace non-ASCII characters by '?'.
553  * Return length of DST.
554  * DST must have space.  If it overlaps SRC, then DST <= SRC must
555  * hold.
556  */
557 size_t
558 copy_utf8_to_ascii_no_funny(char *dst, char *src)
559 {
560     char *p;
561     unsigned char ch;
562
563     p = dst;
564     while ((ch = *src++)) {
565         /* FIXME do the right thing for malformed and overlong sequences */
566         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
567             ;                   /* ignore funny control */
568         else if (ch > 0x7f) {
569             *p++ = '?';         /* replace non-ASCII */
570             while ((*src++ & 0xc0) == 0x80) ;
571         } else
572             *p++ = ch;
573     }
574     *p = 0;
575
576     return p - dst;
577 }
578
579 /*
580  * Return byte-index of the N-th UTF-8 character in UTF-8 string S.
581  * If S doesn't have that many characters, return its length instead.
582  */
583 int
584 ufindpfx(char *s, int n)
585 {
586     int i = 0;
587
588     while (n && s[i])
589     {
590         if ((s[i++] & 0xc0) == 0xc0)
591             while ((s[i] & 0xc0) == 0x80)
592                 i++;
593         --n;
594     }
595     return i;
596 }