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