/* create.c */

#include "copyright.h"

/* Commands that create new objects */

#ifdef WANT_ANSI
#ifdef __STDC__
#include <stdlib.h>
#include <stddef.h>
#endif /* __STDC__ */
#endif /*WANT_ANSI */

#include "mudconf.h"
#include "config.h"
#include "db.h"
#include "interface.h"
#include "externs.h"
#include "match.h"

/* ---------------------------------------------------------------------------
 * parse_linkable_room: Get a location to link to.
 */

static dbref parse_linkable_room(dbref player, char *room_name)
{
dbref room;

	/* skip leading NUMBER_TOKEN if any */

	if (*room_name == NUMBER_TOKEN)
		room_name++;

	/* parse room */

	if (!string_compare(room_name, "here")) {
		room = Location(player);
	} else if (!string_compare(room_name, "home")) {
		return HOME;		/* HOME is always linkable */
	} else {
		room = parse_dbref(room_name);
	}

	/* check that we can link there */

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

/* ---------------------------------------------------------------------------
 * open_exit, do_open: Open a new exit and optionally link it somewhere.
 */

static void open_exit (dbref player, dbref loc, char *direction, char *linkto)
{
dbref	exit;

	if (!Good_obj(loc))
		return;

	if (!direction || !*direction) {
		notify(player, "Open where?");
		return;
	} else if (!controls(player, loc)) {
		notify(player, "Permission denied.");
		return;
	}

	exit = create_obj(player, TYPE_EXIT, direction, 0);
	if (exit == NOTHING)
		return;

	/* Initialize everything and link it in. */

	s_Exits(exit, loc);
	s_Next(exit, Exits(loc));
	s_Exits(loc, exit);

	/* and we're done */

	notify(player, "Opened.");

	/* See if we should do a link */

	if (!linkto || !*linkto)
		return;

	loc = parse_linkable_room(player, linkto);
	if (loc != NOTHING) {

		/* Make sure the player passes the link lock */

		if (!could_doit(player, loc, A_LLINK)) {
			notify(player, "You can't link to there.");
			return;
		}

		/* Link it if the player can pay for it */

		if (!payfor(player, mudconf.linkcost)) {
			notify(player,
				tprintf("You don't have enough %s to link.",
					mudconf.many_coins));
		} else {
			s_Location(exit, loc);
			notify(player, "Linked.");
		}
	}
}

void do_open(dbref player, dbref cause, int key, char *direction,
	char *links[], int nlinks)
{
dbref	loc, destnum;
char	*dest;

	/* Create the exit and link to the destination, if there is one */

	if (nlinks >= 1)
		dest = links[0];
	else
		dest = NULL;

	if (key == OPEN_INVENTORY)
		loc = player;
	else
		loc = Location(player);

	open_exit(player, loc, direction, dest);

	/* Open the back link if we can */

	if (nlinks >= 2) {
		destnum = parse_linkable_room(player, dest);
		if (destnum != NOTHING) {
			open_exit(player, destnum, links[1],
				tprintf("%d", loc));
		}
	}
}

/* ---------------------------------------------------------------------------
 * link_exit, do_link: Set destination(exits), dropto(rooms) or
 * home(player,thing)
 */

static void link_exit (dbref player, dbref exit, dbref dest)
{
int	cost;

	/* Make sure we can link there */

	if ((dest != HOME) && 
	    ((!controls(player, dest) && !(Flags(dest) & LINK_OK)) ||
	     !could_doit(player, dest, A_LLINK))) {
		notify(player, "Permission denied.");
		return;
	}

	/* Make sure it's not already linked */

	if (Location(exit) != NOTHING) {
		if (controls(player, exit)) {
			notify(player,
				"That exit is already linked.");
		} else {
			notify(player, "Permission denied.");
		}
		return;
	}

	/* handle costs */

	cost = mudconf.linkcost;
	if (Owner(exit) != Owner(player))
		cost += mudconf.opencost;

	if (!payfor(player, cost)) {
		if (cost == 1) {
			notify(player,
				tprintf("It costs a %s to link this exit.",
					mudconf.one_coin));
		} else {
			notify(player,
				tprintf("It costs %d %s to link this exit.",
					cost, mudconf.many_coins));
		}
		return;
	}

	/* Pay the owner for his loss */

	if (Owner(exit) != Owner(player)) {
		giveto(Owner(exit), mudconf.opencost);
		s_Owner(exit, Owner(player));
		s_Flags(exit, (Flags(exit) & ~(INHERIT|WIZARD)) | HALT);
	}

	/* link has been validated and paid for, do it and tell the player */

	s_Location(exit, dest);
	if (!Quiet(player))
		notify(player, "Linked.");
}

void do_link(dbref player, dbref cause, int key, char *what, char *where)
{
dbref	thing, room;
char	*buff;

	init_match(player, what, TYPE_EXIT);
	match_exit();
	match_neighbor();
	match_possession();
	match_me();
	match_here();
	match_absolute();
	match_player();
	thing = noisy_match_result();
	if (thing == NOTHING)
		return;

	switch (Typeof(thing)) {
	case TYPE_EXIT:

		/* Set destination */

		room = parse_linkable_room(player, where);
		if (room != NOTHING) 
			link_exit (player, thing, room);
		break;
	case TYPE_PLAYER:
	case TYPE_THING:

		/* Set home */

		if (!Controls(player, thing)) {
			notify(player, "Permission denied.");
			break;
		}
		init_match(player, where, NOTYPE);
		match_exit();
		match_neighbor();
		match_possession();
		match_me();
		match_here();
		match_absolute();
		match_player();
		room = noisy_match_result();
		if (room == NOTHING)
			break;

		if (!can_set_home(player, thing, room) ||
		    !could_doit(player, room, A_LLINK)) {
			notify(player, "Permission denied.");
		} else if (room == HOME) {
			notify(player, "Can't set home to home.");
		} else {			
			s_Home(thing, room);
			if (!Quiet(player))
				notify(player, "Home set.");
		}
		break;
	case TYPE_ROOM:

		/* Set dropto */

		if (!Controls(player, thing)) {
			notify(player, "Permission denied.");
			break;
		}

		room = parse_linkable_room(player, where);
		if (room == NOTHING)
			break;

		if (Typeof(room) != TYPE_ROOM) {
			notify(player, "That is not a room!");
		} else if ((room != HOME) &&
			   ((!controls(player, room) &&
			     !(Flags(room) & LINK_OK)) ||
			    !could_doit(player, room, A_LLINK))) {
			notify(player, "Permission denied.");
		} else {
			s_Dropto(thing, room);
			if (!Quiet(player))
				notify(player, "Dropto set.");
		}
		break;
	default:
		STARTLOG(LOG_BUGS,"BUG","OTYPE")
			buff = alloc_mbuf("do_link.LOG.badtype");
			sprintf(buff, "Strange object type: object #%d = %d",
				thing, Typeof(thing));
			log_text(buff);
			free_mbuf(buff);
		ENDLOG
	}
}

/* ---------------------------------------------------------------------------
 * do_parent: Set an object's parent field.
 */

void do_parent (dbref player, dbref cause, int key, char *tname, char *pname)
{
dbref	thing, parent, curr;
int	lev;
	/* get victim */

	init_match(player, tname, NOTYPE);
	match_neighbor();
	match_possession();
	match_me();
	match_here();
	match_absolute();
	match_player();
	match_exit();
	match_carried_exit();
	thing = noisy_match_result();
	if (thing == NOTHING)
		return;

	/* Make sure we can do it */

	if (!Controls(player, thing)) {
		notify(player, "Permission denied.");
		return;
	}

	/* Find out what the new parent is */

	if (*pname) {
		init_match(player, pname, Typeof(thing));
		match_neighbor();
		match_possession();
		match_me();
		match_here();
		match_absolute();
		match_player();
		match_exit();
		match_carried_exit();
		parent = noisy_match_result();
		if (parent == NOTHING)
			return;

		/* Make sure we have rights to set parent */

		if (!Affects(player, parent)) {
			notify(player, "Permission denied.");
			return;
		}

		/* Verify no recursive reference */

		for (lev=0, curr=parent;
		     (Good_obj(curr) &&
		      (lev < mudconf.parent_nest_lim));
		     curr=Parent(curr), lev++) {
			if (curr==thing) {
				notify(player,
					"You can't have yourself as a parent!");
				return;
			}
		}
	} else {
		parent = NOTHING;
	}

	s_Parent(thing, parent);
	if (!Quiet(thing) && !Quiet(player)) {
		if (parent == NOTHING)
			notify(player, "Parent cleared.");
		else
			notify(player, "Parent set.");
	}
}

/* ---------------------------------------------------------------------------
 * do_dig: Create a new room.
 */

void do_dig (dbref player, dbref cause, int key, char *name,
	char *args[], int nargs)
{
dbref	room;
char	*buff;

	/* we don't need to know player's location!  hooray! */

	if (!name || !*name) {
		notify(player, "Dig what?");
		return;
	}

	room = create_obj(player, TYPE_ROOM, name, 0);
	if (room == NOTHING)
		return;

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

	buff = alloc_sbuf("do_dig");
	if ((nargs >= 1) && args[0] && *args[0]) {
		sprintf(buff, "%d", room);
		open_exit(player, Location(player), args[0], buff);
	}
	if ((nargs >= 2) && args[1] && *args[1]) {
		sprintf(buff, "%d", Location(player));
		open_exit(player, room, args[1], buff);
	}
	free_sbuf(buff);
	if (key == DIG_TELEPORT)
		(void)move_via_teleport(player, room, cause, 0);
}

/* ---------------------------------------------------------------------------
 * do_create: Make a new object.
 */

void do_create(dbref player, dbref cause, int key, char *name, char *coststr)
{
dbref	thing;
int	cost;

	cost = atol(coststr);
	if (!name || !*name) {
		notify(player, "Create what?");
		return;
	} else if (cost < 0) {
		notify(player,
			"You can't create an object for less than nothing!");
		return;
	}

	thing = create_obj(player, TYPE_THING, name, cost);
	if (thing == NOTHING)
		return;

	move_via_generic(thing, player, NOTHING, 0);
	s_Home(thing, new_home(player));
	if (!Quiet(player)) {
		notify(player,
			tprintf("%s created as object #%d",
				Name(thing), thing));
	}
}

/* ---------------------------------------------------------------------------
 * do_clone: Create a copy of an object.
 */

void do_clone(dbref player, dbref cause, int key, char *name, char *arg2)
{
dbref	clone, thing, new_owner, loc;
FLAG	rmv_flags;
int	cost;

	if (key & CLONE_INVENTORY)
		loc = player;
	else
		loc = Location(player);

	if (!Good_obj(loc))
		return;

	init_match(player, name, NOTYPE);
	match_everything();
	thing = noisy_match_result();
	if ((thing == NOTHING) || (thing == AMBIGUOUS))
		return;

	/* Let players clone things set VISUAL.  It's easier than retyping in
	 * all that data
	 */

	if (!Examinable(player, thing)) {
		notify(player, "Permission denied.");
		return;
	}
	if (Typeof(thing) == TYPE_PLAYER) {
		notify(player, "You cannot clone players!");
		return;
	}

	new_owner = (key & CLONE_PRESERVE) ? Owner(thing) : Owner(player);
	if (key & CLONE_SET_COST) {
		cost = atoi(arg2);
		arg2 = NULL;
	} else {
		cost = 1;
		switch (Typeof(thing)) {
		case TYPE_THING:
			cost = OBJECT_DEPOSIT((mudconf.clone_copy_cost) ?
				Pennies(thing) : 1);
			break;
		case TYPE_ROOM:
			cost = mudconf.digcost;
			break;
		case TYPE_EXIT:
			if (!Controls(player, loc)) {
				notify(player, "Permission denied.");
				return;
			}
			cost = mudconf.digcost;
			break;
		}
	}
	clone = create_obj(new_owner, Typeof(thing), Name(thing), cost);
	if (clone == NOTHING)
		return;

	/* Wipe out any old attributes and copy in the new data */

	al_destroy(clone);
	if (key & CLONE_PARENT)
		s_Parent(clone, thing);
	else
		atr_cpy(clone, thing);

	if (arg2 && *arg2)
		s_Name(clone, arg2);
	else if (key & CLONE_PARENT)
		s_Name(clone, Name(thing));

	/* Clear out problem flags from the original */

	rmv_flags = WIZARD;
	if (!(key & CLONE_INHERIT) || (!Inherits(player)))
		rmv_flags |= INHERIT;
	s_Flags(clone, Flags(thing) & ~rmv_flags);

	/* Tell creator about it */

	if (!Quiet(player)) {
		if (arg2 && *arg2)
			notify(player,
				tprintf("%s cloned as %s, new copy is object #%d.",
					Name(thing), arg2, clone));
		else
			notify(player,
				tprintf("%s cloned, new copy is object #%d.",
					Name(thing), clone));
	}

	/* Put the new thing in its new home.  Break any dropto or link, then
	 * try to re-establish it.
	 */

	switch (Typeof(thing)) {
	case TYPE_THING:
		s_Home(clone, clone_home(player, thing));
		move_via_generic(clone, loc, player, 0);
		break;
	case TYPE_ROOM:
		s_Dropto(clone, NOTHING);
		if (Dropto(thing) != NOTHING)
			link_exit(player, clone, Dropto(thing));
		break;
	case TYPE_EXIT:
		s_Exits(loc, insert_first(Exits(loc), clone));
		s_Exits(clone, loc);
		s_Location(clone, NOTHING);
		if (Location(thing) != NOTHING)
			link_exit(player, clone, Location(thing));
		break;
	}

	/* If same owner run ACLONE, else halt it.  Also copy parent
	 * if we can
	 */

	if (new_owner == Owner(thing)) {
		if (!(key & CLONE_PARENT))
			s_Parent(clone, Parent(thing));
		did_it(player, clone, 0, NULL, 0, NULL, A_ACLONE,
			(char **)NULL, 0);
	} else {
		if (!(key & CLONE_PARENT) && Affects(player, thing))
			s_Parent(clone, Parent(thing));
		s_Flags(clone, Flags(clone) | HALT);
	}
}

/* ---------------------------------------------------------------------------
 * do_pcreate: Create new players and robots.
 */

void do_pcreate(dbref player, dbref cause, int key, char *name, char *pass)
{
int	isrobot;
dbref	newplayer;
char	*buff;

	isrobot = (key == PCRE_ROBOT) ? 1 : 0;
	newplayer = create_player(name, pass, player, isrobot);
	if (newplayer == NOTHING) {
		buff=alloc_lbuf("do_pcreate.failed");
		sprintf(buff, "Failure creating '%s'", name);
		notify(player, buff);
		free_lbuf(buff);
		return;
	}

	if (isrobot) {
		move_object(newplayer, Location(player));
		buff=alloc_mbuf("do_pcreate.robot");
		sprintf(buff, "New robot '%s' created with password '%s'",
			name, pass);
		notify(player, buff);
		notify(player, "Your robot has arrived.");
		free_mbuf(buff);
		STARTLOG(LOG_PCREATES,"CRE","ROBOT")
			log_name(newplayer);
			log_text((char *)" created by ");
			log_name(player);
		ENDLOG
	} else {
		move_object(newplayer, mudconf.start_room);
		buff=alloc_mbuf("do_pcreate.player");
		sprintf(buff, "New player '%s' created with password '%s'",
			name, pass);
		notify(player, buff);
		free_mbuf(buff);
		STARTLOG(LOG_PCREATES|LOG_WIZARD,"WIZ","PCREA")
			log_name(newplayer);
			log_text((char *)" created by ");
			log_name(player);
		ENDLOG
	}
}

/* ---------------------------------------------------------------------------
 * destroy_exit, destroy_thing, destroy_player, do_destroy: Destroy things.
 */

static void destroy_exit (dbref player, dbref exit)
{
dbref	loc;

	loc = Exits(exit);
	if ((loc != Location(player)) && !Wizard(player)) {
		notify(player, "You can not destroy exits in another room.");
		return;
	}

	s_Exits(loc, remove_first(Exits(loc), exit));
	destroy_obj(player, exit);
}

static void destroy_thing (dbref player, dbref thing)
{
	move_via_generic(thing, NOTHING, player, 0);
	empty_obj(thing);
	destroy_obj(player, thing);
}

static void destroy_player (dbref player, dbref victim)
{
dbref	aowner;
int	count, aflags;
char	*buf;

	if (!Wizard(player)) {
		notify(player, "Sorry, no suicide allowed.");
		return;
	}
	if (Wizard(victim)) {
		notify(player, "Even you can't do that!");
	}

	/* Bye bye... */

	boot_off(victim, (char *)"You have been destroyed!");
	halt_que(victim, NOTHING);
	count = chown_all(victim, player);

	/* Remove the name from the name hash table */

	delete_player_name(victim, Name(victim));
	buf = atr_pget(victim, A_ALIAS, &aowner, &aflags);
	delete_player_name(victim, buf);
	free_lbuf(buf);

	move_via_generic(victim, NOTHING, player, 0);
	destroy_obj(player, victim);
	notify(player, tprintf("(%d objects @chowned to you)", count));
}

void do_destroy (dbref player, dbref cause, int key, char *what)
{
dbref	thing;

	/* if player owns room check for exit */

	if (controls(player, Location(player))) {
		init_match(player, what, TYPE_EXIT);
		match_exit();
		thing = last_match_result();
	} else {
		thing = NOTHING;
	}

	if ((thing != NOTHING) && (Typeof(thing) == TYPE_EXIT) &&
	    (!Safe(thing, player) || (key & DEST_OVERRIDE))) {
		destroy_exit(player, thing);
		return;
	}

	/* check for player inventory with destroy_ok bit set */

	init_match(player, what, TYPE_THING);
	match_possession();
	thing = last_match_result();

	if (thing != NOTHING) {
		if (controls(player, thing) ||
		    IS(thing, TYPE_THING, THING_DEST_OK)) {
			if (Safe(thing, player) && !(key & DEST_OVERRIDE)) {
				notify(player,
					"Sorry, that object is protected.  Use @destroy/override to destroy it.");
			} else {
				destroy_thing(player, thing);
			}
		} else {
			notify(player, "Permission denied.");
		}
		return;
	}

	/* Check for things I control */

	thing = match_controlled(player, what);
	if (thing == NOTHING)
		return;

	if (Safe(thing, player) && !(key & DEST_OVERRIDE)) {
		notify(player, "Sorry, that is protected.  Use @destroy/override to destroy it.");
		return;
	}

	switch (Typeof(thing)) {
	case TYPE_EXIT:
		destroy_exit(player, thing);
		break;
	case TYPE_THING:
		destroy_thing(player, thing);
		break;
	case TYPE_PLAYER:
		destroy_player(player, thing);
		break;
	case TYPE_ROOM:
		if (Flags(thing) & GOING) {
			notify(player, "No sense beating a dead room.");
		} else {
			notify_all(thing, player,
				"The room shakes and begins to crumble.", 1);
			if (!Quiet(thing) && !Quiet(Owner(thing)))
				notify(Owner(thing),
					tprintf("You will be rewarded shortly for %s(#%d).",
						Name(thing), thing));
			s_Flags(thing, Flags(thing) | GOING);
		}
	}
}