]> 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-2006, Dave Pare, Jeff Bailey, Thomas Ruschak,
4  *                           Ken Stevens, Steve McClure
5  *
6  *  This program 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 2 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, write to the Free Software
18  *  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  *  ---
21  *
22  *  See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
23  *  related information and legal notices. It is expected that any future
24  *  projects/authors will amend these files as needed.
25  *
26  *  ---
27  *
28  *  pr.c: Output to players
29  * 
30  *  Known contributors to this file:
31  *     Dave Pare, 1986, 1989 
32  *     Steve McClure, 1998-2000
33  */
34
35 /*
36  * Player output is fully buffered.  It can block only if the
37  * receiving player is the current player and his last command doesn't
38  * have the C_MOD flag.  Output to another player must not block
39  * because that player could be gone when the printing thread wakes
40  * up, and the code isn't prepared for that.  Output within C_MOD
41  * command never blocks, so that such commands can print freely
42  * without yielding the processor.
43  *
44  * Each line of output starts with an identification character
45  * encoding the output id, followed by space.  Ids less than 10 are
46  * encoded as decimal digits, and larger ids as lower case letters,
47  * starting with 'a'.  Symbolic names for ids are defined in proto.h.
48  */
49
50 #include <config.h>
51
52 #include <string.h>
53 #include <fcntl.h>
54 #include <ctype.h>
55 #include <stdarg.h>
56 #include "proto.h"
57 #include "misc.h"
58 #include "player.h"
59 #include "nat.h"
60 #include "empio.h"
61 #include "file.h"
62 #include "com.h"
63 #include "tel.h"
64 #include "server.h"
65 #include "prototypes.h"
66
67 static void pr_player(struct player *pl, int id, char *buf);
68 static void upr_player(struct player *pl, int id, char *buf);
69 static void outid(struct player *pl, int n);
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 with different id is buffered, terminate it with
118  * 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         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, IO_NOWAIT);
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, IO_NOWAIT);
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  * Initiate an output queue flush, but do not wait for it to complete.
185  */
186 void
187 pr_wall(char *format, ...)
188 {
189     char buf[4096];             /* UTF-8 */
190     struct player *p;
191     va_list ap;
192
193     va_start(ap, format);
194     (void)vsprintf(buf, format, ap);
195     va_end(ap);
196     for (p = player_next(0); p; p = player_next(p)) {
197         if (p->state != PS_PLAYING)
198             continue;
199         pr_player(p, C_FLASH, buf);
200         io_output(p->iop, IO_NOWAIT);
201     }
202 }
203
204 /*
205  * Send ID text BUF to PL, line-buffered.
206  * BUF is user text.
207  * If a partial line with different id is buffered, terminate it with
208  * a newline first.
209  */
210 static void
211 pr_player(struct player *pl, int id, char *buf)
212 {
213     char *p;
214     char *bp;
215     int len;
216
217     bp = buf;
218     while (*bp != '\0') {
219         if (pl->curid != -1 && pl->curid != id) {
220             io_puts(pl->iop, "\n");
221             pl->curid = -1;
222         }
223         if (pl->curid == -1)
224             outid(pl, id);
225         p = strchr(bp, '\n');
226         if (p != NULL) {
227             len = (p - bp) + 1;
228             if ((pl->command && (pl->command->c_flags & C_MOD)) ||
229                 (player != pl))
230                 io_write(pl->iop, bp, len, IO_NOWAIT);
231             else
232                 io_write(pl->iop, bp, len, IO_WAIT);
233             bp += len;
234             pl->curid = -1;
235         } else {
236             len = io_puts(pl->iop, bp);
237             bp += len;
238         }
239     }
240 }
241
242 /*
243  * Send ID text BUF to PL, line-buffered.
244  * This function translates from normal text to user text.
245  * If a partial line with different id is buffered, terminate it with
246  * a newline first.
247  */
248 static void
249 upr_player(struct player *pl, int id, char *buf)
250 {
251     char *bp;
252     int standout = 0;
253     char printbuf[2];
254     char ch;
255
256     printbuf[0] = '\0';
257     printbuf[1] = '\0';
258
259     bp = buf;
260     while ((ch = *bp++)) {
261         if (pl->curid != -1 && pl->curid != id) {
262             io_puts(pl->iop, "\n");
263             pl->curid = -1;
264         }
265         if (pl->curid == -1)
266             outid(pl, id);
267
268         if (ch & 0x80) {
269             if (standout == 0) {
270                 printbuf[0] = 0x0e;
271                 io_puts(pl->iop, printbuf);
272                 standout = 1;
273             }
274             ch &= 0x7f;
275         } else {
276             if (standout == 1) {
277                 printbuf[0] = 0x0f;
278                 io_puts(pl->iop, printbuf);
279                 standout = 0;
280             }
281         }
282         if (ch == '\n') {
283             if ((pl->command && (pl->command->c_flags & C_MOD)) ||
284                 (player != pl))
285                 io_write(pl->iop, &ch, 1, IO_NOWAIT);
286             else
287                 io_write(pl->iop, &ch, 1, IO_WAIT);
288             pl->curid = -1;
289         } else {
290             printbuf[0] = ch;
291             io_puts(pl->iop, printbuf);
292         }
293     }
294 }
295
296 /*
297  * Send id N to PL.
298  * This runs always at the beginning of a line.
299  */
300 static void
301 outid(struct player *pl, int n)
302 {
303     char buf[3];
304
305     if (CANT_HAPPEN(n > C_LAST))
306         n = C_DATA;
307
308     if (n >= 10)
309         buf[0] = 'a' - 10 + n;
310     else
311         buf[0] = '0' + n;
312     buf[1] = ' ';
313     buf[2] = '\0';
314     io_puts(pl->iop, buf);
315     pl->curid = n;
316 }
317
318 /*
319  * Send redirection request REDIR to the current player.
320  * REDIR is UTF-8, but non-ASCII characters can occur only if the
321  * player sent them.  Therefore, it is also user text.
322  */
323 void
324 prredir(char *redir)
325 {
326     pr_id(player, *redir == '>' ? C_REDIR : C_PIPE, "%s\n", redir);
327 }
328
329 /*
330  * Send script execute request FILE 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 prexec(char *file)
336 {
337     pr_id(player, C_EXECUTE, "%s\n", file);
338 }
339
340 /*
341  * Send a command prompt to the current player.
342  */
343 void
344 prprompt(int min, int btu)
345 {
346     pr_id(player, C_PROMPT, "%d %d\n", min, btu);
347 }
348
349 /*
350  * Prompt for a line of non-command input.
351  * Send C_FLUSH prompt PROMPT to the current player.
352  * Read a line of input into BUF[SIZE] and convert it to ASCII.
353  * This may block for input, yielding the processor.  Flush buffered
354  * output when blocking, to make sure player sees the prompt.
355  * Return number of bytes in BUF[], not counting the terminating 0,
356  * or -1 on error.
357  */
358 int
359 prmptrd(char *prompt, char *buf, int size)
360 {
361     int r;
362
363     /*
364      * Each prompt must consume one line of input.  recvclient()
365      * doesn't do that while player->aborted.
366      */
367     CANT_HAPPEN(player->aborted);
368
369     pr_id(player, C_FLUSH, "%s\n", prompt);
370     if ((r = recvclient(buf, size)) < 0)
371         return r;
372     time(&player->curup);
373     if (*buf == 0)
374         return 1;
375     if (player->flags & PF_UTF8)
376         return copy_utf8_to_ascii_no_funny(buf, buf);
377     return copy_ascii_no_funny(buf, buf);
378 }
379
380 /*
381  * Prompt for a line of non-command, UTF-8 input.
382  * Send C_FLUSH prompt PROMPT to the current player.
383  * Read a line of input into BUF[SIZE], replacing funny characters by
384  * '?'.  The result is UTF-8.
385  * This may block for input, yielding the processor.  Flush buffered
386  * output when blocking, to make sure player sees the prompt.
387  * Return number of bytes in BUF[], not counting the terminating 0,
388  * or -1 on error.
389  */
390 int
391 uprmptrd(char *prompt, char *buf, int size)
392 {
393     int r;
394
395     /* See prmptrd() */
396     CANT_HAPPEN(player->aborted);
397
398     pr_id(player, C_FLUSH, "%s\n", prompt);
399     if ((r = recvclient(buf, size)) < 0)
400         return r;
401     time(&player->curup);
402     if (*buf == 0)
403         return 1;
404     if (player->flags & PF_UTF8)
405         return copy_utf8_no_funny(buf, buf);
406     return copy_ascii_no_funny(buf, buf);
407 }
408
409 /*
410  * Print the current time in ctime() format.
411  */
412 void
413 prdate(void)
414 {
415     time_t now;
416
417     (void)time(&now);
418     pr(ctime(&now));
419 }
420
421 /*
422  * Print coordinates X, Y for COUNTRY.
423  * FORMAT must be a printf-style format string that converts exactly
424  * two int values.
425  */
426 void
427 prxy(char *format, coord x, coord y, natid country)
428 {
429     char buf[255];
430     struct natstr *np;
431
432     np = getnatp(country);
433     sprintf(buf, format, xrel(np, x), yrel(np, y));
434     pr(buf);
435 }
436
437 /*
438  * Print to country CN similar to printf().
439  * Use printf-style FORMAT with the optional arguments.
440  * Output is buffered until a newline arrives.
441  * If CN is the current player and we're not in the update, print just
442  * like pr().  Else print into a bulletin.
443  * Because printing like pr() requires normal text, and bulletins
444  * require user text, only plain ASCII is allowed.
445  */
446 void
447 PR(int cn, char *format, ...)
448 {
449     /* XXX should really do this on a per-nation basis */
450     static char longline[MAXNOC][512];
451     int newline;
452     va_list ap;
453     char buf[1024];
454
455     va_start(ap, format);
456     (void)vsprintf(buf, format, ap);
457     va_end(ap);
458     newline = strrchr(buf, '\n') ? 1 : 0;
459     strcat(longline[cn], buf);
460     if (newline) {
461         if (update_pending || (cn && cn != player->cnum))
462             typed_wu(0, cn, longline[cn], TEL_BULLETIN);
463         else
464             pr_player(player, C_DATA, longline[cn]);
465         longline[cn][0] = '\0';
466     }
467 }
468
469 /*
470  * Print the current time in ctime() format to country CN.
471  * If CN is the current player and we're not in the update, print just
472  * like prdate().  Else print into a bulletin.
473  */
474 void
475 PRdate(natid cn)
476 {
477     time_t now;
478
479     (void)time(&now);
480     PR(cn, ctime(&now));
481 }
482
483 /*
484  * Sound the current player's bell.
485  */
486 void
487 pr_beep(void)
488 {
489     struct natstr *np = getnatp(player->cnum);
490
491     if (np->nat_flags & NF_BEEP)
492         pr("\07");
493 }
494
495 /*
496  * Print to country CN similar to printf().
497  * Use printf-style FORMAT with the optional arguments.
498  * If CN is the current player and we're not in the update, print just
499  * like pr().  Else print into a bulletin.
500  * Because printing like pr() requires normal text, and bulletins
501  * require user text, only plain ASCII is allowed.
502  */
503 void
504 mpr(int cn, char *format, ...)
505 {
506     char buf[4096];
507     va_list ap;
508
509     va_start(ap, format);
510     (void)vsprintf(buf, format, ap);
511     va_end(ap);
512     if (cn) {
513         if (update_pending || cn != player->cnum)
514             typed_wu(0, cn, buf, TEL_BULLETIN);
515         else
516             pr_player(player, C_DATA, buf);
517     }
518 }
519
520 /*
521  * Copy SRC without funny characters to DST.
522  * Drop control characters, except for '\t'.
523  * Replace non-ASCII characters by '?'.
524  * Return length of DST.
525  * DST must have space.  If it overlaps SRC, then DST <= SRC must
526  * hold.
527  */
528 size_t
529 copy_ascii_no_funny(char *dst, char *src)
530 {
531     char *p;
532     unsigned char ch;
533
534     p = dst;
535     while ((ch = *src++)) {
536         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
537             ;                   /* ignore funny control */
538         else if (ch > 0x7f)
539             *p++ = '?'; /* replace non-ASCII */
540         else
541             *p++ = ch;
542     }
543     *p = 0;
544
545     return p - dst;
546 }
547
548 /*
549  * Copy UTF-8 SRC without funny characters to DST.
550  * Drop control characters, except for '\t'.
551  * FIXME Replace malformed UTF-8 sequences by '?'.
552  * Return byte length of DST.
553  * DST must have space.  If it overlaps SRC, then DST <= SRC must
554  * hold.
555  */
556 size_t
557 copy_utf8_no_funny(char *dst, char *src)
558 {
559     char *p;
560     unsigned char ch;
561
562     p = dst;
563     while ((ch = *src++)) {
564         /* FIXME do the right thing for malformed and overlong sequences */
565         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
566             ;                   /* ignore funny control */
567         else
568             *p++ = ch;
569     }
570     *p = 0;
571
572     return p - dst;
573 }
574
575 /*
576  * Copy UTF-8 SRC without funny characters to ASCII DST.
577  * Drop control characters, except for '\t'.
578  * Replace non-ASCII characters by '?'.
579  * Return length of DST.
580  * DST must have space.  If it overlaps SRC, then DST <= SRC must
581  * hold.
582  */
583 size_t
584 copy_utf8_to_ascii_no_funny(char *dst, char *src)
585 {
586     char *p;
587     unsigned char ch;
588
589     p = dst;
590     while ((ch = *src++)) {
591         /* FIXME do the right thing for malformed and overlong sequences */
592         if ((ch < 0x20 && ch != '\t' && ch != '\n') || ch == 0x7f)
593             ;                   /* ignore funny control */
594         else if (ch > 0x7f) {
595             *p++ = '?';         /* replace non-ASCII */
596             while ((*src++ & 0xc0) == 0x80) ;
597         } else
598             *p++ = ch;
599     }
600     *p = 0;
601
602     return p - dst;
603 }
604
605 /*
606  * Return byte-index of the N-th UTF-8 character in UTF-8 string S.
607  * If S doesn't have that many characters, return its length instead.
608  */
609 int
610 ufindpfx(char *s, int n)
611 {
612     int i = 0;
613
614     while (n && s[i])
615     {
616         if ((s[i++] & 0xc0) == 0xc0)
617             while ((s[i] & 0xc0) == 0x80)
618                 i++;
619         --n;
620     }
621     return i;
622 }