pennmush/game/
pennmush/game/data/
pennmush/game/log/
pennmush/game/save/
pennmush/game/txt/evt/
pennmush/game/txt/nws/
pennmush/os2/
/* move.c */

#include "copyrite.h"
#include "config.h"

#include <ctype.h>
#ifdef I_STRING
#include <string.h>
#else
#include <strings.h>
#endif

#include "conf.h"
#include "mushdb.h"
#include "intrface.h"
#include "match.h"
#include "externs.h"
#include "globals.h"
#include "parse.h"
#include "confmagic.h"

extern void do_destroy _((dbref player, char *name, int confirm));

void moveto _((dbref what, dbref where));
void moveit _((dbref what, dbref where));
static void send_contents _((dbref loc, dbref dest));
static void maybe_dropto _((dbref loc, dbref dropto));
void enter_room _((dbref player, dbref loc));
void safe_tel _((dbref player, dbref dest));
int can_move _((dbref player, const char *direction));
static dbref find_var_dest _((dbref player, dbref exit_obj));
void do_move _((dbref player, const char *direction, int type));
void do_firstexit _((dbref player, const char *what));
void do_get _((dbref player, const char *what));
void do_drop _((dbref player, const char *name));
void do_enter _((dbref player, const char *what, int is_alias));
void do_leave _((dbref player));
dbref global_exit _((dbref player, const char *direction));
dbref remote_exit _((dbref player, const char *direction));
void move_wrapper _((dbref player, const char *command));


void
moveto(what, where)
    dbref what;
    dbref where;
{
  enter_room(what, where);
}

void
moveit(what, where)
    dbref what;
    dbref where;
{
  dbref loc, old;

  /* remove what from old loc */
  if ((loc = old = Location(what)) != NOTHING) {
    Contents(loc) = remove_first(Contents(loc), what);
  }
  /* test for special cases */
  switch (where) {
  case NOTHING:
    Location(what) = NOTHING;
    return;			/* NOTHING doesn't have contents */
  case HOME:
    where = Exits(what);	/* home */
    safe_tel(what, where);
    return;
    /*NOTREACHED */
    break;
  }

  /* now put what in where */
  PUSH(what, Contents(where));

  Location(what) = where;
  if (!WIZ_NOAENTER || !(Wizard(what) && Dark(what)))
    if ((where != NOTHING) && (old != where)) {
      did_it(what, what, NULL, NULL, "OXMOVE", NULL, NULL, old);
      if (Hearer(what)) {
	did_it(what, old, "LEAVE", NULL, "OLEAVE", "has left.",
	       "ALEAVE", old);
	if (Typeof(where) != TYPE_ROOM)
	  did_it(what, where, NULL, NULL, "OXENTER", NULL, NULL, old);
	if (Typeof(old) != TYPE_ROOM)
	  did_it(what, old, NULL, NULL, "OXLEAVE", NULL, NULL, where);
	did_it(what, where, "ENTER", NULL, "OENTER", "has arrived.",
	       "AENTER", where);
      } else {
	/* non-listeners only trigger the actions not the messages */
	did_it(what, old, NULL, NULL, NULL, NULL, "ALEAVE", old);
	did_it(what, where, NULL, NULL, NULL, NULL, "AENTER", where);
      }
    }
  did_it(what, what, "MOVE", NULL, "OMOVE", NULL, "AMOVE", where);
}

#define Dropper(thing) (Hearer(thing) && Connected(Owner(thing)))

static void
send_contents(loc, dest)
    dbref loc;
    dbref dest;
{
  dbref first;
  dbref rest;
  first = Contents(loc);
  Contents(loc) = NOTHING;

  /* blast locations of everything in list */
  DOLIST(rest, first) {
    Location(rest) = NOTHING;
  }

  while (first != NOTHING) {
    rest = Next(first);
    if (Dropper(first)) {
      Location(first) = loc;
      PUSH(first, Contents(loc));
    } else
      enter_room(first, Sticky(first) ? HOME : dest);
    first = rest;
  }

  Contents(loc) = reverse(Contents(loc));
}

static void
maybe_dropto(loc, dropto)
    dbref loc;
    dbref dropto;
{
  dbref thing;
  if (loc == dropto)
    return;			/* bizarre special case */
  if (Typeof(loc) != TYPE_ROOM)
    return;
  /* check for players */
  DOLIST(thing, Contents(loc)) {
    if (Dropper(thing))
      return;
  }

  /* no players, send everything to the dropto */
  send_contents(loc, dropto);
}

void
enter_room(player, loc)
    dbref player;
    dbref loc;
{
  dbref old;
  dbref dropto;
  static int deep = 0;
  if (deep++ > 15) {
    deep--;
    return;
  }
  if (!GoodObject(player)) {
    deep--;
    return;
  }
  /* check for room == HOME */
  if (loc == HOME)
    loc = Home(player);

  if ((Typeof(player) != TYPE_PLAYER) && (Typeof(player) != TYPE_THING)) {
    fprintf(stderr, "ERROR: Non object moved!! %d\n", player);
    fflush(stderr);
    deep--;
    return;
  }
  if (Typeof(loc) == TYPE_EXIT) {
    fprintf(stderr, "ERROR: Attempt to move %d to exit %d\n", player, loc);
    deep--;
    return;
  }
  if (loc == player) {
    fprintf(stderr, "ERROR: Attempt to move player %d into itself\n", player);
    deep--;
    return;
  }
  /* get old location */
  old = Location(player);

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

  /* if old location has STICKY dropto, send stuff through it */

  if ((loc != old) && Dropper(player) &&
      (old != NOTHING) && (Typeof(old) == TYPE_ROOM) &&
      ((dropto = Location(old)) != NOTHING) && Sticky(old))
    maybe_dropto(old, dropto);


  /* autolook */
  look_room(player, loc, 2);
  deep--;
}


/* teleports player to location while removing items they shouldnt take */
void
safe_tel(player, dest)
    dbref player;
    dbref dest;
{
  dbref first;
  dbref rest;
  if (dest == HOME)
    dest = Home(player);
  if (Owner(Location(player)) == Owner(dest)) {
    enter_room(player, dest);
    return;
  }
  first = Contents(player);
  Contents(player) = NOTHING;

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

  while (first != NOTHING) {
    rest = db[first].next;
    /* if thing is ok to take then move to player else send home.
     * thing is not okay to move if it's STICKY and its home is not
     * the player.
     */
    if (!controls(player, first) && (Sticky(first) && (Home(first) != player)))
      enter_room(first, HOME);
    else {
      PUSH(first, Contents(player));
      db[first].location = player;
    }
    first = rest;
  }
  Contents(player) = reverse(Contents(player));
  enter_room(player, dest);
}

int
can_move(player, direction)
    dbref player;
    const char *direction;
{
#ifdef FIXED_FLAG
  if (!strcasecmp(direction, "home") && !Fixed(Owner(player)))
#else
  if (!strcasecmp(direction, "home"))
#endif
    return 1;

  /* otherwise match on exits */
  return (last_match_result(player, direction, TYPE_EXIT, MAT_EXIT | MAT_ABSOLUTE) != NOTHING);
}

static dbref
find_var_dest(player, exit_obj)
    dbref player;
    dbref exit_obj;
{
  /* This is used to evaluate the u-function DESTINATION on an exit with
   * a VARIABLE (ambiguous) link.
   */

  char const *abuf, *ap;
  char buff[BUFFER_LEN], *bp;
  ATTR *a;
  dbref dest_room;

  a = atr_get(exit_obj, "DESTINATION");
  if (!a)
    return NOTHING;

  abuf = safe_uncompress(a->value);
  if (!abuf)
    return NOTHING;
  if (!*abuf) {
    free((Malloc_t) abuf);
    return NOTHING;
  }
  ap = abuf;
  bp = buff;
  process_expression(buff, &bp, &ap, exit_obj, player, player,
		     PE_DEFAULT, PT_DEFAULT, NULL);
  *bp = '\0';
  dest_room = parse_dbref(buff);

  free((Malloc_t) abuf);
  return (dest_room);
}


void
do_move(player, direction, type)
    dbref player;
    const char *direction;
    int type;


				/* type 0 is normal, type 1 is global */
{
  dbref exit_m, loc, var_dest;

  if (!strcasecmp(direction, "home")) {
    /* send him home */
    /* but steal all his possessions */
    if ((loc = Location(player)) != NOTHING && !Dark(player) && !Dark(loc)) {
      /* tell everybody else */
      notify_except(Contents(loc), player,
		    tprintf("%s goes home.", db[player].name));
    }
    /* 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...");
    safe_tel(player, HOME);
  } else {
    /* find the exit */
    if (DO_GLOBALS && (type == 1))
      exit_m = match_result(player, direction, TYPE_EXIT, MAT_EXIT | MAT_GLOBAL | MAT_CHECK_KEYS | MAT_ABSOLUTE);
    else if (DO_GLOBALS && (type == 2))
      exit_m = match_result(player, direction, TYPE_EXIT, MAT_EXIT | MAT_REMOTES | MAT_CHECK_KEYS | MAT_ABSOLUTE);
    else
      exit_m = match_result(player, direction, TYPE_EXIT, MAT_EXIT | MAT_CHECK_KEYS | MAT_ABSOLUTE);
    if (!Hasprivs(player) && GoodObject(exit_m) && is_dbref(direction) && (Source(exit_m) != Location(player)))
      exit_m = NOTHING;
    switch (exit_m) {
    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:
      /* we got one */
      /* check to see if we got through */
      if (could_doit(player, exit_m)) {

	switch (Location(exit_m)) {
	case HOME:
	  var_dest = Exits(player);
	  break;
	case AMBIGUOUS:
	  var_dest = find_var_dest(player, exit_m);
	  break;
	default:
	  var_dest = Location(exit_m);
	}

	did_it(player, exit_m, "SUCCESS", NULL, "OSUCCESS", NULL,
	       "ASUCCESS", NOTHING);
	did_it(player, exit_m, "DROP", NULL, "ODROP", NULL, "ADROP",
	       var_dest);

/* This comes from Talek's patch. Looks to me like it interferes with
 * normal operation of unlinked exits, though?   -- Amby.
 */
/* It doesn't, because unlinked exits were caught in could_doit(),
 * 25 lines back.  Reinstating to prevent core dumps.  - Talek
 */

	if (!GoodObject(var_dest)) {
	  fprintf(stderr,
		  "Exit #%d destination became %d during move.\n",
		  exit_m, var_dest);
	  notify(player, "Exit destination is invalid.");
	  return;
	}
	switch (Typeof(var_dest)) {

	case TYPE_ROOM:
	  enter_room(player, var_dest);
	  break;

	case TYPE_PLAYER:
	case TYPE_THING:
	  if (Destroyed(var_dest)) {
	    notify(player, "You can't go that way.");
	    return;
	  }
	  if (Location(var_dest) == NOTHING)
	    return;
	  safe_tel(player, var_dest);
	  break;
	case TYPE_EXIT:
	  notify(player, "This feature coming soon.");
	  break;
	}
      } else
	did_it(player, exit_m, "FAILURE", "You can't go that way.",
	       "OFAILURE", NULL, "AFAILURE", NOTHING);
      break;
    }
  }
}

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

  if ((thing = noisy_match_result(player, what, TYPE_EXIT, MAT_EXIT)) == NOTHING)
    return;

  loc = Home(thing);
  if (!controls(player, loc)) {
    notify(player, "You cannot modify exits in that room.");
    return;
  }
  Exits(loc) = remove_first(Exits(loc), thing);
  Home(thing) = loc;
  PUSH(thing, Exits(loc));
  notify(player, tprintf("%s is now the first exit.", Name(thing)));
}


void
do_get(player, what)
    dbref player;
    const char *what;
{
  dbref loc = Location(player);
  dbref thing;
  char tbuf1[BUFFER_LEN];
  long match_flags = MAT_NEIGHBOR | MAT_ABSOLUTE | MAT_CHECK_KEYS;

  if ((Typeof(loc) != TYPE_ROOM) && !(db[loc].flags & ENTER_OK) &&
      !controls(player, loc)) {
    notify(player, "Permission denied.");
    return;
  }
  if (!Long_Fingers(player))
    match_flags |= MAT_CONTROL;

  if (match_result(player, what, TYPE_THING, match_flags) == NOTHING) {
    if (POSSESSIVE_GET) {
      /* take care of possessive get (stealing) */
      thing = parse_match_possessive(player, what);
      if (!GoodObject(thing)) {
	notify(player, "I don't see that here.");
	return;
      }
      /* to steal something, you have to be able to get it, and the
       * object must be ENTER_OK and not locked against you.
       */
      if (could_doit(player, thing) &&
	  (POSSGET_ON_DISCONNECTED ||
	   ((Typeof(Location(thing)) != TYPE_PLAYER) ||
	    Connected(Location(thing)))) &&
	  (controls(player, thing) ||
	   ((Flags(Location(thing)) & ENTER_OK) &&
	    eval_lock(player, Location(thing), Enter_Lock)
	   ))) {
	notify(Location(thing), tprintf("%s was taken from you.", Name(thing)));
	moveto(thing, player);
	notify(thing, "Taken.");
	sprintf(tbuf1, "takes %s.", db[thing].name);
	did_it(player, thing, "SUCCESS", "Taken.", "OSUCCESS", tbuf1, "ASUCCESS",
	       NOTHING);
      } else
	did_it(player, thing, "FAILURE", "You can't take that from there.",
	       "OFAILURE", NULL, "AFAILURE", NOTHING);
    } else {
      notify(player, "I don't see that here.");
    }
    return;
  } else {
    if ((thing = noisy_match_result(player, what, TYPE_THING, match_flags)) != NOTHING) {
      if (db[thing].location == player) {
	notify(player, "You already have that!");
	return;
      }
      switch (Typeof(thing)) {
      case TYPE_PLAYER:
      case TYPE_THING:
	if (thing == player) {
	  notify(player, "You cannot get yourself!");
	  return;
	}
	if (could_doit(player, thing)) {
	  moveto(thing, player);
	  notify(thing, "Taken.");
	  sprintf(tbuf1, "takes %s.", db[thing].name);
	  did_it(player, thing, "SUCCESS", "Taken.", "OSUCCESS", tbuf1,
		 "ASUCCESS", NOTHING);
	} else
	  did_it(player, thing, "FAILURE", "You can't pick that up.",
		 "OFAILURE", NULL, "AFAILURE", NOTHING);
	break;
      case TYPE_EXIT:
	notify(player, "You can't pick up exits.");
	return;
      default:
	notify(player, "You can't take that!");
	break;
      }
    }
  }
}


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

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

  switch (thing = match_result(player, name, TYPE_THING, MAT_POSSESSION | MAT_ABSOLUTE | MAT_CONTROL)) {
  case NOTHING:
    notify(player, "You don't have that!");
    return;
  case AMBIGUOUS:
    notify(player, "I don't know which you mean!");
    return;
  default:
    if (db[thing].location != player) {
      /* Shouldn't ever happen. */
      notify(player, "You can't drop that.");
      return;
    } else if (Typeof(thing) == TYPE_EXIT) {
      notify(player, "Sorry you can't drop exits.");
      return;
#ifdef DROP_LOCK
    } else if (!eval_lock(player, thing, Drop_Lock)) {
      notify(player, "You can't seem to get rid of that.");
      return;
#endif
#ifdef DROP_LOCK
    } else if ((Typeof(loc) == TYPE_ROOM) &&
	       !eval_lock(player, loc, Drop_Lock)) {
      notify(player, "You can't seem to drop things here.");
      return;
#endif
#ifdef DROP_LOCK
    } else if ((Typeof(loc) == TYPE_ROOM) &&
	       !eval_lock(player, loc, Drop_Lock)) {
      notify(player, "You can't seem to drop things here.");
      return;
#endif
    } else if (Sticky(thing)
#ifdef FIXED_FLAG
	       && !Fixed(Owner(thing))
#endif
      ) {
      notify(thing, "Dropped.");
      safe_tel(thing, HOME);
    } else if ((Location(loc) != NOTHING) && (Typeof(loc) == TYPE_ROOM) &&
	       !Sticky(loc)) {
      /* location has immediate dropto */
      notify(thing, "Dropped.");
      moveto(thing, db[loc].location);
    } else {
      notify(thing, "Dropped.");
      moveto(thing, loc);
    }
    break;
  }
  sprintf(tbuf1, "drops %s.", db[thing].name);
  did_it(player, thing, "DROP", "Dropped.", "ODROP", tbuf1, "ADROP", NOTHING);
}


void
do_enter(player, what, is_alias)
    dbref player;
    const char *what;
    int is_alias;


				/* 1 if we got here via enter alias */
{
  dbref thing;
  long match_flags = MAT_CHECK_KEYS | MAT_NEIGHBOR | MAT_EXIT;
  if (is_alias || Hasprivs(player))
    match_flags |= MAT_ABSOLUTE;	/* necessary for enter aliases to work */

  if ((thing = noisy_match_result(player, what, TYPE_THING, match_flags)) == NOTHING) {
    /* notify(player,"I don't see that here.");   */
    return;
  }
  switch (Typeof(thing)) {
  case TYPE_ROOM:
  case TYPE_EXIT:
    notify(player, "Permission denied.");
    break;
  default:
    /* the object must pass the lock. Also, the thing being entered */
    /* has to be controlled, or must be enter_ok */
    if (!(((Flags(thing) & ENTER_OK) || controls(player, thing)) &&
	  (eval_lock(player, thing, Enter_Lock))
	)) {
      did_it(player, thing, "EFAIL", "Permission denied.", "OEFAIL",
	     NULL, "AEFAIL", NOTHING);
      return;
    }
    if (thing == player) {
      notify(player, "Sorry, you must remain beside yourself!");
      return;
    }
    safe_tel(player, thing);
    break;
  }
}

void
do_leave(player)
    dbref player;
{
  if ((Typeof(Location(player)) == TYPE_ROOM) ||
      (IS(Location(player), TYPE_THING, THING_NOLEAVE)
#ifdef LEAVE_LOCK
       || !eval_lock(player, Location(player), Leave_Lock)
#endif
      )) {
    did_it(player, Location(player), "LFAIL", "You can't leave.", "OLFAIL",
	   NULL, "ALFAIL", NOTHING);
    return;
  }
  enter_room(player, db[Location(player)].location);
}

dbref
global_exit(player, direction)
    dbref player;
    const char *direction;
{
  return (last_match_result(player, direction, TYPE_EXIT, MAT_GLOBAL | MAT_EXIT) != NOTHING);
}

dbref
remote_exit(player, direction)
    dbref player;
    const char *direction;
{
  return (last_match_result(player, direction, TYPE_EXIT, MAT_REMOTES | MAT_EXIT) != NOTHING);
}

void
move_wrapper(player, command)
    dbref player;
    const char *command;
{
  /* check local exit, then zone exit, then global. If nothing is
   * matched, treat it as local so player will get an error message.
   */

  if (!Mobile(player))
    return;

#ifdef FIXED_FLAG
  if (!strcasecmp(command, "home") && Fixed(Owner(player))) {
    notify(player, "You can't do that IC!");
    return;
  }
#endif

  if (DO_GLOBALS) {
    if (can_move(player, command))
      do_move(player, command, 0);
    else if ((Zone(Location(player)) != NOTHING) &&
	     remote_exit(player, command))
      do_move(player, command, 2);
    else if ((Location(player) != MASTER_ROOM) &&
	     global_exit(player, command))
      do_move(player, command, 1);
    else
      do_move(player, command, 0);
  } else {
    do_move(player, command, 0);
  }
}