/* $Header: create.c,v 2.0 90/05/05 12:44:12 lachesis Exp $
 * $Log:    create.c,v $
 * Revision 2.0  90/05/05  12:44:12  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.1  90/04/14  14:56:40  lachesis
 * Initial revision
 *
 */
#include "copyright.h"

/* Commands that create new objects */
#include "os.h"

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

static int link_exit (dbref player, dbref exit, char *dest_name, dbref * dest_list);

/* utility for open and link */
static dbref parse_linkable_room (dbref player, const char *room_name)
{
  dbref room;

  /* skip leading NUMBER_TOKEN if any */
  if (*room_name == NUMBER_TOKEN)
    room_name++;

  /* parse room */
  if (!string_compare (room_name, "here")) {
    room = db[player].location;
  } else if (!string_compare (room_name, "home")) {
    return HOME;                /* HOME is always linkable */
  } else {
    room = parse_dbref (room_name);
  }

  /* check room */
  if (room < 0 || room >= db_top || Typeof (room) != TYPE_ROOM) {
    notify (player, "That's not a room!");
    return NOTHING;
  } else if (!can_link_to (player, room)) {
    notify (player, "You can't link to that.");
    return NOTHING;
  } else {
    return room;
  }
}

#ifdef ABODE
/* utility for open and link for objects/players */
static dbref parse_abode_room (dbref player, const char *room_name)
{
  dbref room;

  /* skip leading NUMBER_TOKEN if any */
  if (*room_name == NUMBER_TOKEN)
    room_name++;

  /* parse room */
  if (!string_compare (room_name, "here")) {
    room = db[player].location;
  } else if (!string_compare (room_name, "home")) {
    return HOME;                /* HOME is always linkable */
  } else {
    room = parse_dbref (room_name);
  }

  /* check room */
  if (room < 0 || room >= db_top || Typeof (room) != TYPE_ROOM) {
    notify (player, "That's not a room!");
    return NOTHING;
  } else if (!(controls (player, room) || (db[room].flags & ABODE))) {
    notify (player, "You can't link to that.");
    return NOTHING;
  } else {
    return room;
  }
}
#endif /* ABODE */

/* parse_linkable_dest()
 *
 * A utility for open and link which checks whether a given destination
 * string is valid.  It returns a parsed dbref on success, and NOTHING
 * on failure.
 */

static dbref parse_linkable_dest (dbref player, const char *dest_name)
{
  dbref dobj;                   /* destination room/player/thing/link */
  static char buf[BUFFER_LEN];

  init_match (player, dest_name, NOTYPE);
  match_absolute ();
  match_everything ();
  match_home ();

  if ((dobj = match_result ()) == NOTHING) {
    sprintf (buf, "I couldn't find \"%s\".", dest_name);
    notify (player, buf);
    return NOTHING;
  }
  if (dobj == AMBIGUOUS) {
    notify (player, "I don't know which one you mean!");
    return NOTHING;
#ifndef TELEPORT_TO_PLAYER
  }
  if (Typeof (dobj) == TYPE_PLAYER) {
    sprintf (buf, "You can't link to players.  Destination %s ignored.",
      unparse_object (player, dobj));
    notify (player, buf);
    return NOTHING;
#endif /* TELEPORT_TO_PLAYER */
  }
  if (!can_link_to (player, dobj)) {
    sprintf (buf, "You can't link to %s.", unparse_object (player, dobj));
    notify (player, buf);
    return NOTHING;
  } else {
    return dobj;
  }
}

/* exit_loop_check()
 *
 * Recursive check for loops in destinations of exits.  Checks to see
 * if any circular references are present in the destination chain.
 * Returns 1 if circular reference found, 0 if not.
 */
static int exit_loop_check (dbref source, dbref dest)
{

  int i;
  if (source == dest)
    return 1;                   /* That's an easy one! */
  if (Typeof (dest) != TYPE_EXIT)
    return 0;
  for (i = 0; i < db[dest].sp.exit.ndest; i++) {
    if ((db[dest].sp.exit.dest)[i] == source) {
      return 1;                 /* Found a loop! */
    }
    if (Typeof ((db[dest].sp.exit.dest)[i]) == TYPE_EXIT) {
      if (exit_loop_check (source, (db[dest].sp.exit.dest)[i])) {
        return 1;               /* Found one recursively */
      }
    }
  }
  return 0;                     /* No loops found */
}

/* use this to create an exit */
void do_open (dbref player, const char *direction, const char *linkto)
{
  dbref loc, exit;
  dbref good_dest[MAX_LINKS];
  int i, ndest;

#ifdef RESTRICTED_BUILDING
  if (!Builder (player)) {
    notify (player, "That command is restricted to authorized builders.");
    return;
  }
#endif /* RESTRICTED_BUILDING */

  if ((loc = getloc (player)) == NOTHING)
    return;
  if (!*direction) {
    notify (player, "You must specify a direction or action name to open.");
    return;
  } else if (!ok_name (direction)) {
    notify (player, "That's a strange name for an exit!");
    return;
  }

  if (!controls (player, loc)) {
    notify (player, "Permission denied.");
  } else if (!payfor (player, EXIT_COST)) {
    notify (player, "Sorry, you don't have enough cookies to open an exit.");
  } else {
    /* create the exit */
    exit = new_object ();

    /* initialize everything */
    db[exit].name = alloc_string (direction);
    db[exit].location = loc;
    db[exit].sp.exit.owner = player;
    db[exit].flags = TYPE_EXIT;
    db[exit].sp.exit.ndest = 0;
    db[exit].sp.exit.dest = 0;

    /* link it in */
    PUSH (exit, db[loc].sp.room.exits);

    /* and we're done */
    notify (player, "Opened.");

    /* check second arg to see if we should do a link */
    if (*linkto != '\0') {
      notify (player, "Trying to link...");
      if (!payfor (player, LINK_COST)) {
        notify (player, "You don't have enough cookies to link.");
        return;
      }
      ndest = link_exit (player, exit, (char *) linkto, good_dest);
      db[exit].sp.exit.ndest = ndest;
      db[exit].sp.exit.dest = (dbref *) malloc (sizeof (dbref) * ndest);
      for (i = 0; i < ndest; i++) {
        (db[exit].sp.exit.dest)[i] = good_dest[i];
      }
    }
  }
}

/*
 * link_exit()
 *
 * This routine connects an exit to a bunch of destinations.
 *
 * 'player' contains the player's name.
 * 'exit' is the the exit whose destinations are to be linked.
 * 'dest_name' is a character string containing the list of exits.
 *
 * 'dest_list' is an array of dbref's where the valid destinations are
 * stored.
 *
 */
static int link_exit (dbref player, dbref exit, char *dest_name, dbref * dest_list)
{
  char *p, *q;
  int prdest;
  dbref dest;
  int ndest;
  char buf[BUFFER_LEN], qbuf[BUFFER_LEN];

  prdest = 0;
  ndest = 0;

  while (*dest_name) {
    while (isspace (*dest_name))
      dest_name++;              /* skip white space */
    p = dest_name;
    while (*dest_name && (*dest_name != EXIT_DELIMITER))
      dest_name++;
    /* go to space or end */
    q = strncpy (qbuf, p, BUFFER_LEN);  /* copy word */
    q[(dest_name - p)] = '\0';  /* terminate it */
    if (*dest_name)
      for (dest_name++; *dest_name && isspace (*dest_name); dest_name++)
        /*EMPTY*/;

    if ((dest = parse_linkable_dest (player, q)) == NOTHING)
      continue;

    switch (Typeof (dest)) {
    case TYPE_PLAYER:
    case TYPE_ROOM:
      if (prdest) {
        sprintf (buf,
          "Only one player or room destination allowed. Destination %d ignored.",
          dest);
        notify (player, buf);
        continue;
      }
      dest_list[ndest++] = dest;
      prdest = 1;
      break;
    case TYPE_THING:
      dest_list[ndest++] = dest;
      break;
    case TYPE_EXIT:
      if (exit_loop_check (exit, dest)) {
        sprintf (buf, "Destination %d would create a loop, ignored.", dest);
        notify (player, buf);
        continue;
      }
      dest_list[ndest++] = dest;
      break;
    default:
      notify (player, "Internal error: weird object type.");
      fprintf (stderr, "PANIC weird object: Typeof(%d) = %d\n",
        dest, Typeof (dest));
      break;
    }
    if (dest == HOME) {
      notify (player, "Linked to HOME.");
    } else {
      sprintf (buf, "Linked to %s.", unparse_object (player, dest));
      notify (player, buf);
    }
    if (ndest >= MAX_LINKS) {
      notify (player, "Too many destinations, rest ignored.");
      break;
    }
  }
  return ndest;
}

/* do_link
 *
 * Use this to link to a room that you own.  It also sets home for
 * objects and things, and drop-to's for rooms.
 * It seizes ownership of an unlinked exit, and costs 1 penny
 * plus a penny transferred to the exit owner if they aren't you
 *
 * All destinations must either be owned by you, or be LINK_OK.
 */
void do_link (dbref player, const char *thing_name, const char *dest_name)
{
  dbref thing;
  dbref dest;
  dbref good_dest[MAX_LINKS];

  int ndest, i;

  init_match (player, thing_name, TYPE_EXIT);
  match_all_exits ();
  match_neighbor ();
  match_possession ();
  match_me ();
  match_here ();
  if (Wizard (player)) {
    match_absolute ();
    match_player ();
  }

  if ((thing = noisy_match_result ()) == NOTHING)
    return;

  switch (Typeof (thing)) {
  case TYPE_EXIT:
    /* we're ok, check the usual stuff */
    if (db[thing].sp.exit.ndest != 0) {
      if (controls (player, thing)) {
        notify (player, "That exit is already linked.");
        return;
      } else {
        notify (player, "Permission denied.");
        return;
      }
    }
    /* handle costs */
    if (db[thing].sp.exit.owner == player) {
      if (!payfor (player, LINK_COST)) {
        notify (player, "It costs a cookie to link this exit.");
        return;
      }
    } else {
      if (!payfor (player, LINK_COST + EXIT_COST)) {
        notify (player, "It costs two cookies to link this exit.");
        return;
#ifdef RESTRICTED_BUILDING
      } else if (!Builder (player)) {
        notify (player, "Only authorized builders may seize exits.");
        return;
#endif /* RESTRICTED_BUILDING */
      } else {
        /* pay the owner for his loss */
        db[db[thing].sp.exit.owner].sp.player.pennies += EXIT_COST;
      }
    }

    /* link has been validated and paid for; do it */
    db[thing].sp.exit.owner = player;
    ndest = link_exit (player, thing, (char *) dest_name, good_dest);
    if (ndest == 0) {
      notify (player, "No destinations linked.");
      db[player].sp.player.pennies += LINK_COST;        /* Refund! */
      break;
    }
    db[thing].sp.exit.ndest = ndest;
    db[thing].sp.exit.dest = (dbref *) malloc (sizeof (dbref) * ndest);
    for (i = 0; i < ndest; i++) {
      (db[thing].sp.exit.dest)[i] = good_dest[i];
    }
    break;
  case TYPE_THING:
  case TYPE_PLAYER:
#ifndef ABODE
    if ((dest = parse_linkable_room (player, dest_name)) == NOTHING)
      return;
#else /* ABODE */
    dest = parse_abode_room (player, dest_name);
    if (dest == NOTHING)
      return;
#endif /* ABODE */
    if (Typeof (dest) != TYPE_ROOM) {
      notify (player, "You can only set home to a room.");
      break;
    }
    if (!controls (player, thing)) {
      notify (player, "Permission denied.");
    } else if (dest == HOME) {
      notify (player, "Can't set home to 'home'.");
    } else {
      /* do the link */
      if (Typeof (thing) == TYPE_THING)
        db[thing].sp.thing.home = dest;
      else
        db[thing].sp.player.home = dest;
      notify (player, "Home set.");
    }
    break;
  case TYPE_ROOM:              /* room dropto's */
    dest = parse_linkable_room (player, dest_name);
    if (dest == NOTHING) {
      notify (player, "You can't link to that.");
      return;
    }
    if (!controls (player, thing)) {
      notify (player, "Permission denied.");
    } else {
      /* do the link, in location */
      db[thing].sp.room.dropto = dest;  /* dropto */
      notify (player, "Dropto set.");
    }
    break;
  default:
    notify (player, "Internal error: weird object type.");
    fprintf (stderr, "PANIC weird object: Typeof(%d) = %d\n",
      thing, Typeof (thing));
    break;
  }
}

/*
 * do_dig
 *
 * Use this to create a room.
 */
void do_dig (dbref player, const char *name)
{
  dbref room;
  char buf[BUFFER_LEN];

#ifdef RESTRICTED_BUILDING
  if (!Builder (player)) {
    notify (player, "That command is restricted to authorized builders.");
    return;
  }
#endif /* RESTRICTED_BUILDING */

  /* we don't need to know player's location!  hooray! */
  if (*name == '\0') {
    notify (player, "You must specify a name for the room.");
  } else if (!ok_name (name)) {
    notify (player, "That's a silly name for a room!");
  } else if (!payfor (player, ROOM_COST)) {
    notify (player, "Sorry, you don't have enough cookies to dig a room.");
  } else {
    room = new_object ();

    /* Initialize everything */
    db[room].name = alloc_string (name);
    db[room].sp.room.owner = player;
    db[room].sp.room.exits = NOTHING;
    db[room].sp.room.dropto = NOTHING;
    db[room].flags = TYPE_ROOM | (db[player].flags & JUMP_OK);

    sprintf (buf, "%s created with room number %d.", name, room);
    notify (player, buf);
  }
}

/*
 * do_create
 *
 * Use this to create an object.
 */
void do_create (dbref player, const char *name, int cost)
{
  dbref loc;
  dbref thing;

  static char buf[BUFFER_LEN];

#ifdef RESTRICTED_BUILDING
  if (!Builder (player)) {
    notify (player, "That command is restricted to authorized builders.");
    return;
  }
#endif /* RESTRICTED_BUILDING */

  if (*name == '\0') {
    notify (player, "Create what?");
    return;
  } else if (!ok_name (name)) {
    notify (player, "That's a silly name for a thing!");
    return;
  } else if (cost < 0) {
    notify (player, "You can't create an object for less than nothing!");
    return;
  } else if (cost < OBJECT_COST) {
    cost = OBJECT_COST;
  }

  if (!payfor (player, cost)) {
    notify (player, "Sorry, you don't have enough cookies.");
  } else {
    /* create the object */
    thing = new_object ();

    /* initialize everything */
    db[thing].name = alloc_string (name);
    db[thing].location = player;
    db[thing].sp.thing.owner = player;
    db[thing].sp.thing.value = OBJECT_ENDOWMENT (cost);
    db[thing].sp.thing.actions = NOTHING;
    db[thing].flags = TYPE_THING;

    /* endow the object */
    if (db[thing].sp.thing.value > MAX_OBJECT_ENDOWMENT) {
      db[thing].sp.thing.value = MAX_OBJECT_ENDOWMENT;
    }

    /* home is here (if we can link to it) or player's home */
    if ((loc = db[player].location) != NOTHING && controls (player, loc)) {
      db[thing].sp.thing.home = loc;    /* home */
    } else {
      db[thing].sp.thing.home = db[player].sp.player.home;
      /* set to player's home instead */
    }

    /* link it in */
    PUSH (thing, db[player].contents);

    /* and we're done */
    sprintf (buf, "%s created with number %d.", name, thing);
    notify (player, buf);
  }
}

/*
 * parse_source()
 *
 * This is a utility used by do_action and do_attach.  It parses
 * the source string into a dbref, and checks to see that it
 * exists.
 *
 * The return value is the dbref of the source, or NOTHING if an
 * error occurs.
 *
 */
static dbref parse_source (dbref player, const char *source_name)
{
  dbref source;

  init_match (player, source_name, NOTHING);    /* source type can be any */
  match_neighbor ();
  match_me ();
  match_here ();
  match_possession ();
  if (Wizard (player)) {
    match_absolute ();
  }
  source = noisy_match_result ();

  if (source == NOTHING)
    return NOTHING;

  /* You can only attach actions to things you control */
  if (!controls (player, source)) {
    notify (player, "Permission denied.");
    return NOTHING;
  }
  if (Typeof (source) == TYPE_EXIT) {
    notify (player, "You can't attach an action to an action.");
    return NOTHING;
  }
  return source;
}

/*
 * set_source()
 *
 * This routine sets the source of an action to the specified source.
 * It is called by do_action and do_attach.
 *
 */
static void set_source (dbref player, dbref action, dbref source)
{
  switch (Typeof (source)) {
  case TYPE_ROOM:
    PUSH (action, db[source].sp.room.exits);
    break;
  case TYPE_THING:
    PUSH (action, db[source].sp.thing.actions);
    break;
  case TYPE_PLAYER:
    PUSH (action, db[source].sp.player.actions);
    break;
  default:
    notify (player, "Internal error: weird object type.");
    fprintf (stderr, "PANIC weird object: Typeof(%d) = %d\n",
      source, Typeof (source));
    return;
     /*NOTREACHED*/ break;
  }
  db[action].location = source;
  return;
}

/*
 * do_action()
 *
 * This routine attaches a new existing action to a source object,
 * where possible.
 * The action will not do anything until it is LINKed.
 *
 */
void do_action (dbref player, const char *action_name,
  const char *source_name)
{
  dbref action, source;
  static char buf[BUFFER_LEN];

#ifdef RESTRICTED_BUILDING
  if (!Builder (player)) {
    notify (player, "That command is restricted to authorized builders.");
    return;
  }
#endif /* RESTRICTED_BUILDING */
  if (!*action_name || !*source_name) {
    notify (player, "You must specify an action name and a source object.");
    return;
  } else if (!ok_name (action_name)) {
    notify (player, "That's a strange name for an action!");
    return;
  } else if (!payfor (player, EXIT_COST)) {
    notify (player,
      "Sorry, you don't have enough cookies to make an action.");
    return;
  }
  if ((source = parse_source (player, source_name)) == NOTHING)
    return;

  action = new_object ();

  db[action].name = alloc_string (action_name);
  db[action].location = NOTHING;
  db[action].sp.exit.owner = player;
  db[action].sp.exit.ndest = 0;
  db[action].sp.exit.dest = NULL;
  db[action].flags = TYPE_EXIT;

  set_source (player, action, source);
  sprintf (buf, "Action created with number %d and attached.", action);
  notify (player, buf);
}

/*
 * do_attach()
 *
 * This routine attaches a previously existing action to a source object.
 * The action will not do anything unless it is LINKed.
 *
 */
void do_attach (dbref player, const char *action_name,
  const char *source_name)
{
  dbref action, source;
  dbref oldsrc;                 /* action's old source */
  dbref loc;                    /* player's current location */

  if ((loc = db[player].location) == NOTHING)
    return;

#ifdef RESTRICTED_BUILDING
  if (!Builder (player)) {
    notify (player, "That command is restricted to authorized builders.");
    return;
  }
#endif /* RESTRICTED_BUILDING */
  if (!*action_name || !*source_name) {
    notify (player, "You must specify an action name and a source object.");
    return;
  }

  init_match (player, action_name, TYPE_EXIT);
  match_all_exits ();
  if (Wizard (player))
    match_absolute ();

  if ((action = noisy_match_result ()) == NOTHING)
    return;

  if (Typeof (action) != TYPE_EXIT) {
    notify (player, "That's not an action!");
    return;
  } else if (!controls (player, action)) {
    notify (player, "Permission denied.");
    return;
  }

  if ((source = parse_source (player, source_name)) == NOTHING)
    return;

  if ((oldsrc = db[action].location) == NOTHING) {
    /* old-style, sourceless exit */
    if (!member (action, db[loc].sp.room.exits)) {
      notify (player, "You can't re-attach an exit in another room.");
      return;
    }
    db[db[player].location].sp.room.exits =
      remove_first (db[db[player].location].sp.room.exits, action);
  } else {
    switch (Typeof (oldsrc)) {
    case TYPE_PLAYER:
      db[oldsrc].sp.player.actions =
        remove_first (db[oldsrc].sp.player.actions, action);
      break;
    case TYPE_ROOM:
      db[oldsrc].sp.room.exits =
        remove_first (db[oldsrc].sp.room.exits, action);
      break;
    case TYPE_THING:
      db[oldsrc].sp.thing.actions =
        remove_first (db[oldsrc].sp.thing.actions, action);
      break;
    case TYPE_EXIT:
      fprintf (stderr, "PANIC: Action #%d was sourced to an exit.", action);
      return;
       /*NOTREACHED*/ break;
    }
  }
  set_source (player, action, source);
  notify (player, "Action re-attached.");
}

int unset_source (dbref player, dbref loc, dbref action,
  const char *other_room_msg)
{

  dbref oldsrc;

  if ((oldsrc = db[action].location) == NOTHING) {
    /* old-style, sourceless exit */
    if (!member (action, db[loc].sp.room.exits)) {
      notify (player, other_room_msg);
      return 0;
    }
    db[db[player].location].sp.room.exits =
      remove_first (db[db[player].location].sp.room.exits, action);
  } else {
    switch (Typeof (oldsrc)) {
    case TYPE_PLAYER:
      db[oldsrc].sp.player.actions =
        remove_first (db[oldsrc].sp.player.actions, action);
      break;
    case TYPE_ROOM:
      db[oldsrc].sp.room.exits =
        remove_first (db[oldsrc].sp.room.exits, action);
      break;
    case TYPE_THING:
      db[oldsrc].sp.thing.actions =
        remove_first (db[oldsrc].sp.thing.actions, action);
      break;
    case TYPE_EXIT:
      fprintf (stderr, "PANIC: Action #%d was sourced to an exit.", action);
      return 0;
       /*NOTREACHED*/ break;
    }
  }
  return 1;
}