empserver/src/lib/subs/pr.c
Markus Armbruster 65b6eaeee5 (player_login): Replace pr_player() by equivalent pr_id().
(pr_player, upr_player): Internal linkage.

(pr_hilite): Unused, remove.
2005-06-13 19:05:37 +00:00

596 lines
13 KiB
C

/*
* Empire - A multi-player, client/server Internet based war game.
* Copyright (C) 1986-2005, Dave Pare, Jeff Bailey, Thomas Ruschak,
* Ken Stevens, Steve McClure
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
* ---
*
* See the "LEGAL", "LICENSE", "CREDITS" and "README" files for all the
* related information and legal notices. It is expected that any future
* projects/authors will amend these files as needed.
*
* ---
*
* pr.c: Use to do output to a player
*
* Known contributors to this file:
* Dave Pare, 1986, 1989
* Steve McClure, 1998-2000
*/
/*
* The pr routine historically arranged for nonbuffered i/o
* because stdio didn't used to automatically flush stdout before
* it read something from stdin. Now pr() prepends an "output id"
* in front of each line of text, informing the user interface
* what sort of item it is seeing; prompt, noecho prompt,
* more input data, etc.
*/
#include <string.h>
#include <fcntl.h>
#include <ctype.h>
#include <stdarg.h>
#include "proto.h"
#include "misc.h"
#include "player.h"
#include "nat.h"
#include "empio.h"
#include "file.h"
#include "com.h"
#include "tel.h"
#include "server.h"
#include "prototypes.h"
static void pr_player(struct player *pl, int id, char *buf);
static void upr_player(struct player *pl, int id, char *buf);
static void outid(struct player *pl, int n);
/*
* Print to current player similar to printf().
* Use printf-style FORMAT with the optional arguments.
* Note: `to print' without further qualifications means sending
* C_DATA text.
*/
void
pr(char *format, ...)
{
char buf[4096];
va_list ap;
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
if (player->flags & PF_UTF8)
/* normal text needs to be converted to user text */
upr_player(player, C_DATA, buf);
else
/* normal text and user text are identical */
pr_player(player, C_DATA, buf);
}
/*
* Print UTF-8 text BUF to current player.
*/
void
uprnf(char *buf)
{
char *p;
if (!(player->flags & PF_UTF8)) {
p = malloc(strlen(buf) + 1);
copy_utf8_to_ascii_no_funny(p, buf);
pr_player(player, C_DATA, p);
free(p);
} else
pr_player(player, C_DATA, buf);
}
/*
* Send some text to P with id ID, line-buffered.
* Format text to send using printf-style FORMAT and optional
* arguments. It is assumed to be already user text. Plain ASCII and
* text received from the same player are fine, for anything else the
* caller has to deal with output filtering.
* If a partial line with different id is buffered, terminate it with
* a newline first.
*/
void
pr_id(struct player *p, int id, char *format, ...)
{
char buf[4096];
va_list ap;
if (p->curid >= 0) {
io_puts(p->iop, "\n");
p->curid = -1;
}
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
pr_player(p, id, buf);
}
/*
* Send C_FLASH text to PL.
* Format text to send using printf-style FORMAT and optional
* arguments. It is assumed to be UTF-8.
*/
void
pr_flash(struct player *pl, char *format, ...)
{
char buf[4096]; /* UTF-8 */
va_list ap;
if (pl->state != PS_PLAYING)
return;
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
if (!(pl->flags & PF_UTF8))
copy_utf8_to_ascii_no_funny(buf, buf);
pr_player(pl, C_FLASH, buf);
io_output(pl->iop, IO_NOWAIT);
}
/*
* Send C_INFORM text to PL.
* Format text to send using printf-style FORMAT and optional
* arguments. It is assumed to be plain ASCII.
*/
void
pr_inform(struct player *pl, char *format, ...)
{
char buf[4096];
va_list ap;
if (pl->state != PS_PLAYING)
return;
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
pr_player(pl, C_INFORM, buf);
io_output(pl->iop, IO_NOWAIT);
}
/*
* Send C_FLASH text to everyone.
* Format text to send using printf-style FORMAT and optional
* arguments. It is assumed to be plain ASCII.
*/
void
pr_wall(char *format, ...)
{
char buf[4096]; /* UTF-8 */
struct player *p;
va_list ap;
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
for (p = player_next(0); p; p = player_next(p)) {
if (p->state != PS_PLAYING)
continue;
pr_player(p, C_FLASH, buf);
io_output(p->iop, IO_NOWAIT);
}
}
/*
* Send ID text BUF to PL, line-buffered.
* BUF is user text.
* If a partial line with different id is buffered, terminate it with
* a newline first.
*/
static void
pr_player(struct player *pl, int id, char *buf)
{
char *p;
char *bp;
int len;
bp = buf;
while (*bp != '\0') {
if (pl->curid != -1 && pl->curid != id) {
io_puts(pl->iop, "\n");
pl->curid = -1;
}
if (pl->curid == -1)
outid(pl, id);
p = strchr(bp, '\n');
if (p != NULL) {
len = (p - bp) + 1;
if (pl->command && (pl->command->c_flags & C_MOD))
io_write(pl->iop, bp, len, IO_NOWAIT);
else
io_write(pl->iop, bp, len, IO_WAIT);
bp += len;
pl->curid = -1;
} else {
len = io_puts(pl->iop, bp);
bp += len;
}
}
}
/*
* Send ID text BUF to PL, line-buffered.
* If a partial line with different id is buffered, terminate it with
* a newline first.
*/
static void
upr_player(struct player *pl, int id, char *buf)
{
char *bp;
int standout = 0;
char printbuf[2];
char ch;
printbuf[0] = '\0';
printbuf[1] = '\0';
bp = buf;
while ((ch = *bp++)) {
if (pl->curid != -1 && pl->curid != id) {
io_puts(pl->iop, "\n");
pl->curid = -1;
}
if (pl->curid == -1)
outid(pl, id);
if (ch & 0x80) {
if (standout == 0) {
printbuf[0] = 0x0e;
io_puts(pl->iop, printbuf);
standout = 1;
}
ch &= 0x7f;
} else {
if (standout == 1) {
printbuf[0] = 0x0f;
io_puts(pl->iop, printbuf);
standout = 0;
}
}
if (ch == '\n') {
if (pl->command && (pl->command->c_flags & C_MOD))
io_write(pl->iop, &ch, 1, IO_NOWAIT);
else
io_write(pl->iop, &ch, 1, IO_WAIT);
pl->curid = -1;
} else {
printbuf[0] = ch;
io_puts(pl->iop, printbuf);
}
}
}
/*
* Send id N to PL.
* This runs always at the beginning of a line.
*/
static void
outid(struct player *pl, int n)
{
char buf[3];
if (CANT_HAPPEN(n > C_LAST))
n = C_DATA;
if (n >= 10)
buf[0] = 'a' - 10 + n;
else
buf[0] = '0' + n;
buf[1] = ' ';
buf[2] = '\0';
io_puts(pl->iop, buf);
pl->curid = n;
}
/*
* Send redirection request REDIR to the current player.
* REDIR is UTF-8, but non-ASCII characters can occur only if the
* player sent them. Therefore, it is also user text.
*/
void
prredir(char *redir)
{
pr_id(player, *redir == '>' ? C_REDIR : C_PIPE, "%s\n", redir);
}
/*
* Send script execute request FILE to the current player.
* REDIR is UTF-8, but non-ASCII characters can occur only if the
* player sent them. Therefore, it is also user text.
*/
void
prexec(char *file)
{
pr_id(player, C_EXECUTE, "%s\n", file);
}
/*
* Send a command prompt to the current player.
*/
void
prprompt(int min, int btu)
{
pr_id(player, C_PROMPT, "%d %d\n", min, btu);
}
/*
* Prompt for a line of non-command input.
* Send C_FLUSH prompt PROMPT to the current player.
* Read a line of input into BUF[SIZE] and convert it to ASCII.
* Return number of bytes in BUF[], not counting the terminating 0,
* or -1 on error.
*/
int
prmptrd(char *prompt, char *buf, int size)
{
int r;
char *cp;
pr_id(player, C_FLUSH, "%s\n", prompt);
if ((r = recvclient(buf, size)) < 0)
return r;
time(&player->curup);
if (*buf == 0)
return 1;
if (player->flags & PF_UTF8)
return copy_utf8_to_ascii_no_funny(buf, buf);
return copy_ascii_no_funny(buf, buf);
}
/*
* Prompt for a line of non-command, UTF-8 input.
* Send C_FLUSH prompt PROMPT to the current player.
* Read a line of input into BUF[SIZE], replacing funny characters by
* '?'. The result is UTF-8.
* Return number of bytes in BUF[], not counting the terminating 0,
* or -1 on error.
*/
int
uprmptrd(char *prompt, char *buf, int size)
{
int r;
char *cp;
pr_id(player, C_FLUSH, "%s\n", prompt);
if ((r = recvclient(buf, size)) < 0)
return r;
time(&player->curup);
if (*buf == 0)
return 1;
if (player->flags & PF_UTF8)
return copy_utf8_no_funny(buf, buf);
return copy_ascii_no_funny(buf, buf);
}
/*
* Print the current time in ctime() format.
*/
void
prdate(void)
{
time_t now;
(void)time(&now);
pr(ctime(&now));
}
/*
* Print coordinates X, Y for COUNTRY.
* FORMAT must be a printf-style format string that converts exactly
* two int values.
*/
void
prxy(char *format, coord x, coord y, natid country)
{
char buf[255];
struct natstr *np;
np = getnatp(country);
sprintf(buf, format, xrel(np, x), yrel(np, y));
pr(buf);
}
/*
* Print to country CN similar to printf().
* Use printf-style FORMAT with the optional arguments.
* Output is buffered until a newline arrives.
* If CN is the current player, print just like pr().
* Else print into a bulletin.
* Because printing like pr() requires normal text, and bulletins
* require user text, only plain ASCII is allowed.
*/
void
PR(int cn, char *format, ...)
{
/* XXX should really do this on a per-nation basis */
static char longline[MAXNOC][512];
int newline;
va_list ap;
char buf[1024];
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
newline = strrchr(buf, '\n') ? 1 : 0;
strcat(longline[cn], buf);
if (newline) {
if (update_pending || (cn && cn != player->cnum))
typed_wu(0, cn, longline[cn], TEL_BULLETIN);
else
pr_player(player, C_DATA, longline[cn]);
longline[cn][0] = '\0';
}
}
/*
* Print the current time in ctime() format to country CN.
* If CN is the current player, print like prdate().
* Else print into a bulletin.
*/
void
PRdate(natid cn)
{
time_t now;
(void)time(&now);
PR(cn, ctime(&now));
}
/*
* Sound the current player's bell.
*/
void
pr_beep(void)
{
struct natstr *np = getnatp(player->cnum);
if (np->nat_flags & NF_BEEP)
pr("\07");
}
/*
* Print to country CN similar to printf().
* Use printf-style FORMAT with the optional arguments.
* If CN is the current player, print just like pr().
* Else print into a bulletin.
* Because printing like pr() requires normal text, and bulletins
* require user text, only plain ASCII is allowed.
*/
void
mpr(int cn, char *format, ...)
{
char buf[4096];
va_list ap;
va_start(ap, format);
(void)vsprintf(buf, format, ap);
va_end(ap);
if (cn) {
if (update_pending || cn != player->cnum)
typed_wu(0, cn, buf, TEL_BULLETIN);
else
pr_player(player, C_DATA, buf);
}
}
/*
* Copy SRC without funny characters to DST.
* Drop control characters, except for '\t'.
* Replace non-ASCII characters by '?'.
* Return length of DST.
* DST must have space. If it overlaps SRC, then DST <= SRC must
* hold.
*/
size_t
copy_ascii_no_funny(char *dst, char *src)
{
char *p;
unsigned char ch;
p = dst;
while ((ch = *src++)) {
if ((ch < 0x20 && ch != '\t') || ch == 0x7f)
; /* ignore control */
else if (ch > 0x7f)
*p++ = '?'; /* replace non-ASCII */
else
*p++ = ch;
}
*p = 0;
return p - dst;
}
/*
* Copy UTF-8 SRC without funny characters to DST.
* Drop control characters, except for '\t'.
* FIXME Replace malformed UTF-8 sequences by '?'.
* Return byte length of DST.
* DST must have space. If it overlaps SRC, then DST <= SRC must
* hold.
*/
size_t
copy_utf8_no_funny(char *dst, char *src)
{
char *p;
unsigned char ch;
p = dst;
while ((ch = *src++)) {
/* FIXME do the right thing for malformed and overlong sequences */
if ((ch < 0x20 && ch != '\t') || ch == 0x7f)
; /* ignore control */
else
*p++ = ch;
}
*p = 0;
return p - dst;
}
/*
* Copy UTF-8 SRC without funny characters to ASCII DST.
* Drop control characters, except for '\t'.
* Replace non-ASCII characters by '?'.
* Return length of DST.
* DST must have space. If it overlaps SRC, then DST <= SRC must
* hold.
*/
size_t
copy_utf8_to_ascii_no_funny(char *dst, char *src)
{
char *p;
unsigned char ch;
p = dst;
while ((ch = *src++)) {
/* FIXME do the right thing for malformed and overlong sequences */
if ((ch < 0x20 && ch != '\t') || ch == 0x7f)
; /* ignore control */
else if (ch > 0x7f) {
*p++ = '?'; /* replace non-ASCII */
while ((*src++ & 0xc0) == 0x80) ;
} else
*p++ = ch;
}
*p = 0;
return p - dst;
}
/*
* Return byte-index of the N-th UTF-8 character in UTF-8 string S.
* If S doesn't have that many characters, return its length instead.
*/
int
ufindpfx(char *s, int n)
{
int i = 0;
while (n && s[i])
{
if ((s[i++] & 0xc0) == 0xc0)
while ((s[i] & 0xc0) == 0x80)
i++;
--n;
}
return i;
}