]> git.pond.sub.org Git - empserver/blob - src/lib/subs/pr.c
Doc clarification.
[empserver] / src / lib / subs / pr.c
1 /*
2  *  Empire - A multi-player, client/server Internet based war game.
3  *  Copyright (C) 1986-2005, 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: Use to do output to a player
29  * 
30  *  Known contributors to this file:
31  *     Dave Pare, 1986, 1989 
32  *     Steve McClure, 1998-2000
33  */
34 /*
35  * The pr routine historically arranged for nonbuffered i/o
36  * because stdio didn't used to automatically flush stdout before
37  * it read something from stdin.  Now pr() prepends an "output id"
38  * in front of each line of text, informing the user interface
39  * what sort of item it is seeing; prompt, noecho prompt,
40  * more input data, etc.
41  */
42
43 #include <string.h>
44 #include <fcntl.h>
45 #include <ctype.h>
46 #include <stdarg.h>
47 #include "proto.h"
48 #include "misc.h"
49 #include "player.h"
50 #include "nat.h"
51 #include "empio.h"
52 #include "file.h"
53 #include "com.h"
54 #include "tel.h"
55 #include "server.h"
56 #include "prototypes.h"
57
58 static void pr_player(struct player *pl, int id, char *buf);
59 static void upr_player(struct player *pl, int id, char *buf);
60 static void outid(struct player *pl, int n);
61
62 /*
63  * Print to current player similar to printf().
64  * Use printf-style FORMAT with the optional arguments.
65  * Note: `to print' without further qualifications means sending
66  * C_DATA text.
67  */
68 void
69 pr(char *format, ...)
70 {
71     char buf[4096];
72     va_list ap;
73
74     va_start(ap, format);
75     (void)vsprintf(buf, format, ap);
76     va_end(ap);
77     if (player->flags & PF_UTF8)
78         /* normal text needs to be converted to user text */
79         upr_player(player, C_DATA, buf);
80     else
81         /* normal text and user text are identical */
82         pr_player(player, C_DATA, buf);
83 }
84
85 /*
86  * Print UTF-8 text BUF to current player.
87  */
88 void
89 uprnf(char *buf)
90 {
91     char *p;
92
93     if (!(player->flags & PF_UTF8)) {
94         p = malloc(strlen(buf) + 1);
95         copy_utf8_to_ascii_no_funny(p, buf);
96         pr_player(player, C_DATA, p);
97         free(p);
98     } else
99         pr_player(player, C_DATA, buf);
100 }
101
102 /*
103  * Send some text to P with id ID, line-buffered.
104  * Format text to send using printf-style FORMAT and optional
105  * arguments.  It is assumed to be already user text.  Plain ASCII and
106  * text received from the same player are fine, for anything else the
107  * caller has to deal with output filtering.
108  * If a partial line with different id is buffered, terminate it with
109  * a newline first.
110  */
111 void
112 pr_id(struct player *p, int id, char *format, ...)
113 {
114     char buf[4096];
115     va_list ap;
116
117     if (p->curid >= 0) {
118         io_puts(p->iop, "\n");
119         p->curid = -1;
120     }
121     va_start(ap, format);
122     (void)vsprintf(buf, format, ap);
123     va_end(ap);
124     pr_player(p, id, buf);
125 }
126
127 /*
128  * Send C_FLASH text to PL.
129  * Format text to send using printf-style FORMAT and optional
130  * arguments.  It is assumed to be UTF-8.
131  */
132 void
133 pr_flash(struct player *pl, char *format, ...)
134 {
135     char buf[4096];             /* UTF-8 */
136     va_list ap;
137
138     if (pl->state != PS_PLAYING)
139         return;
140     va_start(ap, format);
141     (void)vsprintf(buf, format, ap);
142     va_end(ap);
143     if (!(pl->flags & PF_UTF8))
144         copy_utf8_to_ascii_no_funny(buf, buf);
145     pr_player(pl, C_FLASH, buf);
146     io_output(pl->iop, IO_NOWAIT);
147 }
148
149 /*
150  * Send C_INFORM text to PL.
151  * Format text to send using printf-style FORMAT and optional
152  * arguments.  It is assumed to be plain ASCII.
153  */
154 void
155 pr_inform(struct player *pl, char *format, ...)
156 {
157     char buf[4096];
158     va_list ap;
159
160     if (pl->state != PS_PLAYING)
161         return;
162     va_start(ap, format);
163     (void)vsprintf(buf, format, ap);
164     va_end(ap);
165     pr_player(pl, C_INFORM, buf);
166     io_output(pl->iop, IO_NOWAIT);
167 }
168
169 /*
170  * Send C_FLASH text to everyone.
171  * Format text to send using printf-style FORMAT and optional
172  * arguments.  It is assumed to be plain ASCII.
173  */
174 void
175 pr_wall(char *format, ...)
176 {
177     char buf[4096];             /* UTF-8 */
178     struct player *p;
179     va_list ap;
180
181     va_start(ap, format);
182     (void)vsprintf(buf, format, ap);
183     va_end(ap);
184     for (p = player_next(0); p; p = player_next(p)) {
185         if (p->state != PS_PLAYING)
186             continue;
187         pr_player(p, C_FLASH, buf);
188         io_output(p->iop, IO_NOWAIT);
189     }
190 }
191
192 /*
193  * Send ID text BUF to PL, line-buffered.
194  * BUF is user text.
195  * If a partial line with different id is buffered, terminate it with
196  * a newline first.
197  */
198 static void
199 pr_player(struct player *pl, int id, char *buf)
200 {
201     char *p;
202     char *bp;
203     int len;
204
205     bp = buf;
206     while (*bp != '\0') {
207         if (pl->curid != -1 && pl->curid != id) {
208             io_puts(pl->iop, "\n");
209             pl->curid = -1;
210         }
211         if (pl->curid == -1)
212             outid(pl, id);
213         p = strchr(bp, '\n');
214         if (p != NULL) {
215             len = (p - bp) + 1;
216             if (pl->command && (pl->command->c_flags & C_MOD))
217                 io_write(pl->iop, bp, len, IO_NOWAIT);
218             else
219                 io_write(pl->iop, bp, len, IO_WAIT);
220             bp += len;
221             pl->curid = -1;
222         } else {
223             len = io_puts(pl->iop, bp);
224             bp += len;
225         }
226     }
227 }
228
229 /*
230  * Send ID text BUF to PL, line-buffered.
231  * This function translates from normal text to user text.
232  * If a partial line with different id is buffered, terminate it with
233  * a newline first.
234  */
235 static void
236 upr_player(struct player *pl, int id, char *buf)
237 {
238     char *bp;
239     int standout = 0;
240     char printbuf[2];
241     char ch;
242
243     printbuf[0] = '\0';
244     printbuf[1] = '\0';
245
246     bp = buf;
247     while ((ch = *bp++)) {
248         if (pl->curid != -1 && pl->curid != id) {
249             io_puts(pl->iop, "\n");
250             pl->curid = -1;
251         }
252         if (pl->curid == -1)
253             outid(pl, id);
254
255         if (ch & 0x80) {
256             if (standout == 0) {
257                 printbuf[0] = 0x0e;
258                 io_puts(pl->iop, printbuf);
259                 standout = 1;
260             }
261             ch &= 0x7f;
262         } else {
263             if (standout == 1) {
264                 printbuf[0] = 0x0f;
265                 io_puts(pl->iop, printbuf);
266                 standout = 0;
267             }
268         }
269         if (ch == '\n') {
270             if (pl->command && (pl->command->c_flags & C_MOD))
271                 io_write(pl->iop, &ch, 1, IO_NOWAIT);
272             else
273                 io_write(pl->iop, &ch, 1, IO_WAIT);
274             pl->curid = -1;
275         } else {
276             printbuf[0] = ch;
277             io_puts(pl->iop, printbuf);
278         }
279     }
280 }
281
282 /*
283  * Send id N to PL.
284  * This runs always at the beginning of a line.
285  */
286 static void
287 outid(struct player *pl, int n)
288 {
289     char buf[3];
290
291     if (CANT_HAPPEN(n > C_LAST))
292         n = C_DATA;
293
294     if (n >= 10)
295         buf[0] = 'a' - 10 + n;
296     else
297         buf[0] = '0' + n;
298     buf[1] = ' ';
299     buf[2] = '\0';
300     io_puts(pl->iop, buf);
301     pl->curid = n;
302 }
303
304 /*
305  * Send redirection request REDIR to the current player.
306  * REDIR is UTF-8, but non-ASCII characters can occur only if the
307  * player sent them.  Therefore, it is also user text.
308  */
309 void
310 prredir(char *redir)
311 {
312     pr_id(player, *redir == '>' ? C_REDIR : C_PIPE, "%s\n", redir);
313 }
314
315 /*
316  * Send script execute request FILE to the current player.
317  * REDIR is UTF-8, but non-ASCII characters can occur only if the
318  * player sent them.  Therefore, it is also user text.
319  */
320 void
321 prexec(char *file)
322 {
323     pr_id(player, C_EXECUTE, "%s\n", file);
324 }
325
326 /*
327  * Send a command prompt to the current player.
328  */
329 void
330 prprompt(int min, int btu)
331 {
332     pr_id(player, C_PROMPT, "%d %d\n", min, btu);
333 }
334
335 /*
336  * Prompt for a line of non-command input.
337  * Send C_FLUSH prompt PROMPT to the current player.
338  * Read a line of input into BUF[SIZE] and convert it to ASCII.
339  * Return number of bytes in BUF[], not counting the terminating 0,
340  * or -1 on error.
341  */
342 int
343 prmptrd(char *prompt, char *buf, int size)
344 {
345     int r;
346     char *cp;
347
348     pr_id(player, C_FLUSH, "%s\n", prompt);
349     if ((r = recvclient(buf, size)) < 0)
350         return r;
351     time(&player->curup);
352     if (*buf == 0)
353         return 1;
354     if (player->flags & PF_UTF8)
355         return copy_utf8_to_ascii_no_funny(buf, buf);
356     return copy_ascii_no_funny(buf, buf);
357 }
358
359 /*
360  * Prompt for a line of non-command, UTF-8 input.
361  * Send C_FLUSH prompt PROMPT to the current player.
362  * Read a line of input into BUF[SIZE], replacing funny characters by
363  * '?'.  The result is UTF-8.
364  * Return number of bytes in BUF[], not counting the terminating 0,
365  * or -1 on error.
366  */
367 int
368 uprmptrd(char *prompt, char *buf, int size)
369 {
370     int r;
371     char *cp;
372
373     pr_id(player, C_FLUSH, "%s\n", prompt);
374     if ((r = recvclient(buf, size)) < 0)
375         return r;
376     time(&player->curup);
377     if (*buf == 0)
378         return 1;
379     if (player->flags & PF_UTF8)
380         return copy_utf8_no_funny(buf, buf);
381     return copy_ascii_no_funny(buf, buf);
382 }
383
384 /*
385  * Print the current time in ctime() format.
386  */
387 void
388 prdate(void)
389 {
390     time_t now;
391
392     (void)time(&now);
393     pr(ctime(&now));
394 }
395
396 /*
397  * Print coordinates X, Y for COUNTRY.
398  * FORMAT must be a printf-style format string that converts exactly
399  * two int values.
400  */
401 void
402 prxy(char *format, coord x, coord y, natid country)
403 {
404     char buf[255];
405     struct natstr *np;
406
407     np = getnatp(country);
408     sprintf(buf, format, xrel(np, x), yrel(np, y));
409     pr(buf);
410 }
411
412 /*
413  * Print to country CN similar to printf().
414  * Use printf-style FORMAT with the optional arguments.
415  * Output is buffered until a newline arrives.
416  * If CN is the current player, print just like pr().
417  * Else print into a bulletin.
418  * Because printing like pr() requires normal text, and bulletins
419  * require user text, only plain ASCII is allowed.
420  */
421 void
422 PR(int cn, char *format, ...)
423 {
424     /* XXX should really do this on a per-nation basis */
425     static char longline[MAXNOC][512];
426     int newline;
427     va_list ap;
428     char buf[1024];
429
430     va_start(ap, format);
431     (void)vsprintf(buf, format, ap);
432     va_end(ap);
433     newline = strrchr(buf, '\n') ? 1 : 0;
434     strcat(longline[cn], buf);
435     if (newline) {
436         if (update_pending || (cn && cn != player->cnum))
437             typed_wu(0, cn, longline[cn], TEL_BULLETIN);
438         else
439             pr_player(player, C_DATA, longline[cn]);
440         longline[cn][0] = '\0';
441     }
442 }
443
444 /*
445  * Print the current time in ctime() format to country CN.
446  * If CN is the current player, print like prdate().
447  * Else print into a bulletin.
448  */
449 void
450 PRdate(natid cn)
451 {
452     time_t now;
453
454     (void)time(&now);
455     PR(cn, ctime(&now));
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 to country CN similar to printf().
472  * Use printf-style FORMAT with the optional arguments.
473  * If CN is the current player, print just like pr().
474  * Else print into a bulletin.
475  * Because printing like pr() requires normal text, and bulletins
476  * require user text, only plain ASCII is allowed.
477  */
478 void
479 mpr(int cn, char *format, ...)
480 {
481     char buf[4096];
482     va_list ap;
483
484     va_start(ap, format);
485     (void)vsprintf(buf, format, ap);
486     va_end(ap);
487     if (cn) {
488         if (update_pending || cn != player->cnum)
489             typed_wu(0, cn, buf, TEL_BULLETIN);
490         else
491             pr_player(player, C_DATA, buf);
492     }
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         } 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     {
591         if ((s[i++] & 0xc0) == 0xc0)
592             while ((s[i] & 0xc0) == 0x80)
593                 i++;
594         --n;
595     }
596     return i;
597 }