/* move.c */
#include "os.h"
#include "copyright.h"

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

void enter_room ();

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

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

  /* remove what from old loc */
  if ((loc = old = 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:
    where = db[what].exits;     /* home */
    safe_tel (what, where);
    return;
     /*NOTREACHED*/ break;
  }

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

  db[what].location = where;
  if (Hearer (what) && (where != NOTHING) && !Dark (where) && (old != where)) {
    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);
    did_it (what, what, "MOVE", NULL, "OMOVE", NULL, "AMOVE", where);
  }
}

#define Dropper(thing) (Hearer(thing) && \
               (db[db[thing].owner].flags & PLAYER_CONNECT))

static 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 (Dropper (first)) {
      db[first].location = loc;
      PUSH (first, db[loc].contents);
    } else
      enter_room (first, (db[first].flags & STICKY) ? HOME : dest);
    first = rest;
  }

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

static void maybe_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, db[loc].contents) {
    if (Dropper (thing))
      return;
  }

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

void enter_room (dbref player, dbref loc)
{
  dbref old;
  dbref dropto;
  static int deep = 0;
  if (deep++ > 15) {
    deep--;
    return;
  }

  if (player < 0 || player >= db_top) {
    deep--;
    return;
  }
#ifdef notdef
  if (loc == NOTHING) {         /* death to -1 */
    free_object (player);
    deep--;
    return;
  }
#endif
  /* check for room == HOME */
  if (loc == HOME)
    loc = db[player].exits;     /* home */

  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;
  }

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

  /* 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 = db[old].location) != NOTHING) && (db[old].flags & STICKY))
    maybe_dropto (old, dropto);


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


/* teleports player to location while removing items they shouldnt take */
void safe_tel (dbref player, dbref dest)
{
  dbref first;
  dbref rest;
  if (dest == HOME)
    dest = db[player].exits;
  if (db[db[player].location].owner == db[dest].owner) {
    enter_room (player, dest);
    return;
  }
  first = db[player].contents;
  db[player].contents = 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 KEY, or it's STICKY and its */
    /* home is not the player */
    if (controls (player, first) ||
      ((Typeof (first) == TYPE_THING && !(db[first].flags & THING_KEY)) &&
        (!(db[first].flags & STICKY) || (db[first].exits == player)))) {
      PUSH (first, db[player].contents);
      db[first].location = player;
    } else
      enter_room (first, HOME);
    first = rest;
  }
  db[player].contents = reverse (db[player].contents);
  enter_room (player, dest);
}

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_exit ();
  return (last_match_result () != NOTHING);
}

void do_move (dbref player, const char *direction, int type     /* type 0 is normal, type 1 is global */
  )
{
  dbref exit;
  dbref loc;
  if (!string_compare (direction, "home")) {
    /* send him home */
    /* but steal all his possessions */
    if ((loc = db[player].location) != NOTHING && !Dark (player) &&
      !Dark (loc)) {
      /* tell everybody else */
      notify_except (db[loc].contents, 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 */
#ifdef DO_GLOBALS
    if (type == 1)
      init_match_global_check_keys (player, direction, TYPE_EXIT);
    else
#endif
      init_match_check_keys (player, direction, TYPE_EXIT);
    match_exit ();
    switch (exit = match_result ()) {
    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)) {
        did_it (player, exit, "SUCCESS", NULL, "OSUCCESS", NULL, "ASUCCESS",
          NOTHING);
        did_it (player, exit, "DROP", NULL, "ODROP", NULL, "ADROP",
          db[exit].location);
        switch (Typeof (db[exit].location)) {
        case TYPE_ROOM:
          enter_room (player, db[exit].location);
          break;
        case TYPE_PLAYER:
        case TYPE_THING:
          {
            if (db[db[exit].location].flags & GOING) {
              notify (player, "You can't go that way.");
              return;
            }
            if (db[db[exit].location].location == NOTHING)
              return;
            safe_tel (player, db[exit].location);
          }
          break;
        case TYPE_EXIT:
          notify (player, "This feature coming soon.");
          break;
        }
      } else
        did_it (player, exit, "FAILURE", "You can't go that way.", "OFAILURE",
          NULL, "AFAILURE", NOTHING);
      break;
    }
  }
}

void do_get (dbref player, const char *what)
{
  dbref loc = db[player].location;
  dbref thing;

  if ((Typeof (loc) != TYPE_ROOM) && !(db[loc].flags & ENTER_OK) &&
    !controls (player, loc)) {
    notify (player, "Permission denied.");
    return;
  }
  if (((thing = is_possess (player, (char*)what)) != NOTHING) &&
    (thing != AMBIGUOUS)) {
    if (could_doit (player, thing) &&
      (db[db[thing].location].flags & ENTER_OK)) {
      notify (db[thing].location, tprintf ("%s was taken from you.",
          db[thing].name));
      moveto (thing, player);
      notify (thing, "Taken.");
      did_it (player, thing, "SUCCESS", "Taken.", "OSUCCESS", NULL,
        "ASUCCESS", NOTHING);
    } else
      did_it (player, thing, "FAILURE", "You can't take that from there.",
        "OFAILURE", NULL, "AFAILURE", NOTHING);
  } else {
    init_match_check_keys (player, what, TYPE_THING);
    match_neighbor ();
    match_exit ();
    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_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.");
          did_it (player, thing, "SUCCESS", "Taken.", "OSUCCESS", NULL,
            "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 (dbref player, const char *name)
{
  dbref loc;
  dbref thing;
  int reward;
  char tbuf1[BUFFER_LEN];

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

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

  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;
  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, "Sorry you can't drop exits.");
      return;
    } else if (db[loc].flags & ROOM_TEMPLE) {
      /* sacrifice time */
      if (Typeof (thing) == TYPE_PLAYER) {
        notify (player, "Hey! No blood sacrifices please!.");
        return;
      }
      if (db[thing].flags & THING_SAFE) {
        notify (player,
          "That object is marked SAFE and cannot be sacrificed.");
        return;
      }
      notify (thing, "You have been sacrificed.");
      moveto (thing, NOTHING);
      /* Patched to destroy sacrificed objects */
      notify (player, tprintf ("%s is consumed in a burst of flame!",
          db[thing].name));
#ifdef FULL_INVIS
      if (!Dark (player)) {
        notify_except (db[loc].contents, player,
          tprintf ("%s sacrifices %s.", db[player].name, db[thing].name));
      } else {
        notify_except (db[loc].contents, player,
          tprintf ("Someone sacrifices %s.", db[thing].name));
      }
#else
      notify_except (db[loc].contents, player,
        tprintf ("%s sacrifices %s.", db[player].name, db[thing].name));
#endif
      /* check for reward */
      if (!controls (player, thing) || (Typeof (player) != TYPE_PLAYER)) {
        reward = Pennies (thing);
        if (reward < 1 || (Pennies (db[player].owner) > MAX_PENNIES)) {
          reward = 1;
        } else if (reward > MAX_OBJECT_ENDOWMENT) {
          reward = MAX_OBJECT_ENDOWMENT;
        }
        giveto (db[player].owner, reward);
        notify (player,
          tprintf ("You have received %d %s for your sacrifice.", reward,
            reward == 1 ? MONEY : MONIES));
        db[thing].flags |= THING_DEST_OK;
        do_destroy (player, tprintf ("#%d", thing), 1);
      }
    } else if (db[thing].flags & STICKY) {
      notify (thing, "Dropped.");
      safe_tel (thing, HOME);
    } else if (db[loc].location != NOTHING &&
      (Typeof (loc) == TYPE_ROOM) && !(db[loc].flags & STICKY)) {
      /* location has immediate dropto */
      notify (thing, "Dropped.");
      moveto (thing, db[loc].location);
    } else {
      notify (thing, "Dropped.");
      moveto (thing, loc);
    }
    break;
  }
  sprintf (tbuf1, "dropped %s.", db[thing].name);
  did_it (player, thing, "DROP", "Dropped.", "ODROP", tbuf1, "ADROP",
    NOTHING);
}


void do_enter (dbref player, const char *what)
{
  dbref thing;
  init_match_check_keys (player, what, TYPE_THING);
  match_neighbor ();
  match_exit ();
  match_absolute ();            /* necessary for enter aliases to work */

  if ((thing = noisy_match_result ()) == 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 (!(((db[thing].flags & ENTER_OK) || controls (player, thing)) &&
        (eval_boolexp (player, db[thing].enterkey, thing, 0, ENTERLOCK)))) {
      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 (dbref player)
{
  if (Typeof (db[player].location) == TYPE_ROOM) {
    notify (player, "You can't leave");
    return;
  }
  enter_room (player, db[db[player].location].location);
}

#ifdef DO_GLOBALS
int global_exit (dbref player, const char *direction)
{
  init_match_global (player, direction, TYPE_EXIT);
  match_exit ();
  return (last_match_result () != NOTHING);
}
#endif