fbmuck-6.05/auto/
fbmuck-6.05/contrib/jresolver/
fbmuck-6.05/contrib/jresolver/org/
fbmuck-6.05/contrib/jresolver/org/fuzzball/
fbmuck-6.05/docs/devel/
fbmuck-6.05/game/
fbmuck-6.05/game/logs/
fbmuck-6.05/game/muf/
fbmuck-6.05/scripts/
fbmuck-6.05/src_docs/
/* $Header: /cvsroot/fbmuck/fbmuck/src/create.c,v 1.22 2004/05/30 22:49:39 winged Exp $ */


#include "copyright.h"
#include "config.h"

/* Commands that create new objects */

#include "db.h"
#include "props.h"
#include "params.h"
#include "tune.h"
#include "interface.h"
#include "externs.h"
#include "match.h"
#include "fbstrings.h"
#include <ctype.h>

struct line *read_program(dbref i);

/* parse_linkable_dest()
 *
 * A utility for open and link which checks whether a given destination
 * string is valid.  It returns a parsed dbref on success, and NOTHING
 * on failure.
 */

static dbref
parse_linkable_dest(int descr, dbref player, dbref exit, const char *dest_name)
{
	dbref dobj;					/* destination room/player/thing/link */
	static char buf[BUFFER_LEN];
	struct match_data md;

	init_match(descr, player, dest_name, NOTYPE, &md);
	match_absolute(&md);
	match_everything(&md);
	match_home(&md);

	if ((dobj = match_result(&md)) == NOTHING || dobj == AMBIGUOUS) {
		snprintf(buf, sizeof(buf), "I couldn't find '%s'.", dest_name);
		notify(player, buf);
		return NOTHING;

	}

	if (!tp_teleport_to_player && Typeof(dobj) == TYPE_PLAYER) {
		snprintf(buf, sizeof(buf), "You can't link to players.  Destination %s ignored.",
				unparse_object(player, dobj));
		notify(player, buf);
		return NOTHING;
	}

	if (!can_link(player, exit)) {
		notify(player, "You can't link that.");
		return NOTHING;
	}
	if (!can_link_to(player, Typeof(exit), dobj)) {
		snprintf(buf, sizeof(buf), "You can't link to %s.", unparse_object(player, dobj));
		notify(player, buf);
		return NOTHING;
	} else {
		return dobj;
	}
}

/* exit_loop_check()
 *
 * Recursive check for loops in destinations of exits.  Checks to see
 * if any circular references are present in the destination chain.
 * Returns 1 if circular reference found, 0 if not.
 */
int
exit_loop_check(dbref source, dbref dest)
{

	int i;

	if (source == dest)
		return 1;				/* That's an easy one! */
	if (Typeof(dest) != TYPE_EXIT)
		return 0;
	for (i = 0; i < DBFETCH(dest)->sp.exit.ndest; i++) {
		if ((DBFETCH(dest)->sp.exit.dest)[i] == source) {
			return 1;			/* Found a loop! */
		}
		if (Typeof((DBFETCH(dest)->sp.exit.dest)[i]) == TYPE_EXIT) {
			if (exit_loop_check(source, (DBFETCH(dest)->sp.exit.dest)[i])) {
				return 1;		/* Found one recursively */
			}
		}
	}
	return 0;					/* No loops found */
}

/* use this to create an exit */
void
do_open(int descr, dbref player, const char *direction, const char *linkto)
{
	dbref loc, exit;
	dbref good_dest[MAX_LINKS];
	char buf[BUFFER_LEN];
	char buf2[BUFFER_LEN];
	char *rname, *qname;
	int i, ndest;

	NOGUEST("@open",player);

	if (!Builder(player)) {
		notify(player, "That command is restricted to authorized builders.");
		return;
	}
	strcpy(buf2, linkto);
	for (rname = buf2; (*rname && (*rname != '=')); rname++) ;
	qname = rname;
	if (*rname)
		rname++;
	*qname = '\0';

	while (((qname--) > buf2) && (isspace(*qname)))
		*qname = '\0';
	qname = buf2;
	for (; *rname && isspace(*rname); rname++) ;

	if ((loc = getloc(player)) == NOTHING)
		return;
	if (!*direction) {
		notify(player, "You must specify a direction or action name to open.");
		return;
	} else if (!ok_name(direction)) {
		notify(player, "That's a strange name for an exit!");
		return;
	}
	if (!controls(player, loc)) {
		notify(player, "Permission denied.");
		return;
	} else if (!payfor(player, tp_exit_cost)) {
		notify_fmt(player, "Sorry, you don't have enough %s to open an exit.", tp_pennies);
		return;
	} else {
		/* create the exit */
		exit = new_object();

		/* initialize everything */
		NAME(exit) = alloc_string(direction);
		DBFETCH(exit)->location = loc;
		OWNER(exit) = OWNER(player);
		FLAGS(exit) = TYPE_EXIT;
		DBFETCH(exit)->sp.exit.ndest = 0;
		DBFETCH(exit)->sp.exit.dest = NULL;

		/* link it in */
		PUSH(exit, DBFETCH(loc)->exits);
		DBDIRTY(loc);

		/* and we're done */
		snprintf(buf, sizeof(buf), "Exit opened with number %d.", exit);
		notify(player, buf);

		/* check second arg to see if we should do a link */
		if (*qname != '\0') {
			notify(player, "Trying to link...");
			if (!payfor(player, tp_link_cost)) {
				notify_fmt(player, "You don't have enough %s to link.", tp_pennies);
			} else {
				ndest = link_exit(descr, player, exit, (char *) qname, good_dest);
				DBFETCH(exit)->sp.exit.ndest = ndest;
				DBFETCH(exit)->sp.exit.dest = (dbref *) malloc(sizeof(dbref) * ndest);
				for (i = 0; i < ndest; i++) {
					(DBFETCH(exit)->sp.exit.dest)[i] = good_dest[i];
				}
				DBDIRTY(exit);
			}
		}
	}

	if (*rname) {
		PData mydat;

		snprintf(buf, sizeof(buf), "Registered as $%s", rname);
		notify(player, buf);
		snprintf(buf, sizeof(buf), "_reg/%s", rname);
		mydat.flags = PROP_REFTYP;
		mydat.data.ref = exit;
		set_property(player, buf, &mydat);
	}
}

int
_link_exit(int descr, dbref player, dbref exit, char *dest_name, dbref * dest_list, int dryrun)
{
	char *p, *q;
	int prdest;
	dbref dest;
	int ndest, error;
	char buf[BUFFER_LEN], qbuf[BUFFER_LEN];

	prdest = 0;
	ndest = 0;
	error = 0;

	while (*dest_name) {
		while (isspace(*dest_name))
			dest_name++;		/* skip white space */
		p = dest_name;
		while (*dest_name && (*dest_name != EXIT_DELIMITER))
			dest_name++;
		q = (char *) strncpy(qbuf, p, BUFFER_LEN);	/* copy word */
		q[(dest_name - p)] = '\0';	/* terminate it */
		if (*dest_name)
			for (dest_name++; *dest_name && isspace(*dest_name); dest_name++) ;

		if ((dest = parse_linkable_dest(descr, player, exit, q)) == NOTHING)
			continue;

		switch (Typeof(dest)) {
		case TYPE_PLAYER:
		case TYPE_ROOM:
		case TYPE_PROGRAM:
			if (prdest) {
				snprintf(buf, sizeof(buf),
						"Only one player, room, or program destination allowed. Destination %s ignored.",
						unparse_object(player, dest));
				notify(player, buf);

				if(dryrun)
					error = 1;

				continue;
			}
			dest_list[ndest++] = dest;
			prdest = 1;
			break;
		case TYPE_THING:
			dest_list[ndest++] = dest;
			break;
		case TYPE_EXIT:
			if (exit_loop_check(exit, dest)) {
				snprintf(buf, sizeof(buf),
						"Destination %s would create a loop, ignored.",
						unparse_object(player, dest));
				notify(player, buf);
				
				if(dryrun)
					error = 1;

				continue;
			}
			dest_list[ndest++] = dest;
			break;
		default:
			notify(player, "Internal error: weird object type.");
			log_status("PANIC: weird object: Typeof(%d) = %d\n", dest, Typeof(dest));

			if(dryrun)
				error = 1;
				
			break;
		}
		if(!dryrun) {
			if (dest == HOME) {
				notify(player, "Linked to HOME.");
			} else {
				snprintf(buf, sizeof(buf), "Linked to %s.", unparse_object(player, dest));
				notify(player, buf);
			}
		}
		
		if (ndest >= MAX_LINKS) {
			notify(player, "Too many destinations, rest ignored.");

			if(dryrun)
				error = 1;

			break;
		}
	}
	
	if(dryrun && error)
		return 0;
		
	return ndest;
}

/*
 * link_exit()
 *
 * This routine connects an exit to a bunch of destinations.
 *
 * 'player' contains the player's name.
 * 'exit' is the the exit whose destinations are to be linked.
 * 'dest_name' is a character string containing the list of exits.
 *
 * 'dest_list' is an array of dbref's where the valid destinations are
 * stored.
 *
 */

int
link_exit(int descr, dbref player, dbref exit, char *dest_name, dbref * dest_list)
{
	return _link_exit(descr, player, exit, dest_name, dest_list, 0);
}

/*
 * link_exit_dry()
 *
 * like link_exit(), but only checks whether the link would be ok or not.
 * error messages are still output.
 */
int
link_exit_dry(int descr, dbref player, dbref exit, char *dest_name, dbref * dest_list)
{
	return _link_exit(descr, player, exit, dest_name, dest_list, 1);
}

/* do_link
 *
 * Use this to link to a room that you own.  It also sets home for
 * objects and things, and drop-to's for rooms.
 * It seizes ownership of an unlinked exit, and costs 1 penny
 * plus a penny transferred to the exit owner if they aren't you
 *
 * All destinations must either be owned by you, or be LINK_OK.
 */
void
do_link(int descr, dbref player, const char *thing_name, const char *dest_name)
{
	dbref thing;
	dbref dest;
	dbref good_dest[MAX_LINKS];
	struct match_data md;

	int ndest, i;

	NOGUEST("@link",player);

	init_match(descr, player, thing_name, TYPE_EXIT, &md);
	match_all_exits(&md);
	match_neighbor(&md);
	match_possession(&md);
	match_me(&md);
	match_here(&md);
	match_absolute(&md);
	match_registered(&md);
	if (Wizard(OWNER(player))) {
		match_player(&md);
	}
	if ((thing = noisy_match_result(&md)) == NOTHING)
		return;

	switch (Typeof(thing)) {
	case TYPE_EXIT:
		/* we're ok, check the usual stuff */
		if (DBFETCH(thing)->sp.exit.ndest != 0) {
			if (controls(player, thing)) {
				notify(player, "That exit is already linked.");
				return;
			} else {
				notify(player, "Permission denied.");
				return;
			}
		}
		/* handle costs */
		if (OWNER(thing) == OWNER(player)) {
			if (!payfor(player, tp_link_cost)) {
				notify_fmt(player, "It costs %d %s to link this exit.",
						   tp_link_cost, (tp_link_cost == 1) ? tp_penny : tp_pennies);
				return;
			}
		} else {
			if (!payfor(player, tp_link_cost + tp_exit_cost)) {
				notify_fmt(player, "It costs %d %s to link this exit.",
						   (tp_link_cost + tp_exit_cost),
						   (tp_link_cost + tp_exit_cost == 1) ? tp_penny : tp_pennies);
				return;
			} else if (!Builder(player)) {
				notify(player, "Only authorized builders may seize exits.");
				return;
			} else {
				/* pay the owner for his loss */
				dbref owner = OWNER(thing);

				SETVALUE(owner, GETVALUE(owner) + tp_exit_cost);
				DBDIRTY(owner);
			}
		}

		/* link has been validated and paid for; do it */
		OWNER(thing) = OWNER(player);
		ndest = link_exit(descr, player, thing, (char *) dest_name, good_dest);
		if (ndest == 0) {
			notify(player, "No destinations linked.");
			SETVALUE(player, GETVALUE(player) + tp_link_cost);
			DBDIRTY(player);
			break;
		}
		DBFETCH(thing)->sp.exit.ndest = ndest;
		if (DBFETCH(thing)->sp.exit.dest) {
		    free(DBFETCH(thing)->sp.exit.dest);
		}
		DBFETCH(thing)->sp.exit.dest = (dbref *) malloc(sizeof(dbref) * ndest);
		for (i = 0; i < ndest; i++) {
			(DBFETCH(thing)->sp.exit.dest)[i] = good_dest[i];
		}
		break;
	case TYPE_THING:
	case TYPE_PLAYER:
		init_match(descr, player, dest_name, TYPE_ROOM, &md);
		match_neighbor(&md);
		match_absolute(&md);
		match_registered(&md);
		match_me(&md);
		match_here(&md);
		if (Typeof(thing) == TYPE_THING)
			match_possession(&md);
		if ((dest = noisy_match_result(&md)) == NOTHING)
			return;
		if (!controls(player, thing)
			|| !can_link_to(player, Typeof(thing), dest)) {
			notify(player, "Permission denied.");
			return;
		}
		if (parent_loop_check(thing, dest)) {
			notify(player, "That would cause a parent paradox.");
			return;
		}
		/* do the link */
		if (Typeof(thing) == TYPE_THING) {
			THING_SET_HOME(thing, dest);
		} else {
			PLAYER_SET_HOME(thing, dest);
		}
		notify(player, "Home set.");
		break;
	case TYPE_ROOM:			/* room dropto's */
		init_match(descr, player, dest_name, TYPE_ROOM, &md);
		match_neighbor(&md);
		match_possession(&md);
		match_registered(&md);
		match_absolute(&md);
		match_home(&md);
		if ((dest = noisy_match_result(&md)) == NOTHING)
			break;
		if (!controls(player, thing) || !can_link_to(player, Typeof(thing), dest)
			|| (thing == dest)) {
			notify(player, "Permission denied.");
		} else {
			DBFETCH(thing)->sp.room.dropto = dest;	/* dropto */
			notify(player, "Dropto set.");
		}
		break;
	case TYPE_PROGRAM:
		notify(player, "You can't link programs to things!");
		break;
	default:
		notify(player, "Internal error: weird object type.");
		log_status("PANIC: weird object: Typeof(%d) = %d\n", thing, Typeof(thing));
		break;
	}
	DBDIRTY(thing);
	return;
}

/*
 * do_dig
 *
 * Use this to create a room.
 */
void
do_dig(int descr, dbref player, const char *name, const char *pname)
{
	dbref room;
	dbref parent;
	dbref newparent;
	char buf[BUFFER_LEN];
	char rbuf[BUFFER_LEN];
	char qbuf[BUFFER_LEN];
	char *rname;
	char *qname;
	struct match_data md;

	NOGUEST("@dig",player);

	if (!Builder(player)) {
		notify(player, "That command is restricted to authorized builders.");
		return;
	}
	if (*name == '\0') {
		notify(player, "You must specify a name for the room.");
		return;
	}
	if (!ok_name(name)) {
		notify(player, "That's a silly name for a room!");
		return;
	}
	if (!payfor(player, tp_room_cost)) {
		notify_fmt(player, "Sorry, you don't have enough %s to dig a room.", tp_pennies);
		return;
	}
	room = new_object();

	/* Initialize everything */
	newparent = DBFETCH(DBFETCH(player)->location)->location;
	while ((newparent != NOTHING) && !(FLAGS(newparent) & ABODE))
		newparent = DBFETCH(newparent)->location;
	if (newparent == NOTHING)
		newparent = tp_default_room_parent;

	NAME(room) = alloc_string(name);
	DBFETCH(room)->location = newparent;
	OWNER(room) = OWNER(player);
	DBFETCH(room)->exits = NOTHING;
	DBFETCH(room)->sp.room.dropto = NOTHING;
	FLAGS(room) = TYPE_ROOM | (FLAGS(player) & JUMP_OK);
	PUSH(room, DBFETCH(newparent)->contents);
	DBDIRTY(room);
	DBDIRTY(newparent);

	snprintf(buf, sizeof(buf), "%s created with room number %d.", name, room);
	notify(player, buf);

	strcpy(buf, pname);
	for (rname = buf; (*rname && (*rname != '=')); rname++) ;
	qname = rname;
	if (*rname)
		*(rname++) = '\0';
	while ((qname > buf) && (isspace(*qname)))
		*(qname--) = '\0';
	qname = buf;
	for (; *rname && isspace(*rname); rname++) ;
	rname = strcpy(rbuf, rname);
	qname = strcpy(qbuf, qname);

	if (*qname) {
		notify(player, "Trying to set parent...");
		init_match(descr, player, qname, TYPE_ROOM, &md);
		match_absolute(&md);
		match_registered(&md);
		match_here(&md);
		if ((parent = noisy_match_result(&md)) == NOTHING || parent == AMBIGUOUS) {
			notify(player, "Parent set to default.");
		} else {
			if (!can_link_to(player, Typeof(room), parent) || room == parent) {
				notify(player, "Permission denied.  Parent set to default.");
			} else {
				moveto(room, parent);
				snprintf(buf, sizeof(buf), "Parent set to %s.", unparse_object(player, parent));
				notify(player, buf);
			}
		}
	}

	if (*rname) {
		PData mydat;

		snprintf(buf, sizeof(buf), "_reg/%s", rname);
		mydat.flags = PROP_REFTYP;
		mydat.data.ref = room;
		set_property(player, buf, &mydat);
		snprintf(buf, sizeof(buf), "Room registered as $%s", rname);
		notify(player, buf);
	}
}

/*
  Use this to create a program.
  First, find a program that matches that name.  If there's one,
  then we put him into edit mode and do it.
  Otherwise, we create a new object for him, and call it a program.
  */
void
do_prog(int descr, dbref player, const char *name)
{
	dbref i;
	int jj;
	dbref newprog;
	char buf[BUFFER_LEN];
	struct match_data md;

	NOGUEST("@program",player);

	if (Typeof(player) != TYPE_PLAYER) {
		notify(player, "Only players can edit programs.");
		return;
	}
	if (!Mucker(player)) {
		notify(player, "You're no programmer!");
		return;
	}
	if (!*name) {
		notify(player, "No program name given.");
		return;
	}
	init_match(descr, player, name, TYPE_PROGRAM, &md);

	match_possession(&md);
	match_neighbor(&md);
	match_registered(&md);
	match_absolute(&md);

	if ((i = match_result(&md)) == NOTHING) {
		newprog = new_object();

		NAME(newprog) = alloc_string(name);
		snprintf(buf, sizeof(buf), "A scroll containing a spell called %s", name);
		SETDESC(newprog, buf);
		DBFETCH(newprog)->location = player;
		FLAGS(newprog) = TYPE_PROGRAM;
		jj = MLevel(player);
		if (jj < 1)
			jj = 2;
		if (jj > 3)
			jj = 3;
		SetMLevel(newprog, jj);

		OWNER(newprog) = OWNER(player);
		ALLOC_PROGRAM_SP(newprog);
		PROGRAM_SET_FIRST(newprog, NULL);
		PROGRAM_SET_CURR_LINE(newprog, 0);
		PROGRAM_SET_SIZ(newprog, 0);
		PROGRAM_SET_CODE(newprog, NULL);
		PROGRAM_SET_START(newprog, NULL);
		PROGRAM_SET_PUBS(newprog, NULL);
		PROGRAM_SET_MCPBINDS(newprog, NULL);
		PROGRAM_SET_PROFTIME(newprog, 0, 0);
		PROGRAM_SET_PROFSTART(newprog, 0);
		PROGRAM_SET_PROF_USES(newprog, 0);
		PROGRAM_SET_INSTANCES(newprog, 0);

		PLAYER_SET_CURR_PROG(player, newprog);

		PUSH(newprog, DBFETCH(player)->contents);
		DBDIRTY(newprog);
		DBDIRTY(player);
		snprintf(buf, sizeof(buf), "Program %s created with number %d.", name, newprog);
		notify(player, buf);
		snprintf(buf, sizeof(buf), "Entering editor.");
		notify(player, buf);
	} else if (i == AMBIGUOUS) {
		notify(player, "I don't know which one you mean!");
		return;
	} else {
		if ((Typeof(i) != TYPE_PROGRAM) || !controls(player, i)) {
			notify(player, "Permission denied!");
			return;
		}
		if (FLAGS(i) & INTERNAL) {
			notify(player, "Sorry, this program is currently being edited.  Try again later.");
			return;
		}
		PROGRAM_SET_FIRST(i, read_program(i));
		FLAGS(i) |= INTERNAL;
		PLAYER_SET_CURR_PROG(player, i);
		notify(player, "Entering editor.");
		/* list current line */
		do_list(player, i, NULL, 0);
		DBDIRTY(i);
	}
	FLAGS(player) |= INTERACTIVE;
	DBDIRTY(player);
}

void
do_edit(int descr, dbref player, const char *name)
{
	dbref i;
	struct match_data md;

	NOGUEST("@edit",player);

	if (Typeof(player) != TYPE_PLAYER) {
		notify(player, "Only players can edit programs.");
		return;
	}
	if (!Mucker(player)) {
		notify(player, "You're no programmer!");
		return;
	}
	if (!*name) {
		notify(player, "No program name given.");
		return;
	}
	init_match(descr, player, name, TYPE_PROGRAM, &md);

	match_possession(&md);
	match_neighbor(&md);
	match_registered(&md);
	match_absolute(&md);

	if ((i = noisy_match_result(&md)) == NOTHING || i == AMBIGUOUS)
		return;

	if ((Typeof(i) != TYPE_PROGRAM) || !controls(player, i)) {
		notify(player, "Permission denied!");
		return;
	}
	if (FLAGS(i) & INTERNAL) {
		notify(player, "Sorry, this program is currently being edited.  Try again later.");
		return;
	}
	FLAGS(i) |= INTERNAL;
	PROGRAM_SET_FIRST(i, read_program(i));
	PLAYER_SET_CURR_PROG(player, i);
	notify(player, "Entering editor.");
	/* list current line */
	do_list(player, i, NULL, 0);
	FLAGS(player) |= INTERACTIVE;
	DBDIRTY(i);
	DBDIRTY(player);
}

void
do_mcpedit(int descr, dbref player, const char *name)
{
	dbref prog;
	struct match_data md;
	McpFrame *mfr;
	McpVer supp;

	NOGUEST("@mcpedit",player);

	mfr = descr_mcpframe(descr);
	if (!mfr) {
		do_edit(descr, player, name);
		return;
	}

	supp = mcp_frame_package_supported(mfr, "dns-org-mud-moo-simpleedit");

	if (supp.verminor == 0 && supp.vermajor == 0) {
		do_edit(descr, player, name);
		return;
	}

	if (!*name) {
		notify(player, "No program name given.");
		return;
	}
	init_match(descr, player, name, TYPE_PROGRAM, &md);

	match_possession(&md);
	match_neighbor(&md);
	match_registered(&md);
	match_absolute(&md);

	prog = match_result(&md);
	if (prog == NOTHING) {
		/* FIXME: must arrange this to query user. */
		notify(player, "I don't see that here!");
		return;
	}

	if (prog == AMBIGUOUS) {
		notify(player, "I don't know which one you mean!");
		return;
	}

	mcpedit_program(descr, player, prog, name);
}


void
do_mcpprogram(int descr, dbref player, const char* name)
{
	dbref prog;
	char buf[BUFFER_LEN];
	struct match_data md;
	int jj;

	NOGUEST("@mcpprogram",player);

	if (Typeof(player) != TYPE_PLAYER) {
		notify(player, "Only players can edit programs.");
		return;
	}
	if (!Mucker(player)) {
		notify(player, "You're no programmer!");
		return;
	}
	if (!*name) {
		notify(player, "No program name given.");
		return;
	}
	init_match(descr, player, name, TYPE_PROGRAM, &md);

	match_possession(&md);
	match_neighbor(&md);
	match_registered(&md);
	match_absolute(&md);

	prog = match_result(&md);

	if (prog == NOTHING) {
		prog = new_object();

		NAME(prog) = alloc_string(name);
		snprintf(buf, sizeof(buf), "A scroll containing a spell called %s", name);
		SETDESC(prog, buf);
		DBFETCH(prog)->location = player;
		FLAGS(prog) = TYPE_PROGRAM;
		jj = MLevel(player);
		if (jj < 1)
			jj = 2;
		if (jj > 3)
			jj = 3;
		SetMLevel(prog, jj);

		OWNER(prog) = OWNER(player);
		ALLOC_PROGRAM_SP(prog);
		PROGRAM_SET_FIRST(prog, NULL);
		PROGRAM_SET_CURR_LINE(prog, 0);
		PROGRAM_SET_SIZ(prog, 0);
		PROGRAM_SET_CODE(prog, NULL);
		PROGRAM_SET_START(prog, NULL);
		PROGRAM_SET_PUBS(prog, NULL);
		PROGRAM_SET_MCPBINDS(prog, NULL);
		PROGRAM_SET_PROFTIME(prog, 0, 0);
		PROGRAM_SET_PROFSTART(prog, 0);
		PROGRAM_SET_PROF_USES(prog, 0);
		PROGRAM_SET_INSTANCES(prog, 0);

		PLAYER_SET_CURR_PROG(player, prog);

		PUSH(prog, DBFETCH(player)->contents);
		DBDIRTY(prog);
		DBDIRTY(player);
		snprintf(buf, sizeof(buf), "Program %s created with number %d.", name, prog);
		notify(player, buf);

	} else if (prog == AMBIGUOUS) {
		notify(player, "I don't know which one you mean!");
		return;
	}

	mcpedit_program(descr, player, prog, name);
}


void
mcpedit_program(int descr, dbref player, dbref prog, const char* name)
{
	char namestr[BUFFER_LEN];
	char refstr[BUFFER_LEN];
	struct line *curr;
	McpMesg msg;
	McpFrame *mfr;
	McpVer supp;

	mfr = descr_mcpframe(descr);
	if (!mfr) {
		do_edit(descr, player, name);
		return;
	}

	supp = mcp_frame_package_supported(mfr, "dns-org-mud-moo-simpleedit");

	if (supp.verminor == 0 && supp.vermajor == 0) {
		do_edit(descr, player, name);
		return;
	}

	if (Typeof(player) != TYPE_PLAYER) {
		show_mcp_error(mfr, "@mcpedit", "Only players can edit programs.");
		return;
	}
	if (!Mucker(player)) {
		show_mcp_error(mfr, "@mcpedit", "You're no programmer!");
		return;
	}
	if (!*name) {
		show_mcp_error(mfr, "@mcpedit", "No program name given.");
		return;
	}

	if ((Typeof(prog) != TYPE_PROGRAM) || !controls(player, prog)) {
		show_mcp_error(mfr, "@mcpedit", "Permission denied!");
		return;
	}
	if (FLAGS(prog) & INTERNAL) {
		show_mcp_error(mfr, "@mcpedit", "Sorry, this program is currently being edited.  Try again later.");
		return;
	}
	PROGRAM_SET_FIRST(prog, read_program(prog));
	PLAYER_SET_CURR_PROG(player, prog);

	snprintf(refstr, sizeof(refstr), "%d.prog.", prog);
	snprintf(namestr, sizeof(namestr), "a program named %s(%d)", NAME(prog), prog);
	mcp_mesg_init(&msg, "dns-org-mud-moo-simpleedit", "content");
	mcp_mesg_arg_append(&msg, "reference", refstr);
	mcp_mesg_arg_append(&msg, "type", "muf-code");
	mcp_mesg_arg_append(&msg, "name", namestr);
	for (curr = PROGRAM_FIRST(prog); curr; curr = curr->next) {
		mcp_mesg_arg_append(&msg, "content", DoNull(curr->this_line));
	}
	mcp_frame_output_mesg(mfr, &msg);
	mcp_mesg_clear(&msg);

	free_prog_text(PROGRAM_FIRST(prog));
	PROGRAM_SET_FIRST(prog, NULL);
}

/*
 * copy a single property, identified by its name, from one object to
 * another. helper routine for copy_props (below).
 */
void
copy_one_prop(dbref player, dbref source, dbref destination, char *propname)
{
	PropPtr currprop;
	PData newprop;

	/* read property from old object */
	currprop = get_property(source, propname);

#ifdef DISKBASE
	/* make sure the property value is there */
	propfetch(source, currprop);
#endif

	if(currprop) {

		/* flags can be copied. */		
		newprop.flags = currprop->flags;

		/* data, however, must be cloned in case it's a string or a
		   lock. */
		switch(PropType(currprop)) {
			case PROP_STRTYP:
				newprop.data.str = alloc_string((currprop->data).str);
				break;
			case PROP_INTTYP:
			case PROP_FLTTYP:
			case PROP_REFTYP:
				newprop.data = currprop->data;
				break;
			case PROP_LOKTYP:
				newprop.data.lok = copy_bool((currprop->data).lok);
			case PROP_DIRTYP:
				break;
		}

		/* now hook the new property into the destination object. */
		set_property(destination, propname, &newprop);
	}
	
	return;
}

/*
 * copy a property (sub)tree from one object to another one. this is a
 * helper routine used by do_clone, based loosely on listprops_wildcard from
 * look.c.
 */
void
copy_props(dbref player, dbref source, dbref destination, const char *dir)
{
	char propname[BUFFER_LEN];
	char buf[BUFFER_LEN];
	char buf2[BUFFER_LEN];
	PropPtr propadr, pptr;

	/* loop through all properties in the current propdir */
	propadr = first_prop(source, (char *) dir, &pptr, propname);
	while (propadr) {
	
		/* generate name for current property */
		snprintf(buf, sizeof(buf), "%s%c%s", dir, PROPDIR_DELIMITER, propname);

		/* notify player */
		if(tp_verbose_clone && Wizard(OWNER(player))) {
			snprintf(buf2, sizeof(buf2), "copying property %s", buf);
			notify(player, buf2);
		}

		/* copy this property */
		copy_one_prop(player, source, destination, buf);
		
		/* recursively copy this property directory */
		copy_props(player, source, destination, buf);
		
		/* find next property in current dir */
		propadr = next_prop(pptr, propadr, propname);

	}
	
	/* chaos and disorder - our work here is done. */
	return;
}

/*
 * do_clone
 *
 * Use this to clone an object.
 */
void
do_clone(int descr, dbref player, char *name)
{
	static char buf[BUFFER_LEN];
	dbref  thing, clonedthing;
	int    cost;
	struct match_data md;

	/* Perform sanity checks */

	NOGUEST("@clone",player);

	if (!Builder(player)) {
		notify(player, "That command is restricted to authorized builders.");
		return;
	}
	
	if (*name == '\0') {
		notify(player, "Clone what?");
		return;
	} 

	/* All OK so far, so try to find the thing that should be cloned. We
	   do not allow rooms, exits, etc. to be cloned for now. */

	init_match(descr, player, name, TYPE_THING, &md);
	match_possession(&md);
	match_neighbor(&md);
	match_registered(&md);
	match_absolute(&md);
	
	if ((thing = noisy_match_result(&md)) == NOTHING)
		return;

	if (thing == AMBIGUOUS) {
		notify(player, "I don't know which one you mean!");
		return;
	}

	/* Further sanity checks */

	/* things only. */
	if(Typeof(thing) != TYPE_THING) {
		notify(player, "That is not a cloneable object.");
		return;
	}		
	
	/* check the name again, just in case reserved name patterns have
	   changed since the original object was created. */
	if (!ok_name(NAME(thing))) {
		notify(player, "You cannot clone something with such a weird name!");
		return;
	}

	/* no stealing stuff. */
	if(!controls(player, thing)) {
		notify(player, "Permission denied.");
		return;
	}

	/* there ain't no such lunch as a free thing. */
	cost = OBJECT_GETCOST(GETVALUE(thing));
	if (cost < tp_object_cost) {
		cost = tp_object_cost;
	}
	
	if (!payfor(player, cost)) {
		notify_fmt(player, "Sorry, you don't have enough %s.", tp_pennies);
		return;
	} else {
		if(tp_verbose_clone) {
			snprintf(buf, sizeof(buf), "Now cloning %s...", unparse_object(player, thing));
			notify(player, buf);
		}
		
		/* create the object */
		clonedthing = new_object();

		/* initialize everything */
		NAME(clonedthing) = alloc_string(NAME(thing));
		ALLOC_THING_SP(clonedthing);
		DBFETCH(clonedthing)->location = player;
		OWNER(clonedthing) = OWNER(player);
		SETVALUE(clonedthing, GETVALUE(thing));
/* FIXME: should we clone attached actions? */
		DBFETCH(clonedthing)->exits = NOTHING;
		FLAGS(clonedthing) = FLAGS(thing);

		/* copy all properties */
		copy_props(player, thing, clonedthing, "");

		/* endow the object */
		if (GETVALUE(thing) > tp_max_object_endowment) {
			SETVALUE(thing, tp_max_object_endowment);
		}
		
		/* Home, sweet home */
		THING_SET_HOME(clonedthing, THING_HOME(thing));

		/* link it in */
		PUSH(clonedthing, DBFETCH(player)->contents);
		DBDIRTY(player);

		/* and we're done */
		snprintf(buf, sizeof(buf), "%s created with number %d.", NAME(thing), clonedthing);
		notify(player, buf);
		DBDIRTY(clonedthing);
	}
	
}

/*
 * do_create
 *
 * Use this to create an object.
 */
void
do_create(dbref player, char *name, char *acost)
{
	dbref loc;
	dbref thing;
	int cost;

	static char buf[BUFFER_LEN];
	char buf2[BUFFER_LEN];
	char *rname, *qname;

	NOGUEST("@create",player);

	strcpy(buf2, acost);
	for (rname = buf2; (*rname && (*rname != '=')); rname++) ;
	qname = rname;
	if (*rname)
		*(rname++) = '\0';
	while ((qname > buf2) && (isspace(*qname)))
		*(qname--) = '\0';
	qname = buf2;
	for (; *rname && isspace(*rname); rname++) ;

	cost = atoi(qname);
	if (!Builder(player)) {
		notify(player, "That command is restricted to authorized builders.");
		return;
	}
	if (*name == '\0') {
		notify(player, "Create what?");
		return;
	} else if (!ok_name(name)) {
		notify(player, "That's a silly name for a thing!");
		return;
	} else if (cost < 0) {
		notify(player, "You can't create an object for less than nothing!");
		return;
	} else if (cost < tp_object_cost) {
		cost = tp_object_cost;
	}
	if (!payfor(player, cost)) {
		notify_fmt(player, "Sorry, you don't have enough %s.", tp_pennies);
		return;
	} else {
		/* create the object */
		thing = new_object();

		/* initialize everything */
		NAME(thing) = alloc_string(name);
		ALLOC_THING_SP(thing);
		DBFETCH(thing)->location = player;
		OWNER(thing) = OWNER(player);
		SETVALUE(thing, OBJECT_ENDOWMENT(cost));
		DBFETCH(thing)->exits = NOTHING;
		FLAGS(thing) = TYPE_THING;

		/* endow the object */
		if (GETVALUE(thing) > tp_max_object_endowment) {
			SETVALUE(thing, tp_max_object_endowment);
		}
		if ((loc = DBFETCH(player)->location) != NOTHING && controls(player, loc)) {
			THING_SET_HOME(thing, loc);	/* home */
		} else {
			THING_SET_HOME(thing, player);	/* home */
			/* set thing's home to player instead */
		}

		/* link it in */
		PUSH(thing, DBFETCH(player)->contents);
		DBDIRTY(player);

		/* and we're done */
		snprintf(buf, sizeof(buf), "%s created with number %d.", name, thing);
		notify(player, buf);
		DBDIRTY(thing);
	}
	if (*rname) {
		PData mydat;

		snprintf(buf, sizeof(buf), "Registered as $%s", rname);
		notify(player, buf);
		snprintf(buf, sizeof(buf), "_reg/%s", rname);
		mydat.flags = PROP_REFTYP;
		mydat.data.ref = thing;
		set_property(player, buf, &mydat);
	}
}

/*
 * parse_source()
 *
 * This is a utility used by do_action and do_attach.  It parses
 * the source string into a dbref, and checks to see that it
 * exists.
 *
 * The return value is the dbref of the source, or NOTHING if an
 * error occurs.
 *
 */
dbref
parse_source(int descr, dbref player, const char *source_name)
{
	dbref source;
	struct match_data md;

	init_match(descr, player, source_name, NOTYPE, &md);
	/* source type can be any */
	match_neighbor(&md);
	match_me(&md);
	match_here(&md);
	match_possession(&md);
	match_registered(&md);
	match_absolute(&md);
	source = noisy_match_result(&md);

	if (source == NOTHING)
		return NOTHING;

	/* You can only attach actions to things you control */
	if (!controls(player, source)) {
		notify(player, "Permission denied.");
		return NOTHING;
	}
	if (Typeof(source) == TYPE_EXIT) {
		notify(player, "You can't attach an action to an action.");
		return NOTHING;
	}
	if (Typeof(source) == TYPE_PROGRAM) {
		notify(player, "You can't attach an action to a program.");
		return NOTHING;
	}
	return source;
}

/*
 * set_source()
 *
 * This routine sets the source of an action to the specified source.
 * It is called by do_action and do_attach.
 *
 */
void
set_source(dbref player, dbref action, dbref source)
{
	switch (Typeof(source)) {
	case TYPE_ROOM:
	case TYPE_THING:
	case TYPE_PLAYER:
		PUSH(action, DBFETCH(source)->exits);
		break;
	default:
		notify(player, "Internal error: weird object type.");
		log_status("PANIC: tried to source %d to %d: type: %d\n",
				   action, source, Typeof(source));
		return;
		break;
	}
	DBDIRTY(source);
	DBSTORE(action, location, source);
	return;
}

int
unset_source(dbref player, dbref loc, dbref action)
{

	dbref oldsrc;

	if ((oldsrc = DBFETCH(action)->location) == NOTHING) {
		/* old-style, sourceless exit */
		if (!member(action, DBFETCH(loc)->exits)) {
			return 0;
		}
		DBSTORE(DBFETCH(player)->location, exits,
				remove_first(DBFETCH(DBFETCH(player)->location)->exits, action));
	} else {
		switch (Typeof(oldsrc)) {
		case TYPE_PLAYER:
		case TYPE_ROOM:
		case TYPE_THING:
			DBSTORE(oldsrc, exits, remove_first(DBFETCH(oldsrc)->exits, action));
			break;
		default:
			log_status("PANIC: source of action #%d was type: %d.\n", action, Typeof(oldsrc));
			return 0;
			/* NOTREACHED */
			break;
		}
	}
	return 1;
}

/*
 * do_action()
 *
 * This routine attaches a new existing action to a source object,
 * where possible.
 * The action will not do anything until it is LINKed.
 *
 */
void
do_action(int descr, dbref player, const char *action_name, const char *source_name)
{
	dbref action, source;
	static char buf[BUFFER_LEN];
	char buf2[BUFFER_LEN];
	char *rname, *qname;

	NOGUEST("@action",player);

	if (!Builder(player)) {
		notify(player, "That command is restricted to authorized builders.");
		return;
	}
	strcpy(buf2, source_name);
	for (rname = buf2; (*rname && (*rname != '=')); rname++) ;
	qname = rname;
	if (*rname)
		*(rname++) = '\0';
	while ((qname > buf2) && (isspace(*qname)))
		*(qname--) = '\0';
	qname = buf2;
	for (; *rname && isspace(*rname); rname++) ;

	if (!*action_name || !*qname) {
		notify(player, "You must specify an action name and a source object.");
		return;
	} else if (!ok_name(action_name)) {
		notify(player, "That's a strange name for an action!");
		return;
	}
	if (((source = parse_source(descr, player, qname)) == NOTHING))
		return;
        if (!payfor(player, tp_exit_cost)) {
                notify_fmt(player, "Sorry, you don't have enough %s to make an action.", tp_pennies);
                return;
        }

	action = new_object();

	NAME(action) = alloc_string(action_name);
	DBFETCH(action)->location = NOTHING;
	OWNER(action) = OWNER(player);
	DBFETCH(action)->sp.exit.ndest = 0;
	DBFETCH(action)->sp.exit.dest = NULL;
	FLAGS(action) = TYPE_EXIT;

	set_source(player, action, source);
	snprintf(buf, sizeof(buf), "Action created with number %d and attached.", action);
	notify(player, buf);
	DBDIRTY(action);

	if (*rname) {
		PData mydat;

		snprintf(buf, sizeof(buf), "Registered as $%s", rname);
		notify(player, buf);
		snprintf(buf, sizeof(buf), "_reg/%s", rname);
		mydat.flags = PROP_REFTYP;
		mydat.data.ref = action;
		set_property(player, buf, &mydat);
	}
}

/*
 * do_attach()
 *
 * This routine attaches a previously existing action to a source object.
 * The action will not do anything unless it is LINKed.
 *
 */
void
do_attach(int descr, dbref player, const char *action_name, const char *source_name)
{
	dbref action, source;
	dbref loc;				/* player's current location */
	struct match_data md;

	NOGUEST("@attach",player);

	if ((loc = DBFETCH(player)->location) == NOTHING)
		return;

	if (!Builder(player)) {
		notify(player, "That command is restricted to authorized builders.");
		return;
	}
	if (!*action_name || !*source_name) {
		notify(player, "You must specify an action name and a source object.");
		return;
	}
	init_match(descr, player, action_name, TYPE_EXIT, &md);
	match_all_exits(&md);
	match_registered(&md);
	match_absolute(&md);

	if ((action = noisy_match_result(&md)) == NOTHING)
		return;

	if (Typeof(action) != TYPE_EXIT) {
		notify(player, "That's not an action!");
		return;
	} else if (!controls(player, action)) {
		notify(player, "Permission denied.");
		return;
	}
	if (((source = parse_source(descr, player, source_name)) == NOTHING)
		|| Typeof(source) == TYPE_PROGRAM)
		return;

	if (!unset_source(player, loc, action)) {
		return;
	}
	set_source(player, action, source);
	notify(player, "Action re-attached.");
	if (MLevRaw(action)) {
		SetMLevel(action, 0);
		notify(player, "Action priority Level reset to zero.");
	}
}