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