/* predicates.c */

#include "autoconf.h"
#include "copyright.h"
#ifndef	lint
static char *RCSid = "$Id: predicates.c,v 1.8 1995/03/21 00:01:03 ambar Exp $";
USE(RCSid);
#endif

#include "interface.h"
#include "match.h"
#include "command.h"
#include "attrs.h"

extern dbref FDECL(match_thing, (dbref, char *));
extern void FDECL(load_quota, (int *, dbref, int));
extern void FDECL(save_quota, (int *, dbref, int));
static int FDECL(type_quota, (int));
static int FDECL(pay_quota, (dbref, int, int));
static void FDECL(add_quota, (dbref, int, int));

#ifdef NEED_VSPRINTF_DCL
extern char *FDECL(vsprintf, (char *, char *, va_list));
#endif

#if defined(__STDC__) && defined(STDC_HEADERS)
char *
tprintf(char *format,...)
#else
char *
tprintf(va_alist)
    va_dcl

#endif
{
    static char buff[10000];
    va_list ap;

#if defined(__STDC__) && defined(STDC_HEADERS)
    va_start(ap, format);
#else
    char *format;

    va_start(ap);
    format = va_arg(ap, char *);

#endif
    vsprintf(buff, format, ap);
    va_end(ap);
    buff[9999] = '\0';
    return buff;
}

/* ---------------------------------------------------------------------------
 * insert_first, remove_first: Insert or remove objects from lists.
 */

dbref 
insert_first(head, thing)
    dbref head, thing;
{
    s_Next(thing, head);
    return thing;
}

dbref 
remove_first(head, thing)
    dbref head, thing;
{
    dbref prev;

    if (head == thing)
	return (Next(thing));

    DOLIST(prev, head) {
	if (Next(prev) == thing) {
	    s_Next(prev, Next(thing));
	    return head;
	}
    }
    return head;
}

/* ---------------------------------------------------------------------------
 * reverse_list: Reverse the order of members in a list.
 */

dbref 
reverse_list(list)
    dbref list;
{
    dbref newlist, rest;

    newlist = NOTHING;
    while (list != NOTHING) {
	rest = Next(list);
	s_Next(list, newlist);
	newlist = list;
	list = rest;
    }
    return newlist;
}

/* ---------------------------------------------------------------------------
 * member - indicate if thing is in list
 */

int 
member(thing, list)
    dbref thing, list;
{
    DOLIST(list, list) {
	if (list == thing)
	    return 1;
    }
    return 0;
}

/* ---------------------------------------------------------------------------
 * is_integer, is_number: see if string contains just a number.
 */

int 
is_integer(str)
    char *str;
{
    while (*str && isspace(*str))
	str++;			/* Leading spaces */
    if (*str == '-') {		/* Leading minus */
	str++;
	if (!*str)
	    return 0;		/* but not if just a minus */
    }
    if (!isdigit(*str))		/* Need at least 1 integer */
	return 0;
    while (*str && isdigit(*str))
	str++;			/* The number (int) */
    while (*str && isspace(*str))
	str++;			/* Trailing spaces */
    return (*str ? 0 : 1);
}

int 
is_number(str)
    char *str;
{
    int got_one;

    while (*str && isspace(*str))
	str++;			/* Leading spaces */
    if (*str == '-') {		/* Leading minus */
	str++;
	if (!*str)
	    return 0;		/* but not if just a minus */
    }
    got_one = 0;
    if (isdigit(*str))
	got_one = 1;		/* Need at least one digit */
    while (*str && isdigit(*str))
	str++;			/* The number (int) */
    if (*str == '.')
	str++;			/* decimal point */
    if (isdigit(*str))
	got_one = 1;		/* Need at least one digit */
    while (*str && isdigit(*str))
	str++;			/* The number (fract) */
    while (*str && isspace(*str))
	str++;			/* Trailing spaces */
    return ((*str || !got_one) ? 0 : 1);
}

#ifndef STANDALONE

int 
could_doit(player, thing, locknum)
    dbref player, thing;
    int locknum;
{
    char *key;
    dbref aowner;
    int aflags, doit;

    /* no if nonplayer trys to get key */

    if (!isPlayer(player) && Key(thing)) {
	return 0;
    }
    key = atr_get(thing, locknum, &aowner, &aflags);
    doit = eval_boolexp_atr(player, thing, thing, key);
    free_lbuf(key);
    return doit;
}

int 
can_see(player, thing, can_see_loc)
    dbref player, thing;
    int can_see_loc;
{
    /* Don't show if all the following apply:
     * Sleeping players should not be seen.
     * The thing is a disconnected player.
     * The player is not a puppet.
     */

    if (mudconf.dark_sleepers && isPlayer(thing) &&
	!Connected(thing) && !Puppet(thing)) {
	return 0;
    }
    /* You don't see yourself or exits */

    if ((player == thing) || isExit(thing)) {
	return 0;
    }
    /* If loc is not dark, you see it if it's not dark or you control it.
     * If loc is dark, you see it if you control it.  Seeing your own
     * dark objects is controlled by mudconf.see_own_dark.
     * In dark locations, you also see things that are LIGHT and !DARK.
     */

    if (can_see_loc) {
	return (!Dark(thing) ||
		(mudconf.see_own_dark && MyopicExam(player, thing)));
    } else {
	return ((Light(thing) && !Dark(thing)) ||
		(mudconf.see_own_dark && MyopicExam(player, thing)));
    }
}

static int 
canpayquota(player, who, cost, objtype)
    dbref player, who;
    int cost, objtype;
{
    dbref aowner;
    char *quota_str;
    int aflags;
    register int quota, qtype;
    int q_list[5];

    /* If no cost, succeed */

    if (cost <= 0)
	return 1;

#ifndef STANDALONE
    /* determine basic quota */

    load_quota(q_list, Owner(who), A_RQUOTA);
    quota = q_list[QTYPE_ALL];

    /* enough to build?  Wizards always have enough. */

    quota -= cost;
    if ((quota < 0) && !Wizard(who) && !Wizard(Owner(who)))
	return 0;

    if (mudconf.typed_quotas) {
	quota = q_list[type_quota(objtype)];
	if ((quota <= 0) && !Wizard(player) && !Wizard(Owner(player)))
	    return 0;
    }
#endif				/* ! STANDALONE */

    return 1;
}

int 
canpayfees(player, who, pennies, quota, objtype)
    dbref player, who;
    int pennies, quota, objtype;
{
    if (!Wizard(who) && !Wizard(Owner(who)) &&
	!Immortal(who) && !Immortal(Owner(who)) &&
	(Pennies(Owner(who)) < pennies)) {
	if (player == who) {
	    notify(player,
		   tprintf("Sorry, you don't have enough %s.",
			   mudconf.many_coins));
	} else {
	    notify(player,
		   tprintf("Sorry, that player doesn't have enough %s.",
			   mudconf.many_coins));
	}
	return 0;
    }
    if (mudconf.quotas) {
	if (!canpayquota(player, who, quota, objtype)) {
	    if (player == who) {
		notify(player,
		       "Sorry, your building contract has run out.");
	    } else {
		notify(player,
		     "Sorry, that player's building contract has run out.");
	    }
	    return 0;
	}
    }
    return 1;
}

#endif /* STANDALONE */

static int 
type_quota(objtype)
    int objtype;
{
    int qtype;

    /* determine typed quota */

    switch (objtype) {
    case TYPE_ROOM:
	qtype = QTYPE_ROOM;
	break;
    case TYPE_EXIT:
	qtype = QTYPE_EXIT;
	break;
    case TYPE_PLAYER:
	qtype = QTYPE_PLAYER;
	break;
    default:
	qtype = QTYPE_THING;
    }
    return (qtype);
}

int 
payfees(who, pennies, quota, objtype)
    dbref who;
    int pennies, quota, objtype;
{
    /* You /must/ have called canpayfees() first.  If not, your
     * database will be eaten by rabid squirrels. */
    if (mudconf.quotas)
	pay_quota(who, quota, objtype);
    return payfor(who, pennies);
}

static int 
pay_quota(who, cost, objtype)
    dbref who;
    int cost, objtype;
{
    /* If no cost, succeed.  Negative costs /must/ be managed, however */

    if (cost == 0)
	return 1;

    add_quota(who, -cost, type_quota(objtype));

    return 1;
}

int 
payfor(who, cost)
    dbref who;
    int cost;
{
    dbref tmp;

    if (Wizard(who) || Wizard(Owner(who)) ||
	Immortal(who) || Immortal(Owner(who))) {
	return 1;
    }
    who = Owner(who);
    if ((tmp = Pennies(who)) >= cost) {
	s_Pennies(who, tmp - cost);
	return 1;
    }
    return 0;
}

static void 
add_quota(who, payment, type)
    dbref who;
    int payment, type;
{
#ifndef STANDALONE
    dbref aowner;
    int aflags;
    char buf[20], *quota;
    int q_list[5];

    load_quota(q_list, Owner(who), A_RQUOTA);
    q_list[QTYPE_ALL] += payment;

    if (mudconf.typed_quotas)
	q_list[type] += payment;

    save_quota(q_list, Owner(who), A_RQUOTA);
#endif				/* ! STANDALONE */
}

void 
giveto(who, pennies)
    dbref who;
    int pennies;
{
    if (Wizard(who) || Wizard(Owner(who)) ||
	Immortal(who) || Immortal(Owner(who))) {
	return;
    }
    who = Owner(who);
    s_Pennies(who, Pennies(who) + pennies);
}

int 
ok_name(name)
    const char *name;
{
    const char *cp;

    /* Disallow leading spaces */

    if (isspace(*name))
	return 0;

    /* Only printable characters */

    for (cp = name; cp && *cp; cp++) {
	if (!isprint(*cp))
	    return 0;
    }

    /* Disallow trailing spaces */
    cp--;
    if (isspace(*cp))
	return 0;

    /* Exclude names that start with or contain certain magic cookies */

    return (name &&
	    *name &&
	    *name != LOOKUP_TOKEN &&
	    *name != NUMBER_TOKEN &&
	    *name != NOT_TOKEN &&
	    !index(name, ARG_DELIMITER) &&
	    !index(name, AND_TOKEN) &&
	    !index(name, OR_TOKEN) &&
	    string_compare(name, "me") &&
	    string_compare(name, "home") &&
	    string_compare(name, "here"));
}

int 
ok_player_name(name)
    const char *name;
{
    const char *cp, *good_chars;

    /* No leading spaces */

    if (isspace(*name))
	return 0;

    /* Not too long and a good name for a thing */

    if (!ok_name(name) || (strlen(name) >= PLAYER_NAME_LIMIT))
	return 0;

#ifndef STANDALONE
    if (mudconf.name_spaces)
	good_chars = " `$_-.,'";
    else
	good_chars = "`$_-.,'";
#else
    good_chars = " `$_-.,'";
#endif
    /* Make sure name only contains legal characters */

    for (cp = name; cp && *cp; cp++) {
	if (isalnum(*cp))
	    continue;
	if (!index(good_chars, *cp))
	    return 0;
    }
    return 1;
}

int 
ok_attr_name(attrname)
    const char *attrname;
{
    const char *scan;

    if (!isalpha(*attrname))
	return 0;
    for (scan = attrname; *scan; scan++) {
	if (isalnum(*scan))
	    continue;
	if (!(index("-_.@#$^&~=+", *scan)))
	    return 0;
    }
    return 1;
}

int 
ok_password(password)
    const char *password;
{
    const char *scan;

    if (*password == '\0')
	return 0;

    for (scan = password; *scan; scan++) {
	if (!(isprint(*scan) && !isspace(*scan))) {
	    return 0;
	}
    }

    /* Needed.  Change it if you like, but be sure yours is the same. */
    if ((strlen(password) == 13) &&
	(password[0] == 'X') &&
	(password[1] == 'X'))
	return 0;

    return 1;
}

#ifndef STANDALONE

/* ---------------------------------------------------------------------------
 * handle_ears: Generate the 'grows ears' and 'loses ears' messages.
 */

void 
handle_ears(thing, could_hear, can_hear)
    dbref thing;
    int could_hear, can_hear;
{
    char *buff, *bp;
    int gender;

    if (!could_hear && can_hear) {
	buff = alloc_lbuf("handle_ears.grow");
	strcpy(buff, Name(thing));
	if (isExit(thing)) {
	    for (bp = buff; *bp && (*bp != ';'); bp++);
	    *bp = '\0';
	}
	gender = get_gender(thing);
	notify_check(thing, thing,
		     tprintf("%s %s now listening.",
			     buff, (gender == 4) ? "are" : "is"),
		     (MSG_ME | MSG_NBR | MSG_LOC | MSG_INV));
	free_lbuf(buff);
    } else if (could_hear && !can_hear) {
	buff = alloc_lbuf("handle_ears.lose");
	strcpy(buff, Name(thing));
	if (isExit(thing)) {
	    for (bp = buff; *bp && (*bp != ';'); bp++);
	    *bp = '\0';
	}
	gender = get_gender(thing);
	notify_check(thing, thing,
		     tprintf("%s %s no longer listening.",
			     buff, (gender == 4) ? "are" : "is"),
		     (MSG_ME | MSG_NBR | MSG_LOC | MSG_INV));
	free_lbuf(buff);
    }
}

/* for lack of better place the @switch code is here */

void 
do_switch(player, cause, key, expr, args, nargs, cargs, ncargs)
    dbref player, cause;
    int key, nargs, ncargs;
    char *expr, *args[], *cargs[];
{
    int a, any;
    char *buff;

    if (!expr || (nargs <= 0))
	return;

    if (key == SWITCH_DEFAULT) {
	if (mudconf.switch_df_all)
	    key = SWITCH_ANY;
	else
	    key = SWITCH_ONE;
    }
    /* now try a wild card match of buff with stuff in coms */

    any = 0;
    for (a = 0; (a < (nargs - 1)) && args[a] && args[a + 1]; a += 2) {
	buff = exec(player, cause, EV_FCHECK | EV_EVAL | EV_TOP, args[a],
		    cargs, ncargs);
	if (wild_match(buff, expr, (char **) NULL, 0, 1)) {
	    wait_que(player, cause, 0, NOTHING, args[a + 1],
		     cargs, ncargs, mudstate.global_regs);
	    if (key == SWITCH_ONE) {
		free_lbuf(buff);
		return;
	    }
	    any = 1;
	}
	free_lbuf(buff);
    }
    if ((a < nargs) && !any && args[a])
	wait_que(player, cause, 0, NOTHING, args[a], cargs, ncargs,
		 mudstate.global_regs);
}

/* ---------------------------------------------------------------------------
 * do_comment: Implement the @@ (comment) command. Very cpu-intensive :-)
 */

void 
do_comment(player, cause, key)
    dbref player, cause;
    int key;
{
}

static dbref 
promote_dflt(old, new)
    dbref old, new;
{
    switch (new) {
    case NOPERM:
	return NOPERM;
    case AMBIGUOUS:
	if (old == NOPERM)
	    return old;
	else
	    return new;
    }

    if ((old == NOPERM) || (old == AMBIGUOUS))
	return old;

    return NOTHING;
}

dbref 
match_possessed(player, thing, target, dflt, check_enter)
    dbref player, thing, dflt;
    char *target;
    int check_enter;
{
    dbref result, result1;
    int control;
    char *buff, *start, *place, *s1, *d1, *temp;

    /* First, check normally */

    if (Good_obj(dflt))
	return dflt;

    /* Didn't find it directly.  Recursively do a contents check */

    start = target;
    while (*target) {

	/* Fail if no ' characters */

	place = target;
	target = (char *) index(place, '\'');
	if ((target == NULL) || !*target)
	    return dflt;

	/* If string started with a ', skip past it */

	if (place == target) {
	    target++;
	    continue;
	}
	/* If next character is not an s or a space, skip past */

	temp = target++;
	if (!*target)
	    return dflt;
	if ((*target != 's') && (*target != 'S') && (*target != ' '))
	    continue;

	/* If character was not a space make sure the following
	 * character is a space.
	 */

	if (*target != ' ') {
	    target++;
	    if (!*target)
		return dflt;
	    if (*target != ' ')
		continue;
	}
	/* Copy the container name to a new buffer so we can
	 * terminate it.
	 */

	buff = alloc_lbuf("is_posess");
	for (s1 = start, d1 = buff; *s1 && (s1 < temp); *d1++ = (*s1++));
	*d1 = '\0';

	/* Look for the container here and in our inventory.  Skip
	 * past if we can't find it.
	 */

	init_match(thing, buff, NOTYPE);
	if (player == thing) {
	    match_neighbor();
	    match_possession();
	} else {
	    match_possession();
	}
	result1 = match_result();

	free_lbuf(buff);
	if (!Good_obj(result1)) {
	    dflt = promote_dflt(dflt, result1);
	    continue;
	}
	/* If we don't control it and it is either dark or opaque,
	 * skip past.
	 */

	control = Controls(player, result1);
	if ((Dark(result1) || Opaque(result1)) && !control) {
	    dflt = promote_dflt(dflt, NOTHING);
	    continue;
	}
	/* Validate object has the ENTER bit set, if requested */

	if ((check_enter) && !Enter_ok(result1) && !control) {
	    dflt = promote_dflt(dflt, NOPERM);
	    continue;
	}
	/* Look for the object in the container */

	init_match(result1, target, NOTYPE);
	match_possession();
	result = match_result();
	result = match_possessed(player, result1, target, result,
				 check_enter);
	if (Good_obj(result))
	    return result;
	dflt = promote_dflt(dflt, result);
    }
    return dflt;
}

/* ---------------------------------------------------------------------------
 * parse_range: break up <what>,<low>,<high> syntax
 */

void 
parse_range(name, low_bound, high_bound)
    char **name;
    dbref *low_bound, *high_bound;
{
    char *buff1, *buff2;

    buff1 = *name;
    if (buff1 && *buff1)
	*name = parse_to(&buff1, ',', EV_STRIP_TS);
    if (buff1 && *buff1) {
	buff2 = parse_to(&buff1, ',', EV_STRIP_TS);
	if (buff1 && *buff1) {
	    while (*buff1 && isspace(*buff1))
		buff1++;
	    if (*buff1 == NUMBER_TOKEN)
		buff1++;
	    *high_bound = atoi(buff1);
	    if (*high_bound >= mudstate.db_top)
		*high_bound = mudstate.db_top - 1;
	} else {
	    *high_bound = mudstate.db_top - 1;
	}
	while (*buff2 && isspace(*buff2))
	    buff2++;
	if (*buff2 == NUMBER_TOKEN)
	    buff2++;
	*low_bound = atoi(buff2);
	if (*low_bound < 0)
	    *low_bound = 0;
    } else {
	*low_bound = 0;
	*high_bound = mudstate.db_top - 1;
    }
}

int 
parse_thing_slash(player, thing, after, it)
    dbref player, *it;
    char *thing, **after;
{
    char *str;

    /* get name up to / */
    for (str = thing; *str && (*str != '/'); str++);

    /* If no / in string, return failure */

    if (!*str) {
	*after = NULL;
	*it = NOTHING;
	return 0;
    }
    *str++ = '\0';
    *after = str;

    /* Look for the object */

    init_match(player, thing, NOTYPE);
    match_everything(MAT_EXIT_PARENTS);
    *it = match_result();

    /* Return status of search */

    return (Good_obj(*it));
}

extern NAMETAB lock_sw[];

int 
get_obj_and_lock(player, what, it, attr, errmsg)
    dbref player, *it;
    char *what, *errmsg;
    ATTR **attr;
{
    char *str, *tbuf;
    int anum;

    tbuf = alloc_lbuf("get_obj_and_lock");
    strcpy(tbuf, what);
    if (parse_thing_slash(player, tbuf, &str, it)) {

	/* <obj>/<lock> syntax, use the named lock */

	anum = search_nametab(player, lock_sw, str);
	if (anum == -1) {
	    free_lbuf(tbuf);
	    strcpy(errmsg, "#-1 LOCK NOT FOUND");
	    return 0;
	}
    } else {

	/* Not <obj>/<lock>, do a normal get of the default lock */

	*it = match_thing(player, what);
	if (!Good_obj(*it)) {
	    free_lbuf(tbuf);
	    strcpy(errmsg, "#-1 NOT FOUND");
	    return 0;
	}
	anum = A_LOCK;
    }

    /* Get the attribute definition, fail if not found */

    free_lbuf(tbuf);
    *attr = atr_num(anum);
    if (!(*attr)) {
	strcpy(errmsg, "#-1 LOCK NOT FOUND");
	return 0;
    }
    return 1;
}

#endif /* STANDALONE */

/* ---------------------------------------------------------------------------
 * where_is: Returns place where obj is linked into a list.
 * ie. location for players/things, source for exits, NOTHING for rooms.
 */

dbref 
where_is(what)
    dbref what;
{
    dbref loc;

    if (!Good_obj(what))
	return NOTHING;

    switch (Typeof(what)) {
    case TYPE_PLAYER:
    case TYPE_THING:
    case TYPE_ZONE:
	loc = Location(what);
	break;
    case TYPE_EXIT:
	loc = Exits(what);
	break;
    default:
	loc = NOTHING;
	break;
    }
    return loc;
}

/* ---------------------------------------------------------------------------
 * where_room: Return room containing player, or NOTHING if no room or
 * recursion exceeded.  If player is a room, returns itself.
 */

dbref 
where_room(what)
    dbref what;
{
    int count;

    for (count = mudconf.ntfy_nest_lim; count > 0; count--) {
	if (!Good_obj(what))
	    break;
	if (isRoom(what))
	    return what;
	if (!Has_location(what))
	    break;
	what = Location(what);
    }
    return NOTHING;
}

int 
locatable(player, it, cause)
    dbref player, it, cause;
{
    dbref loc_it, room_it;
    int findable_room;

    /* No sense if trying to locate a bad object */

    if (!Good_obj(it))
	return 0;

    loc_it = where_is(it);

    /* Succeed if we can examine the target, if we are the target,
     * if we can examine the location, if a wizard caused the lookup,
     * or if the target caused the lookup.
     */

    if (Examinable(player, it) ||
	(loc_it == player) ||
	((loc_it != NOTHING) &&
	 (Examinable(player, loc_it) || loc_it == where_is(player))) ||
	Wizard(cause) ||
	(it == cause))
	return 1;

    room_it = where_room(it);
    if (Good_obj(room_it))
	findable_room = !Hideout(room_it);
    else
	findable_room = 1;

    /* Succeed if we control the containing room or if the target is
     * findable and the containing room is not unfindable.
     */

    if (((room_it != NOTHING) && Examinable(player, room_it)) ||
	(Findable(it) && findable_room))
	return 1;

    /* We can't do it. */

    return 0;
}

/* ---------------------------------------------------------------------------
 * nearby: Check if thing is nearby player (in inventory, in same room, or
 * IS the room.
 */

int 
nearby(player, thing)
    dbref player, thing;
{
    int thing_loc, player_loc;

    if (!Good_obj(player) || !Good_obj(thing))
	return 0;
    thing_loc = where_is(thing);
    if (thing_loc == player)
	return 1;
    player_loc = where_is(player);
    if ((thing_loc == player_loc) || (thing == player_loc))
	return 1;
    return 0;
}

/* ---------------------------------------------------------------------------
 * next_exit: return next exit that is ok to see.
 */

dbref 
next_exit(player, this, exam_here)
    dbref player, this;
    int exam_here;
{
    if (isRoom(this))
	return NOTHING;
    if (isExit(this) && exam_here)
	return this;

    while ((this != NOTHING) && Dark(this) && !Light(this) &&
	   !Examinable(player, this))
	this = Next(this);

    return this;
}

#ifndef STANDALONE

/* ---------------------------------------------------------------------------
 * did_it: Have player do something to/with thing
 */

void 
did_it(player, thing, what, def, owhat, odef, awhat, args, nargs)
    dbref player, thing;
    int what, owhat, awhat, nargs;
    char *args[];
    const char *def, *odef;
{
    char *d, *buff, *act, *charges, *preserve[MAX_GLOBAL_REGS];
    dbref loc, aowner;
    int num, aflags, need_pres;

    /* If we need to call exec() from within this function, we first save
     * the state of the global registers, in order to avoid munging them
     * inappropriately. Do note that the restoration to their original
     * values occurs BEFORE the execution of the @a-attribute. Therefore,
     * any changing of setq() values done in the @-attribute and @o-attribute
     * will NOT be passed on. This prevents odd behaviors that result from
     * odd @verbs and so forth (the idea is to preserve the caller's control
     * of the global register values).
     */

    need_pres = 0;

    /* message to player */

    if (what > 0) {
	d = atr_pget(thing, what, &aowner, &aflags);
	if (*d) {
	    need_pres = 1;
	    save_global_regs("did_it_save", preserve);
	    buff = exec(thing, player, EV_EVAL | EV_FIGNORE | EV_TOP,
			d, args, nargs);
#ifdef PUEBLO_SUPPORT
	    if ((aflags & AF_HTML) && Html(player)) {
		char *buff_cp = buff + strlen(buff);
		safe_str("\r\n", buff, &buff_cp);
		notify_html(player, buff);
	    }
	    else
		notify(player, buff);
#else
	    notify(player, buff);
#endif /* PUEBLO_SUPPORT */
	    free_lbuf(buff);
	} else if (def) {
	    notify(player, def);
	}
	free_lbuf(d);
    } else if ((what < 0) && def) {
	notify(player, def);
    }

    /* message to neighbors */

    if ((owhat > 0) && Has_location(player) &&
	Good_obj(loc = Location(player))) {
	d = atr_pget(thing, owhat, &aowner, &aflags);
	if (*d) {
	    if (!need_pres) {
		need_pres = 1;
		save_global_regs("did_it_save", preserve);
	    }
	    buff = exec(thing, player, EV_EVAL | EV_FIGNORE | EV_TOP,
			d, args, nargs);
	    notify_except2(loc, player, player, thing,
			   tprintf("%s %s", Name(player), buff));
	    free_lbuf(buff);
	} else if (odef) {
	    notify_except2(loc, player, player, thing,
			   tprintf("%s %s", Name(player), odef));
	}
	free_lbuf(d);
    } else if ((owhat < 0) && def && Has_location(player) &&
	       Good_obj(loc = Location(player))) {
	if (odef) {
	    notify_except2(loc, player, player, thing,
			   tprintf("%s %s", Name(player), odef));
	}
    }

    /* If we preserved the state of the global registers, restore them. */

    if (need_pres)
	restore_global_regs("did_it_restore", preserve);

    /* do the action attribute */

    if (awhat > 0) {
	if (*(act = atr_pget(thing, awhat, &aowner, &aflags))) {
	    charges = atr_pget(thing, A_CHARGES, &aowner, &aflags);
	    if (*charges) {
		num = atoi(charges);
		if (num > 0) {
		    buff = alloc_sbuf("did_it.charges");
		    sprintf(buff, "%d", num - 1);
		    atr_add_raw(thing, A_CHARGES, buff);
		    free_sbuf(buff);
		} else if (*(buff = atr_pget(thing, A_RUNOUT, &aowner, &aflags))) {
		    free_lbuf(act);
		    act = buff;
		} else {
		    free_lbuf(act);
		    free_lbuf(buff);
		    free_lbuf(charges);
		    return;
		}
	    }
	    free_lbuf(charges);
	    wait_que(thing, player, 0, NOTHING, act, args, nargs,
		     mudstate.global_regs);
	}
	free_lbuf(act);
    }
}

/* ---------------------------------------------------------------------------
 * do_verb: Command interface to did_it.
 */

void 
do_verb(player, cause, key, victim_str, args, nargs)
    dbref player, cause;
    int key, nargs;
    char *victim_str, *args[];
{
    dbref actor, victim, aowner;
    int what, owhat, awhat, nxargs, restrict, aflags, i;
    ATTR *ap;
    const char *whatd, *owhatd;
    char *xargs[10];

    /* Look for the victim */

    if (!victim_str || !*victim_str) {
	notify(player, "Nothing to do.");
	return;
    }
    /* Get the victim */

    init_match(player, victim_str, NOTYPE);
    match_everything(MAT_EXIT_PARENTS);
    victim = noisy_match_result();
    if (!Good_obj(victim))
	return;

    /* Get the actor.  Default is my cause */

    if ((nargs >= 1) && args[0] && *args[0]) {
	init_match(player, args[0], NOTYPE);
	match_everything(MAT_EXIT_PARENTS);
	actor = noisy_match_result();
	if (!Good_obj(actor))
	    return;
    } else {
	actor = cause;
    }

    /* Check permissions.  There are two possibilities
     * 1: Player controls both victim and actor.  In this case victim runs
     *    his action list.
     * 2: Player controls actor.  In this case victim does not run his
     *    action list and any attributes that player cannot read from
     *    victim are defaulted.
     */

    if (!controls(player, actor)) {
	notify_quiet(player, "Permission denied,");
	return;
    }
    restrict = !controls(player, victim);

    what = -1;
    owhat = -1;
    awhat = -1;
    whatd = NULL;
    owhatd = NULL;
    nxargs = 0;

    /* Get invoker message attribute */

    if (nargs >= 2) {
	ap = atr_str(args[1]);
	if (ap && (ap->number > 0))
	    what = ap->number;
    }
    /* Get invoker message default */

    if ((nargs >= 3) && args[2] && *args[2]) {
	whatd = args[2];
    }
    /* Get others message attribute */

    if (nargs >= 4) {
	ap = atr_str(args[3]);
	if (ap && (ap->number > 0))
	    owhat = ap->number;
    }
    /* Get others message default */

    if ((nargs >= 5) && args[4] && *args[4]) {
	owhatd = args[4];
    }
    /* Get action attribute */

    if (nargs >= 6) {
	ap = atr_str(args[5]);
	if (ap)
	    awhat = ap->number;
    }
    /* Get arguments */

    if (nargs >= 7) {
	parse_arglist(victim, actor, args[6], '\0',
		   EV_STRIP_LS | EV_STRIP_TS, xargs, 10, (char **) NULL, 0);
	for (nxargs = 0; (nxargs < 10) && xargs[nxargs]; nxargs++);
    }
    /* If player doesn't control both, enforce visibility restrictions */

    if (restrict) {
	atr_get_info(victim, what, &aowner, &aflags);
	if (what != -1) {
	    ap = atr_num(what);
	    if (!ap || !Read_attr(player, victim, ap, aowner, aflags) ||
		((ap->number == A_DESC) && !mudconf.read_rem_desc &&
		 !controls(player, victim) && !nearby(player, victim)))
		what = -1;
	}
	atr_get_info(victim, owhat, &aowner, &aflags);
	if (owhat != -1) {
	    ap = atr_num(owhat);
	    if (!ap || !Read_attr(player, victim, ap, aowner, aflags) ||
		((ap->number == A_DESC) && !mudconf.read_rem_desc &&
		 !controls(player, victim) && !nearby(player, victim)))
		owhat = -1;
	}
	awhat = 0;
    }
    /* Go do it */

    did_it(actor, victim, what, whatd, owhat, owhatd, awhat,
	   xargs, nxargs);

    /* Free user args */

    for (i = 0; i < nxargs; i++)
	free_lbuf(xargs[i]);

}

#endif /* STANDALONE */
