/* $Header: /belch_a/users/rearl/tinymuck/src/RCS/move.c,v 1.11 90/09/16 04:42:35 rearl Exp $ */

/*
 * $Log:	move.c,v $
 * Revision 1.11  90/09/16  04:42:35  rearl
 * Preparation code added for disk-based MUCK.
 * 
 * Revision 1.10  90/09/15  22:26:57  rearl
 * Fixed moveto bug from HOME.
 * 
 * Revision 1.9  90/09/13  06:28:04  rearl
 * send_contents changed to be usable for do_toad() in wiz.c
 * 
 * Revision 1.8  90/09/10  02:20:29  rearl
 * Put exec_or_notify in the drop messages of exits.
 * 
 * Revision 1.7  90/09/04  18:39:43  rearl
 * Added some prototypes for externs.
 * 
 * Revision 1.6  90/09/01  05:59:21  rearl
 * Took out drop code for rooms, setting parent depends on @tel now.
 * 
 * Revision 1.5  90/08/27  03:31:58  rearl
 * Added environment support.
 * 
 * Revision 1.4  90/08/15  03:06:01  rearl
 * Fixed 0 PENNY_RATE bug.  Took out #ifdef GENDER.
 * 
 * Revision 1.3  90/08/05  03:19:47  rearl
 * Redid matching routines.
 * 
 * Revision 1.2  90/07/29  17:41:43  rearl
 * Fixed moveto problems relating to ROOM programs, also programs
 * go to their owner rather than "home" -- programs have no home by
 * definition.
 * 
 * Revision 1.1  90/07/19  23:03:55  casie
 * Initial revision
 * 
 *
 */

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

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

#ifdef RECYCLE
void recycle(dbref, dbref);
#endif /* RECYCLE */
void moveto_internal(dbref, dbref);
void enter_room_internal(dbref, dbref);

void moveto(dbref what, dbref where)
{
  if(can_hear(what)) {
    enter_room_internal(what, where); }
  else {
    moveto_internal(what, where); }
}

void moveto_internal(dbref what, dbref where)
{
  dbref loc;
  /* remove what from old loc */
  if((loc = DBFETCH(what)->location) != NOTHING) {
    DBSTORE(loc, contents, remove_first(DBFETCH(loc)->contents, what));
  }
  
  /* test for special cases */
  switch(where) {
  case NOTHING:
    DBSTORE(what, location, NOTHING);
    return;			/* NOTHING doesn't have contents */
  case HOME:
    switch(Typeof(what)) {
    case TYPE_PLAYER:
      where = DBFETCH(what)->link;
      break;
    case TYPE_THING:
      where = DBFETCH(what)->link;
      break;
    case TYPE_ROOM:
      where = GLOBAL_ENVIRONMENT;
      break;
    case TYPE_PROGRAM:
      where = OWNER(what);
      break;
    default:
      where = GLOBAL_ENVIRONMENT;
    }
  }
  if(parent_loop_check(what, where))
    what = PLAYER_START;
  if(parent_loop_check(what, where))
    what = GLOBAL_ENVIRONMENT;
  if(parent_loop_check(what, where))
    return;
  if(!is_ok(what)) {
    printf("Bad what: %d\n",what);
    what = PLAYER_START;
  }
  /* now put what in where */
  PUSH(what, DBFETCH(where)->contents);
  DBDIRTY(where);
  DBSTORE(what, location, where);
}

dbref reverse(dbref);
void send_contents(dbref loc, dbref dest)
{
  dbref first;
  dbref rest;
  puts("starting send_contents");
  first = DBFETCH(loc)->contents;
  DBSTORE(loc, contents, NOTHING);
  
  /* blast locations of everything in list */
  DOLIST(rest, first) {
    DBSTORE(rest, location, NOTHING);
  }
  
  while(first != NOTHING) {
    rest = DBFETCH(first)->next;
    if((Typeof(first) != TYPE_THING)
       && (Typeof(first) != TYPE_PROGRAM)) {
      moveto(first, loc);
    } else {
      moveto(first, FLAGS(first) & STICKY ? HOME : dest);
    }
    first = rest;
  }
  
  DBSTORE(loc, contents, reverse(DBFETCH(loc)->contents));
  puts("done send_contents");
}

void maybe_dropto(dbref loc, dbref dropto)
{
  dbref thing;
  
  if(loc == dropto) return;	/* bizarre special case */
  
  /* check for players */
  DOLIST(thing, DBFETCH(loc)->contents) {
    if(Typeof(thing) == TYPE_PLAYER) return;
  }
  
  /* no players, send everything to the dropto */
  send_contents(loc, dropto);
}

int parent_loop_check_internal(dbref source, dbref dest, int depth)
{
  if(depth > 20) return 1;
  if(dest == NOTHING) return 0;
  return(parent_loop_check_internal(source, DBFETCH(dest)->location,
				    depth + 1));
}

int parent_loop_check(dbref source, dbref dest)
{
  if(source == NOTHING) return 1;
  if(dest == HOME)
    dest = DBFETCH(source)->link;
  if (dest == NOTHING) return 0;
  if(Typeof(source) != TYPE_ROOM || Typeof(dest) != TYPE_ROOM) return 0;
  if (source == dest) return 1;       /* That's an easy one! */
  if(Typeof(dest) == TYPE_GARBAGE) return 1;

  return parent_loop_check_internal(source, DBFETCH(dest)->location, 0);
}

void enter_room(dbref player, dbref loc, dbref exit)
{ /*we ignore exit. */
  moveto(player,loc);
}

void enter_room_internal(dbref player, dbref loc)
{
  dbref old;
  dbref dropto;
  char buf[BUFFER_LEN];
  
  /* check for room == HOME */
  if(loc == HOME) loc = DBFETCH(player)->link; /* home */
  
  /* get old location */
  old = DBFETCH(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.", NAME(player));
	notify_except(DBFETCH(old)->contents, player, buf);
      }
    }

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

    /* if old location has STICKY dropto, send stuff through it */
    if(old != NOTHING && (dropto = DBFETCH(old)->link) != NOTHING
       && (FLAGS(old) & STICKY)) {
      maybe_dropto(old, dropto);
    }

    /* tell other folks in new location if not DARK */
    if(loc != NOTHING && !Dark(loc) && !Dark(player)) {
      sprintf(buf, "%s has arrived.", NAME(player));
      notify_except(DBFETCH(loc)->contents, player, buf);
    }
  }
  
  /* autolook */
  if(loc != NOTHING)
    look_room(player, loc);

#if (PENNY_RATE) != 0
  /* check for pennies */
  if(!controls(player, loc)
     && DBFETCH(player)->sp.player.pennies <= MAX_PENNIES
     && (random() % PENNY_RATE) == 0) {
    notify(player, "You found a penny!");
    DBFETCH(player)->sp.player.pennies++;
    DBDIRTY(player);
  }
#endif /* PENNY_RATE */
}

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, DBFETCH(thing)->link, DBFETCH(thing)->location);
    break;
  case TYPE_THING:
    send_contents(thing,HOME);
    moveto(thing, DBFETCH(thing)->link);	/* home */
    break;
  case TYPE_PROGRAM:
    moveto(thing, OWNER(thing));
    break; 
  default:
    /* no effect */
    break;
  }
  return;
}

int can_move(dbref player, const char *direction)
{
  struct match_data md;
  
  if(!string_compare(direction, "home")) return 1;
  
  /* otherwise match on exits */
  init_match(player, direction, TYPE_EXIT, &md);
  match_all_exits(&md);
  return(last_match_result(&md) != NOTHING);
}

/*
 * trigger()
 *
 * This procedure 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 < DBFETCH(exit)->sp.exit.ndest; i++) {
    dest = (DBFETCH(exit)->sp.exit.dest)[i];
    if (dest == HOME) dest = DBFETCH(player)->link;
    switch(Typeof(dest)) {
    case TYPE_ROOM:
      if (pflag) {
	if (get_attr(exit, "Drop"))
	  exec_or_notify(player, exit, get_attr(exit, "Drop"));
	if (get_attr(exit, "Odrop") && !Dark(player))
	  {
	    sprintf(buf, "%s %s", NAME(player),
		    pronoun_substitute(player, get_attr(exit, "Odrop"), exit));
	    notify_except(DBFETCH(dest)->contents, player, buf);
	  }
	enter_room(player, dest, exit);
	succ = 1;
      }
      break;
    case TYPE_THING:
      if (Typeof(DBFETCH(exit)->location) == TYPE_THING) {
	moveto (dest, DBFETCH(DBFETCH(exit)->location)->location);
	if (!(FLAGS(exit) & STICKY)) {
	  /* send home source object */
	  sobjact = 1;
	}
      } else {
	moveto (dest, DBFETCH(exit)->location);
      }
      if (get_attr(exit, "Succ")) succ = 1;
      break;
    case TYPE_EXIT:	/* It's a meta-link(tm)! */
      trigger(player, (DBFETCH(exit)->sp.exit.dest)[i], 0);
      if (get_attr(exit, "Succ")) succ = 1;
      break;
    case TYPE_PLAYER:
      if (pflag && DBFETCH(dest)->location != NOTHING) {
	succ = 1;
	if (FLAGS(dest) & JUMP_OK) {
	  if (get_attr(exit, "Drop"))
	    exec_or_notify(player, exit, get_attr(exit, "Drop"));
	  if (get_attr(exit, "Odrop") && !Dark(player)) {
	    sprintf(buf, "%s %s", NAME(player),
		    pronoun_substitute(player, get_attr(exit, "Odrop"), exit));
	    notify_except(DBFETCH(DBFETCH(dest)->location)->contents, player,
			  buf);
	  }
	  enter_room(player, DBFETCH(dest)->location, exit);
	} else {
	  notify(player, "That player does not wish to be disturbed.");
	}
      }
      break;
    case TYPE_PROGRAM:
      if (DBFETCH(player)->run) return;
      (void) interp(player, dest, exit);
      if (!(DBFETCH(player)->run -> pc))
	{
	  free((void *) DBFETCH(player)->run);
	  DBFETCH(player)->run = 0;
	  DBDIRTY(player);
	}
      return;
    }
  }
  if (sobjact) send_home(DBFETCH(exit)->location);
  if (!succ && pflag) notify(player, "Done.");
}

void do_move(dbref player, const char *direction)
{
  dbref exit;
  dbref loc;
  struct match_data md;

  if(!string_compare(direction, "home")) {
    /* send him home */
    /* but steal all his possessions */
    if((loc = DBFETCH(player)->location) != NOTHING) {
      /* tell everybody else */
      sprintf(buf, "%s goes home.", NAME(player));
      notify_except(DBFETCH(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, &md);
    match_all_exits(&md);
    switch(exit = match_result(&md)) {
    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 = DBFETCH(player)->location;
      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;
  struct match_data md;
  
  init_match_check_keys(player, what, TYPE_THING, &md);
  match_neighbor(&md);
  if(Arch(player)) match_absolute(&md); /* the wizard has long fingers */
  
  if((thing = noisy_match_result(&md)) != NOTHING) {
    if(DBFETCH(thing)->location == player) {
      notify(player, "You already have that!");
      return;
    }
    switch(Typeof(thing)) {
    case TYPE_THING:
    case TYPE_PROGRAM:
    case TYPE_PLAYER:
      if(parent_loop_check(thing,player)) {
	notify(player,"You can't take that!");
	return;
      }
      if(can_doit(player, thing, "You can't pick that up.")) {
	moveto(thing, player);
	notify(player, "Taken.");
      }
      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];
  struct match_data md;
  
  if((loc = getloc(player)) == NOTHING) return;    

  init_match(player, name, NOTYPE, &md);
  match_possession(&md);
  if ((thing = noisy_match_result(&md)) == NOTHING
      || thing == AMBIGUOUS) return;
  
  switch (Typeof(thing)) {
  case TYPE_THING:
  case TYPE_PROGRAM:
  case TYPE_PLAYER:
    if(DBFETCH(thing)->location != player) {
      /* Shouldn't ever happen. */
      notify(player, "You can't drop that.");
      break;
    }
    if ((FLAGS(thing) & STICKY) && Typeof(thing) == TYPE_THING) {
      send_home(thing);
    } else {
      int immediate_dropto = (DBFETCH(loc)->link != NOTHING
			      && !(FLAGS(loc) & STICKY));
      
      moveto(thing, (immediate_dropto && (Typeof(loc)==TYPE_ROOM)) ?
	     DBFETCH(loc)->link : loc);
    }
    if (get_attr(thing, "Drop"))
      exec_or_notify(player, thing, get_attr(thing, "Drop"));
    else
      notify(player, "Dropped.");

    if (get_attr(loc, "Drop") && Typeof(loc)==TYPE_ROOM)
      exec_or_notify(thing, loc, get_attr(loc, "Drop"));
    
    if (get_attr(thing, "Odrop")) {
      sprintf(buf, "%s %s", NAME(player),
	      pronoun_substitute(player, get_attr(thing, "Odrop"), thing));
    } else {
      sprintf(buf, "%s drops %s.", NAME(player), NAME(thing));
    }
    notify_except(DBFETCH(loc)->contents, player, buf);
    if(get_attr(thing, "Adrop")) {
      trigobj(thing, get_attr(thing, "Adrop"),player);
    }
    if(get_attr(loc, "Adrop") && Typeof(loc)==TYPE_ROOM) {
      trigobj(loc, get_attr(loc, "Adrop"),thing);
    }
    if (get_attr(loc, "Odrop") && Typeof(loc)==TYPE_ROOM) {
      sprintf(buf, "%s %s", NAME(thing),
	      pronoun_substitute(thing, get_attr(loc, "Odrop"), loc));
      notify_except(DBFETCH(loc)->contents, player, buf);
    }
    break;
  default:
    notify(player, "You can't drop that.");
    break;
  }
}

#ifdef RECYCLE
void do_recycle(dbref player, const char *name)
{
  dbref thing;
  struct match_data md;
  
  init_match(player, name, TYPE_THING, &md);
  match_all_exits(&md);
  match_neighbor(&md);
  match_possession(&md);
  match_here(&md);
  match_absolute(&md);
  
  if((thing = noisy_match_result(&md)) != NOTHING) {
    if(!controls(player, thing)) {
      notify(player, "Permission denied.");
    } else {
      switch(Typeof(thing)) {
      case TYPE_ROOM:
	if(OWNER(thing) != player) {
	  notify(player, "Permission denied.");
	  return;
	}
	if(thing == PLAYER_START || thing == GLOBAL_ENVIRONMENT) {
	  notify(player, "This room may not be recycled.");
	  return;
	}
	break;
      case TYPE_THING:
	if(OWNER(thing) != player) {
	  notify(player, "Permission denied.");
	  return;
	}
	break;
      case TYPE_EXIT:
	if(OWNER(thing) != player) {
	  notify(player, "Permission denied.");
	  return;
	}
	if(!unset_source(player, DBFETCH(player)->location, thing)) {
	  return;
	}
	break;
      case TYPE_PLAYER:
	notify(player, "You can't recycle a player!");
	return;
	/*NOTREACHED*/
	break;
      case TYPE_PROGRAM:
	if(OWNER(thing) != player) {
	  notify(player, "Permission denied.");
	  return;
	}
	break;
      case TYPE_DAEMON:
	if(OWNER(thing) != player) {
	  notify(player, "Permission denied.");
	  return;
	}
	break;
      case TYPE_GARBAGE:
	notify(player, "That's already garbage!");
	return;
	/*NOTREACHED*/
	break;
      }
      recycle(player, thing);
      notify(player,"Thank you for recycling.");
    }
  }
}

void recycle(dbref player, dbref thing)
{
  extern dbref recyclable;
  static int depth = 0;
  dbref first;
  dbref rest;
  char buf[2048];
  
  depth++;
  switch(Typeof(thing)) {
  case TYPE_ROOM:
    DBFETCH(OWNER(thing))->sp.player.pennies += ROOM_COST;
    DBDIRTY(OWNER(thing));
    for(first = DBFETCH(thing)->exits; first != NOTHING; first = rest) {
      rest = DBFETCH(first)->next;
      if(DBFETCH(first)->location == NOTHING || DBFETCH(first)->location == thing)
	recycle(player, first);
    }
    notify_except(DBFETCH(thing)->contents, NOTHING,
		  "The room shakes and crumbles...");
    break;
  case TYPE_THING:
    halt_long(thing);
    DBFETCH(OWNER(thing))->sp.player.pennies +=
	DBFETCH(thing)->sp.thing.value;
    DBDIRTY(OWNER(thing));
    for(first = DBFETCH(thing)->exits; first != NOTHING;
	first = rest) {
      rest = DBFETCH(first)->next;
      if(DBFETCH(first)->location == NOTHING
	 || DBFETCH(first)->location == thing)
	recycle(player, first);
    }
    notify_except(DBFETCH(thing)->contents, NOTHING,
		  "This place shakes and crumbles...");
    break;
  case TYPE_EXIT:
    DBFETCH(OWNER(thing))->sp.player.pennies += EXIT_COST;
    if(DBFETCH(thing)->sp.exit.ndest != 0)
	DBFETCH(OWNER(thing))->sp.player.pennies += LINK_COST;
    DBDIRTY(OWNER(thing));
    break;
  case TYPE_PROGRAM:
    sprintf(buf, "muf/%d.m", (int) thing);
    unlink(buf);
    break;
  }
  
  for(rest = 0; rest < db_top; rest++) {
    switch(Typeof(rest)) {
    case TYPE_ROOM:
      if(DBFETCH(rest)->link == thing)
	DBFETCH(rest)->link = NOTHING;
      if(DBFETCH(rest)->exits == thing)
	DBFETCH(rest)->exits = DBFETCH(thing)->next;
      if(OWNER(rest) == thing)
	OWNER(rest) = GOD;
      break;
    case TYPE_THING:
      if(DBFETCH(rest)->link == thing) {
	if(DBFETCH(OWNER(rest))->link == thing)
	  DBSTORE(OWNER(rest), link, PLAYER_START);
	DBFETCH(rest)->link = DBFETCH(OWNER(rest))->link;
      }
      if(DBFETCH(rest)->exits == thing)
	DBFETCH(rest)->exits = DBFETCH(thing)->next;
      if(OWNER(rest) == thing)
	OWNER(rest) = GOD;
      break;
    case TYPE_EXIT:
      {
	int i, j;
	
	for (i = j = 0; i < DBFETCH(rest)->sp.exit.ndest; i++) {
	  if((DBFETCH(rest)->sp.exit.dest)[i] != thing)
	    (DBFETCH(rest)->sp.exit.dest)[j++] =
	      (DBFETCH(rest)->sp.exit.dest)[i];
	}
	if(j < DBFETCH(rest)->sp.exit.ndest) {
	    DBFETCH(OWNER(rest))->sp.player.pennies += LINK_COST;
	    DBDIRTY(OWNER(rest));
	    DBFETCH(rest)->sp.exit.ndest = j;
	}
      }
      if(OWNER(rest) == thing)
	OWNER(rest) = GOD;
      break;
    case TYPE_PLAYER:
      if(DBFETCH(rest)->link == thing)
	DBFETCH(rest)->link = PLAYER_START;
      if(DBFETCH(rest)->exits == thing)
	DBFETCH(rest)->exits = DBFETCH(thing)->next;
      if(DBFETCH(rest)->curr_prog == thing)
	DBFETCH(rest)->curr_prog = 0;
      break;
    case TYPE_PROGRAM:
      if(OWNER(rest) == thing)
	OWNER(rest) = GOD;
      break;
    case TYPE_DAEMON:
      if(OWNER(rest) == thing)
	OWNER(rest) = GOD;
    }
    if(DBFETCH(rest)->location == thing)
      DBFETCH(rest)->location = NOTHING;
    if(DBFETCH(rest)->contents == thing)
      DBFETCH(rest)->contents = DBFETCH(thing)->next;
    if(DBFETCH(rest)->next == thing)
      DBFETCH(rest)->next = DBFETCH(thing)->next;
    DBDIRTY(rest);
  }
  
  for(first = DBFETCH(thing)->contents; first != NOTHING; first = rest) {
    rest = DBFETCH(first)->next;
    if(Typeof(first) == TYPE_PLAYER)
      enter_room(first, HOME, DBFETCH(thing)->location);
    else
      moveto(first, HOME);
  }
  moveto(thing, NOTHING);
  --depth;
  
  db_free_object(thing);
  db_clear_object(thing);
  NAME(thing) = "<garbage>";
  add_attr(thing, "Desc", "<recyclable>");
  DBFETCH(thing)->key = TRUE_BOOLEXP;
  FLAGS(thing) = TYPE_GARBAGE;
  
  DBFETCH(thing)->next = recyclable;
  recyclable = thing;
  DBDIRTY(thing);
}
#endif /* RECYCLE */

void do_enter(dbref player, const char *thing)
{
  dbref what;
  dbref where = DBFETCH(player) -> location;
  struct match_data md;
  init_match(player,thing,TYPE_THING,&md);
  match_neighbor(&md);
  if(Arch(player)) {
    match_absolute(&md);
    match_player(&md);
  }
  what = noisy_match_result(&md);
  if(!is_ok(what))
    return;
  if(((FLAGS(what)&JUMP_OK) ||
     controls(player,what)) &&
     (Typeof(what)==TYPE_PLAYER || Typeof(what)==TYPE_THING)) {
    if(get_attr(what,"Enter"))
      notify(player, get_attr(what,"Enter"));
    if(get_attr(what,"Oenter") && !Dark(player)) {
      sprintf(buf,"%%n %s", get_attr(what,"Oenter"));
      notify_except(DBFETCH(where)->contents, player,
		    pronoun_substitute(player, buf, what));
    }
    moveto(player, what);
    if(get_attr(what, "Aenter"))
      trigobj(what, get_attr(what, "Aenter"), player);
  } else {
    notify(player,"Permission denied.");
  }
}

void do_leave(dbref player)
{
  dbref where = DBFETCH(player)->location;

  if(Typeof(where) == TYPE_ROOM)
    notify(player, "You can't leave.");
  else {
    if(get_attr(where, "Leave"))
      notify(player, get_attr(where, "Enter"));
    if(get_attr(where, "Oleave") && !Dark(player)) {
      sprintf(buf, "%%n %s", get_attr(where, "Oleave"));
      notify_except(DBFETCH(where)->contents, player,
		    pronoun_substitute(player, buf, where));
    }
    moveto(player, DBFETCH(where)->location);
    if(get_attr(where, "Aleave"))
      trigobj(where, get_attr(where, "Aleave"), player);
  }
}