nakedmudv3.6/
nakedmudv3.6/lib/
nakedmudv3.6/lib/help/A/
nakedmudv3.6/lib/help/B/
nakedmudv3.6/lib/help/C/
nakedmudv3.6/lib/help/D/
nakedmudv3.6/lib/help/G/
nakedmudv3.6/lib/help/H/
nakedmudv3.6/lib/help/J/
nakedmudv3.6/lib/help/L/
nakedmudv3.6/lib/help/M/
nakedmudv3.6/lib/help/O/
nakedmudv3.6/lib/help/P/
nakedmudv3.6/lib/help/R/
nakedmudv3.6/lib/help/S/
nakedmudv3.6/lib/help/W/
nakedmudv3.6/lib/logs/
nakedmudv3.6/lib/misc/
nakedmudv3.6/lib/players/
nakedmudv3.6/lib/txt/
nakedmudv3.6/lib/world/
nakedmudv3.6/lib/world/examples/
nakedmudv3.6/lib/world/examples/mproto/
nakedmudv3.6/lib/world/examples/oproto/
nakedmudv3.6/lib/world/examples/reset/
nakedmudv3.6/lib/world/examples/rproto/
nakedmudv3.6/lib/world/examples/trigger/
nakedmudv3.6/lib/world/limbo/
nakedmudv3.6/lib/world/limbo/room/
nakedmudv3.6/lib/world/limbo/rproto/
nakedmudv3.6/src/alias/
nakedmudv3.6/src/dyn_vars/
nakedmudv3.6/src/editor/
nakedmudv3.6/src/example_module/
nakedmudv3.6/src/help2/
nakedmudv3.6/src/set_val/
nakedmudv3.6/src/socials/
nakedmudv3.6/src/time/
//*****************************************************************************
//
// olc.c
//
// My first go at OLC was during the pre-module time, and it really wasn't all
// that well put together in the first place. This is the second go at it...
// this time, OLC is more like some general functions that make it easy to do
// online editing, rather than a full system. It will be amenable to adding
// new extentions to the general OLC framework from other modules, and it will
// essentially just be a bunch spiffier.
//
//*****************************************************************************

#include "../mud.h"
#include "../utils.h"
#include "../socket.h"
#include "../world.h"
#include "../character.h"  // for POS_XXX <-- we should change the place of this
#include "../auxiliary.h"
#include "../prototype.h"
#include "../zone.h"
#include "../room_reset.h"
#include "../room.h"
#include "../handler.h"
#include "../object.h"
#include "../inform.h"
#include "olc.h"



//*****************************************************************************
// optional modules
//*****************************************************************************
#ifdef MODULE_HELP2
#include "../help2/help.h"
#endif



//*****************************************************************************
//
// local data structures, defines, and functions
//
//*****************************************************************************
typedef struct olc_data {
  void    (* menu)(SOCKET_DATA *, void *);
  int  (* chooser)(SOCKET_DATA *, void *, const char *);
  bool  (* parser)(SOCKET_DATA *, void *, int, const char *);
  void *(* copier)(void *);
  void  (* copyto)(void *, void *);
  void (* deleter)(void *);
  void   (* saver)(void *); 
  void *data;
  void *working_copy;
  int  cmd;
} OLC_DATA;

OLC_DATA *newOLC(void    (* menu)(SOCKET_DATA *, void *),
		 int  (* chooser)(SOCKET_DATA *, void *, const char *),
		 bool  (* parser)(SOCKET_DATA *, void *, int, const char *),
		 void *(* copier)(void *),
		 void  (* copyto)(void *, void *),
		 void (* deleter)(void *),
		 void   (* saver)(void *),
		 void *data) {
  OLC_DATA *olc     = malloc(sizeof(OLC_DATA));
  olc->menu         = menu;
  olc->chooser      = chooser;
  olc->parser       = parser;
  olc->copier       = copier;
  olc->copyto       = copyto;
  olc->deleter      = deleter;
  olc->saver        = saver;
  olc->data         = data;
  olc->working_copy = (copier ? copier(data) : data);
  olc->cmd          = MENU_NOCHOICE;
  return olc;
}

void deleteOLC(OLC_DATA *olc) {
  if(olc->deleter) olc->deleter(olc->working_copy);
  free(olc);
}



//*****************************************************************************
//
// auxiliary data we put on the socket
//
//*****************************************************************************
typedef struct olc_aux_data {
  LIST *olc_stack; // the list of OLCs we have opened
} OLC_AUX_DATA;

OLC_AUX_DATA *
newOLCAuxData() {
  OLC_AUX_DATA *data = malloc(sizeof(OLC_AUX_DATA));
  data->olc_stack = newList();
  return data;
}

void
deleteOLCAuxData(OLC_AUX_DATA *data) {
  if(data->olc_stack) deleteListWith(data->olc_stack, deleteOLC);
  free(data);
}



//*****************************************************************************
//
// Implementation of the OLC framework
//
//*****************************************************************************

//
// display the current menu to the socket, as well as the generic prompt
//
void olc_menu(SOCKET_DATA *sock) {
  // send the current menu
  OLC_AUX_DATA *aux_olc = socketGetAuxiliaryData(sock, "olc_aux_data");
  OLC_DATA         *olc = listGet(aux_olc->olc_stack, 0);
  // don't display then menu if we've made a menu choice
  if(olc->cmd == MENU_NOCHOICE) {
    text_to_buffer(sock, CLEAR_SCREEN);
    olc->menu(sock, olc->working_copy);
#ifdef MODULE_HELP2
    text_to_buffer(sock, "\r\n{gEnter choice, ? [topic] for help, or Q to quit: ");
#else
    text_to_buffer(sock, "\r\n{gEnter choice, or Q to quit: ");
#endif
  }
}


//
// handle the cleanup process for exiting OLC; pop the top handler off of
// the OLC stack and delete it. Save changes if neccessary. Exit the OLC handler
// if we have no more OLC work to do. Return the new and improved version of
// whatever it was we were editing.
//
void *olc_exit(SOCKET_DATA *sock, bool save) {
  // pull up the OLC data to manipulate
  OLC_AUX_DATA *aux_olc = socketGetAuxiliaryData(sock, "olc_aux_data");
  OLC_DATA         *olc = listGet(aux_olc->olc_stack, 0);
  void            *data = olc->data;

  // pop our OLC off of the OLC stack
  listPop(aux_olc->olc_stack);

  // if we need to do any saving, do it now
  if(save) {
    // first, save the changes on the item
    if(olc->working_copy != olc->data && olc->copyto)
      olc->copyto(olc->working_copy, olc->data);
    // now, make sure the changes go to disk
    if(olc->saver)
      olc->saver(olc->data);
  }

  // delete our OLC data (this also deletes our working copy 
  // if it's not the same as our main copy)
  deleteOLC(olc);

  // if our OLC stack is empty, pop the OLC input handler
  if(listSize(aux_olc->olc_stack) == 0)
    socketPopInputHandler(sock);
  return data;
}


//
// Handle input while in OLC
//
void olc_handler(SOCKET_DATA *sock, char *arg) {
  // pull up the OLC data. Are we entering a new command, or are
  // we entering in an argument from a menu choice?
  OLC_AUX_DATA *aux_olc = socketGetAuxiliaryData(sock, "olc_aux_data");
  OLC_DATA         *olc = listGet(aux_olc->olc_stack, 0);

  // we're giving an argument for a menu choice we've already selected
  if(olc->cmd > MENU_NOCHOICE) {
    // the change went alright. Re-display the menu
    if(olc->parser(sock, olc->working_copy, olc->cmd, arg)) {
      olc->cmd = MENU_NOCHOICE;
      //olc_menu(sock);
    }
    else
      text_to_buffer(sock, "Invalid choice!\r\nTry again: ");
  }

  // we're being prompted if we want to save our changes or not before quitting
  else if(olc->cmd == MENU_CHOICE_CONFIRM_SAVE) {
    switch(toupper(*arg)) {
    case 'Y':
    case 'N':
      olc_exit(sock, (toupper(*arg) == 'Y' ? TRUE : FALSE));
      break;
    default:
      text_to_buffer(sock, "Invalid choice!\r\nTry again: ");
      break;
    }
  }

  // we're entering a new choice from our current menu
  else {
    switch(*arg) {
      // we're quitting
    case 'q':
    case 'Q':
      // if our working copy is different from our actual data, prompt to
      // see if we want to save our changes or not
      if(olc->saver) {
	text_to_buffer(sock, "Save changes (Y/N): ");
	olc->cmd = MENU_CHOICE_CONFIRM_SAVE;
      }
      else {
	olc_exit(sock, TRUE);
      }
      break;

#ifdef MODULE_HELP2
    case '?': {
      while(*arg == '?' || isspace(*arg))
	arg++;
      // we have a prompt; don't redisplay our menu
      HELP_DATA *data = get_help(arg, TRUE);
      if(data == NULL) {
	send_to_socket(sock, "No help available.\r\nTry again: ");
	olc->cmd = MENU_CHOICE_INVALID;
      }
      else if(*helpGetUserGroups(data) && socketGetChar(sock) &&
	      !bitIsOneSet(charGetUserGroups(socketGetChar(sock)),
			   helpGetUserGroups(data))) {
	send_to_socket(sock, "You may not view that help file.\r\nTry again: ");
	olc->cmd = MENU_CHOICE_INVALID;
      }
      else {
	// we've (tried to) switched handlers... no menu display
	BUFFER *buf = build_help(arg);
	olc->cmd = MENU_NOCHOICE;
	start_reader(sock, bufferString(buf));
	deleteBuffer(buf);
      }
      break;
    }
#endif

    default: {
      int cmd = olc->chooser(sock, olc->working_copy, arg);
      // the menu choice we entered wasn't a valid one. redisplay the menu
      if(cmd == MENU_CHOICE_INVALID)
	;//olc_menu(sock);
      // the menu choice was acceptable. Note this in our data
      else if(cmd > MENU_NOCHOICE)
	olc->cmd = cmd;
      break;
    }
    }
  }
}



//*****************************************************************************
// builder commands
//*****************************************************************************

//
// Load a copy of a specific mob/object
// usage: load <mob | obj> <vnum>
COMMAND(cmd_load) {
  if(!arg || !*arg)
    send_to_char(ch, "What did you want to load?\r\n");
  else {
    char   type[SMALL_BUFFER];
    char   name[SMALL_BUFFER];
    char locale[SMALL_BUFFER];
    char    key[SMALL_BUFFER];
    arg = one_arg(arg, type);
    if(!parse_worldkey_relative(ch, arg, name, locale)) {
      send_to_char(ch, "What did you want to load?\r\n");
      return;
    }

    sprintf(key, "%s@%s", name, locale);
    if(!strncasecmp("mobile", type, strlen(type))) {
      PROTO_DATA *proto = worldGetType(gameworld, "mproto", key);
      if(proto == NULL)
	send_to_char(ch, "No mobile prototype exists with that key.\r\n");
      else if(protoIsAbstract(proto))
	send_to_char(ch, "That prototype is abstract.\r\n");
      else {
	CHAR_DATA *mob = protoMobRun(proto);
	if(mob == NULL)
	  send_to_char(ch, "There was an error running the prototype.\r\n");
	else {
	  send_to_char(ch, "You create %s.\r\n", charGetName(mob));
	  char_to_room(mob, charGetRoom(ch));
	}
      }
    }

    else if(!strncasecmp("object", type, strlen(type))) {
      PROTO_DATA *proto = worldGetType(gameworld, "oproto", key);
      if(proto == NULL)
	send_to_char(ch, "No object prototype exists with that key.\r\n");
      else if(protoIsAbstract(proto))
	send_to_char(ch, "That prototype is abstract.\r\n");
      else {
	OBJ_DATA *obj = protoObjRun(proto);
	if(obj == NULL)
	  send_to_char(ch, "There was an error running the prototype.\r\n");
	else {
	  send_to_char(ch, "You create %s.\r\n", objGetName(obj));
	  obj_to_char(obj, ch);
	}
      }
    }

    // type not found
    else 
      send_to_char(ch, "What type of thing did you want to load?\r\n");
  }
}


//
// remove an object or player from the game. If no argument is supplied, all
// objects and non-player characters are removed from the current room.
//   usage: purge <target>
COMMAND(cmd_purge) {
  void    *found = NULL;
  int found_type = PARSE_NONE;

  if(!parse_args(ch, TRUE, cmd, arg, "| { ch.room.noself obj.room }",
		 &found, &found_type))
    return;

  // purge everything in the current room
  if(found == NULL) {
    LIST_ITERATOR *list_i = newListIterator(roomGetContents(charGetRoom(ch)));
    OBJ_DATA *obj;
    CHAR_DATA *vict;

    send_to_char(ch, "You purge the room.\r\n");
    message(ch, NULL, NULL, NULL, FALSE, TO_ROOM,
	    "$n raises $s arms, and white flames engulf the entire room.");

    // purge all the objects. 
    ITERATE_LIST(obj, list_i) {
      extract_obj(obj);
    } deleteListIterator(list_i);

    // and now all of the non-characters
    list_i = newListIterator(roomGetCharacters(charGetRoom(ch)));
    ITERATE_LIST(vict, list_i) {
      if(vict == ch || !charIsNPC(vict)) 
	continue;
      extract_mobile(vict);
    } deleteListIterator(list_i);
  }

  // purge characters
  else if(found_type == PARSE_CHAR) {
    // we can only purge him if we have all the same groups as him, and more
    if(!charHasMoreUserGroups(ch, found))
      send_to_char(ch, "Erm, you better not try that on %s. %s has "
		   "just as much priviledges as you.\r\n", 
		   HIMHER(found), HESHE(found));
    else {
      send_to_char(ch, "You purge %s.\r\n", charGetName(found));
      message(ch, found, NULL, NULL, FALSE, TO_ROOM,
	      "$n raises $s arms, and white flames engulf $N.");
      extract_mobile(found);
    }
  }

  // purge objects
  else if(found_type == PARSE_OBJ) {
    send_to_char(ch, "You purge %s.\r\n", objGetName(found));
    message(ch, NULL, found, NULL, FALSE, TO_ROOM,
	    "$n raises $s arms, and white flames engulf $o.");
    obj_from_room(found);
    extract_obj(found);
  }
}


//
// reruns the room's load script, and replaces the old version of the room with
// the new one.
COMMAND(cmd_rreload) {
  PROTO_DATA   *proto = NULL;
  ROOM_DATA *old_room = NULL;
  ROOM_DATA *new_room = NULL;
  ZONE_DATA     *zone = NULL;
  const char     *key = roomGetClass(charGetRoom(ch));

  // unless an arg is supplied, we're working on the current room.
  if(arg && *arg)
    key = get_fullkey_relative(arg, get_key_locale(key));

  // make sure all of our requirements are met
  if( (zone = worldGetZone(gameworld, get_key_locale(key))) == NULL)
    send_to_char(ch, "That zone does not exist!\r\n");
  else if(!canEditZone(zone, ch))
    send_to_char(ch, "You are not authorized to edit that zone.\r\n");
  else if( (proto = worldGetType(gameworld, "rproto", key)) == NULL)
    send_to_char(ch, "No prototype for that room exists.\r\n");
  else if(!worldRoomLoaded(gameworld, key))
    send_to_char(ch, "No room with that key is currently loaded.\r\n");
  else {
    // try running the proto to get our new room...
    old_room = worldGetRoom(gameworld, key);
    new_room = protoRoomRun(proto);
    if(new_room == NULL)
      send_to_char(ch, "There was an error reloading the room.\r\n");
    else {
      do_mass_transfer(old_room, new_room, TRUE, TRUE, TRUE);
      extract_room(old_room);
      worldPutRoom(gameworld, key, new_room);
      send_to_char(ch, "Room reloaded.\r\n");
    }
  }
}


//
// trigger all of a specified zone's reset scripts and such. If no vnum is
// supplied, the zone the user is currently in is reset.
//   usage: zreset <zone vnum>
COMMAND(cmd_zreset) {
  ZONE_DATA *zone = NULL;

  if(!arg || !*arg)
    zone= worldGetZone(gameworld,get_key_locale(roomGetClass(charGetRoom(ch))));
  else
    zone= worldGetZone(gameworld, arg);

  if(zone == NULL)
    send_to_char(ch, "Which zone did you want to reset?\r\n");
  else if(!canEditZone(zone, ch))
    send_to_char(ch, "You are not authorized to edit that zone.\r\n");
  else {
    send_to_char(ch, "%s has been reset.\r\n", zoneGetName(zone));
    zoneForceReset(zone);
  }
}

COMMAND(cmd_rdelete) {
  char *name = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word", &name))
    return;
  if(do_delete(ch, "rproto", deleteProto, name)) {
    do_delete(ch, "reset", deleteResetList, name);
    send_to_char(ch, "If the room has already been used, do not forget to "
		 "also purge the current instance of it.\r\n");
  }
}

COMMAND(cmd_mdelete) {
  char *name = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word", &name))
    return;
  do_delete(ch, "mproto", deleteProto, name);
}

COMMAND(cmd_odelete) {
  char *name = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word", &name))
    return;
  do_delete(ch, "oproto", deleteProto, name);
}



//*****************************************************************************
// Functions for listing different types of data (zones, mobs, objs, etc...)
//*****************************************************************************
//
// returns yes/no if the prototype is abstract or not
const char *prototype_list_info(PROTO_DATA *data) {
  static char buf[SMALL_BUFFER];
  sprintf(buf, "%-50s %3s", 
	  (*protoGetParents(data) ? protoGetParents(data) : "-------"),
	  (protoIsAbstract(data)  ? "yes" : "no"));
  return buf;
}

// this is used for the header when printing out zone proto info
#define PROTO_LIST_HEADER \
"Parents                                       Abstract"


COMMAND(cmd_rlist) {
  do_list(ch, (arg&&*arg?arg:get_key_locale(roomGetClass(charGetRoom(ch)))),
	  "rproto", PROTO_LIST_HEADER, prototype_list_info);
}

COMMAND(cmd_mlist) {
  do_list(ch, (arg&&*arg?arg:get_key_locale(roomGetClass(charGetRoom(ch)))),
	  "mproto", PROTO_LIST_HEADER, prototype_list_info);
}

COMMAND(cmd_olist) {
  do_list(ch, (arg&&*arg?arg:get_key_locale(roomGetClass(charGetRoom(ch)))),
	  "oproto", PROTO_LIST_HEADER, prototype_list_info);
}

COMMAND(cmd_mrename) {
  char *from = NULL, *to = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word word", &from, &to))
    return;
  do_rename(ch, "mproto", from, to);
}

COMMAND(cmd_rrename) {
  char *from = NULL, *to = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word word", &from, &to))
    return;
  if(do_rename(ch, "rproto", from, to)) {
    do_rename(ch, "reset", from, to);
    send_to_char(ch, "No not forget to purge any instances of %s already "
		 "loaded.\r\n", from); 
  }
}

COMMAND(cmd_orename) {
  char *from = NULL, *to = NULL;
  if(!parse_args(ch, TRUE, cmd, arg, "word word", &from, &to))
    return;
  do_rename(ch, "oproto", from, to);
}

COMMAND(cmd_zlist) {
  LIST *keys = worldGetZoneKeys(gameworld);

  // first, order all the zones
  listSortWith(keys, strcasecmp);

  // now, iterate across them all and show them
  LIST_ITERATOR *zone_i = newListIterator(keys);
  ZONE_DATA       *zone = NULL;
  char             *key = NULL;

  send_to_char(ch,
" {wKey            Name                                             Editors  Timer\r\n"
"{b--------------------------------------------------------------------------------\r\n{n");

  ITERATE_LIST(key, zone_i) {
    if( (zone = worldGetZone(gameworld, key)) != NULL) {
      send_to_char(ch, " {c%-14s %-30s %25s  {w%5d\r\n", key, zoneGetName(zone),
		   zoneGetEditors(zone), zoneGetPulseTimer(zone));
    }
  } deleteListIterator(zone_i);
  deleteListWith(keys, free);
  send_to_char(ch, "{g");
}



//*****************************************************************************
// implementation of olc.h
//*****************************************************************************
void init_olc2() {
  // install our auxiliary data on the socket so 
  // we can keep track of our olc data
  auxiliariesInstall("olc_aux_data",
		     newAuxiliaryFuncs(AUXILIARY_TYPE_SOCKET,
				       newOLCAuxData, deleteOLCAuxData,
				       NULL, NULL, NULL, NULL));

  // initialize all of our basic OLC commands. We should probably have init
  // functions to add each of the commands... consistency in style an all...
  extern COMMAND(cmd_redit);
  extern COMMAND(cmd_resedit);
  extern COMMAND(cmd_zedit);
  extern COMMAND(cmd_medit);
  extern COMMAND(cmd_oedit);
  extern COMMAND(cmd_accedit);
  extern COMMAND(cmd_pcedit);
  extern COMMAND(cmd_mpedit);
  extern COMMAND(cmd_opedit);
  extern COMMAND(cmd_rpedit);
  extern COMMAND(cmd_dig);
  extern COMMAND(cmd_fill);
  extern COMMAND(cmd_instantiate);

  add_cmd("zedit",   NULL, cmd_zedit,   "builder", TRUE);
  add_cmd("redit",   NULL, cmd_redit,   "builder", TRUE);
  add_cmd("resedit", NULL, cmd_resedit, "builder", TRUE);
  add_cmd("medit",   NULL, cmd_medit,   "builder", TRUE);
  add_cmd("oedit",   NULL, cmd_oedit,   "builder", TRUE);
  add_cmd("accedit", NULL, cmd_accedit, "admin",   TRUE);
  add_cmd("pcedit",  NULL, cmd_pcedit,  "admin",   TRUE);
  add_cmd("mpedit",  NULL, cmd_mpedit,  "scripter",TRUE);
  add_cmd("opedit",  NULL, cmd_opedit,  "scripter",TRUE);
  add_cmd("rpedit",  NULL, cmd_rpedit,  "scripter",TRUE);

  add_cmd("dig",     NULL, cmd_dig,     "builder", TRUE);
  add_cmd("fill",    NULL, cmd_fill,    "builder", TRUE);
  add_cmd("purge",   NULL, cmd_purge,   "builder", FALSE);
  add_cmd("load",    NULL, cmd_load,    "builder", FALSE);
  add_cmd("rcopy",   NULL, cmd_instantiate,"builder", TRUE);

  add_cmd("mlist",   NULL, cmd_mlist,   "builder", FALSE);
  add_cmd("mdelete", NULL, cmd_mdelete, "builder", FALSE);
  add_cmd("mrename", NULL, cmd_mrename, "builder", FALSE);
  add_cmd("olist",   NULL, cmd_olist,   "builder", FALSE);
  add_cmd("odelete", NULL, cmd_odelete, "builder", FALSE);
  add_cmd("orename", NULL, cmd_orename, "builder", FALSE);
  add_cmd("rreload", NULL, cmd_rreload, "builder", FALSE);
  add_cmd("rlist",   NULL, cmd_rlist,   "builder", FALSE);
  add_cmd("rdelete", NULL, cmd_rdelete, "builder", FALSE);
  add_cmd("rrename", NULL, cmd_rrename, "builder", FALSE);
  add_cmd("zlist",   NULL, cmd_zlist,   "builder", TRUE);
  add_cmd("zreset",  NULL, cmd_zreset,  "builder", FALSE);
}

void do_olc(SOCKET_DATA *sock,
	    void *menu,
	    void *chooser,
	    void *parser,
	    void *copier,
	    void *copyto,
	    void *deleter,
	    void *saver,
	    void *data) {
  // first, we create a new OLC data structure, and then push it onto the stack
  OLC_DATA *olc = newOLC(menu, chooser, parser, copier, copyto, deleter,
			 saver, data);
  OLC_AUX_DATA *aux_olc = socketGetAuxiliaryData(sock, "olc_aux_data");
  listPush(aux_olc->olc_stack, olc);

  // if this is the only olc data on the stack, then enter the OLC handler
  if(listSize(aux_olc->olc_stack) == 1)
    socketPushInputHandler(sock, olc_handler, olc_menu);
}

void olc_display_table(SOCKET_DATA *sock, const char *getName(int val),
		       int num_vals, int num_cols) {
  int i, print_room;
  char fmt[100];

  print_room = (80 - 10*num_cols)/num_cols;
  sprintf(fmt, "  {c%%2d{y) {g%%-%ds%%s", print_room);

  for(i = 0; i < num_vals; i++)
    send_to_socket(sock, fmt, i, getName(i), 
		   (i % num_cols == (num_cols - 1) ? "\r\n" : "   "));

  if(i % num_cols != 0)
    send_to_socket(sock, "\r\n");
}

void olc_display_list(SOCKET_DATA *sock, LIST *list, int num_cols) {
  char fmt[100];
  LIST_ITERATOR *list_i = newListIterator(list);
  int print_room, i = 0;
  char *str = NULL;
  
  print_room = (80 - 10*num_cols)/num_cols;
  sprintf(fmt, "  {c%%2d{y) {g%%-%ds%%s", print_room);

  ITERATE_LIST(str, list_i) {
    send_to_socket(sock, fmt, i, str, (i % num_cols == (num_cols - 1) ? 
				       "\r\n" : "   "));
    i++;
  }
  deleteListIterator(list_i);

  if(i % num_cols != 0)
    send_to_socket(sock, "\r\n");
}