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