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