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