/* $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 "os.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);
}

static 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
    && OS_RAND () % 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.
 *
 */

static 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 */