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