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