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