]> git.pond.sub.org Git - empserver/blob - src/lib/subs/pr.c
42ae1bc23cc9f4473879d599925080436f035706
[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, 0);
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, 0);
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(NULL); 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, 0);
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             io_write(pl->iop, bp, len,
237                      player == pl
238                      && !(pl->command && (pl->command->c_flags & C_MOD)));
239             bp += len;
240             pl->curid = -1;
241         } else {
242             len = io_puts(pl->iop, bp);
243             bp += len;
244         }
245     }
246 }
247
248 /*
249  * Send ID text BUF to PL, line-buffered.
250  * This function translates from normal text to user text.
251  * If a partial line with different id is buffered, terminate it with
252  * a newline first.
253  */
254 static void
255 upr_player(struct player *pl, int id, char *buf)
256 {
257     char *bp;
258     int standout = 0;
259     char printbuf[2];
260     char ch;
261
262     printbuf[0] = '\0';
263     printbuf[1] = '\0';
264
265     bp = buf;
266     while ((ch = *bp++)) {
267         if (pl->curid != -1 && pl->curid != id) {
268             io_puts(pl->iop, "\n");
269             pl->curid = -1;
270         }
271         if (pl->curid == -1)
272             outid(pl, id);
273
274         if (ch & 0x80) {
275             if (standout == 0) {
276                 printbuf[0] = 0x0e;
277                 io_puts(pl->iop, printbuf);
278                 standout = 1;
279             }
280             ch &= 0x7f;
281         } else {
282             if (standout == 1) {
283                 printbuf[0] = 0x0f;
284                 io_puts(pl->iop, printbuf);
285                 standout = 0;
286             }
287         }
288         if (ch == '\n') {
289             io_write(pl->iop, &ch, 1,
290                      player == pl
291                      && !(pl->command && (pl->command->c_flags & C_MOD)));
292             pl->curid = -1;
293         } else {
294             printbuf[0] = ch;
295             io_puts(pl->iop, printbuf);
296         }
297     }
298 }
299
300 /*
301  * Send id N to PL.
302  * This runs always at the beginning of a line.
303  */
304 static void
305 outid(struct player *pl, int n)
306 {
307     char buf[3];
308
309     if (CANT_HAPPEN(n > C_LAST))
310         n = C_DATA;
311
312     if (n >= 10)
313         buf[0] = 'a' - 10 + n;
314     else
315         buf[0] = '0' + n;
316     buf[1] = ' ';
317     buf[2] = '\0';
318     io_puts(pl->iop, buf);
319     pl->curid = n;
320 }
321
322 /*
323  * Send redirection request REDIR to the current player.
324  * REDIR is UTF-8, but non-ASCII characters can occur only if the
325  * player sent them.  Therefore, it is also user text.
326  */
327 void
328 prredir(char *redir)
329 {
330     pr_id(player, *redir == '>' ? C_REDIR : C_PIPE, "%s\n", redir);
331 }
332
333 /*
334  * Send script execute request FILE to the current player.
335  * REDIR is UTF-8, but non-ASCII characters can occur only if the
336  * player sent them.  Therefore, it is also user text.
337  */
338 void
339 prexec(char *file)
340 {
341     pr_id(player, C_EXECUTE, "%s\n", file);
342 }
343
344 /*
345  * Send a command prompt to the current player.
346  */
347 void
348 prprompt(int min, int btu)
349 {
350     pr_id(player, C_PROMPT, "%d %d\n", min, btu);
351 }
352
353 /*
354  * Prompt for a line of non-command input.
355  * Send C_FLUSH prompt PROMPT to the current player.
356  * Read a line of input into BUF[SIZE] and convert it to ASCII.
357  * This may block for input, yielding the processor.  Flush buffered
358  * output when blocking, to make sure player sees the prompt.
359  * Return number of bytes in BUF[], not counting the terminating 0,
360  * or -1 on error.
361  */
362 int
363 prmptrd(char *prompt, char *buf, int size)
364 {
365     int r;
366
367     if (CANT_HAPPEN(!prompt))
368         prompt = "? ";
369
370     pr_id(player, C_FLUSH, "%s\n", prompt);
371     if ((r = recvclient(buf, size)) < 0)
372         return r;
373     time(&player->curup);
374     if (*buf == 0)
375         return 1;
376     if (player->flags & PF_UTF8)
377         return copy_utf8_to_ascii_no_funny(buf, buf);
378     return copy_ascii_no_funny(buf, buf);
379 }
380
381 /*
382  * Prompt for a line of non-command, UTF-8 input.
383  * Send C_FLUSH prompt PROMPT to the current player.
384  * Read a line of input into BUF[SIZE], replacing funny characters by
385  * '?'.  The result is UTF-8.
386  * This may block for input, yielding the processor.  Flush buffered
387  * output when blocking, to make sure player sees the prompt.
388  * Return number of bytes in BUF[], not counting the terminating 0,
389  * or -1 on error.
390  */
391 int
392 uprmptrd(char *prompt, char *buf, int size)
393 {
394     int r;
395
396     if (CANT_HAPPEN(!prompt))
397         prompt = "? ";
398
399     pr_id(player, C_FLUSH, "%s\n", prompt);
400     if ((r = recvclient(buf, size)) < 0)
401         return r;
402     time(&player->curup);
403     if (*buf == 0)
404         return 1;
405     if (player->flags & PF_UTF8)
406         return copy_utf8_no_funny(buf, buf);
407     return copy_ascii_no_funny(buf, buf);
408 }
409
410 /*
411  * Print the current time in ctime() format.
412  */
413 void
414 prdate(void)
415 {
416     time_t now;
417
418     (void)time(&now);
419     pr(ctime(&now));
420 }
421
422 /*
423  * Print coordinates X, Y for COUNTRY.
424  * FORMAT must be a printf-style format string that converts exactly
425  * two int values.
426  */
427 void
428 prxy(char *format, coord x, coord y, natid country)
429 {
430     struct natstr *np;
431
432     np = getnatp(country);
433     pr(format, xrel(np, x), yrel(np, y));
434 }
435
436 /*
437  * Print to country CN similar to printf().
438  * Use printf-style FORMAT with the optional arguments.
439  * Output is buffered until a newline arrives.
440  * If CN is the current player and we're not in the update, print just
441  * like pr().  Else print into a bulletin.
442  * Because printing like pr() requires normal text, and bulletins
443  * require user text, only plain ASCII is allowed.
444  */
445 void
446 PR(int cn, char *format, ...)
447 {
448     /* XXX should really do this on a per-nation basis */
449     static char longline[MAXNOC][512];
450     int newline;
451     va_list ap;
452     char buf[1024];
453
454     va_start(ap, format);
455     (void)vsprintf(buf, format, ap);
456     va_end(ap);
457     newline = strrchr(buf, '\n') ? 1 : 0;
458     strcat(longline[cn], buf);
459     if (newline) {
460         if (update_running || (cn && cn != player->cnum))
461             typed_wu(0, cn, longline[cn], TEL_BULLETIN);
462         else
463             pr_player(player, C_DATA, longline[cn]);
464         longline[cn][0] = '\0';
465     }
466 }
467
468 /*
469  * Print the current time in ctime() format to country CN.
470  * If CN is the current player and we're not in the update, print just
471  * like prdate().  Else print into a bulletin.
472  */
473 void
474 PRdate(natid cn)
475 {
476     time_t now;
477
478     (void)time(&now);
479     PR(cn, ctime(&now));
480 }
481
482 /*
483  * Sound the current player's bell.
484  */
485 void
486 pr_beep(void)
487 {
488     struct natstr *np = getnatp(player->cnum);
489
490     if (np->nat_flags & NF_BEEP)
491         pr("\07");
492 }
493
494 /*
495  * Print to country CN similar to printf().
496  * Use printf-style FORMAT with the optional arguments.
497  * If CN is zero, don't print anything.
498  * Else, if CN is the current player and we're not in the update,
499  * print just like pr().  Else print into a bulletin.
500  * Because printing like pr() requires normal text, and bulletins
501  * require user text, only plain ASCII is allowed.
502  */
503 void
504 mpr(int cn, char *format, ...)
505 {
506     char buf[4096];
507     va_list ap;
508
509     if (!cn)
510         return;
511     va_start(ap, format);
512     (void)vsprintf(buf, format, ap);
513     va_end(ap);
514     if (update_running || cn != player->cnum)
515         typed_wu(0, cn, buf, TEL_BULLETIN);
516     else
517         pr_player(player, C_DATA, buf);
518 }
519
520 /*
521  * Copy SRC without funny characters to DST.
522  * Drop control characters, except for '\t'.
523  * Replace non-ASCII characters by '?'.
524  * Return length of DST.
525  * DST must have space.  If it overlaps SRC, then DST <= SRC must
526  * hold.
527  */
528 size_t
529 copy_ascii_no_funny(char *dst, char *src)
530 {
531     char *p;
532     unsigned char ch;
533
534     p = dst;
535     while ((ch = *src++)) {
536         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
537             ;                   /* ignore funny control */
538         else if (ch > 0x7f)
539             *p++ = '?'; /* replace non-ASCII */
540         else
541             *p++ = ch;
542     }
543     *p = 0;
544
545     return p - dst;
546 }
547
548 /*
549  * Copy UTF-8 SRC without funny characters to DST.
550  * Drop control characters, except for '\t'.
551  * FIXME Replace malformed UTF-8 sequences by '?'.
552  * Return byte length of DST.
553  * DST must have space.  If it overlaps SRC, then DST <= SRC must
554  * hold.
555  */
556 size_t
557 copy_utf8_no_funny(char *dst, char *src)
558 {
559     char *p;
560     unsigned char ch;
561
562     p = dst;
563     while ((ch = *src++)) {
564         /* FIXME do the right thing for malformed and overlong sequences */
565         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
566             ;                   /* ignore funny control */
567         else
568             *p++ = ch;
569     }
570     *p = 0;
571
572     return p - dst;
573 }
574
575 /*
576  * Copy UTF-8 SRC without funny characters to ASCII DST.
577  * Drop control characters, except for '\t'.
578  * Replace non-ASCII characters by '?'.
579  * Return length of DST.
580  * DST must have space.  If it overlaps SRC, then DST <= SRC must
581  * hold.
582  */
583 size_t
584 copy_utf8_to_ascii_no_funny(char *dst, char *src)
585 {
586     char *p;
587     unsigned char ch;
588
589     p = dst;
590     while ((ch = *src++)) {
591         /* FIXME do the right thing for malformed and overlong sequences */
592         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
593             ;                   /* ignore funny control */
594         else if (ch > 0x7f) {
595             *p++ = '?';         /* replace non-ASCII */
596             while ((*src++ & 0xc0) == 0x80) ;
597         } else
598             *p++ = ch;
599     }
600     *p = 0;
601
602     return p - dst;
603 }
604
605 /*
606  * Return byte-index of the N-th UTF-8 character in UTF-8 string S.
607  * If S doesn't have that many characters, return its length instead.
608  */
609 int
610 ufindpfx(char *s, int n)
611 {
612     int i = 0;
613
614     while (n && s[i])
615     {
616         if ((s[i++] & 0xc0) == 0xc0)
617             while ((s[i] & 0xc0) == 0x80)
618                 i++;
619         --n;
620     }
621     return i;
622 }