dbx/cnf/
dbx/lib/
dbx/lib/misc/
dbx/lib/text/help/
dbx/lib/world/
dbx/lib/world/qst/
dbx/src/
/**************************************************************************
*   File: modify.c                                      Part of CircleMUD *
*  Usage: Run-time modification of game variables                         *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
**************************************************************************/

#include "conf.h"
#include "sysdep.h"


#include "structs.h"
#include "utils.h"
#include "interpreter.h"
#include "handler.h"
#include "db.h"
#include "comm.h"
#include "spells.h"
#include "mail.h"
#include "boards.h"
#include "olc.h"


void show_string(struct descriptor_data *d, char *input);

#define PARSE_FORMAT	0
#define PARSE_REPLACE	1
#define PARSE_HELP	2
#define PARSE_DELETE	3
#define PARSE_INSERT	4
#define PARSE_LIST_NORM	5
#define PARSE_LIST_NUM	6
#define PARSE_EDIT	7

extern struct spell_entry spells[];
extern char *MENU;

/* local functions */
void string_add(struct descriptor_data *d, char *str);
ACMD(do_skillset);
char *next_page(char *str);
int count_pages(char *str);
void paginate_string(char *str, struct descriptor_data *d);

const char *string_fields[] =
{
  "name",
  "short",
  "long",
  "description",
  "title",
  "delete-description",
  "\n"
};

/*
 * Maximum length for text field x+1
 */
int length[] =
{
  15,
  60,
  256,
  240,
  60
};

/*************************************
 * Modification of malloc'ed strings.*
 *************************************/

/*
 * Handle some editor commands.
 */
void parse_action(int command, char *string, struct descriptor_data *d)
{
  int indent = 0, rep_all = 0, flags = 0, total_len, replaced;
  register int j = 0;
  int i, line_low, line_high;
  char *s, *t, temp;

  switch (command) {
  case PARSE_HELP:
    sprintf(buf,
	    "Editor command formats: /<letter>\r\n\r\n"
	    "/a         -  aborts editor\r\n"
	    "/c         -  clears buffer\r\n"
	    "/d#        -  deletes a line #\r\n"
	    "/e# <text> -  changes the line at # with <text>\r\n"
	    "/f         -  formats text\r\n"
	    "/fi        -  indented formatting of text\r\n"
	    "/h         -  list text editor commands\r\n"
	    "/i# <text> -  inserts <text> before line #\r\n"
	    "/l         -  lists buffer\r\n"
	    "/n         -  lists buffer with line numbers\r\n"
	    "/r 'a' 'b' -  replace 1st occurance of text <a> in buffer with text <b>\r\n"
	    "/ra 'a' 'b'-  replace all occurances of text <a> within buffer with text <b>\r\n"
	    "              usage: /r[a] 'pattern' 'replacement'\r\n"
	    "/s         -  saves text\r\n");
    SEND_TO_Q(buf, d);
    break;
  case PARSE_FORMAT:
    while (isalpha(string[j]) && j < 2) {
      switch (string[j]) {
      case 'i':
	if (!indent) {
	  indent = TRUE;
	  flags += FORMAT_INDENT;
	}
	break;
      default:
	break;
      }
      j++;
    }
    format_text(d->str, flags, d, d->max_str);
    sprintf(buf, "Text formatted with%s indent.\r\n", (indent ? "" : "out"));
    SEND_TO_Q(buf, d);
    break;
  case PARSE_REPLACE:
    while (isalpha(string[j]) && j < 2) {
      switch (string[j]) {
      case 'a':
	if (!indent)
	  rep_all = 1;
	break;
      default:
	break;
      }
      j++;
    }
    if ((s = strtok(string, "'")) == NULL) {
      SEND_TO_Q("Invalid format.\r\n", d);
      return;
    } else if ((s = strtok(NULL, "'")) == NULL) {
      SEND_TO_Q("Target string must be enclosed in single quotes.\r\n", d);
      return;
    } else if ((t = strtok(NULL, "'")) == NULL) {
      SEND_TO_Q("No replacement string.\r\n", d);
      return;
    } else if ((t = strtok(NULL, "'")) == NULL) {
      SEND_TO_Q("Replacement string must be enclosed in single quotes.\r\n", d);
      return;
    } else if ((total_len = ((strlen(t) - strlen(s)) + strlen(*d->str))) <= d->max_str) {
      if ((replaced = replace_str(d->str, s, t, rep_all, d->max_str)) > 0) {
	sprintf(buf, "Replaced %d occurance%sof '%s' with '%s'.\r\n", replaced, ((replaced != 1) ? "s " : " "), s, t);
	SEND_TO_Q(buf, d);
      } else if (replaced == 0) {
	sprintf(buf, "String '%s' not found.\r\n", s);
	SEND_TO_Q(buf, d);
      } else
	SEND_TO_Q("ERROR: Replacement string causes buffer overflow, aborted replace.\r\n", d);
    } else
      SEND_TO_Q("Not enough space left in buffer.\r\n", d);
    break;
  case PARSE_DELETE:
    switch (sscanf(string, " %d - %d ", &line_low, &line_high)) {
    case 0:
      SEND_TO_Q("You must specify a line number or range to delete.\r\n", d);
      return;
    case 1:
      line_high = line_low;
      break;
    case 2:
      if (line_high < line_low) {
	SEND_TO_Q("That range is invalid.\r\n", d);
	return;
      }
      break;
    }

    i = 1;
    total_len = 1;
    if ((s = *d->str) == NULL) {
      SEND_TO_Q("Buffer is empty.\r\n", d);
      return;
    } else if (line_low > 0) {
      while (s && (i < line_low))
	if ((s = strchr(s, '\n')) != NULL) {
	  i++;
	  s++;
	}
      if ((i < line_low) || (s == NULL)) {
	SEND_TO_Q("Line(s) out of range; not deleting.\r\n", d);
	return;
      }
      t = s;
      while (s && (i < line_high))
	if ((s = strchr(s, '\n')) != NULL) {
	  i++;
	  total_len++;
	  s++;
	}
      if ((s) && ((s = strchr(s, '\n')) != NULL)) {
	s++;
	while (*s != '\0')
	  *(t++) = *(s++);
      } else
	total_len--;
      *t = '\0';
      RECREATE(*d->str, char, strlen(*d->str) + 3);

      sprintf(buf, "%d line%sdeleted.\r\n", total_len,
	      ((total_len != 1) ? "s " : " "));
      SEND_TO_Q(buf, d);
    } else {
      SEND_TO_Q("Invalid line numbers to delete must be higher than 0.\r\n", d);
      return;
    }
    break;
  case PARSE_LIST_NORM:
    /*
     * Note: Rv's buf, buf1, buf2, and arg variables are defined to 32k so
     * they are probly ok for what to do here. 
     */
    *buf = '\0';
    if (*string != '\0')
      switch (sscanf(string, " %d - %d ", &line_low, &line_high)) {
      case 0:
	line_low = 1;
	line_high = 999999;
	break;
      case 1:
	line_high = line_low;
	break;
    } else {
      line_low = 1;
      line_high = 999999;
    }

    if (line_low < 1) {
      SEND_TO_Q("Line numbers must be greater than 0.\r\n", d);
      return;
    } else if (line_high < line_low) {
      SEND_TO_Q("That range is invalid.\r\n", d);
      return;
    }
    *buf = '\0';
    if ((line_high < 999999) || (line_low > 1))
      sprintf(buf, "Current buffer range [%d - %d]:\r\n", line_low, line_high);
    i = 1;
    total_len = 0;
    s = *d->str;
    while (s && (i < line_low))
      if ((s = strchr(s, '\n')) != NULL) {
	i++;
	s++;
      }
    if ((i < line_low) || (s == NULL)) {
      SEND_TO_Q("Line(s) out of range; no buffer listing.\r\n", d);
      return;
    }
    t = s;
    while (s && (i <= line_high))
      if ((s = strchr(s, '\n')) != NULL) {
	i++;
	total_len++;
	s++;
      }
    if (s) {
      temp = *s;
      *s = '\0';
      strcat(buf, t);
      *s = temp;
    } else
      strcat(buf, t);
    /*
     * This is kind of annoying...but some people like it.
     */
#if 0
    sprintf(buf, "%s\r\n%d line%sshown.\r\n", buf, total_len,
	((total_len != 1)?"s ":" ")); 
#endif
    page_string(d, buf, TRUE);
    break;
  case PARSE_LIST_NUM:
    /*
     * Note: Rv's buf, buf1, buf2, and arg variables are defined to 32k so
     * they are probly ok for what to do here. 
     */
    *buf = '\0';
    if (*string != '\0')
      switch (sscanf(string, " %d - %d ", &line_low, &line_high)) {
      case 0:
	line_low = 1;
	line_high = 999999;
	break;
      case 1:
	line_high = line_low;
	break;
    } else {
      line_low = 1;
      line_high = 999999;
    }

    if (line_low < 1) {
      SEND_TO_Q("Line numbers must be greater than 0.\r\n", d);
      return;
    }
    if (line_high < line_low) {
      SEND_TO_Q("That range is invalid.\r\n", d);
      return;
    }
    *buf = '\0';
    i = 1;
    total_len = 0;
    s = *d->str;
    while (s && (i < line_low))
      if ((s = strchr(s, '\n')) != NULL) {
	i++;
	s++;
      }
    if ((i < line_low) || (s == NULL)) {
      SEND_TO_Q("Line(s) out of range; no buffer listing.\r\n", d);
      return;
    }
    t = s;
    while (s && (i <= line_high))
      if ((s = strchr(s, '\n')) != NULL) {
	i++;
	total_len++;
	s++;
	temp = *s;
	*s = '\0';
	sprintf(buf, "%s%4d:\r\n", buf, (i - 1));
	strcat(buf, t);
	*s = temp;
	t = s;
      }
    if (s && t) {
      temp = *s;
      *s = '\0';
      strcat(buf, t);
      *s = temp;
    } else if (t)
      strcat(buf, t);
    /*
     * This is kind of annoying .. seeing as the lines are numbered.
     */
#if 0
     sprintf(buf, "%s\r\n%d numbered line%slisted.\r\n", buf, total_len,
	((total_len != 1)?"s ":" ")); 
#endif
    page_string(d, buf, TRUE);
    break;

  case PARSE_INSERT:
    half_chop(string, buf, buf2);
    if (*buf == '\0') {
      SEND_TO_Q("You must specify a line number before which to insert text.\r\n", d);
      return;
    }
    line_low = atoi(buf);
    strcat(buf2, "\r\n");

    i = 1;
    *buf = '\0';
    if ((s = *d->str) == NULL) {
      SEND_TO_Q("Buffer is empty, nowhere to insert.\r\n", d);
      return;
    }
    if (line_low > 0) {
      while (s && (i < line_low))
	if ((s = strchr(s, '\n')) != NULL) {
	  i++;
	  s++;
	}
      if ((i < line_low) || (s == NULL)) {
	SEND_TO_Q("Line number out of range; insert aborted.\r\n", d);
	return;
      }
      temp = *s;
      *s = '\0';
      if ((strlen(*d->str) + strlen(buf2) + strlen(s + 1) + 3) > d->max_str) {
	*s = temp;
	SEND_TO_Q("Insert text pushes buffer over maximum size, insert aborted.\r\n", d);
	return;
      }
      if (*d->str && (**d->str != '\0'))
	strcat(buf, *d->str);
      *s = temp;
      strcat(buf, buf2);
      if (s && (*s != '\0'))
	strcat(buf, s);
      RECREATE(*d->str, char, strlen(buf) + 3);

      strcpy(*d->str, buf);
      SEND_TO_Q("Line inserted.\r\n", d);
    } else {
      SEND_TO_Q("Line number must be higher than 0.\r\n", d);
      return;
    }
    break;

  case PARSE_EDIT:
    half_chop(string, buf, buf2);
    if (*buf == '\0') {
      SEND_TO_Q("You must specify a line number at which to change text.\r\n", d);
      return;
    }
    line_low = atoi(buf);
    strcat(buf2, "\r\n");

    i = 1;
    *buf = '\0';
    if ((s = *d->str) == NULL) {
      SEND_TO_Q("Buffer is empty, nothing to change.\r\n", d);
      return;
    }
    if (line_low > 0) {
      /*
       * Loop through the text counting \\n characters until we get to the line/
       */
      while (s && (i < line_low))
	if ((s = strchr(s, '\n')) != NULL) {
	  i++;
	  s++;
	}
      /*
       * Make sure that there was a THAT line in the text.
       */
      if ((i < line_low) || (s == NULL)) {
	SEND_TO_Q("Line number out of range; change aborted.\r\n", d);
	return;
      }
      /*
       * If s is the same as *d->str that means I'm at the beginning of the
       * message text and I don't need to put that into the changed buffer.
       */
      if (s != *d->str) {
	/*
	 * First things first .. we get this part into the buffer.
	 */
	temp = *s;
	*s = '\0';
	/*
	 * Put the first 'good' half of the text into storage.
	 */
	strcat(buf, *d->str);
	*s = temp;
      }
      /*
       * Put the new 'good' line into place.
       */
      strcat(buf, buf2);
      if ((s = strchr(s, '\n')) != NULL) {
	/*
	 * This means that we are at the END of the line, we want out of
	 * there, but we want s to point to the beginning of the line
	 * AFTER the line we want edited 
	 */
	s++;
	/*
	 * Now put the last 'good' half of buffer into storage.
	 */
	strcat(buf, s);
      }
      /*
       * Check for buffer overflow.
       */
      if (strlen(buf) > d->max_str) {
	SEND_TO_Q("Change causes new length to exceed buffer maximum size, aborted.\r\n", d);
	return;
      }
      /*
       * Change the size of the REAL buffer to fit the new text.
       */
      RECREATE(*d->str, char, strlen(buf) + 3);
      strcpy(*d->str, buf);
      SEND_TO_Q("Line changed.\r\n", d);
    } else {
      SEND_TO_Q("Line number must be higher than 0.\r\n", d);
      return;
    }
    break;
  default:
    SEND_TO_Q("Invalid option.\r\n", d);
    mudlog("SYSERR: invalid command passed to parse_action", BRF, LVL_IMPL, TRUE);
    return;
  }
}


/*
 Add user input to the 'current' string (as defined by d->str).
 */
void string_add(struct descriptor_data *d, char *str)
{
  FILE *fl;
  int terminator = 0, action = 0;
  register int i = 2, j = 0;
  char actions[MAX_INPUT_LENGTH];


  /* determine if this is the terminal string, and truncate if so */
  /* changed to only accept '@' at the beginning of line - J. Elson 1/17/94 */
  /* Changed to accept '/<letter>' style editing commands - instead */
  /*   of solitary '@' to end. - M. Scott 10/15/96 */

  delete_doubledollar(str);

#if 0
  /* Removed old handling of '@' character, put #if 1 to re-enable it. */
  if ((terminator = (*str == '@')))
    *str = '\0';
#endif

#if 0
  /*
   * Erase any ~'s inserted by people in the editor.  This prevents anyone
   * using online creation from causing parse errors in the world files.
   * Derived from an idea by Sammy <samedi@dhc.net> (who happens to like
   * his tildes thank you very much.), -gg 2/20/98
   */
  {
    char *tilde = str;
    while ((tilde = strchr(tilde, '~')))
      *tilde = ' ';
  }
#endif

  if ((action = (*str == '/'))) {
    while (str[i] != '\0') {
      actions[j] = str[i];
      i++;
      j++;
    }
    actions[j] = '\0';
    *str = '\0';
    switch (str[1]) {
    case 'a':
      terminator = 2;		/* Working on an abort message, */
      break;
    case 'c':
      if (*(d->str)) {
	free(*d->str);
	*(d->str) = NULL;
	SEND_TO_Q("Current buffer cleared.\r\n", d);
      } else
	SEND_TO_Q("Current buffer empty.\r\n", d);
      break;
    case 'd':
      parse_action(PARSE_DELETE, actions, d);
      break;
    case 'e':
      parse_action(PARSE_EDIT, actions, d);
      break;
    case 'f':
      if (*(d->str))
	parse_action(PARSE_FORMAT, actions, d);
      else
	SEND_TO_Q("Current buffer empty.\r\n", d);
      break;
    case 'i':
      if (*(d->str))
	parse_action(PARSE_INSERT, actions, d);
      else
	SEND_TO_Q("Current buffer empty.\r\n", d);
      break;
    case 'h':
      parse_action(PARSE_HELP, actions, d);
      break;
    case 'l':
      if (*d->str)
	parse_action(PARSE_LIST_NORM, actions, d);
      else
	SEND_TO_Q("Current buffer empty.\r\n", d);
      break;
    case 'n':
      if (*d->str)
	parse_action(PARSE_LIST_NUM, actions, d);
      else
	SEND_TO_Q("Current buffer empty.\r\n", d);
      break;
    case 'r':
      parse_action(PARSE_REPLACE, actions, d);
      break;
    case 's':
      terminator = 1;
      *str = '\0';
      break;
    default:
      SEND_TO_Q("Invalid option.\r\n", d);
      break;
    }
  }


  if (!(*d->str)) {
    if (strlen(str) > d->max_str) {
      send_to_char("String too long - Truncated.\r\n",
		   d->character);
      *(str + d->max_str) = '\0';
      /* Changed this to NOT abort out.. just give warning. */
      /* terminator = 1; */
    }
    CREATE(*d->str, char, strlen(str) + 3);
    strcpy(*d->str, str);
  } else {
    if (strlen(str) + strlen(*d->str) > d->max_str) {
      send_to_char("String too long, limit reached on message. Last line ignored.\r\n",
                   d->character);
      /* terminator = 1; */
    } else {
      if (!(*d->str = (char *) realloc(*d->str, strlen(*d->str) +
				       strlen(str) + 3))) {
	perror("SYSERR: string_add");
	exit(1);
      }
      strcat(*d->str, str);
    }
  }

  if (terminator) {
    /*
     * OLC Edits
     */
    extern void oedit_disp_menu(struct descriptor_data *d);
    extern void oedit_disp_extradesc_menu(struct descriptor_data *d);
    extern void redit_disp_menu(struct descriptor_data *d);
    extern void redit_disp_extradesc_menu(struct descriptor_data *d);
    extern void redit_disp_exit_menu(struct descriptor_data *d);
    extern void medit_disp_menu(struct descriptor_data *d);
    extern void hedit_disp_menu(struct descriptor_data *d);
    extern void trigedit_disp_menu(struct descriptor_data *d);


#if defined(OASIS_MPROG)
    extern void medit_change_mprog(struct descriptor_data *d);

    if (STATE(d) == CON_MEDIT) {
      switch (OLC_MODE(d)) {
      case MEDIT_D_DESC:
	medit_disp_menu(d);
	break;
      case MEDIT_MPROG_COMLIST:
	medit_change_mprog(d);
	break;
      }
    }
#endif

    /*
     * Here we check for the abort option and reset the pointers.
     */
    if ((terminator == 2) &&
	((STATE(d) == CON_REDIT) ||
	 (STATE(d) == CON_HEDIT) ||
	 (STATE(d) == CON_MEDIT) ||
	 (STATE(d) == CON_OEDIT) ||
         (STATE(d) == CON_TRIGEDIT) ||
         (STATE(d) == CON_TEXTED) || 
	 (STATE(d) == CON_EXDESC))) {
      free(*d->str);
      if (d->backstr) {
	*d->str = d->backstr;
      } else
	*d->str = NULL;
      d->backstr = NULL;
      d->str = NULL;
    }
    /*
     * This fix causes the editor to NULL out empty messages -- M. Scott 
     * Fixed to fix the fix for empty fixed messages. -- gg
     */
    else if ((d->str) && (*d->str) && (**d->str == '\0')) {
      free(*d->str);
      *d->str = str_dup("Nothing.\r\n");
    }
    if (STATE(d) == CON_MEDIT)
      medit_disp_menu(d);
    if (STATE(d) == CON_TRIGEDIT)
      trigedit_disp_menu(d);

    if (STATE(d) == CON_OEDIT) {
      switch (OLC_MODE(d)) {
      case OEDIT_ACTDESC:
	oedit_disp_menu(d);
	break;
      case OEDIT_EXTRADESC_DESCRIPTION:
	oedit_disp_extradesc_menu(d);
	break;
      }
    } else if (STATE(d) == CON_HEDIT)
      hedit_disp_menu(d);
    else if (STATE(d) == CON_REDIT) {
      switch (OLC_MODE(d)) {
      case REDIT_DESC:
	redit_disp_menu(d);
	break;
      case REDIT_EXIT_DESCRIPTION:
	redit_disp_exit_menu(d);
	break;
      case REDIT_EXTRADESC_DESCRIPTION:
	redit_disp_extradesc_menu(d);
	break;
      }
    } else if (!d->connected && (PLR_FLAGGED(d->character, PLR_MAILING))) {
      if ((terminator == 1) && *d->str) {
	store_mail(d->mail_to, GET_IDNUM(d->character), *d->str);
	SEND_TO_Q("Message sent!\r\n", d);
      } else
	SEND_TO_Q("Mail aborted.\r\n", d);
      d->mail_to = 0;
      free(*d->str);
      free(d->str);
    } else if (d->mail_to >= BOARD_MAGIC) {
      Board_save_board(d->mail_to - BOARD_MAGIC);
      SEND_TO_Q("Post not aborted, use REMOVE <post #>.\r\n", d);
      d->mail_to = 0;
    } else if (STATE(d) == CON_EXDESC) {
      if (terminator != 1)
	SEND_TO_Q("Description aborted.\r\n", d);
      SEND_TO_Q(MENU, d);
      d->connected = CON_MENU;
    } else if (STATE(d) == CON_TEXTED) {
      if (terminator == 1) {
        if (!(fl = fopen((char *)OLC_STORAGE(d), "w"))) {
          sprintf(buf, "SYSERR: Can't write file '%s'.", OLC_STORAGE(d));
          mudlog(buf, CMP, LVL_IMPL, TRUE);
        } else {
          if (*d->str) {
            fputs(stripcr(buf1, *d->str), fl);
          }
          fclose(fl);
          sprintf(buf, "OLC: %s saves '%s'.", GET_NAME(d->character),
		OLC_STORAGE(d));
          mudlog(buf, CMP, LVL_GOD, TRUE);
          SEND_TO_Q("Saved.\r\n", d);
        }
      }
      else SEND_TO_Q("Edit aborted.\r\n", d);
      act("$n stops editing some SENZUs.", TRUE, d->character, 0, 0, TO_ROOM);
      free(OLC_STORAGE(d));
      OLC_STORAGE(d) = NULL;
      free(d->olc);
      STATE(d) = CON_PLAYING;
    } else if (!d->connected && d->character && !IS_NPC(d->character)) {
      if (terminator == 1) {
	if (strlen(*d->str) == 0) {
	  free(*d->str);
	  *d->str = NULL;
	}
      } else {
	free(*d->str);
	if (d->backstr)
	  *d->str = d->backstr;
	else
	  *d->str = NULL;
	d->backstr = NULL;
	SEND_TO_Q("Message aborted.\r\n", d);
      }
    }
    if (d->character && !IS_NPC(d->character))
      REMOVE_BIT(PLR_FLAGS(d->character), PLR_WRITING | PLR_MAILING);
    if (d->backstr)
      free(d->backstr);
    d->backstr = NULL;
    d->str = NULL;
  } else if (!action)
    strcat(*d->str, "\r\n");
}



/* **********************************************************************
*  Modification of character skills                                     *
********************************************************************** */

ACMD(do_skillset)
{
  struct char_data *vict;
  char name[MAX_INPUT_LENGTH], buf2[128];
  char buf[MAX_INPUT_LENGTH], help[MAX_STRING_LENGTH];
  int skill, value, i, qend;

  argument = one_argument(argument, name);

  /*
   * No arguments. print an informative text.
   */
  if (!*name) {
    send_to_char("Syntax: skillset <name> '<skill>' <value>\r\n", ch);
    strcpy(help, "Skill being one of the following:\r\n");
    for (i = 0; spells[i].name[0] != '\n'; i++) {
      if (spells[i].name[0] == '!')
	continue;
      sprintf(help + strlen(help), "%18s", spells[i].name);
      if (i % 4 == 3) {
	strcat(help, "\r\n");
	send_to_char(help, ch);
	*help = '\0';
      }
    }
    if (*help)
      send_to_char(help, ch);
    send_to_char("\r\n", ch);
    return;
  }
  if (!(vict = get_char_vis(ch, name))) {
    send_to_char(NOPERSON, ch);
    return;
  }
  skip_spaces(&argument);

  /* If there is no chars in argument */
  if (!*argument) {
    send_to_char("Skill name expected.\r\n", ch);
    return;
  }
  if (*argument != '\'') {
    send_to_char("Skill must be enclosed in: ''\r\n", ch);
    return;
  }
  /* Locate the last quote && lowercase the magic words (if any) */

  for (qend = 1; *(argument + qend) && (*(argument + qend) != '\''); qend++)
    *(argument + qend) = LOWER(*(argument + qend));

  if (*(argument + qend) != '\'') {
    send_to_char("Skill must be enclosed in: ''\r\n", ch);
    return;
  }
  strcpy(help, (argument + 1));
  help[qend - 1] = '\0';
  if ((skill = find_skill_num(help)) <= 0) {
    send_to_char("Unrecognized skill.\r\n", ch);
    return;
  }
  argument += qend + 1;		/* skip to next parameter */
  argument = one_argument(argument, buf);

  if (!*buf) {
    send_to_char("Learned value expected.\r\n", ch);
    return;
  }
  value = atoi(buf);
  if (value < 0) {
    send_to_char("Minimum value for learned is 0.\r\n", ch);
    return;
  }
  if (value > 100) {
    send_to_char("Max value for learned is 100.\r\n", ch);
    return;
  }
  if (IS_NPC(vict)) {
    send_to_char("You can't set NPC skills.\r\n", ch);
    return;
  }
  sprintf(buf2, "%s changed %s's %s to %d.", GET_NAME(ch), GET_NAME(vict),
	  spells[skill].name, value);
  mudlog(buf2, BRF, -1, TRUE);

  SET_SKILL(vict, skill, value);

  sprintf(buf2, "You change %s's %s to %d.\r\n", GET_NAME(vict),
	  spells[skill].name, value);
  send_to_char(buf2, ch);
}



/*********************************************************************
* New Pagination Code
* Michael Buselli submitted the following code for an enhanced pager
* for CircleMUD.  All functions below are his.  --JE 8 Mar 96
*********************************************************************/

#define PAGE_LENGTH     22
#define PAGE_WIDTH      80

/*
 * Traverse down the string until the begining of the next page has been
 * reached.  Return NULL if this is the last page of the string.
 */
char *next_page(char *str)
{
  int col = 1, line = 1, spec_code = FALSE;

  for (;; str++) {
    /*
     * If we're at the end of the string, return NULL.
     */
    if (*str == '\0')
      return NULL;

    /*
     * If we're at the start of the next page, return this fact.
     */
    else if (line > PAGE_LENGTH)
      return str;

    /*
     * Check for the begining of an ANSI color code block.
     */
    else if (*str == '\x1B' && !spec_code)
      spec_code = TRUE;

    /*
     * Check for the end of an ANSI color code block.
     */
    else if (*str == 'm' && spec_code)
      spec_code = FALSE;

    /*
     * Check for everything else.
     */
    else if (!spec_code) {
      /*
       * Carriage return puts us in column one.
       */
      if (*str == '\r')
	col = 1;
      /*
       * Newline puts us on the next line.
       */
      else if (*str == '\n')
	line++;

      /*
       * We need to check here and see if we are over the page width,
       * and if so, compensate by going to the begining of the next line. 
       */
      else if (col++ > PAGE_WIDTH) {
	col = 1;
	line++;
      }
    }
  }
}

/*
 * Function that returns the number of pages in the string.
 */
int count_pages(char *str)
{
  int pages;

  for (pages = 1; (str = next_page(str)); pages++);
  return pages;
}

/*
 * This function assigns all the pointers for showstr_vector for the
 * page_string function, after showstr_vector has been allocated and
 * showstr_count set.
 */
void paginate_string(char *str, struct descriptor_data *d)
{
  int i;

  if (d->showstr_count)
    *(d->showstr_vector) = str;

  for (i = 1; i < d->showstr_count && str; i++)
    str = d->showstr_vector[i] = next_page(str);

  d->showstr_page = 0;
}

/*
 * The call that gets the paging ball rolling... 
 */
void page_string(struct descriptor_data *d, char *str, int keep_internal)
{
  if (!d)
    return;

  if (!str || !*str) {
    send_to_char("", d->character);
    return;
  }
  CREATE(d->showstr_vector, char *, d->showstr_count = count_pages(str));

  if (keep_internal) {
    d->showstr_head = str_dup(str);
    paginate_string(d->showstr_head, d);
  } else
    paginate_string(str, d);

  show_string(d, "");
}

/*
 * The call that displays the next page. 
 */
void show_string(struct descriptor_data *d, char *input)
{
  char buffer[MAX_STRING_LENGTH];
  int diff;

  any_one_arg(input, buf);

  /*
   * Q is for quit. :) 
   */
  if (LOWER(*buf) == 'q') {
    free(d->showstr_vector);
    d->showstr_count = 0;
    if (d->showstr_head) {
      free(d->showstr_head);
      d->showstr_head = NULL;
    }
    return;
  }
  /*
   * R is for refresh, so back up one page internally so we can display
   * it again. 
   */
  else if (LOWER(*buf) == 'r')
    d->showstr_page = MAX(0, d->showstr_page - 1);

  /*
   * B is for back, so back up two pages internally so we can display the
   * correct page here. 
   */
  else if (LOWER(*buf) == 'b')
    d->showstr_page = MAX(0, d->showstr_page - 2);

  /*
   * Feature to 'goto' a page.  Just type the number of the page and you
   * are there! Very handy.
   */
  else if (isdigit(*buf))
    d->showstr_page = MAX(0, MIN(atoi(buf) - 1, d->showstr_count - 1));

  else if (*buf) {
    send_to_char(
		  "Valid commands while paging are RETURN, Q, R, B, or a numeric value.\r\n",
		  d->character);
    return;
  }
  /*
   * If we're displaying the last page, just send it to the character, and
   * then free up the space we used. 
   */
  if (d->showstr_page + 1 >= d->showstr_count) {
    send_to_char(d->showstr_vector[d->showstr_page], d->character);
    free(d->showstr_vector);
    d->showstr_count = 0;
    if (d->showstr_head) {
      free(d->showstr_head);
      d->showstr_head = NULL;
    }
  }
  /* Or if we have more to show.... */
  else {
    diff = (int) d->showstr_vector[d->showstr_page + 1];
    diff -= (int) d->showstr_vector[d->showstr_page];
    if (diff >= MAX_STRING_LENGTH)
      diff = MAX_STRING_LENGTH - 1;
    strncpy(buffer, d->showstr_vector[d->showstr_page], diff);
    buffer[diff] = '\0';
    send_to_char(buffer, d->character);
    d->showstr_page++;
  }
}