AlloyMUSH-1.1/conf/
AlloyMUSH-1.1/misc/
AlloyMUSH-1.1/scripts/
AlloyMUSH-1.1/vms/
/* func-alloy.c - function handlers for functions new to AlloyMUSH */

#ifndef lint
static char RCSid[] = "$Id$";

#endif

#include "copyright.h"
#include "autoconf.h"

#include <limits.h>
#include <string.h>
#include <math.h>

#include "externs.h"
#include "flags.h"
#include "attrs.h"
#include "match.h"
#include "command.h"
#include "functions.h"
#include "misc.h"
#include "alloc.h"
#include "powers.h"

#ifdef FLOATING_POINTS
#ifndef linux			/* linux defines atof as a macro */
double atof();
#endif				/* ! linux */
#define aton atof
typedef double NVAL;
#else
#define aton atoi
typedef int NVAL;
#endif				/* FLOATING_POINTS */

UFUN *ufun_head;

extern void FDECL(cf_log_notfound, (dbref player, char *cmd,
				    const char *thingname, char *thing));

extern void FDECL(make_portlist,
		       (dbref player, dbref target, char *buff));

/* This is the prototype for functions */

#define	FUNCTION(x)	\
void x(buff, player, cause, fargs, nfargs, cargs, ncargs) \
char *buff; \
dbref player, cause; \
char *fargs[], *cargs[]; \
int nfargs, ncargs;

/* This is for functions that take an optional delimiter character */

#define varargs_preamble(xname,xnargs)	                        \
if (!fn_range_check(xname, nfargs, xnargs-1, xnargs, buff))	\
return;							        \
if (!delim_check(fargs, nfargs, xnargs, &sep, buff, 0,		\
    player, cause, cargs, ncargs))                              \
return;

#define evarargs_preamble(xname, xnargs)                        \
if (!fn_range_check(xname, nfargs, xnargs-1, xnargs, buff))     \
return;                                                         \
if (!delim_check(fargs, nfargs, xnargs, &sep, buff, 1,          \
    player, cause, cargs, ncargs))                              \
return;

#define mvarargs_preamble(xname,xminargs,xnargs)	        \
if (!fn_range_check(xname, nfargs, xminargs, xnargs, buff))	\
return;							        \
if (!delim_check(fargs, nfargs, xnargs, &sep, buff, 0,          \
    player, cause, cargs, ncargs))                              \
return;

/* Trim off leading and trailing spaces if the separator char is a space */

static char *
trim_space_sep(str, sep)
    char *str, sep;
{
    char *p;

    if (sep != ' ')
	return str;
    while (*str && (*str == ' '))
	str++;
    for (p = str; *p; p++);
    for (p--; *p == ' ' && p > str; p--);
    p++;
    *p = '\0';
    return str;
}

/* next_token: Point at start of next token in string */

static char *
next_token(str, sep)
    char *str, sep;
{
    while (*str && (*str != sep))
	str++;
    if (!*str)
	return NULL;
    str++;
    if (sep == ' ') {
	while (*str == sep)
	    str++;
    }
    return str;
}

/* split_token: Get next token from string as null-term string.  String is
 * destructively modified.
 */

static char *
split_token(sp, sep)
    char **sp, sep;
{
    char *str, *save;

    save = str = *sp;
    if (!str) {
	*sp = NULL;
	return NULL;
    }
    while (*str && (*str != sep))
	str++;
    if (*str) {
	*str++ = '\0';
	if (sep == ' ') {
	    while (*str == sep)
		str++;
	}
    } else {
	str = NULL;
    }
    *sp = str;
    return save;
}

/* ---------------------------------------------------------------------------
 * List management utilities.
 */

#define	ALPHANUM_LIST	1
#define	NUMERIC_LIST	2
#define	DBREF_LIST	3
#define	FLOAT_LIST	4

static int 
autodetect_list(ptrs, nitems)
    char *ptrs[];
    int nitems;
{
    int sort_type, i;
    char *p;

    sort_type = NUMERIC_LIST;
    for (i = 0; i < nitems; i++) {
	switch (sort_type) {
	case NUMERIC_LIST:
	    if (!is_number(ptrs[i])) {

		/* If non-numeric, switch to alphanum sort.
		 * Exception: if this is the first element and
		 * it is a good dbref, switch to a dbref sort.
		 * We're a little looser than the normal
		 * 'good dbref' rules, any number following
		 * the #-sign is accepted.
		 */

		if (i == 0) {
		    p = ptrs[i];
		    if (*p++ != NUMBER_TOKEN) {
			return ALPHANUM_LIST;
		    } else if (is_integer(p)) {
			sort_type = DBREF_LIST;
		    } else {
			return ALPHANUM_LIST;
		    }
		} else {
		    return ALPHANUM_LIST;
		}
	    } else if (index(ptrs[i], '.')) {
		sort_type = FLOAT_LIST;
	    }
	    break;
	case FLOAT_LIST:
	    if (!is_number(ptrs[i])) {
		sort_type = ALPHANUM_LIST;
		return ALPHANUM_LIST;
	    }
	    break;
	case DBREF_LIST:
	    p = ptrs[i];
	    if (*p++ != NUMBER_TOKEN)
		return ALPHANUM_LIST;
	    if (!is_integer(p))
		return ALPHANUM_LIST;
	    break;
	default:
	    return ALPHANUM_LIST;
	}
    }
    return sort_type;
}

static int 
get_list_type(fargs, nfargs, type_pos, ptrs, nitems)
    char *fargs[], *ptrs[];
    int nfargs, nitems, type_pos;
{
    if (nfargs >= type_pos) {
	switch (ToLower(*fargs[type_pos - 1])) {
	case 'd':
	    return DBREF_LIST;
	case 'n':
	    return NUMERIC_LIST;
	case 'f':
	    return FLOAT_LIST;
	case '\0':
	    return autodetect_list(ptrs, nitems);
	default:
	    return ALPHANUM_LIST;
	}
    }
    return autodetect_list(ptrs, nitems);
}

static int 
list2arr(arr, maxlen, list, sep)
    char *arr[], *list, sep;
    int maxlen;
{
    char *p;
    int i;

    list = trim_space_sep(list, sep);
    p = split_token(&list, sep);
    for (i = 0; p && i < maxlen; i++, p = split_token(&list, sep)) {
	arr[i] = p;
    }
    return i;
}

static void 
arr2list(arr, alen, list, sep)
    char *arr[], *list, sep;
    int alen;
{
    char *p;
    int i;

    p = list;
    for (i = 0; i < alen; i++) {
	safe_str(arr[i], list, &p);
	safe_chr(sep, list, &p);
    }
    if (p != list)
	p--;
    *p = '\0';
}

static int 
dbnum(dbr)
    char *dbr;
{
    if ((strlen(dbr) < 2) && (*dbr != '#'))
	return 0;
    else
	return atoi(dbr + 1);
}

/* ---------------------------------------------------------------------------
 * fval: copy the floating point value into a buffer and make it presentable.
 *       alternatively, if we're not using floating points, then we just
 *       print the integer.
 */

#ifdef FLOATING_POINTS
static void fval(buff, result)
    char *buff;
    double result;
{
    char *p;

    sprintf(buff, "%.6f", result);	/* get double val into buffer */

    /* remove useless trailing 0's */
    if ((p = (char *) rindex(buff, '0')) == NULL) 
	return;
    else if (*(p + 1) == '\0') {
	while (*p == '0')
	    *p-- = '\0';
    }

    p = (char *) rindex(buff, '.');	/* take care of dangling '.' */
    if (*(p + 1) == '\0')
	*p = '\0';
}
#else
#define fval(b,n)   sprintf(b, "%d", n)
#endif				/* FLOATING_POINTS */

/* ---------------------------------------------------------------------------
 * fn_range_check: Check # of args to a function with an optional argument
 * for validity.
 */

static int 
fn_range_check(fname, nfargs, minargs, maxargs, result)
    const char *fname;
    char *result;
    int nfargs, minargs, maxargs;
{
    if ((nfargs >= minargs) && (nfargs <= maxargs))
	return 1;

    if (maxargs == (minargs + 1))
	sprintf(result, "#-1 FUNCTION (%s) EXPECTS %d OR %d ARGUMENTS",
		fname, minargs, maxargs);
    else
	sprintf(result,
		"#-1 FUNCTION (%s) EXPECTS BETWEEN %d AND %d ARGUMENTS",
		fname, minargs, maxargs);
    return 0;
}

/* ---------------------------------------------------------------------------
 * delim_check: obtain delimiter
 */

static int 
delim_check(fargs, nfargs, sep_arg, sep, buff, eval, player, cause,
	    cargs, ncargs)
    char *fargs[], *cargs[], *sep, *buff;
    int nfargs, ncargs, sep_arg, eval;
    dbref player, cause;
{
    char *tstr;
    int tlen;

    if (nfargs >= sep_arg) {
	tlen = strlen(fargs[sep_arg - 1]);
	if (tlen <= 1)
	    eval = 0;
	if (eval) {
	    tstr = exec(player, cause, EV_EVAL | EV_FCHECK,
			fargs[sep_arg - 1], cargs, ncargs);
	    tlen = strlen(tstr);
	    *sep = *tstr;
	    free_lbuf(tstr);
	}

	if (tlen == 0) {
	    *sep = ' ';
	} else if (tlen != 1) {
	    strcpy(buff,
		   "#-1 SEPARATOR MUST BE ONE CHARACTER");
	    return 0;
	} else if (!eval) {
	    *sep = *fargs[sep_arg - 1];
	}
    } else {
	*sep = ' ';
    }
    return 1;
}

/* fun_chr: return a specified ASCII character
 * Defined as CA_WIZARD, so mortals don't go around beeping at people.
 * by Alloy [3-10-95]
 */
 
FUNCTION(fun_chr)
{
int which;

	which = atoi(fargs[0]);
	
	if ((which < 0) || (which > 255))
	{
		strcpy(buff, "#-1 INVALID ASCII CHAR");
		return;
	}
	
	sprintf(buff, "%c", (char)which);
}

/* ---------------------------------------------------------------------------
 * fun_create: Creates a room, thing, zone, or exit
 * check_command: needed by fun_create
 * Original code by Darkenelf [??], modified by Alloy [3-24-95]
 */

static int check_command(player, name, buff)
dbref   player;
char    *name, *buff;
{
        CMDENT  *cmd;

        if((cmd = (CMDENT *)hashfind(name, &mudstate.command_htab)))
                if(!check_access(player, cmd->perms))
                  {       strcpy(buff, "#-1 PERMISSION DENIED");
                        return(1);
                        }
        return(0);
      }

FUNCTION(fun_create)
{
        dbref   thing;
        int     cost;
        char    sep, *name;

        varargs_preamble("CREATE", 3);

        name = fargs[0];
        if(!name || !*name)
          {       strcpy(buff, "#-1 ILLEGAL NAME");
                return;
                }

        switch(ToLower(sep)) {
        case 'r':
                if(check_command(player, "@dig", buff)) return;
                thing = create_obj(player, TYPE_ROOM, name, 0);
                break;
        case 'e':
                if(check_command(player, "@open", buff)) return;
                thing = create_obj(player, TYPE_EXIT, name, 0);
                if(thing != NOTHING)
                  {       s_Exits(thing, player);
                        s_Next(thing, Exits(player));
                        s_Exits(player,thing);
                        }
                break;
        case 't':
        case ' ':
                if(check_command(player, "@create", buff)) return;
                cost = atoi(fargs[1]);
                if(cost<0)
                  {       strcpy(buff, "#-1 ILLEGAL COST");
                        return;
                        }
                thing = create_obj(player, TYPE_THING, name, cost);
                if(thing != NOTHING)
                  {       move_via_generic(thing, player, NOTHING, 0);
                        s_Home(thing, new_home(player));
                        }
                break;
        case 'z':		/* by Alloy */
        	if (check_command(player, "@mkzone", buff)) return;
        	cost = mudconf.zonecost;
        	thing = create_obj(player, TYPE_ZONE, name, cost);
        	if (thing != NOTHING)
        	{
        		move_via_generic(thing, player, NOTHING, 0);
        		s_Home(thing, new_home(player));
        	}
        	break;
        default:
                strcpy(buff, "#-1 ILLEGAL OBJECT TYPE");
                return;
              }

        if(thing != NOTHING)
        {
        	switch (ToLower(sep))
        	{
        	case 'r':
        		notify_quiet(player, tprintf(
        		"%s created with room number %d.", name, thing));
        		break;
        	case 'e':
        		notify_quiet(player, "Opened.");
        		break;
        	case 't':
        	case ' ':
        	case 'z':
        		notify_quiet(player, tprintf(
                	"%s created as object #%d", name, thing));
                }
        }

        sprintf(buff, "#%d", thing);
      }

/* fun_set: sets an attribute and returns nothing
 * set_attr_internal: needed by fun_set
 * indiv_attraccess_nametab: needed by fun_set
 * by Alloy [3-18-95]
 */

extern NAMETAB indiv_attraccess_nametab[];

static void 
set_attr_internal(player, thing, attrnum, attrtext, key)
    dbref player, thing;
    int attrnum, key;
    char *attrtext;
{
    dbref aowner;
    int aflags, could_hear;
    ATTR *attr;

    attr = atr_num(attrnum);
    atr_pget_info(thing, attrnum, &aowner, &aflags);
    if (attr && Set_attr(player, thing, attr, aflags)) {
	if ((attr->check != NULL) &&
	    (!(*attr->check) (0, player, thing, attrnum, attrtext)))
	    return;
	could_hear = Hearer(thing);
	atr_add(thing, attrnum, attrtext, Owner(player), aflags);
	handle_ears(thing, could_hear, Hearer(thing));
	if (!(key & SET_QUIET) && !Quiet(player) && !Quiet(thing))
	    notify_quiet(player, "Set.");
    } else {
	notify_quiet(player, "Permission denied.");
    }
}

FUNCTION(fun_set)
{
	dbref thing, thing2, aowner;
	char *p, *buff2;
	int atr, aflags, clear, flagvalue, could_hear;
	ATTR *attr, *attr2;
	
	/* do we have set(obj/attr,atrflag)? */
	if (parse_attrib(player, fargs[0], &thing, &atr)) {
		if (atr != NOTHING) {
			/* must have flag name */
			if (!fargs[1] || !*fargs[1]) {
				notify_quiet(player,
					"I don't know what you want to set!");
				strcpy(buff, "#-1");
				return;
			}
			/* check for clearing */
			clear = 0;
			if (*fargs[1] == NOT_TOKEN) {
				fargs[1]++;
				clear = 1;
			}
			
			/* make sure player specified a valid attr flag */
			flagvalue = search_nametab(player,
				indiv_attraccess_nametab, fargs[1]);
			if (flagvalue == -1) {
				notify_quiet(player, "You can't set that!");
				strcpy(buff, "#-1");
				return;
			}
			/* make sure the attribute is actually there */
			if (!atr_get_info(thing, atr, &aowner, &aflags)) {
				notify_quiet(player,
					"Attribute not present on object.");
				strcpy(buff, "#-1");
				return;
			}
			/* can we write to the attr? */
			attr = atr_num(atr);
			if (!attr || !Set_attr(player, thing, attr, aflags)) {
				notify_quiet(player, "Permission denied.");
				strcpy(buff, "#-1");
				return;
			}
			/* do it! */
			if (clear)
				aflags &= ~flagvalue;
			else
				aflags |= flagvalue;
			could_hear = Hearer(thing);
			atr_set_flags(thing, atr, aflags);
			
			/* tell the player */
			handle_ears(thing, could_hear, Hearer(thing));
			if (!Quiet(player) && !Quiet(thing)) {
				if (clear)
					notify_quiet(player, "Cleared.");
				else
					notify_quiet(player, "Set.");
			}
			buff[0] = '\0';
			return;
		}
	}
	/* find thing */
	if ((thing = match_controlled(player, fargs[0])) == NOTHING) {
		strcpy(buff, "#-1");
		return;
	}
	/* check for attribute set first */
	for (p = fargs[1]; *p && (*p != ':'); p++);
	
	if (*p) {
		*p++ = 0;
		atr = mkattr(fargs[1]);
		if (atr <= 0) {
			notify_quiet(player, "Couldn't create attribute.");
			strcpy(buff, "#-1");
			return;
		}
		attr = atr_num(atr);
		if (!attr) {
			notify_quiet(player, "Permission denied.");
			strcpy(buff, "#-1");
			return;
		}
		atr_get_info(thing, atr, &aowner, &aflags);
		if (!Set_attr(player, thing, attr, aflags)) {
			notify_quiet(player, "Permission denied.");
			strcpy(buff, "#-1");
			return;
		}
		buff2 = alloc_lbuf("fun_set");
		
		/* check for _ */
		if (*p == '_') {
			strcpy(buff2, p + 1);
			if (!parse_attrib(player, p + 1, &thing2, &atr) ||
				(atr == NOTHING)) {
				notify_quiet(player, "No match.");
				strcpy(buff, "#-1");
				free_lbuf(buff2);
				return;
			}
			attr2 = atr_num(atr);
			p = buff2;
			atr_pget_str(buff2, thing2, atr, &aowner, &aflags);
			
			if (!attr2 ||
				!See_attr(player, thing2, attr2, aowner, aflags)) {
				notify_quiet(player, "Permission denied.");
				strcpy(buff, "#-1");
				free_lbuf(buff2);
				return;
			}
		}
		/* do it! */
		
		set_attr_internal(player, thing, attr->number, p, 0);
		free_lbuf(buff2);
		buff[0] = '\0';
		return;
	}
	/* set or clear a flag */
	
	flag_set(thing, player, fargs[1], 0);
	buff[0] = '\0';
}

/* fun_ansi: do ansi color
 * fun_stripansi: take out nasty ansi codes for !ansi players
 * stolen from Penn by Alloy [3-20-95, 3-26-95]
 */
 
FUNCTION(fun_ansi)
{
    char *bp;

    bp = buff;

    safe_str(ltrs2ansi(fargs[0]), buff, &bp);
    safe_str(fargs[1], buff, &bp);
    safe_str(ANSI_NORMAL, buff, &bp);
    *bp = '\0';
}

FUNCTION(fun_stripansi)
{
        char *buf = alloc_lbuf("fun_stripansi");
	char *p = (char *) fargs[0];
	char *q = buf;
	
	while (p && *p) {
		if (*p == ESC_CHAR) {
			/* Start of ANSI code. Skip to end. */
			while (*p && !isalpha(*p))
				p++;
			if (*p)
				p++;
		} else
			*q++ = *p++;
	}
	*q = '\0';
	strcpy(buff, buf);
	free_lbuf(buf);
}

/* fun_zone: what is the zone of this thing? -or- @zone thing=zone
 * by Alloy [3-20-95]
 */
 
FUNCTION(fun_zone)
{
    char *atr;
    dbref curr, it, zone;
    int lev;
    
    it = match_thing(player, fargs[0]);
    
    if (nfargs < 2) {
    	if (Good_obj(it) && (Examinable(player, it) || (it == cause))) {
    	    sprintf(buff, "#%d", Zone(it));
    	} else {
    	    strcpy(buff, "#-1");
    	}
    	return;
    }
    
    if (!controls(player, it)) {
    	notify_quiet(player, "Permission denied.");
    	strcpy(buff, "#-1 PERMISSION DENIED");
    	return;
    }
    
    if (*fargs[1]) {
    	init_match(player, fargs[1], TYPE_ZONE);
    	match_everything(0);
    	zone = noisy_match_result();
    	if (zone == NOTHING) {
    	    strcpy(buff, "#-1 NO MATCH");
    	    return;
    	}
    	if (!isZone(zone)) {
    	    strcpy(buff, "#-1 NOT A ZONE");
    	    return;
    	}
    	
    	if (!Controls(player, zone)) {
    	    notify_quiet(player, "Permission denied.");
    	    strcpy(buff, "#-1 PERMISSION DENIED");
    	    return;
    	}
    	
    	ITER_ZONES(zone, curr, lev) {
    	    if (curr == it) {
    	    	notify_quiet(player, "Recursive zoning could cause problems.");
    	    	strcpy(buff, "#-1 RECURSIVE ZONE");
    	    	return;
    	    }
    	}
    	
    	if (atoi(atr_get_raw(zone, A_QUOTA)) != 0) {
    	    atr = atr_get_raw(zone, A_RQUOTA);
    	    lev = atoi(atr);
    	    if ((lev < 1) &&
    	    	!(Wizard(player) || Has_power(player, POW_FREE_QUOTA) ||
    	    	  Has_power(it, POW_FREE_QUOTA))) {
    	    	strcpy(buff, "#-1 ZONE FULL");
    	    	return;
    	    }
    	    
    	    atr_add_raw(zone, A_RQUOTA, tprintf("%d", --lev));
    	}
    } else {
    	if (Good_obj(Zone(it))) {
    	    atr = atr_get_raw(Zone(it), A_QUOTA);
    	    if (atoi(atr) != 0) {
    	    	atr = atr_get_raw(Zone(it), A_RQUOTA);
    	    	lev = atoi(atr);
    	    	atr_add_raw(Zone(it), A_RQUOTA, tprintf("%d", ++lev));
    	    }
    	}
    	
    	zone = NOTHING;
    }
    
    s_Zone(it, zone);
    if (!Quiet(it) && !Quiet(player)) {
    	if (zone == NOTHING)
    	    notify_quiet(player, "Zone cleared.");
    	else
    	    notify_quiet(player, "Zone set.");
    }
    
    *buff = '\0';
}

/* fun_trigger: trigger an attribute
 * by Alloy [3-21-95]
 */
 
FUNCTION(fun_trigger)
{
  dbref thing;
  int attrib;
  
  if (!parse_attrib(player, fargs[0], &thing, &attrib) || (attrib == NOTHING)) {
    notify_quiet(player, "No match.");
    return;
  }
  if (!controls(player, thing)) {
    notify_quiet(player, "Permission denied.");
    return;
  }
  did_it(player, thing, 0, NULL, 0, NULL, attrib, &(fargs[1]), nfargs - 1);
  
  if (!Quiet(player))
    notify_quiet(player, "Triggered.");
  
  buff[0] = '\0';
}

/* fun_tport: teleport an object
 * by Alloy [3-21-95]
 */

FUNCTION(fun_tport)
{
  dbref victim, destination, loc;
  char *to;
  
  if (*fargs[1] == '\0') {
    victim = player;
    to = fargs[0];
  } else {
    init_match(player, fargs[0], NOTYPE);
    match_everything(MAT_NO_EXITS);
    victim = noisy_match_result();
    
    if (victim == NOTHING) {
      strcpy(buff, "#-1");
      return;
    }
  
    to = fargs[1];
  }
  
  if (!Has_location(victim)) {
    notify_quiet(player, "You can't teleport that.");
    strcpy(buff, "#-1");
    return;
  }
  
  if (!Controls(player, victim) && !Controls(player, Location(victim))) {
    notify_quiet(player, "Permission denied.");
    strcpy(buff, "#-1");
    return;
  }
  
  if (!string_compare(to, "home")) {
    (void) move_via_teleport(victim, HOME, cause, 0);
    buff[0] = '\0';
    return;
  }
  
  init_match(player, to, NOTYPE);
  match_everything(0);
  destination = match_result();
  
  switch (destination) {
  case NOTHING:
    notify_quiet(player, "No match.");
    strcpy(buff, "#-1");
    return;
  case AMBIGUOUS:
    notify_quiet(player,
    		 "I don't know which destination you mean!");
    strcpy(buff, "#-1");
    return;
  default:
    if (victim == destination) {
      notify_quiet(player, "Bad destination.");
      strcpy(buff, "#-1");
      return;
    }
  }
  
  if (mudconf.fascist_tport) {
    loc = where_room(victim);
    if (!Good_obj(loc) || !isRoom(loc) ||
      (!Controls(player, loc) && !Jump_ok(loc))) {
      notify_quiet(player, "Permission denied.");
      strcpy(buff, "#-1");
      return;
    }
  }
  if (Has_contents(destination)) {
    if (!Controls(player, destination) &&
      (!Jump_ok(destination) || !could_doit(victim, destination, A_LTPORT))) {
      
      if (player != victim)
        notify_quiet(player, "Permission denied.");
      did_it(victim, destination,
        A_TFAIL, "You can't teleport there!",
        A_OTFAIL, 0, A_ATFAIL, (char **) NULL, 0);
      strcpy(buff, "#-1");
      return;
    }
    
    if (move_via_teleport(victim, destination, cause, 0)) {
      if (player != victim) {
        if (!Quiet(player))
          notify_quiet(player, "Teleported.");
      }
    }
  } else if (isExit(destination)) {
    if (Exits(destination) == Location(victim)) {
      move_exit(victim, destination, 0, "You can't go that way.", 0);
    } else {
      notify_quiet(player, "I can't find that exit.");
    }
  }
  buff[0] = '\0';
}

/* fun_link: link an object
 * link_exit, parse_linkable_room: needed by fun_link
 * by Alloy [3-21-95]
 */
 
static dbref 
parse_linkable_room(player, room_name)
    dbref player;
    char *room_name;
{
    dbref room;

    init_match(player, room_name, NOTYPE);
    match_everything(MAT_NO_EXITS | MAT_NUMERIC | MAT_HOME | MAT_VAR);
    room = match_result();

    /* HOME is always linkable */

    if (room == HOME)
	return HOME;
    else if (room == VARIABLE)
    	return VARIABLE;

    /* Make sure we can link to it */

    if (!Good_obj(room)) {
	notify_quiet(player, "That's not a valid object.");
	return NOTHING;
    } else if (!Has_contents(room) || !Linkable(player, room)) {
	notify_quiet(player, "You can't link to that.");
	return NOTHING;
    } else {
	return room;
    }
}

static void
link_exit(player, exit, dest)
    dbref player, exit, dest;
{
  int cost, quot;
  
  if ((dest != HOME) && (dest != VARIABLE) &&
    ((!controls(player, dest) && !Link_ok(dest)) ||
     (!could_doit(player, dest, A_LLINK) &&
      (!Wizard(player) || mudconf.wiz_obey_linklock)))) {
    notify_quiet(player, "Permission denied.");
    return;
  }
  
  if ((Location(exit) != NOTHING) && !controls(player, exit)) {
    notify_quiet(player, "Permission denied.");
    return;
  }
  
  cost = mudconf.linkcost;
  quot = 0;
  if (Owner(exit) != Owner(player)) {
    cost += mudconf.opencost;
    quot += mudconf.exit_quota;
  }
  if (!canpayfees(player, player, cost, quot, TYPE_EXIT))
    return;
  else
    payfees(player, cost, quot, TYPE_EXIT);
    
  if (Owner(exit) != Owner(player)) {
    payfees(Owner(exit), -mudconf.opencost, -quot, TYPE_EXIT);
    s_Owner(exit, Owner(player));
    s_Flags(exit, (Flags(exit) & ~(INHERIT | WIZARD)) | HALT);
  }
  
  s_Location(exit, dest);
  if (!Quiet(player));
    notify_quiet(player, "Linked.");
}

FUNCTION(fun_link)
{
  dbref thing, room;
  char *buff2;
  
  init_match(player, fargs[0], TYPE_EXIT);
  match_everything(0);
  thing = noisy_match_result();
  if (thing == NOTHING) {
    strcpy(buff, "#-1");
    return;
  }
  
  if (!fargs[1] || !*fargs[1]) {
    do_unlink(player, cause, 0, fargs[0]);
    buff[0] = '\0';
    return;
  }
  switch (Typeof(thing)) {
  case TYPE_EXIT:
    room = parse_linkable_room(player, fargs[1]);
    if (room != NOTHING)
      link_exit(player, thing, room);
      buff[0] = '\0';
      break;
  case TYPE_PLAYER:
  case TYPE_THING:
    if (!Controls(player, thing)) {
      notify_quiet(player, "Permission denied.");
      strcpy(buff, "#-1");
      break;
    }
    init_match(player, fargs[1], NOTYPE);
    match_everything(MAT_NO_EXITS);
    room = noisy_match_result();
    if (!Good_obj(room)) {
      strcpy(buff, "#-1");
      break;
    }
    if (!Has_contents(room)) {
      notify_quiet(player, "Can't link to an exit.");
      strcpy(buff, "#-1");
      break;
    }
    if (!can_set_home(player, thing, room) ||
        (!could_doit(player, room, A_LLINK) &&
         (!Wizard(player) || mudconf.wiz_obey_linklock))) {
      notify_quiet(player, "Permission denied.");
      strcpy(buff, "#-1");
    } else if (room == HOME) {
      notify_quiet(player, "Can't set home to home.");
      strcpy(buff, "#-1");
    } else {
      s_Home(thing, room);
      if (!Quiet(player))
        notify_quiet(player, "Home set.");
      buff[0] = '\0';
    }
    break;
  case TYPE_ROOM:
    if (!Controls(player, thing)) {
      notify_quiet(player, "Permission denied.");
      strcpy(buff, "#-1");
      break;
    }
    room = parse_linkable_room(player, fargs[1]);
    if (!(Good_obj(room) || (room == HOME))) {
      strcpy(buff, "#-1");
      break;
    }
    
    if ((room != HOME) && !isRoom(room)) {
      notify_quiet(player, "That is not a room!");
      strcpy(buff, "#-1");
    } else if ((room != HOME) &&
    	       ((!controls(player, room) && !Link_ok(room)) ||
    	        (!could_doit(player, room, A_LLINK) &&
    	         (!Wizard(player) || mudconf.wiz_obey_linklock)))) {
      notify_quiet(player, "Permission denied.");
      strcpy(buff, "#-1");
    } else {
      s_Dropto(thing, room);
      if (!Quiet(player))
        notify_quiet(player, "Dropto set.");
      buff[0] = '\0';
    }
    break;
  case TYPE_ZONE:
    notify_quiet(player, "Why bother?");
    break;
  default:
    STARTLOG(LOG_BUGS, "BUG", "OTYPE");
      buff2 = alloc_mbuf("fun_link.LOG.badtype");
    sprintf(buff2, "Strange object type: object #%d = %d", thing,
    	    Typeof(thing));
    log_text(buff2);
    free_mbuf(buff);
    ENDLOG
  }
}

/* fun_powers: what powers does this have?
 * by Alloy [4-26-95]
 */

FUNCTION(fun_powers)
{
    dbref it;
    char *tmp, *p;
    
    it = match_thing(player, fargs[0]);
    if (!Good_obj(it)) {
    	sprintf(buff, "#-1 BAD OBJECT");
    	return;
    }
    
    if (!Examinable(player, it)) {
    	sprintf(buff, "#-1 PERMISSION DENIED");
    	return;
    }
    
    if (Powers(it) == 0) {
    	buff[0] = '\0';
    	return;
    }
    
    tmp = (char *) unparse_powers(Powers(it));
    strcpy(buff, tmp);
    free_lbuf(tmp);
}

/* fun_column: arrange stuff into a neat table, assuming that the user's screen
 * is 76 chars wide.
 * by Alloy [4-28-95]
 * fixed [11-10-95]
 */

FUNCTION(fun_column)
{
    int width, spaces, num, i;
    float kar;
    int count = 0;
    char *r, *s, *bp, sep;
    
    varargs_preamble("COLUMN", 3);
    
    if (atoi(fargs[1]) < 1) {
    	strcpy(buff, "#-1 INVALID COLUMN NUMBER");
    	return;
    }
    
    num = atoi(fargs[1]);
    
    bp = buff;
    
    kar = 77 / atoi(fargs[1]);
    width = (int) kar - 0.5;		/* strip off the fraction part */
    
    s = trim_space_sep(fargs[0], sep);
    
    while (s) {
    	r = split_token(&s, sep);
    	if (count == num) {
    	    safe_str((char *) "\r\n", buff, &bp);
    	    count = 0;
    	}
    	spaces = width - strlen(r);
    	if (spaces == 0)
    	    safe_str(r, buff, &bp);
    	else if (spaces < 0)
	    for (i = 0; *r && i < width; r++, i++)
	    	safe_chr(*r, buff, &bp);
	else {
	    safe_str(r, buff, &bp);
	    for (i = 0; i < spaces; i++)
	    	safe_chr(' ', buff, &bp);
	}
	count++;
    }
}

/* fun_elist: turns "foo bar baz spool" into "foo, bar, baz, and spool"
 * by Alloy [5-19-95]
 * evil bug (elist with no args crashes the server) fixed [11-10-95]
 */

FUNCTION(fun_elist)
{
    int count, i;
    char *bp, *items[LBUF_SIZE / 2], sep, *word;
    
    if (nfargs == 0) {
    	strcpy(buff, "nothing");
    	return;
    }
    
    if (!*fargs[0]) {
    	buff[0] = '\0';
    	return;
    }
    
    word = alloc_sbuf("fun_elist");

    if (nfargs < 3)
    	sep = ' ';
    else
    	sep = *fargs[2];
    
    bp = word;
    if (nfargs < 2)
    	strcpy(word, "and");
    else
    	safe_sb_str(fargs[1], word, &bp);
    
    bp = buff;
    
    count = list2arr(items, LBUF_SIZE / 2, fargs[0], sep);
    
    switch (count) {
    	case 0: buff[0] = '\0'; break;
    	case 1: strcpy(buff, items[0]); break;
    	case 2:
    	    safe_str(tprintf("%s %s %s", items[0], word, items[1]), buff, &bp);
    	    break;
    	default:
    	    for (i = 0; i < count; i++) {
    	    	if (i == count - 1)
    	    	    safe_str(tprintf("%s %s", word, items[i]), buff, &bp);
    	    	else
    	    	    safe_str(tprintf("%s, ", items[i]), buff, &bp);
    	    }
    }
    free_sbuf(word);
}

/* fun_wrap: wraps some text so that lines are n chars long. if a sep is
 * specified, it is inserted every n chars instead of a newline
 */

FUNCTION(fun_wrap)
{
    char *bp, sep, *p;
    int i, sepped, wrapat;
    
    varargs_preamble("WRAP", 3);
    
    wrapat = atoi(fargs[1]);
    bp = buff;
    i = 0;
    
    for (p = fargs[0]; *p; p++) {
    	safe_chr(*p, buff, &bp);
    	i++;
    	if (*p == '\r') {
    	    safe_chr(*(++p), buff, &bp);
    	    i = 0;
    	}
    	
    	if ((i != 0) && !(i % wrapat)) {
    	    sepped = 1;
    	    if (sep == ' ')
    	    	safe_str("\r\n", buff, &bp);
    	    else
    	    	safe_chr(sep, buff, &bp);
    	}
    	else
    	    sepped = 0;
    }
    
    if (sepped)
    	buff[strlen(buff) - ((sep == ' ') ? 2 : 1)] = '\0';
}

/* fun_id: turn an object match into something like Wizard(#1PWc)
 * by Alloy [10-18-95]
 */

FUNCTION(fun_id)
{
    char *s;
    dbref it;
    
    it = match_thing(player, fargs[0]);
    if (it == NOTHING) {
    	buff[0] = '\0';
    	return;
    }
    if (!mudconf.read_rem_name ||
    	!(mudconf.pub_flags || Examinable(player, it) || (it == cause)))
    	if (!nearby_or_control(player, it) && !isPlayer(it)) {
    	    strcpy(buff, "#-1 TOO FAR AWAY TO SEE");
    	    return;
    	}

    strcpy(buff, s = unparse_object(player, it, 0));
    free_lbuf(s);
}

/* fun_art: return the article that should go with a certain noun
 * by Alloy [10-21-95]
 */

FUNCTION(fun_art)
{
    char c, *p;
    
    p = fargs[0];
    c = ToLower(*p);
    
    if (index("aeiou",c))
    	strcpy(buff, "an");
    else
    	strcpy(buff, "a");
}

/* fun_setqmatch: like strmatch, but put the matches into the setq registers
 * by Alloy [11-19-95]
 */

FUNCTION(fun_setqmatch)
{
    char *args[MAX_GLOBAL_REGS];
    int i;
    
    if (wild_match(fargs[1], fargs[0], args, MAX_GLOBAL_REGS, 0)) {
    	for (i = 0; i < MAX_GLOBAL_REGS; i++) {
    	    if (args[i]) {
    	    	if (!mudstate.global_regs[i])
    	    	    mudstate.global_regs[i] = alloc_lbuf("fun_setqmatch");
    	    	strcpy(mudstate.global_regs[i], args[i]);
    	    	free_lbuf(args[i]);
    	    } else break;
    	}
    	strcpy(buff, "1");
    } else {
    	strcpy(buff, "0");
    }
}

/* fun_listmatch: like setqmatch, but return a list rather than putting the
 * matches into the setq registers
 * by Alloy [11-19-95]
 */

FUNCTION(fun_listmatch)
{
    char *args[MAX_GLOBAL_REGS], *bp, sep;
    int i;
    
    varargs_preamble("LISTMATCH", 3);
    
    bp = buff;
    
    if (wild_match(fargs[1], fargs[0], args, MAX_GLOBAL_REGS, 0)) {
    	*buff = '\0';		/* so we get a blank if no wildcards */
    	for (i = 0; i < MAX_GLOBAL_REGS; i++) {
    	    if (args[i]) {
    	    	if (bp != buff) safe_chr(sep, buff, &bp);
    	    	safe_str(args[i], buff, &bp);
    	    	free_lbuf(args[i]);
    	    } else {
    	    	/* we've hit the last match, so stop */
    	    	break;
    	    }
    	}
    } else {
    	*buff = '\0';
    }
}

/* fun_baseconv: convert a number from base A to base B.
 * by Alloy [11-20-95]
 */

FUNCTION(fun_baseconv)
{
    char c, *p, *tmp;
    int factor;
    int decnum, old, new, this, i;
    
    if (!(fargs[0]) || !*(fargs[0])) {
    	strcpy(buff, "0");
    	return;
    }
    
    old = atoi(fargs[1]);
    new = atoi(fargs[2]);
    
    if ((old < 2) || (old > 36) || (new < 2) || (new > 36)) {
    	strcpy(buff, "#-1 INVALID BASE");
    	return;
    }
    
    /* convert input to a decimal number */
    
    this = strlen(fargs[0]) - 1;
    factor = 1;
    for (i = 0; i < this; i++) {
    	factor *= old;
    }
    decnum = 0;
    
    for (p = fargs[0]; *p; p++) {
        *p = ToLower(*p);
        if (isdigit(*p)) {
            this = *p - '0';
        } else if ((*p >= 'a') && (*p <= 'z') &&
                   ((*p - 'a' + 10) < old)) {
            this = *p - 'a' + 10;
        } else {
            strcpy(buff, "#-1 INVALID DIGIT");
            return;
        }
        
        decnum += this * factor;
        factor /= old;
    }
    
    /* convert decimal number to new base */
    
    if (new == 10) {
    	/* cheat */
    	sprintf(buff, "%d", decnum);
    } else {
    	tmp = alloc_mbuf("fun_baseconv");
    	*buff = '\0';
    	do {
    	    this = decnum % new;
    	    decnum /= new;
    	    if (this <= 9)
    	    	c = this + '0';
    	    else
    	    	c = this - 10 + 'A';
    	    
    	    *tmp = c;
    	    strcpy(tmp + 1, buff);
    	    strcpy(buff, tmp);
    	} while (decnum);
    	free_mbuf(tmp);
    }
}