/* $Header: move.c,v 2.0 90/05/05 12:45:38 lachesis Exp $
 * $Log:	move.c,v $
 * Revision 2.0  90/05/05  12:45:38  lachesis
 * Incorporated ABODE and HAVEN flags (remembering to convert FireFoot's
 * usage of those flags to ours).
 * Added Examine of objects that don't belong to you, added GOD_PRIV.
 * 
 * Revision 1.3  90/04/24  14:46:32  lachesis
 * Fixed the @odrop on rooms to links.
 * 
 * Revision 1.2  90/04/20  14:06:47  lachesis
 * Added @odrop && @drop.
 * 
 * Revision 1.1  90/04/14  14:56:47  lachesis
 * Initial revision
 * 
 */
#include "copyright.h"

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

void moveto(dbref what, dbref where)
{
    dbref loc;

    /* remove what from old loc */
    if((loc = db[what].location) != NOTHING) {
	db[loc].contents = remove_first(db[loc].contents, what);
    }

    /* test for special cases */
    switch(where) {
      case NOTHING:
	db[what].location = NOTHING;
	return;			/* NOTHING doesn't have contents */
      case HOME:
	switch(Typeof(what)) {
	  case TYPE_PLAYER:
	    where = db[what].sp.player.home;
	    break;
	  case TYPE_THING:
	    where = db[what].sp.thing.home;
	    break;
	}
    }

    /* now put what in where */
    PUSH(what, db[where].contents);

    db[what].location = where;
}

void send_contents(dbref loc, dbref dest)
{
    dbref first;
    dbref rest;

    first = db[loc].contents;
    db[loc].contents = NOTHING;

    /* blast locations of everything in list */
    DOLIST(rest, first) {
	db[rest].location = NOTHING;
    }

    while(first != NOTHING) {
	rest = db[first].next;
	if(Typeof(first) != TYPE_THING) {
	    moveto(first, loc);
	} else {
	    moveto(first, db[first].flags & STICKY ? HOME : dest);
	}
	first = rest;
    }

    db[loc].contents = reverse(db[loc].contents);
}

void maybe_dropto(dbref loc, dbref dropto)
{
    dbref thing;

    if(loc == dropto) return;	/* bizarre special case */

    /* check for players */
    DOLIST(thing, db[loc].contents) {
	if(Typeof(thing) == TYPE_PLAYER) return;
    }
    
    /* no players, send everything to the dropto */
    send_contents(loc, dropto);
}

void enter_room(dbref player, dbref loc)
{
    dbref old;
    dbref dropto;
    char buf[BUFFER_LEN];

    /* check for room == HOME */
    if(loc == HOME) loc = db[player].sp.player.home; /* home */

    /* get old location */
    old = db[player].location;

    /* check for self-loop */
    /* self-loops don't do move or other player notification */
    /* but you still get autolook and penny check */
    if(loc != old) {

	if(old != NOTHING) {
	    /* notify others unless DARK */
	    if(!Dark(old) && !Dark(player)) {
		sprintf(buf, "%s has left.", db[player].name);
		notify_except(db[old].contents, player, buf);
	    }
	}

	/* go there */
	moveto(player, loc);

	/* if old location has STICKY dropto, send stuff through it */
	if(old != NOTHING
	   && (dropto = db[old].sp.room.dropto) != NOTHING
	   && (db[old].flags & STICKY)) {
	    maybe_dropto(old, dropto);
	}

	/* tell other folks in new location if not DARK */
	if(!Dark(loc) && !Dark(player)) {
	    sprintf(buf, "%s has arrived.", db[player].name);
	    notify_except(db[loc].contents, player, buf);
	}
    }

    /* autolook */
    look_room(player, loc);

    /* check for pennies */
    if(!controls(player, loc)
       && db[player].sp.player.pennies <= MAX_PENNIES
       && random() % PENNY_RATE == 0) {
	notify(player, "You found a cookie!");
	db[player].sp.player.pennies++;
    }
}
	    
void send_home(dbref thing)
{
    switch(Typeof(thing)) {
      case TYPE_PLAYER:
	/* send his possessions home first! */
	/* that way he sees them when he arrives */
	send_contents(thing, HOME);
	enter_room(thing, db[thing].sp.player.home); /* home */
	break;
      case TYPE_THING:
	moveto(thing, db[thing].sp.thing.home);	/* home */
	break;
      default:
	/* no effect */
	break;
    }
}
    
int can_move(dbref player, const char *direction)
{
    if(!string_compare(direction, "home")) return 1;

    /* otherwise match on exits */
    init_match(player, direction, TYPE_EXIT);
    match_all_exits();
    return(last_match_result() != NOTHING);
}

/*
 * trigger()
 *
 * This procudure triggers a series of actions, or meta-actions
 * which are contained in the 'dest' field of the exit.
 * Locks other than the first one are over-ridden.
 * 
 * `player' is the player who triggered the exit
 * `exit' is the exit triggered
 * `pflag' is a flag which indicates whether player and room exits
 * are to be used (non-zero) or ignored (zero).  Note that
 * player/room destinations triggered via a meta-link are
 * ignored.
 *
 */

void trigger(dbref player, dbref exit, int pflag)
{
    int  i;
    dbref  dest;
    int  sobjact;	/* sticky object action flag, sends home source obj */
    int  succ;
    char buf[BUFFER_LEN];

    sobjact = 0;
    succ = 0;

    for (i = 0; i < db[exit].sp.exit.ndest; i++) {
	dest = (db[exit].sp.exit.dest)[i];
	if (dest == HOME) dest = db[player].sp.player.home;
	switch(Typeof(dest)) {
	  case TYPE_ROOM:
	    if (pflag) {
	        if (db[exit].drop_message)
		  notify(player, db[exit].drop_message);
		if (db[exit].odrop)
		  {
#ifdef GENDER
		    pronoun_substitute(buf, player, db[exit].odrop, 1);
#else /* GENDER */
		    sprintf(buf, "%s %s", db[player].name, db[exit].odrop);
#endif /* GENDER */
		    notify_except(db[dest].contents, player, buf);
		  }
		enter_room(player, dest);
		succ = 1;
	    }
	    break;
	  case TYPE_THING:
	    if (Typeof(db[exit].location) == TYPE_THING) {
		moveto (dest, db[db[exit].location].location);
		if (!(db[exit].flags & STICKY)) {
		    /* send home source object */
		    sobjact = 1;
		}
	    } else {
		moveto (dest, db[exit].location);
	    }
	    if (db[exit].succ_message) succ = 1;
	    break;
	  case TYPE_EXIT:	/* It's a meta-link(tm)! */
	    trigger(player, (db[exit].sp.exit.dest)[i], 0);
	    if (db[exit].succ_message) succ = 1;
	    break;
	  case TYPE_PLAYER:
	    if (pflag && db[dest].location != NOTHING) {
		succ = 1;
		if (db[dest].flags & JUMP_OK) {
	        if (db[exit].drop_message)
  		    notify(player, db[exit].drop_message);
		    if (db[exit].odrop)
		      {
#ifdef GENDER
			char buf[BUFFER_LEN];
			pronoun_substitute(buf, player, db[exit].odrop, 1);
			notify_except(db[db[player].location].contents, player, buf);
#else /* GENDER */
			notify_except(db[db[player].location].contents, player, db[exit].odrop);
#endif
		      }
		    enter_room(player, db[dest].location);
		}
		else {
		    notify(player,
		    	   "That player does not wish to be disturbed.");
		}
	    }
	    break;
	}
    }
    if (sobjact) send_home(db[exit].location);
    if (!succ && pflag) notify(player, "Done.");
}

void do_move(dbref player, const char *direction)
{
    dbref exit;
    dbref loc;
    char buf[BUFFER_LEN];

    if(!string_compare(direction, "home")) {
	/* send him home */
	/* but steal all his possessions */
	if((loc = db[player].location) != NOTHING) {
	    /* tell everybody else */
	    sprintf(buf, "%s goes home.", db[player].name);
	    notify_except(db[loc].contents, player, buf);
	}
	/* give the player the messages */
	notify(player, "There's no place like home...");
	notify(player, "There's no place like home...");
	notify(player, "There's no place like home...");
	notify(player, "You wake up back home, without your possessions.");
	send_home(player);
    } else {
	/* find the exit */
	init_match_check_keys(player, direction, TYPE_EXIT);
	match_all_exits();
	switch(exit = match_result()) {
	  case NOTHING:
	    notify(player, "You can't go that way.");
	    break;
	  case AMBIGUOUS:
	    notify(player, "I don't know which way you mean!");
	    break;
	  default:
	    /* we got one */
	    /* check to see if we got through */
	    loc = db[player].location;
	    if( !(db[loc].flags & JUMP_OK)
	            && (db[exit].location != NOTHING)
		    && (Typeof(db[exit].location) == TYPE_PLAYER ||
			Typeof(db[exit].location) == TYPE_THING)
		    && db[exit].sp.exit.owner != db[loc].sp.room.owner
		    && db[exit].location != loc) {
		notify(player, "You can't do that here.");
	    } else if(can_doit(player, exit, "You can't go that way.")) {
		trigger(player, exit, 1);
	    }
	    break;
	}
    }
}

void do_get(dbref player, const char *what)
{
    dbref thing;

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

    if((thing = noisy_match_result()) != NOTHING) {
	if(db[thing].location == player) {
	    notify(player, "You already have that!");
	    return;
	}
	switch(Typeof(thing)) {
	  case TYPE_THING:
	    if(can_doit(player, thing, "You can't pick that up.")) {
		moveto(thing, player);
		notify(player, "Taken.");
	    }
	    break;
	  case TYPE_EXIT:
	    if (controls(player, thing)) {
		notify(player, "Use @attach exit=me to move an exit.");
	    } else {
		notify(player, "I don't see that here.");
	    }
	    break;
	  default:
	    notify(player, "You can't take that!");
	    break;
	}
    }
}

void do_drop(dbref player, const char *name)
{
    dbref loc;
    dbref thing;
    char buf[BUFFER_LEN];
    int reward;

    if((loc = getloc(player)) == NOTHING) return;    

    init_match(player, name, TYPE_THING);
    match_player_actions();
    match_possession();

    switch(thing = match_result()) {
      case NOTHING:
	notify(player, "You don't have that!");
	break;
      case AMBIGUOUS:
	notify(player, "I don't know which you mean!");
	break;
      default:
	if(db[thing].location != player) {
	    /* Shouldn't ever happen. */
	    notify(player, "You can't drop that.");
	} else if(Typeof(thing) == TYPE_EXIT) {
	    notify(player, "Use @attach exit=here to drop an exit.");
	    break;
	} else if(db[loc].flags & TEMPLE) {
	    /* sacrifice time */
	    send_home(thing);
	    sprintf(buf,"A kindly nurse takes %s from you and puts it out of your view.",db[thing].name);
	    notify(player, buf);
	    sprintf(buf, "%s hands %s to the nurse.",
		    db[player].name, db[thing].name);
	    notify_except(db[loc].contents, player, buf);

	    /* check for reward */
	    if(!controls(player, thing)) {
		reward = db[thing].sp.thing.value;
		if(reward < 1 || db[player].sp.player.pennies > MAX_PENNIES) {
		    reward = 1;
		} else if(reward > MAX_OBJECT_ENDOWMENT) {
		    reward = MAX_OBJECT_ENDOWMENT;
		}

		db[player].sp.player.pennies += reward;
		sprintf(buf,
                        "The nurse gives you %d %s for returning this item.",
			reward,
			reward == 1 ? "cookie" : "cookies");
		notify(player, buf);
	    }
	} else if(db[thing].flags & STICKY) {
	    send_home(thing);
	    if (db[thing].drop_message)
	      notify(player, db[thing].drop_message);
	    else
	      notify(player, "Dropped.");
	    if (db[loc].drop_message)
	      notify(player, db[loc].drop_message);
	} else if(db[loc].sp.room.dropto != NOTHING && !(db[loc].flags &
		  STICKY)) {
	    /* location has immediate dropto */
	    moveto(thing, db[loc].sp.room.dropto);
	    if (db[thing].drop_message)
	      notify(player, db[thing].drop_message);
	    else
	      notify(player, "Dropped.");
	    if (db[loc].drop_message)
	      notify(player, db[loc].drop_message);
	} else {
	    moveto(thing, loc);
	    if (db[thing].drop_message)
	      notify(player, db[thing].drop_message);
	    else
	      notify(player, "Dropped.");
	    if (db[loc].drop_message)
	      notify(player, db[loc].drop_message);
	    if (db[thing].odrop)
	      {
#ifdef GENDER
		pronoun_substitute(buf, player, db[thing].odrop, 1);
#else /* GENDER */
		sprintf(buf, "%s %s", db[player].name, db[thing].odrop);
#endif /* GENDER */
	      }
	    else
	      sprintf(buf, "%s dropped %s.", db[player].name, db[thing].name);
	    notify_except(db[loc].contents, player, buf);
	    if (db[loc].odrop)
	      {
#ifdef GENDER
		pronoun_substitute(buf, thing, db[loc].odrop, 1);
#else /* !GENDER */
		sprintf(buf, "%s %s", db[thing].name, db[loc].odrop);
#endif /* GENDER */
		notify_except(db[loc].contents, player, buf);
	      }
	}
	break;
    }
}

#ifdef RECYCLE
void recycle(dbref player, dbref thing)
{
    extern dbref recyclable;
    struct object *o = db+thing;
    dbref first;
    dbref rest;

    switch(Typeof(thing)) {
      case TYPE_ROOM:
	for(first = db[thing].sp.room.exits; first != NOTHING; first = rest) {
	    rest = db[first].next;
	    if(db[first].location == NOTHING || db[first].location == thing)
		recycle(player, first);
	}
	notify_except(db[thing].contents, NOTHING,
		      "You feel a wrenching sensation...");
	break;
      case TYPE_THING:
	for(first = db[thing].sp.thing.actions; first != NOTHING;
	    first = rest) {
	    rest = db[first].next;
	    if(db[first].location == NOTHING || db[first].location == thing)
		recycle(player, first);
	}
	break;
    }

    for(rest = 0; rest < db_top; rest++) {
	switch(Typeof(rest)) {
	    case TYPE_ROOM:
		if(db[rest].sp.room.dropto == thing)
		    db[rest].sp.room.dropto = NOTHING;
		if(db[rest].sp.room.exits == thing)
		    db[rest].sp.room.exits = db[thing].next;
		if(db[rest].sp.room.owner == thing)
		    db[rest].sp.room.owner = GOD;
		break;
	    case TYPE_THING:
		if(db[rest].sp.thing.home == thing) {
		    if(db[db[rest].sp.thing.owner].sp.player.home == thing)
			db[db[rest].sp.thing.owner].sp.player.home =
			    PLAYER_START;
		    db[rest].sp.thing.home =
			db[db[rest].sp.thing.owner].sp.player.home;
		}
		if(db[rest].sp.thing.actions == thing)
		    db[rest].sp.thing.actions = db[thing].next;
		if(db[rest].sp.thing.owner == thing)
		    db[rest].sp.thing.owner = GOD;
		break;
	    case TYPE_EXIT:
		{
		    int i, j;
		    
		    for (i = j = 0; i < db[rest].sp.exit.ndest; i++) {
			if((db[rest].sp.exit.dest)[i] != thing)
			    (db[rest].sp.exit.dest)[j++] =
				(db[rest].sp.exit.dest)[i];
		    }
		    db[rest].sp.exit.ndest = j;
		}
		if(db[rest].sp.exit.owner == thing)
		    db[rest].sp.exit.owner = GOD;
		break;
	    case TYPE_PLAYER:
		if(db[rest].sp.player.home == thing)
		    db[rest].sp.player.home = PLAYER_START;
		if(db[rest].sp.player.actions == thing)
		    db[rest].sp.player.actions = db[thing].next;
		break;
	}
	if(db[rest].location == thing)
	    db[rest].location = NOTHING;
	if(db[rest].contents == thing)
	    db[rest].contents = db[thing].next;
	if(db[rest].next == thing)
	    db[rest].next = db[thing].next;
    }

    for(first = db[thing].contents; first != NOTHING; first = rest) {
	rest = db[first].next;
	if(Typeof(first) == TYPE_PLAYER)
	    enter_room(first, HOME);
	else
	    moveto(first, HOME);
    }
    
    moveto(thing, NOTHING);
    db_free_object(thing);
    db_clear_object(thing);
    o->name = "<garbage>";
    o->description = "<recyclable>";
    o->key = TRUE_BOOLEXP;
    o->flags = TYPE_GARBAGE;
    
    o->next = recyclable;
    recyclable = thing;
    
}

void do_recycle(dbref player, const char *name)
{
    dbref thing;

    init_match(player, name, TYPE_THING);
    match_all_exits();
    match_neighbor();
    match_possession();
    match_here();
    if(Wizard(player)) {
	match_absolute();
    }

    if((thing = noisy_match_result()) != NOTHING) {
	if(!controls(player, thing)) {
	    notify(player, "Permission denied.");
	} else {
	    switch(Typeof(thing)) {
	      case TYPE_ROOM:
		if(db[thing].sp.room.owner != player) {
		    notify(player, "Permission denied.");
		    return;
		}
		if(thing == PLAYER_START) {
		    notify(player, "This room may not be recycled.");
		    return;
		}
		break;
	      case TYPE_THING:
		if(db[thing].sp.thing.owner != player) {
		    notify(player, "Permission denied.");
		    return;
		}
		break;
	      case TYPE_EXIT:
		if(db[thing].sp.exit.owner != player) {
		    notify(player, "Permission denied.");
		    return;
		}
		if(!unset_source(player, db[player].location, thing,
				 "You can't recycle an exit in another room."))
		    return;
		break;
	      case TYPE_PLAYER:
		notify(player, "You can't recycle a player!");
		return;
	      case TYPE_GARBAGE:
		notify(player, "That's already garbage!");
		return;
	    }
	    recycle(player, thing);
	    notify(player, "Thank you for recycling.");
	}
    }
}
    
#endif /* RECYCLE */