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