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