/* move.c */

#include "copyright.h"

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

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

/* ---------------------------------------------------------------------------
 * process_leave_loc: Generate messages and actions resulting from leaving
 * a place.
 */

static void process_leave_loc (dbref thing, dbref dest, dbref cause,
	int canhear, int hush)
{
dbref	loc;
int	quiet, oattr, aattr;

	loc = Location(thing);
	if ((loc == NOTHING) || (loc == dest))
		return;

	if (dest == HOME)
		dest = Home(thing);

	/* Run the LEAVE attributes in the current room if we meet any of
	 * following criteria:
	 * - The current room has wizard privs.
	 * - Neither the current room nor the moving object are dark.
	 * - The moving object can hear and does not hav wizard privs.
	 * EXCEPT if we were called with the HUSH_LEAVE key.
	 */

	quiet = (!Wizard(loc) &&
		 (Dark(thing) || Dark(loc)) &&
		 (!canhear || Wizard(thing))) ||
		(hush & HUSH_LEAVE);
	oattr = quiet ? 0 : A_OLEAVE;
	aattr = quiet ? 0 : A_ALEAVE;
	did_it(thing, loc, A_LEAVE, NULL, oattr, NULL, aattr,
			(char **)NULL, 0);

	/* Do OXENTER for receiving room */

	if ((dest != NOTHING) && !quiet)
		did_it(thing, dest, 0, NULL, A_OXENTER, NULL, 0,
			(char **)NULL, 0);

	/* Display the 'has left' message if we meet any of the following
	 * criteria:
	 * - Neither the current room nor the moving object are dark.
	 * - The object can hear and is not wizard.
	 */

	if ((!Dark(thing) && !Dark(loc)) ||
	    (canhear && !Wizard(thing))) {
		notify_except2(loc, thing, thing, cause,
			tprintf("%s has left.", Name(thing)), 1);
	}
}

/* ---------------------------------------------------------------------------
 * process_enter_loc: Generate messages and actions resulting from entering
 * a place.
 */

static void process_enter_loc (dbref thing, dbref src, dbref cause,
	int canhear, int hush)
{
dbref	loc;
int	quiet, oattr, aattr;

	loc = Location(thing);
	if ((loc == NOTHING) || (loc == src))
		return;

	/* Run the ENTER attributes in the current room if we meet any of
	 * following criteria:
	 * - The current room has wizard privs.
	 * - Neither the current room nor the moving object are dark.
	 * - The moving object can hear and does not hav wizard privs.
	 * EXCEPT if we were called with the HUSH_ENTER key.
	 */

	quiet =	(!Wizard(loc) &&
		 (Dark(thing) || Dark(loc)) &&
		 (!canhear || Wizard(thing))) ||
		(hush & HUSH_ENTER);
	oattr = quiet ? 0 : A_OENTER;
	aattr = quiet ? 0 : A_AENTER;
	did_it(thing, loc, A_ENTER, NULL, oattr, NULL, aattr,
		(char **)NULL, 0);

	/* Do OXLEAVE for sending room */

	if ((src != NOTHING) && !quiet)
		did_it(thing, src, 0, NULL, A_OXLEAVE, NULL, 0,
			(char **)NULL, 0);

	/* Display the 'has arrived' message if we meet all of the following
	 * criteria:
	 * - The moving object can hear.
	 * - The object is not both dark and wizard.
	 */

	if (canhear && !(Dark(thing) && Wizard(thing))) {
		notify_except2(loc, thing, thing, cause,
			tprintf("%s has arrived.", Name(thing)), 1);
	}
}

/* ---------------------------------------------------------------------------
 * move_object: Physically move an object from one place to another.
 * Does not generate any messages or actions.
 */

void move_object (dbref thing, dbref dest)
{
dbref	src;

	/* Remove from the source location */

	src = Location(thing);
	if (src != NOTHING)
		s_Contents(src, remove_first(Contents(src), thing));

	/* Special check for HOME */

	if (dest == HOME)
		dest = Home(thing);

	/* Add to destination location */

	if (dest != NOTHING)
		s_Contents(dest, insert_first(Contents(dest), thing));
	else
		s_Next(thing, NOTHING);
	s_Location(thing, dest);

	/* Look around and do the penny check */

	look_in (thing, dest, 1);
	if ((Typeof(thing) == TYPE_PLAYER) &&
	    (mudconf.payfind > 0) &&
	    (Pennies(thing) < mudconf.paylimit) &&
	    (!Controls(thing, dest)) &&
            ((random() % mudconf.payfind) == 0)) {
		giveto(thing, 1);
		notify(thing, tprintf("You found a %s!", mudconf.one_coin));
	}
}

/* ---------------------------------------------------------------------------
 * send_dropto, process_sticky_dropto, process_dropped_dropto,
 * process_sacrifice_dropto: Check for and process droptos.
 */

/* send_dropto: Send an object through the dropto of a room */

static void send_dropto (dbref thing, dbref player)
{
	if (!(Flags(thing) & STICKY))
		move_via_generic(thing, Dropto(Location(thing)), player, 0);
	else
		move_via_generic(thing, HOME, player, 0);
	divest_object(thing);

}

/* process_sticky_dropto: Call when an object leaves the room to see if
 * we should empty the room
 */

static void process_sticky_dropto (dbref loc, dbref player)
{
dbref	dropto, thing, next;

	/* Do nothing if checking anything but a sticky room */

	if (!Good_obj(loc) || !IS(loc, TYPE_ROOM, STICKY))
		return;

	/* Make sure dropto loc is valid */

	dropto = Dropto(loc);
	if ((dropto == NOTHING) || (dropto == loc))
		return;

	/* Make sure no players hanging out */

	DOLIST(thing, Contents(loc)) {
		if (Dropper(thing))
			return;
	}

	/* Send everything through the dropto */

	s_Contents(loc, reverse_list(Contents(loc)));
	SAFE_DOLIST(thing, next, Contents(loc)) {
		send_dropto(thing, player);
	}
}

/* process_dropped_dropto: Check what to do when someone drops an object. */

static void process_dropped_dropto (dbref thing, dbref player)
{
dbref	loc;

	/* If STICKY, send home */

	if (Flags(thing) & STICKY) {
		move_via_generic(thing, HOME, player, 0);
		divest_object(thing);
		return;
	}

	/* Process the dropto if location is a room and is not STICKY */

	loc = Location(thing);
	if ((Typeof(loc) == TYPE_ROOM) && (Dropto(loc) != NOTHING))
		if (!(Flags(loc) & STICKY))
			send_dropto(thing, player);
}

static void process_sacrifice_dropto (dbref thing, dbref player)
{
dbref	loc;

	/* Check for autodestroy on sacrifice */

	if (mudconf.sac_dest || IS(thing, TYPE_THING, THING_DEST_OK)) {
		move_via_generic(thing, NOTHING, player, 0);
		empty_obj(thing);
		destroy_obj(NOTHING, thing);
		return;
	}

	/* If STICKY, send home */

	if (Flags(thing) & STICKY) {
		move_via_generic(thing, HOME, player, 0);
		divest_object(thing);
		return;
	}

	/* Process the dropto on the location.  If none (or if the location
	 * is not a room, send the object home */

	loc = Location(thing);
	if (Typeof(loc) != TYPE_ROOM) {
		move_via_generic(thing, HOME, player, 0);
		divest_object(thing);
	} else if (Dropto(loc) != NOTHING) {
		send_dropto(thing, player);
	} else {
		move_via_generic(thing, HOME, player, 0);
		divest_object(thing);
	}
}

/* ---------------------------------------------------------------------------
 * move_via_generic: Generic move routine, generates standard messages and
 * actions.
 */

void move_via_generic (dbref thing, dbref dest, dbref cause, int hush)
{
dbref	src;
int	canhear;

	if (dest == HOME)
		dest = Home(thing);
	src = Location(thing);
	canhear = Hearer(thing);
	process_leave_loc(thing, dest, cause, canhear, hush);
	move_object(thing, dest);
	did_it(thing, thing, A_MOVE, NULL, A_OMOVE, NULL, A_AMOVE,
		(char **)NULL, 0);
	process_enter_loc(thing, src, cause, canhear, hush);
}

/* ---------------------------------------------------------------------------
 * move_via_exit: Exit move routine, generic + exit messages + dropto check.
 */

void move_via_exit (dbref thing, dbref dest, dbref cause, dbref exit, int hush)
{
dbref	src;
int	canhear, darkwiz, quiet, oattr, aattr;

	if (dest == HOME)
		dest = Home(thing);
	src = Location(thing);
	canhear = Hearer(thing);

	/* Dark wizards don't trigger OSUCC/ASUCC */

	darkwiz = (Wizard(thing) && Dark(thing));
	quiet = darkwiz || (hush & HUSH_EXIT);

	oattr = quiet ? 0 : A_OSUCC;
	aattr = quiet ? 0 : A_ASUCC;
	did_it(thing, exit, A_SUCC, NULL, oattr, NULL, aattr,
		(char **)NULL, 0);
	process_leave_loc(thing, dest, cause, canhear, hush);
	move_object(thing, dest);

	/* Dark wizards don't trigger ODROP/ADROP */

	oattr = quiet ? 0 : A_ODROP;
	aattr = quiet ? 0 : A_ADROP;
	did_it(thing, exit, A_DROP, NULL, oattr, NULL, aattr,
		(char **)NULL, 0);

	did_it(thing, thing, A_MOVE, NULL, A_OMOVE, NULL, A_AMOVE,
		(char **)NULL, 0);
	process_enter_loc(thing, src, cause, canhear, hush);
	process_sticky_dropto(src, thing);
}

/* ---------------------------------------------------------------------------
 * move_via_teleport: Teleport move routine, generic + teleport messages +
 * divestiture + dropto check.
 */

int move_via_teleport (dbref thing, dbref dest, dbref cause, int hush)
{
dbref	src, curr;
int	canhear, count;

	src = Location(thing);
	if ((dest != HOME) && Good_obj(src)) {
		curr = src;
		for (count=20; count>0; count--) {
			if (!could_doit(thing, curr, A_LTELOUT)) {
				if ((thing == cause) || (cause == NOTHING))
					notify(thing,
						"You can't teleport out!");
				else
					notify(cause,
						"You can't teleport that out!");
				return 0;
			}
			if (Typeof(curr) == TYPE_ROOM)
				break;
			curr = Location(curr);
		}
	}
	if (dest == HOME)
		dest = Home(thing);
	canhear = Hearer(thing);
	did_it(thing, thing, 0, NULL, A_OXTPORT, NULL, 0, (char **)NULL, 0);
	process_leave_loc(thing, dest, NOTHING, canhear, hush);
	move_object(thing, dest);
	did_it(thing, thing, A_TPORT, NULL, A_OTPORT, NULL, A_ATPORT,
		(char **)NULL, 0);
	did_it(thing, thing, A_MOVE, NULL, A_OMOVE, NULL, A_AMOVE,
		(char **)NULL, 0);
	process_enter_loc(thing, src, NOTHING, canhear, hush);
	divest_object(thing);
	process_sticky_dropto(src, thing);
	return 1;
}

/* ---------------------------------------------------------------------------
 * move_exit: Try to move a player through an exit.
 */

void move_exit (dbref player, dbref exit, int divest, const char *failmsg,
	int hush)
{
dbref	loc;
int	oattr, aattr;

	loc = Location(exit);
	if (loc == HOME)
		loc = Home(player);
	if (Good_obj(loc) && could_doit(player, exit, A_LOCK)) {
		switch (Typeof(loc)) {
		case TYPE_ROOM:
			move_via_exit(player, loc, NOTHING, exit, hush);
			if (divest)
				divest_object(player);
			break;
		case TYPE_PLAYER:
		case TYPE_THING:
			if (Flags(loc) & GOING) {
				notify(player, "You can't go that way.");
				return;
			}
			move_via_exit(player, loc, NOTHING, exit, hush);
			divest_object(player);
			break;
		case TYPE_EXIT:
			notify(player, "You can't go that way.");
			return;
		}
	} else {
		if ((Wizard(player) && Dark(player)) || (hush & HUSH_EXIT)) {
			oattr = 0;
			aattr = 0;
		} else {
			oattr = A_OFAIL;
			aattr = A_AFAIL;
		}
		did_it(player, exit, A_FAIL, failmsg, oattr, NULL, aattr,
			(char **)NULL, 0);
	}
}

/* ---------------------------------------------------------------------------
 * do_move: Move from one place to another via exits or 'home'.
 */

void do_move(dbref player, dbref cause, int key, char *direction)
{
dbref	exit, loc;
int	i, quiet;

	if (!string_compare(direction, "home")) {	/* go home w/o stuff */
		if ((loc = Location(player)) != NOTHING &&
		    !Dark(player) && !Dark(loc)) {

			/* tell all */

			notify_except(loc, player, player,
				tprintf("%s goes home.", Name(player)), 1);
		}

		/* give the player the messages */

		for (i=0; i<3; i++)
			notify(player, "There's no place like home...");
		move_via_generic(player, HOME, NOTHING, 0);
		divest_object(player);
		process_sticky_dropto(loc, player);
		return;
	}

	/* find the exit */

	init_match_check_keys(player, direction, TYPE_EXIT);
	match_exit();
	exit = match_result();
	switch (exit) {
	case NOTHING:	/* try to force the object */
		notify(player, "You can't go that way.");
		break;
	case AMBIGUOUS:
		notify(player, "I don't know which way you mean!");
		break;
	default:
		quiet = 0;
		if ((key & MOVE_QUIET) && Controls(player, exit))
			quiet = HUSH_EXIT;
		move_exit(player, exit, 0, "You can't go that way.", quiet);
	}
}

/* ---------------------------------------------------------------------------
 * do_get: Get an object.
 */

void do_get(dbref player, dbref cause, int key, char *what)
{
dbref	thing, loc, exitloc;
int	quiet, oattr, aattr;

	quiet = 0;
	loc = Location(player);
	if (!Good_obj(loc))
		return;

	/* You can only pick up things in rooms and ENTER_OK objects/players */

	if ((Typeof(loc) != TYPE_ROOM) && !(Flags(loc) & ENTER_OK) &&
	    !controls(player, loc)) {
		notify(player, "Permission denied.");
		return;
	}

	/* Handle taking things from other people */

	thing = is_possess(player, what);
	if ((thing != NOTHING) && (thing != AMBIGUOUS)) {
		if ((key & GET_QUIET) && Controls(player, thing))
			quiet = 1;
		if (could_doit(player, thing, A_LOCK) &&
		    (Flags(Location(thing)) & ENTER_OK)) {
			notify(Location(thing),
				tprintf("%s was taken from you.", 
				      Name(thing)));
			move_via_generic(thing, player, player, 0);
			notify(thing, "Taken.");
			oattr = quiet ? 0 : A_OSUCC;
			aattr = quiet ? 0 : A_ASUCC;
			did_it(player, thing, A_SUCC, "Taken.", oattr, NULL,
					aattr, (char **)NULL, 0);
		} else {
			oattr = quiet ? 0 : A_OFAIL;
			aattr = quiet ? 0 : A_AFAIL;
			did_it(player, thing,
				A_FAIL, "You can't take that from there.",
				oattr, NULL, aattr, (char **)NULL, 0);
		}
		return;
	}

	/* Look for the object */

	init_match_check_keys(player, what, TYPE_THING);
	match_neighbor();
	match_exit();
	if (Wizard(player))
		match_absolute();	/* the wizard has long fingers */
	thing = noisy_match_result();
	if (thing == NOTHING)
		return;

	switch (Typeof(thing)) {
	case TYPE_PLAYER:
	case TYPE_THING:
		/* You can't take what you already have */

		if (Location(thing) == player) {
			notify(player, "You already have that!");
			return;
		}
		if ((key & GET_QUIET) && Controls(player, thing))
			quiet = 1;

		if (thing == player) {
			notify(player, "You cannot get yourself!");
		} else if (could_doit(player, thing, A_LOCK)) {
			move_via_generic(thing, player, player, 0);
			notify(thing, "Taken.");
			oattr = quiet ? 0 : A_OSUCC;
			aattr = quiet ? 0 : A_ASUCC;
			did_it(player, thing, A_SUCC, "Taken.", oattr, NULL,
				aattr, (char **)NULL, 0);
		} else {
			oattr = quiet ? 0 : A_OFAIL;
			aattr = quiet ? 0 : A_AFAIL;
			did_it(player, thing,
				A_FAIL, "You can't pick that up.",
				oattr, NULL, aattr, (char **)NULL, 0);
		}
		break;
	case TYPE_EXIT:
		/* You can't take what you already have */

		if (Exits(thing) == player) {
			notify(player, "You already have that!");
			return;
		}

		/* You must control either the exit or the location */

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

		/* Do it */

		exitloc = Exits(thing);
		s_Exits(exitloc, remove_first(Exits(exitloc), thing));
		s_Exits(player, insert_first(Exits(player), thing));
		s_Exits(thing, player);
		if (!Quiet(player))
			notify(player, "Exit taken.");
		break;
	default:
		notify(player, "You can't take that!");
		break;
	}
}

/* ---------------------------------------------------------------------------
 * drop_sacrifice, do_drop: Drop an object.
 */

static void drop_sacrifice (dbref player, dbref thing)
{
dbref	loc;
char	*buf;
int	reward, maxreward;

	/* Only players may sacrifice things */

	if ((Typeof(player) != TYPE_PLAYER) ||
	    (Controls(player, thing)) ||
	    (Typeof(thing) == TYPE_PLAYER))
		return;

	loc = Location(player);
	notify(thing, "You have been sacrificed.");
	notify(player,
		tprintf("%s is consumed in a burst of flame!",
			Name(thing)));
	if (Good_obj(loc)) {
		buf = alloc_sbuf("drop_sacrifice");
		strcpy(buf, Name(player));
		notify_except(loc, player, player,
			tprintf("%s sacrifices %s.", buf,
				Name(thing)), 1);
		free_sbuf(buf);
	}

	/* Give the player his reward and tell him about it */

	reward = Pennies(thing);
	maxreward = OBJECT_ENDOWMENT(mudconf.createmax);
	if ((reward < 1) || (Pennies(Owner(player)) > mudconf.paylimit))
		reward = 1;
	else if (reward > maxreward)
		reward = maxreward;
	giveto(Owner(player), reward);
	notify(player,
		tprintf("You have received %d %s for your sacrifice.",
			reward,
			(reward==1) ? mudconf.one_coin : mudconf.many_coins));
}

void do_drop(dbref player, dbref cause, int key, char *name)
{
dbref	loc, exitloc, thing;
char	*buf;
int	quiet, oattr, aattr;

	loc = Location(player);
	if (!Good_obj(loc))
		return;

	init_match(player, name, TYPE_THING);
	match_possession();
	match_carried_exit();
	if (Wizard(player))
		match_absolute();	/* the wizard has long fingers */
	switch (thing = match_result()) {
	case NOTHING:
		notify(player, "You don't have that!");
		return;
	case AMBIGUOUS:
		notify(player, "I don't know which you mean!");
		return;
	}

	switch (Typeof(thing)) {
	case TYPE_THING:
	case TYPE_PLAYER:

		/* You have to be carrying it */

		if (((Location(thing) != player) && !Wizard(player)) ||
		    (!could_doit(player, thing, A_LDROP))) {
			notify(player, "You can't drop that.");
			return;
		}

		/* Move it */

		move_via_generic (thing, Location(player), player, 0);
		notify(thing, "Dropped.");
		quiet = 0;
		if ((key & DROP_QUIET) && Controls(player, thing))
			quiet = 1;
		buf = alloc_lbuf("do_drop.did_it");
		sprintf(buf, "dropped %s.", Name(thing));
		oattr = quiet ? 0 : A_ODROP;
		aattr = quiet ? 0 : A_ADROP;
		did_it(player, thing, A_DROP, "Dropped.", oattr, buf,
			aattr, (char **)NULL, 0);
		free_lbuf(buf);

		/* Check for droptos or sacrifice */

		if (IS(loc, TYPE_ROOM, ROOM_TEMPLE)) {
			drop_sacrifice(player, thing);
			process_sacrifice_dropto(thing, player);
		} else {
			process_dropped_dropto(thing, player);
		}

		break;
	case TYPE_EXIT:

		/* You have to be carrying it */

		if ((Exits(thing) != player) && !Wizard(player)) {
			notify(player, "You can't drop that.");
			return;
		}

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

		/* Do it */

		exitloc = Exits(thing);
		s_Exits(exitloc, remove_first(Exits(exitloc), thing));
		s_Exits(loc, insert_first(Exits(loc), thing));
		s_Exits(thing, loc);

		if (!Quiet(player))
			notify(player, "Exit dropped.");
		break;
	default:
		notify(player, "You can't drop that.");
	}

}

/* ---------------------------------------------------------------------------
 * do_enter, do_leave: The enter and leave commands.
 */

void do_enter_internal (dbref player, dbref thing, int quiet)
{
dbref	loc;
int	oattr, aattr;

	if (!(Flags(thing) & ENTER_OK) && !controls(player, thing)) {
		oattr = quiet ? 0 : A_OEFAIL;
		aattr = quiet ? 0 : A_AEFAIL;
		did_it (player, thing, A_EFAIL, "Permission denied.",
				oattr, NULL, aattr, (char **)NULL, 0);
	} else if (player == thing) {
		notify(player, "You can't enter yourself!");
	} else if (could_doit(player, thing, A_LENTER)) {
		loc = Location(player);
		oattr = quiet ? HUSH_ENTER : 0;
		move_via_generic(player, thing, NOTHING, oattr);
		divest_object(player);
		process_sticky_dropto(loc, player);
	} else {
		oattr = quiet ? 0 : A_OEFAIL;
		aattr = quiet ? 0 : A_AEFAIL;
		did_it (player, thing, A_EFAIL, "You can't enter that.",
			oattr, NULL, aattr, (char **)NULL, 0);
	}
}

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

	init_match(player, what, TYPE_THING);
	match_neighbor();
	if (Wizard(player))
		match_absolute();	/* the wizard has long fingers */

	if ((thing = noisy_match_result()) == NOTHING)
		return;

	switch (Typeof(thing)) {
	case TYPE_PLAYER:
	case TYPE_THING:
		quiet = 0;
		if ((key & MOVE_QUIET) && Controls(player, thing))
			quiet = 1;
		do_enter_internal(player, thing, quiet);
		break;
	default:
		notify(player, "Permission denied.");
	}
	return;
}

void do_leave(dbref player, dbref cause, int key)
{
dbref	loc;
int	quiet, oattr, aattr;

	loc = Location(player);

	if (!Good_obj(loc) || (Typeof(loc) == TYPE_ROOM)) {
		notify(player, "You can't leave.");
		return;
	}
	quiet = 0;
	if ((key & MOVE_QUIET) && Controls(player, loc))
		quiet = HUSH_LEAVE;
	if (could_doit(player, loc, A_LLEAVE)) {
		move_via_generic(player, Location(loc), NOTHING, quiet);
	} else {
		oattr = quiet ? 0 : A_OLFAIL;
		aattr = quiet ? 0 : A_ALFAIL;
		did_it (player, loc, A_LFAIL, "You can't leave.",
			oattr, NULL, aattr, (char **)NULL, 0);
	}
}