/* flags.c - flag manipulation routines */

#include "copyright.h"

#include <stdio.h>
#include <ctype.h>
#include <string.h>

#include "db.h"
#include "mudconf.h"
#include "externs.h"
#include "command.h"
#include "flags.h"

/* ---------------------------------------------------------------------------
 * fh_any: set or clear indicated bit, no security checking
 */

int fh_any (dbref target, dbref player, int flag, int reset)
{
	if (reset)
		s_Flags(target, Flags(target) & ~flag);
	else
		s_Flags(target, Flags(target) | flag);
	return 1;
}

/* ---------------------------------------------------------------------------
 * fh_od: only GOD may set or clear the bit
 */

int fh_god (dbref target, dbref player, int flag, int reset)
{
	if (!God(player)) return 0;
	return (fh_any(target, player, flag, reset));
}

/* ---------------------------------------------------------------------------
 * fh_wiz: only WIZARDS (or GOD) may set or clear the bit
 */

int fh_wiz (dbref target, dbref player, int flag, int reset)
{
	if (!Wizard(player) & !God(player)) return 0;
	return (fh_any(target, player, flag, reset));
}

/* ---------------------------------------------------------------------------
 * fh_player: only players may set or clear this bit.
 */

int fh_player (dbref target, dbref player, int flag, int reset)
{
	if (Typeof(player) != TYPE_PLAYER) return 0;
	return (fh_any(target, player, flag, reset));
}

/* ---------------------------------------------------------------------------
 * fh_wiz_bit: Only GOD may set/clear this bit on others.
 */

int fh_wiz_bit (dbref target, dbref player, int flag, int reset)
{
	if (!God(player)) return 0;
	if (God(target) && reset) {
		notify(player, "You cannot make yourself mortal.");
		return 0;
	}
	return (fh_any(target, player, flag, reset));
}

/* ---------------------------------------------------------------------------
 * fh_dark_bit: manipulate the dark bit. Nonwizards may not set on players.
 */

int fh_dark_bit (dbref target, dbref player, int flag, int reset)
{
	if (!reset && (Typeof(target) == TYPE_PLAYER) &&
		(!Wizard(player) && !God(player))) return 0;
	return (fh_any(target, player, flag, reset));
}

/* ---------------------------------------------------------------------------
 * fh_going_bit: manipulate the going bit.  Non-gods may only clear on rooms.
 */

int fh_going_bit (dbref target, dbref player, int flag, int reset)
{
	if ((Typeof(target) == TYPE_ROOM) && (Flags(target) & GOING) && reset) {
		notify(player, "Your room has been spared from destruction.");
		return (fh_any(target, player, flag, reset));
	}
	if (!God(player)) return 0;
	return (fh_any(target, player, flag, reset));
}

/* ---------------------------------------------------------------------------
 * fh_puppet_bit: set or clear the puppet bit (extra feedback)
 */

int fh_hearing_bit (dbref target, dbref player, int flag, int reset)
{
int	could_hear;

	could_hear = Hearer(target);
	fh_any(target, player, flag, reset);
	handle_ears(target, could_hear, Hearer(target));
	return 1;
}

FLAGENT gen_flags[] = { 
{(char *)"WIZARD",	WIZARD,		'W', 0,		fh_wiz_bit},
{(char *)"LINK_OK",	LINK_OK,	'L', 0,		fh_any},
{(char *)"DARK",	DARK,		'D', 0,		fh_dark_bit},
{(char *)"STICKY",	STICKY,		'S', 0,		fh_any},
{(char *)"HAVEN",	HAVEN,		'H', 0,		fh_any},
{(char *)"QUIET",	QUIET,		'Q', 0,		fh_any},
{(char *)"HALTED",	HALT,		'h', 0,		fh_any},
{(char *)"GOING",	GOING,		'G', 0,		fh_going_bit},
{(char *)"PUPPET",	PUPPET,		'p', 0,		fh_hearing_bit},
{(char *)"CHOWN_OK",	CHOWN_OK,	'C', 0,		fh_any},
{(char *)"ENTER_OK",	ENTER_OK,	'e', 0,		fh_any},
{(char *)"MONITOR",	MONITOR,	'M', 0,		fh_hearing_bit},
{(char *)"VISUAL",	VISUAL,		'V', 0,		fh_any},
{(char *)"IMMORTAL",	IMMORTAL,	'i', 0,		fh_wiz},
{(char *)"STARTUP",	STARTUP,	'z', CA_WIZARD,	fh_god},
{(char *)"OPAQUE",	OPAQUE,		'O', 0,		fh_any},
{(char *)"VERBOSE",	VERBOSE,	'v', 0,		fh_any},
{(char *)"INHERIT",	INHERIT,	'I', 0,		fh_player},
{(char *)"NOSPOOF",	NOSPOOF,	'N', 0,		fh_any},
{(char *)"AUDIBLE",	HEARTHRU,	'a', 0,		fh_hearing_bit},
{ NULL,			0,		' ', 0,		NULL}};

FLAGENT exit_flags[] = {
{(char *)"KEY",		EXIT_KEY,	'K', 0,		fh_any},
{(char *)"ROBOT",	EXIT_ROBOT,	'r', 0,		fh_any},
{(char *)"SAFE",	EXIT_SAFE,	's', 0,		fh_any},
{(char *)"TRANSPARENT",	EXIT_SEETHRU,	't', 0,		fh_any},
{ NULL,			0,		' ', 0,		NULL}};

FLAGENT thing_flags[] = {
{(char *)"KEY",		THING_KEY,	'K', 0,		fh_any},
{(char *)"ROBOT",	THING_ROBOT,	'r', 0,		fh_wiz},
{(char *)"DESTROY_OK",	THING_DEST_OK,	'd', 0,		fh_any},
{(char *)"SAFE",	THING_SAFE,	's', 0,		fh_any},
{ NULL,			0,		' ', 0,		NULL}};

FLAGENT player_flags[] = {
{(char *)"BUILDER",	PLAYER_BUILD,	'B', 0,		fh_wiz},
{(char *)"GAGGED",	PLAYER_GAGGED,	'g', 0,		fh_wiz},
{(char *)"CONNECTED",	PLAYER_CONNECT,	'c', 0,		fh_god},
{(char *)"ROBOT",	PLAYER_ROBOT,	'r', 0,		fh_wiz},
{(char *)"SLAVE",	PLAYER_SLAVE,	's', 0,		fh_wiz},
{(char *)"UNFINDABLE",	PLAYER_UNFIND,	'U', 0,		fh_any},
{(char *)"SUSPECT",	PLAYER_SUSPECT,	'u', CA_WIZARD,	fh_wiz},
{ NULL,			0,		' ', 0,		NULL}};

FLAGENT room_flags[] = {
{(char *)"FLOATING",	ROOM_FLOATING,	'F', 0,		fh_any},
{(char *)"TEMPLE",	ROOM_TEMPLE,	'T', 0,		fh_wiz},
{(char *)"ABODE",	ROOM_ABODE,	'A', 0,		fh_any},
{(char *)"JUMP_OK",	ROOM_JUMP_OK,	'J', 0,		fh_any},
{(char *)"SAFE",	ROOM_SAFE,	's', 0,		fh_any},
{ NULL,			0,		' ', 0,		NULL}};

OBJENT object_types[8] = {
{(char *)"ROOM",	'R',	CA_PUBLIC,	room_flags,
	&mudstate.r_flags_htab},
{(char *)"THING",	' ',	CA_PUBLIC,	thing_flags,
	&mudstate.t_flags_htab},
{(char *)"EXIT",	'E',	CA_PUBLIC,	exit_flags,
	&mudstate.e_flags_htab},
{(char *)"PLAYER",	'P',	CA_PUBLIC,	player_flags,
	&mudstate.p_flags_htab},
{(char *)"FREE",	'X',	CA_WIZARD,	NULL,		NULL},
{(char *)"LOST",	'Z',	CA_WIZARD,	NULL,		NULL},
{(char *)"TYPE6",	'-',	CA_GOD,		NULL,		NULL},
{(char *)"TYPE7",	'-',	CA_GOD,		NULL,		NULL}};

/* ---------------------------------------------------------------------------
 * init_flagtab: initialize flag hash tables.
 */

void init_flagtab()
{
FLAGENT	*fp;
char	*nbuf, *np, *bp;

	hashinit(&mudstate.p_flags_htab, 67);
	hashinit(&mudstate.t_flags_htab, 67);
	hashinit(&mudstate.r_flags_htab, 67);
	hashinit(&mudstate.e_flags_htab, 67);

	nbuf=alloc_sbuf("init_flagtab");
	for (fp=gen_flags; fp->flagname; fp++) {
		for (np=nbuf,bp=fp->flagname; *bp; np++,bp++) *np=ToLower(*bp);
		*np='\0';
		hashadd(nbuf, (int *)fp, &mudstate.p_flags_htab);
		hashadd(nbuf, (int *)fp, &mudstate.t_flags_htab);
		hashadd(nbuf, (int *)fp, &mudstate.r_flags_htab);
		hashadd(nbuf, (int *)fp, &mudstate.e_flags_htab);
	}
	for (fp=player_flags; fp->flagname; fp++) {
		for (np=nbuf,bp=fp->flagname; *bp; np++,bp++) *np=ToLower(*bp);
		*np='\0';
		hashadd(nbuf, (int *)fp, &mudstate.p_flags_htab);
		}
	for (fp=thing_flags; fp->flagname; fp++) {
		for (np=nbuf,bp=fp->flagname; *bp; np++,bp++) *np=ToLower(*bp);
		*np='\0';
		hashadd(nbuf, (int *)fp, &mudstate.t_flags_htab);
		}
	for (fp=room_flags; fp->flagname; fp++) {
		for (np=nbuf,bp=fp->flagname; *bp; np++,bp++) *np=ToLower(*bp);
		*np='\0';
		hashadd(nbuf, (int *)fp, &mudstate.r_flags_htab);
		}
	for (fp=exit_flags; fp->flagname; fp++) {
		for (np=nbuf,bp=fp->flagname; *bp; np++,bp++) *np=ToLower(*bp);
		*np='\0';
		hashadd(nbuf, (int *)fp, &mudstate.e_flags_htab);
	}
	free_sbuf(nbuf);
}

/* ---------------------------------------------------------------------------
 * display_flagtable: display available flags.
 */

void display_flagtab (dbref player, FLAGENT *flagp, const char *prefix)
{
char	*buf, *bp;
FLAGENT *fp;

	bp = buf = alloc_lbuf("display_flagtab");
	safe_str((char *)prefix, buf, &bp);
	for (fp=flagp; fp->flagname; fp++) {
		if ((fp->listperm & CA_WIZARD) && !Wizard(player)) continue;
		if ((fp->listperm & CA_GOD) && !God(player)) continue;
		safe_chr(' ', buf, &bp);
		safe_str(fp->flagname, buf, &bp);
		safe_chr('(', buf, &bp);
		safe_chr(fp->flaglett, buf, &bp);
		safe_chr(')', buf, &bp);
	}
	*bp='\0';
	notify(player, buf);
	free_lbuf(buf);
}

FLAGENT *find_flag(dbref thing, char *flagname)
{
HASHTAB	*hp;
char	*cp;

	/* Make sure that the object type can have flags */

	hp = object_types[Typeof(thing)].flaghtab;
	if (hp == NULL)
		return NULL;

	/* Make sure the flag name is valid */

	for (cp=flagname; *cp; cp++) *cp = ToLower(*cp);
	return (FLAGENT *)hashfind(flagname, hp);
}

/* ---------------------------------------------------------------------------
 * flag_set: Set or clear a specified flag on an object. 
 */

void flag_set (dbref target, dbref player, char *flag)
{
FLAGENT	*fp;
int	negate, result;

	/* Trim spaces, and handle the negation character */

	negate = 0;
	while (*flag && isspace(*flag)) flag++;
	if (*flag == '!') {
		negate = 1;
		flag++;
	}
	while (*flag && isspace(*flag)) flag++;

	/* Make sure a flag name was specified */

	if (*flag == '\0') {
		if (negate)
			notify(player, "You must specify a flag to clear.");
		else
			notify(player, "You must specify a flag to set.");
		return;
	}

	fp = find_flag(target, flag);
	if (fp == NULL) {
		notify(player, "I don't understand that flag.");
		return;
	}

	/* Invoke the flag handler, and print feedback */

	result = fp->handler(target, player, fp->flagvalue, negate);
	if (!result)
		notify(player, "Permission denied.");
	else if (!Quiet(player))
		notify(player, (negate ? "Cleared." : "Set."));
	return;
}

/* ---------------------------------------------------------------------------
 * decode_flags: converts a flags word into corresponding letters.
 */

char *decode_flags (dbref player, FLAG flagword, int flagtype)
{
char	*buf, *bp;
FLAGENT	*fp, *tfp;
int	operm;

	buf = bp = alloc_sbuf("decode_flags");
	*bp = '\0';

        if (!Good_obj(player)) {
		STARTLOG(LOG_PROBLEMS,"OBJ","RANGE")
			bp = alloc_mbuf("decode_flags.LOG");
			sprintf(bp, "Object number (#%d) out of range in decode_flags",
				player);
			log_text(bp);
			free_mbuf(bp);
		ENDLOG
		strcpy(buf, "#-2 ERROR");
		return buf;
	}

	operm = object_types[flagtype].perm;
	if (!((operm == CA_PUBLIC) ||
	    (operm == CA_WIZARD && Wizard(player)) ||
	    (operm == CA_GOD && God(player)))) return buf;

	if (object_types[flagtype].lett != ' ')
		safe_chr(object_types[flagtype].lett, buf, &bp);

	for (fp=gen_flags; fp->flagname; fp++) {
		if (flagword & fp->flagvalue) {
			if ((fp->listperm & CA_WIZARD) && !Wizard(player))
				continue;
			if ((fp->listperm & CA_GOD) && !God(player))
				continue;
			safe_chr(fp->flaglett, buf, &bp);
		}
	}

	tfp = object_types[flagtype].flaglist;
	if (tfp != NULL) for (fp=tfp; fp->flagname; fp++) {
		if (flagword & fp->flagvalue) {
			if ((fp->listperm & CA_WIZARD) && !Wizard(player))
				continue;
			if ((fp->listperm & CA_GOD) && !God(player))
				continue;
			/* don't show CONNECT flag on dark wizards to mortals */
			if ((flagtype == TYPE_PLAYER) &&
			    (fp->flagvalue == PLAYER_CONNECT) &&
			    ((flagword & (WIZARD|DARK)) == (WIZARD|DARK)) &&
			    !Wizard(player))
				continue;
			safe_chr(fp->flaglett, buf, &bp);
		}
	}

	*bp = '\0';
	return buf;
}

/* ---------------------------------------------------------------------------
 * flag_description: Return an mbuf containing the type and flags on thing.
 */

char *flag_description(dbref player, dbref target)
{
char	*buff, *bp;
FLAGENT	*fp, *tfp;
int	otype;

	/* Allocate the return buffer */

	otype = Typeof(target);
	bp = buff = alloc_mbuf("flag_description");

	/* Store the header strings and object type */

	safe_str((char *)"Type: ", buff, &bp);
	safe_str(object_types[otype].name, buff, &bp);
	safe_str((char *)" Flags:", buff, &bp);
	if (object_types[otype].perm != CA_PUBLIC) {
		*bp = '\0';
		return buff;
	}

	/* Store the type-invariant flags */

	for (fp=gen_flags; fp->flagname; fp++) {
		if (Flags(target) & fp->flagvalue) {
			if ((fp->listperm & CA_WIZARD) && !Wizard(player))
				continue;
			if ((fp->listperm & CA_GOD) && !God(player))
				continue;
			safe_chr(' ', buff, &bp);
			safe_str(fp->flagname, buff, &bp);
		}
	}

	/* Store the type-specific flags */

	tfp = object_types[otype].flaglist;
	if (tfp != NULL) for (fp=tfp; fp->flagname; fp++) {
		if (Flags(target) & fp->flagvalue) {
			if ((fp->listperm & CA_WIZARD) && !Wizard(player))
				continue;
			if ((fp->listperm & CA_GOD) && !God(player))
				continue;
			/* don't show CONNECT flag on dark wizards to mortals */
			if ((otype == TYPE_PLAYER) &&
			    (fp->flagvalue == PLAYER_CONNECT) &&
			    ((Flags(target) & (WIZARD|DARK)) == (WIZARD|DARK)) &&
			    !Wizard(player))
				continue;
			safe_chr(' ', buff, &bp);
			safe_str(fp->flagname, buff, &bp);
		}
	}

	/* Terminate the string, and return the buffer to the caller */

	*bp = '\0';
	return buff;
}

/* ---------------------------------------------------------------------------
 * Return an lbuf containing the name and number of an object
 */

char *unparse_object_numonly(dbref target)
{
char	*buf;

	buf=alloc_lbuf("unparse_object_numonly");
	if (target == NOTHING) {
		strcpy(buf, "*NOTHING*");
	} else if (target == HOME) {
		strcpy(buf, "*HOME*");
	} else if (!Good_obj(target)) {
		sprintf(buf, "*ILLEGAL*(#%d)", target);
	} else {
		sprintf(buf, "%s(#%d)", Name(target), target);
	}
	return buf;
}

/* ---------------------------------------------------------------------------
 * Return an lbuf pointing to the object name and possibly the db# and flags
 */

char *unparse_object(dbref player, dbref target)
{
char	*buf, *fp;

	buf=alloc_lbuf("unparse_object");
	if (target == NOTHING) {
		strcpy(buf, "*NOTHING*");
	} else if (target == HOME) {
		strcpy(buf, "*HOME*");
	} else if (!Good_obj(target)) {
		sprintf(buf, "*ILLEGAL*(#%d)", target);
	} else {
		if (Examinable(player, target) ||
		    Linkable(player, target) ||
		    Abode(target) ||
		    (Flags(target) & CHOWN_OK) ||
		    (IS(target, TYPE_THING, THING_DEST_OK)) ||
		    (IS(target, TYPE_ROOM, ROOM_JUMP_OK))) {

			/* show everything */
			fp = unparse_flags(player, target);
			sprintf(buf, "%s(#%d%s)", Name(target), target, fp);
			free_sbuf(fp);
		} else {
			/* show only the name. */
			strcpy(buf, Name(target));
		}
	}
	return buf;
}