dbx/cnf/
dbx/lib/
dbx/lib/misc/
dbx/lib/text/help/
dbx/lib/world/
dbx/lib/world/qst/
dbx/src/
/***************************************************************************
 *  OasisOLC - olc.c 		                                           *
 *    				                                           *
 *  Copyright 1996 Harvey Gilpin.                                          *
 ***************************************************************************/
 
#define _OASIS_OLC_
 
#include "conf.h"
#include "sysdep.h"
#include "structs.h"
#include "interpreter.h"
#include "comm.h"
#include "utils.h"
#include "db.h"
#include "olc.h"
#include "dg_olc.h"
#include "screen.h"
#include "handler.h"
  
/*
 * External data structures.
 */
extern struct obj_data *obj_proto;
extern struct char_data *mob_proto;
extern struct index_data *mob_index;
extern struct index_data *obj_index;
extern struct room_data *world;
extern int top_of_zone_table;
extern struct social_messg *soc_mess_list;
extern int top_of_socialt;
extern int top_of_world;
extern int rev_dir[];
extern const char *dirs[];
extern struct zone_data *zone_table;
extern struct descriptor_data *descriptor_list;
  
/*
 * External functions.
 */
extern int zedit_setup(struct descriptor_data *d, int room_num);
extern int zedit_save_to_disk(int zone);
extern int zedit_new_zone(struct char_data *ch, int new_zone);
extern int medit_setup_new(struct descriptor_data *d);
extern int medit_setup_existing(struct descriptor_data *d, int rmob_num);
extern int medit_save_to_disk(int zone);
extern int redit_setup_new(struct descriptor_data *d);
extern int redit_setup_existing(struct descriptor_data *d, int rroom_num);
extern void redit_save_internally(struct descriptor_data *d);
extern int redit_save_to_disk(int zone);
extern int oedit_setup_new(struct descriptor_data *d);
extern int oedit_setup_existing(struct descriptor_data *d, int robj_num);
extern int oedit_save_to_disk(int zone);
extern int sedit_setup_new(struct descriptor_data *d);
extern int sedit_setup_existing(struct descriptor_data *d, int robj_num);
extern int sedit_save_to_disk(int zone);
extern int real_shop(int vnum);
extern int free_shop(struct shop_data *shop);
extern int free_room(struct room_data *room);
extern void medit_free_mobile(struct char_data *mob);
extern void trigedit_setup_new(struct descriptor_data *d);
extern void trigedit_setup_existing(struct descriptor_data *d, int rtrg_num);
extern int real_trigger(int vnum);
extern hedit_save_to_disk(void);
extern free_help(struct help_index_element *help);
int find_help_rnum(char *keyword);
extern hedit_setup_new(struct descriptor_data *d, char *new_key);
extern hedit_setup_existing(struct descriptor_data *d, int rnum);
extern aedit_save_to_disk(struct descriptor_data *d);
extern free_action(struct social_messg *action);
extern int find_action(int cmd);

/*
 * Internal function prototypes.
 */
int can_edit_zone(struct char_data *ch, int number);
int real_zone(int number);
void olc_saveinfo(struct char_data *ch);

/*
 * Global string constants.
 */
const char *save_info_msg[8] = {"Rooms", "Objects", "Zone info",
	"Mobiles", "Shops", "Triggers", "Help", "Actions"}; 
 
/*
 * Internal data structures.
 */

struct olc_scmd_data {
  char *text;
  int con_type;
};
  
struct olc_scmd_data olc_scmd_info[8] =
{
  {"room", CON_REDIT},
  {"object", CON_OEDIT},
  {"room", CON_ZEDIT},
  {"mobile", CON_MEDIT},
  {"shop", CON_SEDIT},
  {"trigger", CON_TRIGEDIT},
  {"help", CON_HEDIT},
  {"action", CON_AEDIT}
};
  
 /*------------------------------------------------------------*/
 
 /*
  * Exported ACMD do_olc function.
  *
  * This function is the OLC interface.  It deals with all the 
  * generic OLC stuff, then passes control to the sub-olc sections.
  */
  
  ACMD(do_olc)
  {
   int number = -1, save = 0, real_num;
   struct descriptor_data *d;
 
   /*
    * No screwing around as a mobile.
    */
   if (IS_NPC(ch))
      return;
  
   if (subcmd == SCMD_OLC_SAVEINFO) {
     olc_saveinfo(ch);
      return;
    }
  
   /*
    * Parse any arguments.
    */
   two_arguments(argument, buf1, buf2);
   if (!*buf1) {		/* No argument given. */
     switch (subcmd) {
     case SCMD_OLC_ZEDIT:
     case SCMD_OLC_REDIT:
       number = world[IN_ROOM(ch)].number;
       break;
     case SCMD_OLC_TRIGEDIT:
     case SCMD_OLC_OEDIT:
     case SCMD_OLC_MEDIT:
     case SCMD_OLC_SEDIT:
       sprintf(buf, "Specify a %s VNUM to edit.\r\n", olc_scmd_info[subcmd].text);
       send_to_char(buf, ch);
       return;
     case SCMD_OLC_HEDIT:
       /*
        * Altered as a nitpick.  Why waste a sprintf() call when you know
        * that it's going to be a help entry?  TR 5-18-98
        */
       send_to_char("Specify a help entry to edit.\r\n", ch);
       return;
     case SCMD_OLC_AEDIT:
       send_to_char("Specify an action to edit.\r\n", ch);
       return;
     }
   } else if (!isdigit(*buf1)) {
     if (strn_cmp("save", buf1, 4) == 0) {
       if ((subcmd == SCMD_OLC_HEDIT) || (subcmd == SCMD_OLC_AEDIT)) {
         save = 1;
         number = 0;
       } else if (!*buf2) {
 	 send_to_char("Save which zone?\r\n", ch);
   	 return;
       } else {
 	 save = 1;
 	 number = atoi(buf2) * 100;
       }
     } else if ((subcmd == SCMD_OLC_HEDIT) || (subcmd == SCMD_OLC_AEDIT))
       number = 0;
     else if (subcmd == SCMD_OLC_ZEDIT && GET_LEVEL(ch) >= LVL_IMMORT) {
       if ((strn_cmp("new", buf1, 3) == 0) && *buf2)
 	zedit_new_zone(ch, atoi(buf2));
       else
 	send_to_char("Specify a new zone number.\r\n", ch);
       return;
     } else {
       send_to_char("Yikes!  Stop that, someone will get hurt!\r\n", ch);
       return;
     }
   }
   /*
    * If a numeric argument was given, get it.
    */
   if ((number == -1) && ((subcmd != SCMD_OLC_AEDIT) || (subcmd != SCMD_OLC_HEDIT)))
     number = atoi(buf1);
 
   /*
    * Check that whatever it is isn't already being edited.
    */
   for (d = descriptor_list; d; d = d->next)
     if (d->connected == olc_scmd_info[subcmd].con_type)
       if (d->olc && OLC_NUM(d) == number) {
	if (subcmd == SCMD_OLC_HEDIT)
	  sprintf(buf, "Help files are already being editted by %s.\r\n",
		  (CAN_SEE(ch, d->character) ? GET_NAME(d->character) : "someone"));
	else if (subcmd == SCMD_OLC_AEDIT)
          sprintf(buf, "Actions are already being editted by %s.\r\n",
		  (CAN_SEE(ch, d->character) ? GET_NAME(d->character) : "someone"));
        else
	  sprintf(buf, "That %s is currently being edited by %s.\r\n",
 		olc_scmd_info[subcmd].text, GET_NAME(d->character));
 	send_to_char(buf, ch);
 	return;
       }
   d = ch->desc;
  
   /*
    * Give descriptor an OLC struct.
    */
   CREATE(d->olc, struct olc_data, 1);
 
   /*
    * Find the zone (or help rnum).
    */
   if (subcmd != SCMD_OLC_AEDIT) {
     if (subcmd == SCMD_OLC_HEDIT && !save)
       OLC_ZNUM(d) = find_help_rnum(buf1);
     else if ((OLC_ZNUM(d) = real_zone(number)) == -1) {
       send_to_char("Sorry, there is no zone for that number!\r\n", ch);
       free(d->olc);
       return;
     }
   }
   /*
    * Everyone but IMPLs can only edit zones they have been assigned.
    */
   if (GET_LEVEL(ch) < LVL_IMMORT) {
     if (!can_edit_zone(ch, OLC_ZNUM(d)) && (subcmd != SCMD_OLC_AEDIT) && (subcmd != SCMD_OLC_HEDIT)) {
       send_to_char("You do not have permission to edit this zone.\r\n", ch);
       free(d->olc);
       return;
     }
   }

   if (save) {
     const char *type = NULL;
  
     switch (subcmd) {
     /*
      * Removed as a nitpick.  Why do something if it's unnecessary?
      * (cases for AEDIT and HEDIT removed)
      */
     case SCMD_OLC_AEDIT: type = "action"; break;
     case SCMD_OLC_HEDIT: type = "help"; break;
     case SCMD_OLC_REDIT: type = "room"; break;
     case SCMD_OLC_ZEDIT: type = "zone"; break;
     case SCMD_OLC_SEDIT: type = "shop"; break;
     case SCMD_OLC_MEDIT: type = "mobile"; break;
     case SCMD_OLC_OEDIT: type = "object"; break;
     }
     if (!type) {
       send_to_char("Oops, I forgot what you wanted to save.\r\n", ch);
       return;
     }
     if (subcmd == SCMD_OLC_HEDIT) {
       send_to_char("Saving all help entries.\r\n", ch);
       sprintf(buf, "OLC: %s saves help entries.", GET_NAME(ch));
       mudlog(buf, NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
     } else if (subcmd == SCMD_OLC_AEDIT) {
       send_to_char("Saving all actions.\r\n", ch);
       sprintf(buf, "OLC: %s saves all actions.", GET_NAME(ch));
       mudlog(buf, NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
     } else {
       sprintf(buf, "Saving all %ss in zone %d.\r\n",
 		type, zone_table[OLC_ZNUM(d)].number);
       send_to_char(buf, ch);
       sprintf(buf, "OLC: %s saves %s info for zone %d.", GET_NAME(ch),  type,
 		zone_table[OLC_ZNUM(d)].number);
       mudlog(buf, CMP, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
     }
 
     switch (subcmd) {
     case SCMD_OLC_REDIT: redit_save_to_disk(OLC_ZNUM(d)); break;
     case SCMD_OLC_ZEDIT: zedit_save_to_disk(OLC_ZNUM(d)); break;
     case SCMD_OLC_OEDIT: oedit_save_to_disk(OLC_ZNUM(d)); break;
     case SCMD_OLC_MEDIT: medit_save_to_disk(OLC_ZNUM(d)); break;
     case SCMD_OLC_SEDIT: sedit_save_to_disk(OLC_ZNUM(d)); break;
     case SCMD_OLC_HEDIT: hedit_save_to_disk(); break;
     case SCMD_OLC_AEDIT: aedit_save_to_disk(d); break;
     }
     free(d->olc);
     return;
   }

   if (subcmd != SCMD_OLC_AEDIT) OLC_NUM(d) = number;
   else {
     OLC_NUM(d) = 0;
     OLC_STORAGE(d) = str_dup(buf1);
     for (OLC_ZNUM(d) = 0; (OLC_ZNUM(d) <= top_of_socialt); OLC_ZNUM(d)++)
       if (is_abbrev(OLC_STORAGE(d), soc_mess_list[OLC_ZNUM(d)].command))
         break;
     if (OLC_ZNUM(d) > top_of_socialt)  {
       if (find_command(OLC_STORAGE(d)) > NOTHING)  {
         cleanup_olc(d, CLEANUP_ALL);
         send_to_char("That command already exists.\r\n", ch);
         return;
       }
       sprintf(buf, "Do you wish to add the '%s' action? ", OLC_STORAGE(d));
       send_to_char(buf, ch);
       OLC_MODE(d) = AEDIT_CONFIRM_ADD;
     } else  {
       sprintf(buf, "Do you wish to edit the '%s' action? ", soc_mess_list[OLC_ZNUM(d)].command);
       send_to_char(buf, ch);
       OLC_MODE(d) = AEDIT_CONFIRM_EDIT;
     }
   }
 
   /*
    * Steal player's descriptor start up subcommands.
    */
   switch (subcmd) {
   case SCMD_OLC_TRIGEDIT:
     if ((real_num = real_trigger(number)) >= 0)
       trigedit_setup_existing(d, real_num);
     else
       trigedit_setup_new(d);
     STATE(d) = CON_TRIGEDIT;
     break;
   case SCMD_OLC_REDIT:
     if ((real_num = real_room(number)) >= 0)
       redit_setup_existing(d, real_num);
     else
       redit_setup_new(d);
     STATE(d) = CON_REDIT;
      break;
   case SCMD_OLC_ZEDIT:
     if ((real_num = real_room(number)) < 0) {
       send_to_char("That room does not exist.\r\n", ch);
       free(d->olc);
       return;
     }
     zedit_setup(d, real_num);
     STATE(d) = CON_ZEDIT;
      break;
   case SCMD_OLC_MEDIT:
     if ((real_num = real_mobile(number)) < 0)
       medit_setup_new(d);
     else
       medit_setup_existing(d, real_num);
     STATE(d) = CON_MEDIT;
      break;
   case SCMD_OLC_OEDIT:
     if ((real_num = real_object(number)) >= 0)
       oedit_setup_existing(d, real_num);
     else
       oedit_setup_new(d);
     STATE(d) = CON_OEDIT;
     break;
   case SCMD_OLC_SEDIT:
     if ((real_num = real_shop(number)) >= 0)
       sedit_setup_existing(d, real_num);
     else
       sedit_setup_new(d);
     STATE(d) = CON_SEDIT;
     break;
   case SCMD_OLC_HEDIT:
     if (OLC_ZNUM(d) < 0)
       hedit_setup_new(d, buf1);
     else
       hedit_setup_existing(d, OLC_ZNUM(d));
     STATE(d) = CON_HEDIT;
     break;
   case SCMD_OLC_AEDIT:
     STATE(d) = CON_AEDIT;
     break;
   }
   act("$n starts using OLC.", TRUE, d->character, 0, 0, TO_ROOM);
   SET_BIT(PLR_FLAGS(ch), PLR_WRITING);
 }
  
 /*------------------------------------------------------------*\
  Internal utilities 
 \*------------------------------------------------------------*/
  
 void olc_saveinfo(struct char_data *ch)
 {
   struct olc_save_info *entry;
  
   if (olc_save_list)
     send_to_char("The following OLC components need saving:-\r\n", ch);
   else
     send_to_char("The database is up to date.\r\n", ch);
  
   for (entry = olc_save_list; entry; entry = entry->next) {
     if ((int)entry->type == OLC_SAVE_HELP)
       sprintf(buf, " - Help Entries.\r\n");
     else if ((int)entry->type == OLC_SAVE_ACTION)
       sprintf(buf, " - Actions.\r\n");
     else
       sprintf(buf, " - %s for zone %d.\r\n",
 		save_info_msg[(int)entry->type], entry->zone);
     send_to_char(buf, ch);
    }
  }
  
 int real_zone(int number)
 {
   int counter;
 
   for (counter = 0; counter <= top_of_zone_table; counter++)
     if ((number >= (zone_table[counter].number * 100)) &&
 	(number <= (zone_table[counter].top)))
       return counter;
 
   return -1;
 }
  
 /*------------------------------------------------------------*\
  Exported utilities 
 \*------------------------------------------------------------*/
 
 /*
  * Add an entry to the 'to be saved' list.
  */
 
 void olc_add_to_save_list(int zone, byte type)
  {
   struct olc_save_info *new;
 
   /*
    * Return if it's already in the list.
    */
   for (new = olc_save_list; new; new = new->next)
     if ((new->zone == zone) && (new->type == type))
       return;
 
   CREATE(new, struct olc_save_info, 1);
   new->zone = zone;
   new->type = type;
   new->next = olc_save_list;
   olc_save_list = new;
  }
  
 /*
  * Remove an entry from the 'to be saved' list.
  */
  
 void olc_remove_from_save_list(int zone, byte type)
  {
   struct olc_save_info **entry;
   struct olc_save_info *temp;
  
   for (entry = &olc_save_list; *entry; entry = &(*entry)->next)
     if (((*entry)->zone == zone) && ((*entry)->type == type)) {
       temp = *entry;
       *entry = temp->next;
       free(temp);
       return;
      }
  }
  
 /*
  * Set the colour string pointers for that which this char will
  * see at color level NRM.  Changing the entries here will change 
  * the colour scheme throughout the OLC.
  */
  
 void get_char_cols(struct char_data *ch)
 {
   nrm = CCNRM(ch, C_NRM);
   grn = CCGRN(ch, C_NRM);
   cyn = CCCYN(ch, C_NRM);
   yel = CCYEL(ch, C_NRM);
 }
 
 /*
  * This procedure removes the '\r\n' from a string so that it may be
  * saved to a file.  Use it only on buffers, not on the original
  * strings.
  */
 void strip_string(char *buffer)
  {
   register char *ptr, *str;
  
   ptr = buffer;
   str = ptr;
  
   while ((*str = *ptr)) {
     str++;
     ptr++;
     if (*ptr == '\r')
       ptr++;
    }
 }
  
 /*
  * This procdure frees up the strings and/or the structures
  * attatched to a descriptor, sets all flags back to how they
  * should be.
  */
  
 void cleanup_olc(struct descriptor_data *d, byte cleanup_type)
 {
   if (d->olc) {
     /*
      * Check for storage.
      */
     if (OLC_STORAGE(d))
       free(OLC_STORAGE(d));

     /*
      * Check for help.
      */
     if (OLC_HELP(d)) {
       switch (cleanup_type) {
       case CLEANUP_ALL:	free_help(OLC_HELP(d));	break;
       case CLEANUP_STRUCTS:  free(OLC_HELP(d));	break;
       default: /* The caller has screwed up. */	break;
       }
     }
     /*
      * Check for a room.
      */
     if (OLC_ROOM(d)) {
       /*
        * free_room doesn't perform sanity checks, must be careful here.
        */
       switch (cleanup_type) {
       case CLEANUP_ALL:	free_room(OLC_ROOM(d));	break;
       case CLEANUP_STRUCTS:  free(OLC_ROOM(d));	break;
       default: /* The caller has screwed up. */	break;
       }
     }
     /*
      * Check for an object.
      */
     if (OLC_OBJ(d))
       /*
        * free_obj() makes sure strings aern't part of the prototype.
        */
       free_obj(OLC_OBJ(d));
 
     /*
      * Check for a mob.
      */
     if (OLC_MOB(d))
       /*
        * medit_free_mobile() makes sure strings are not in the prototype.
        */
       medit_free_mobile(OLC_MOB(d));
 
     /*
      * Check for a zone.
      */
     if (OLC_ZONE(d)) {
       /*
        * cleanup_type is irrelevant here, free() everything.
        */
       free(OLC_ZONE(d)->name);
       free(OLC_ZONE(d)->cmd);
       free(OLC_ZONE(d));
     }
 
    /*
     * Check for a shop.
     */
    if (OLC_SHOP(d)) {
       /*
        * free_shop doesn't perform sanity checks, we must be careful here.
        */
        switch (cleanup_type) {
        case CLEANUP_ALL: free_shop(OLC_SHOP(d));	break;
        case CLEANUP_STRUCTS:  free(OLC_SHOP(d));	break;
        default: /* The caller has screwed up. */	break;
      }
    }

    /*. Check for aedit stuff -- M. Scott */
    if (OLC_ACTION(d))  {
      switch(cleanup_type)  {
      case CLEANUP_ALL:
        free_action(OLC_ACTION(d));
        break;
      case CLEANUP_STRUCTS:
        free(OLC_ACTION(d));
        break;
      default:
        /* Caller has screwed up */
        break;
      }
    }

    /*
     * Restore descriptor playing status.
     */
    if (d->character) {
      REMOVE_BIT(PLR_FLAGS(d->character), PLR_WRITING);
      STATE(d) = CON_PLAYING;
      act("$n stops using OLC.", TRUE, d->character, 0, 0, TO_ROOM);
    }
    free(d->olc);
  }
}

/* Everything below this line is part of the OLC+ package specifically. */

/* Can they edit a zone?  This takes a zone's rnum.   *
 * Any reason to add a REAL/VIRTUAL setup? TR 5-20-98 */
int can_edit_zone(struct char_data *ch, int number)
{
  if (GET_LEVEL(ch) >= LVL_IMMORT)
    return TRUE;

  if (GET_LEVEL(ch) < LVL_IMMORT)
    return FALSE;
  if (!is_name(GET_NAME(ch), zone_table[number].builders))
    return FALSE;

  return TRUE;
}

/* This handy little function will give the zone number *
 * of any object, room, or mob.      TR 5-18-98         */

#define ROOM	0
#define OBJECT	1
#define MOBILE	2

int zone_number(void *what, int type)
{
  struct char_data *character;
  struct obj_data *object;
  struct room_data *room;
  int return_value;

  switch (type) {
  case ROOM:
    room = (struct room_data *)what;
    return_value = zone_table[real_zone(room->number)].number;
    break;
  case OBJECT:
    object = (struct obj_data *)what;
    return_value = (GET_OBJ_VNUM(object) / 100);
    break;
  case MOBILE:
    character = (struct char_data *)what;
    if (IS_NPC(character))
      return_value = -1;
    /* This formula seems to work in all cases, although I   *
     * know it wouldn't in the case of rooms.  The documents *
     * state that the top_of_zone specifies the last ROOM,   *
     * but not object.  I think the default is (zone#*10)+99 */
    else
      /* Doesn't this look like it wouldn't work?  It seems  *
       * that C has decided to simply truncate integers in   *
       * lieu of rounding them...  Is this machine specific? *
       *                                   TR 5-20-98        */
      return_value = (GET_MOB_VNUM(character) / 100);
    break;
  default:
    return_value = -1;
    break;
  }

  return return_value;
}


/* This little function has real potential.  Give it  *
 * a source room's rnum and a target room's rnum, and *
 * it will do a copy.  Use it in your own commands.   *
 *                                 TR 5-20-98         */
void copy_room(int rnum_src, int rnum_targ)
{
  if (world[rnum_src].name)
    world[rnum_targ].name = str_dup(world[rnum_src].name);
  if (world[rnum_src].description)
    world[rnum_targ].description = str_dup(world[rnum_src].description);
  world[rnum_targ].sector_type = world[rnum_src].sector_type;
  world[rnum_targ].room_flags = world[rnum_src].room_flags;
  /* Note:  ex_descriptions are not being      *
   * copied.  I think it will stay that way.   *
   *                       TR 5-20-98          */
  return;
}


/* Same as copy_room, but with objects.  No error checking.  *
 * Should this be made an integer so a check can be made for *
 * success?                             TR 2-20-98           */
void copy_object(int rnum_src, int rnum_targ)
{
  if (obj_proto[rnum_src].name)
    obj_proto[rnum_targ].name = str_dup(obj_proto[rnum_src].name);
  if (obj_proto[rnum_src].description)
    obj_proto[rnum_targ].description = str_dup(obj_proto[rnum_src].description);
  if (obj_proto[rnum_src].short_description)
    obj_proto[rnum_targ].short_description = str_dup(obj_proto[rnum_src].short_description);
  if (obj_proto[rnum_src].action_description)
    obj_proto[rnum_targ].action_description = str_dup(obj_proto[rnum_src].action_description);
  if (obj_proto[rnum_src].ex_description)
    obj_proto[rnum_targ].ex_description = obj_proto[rnum_src].ex_description;
  obj_proto[rnum_targ].obj_flags = obj_proto[rnum_src].obj_flags;
  obj_proto[rnum_targ].worn_on = obj_proto[rnum_src].worn_on;
  /* add more if you want... */

  return;
}

/*
 * Command interface for:
 * Copying a room or object to another.
 * Attempt at ending the spaghetti:  5-20-98
 */

#define COPY_FORMAT	"Usage:  copy { room | obj } <source> <target>\r\n"

ACMD(do_copy)
{
  char src_num[256], targ_num[256], type[256];
  int vnum_targ = 0, rnum_targ = 0, vnum_src = 0, rnum_src = 0;
  int save_zone = 0, room_or_obj = -1;

  argument = two_arguments(argument, type, src_num);
  one_argument(argument, targ_num);

  /* Here are reasons to give up.  I think they're all right here. */
  if (!*type || !*src_num) {
    send_to_char(COPY_FORMAT, ch);
    return;
  } else if (!*targ_num && (room_or_obj == OBJECT)) {
    send_to_char("You must specify a target when copying objects.\r\n", ch);
    return;
  }

  if (is_abbrev(type, "room") && is_number(src_num)) {
    room_or_obj = ROOM;
    vnum_src = atoi(src_num);
    rnum_src = real_room(vnum_src);
    if (!*targ_num) {
      vnum_targ = world[IN_ROOM(ch)].number;
      rnum_targ = IN_ROOM(ch);
    } else {
      if (!is_number(targ_num)) {
        send_to_char(COPY_FORMAT, ch);
        return;
      }
      vnum_targ = atoi(targ_num);
      rnum_targ = real_room(vnum_targ);
    }
    save_zone = zone_number(&world[rnum_targ], ROOM);
  } else if (is_abbrev(type, "obj") && *targ_num && is_number(src_num) && is_number(targ_num)) {
    room_or_obj = OBJECT;
    vnum_src = atoi(src_num);
    rnum_src = real_object(vnum_src);
    vnum_targ = atoi(targ_num);
    rnum_targ = real_object(vnum_targ);
    save_zone = zone_number(&obj_index[rnum_targ], OBJECT);
  } else {
    send_to_char(COPY_FORMAT, ch);
    return;
  }

  if ((rnum_src < 0) || (rnum_targ < 0)) {
    sprintf(buf, "The source and target %ss must both currently exist.\r\n", (room_or_obj == OBJECT ? "object" : "room"));
    send_to_char(buf, ch);
    return;
  } else if (!can_edit_zone(ch, real_zone(save_zone*100))) {
    send_to_char("You cannot edit that zone.\r\n", ch);
    return;
  }

  /* We should now be ready to go.  All errors have been trapped (?) *
   * and we know what to do.                      TR 5-21-98         */
  switch (room_or_obj) {
  case ROOM:
    copy_room(rnum_src, rnum_targ);
    break;
  case OBJECT:
    copy_object(rnum_src, rnum_targ);
    break;
  default:
    mudlog("SYSERR: OLC: Reached default case in do_copy!", NRM, MAX(LVL_IMMORT, GET_INVIS_LEV(ch)), TRUE);
    send_to_char("There was an error in your copy.  Please report to an "
		"administrator.\r\n", ch);
    return;
  }

  /* I cheated right here a little bit.  By coincidence, *
   * ROOM == OLC_SAVE_ROOM, and OBJECT = OLC_SAVE_OBJ.   *
   * I think this is a Good Thing(tm), although if you   *
   * change the OLC_SAVE_x defines, it will blow up.  :P *
   *                                    TR 5-21-98       */
  sprintf(buf, "You copy %s %d to %d.\r\n", (room_or_obj == ROOM ? "room" : "object"), vnum_src, vnum_targ);
  send_to_char(buf, ch);
  olc_add_to_save_list(save_zone, room_or_obj);
}


/* Supporting functions for RLINK.  A few of these are right out of *
 * OBuild, but I can't remember which.  :P  Credits to Samedi, and  *
 * Daniel Burke, I believe.                   TR 5-21-98            */

/* Create an exit in a room (rnum) in this direction.  (No target) */
int create_dir(int room, int dir)
{
   if ((room > top_of_world) || (room < 0)) {
      log("create_dir(): tried to create invalid door");
      return FALSE;
   }
   if (world[room].dir_option[dir])
     return FALSE;

   CREATE(world[room].dir_option[dir], struct room_direction_data, 1);
   world[room].dir_option[dir]->to_room = NOWHERE;
   world[room].dir_option[dir]->exit_info = 0;
   world[room].dir_option[dir]->general_description = str_dup("You see nothing special.\r\n");
   world[room].dir_option[dir]->keyword = NULL;
   world[room].dir_option[dir]->key = -1;

   return TRUE;

}


/* Remove an exit from a room (rnum). */
int free_dir(int room, int dir)
{
   if ((room > top_of_world) || (room < 0)) {
      log("free_dir(): tried to free invalid door");
      return FALSE;   
   }
   if ((dir < 0) || (dir >= NUM_OF_DIRS)) {
      log("free_dir(): tried to free invalid door");
      return FALSE;
   }

   if (!world[room].dir_option[dir])
     return FALSE;

   world[room].dir_option[dir]->to_room = NOWHERE;
   world[room].dir_option[dir]->exit_info = 0;
   if (world[room].dir_option[dir]->general_description)
     free(world[room].dir_option[dir]->general_description);
   if (world[room].dir_option[dir]->keyword)
     free(world[room].dir_option[dir]->keyword);
   world[room].dir_option[dir]->key = -1;
   free(world[room].dir_option[dir]);
   world[room].dir_option[dir] = NULL;

   return TRUE;

}


/* These defines were harvested from....  zedit.c ?      */
#define ZCMD (zone_table[zone].cmd[cmd_no])
#define W_EXIT(room, num) (world[(room)].dir_option[(num)])
/* ***************************************************** */
#define RLINK_FORMAT	"Usage:  rlink <dir> <connect|disconnect> <1|2> [target]\r\n"

/* The big baby. */
ACMD(do_rlink)
{
  char direction[10], command[20], type[10], target[10];
  int vnum_base = 0, vnum_targ = 0, rnum_base = 0, rnum_targ = 0;
  int dir = 0, k = 0, type_int = 0, top_room = 0;
  int save_zone_1 = 0, save_zone_2 = 0, create_new_room = FALSE;

  argument = two_arguments(argument, direction, command);
  two_arguments(argument, type, target);

  if (!*direction || !*command || !*type) {
    send_to_char(RLINK_FORMAT, ch); 
    return;
  } else if (!is_number(type)) {
    send_to_char(RLINK_FORMAT, ch);
    return;
  }

  type_int = atoi(type);

  if (type_int != 1 && type_int != 2) {
    send_to_char(RLINK_FORMAT, ch);
    return;
  }

  vnum_base = world[IN_ROOM(ch)].number;
  rnum_base = IN_ROOM(ch);

  if (!*target && !is_abbrev(command, "disconnect")) {
    create_new_room = TRUE; 
  } else {
    if (!is_number(target)) {
      send_to_char(RLINK_FORMAT, ch);
      return;
    }
    vnum_targ = atoi(target);
    rnum_targ = real_room(vnum_targ);
  }

  if (rnum_targ < 0) {
    send_to_char(RLINK_FORMAT, ch);
    return;
  }

  save_zone_1 = zone_number(&world[IN_ROOM(ch)], ROOM);

  if (!can_edit_zone(ch, real_zone(save_zone_1*100))) {
    send_to_char("You cannot create exits in this zone.\r\n", ch);
    return;
  }

  if (!create_new_room) {
    if (rnum_targ < 0) {
      send_to_char(RLINK_FORMAT, ch);
      return;
    }
    save_zone_2 = zone_number(&world[rnum_targ], ROOM);
  } else {
    top_room = (zone_table[real_zone(world[IN_ROOM(ch)].number)].top);

    for (k = (save_zone_1 * 100); k <= top_room; k++) {
      if (k > top_room) {
        send_to_char("Cannot create a new room in this zone!\r\n", ch);
        return;
      }
      if (real_room(k) < 0) {
        CREATE(ch->desc->olc, struct olc_data, 1);
        CREATE(OLC_ROOM(ch->desc), struct room_data, 1);

        OLC_ZNUM(ch->desc) = world[ch->in_room].zone;
        OLC_NUM(ch->desc) = k;
        OLC_ROOM(ch->desc)->number = k;
        OLC_ROOM(ch->desc)->zone = world[ch->in_room].zone;
        OLC_ROOM(ch->desc)->name = str_dup("An unfinished room");
        OLC_ROOM(ch->desc)->description = str_dup("You are in an unfinished room.\r\n");
        OLC_ITEM_TYPE(ch->desc) = WLD_TRIGGER;
        OLC_VAL(ch->desc) = 0;
        vnum_targ = k;
    
        redit_save_internally(ch->desc);
        cleanup_olc(ch->desc, CLEANUP_STRUCTS);
        rnum_targ = real_room(vnum_targ);
        save_zone_2 = save_zone_1;
        sprintf(buf, "You have created new room #%i.\r\n", vnum_targ);
        send_to_char(buf, ch);
        break;
      }
    }
  }

  /* save_zone_2 has definitely been established.  If it's a two *
   * way exit, don't let them do the linkage.   TR 5-21-98       */

  if (!can_edit_zone(ch, real_zone(save_zone_2*100)) && type_int == 2) {
    send_to_char("You cannot create exits in the target zone.\r\n", ch);
    return;
  }

  switch (*direction) {
    case 'n':
    case 'N':
      dir = NORTH;
      break;
    case 'e':
    case 'E':
      dir = EAST;
      break;
    case 's':
    case 'S':
      dir = SOUTH;
      break;
    case 'w':
    case 'W':
      dir = WEST;
      break;
    case 'u':
    case 'U':
      dir = UP;
      break;
    case 'd':
    case 'D':
      dir = DOWN;
      break;
    default:
      send_to_char("No such direction!\r\n", ch);
      return;
  }

  if (is_abbrev(command, "connect")) {
    if (!world[ch->in_room].dir_option[dir]) {
      create_dir(IN_ROOM(ch), dir);
      world[IN_ROOM(ch)].dir_option[dir]->to_room = rnum_targ;
    } else
      world[IN_ROOM(ch)].dir_option[dir]->to_room = rnum_targ;

    if (type_int == 2) {
      if (!world[rnum_targ].dir_option[rev_dir[dir]])
        create_dir(rnum_targ, rev_dir[dir]);
      world[rnum_targ].dir_option[rev_dir[dir]]->to_room = IN_ROOM(ch);
      if (!save_zone_2) save_zone_2 = zone_number(&world[rnum_targ], ROOM);
    }
  } else if (is_abbrev(command, "disconnect")) {
    if (type_int == 2) {
      if (world[IN_ROOM(ch)].dir_option[dir]->to_room) {
        free_dir(world[IN_ROOM(ch)].dir_option[dir]->to_room, rev_dir[dir]);
        if (world[IN_ROOM(ch)].dir_option[dir])
          free_dir(IN_ROOM(ch), dir);
        else {
          send_to_char("No such exit!\r\n", ch);
          return;
        }
        save_zone_2 = zone_number(&world[rnum_targ], ROOM);
      } else {
        send_to_char("There is no reciprocol exit to remove.\r\n", ch);
        if (world[IN_ROOM(ch)].dir_option[dir]->to_room) {
          free_dir(IN_ROOM(ch), dir);
        } else {
          send_to_char("No such exit!\r\n", ch);
          return;
        }

      }
    } else if (type_int == 1) {
      if (!world[IN_ROOM(ch)].dir_option[dir]->to_room) {
        send_to_char("No such exit!\r\n", ch); 
        return;
      } else {
        free_dir(IN_ROOM(ch), dir);
      }
    } else {
      send_to_char("Invalid disconnect type.\r\n", ch);
      return;
    }
  } else {
    send_to_char("Invalid command type.  Valid choices are connect and disconnect.\r\n", ch);
    return;
  }

  if (is_abbrev(command, "connect")) {
    sprintf(buf, "You make an exit %s to room %d.\r\n", dirs[dir], vnum_targ);
    send_to_char(buf, ch);
  } else
    send_to_char("Exit deleted.\r\n", ch);

  olc_add_to_save_list(save_zone_1, OLC_SAVE_ROOM);
  if (save_zone_2)
    olc_add_to_save_list(save_zone_2, OLC_SAVE_ROOM);
  return;
}