1stMUD/corefiles/
1stMUD/gods/
1stMUD/notes/
1stMUD/player/
1stMUD/win32/
1stMUD/win32/ROM/
/**************************************************************************
*  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
*  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
*                                                                         *
*  Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael         *
*  Chastain, Michael Quan, and Mitchell Tse.                              *
*                                                                         *
*  In order to use any part of this Merc Diku Mud, you must comply with   *
*  both the original Diku license in 'license.doc' as well the Merc       *
*  license in 'license.txt'.  In particular, you may not remove either of *
*  these copyright notices.                                               *
*                                                                         *
*  Much time and thought has gone into this software and you are          *
*  benefiting.  We hope that you share your changes too.  What goes       *
*  around, comes around.                                                  *
***************************************************************************
*       ROM 2.4 is copyright 1993-1998 Russ Taylor                        *
*       ROM has been brought to you by the ROM consortium                 *
*           Russ Taylor (rtaylor@hypercube.org)                           *
*           Gabrielle Taylor (gtaylor@hypercube.org)                      *
*           Brian Moore (zump@rom.org)                                    *
*       By using this code, you have agreed to follow the terms of the    *
*       ROM license, in the file Rom24/doc/rom.license                    *
***************************************************************************
*       1stMUD ROM Derivative (c) 2001-2002 by Ryan Jennings              *
*            http://1stmud.dlmud.com/  <r-jenn@shaw.ca>                   *
***************************************************************************/
/***************************************************************************
 *  File: string.c                                                         *
 *                                                                         *
 *  Much time and thought has gone into this software and you are          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 *                                                                         *
 *  This code was freely distributed with the The Isles 1.1 source code,   *
 *  and has been used here for OLC - OLC would not be what it is without   *
 *  all the previous coders who released their source code.                *
 *                                                                         *
 ***************************************************************************/

#include <sys/types.h>
#include <stdarg.h>
#include <ctype.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "merc.h"
#include "tables.h"
#include "olc.h"
#if defined(WIN32)
#include "../win32/winstuff.h"
#endif

const char *string_linedel(const char *, int);
const char *string_lineadd(const char *, const char *, int);
const char *numlineas(const char *);

/*****************************************************************************
 Name:		string_edit
 Purpose:	Clears string and puts player into editing mode.
 Called by:	none
 ****************************************************************************/
void string_edit(CHAR_DATA * ch, const char **pString)
{
	chprintln(ch, "-========- Entering EDIT Mode -=========-");
	chprintlnf(ch, "    Type %ch on a new line for help", STR_EDIT_KEY(ch));
	chprintlnf(ch, " Terminate with a %cq or @ on a blank line.",
			   STR_EDIT_KEY(ch));
	chprintln(ch, "-=======================================-");

	if (*pString == NULL)
	{
		*pString = str_dup("");
	}
	else
	{
		*(char *) *pString = '\0';
	}

	ch->desc->pString = pString;

	return;
}

/*****************************************************************************
 Name:		string_append
 Purpose:	Puts player into append mode for given string.
 Called by:	(many)olc_act.c
 ****************************************************************************/
void string_append(CHAR_DATA * ch, const char **pString)
{
	chprintln(ch, "-=======- Entering APPEND Mode -========-");
	chprintlnf(ch, "    Type %ch on a new line for help", STR_EDIT_KEY(ch));
	chprintlnf(ch, " Terminate with a %cq or @ on a blank line.",
			   STR_EDIT_KEY(ch));
	chprintln(ch, "-=======================================-");

	if (*pString == NULL)
	{
		*pString = str_dup("");
	}
	chprint(ch, numlineas(*pString));

/* numlineas entrega el string con \n\r */
/*  if ( *(*pString + strlen( *pString ) - 1) != '\r' )
	chprint( "\n\r", ch ); */

	ch->desc->pString = pString;

	return;
}

/*****************************************************************************
 Name:		string_replace
 Purpose:	Substitutes one string for another.
 Called by:	string_add(string.c) (aedit_builder)olc_act.c.
 ****************************************************************************/
const char *string_replace(const char *orig, char *old, char *pnew)
{
	char xbuf[MAX_STRING_LENGTH];
	int i;

	xbuf[0] = '\0';
	strcpy(xbuf, orig);
	if (strstr(orig, old) != NULL)
	{
		i = strlen(orig) - strlen(strstr(orig, old));
		xbuf[i] = '\0';
		strcat(xbuf, pnew);
		strcat(xbuf, &orig[i + strlen(old)]);
		free_string(orig);
	}

	return str_dup(xbuf);
}

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

const char *del_last_line(const char *string)
{
	int len;
	bool found = FALSE;

	char xbuf[MSL];

	xbuf[0] = '\0';
	if (IS_NULLSTR(string))
		return (str_dup(xbuf));

	strcpy(xbuf, string);

	for (len = strlen(xbuf); len > 0; len--)
	{
		if (xbuf[len] == '\r')
		{
			if (!found)
			{

				if (len > 0)
					len--;
				found = TRUE;
			}
			else
			{

				xbuf[len + 1] = '\0';
				free_string(string);
				return (str_dup(xbuf));
			}
		}
	}
	xbuf[0] = '\0';
	free_string(string);
	return (str_dup(xbuf));
}

const char *string_replace_all(const char *orig, char *old, char *newstr)
{
	if (!strstr(orig, old) || !str_cmp(old, newstr))
	{
		return orig;
	}

	while (strstr(orig, old))
	{
		orig = string_replace(orig, old, newstr);
	}
	return orig;
}

void parse_action(const char **text, int command, const char *string,
				  CHAR_DATA * ch)
{
	char arg1[MAX_INPUT_LENGTH], arg2[MAX_INPUT_LENGTH];

	switch (command)
	{
	case PARSE_HELP:
		chprintln(ch,
				  "-------------------------------------------------------");
		chprintlnf(ch,
				   "Edit help (commands on blank line):\n\r"
				   "%ch               - get help (this info)\n\r"
				   "%cs               - show string so far\n\r"
				   "%cS               - show string so far without line numbers\n\r"
				   "%cf               - formats text\n\r"
				   "%cc               - clear string so far\n\r"
				   "%cd#              - delete line number <num>\n\r"
				   "%cd               - delete last line\n\r"
				   "%ci# <str>        - insert <str> on line <num>\n\r"
				   "%ce# <str>        - replace line <num> with <str>\n\r"
				   "%cr 'a' 'b'       - replace first occurance of text\n\r"
				   "%cR 'a' 'b'       - replace all occurances of text\n\r"
				   "                   usage: %cr 'pattern' 'replacement'\n\r"
				   "%c| <command>     - execute a mud command\n\r"
				   "%cq               - end string", STR_EDIT_KEY(ch),
				   STR_EDIT_KEY(ch), STR_EDIT_KEY(ch), STR_EDIT_KEY(ch),
				   STR_EDIT_KEY(ch), STR_EDIT_KEY(ch), STR_EDIT_KEY(ch),
				   STR_EDIT_KEY(ch), STR_EDIT_KEY(ch), STR_EDIT_KEY(ch),
				   STR_EDIT_KEY(ch), STR_EDIT_KEY(ch), STR_EDIT_KEY(ch),
				   STR_EDIT_KEY(ch));
		chprintln(ch, "------------------------------------------------------");
		break;
	case PARSE_FORMAT:
		*text = format_string(*text);
		chprintln(ch, "String formatted.");
		break;
	case PARSE_EDIT:
		string = one_argument(string, arg1);
		if (arg1[0] == '\0')
		{
			chprintln(ch, "You must specify a line number.");
			return;
		}
		*text = string_linedel(*text, atoi(arg1));
		*text = string_lineadd(*text, string, atoi(arg1));
		chprintln(ch, "Line replaced.");
		break;
	case PARSE_DELETE:
		if (string[0] == '\0')
			*text = del_last_line(*text);
		else
			*text = string_linedel(*text, atoi(string));
		chprintln(ch, "Line deleted.");
		break;
	case PARSE_REPLACE:
		string = first_arg(string, arg1, FALSE);
		string = first_arg(string, arg2, FALSE);
		if (arg1[0] == '\0')
		{
			chprintln(ch, "Usage: .r 'old string' 'new string'");
			return;
		}
		*text = string_replace(*text, arg1, arg2);
		chprintlnf(ch, "'%s' replaced with '%s'.", arg1, arg2);
		break;
	case PARSE_REPLACE_ALL:
		string = first_arg(string, arg1, FALSE);
		string = first_arg(string, arg2, FALSE);
		if (arg1[0] == '\0')
		{
			chprintln(ch, "Usage: .R 'old string' 'new string'");
			return;
		}
		*text = string_replace_all(*text, arg1, arg2);
		chprintlnf(ch, "All occurances of '%s' replaced with '%s'.",
				   arg1, arg2);
		break;
	case PARSE_INSERT:
		string = first_arg(string, arg1, FALSE);
		*text = string_lineadd(*text, string, atoi(arg1));
		chprintln(ch, "Line inserted.");
		break;
	case PARSE_LIST_NORM:
		chprintln(ch, "String so far:");
		page_to_char(*text, ch);
		break;
	case PARSE_LIST_NUM:
		chprintln(ch, "String so far:");
		page_to_char(numlineas(*text), ch);
		break;
	default:
		chprintln(ch, "Invalid command.");
		bug("invalid command passed", 0);
		break;
	}
}

int parse_string_command(const char **text, const char *str, CHAR_DATA * ch)
{
	int i = 2, j = 0;
	char actions[MAX_INPUT_LENGTH];

	if ((*str == STR_EDIT_KEY(ch)))
	{
		while (str[i] != '\0')
		{
			actions[j] = str[i];
			i++;
			j++;
		}
		actions[j] = '\0';
		*(char *) str = '\0';
		switch (str[1])
		{
		case 'q':
			*(char *) str = '\0';
			return STRING_END;
		case '|':
			interpret(ch, actions);
			chprintln(ch, "Command performed.");
			return STRING_FOUND;
		case 'c':
			chprintln(ch, "String cleared.");
			replace_string(*text, "");
			return STRING_FOUND;
		case 's':
			parse_action(text, PARSE_LIST_NUM, actions, ch);
			return STRING_FOUND;
		case 'S':
			parse_action(text, PARSE_LIST_NORM, actions, ch);
			return STRING_FOUND;
		case 'r':
			parse_action(text, PARSE_REPLACE, actions, ch);
			return STRING_FOUND;
		case 'R':
			parse_action(text, PARSE_REPLACE_ALL, actions, ch);
			return STRING_FOUND;
		case 'f':
			parse_action(text, PARSE_FORMAT, actions, ch);
			return STRING_FOUND;
		case 'd':
			parse_action(text, PARSE_DELETE, actions, ch);
			return STRING_FOUND;
		case 'i':
			parse_action(text, PARSE_INSERT, actions, ch);
			return STRING_FOUND;
		case 'e':
			parse_action(text, PARSE_EDIT, actions, ch);
			return STRING_FOUND;
		case 'h':
			parse_action(text, PARSE_HELP, actions, ch);
			return STRING_FOUND;
		default:
			chprintln(ch, "Invalid command.");
			return STRING_FOUND;
		}
	}
	else if (*str == '@')
	{
		*(char *) str = '\0';
		return STRING_END;
	}
	return STRING_NONE;
}

void string_add(CHAR_DATA * ch, char *argument)
{
	char buf[MSL];
	int action = 0;

	action = parse_string_command(ch->desc->pString, argument, ch);

	switch (action)
	{
	case STRING_END:
		if (ch->desc->editor == ED_MPCODE)
		{
			MOB_INDEX_DATA *mob;
			int hash;
			PROG_LIST *mpl;
			PROG_CODE *mpc;

			EDIT_MPCODE(ch, mpc);

			if (mpc != NULL)
				for (hash = 0; hash < MAX_KEY_HASH; hash++)
					for (mob = mob_index_hash[hash]; mob; mob = mob->next)
						for (mpl = mob->first_mprog; mpl; mpl = mpl->next)
							if (mpl->vnum == mpc->vnum)
							{
								chprintlnf(ch, "Fixing mob %ld.", mob->vnum);
								replace_string(mpl->code, mpc->code);
							}
		}
		if (ch->desc->editor == ED_OPCODE)	/* for the objprogs */
		{
			OBJ_INDEX_DATA *obj;
			int hash;
			PROG_LIST *opl;
			PROG_CODE *opc;

			EDIT_OPCODE(ch, opc);

			if (opc != NULL)
				for (hash = 0; hash < MAX_KEY_HASH; hash++)
					for (obj = obj_index_hash[hash]; obj; obj = obj->next)
						for (opl = obj->first_oprog; opl; opl = opl->next)
							if (opl->vnum == opc->vnum)
							{
								chprintlnf(ch, "Fixing object %ld.", obj->vnum);
								replace_string(opl->code, opc->code);
							}
		}

		if (ch->desc->editor == ED_RPCODE)	/* for the roomprogs */
		{
			ROOM_INDEX_DATA *room;
			int hash;
			PROG_LIST *rpl;
			PROG_CODE *rpc;

			EDIT_RPCODE(ch, rpc);

			if (rpc != NULL)
				for (hash = 0; hash < MAX_KEY_HASH; hash++)
					for (room = room_index_hash[hash]; room; room = room->next)
						for (rpl = room->first_rprog; rpl; rpl = rpl->next)
							if (rpl->vnum == rpc->vnum)
							{
								chprintlnf(ch, "Fixing room %ld.", room->vnum);
								replace_string(rpl->code, rpc->code);
							}
		}
		ch->desc->pString = NULL;
		chprintln(ch, "Done editing.");
		return;
	case STRING_FOUND:
		return;
	default:
	case STRING_NONE:
		strcpy(buf, *ch->desc->pString);

		/*
		 * Truncate strings to MAX_STRING_LENGTH.
		 * --------------------------------------
		 */
		if (strlen(buf) + strlen(argument) >= ((MSL * 2) - 6))
		{
			chprintln(ch, "String too long, last line skipped.");
			/* Force character out of editing mode. */
			ch->desc->pString = NULL;
			return;
		}

		/*
		 * Ensure no tilde's inside string.
		 * --------------------------------
		 */
		smash_tilde(argument);
		strcat(buf, argument);
		strcat(buf, "\n\r");
		free_string(*ch->desc->pString);
		*ch->desc->pString = str_dup(buf);
		return;
	}
}

/*
 * Thanks to Kalgen for the new procedure (no more bug!)
 * Original wordwrap() written by Surreality.
 */
/*****************************************************************************
 Name:		format_string
 Purpose:	Special string formating and word-wrapping.
 Called by:	string_add(string.c) (many)olc_act.c
 ****************************************************************************/
const char *format_string(const char *oldstring /*, bool fSpace */ )
{
	char xbuf[MAX_STRING_LENGTH];
	char xbuf2[MAX_STRING_LENGTH];
	const char *rdesc;
	int i = 0;
	bool cap = TRUE;

	xbuf[0] = xbuf2[0] = 0;

	i = 0;

	for (rdesc = oldstring; *rdesc; rdesc++)
	{
		if (*rdesc == '\n')
		{
			if (xbuf[i - 1] != ' ')
			{
				xbuf[i] = ' ';
				i++;
			}
		}
		else if (*rdesc == '\r');
		else if (*rdesc == ' ')
		{
			if (xbuf[i - 1] != ' ')
			{
				xbuf[i] = ' ';
				i++;
			}
		}
		else if (*rdesc == ')')
		{
			if (xbuf[i - 1] == ' ' && xbuf[i - 2] == ' ' &&
				(xbuf[i - 3] == '.' || xbuf[i - 3] == '?' ||
				 xbuf[i - 3] == '!'))
			{
				xbuf[i - 2] = *rdesc;
				xbuf[i - 1] = ' ';
				xbuf[i] = ' ';
				i++;
			}
			else
			{
				xbuf[i] = *rdesc;
				i++;
			}
		}
		else if (*rdesc == '.' || *rdesc == '?' || *rdesc == '!')
		{
			if (xbuf[i - 1] == ' ' && xbuf[i - 2] == ' ' &&
				(xbuf[i - 3] == '.' || xbuf[i - 3] == '?' ||
				 xbuf[i - 3] == '!'))
			{
				xbuf[i - 2] = *rdesc;
				if (*(rdesc + 1) != '\"')
				{
					xbuf[i - 1] = ' ';
					xbuf[i] = ' ';
					i++;
				}
				else
				{
					xbuf[i - 1] = '\"';
					xbuf[i] = ' ';
					xbuf[i + 1] = ' ';
					i += 2;
					rdesc++;
				}
			}
			else
			{
				xbuf[i] = *rdesc;
				if (*(rdesc + 1) != '\"')
				{
					xbuf[i + 1] = ' ';
					xbuf[i + 2] = ' ';
					i += 3;
				}
				else
				{
					xbuf[i + 1] = '\"';
					xbuf[i + 2] = ' ';
					xbuf[i + 3] = ' ';
					i += 4;
					rdesc++;
				}
			}
			cap = TRUE;
		}
		else
		{
			xbuf[i] = *rdesc;
			if (cap)
			{
				cap = FALSE;
				xbuf[i] = UPPER(xbuf[i]);
			}
			i++;
		}
	}
	xbuf[i] = 0;
	strcpy(xbuf2, xbuf);

	rdesc = xbuf2;

	xbuf[0] = 0;

	for (;;)
	{
		for (i = 0; i < 77; i++)
		{
			if (!*(rdesc + i))
				break;
		}
		if (i < 77)
		{
			break;
		}
		for (i = (xbuf[0] ? 76 : 73); i; i--)
		{
			if (*(rdesc + i) == ' ')
				break;
		}
		if (i)
		{
			*(char *) (rdesc + i) = 0;
			strcat(xbuf, rdesc);
			strcat(xbuf, "\n\r");
			rdesc += i + 1;
			while (*rdesc == ' ')
				rdesc++;
		}
		else
		{
			bug("No spaces", 0);
			*(char *) (rdesc + 75) = 0;
			strcat(xbuf, rdesc);
			strcat(xbuf, "-\n\r");
			rdesc += 76;
		}
	}
	while (*(rdesc + i) &&
		   (*(rdesc + i) == ' ' || *(rdesc + i) == '\n' ||
			*(rdesc + i) == '\r'))
		i--;
	*(char *) (rdesc + i + 1) = 0;
	strcat(xbuf, rdesc);
	if (xbuf[strlen(xbuf) - 2] != '\n')
		strcat(xbuf, "\n\r");

	free_string(oldstring);
	return (str_dup(xbuf));
}

/*
 * Used above in string_add.  Because this function does not
 * modify case if fCase is FALSE and because it understands
 * parenthesis, it would probably make a nice replacement
 * for one_argument.
 */
/*****************************************************************************
 Name:		first_arg
 Purpose:	Pick off one argument from a string and return the rest.
 		Understands quates, parenthesis (barring ) ('s) and
 		percentages.
 Called by:	string_add(string.c)
 ****************************************************************************/
const char *first_arg(const char *argument, char *arg_first, bool fCase)
{
	char cEnd;

	while (*argument == ' ')
		argument++;

	cEnd = ' ';
	if (*argument == '\'' || *argument == '"' || *argument == '%' ||
		*argument == '(')
	{
		if (*argument == '(')
		{
			cEnd = ')';
			argument++;
		}
		else
			cEnd = *argument++;
	}

	while (*argument != '\0')
	{
		if (*argument == cEnd)
		{
			argument++;
			break;
		}
		if (fCase)
			*arg_first = LOWER(*argument);
		else
			*arg_first = *argument;
		arg_first++;
		argument++;
	}
	*arg_first = '\0';

	while (*argument == ' ')
		argument++;

	return argument;
}

/*
 * Used in olc_act.c for aedit_builders.
 */
const char *string_unpad(const char *argument)
{
	char buf[MAX_STRING_LENGTH];
	char *s;

	s = (char *) argument;

	while (*s == ' ')
		s++;

	strcpy(buf, s);
	s = buf;

	if (*s != '\0')
	{
		while (*s != '\0')
			s++;
		s--;

		while (*s == ' ')
			s--;
		s++;
		*s = '\0';
	}

	free_string(argument);
	return str_dup(buf);
}

/*
 * Same as capitalize but changes the pointer's data.
 * Used in olc_act.c in aedit_builder.
 */
const char *string_proper(const char *argument)
{
	char *s;

	s = (char *) argument;

	while (*s != '\0')
	{
		if (*s != ' ')
		{
			*s = UPPER(*s);
			while (*s != ' ' && *s != '\0')
				s++;
		}
		else
		{
			s++;
		}
	}

	return argument;
}

const char *string_linedel(const char *string, int line)
{
	const char *strtmp = string;
	char buf[MAX_STRING_LENGTH];
	int cnt = 1, tmp = 0;

	buf[0] = '\0';

	for (; *strtmp != '\0'; strtmp++)
	{
		if (cnt != line)
			buf[tmp++] = *strtmp;

		if (*strtmp == '\n')
		{
			if (*(strtmp + 1) == '\r')
			{
				if (cnt != line)
					buf[tmp++] = *(++strtmp);
				else
					++strtmp;
			}

			cnt++;
		}
	}

	buf[tmp] = '\0';

	free_string(string);
	return str_dup(buf);
}

const char *string_lineadd(const char *string, const char *newstr, int line)
{
	const char *strtmp = string;
	int cnt = 1, tmp = 0;
	bool done = FALSE;
	char buf[MAX_STRING_LENGTH];

	buf[0] = '\0';

	for (; *strtmp != '\0' || (!done && cnt == line); strtmp++)
	{
		if (cnt == line && !done)
		{
			strcat(buf, newstr);
			strcat(buf, "\n\r");
			tmp += strlen(newstr) + 2;
			cnt++;
			done = TRUE;
		}

		buf[tmp++] = *strtmp;

		if (done && *strtmp == '\0')
			break;

		if (*strtmp == '\n')
		{
			if (*(strtmp + 1) == '\r')
				buf[tmp++] = *(++strtmp);

			cnt++;
		}

		buf[tmp] = '\0';
	}

	free_string(string);
	return str_dup(buf);
}

/* buf queda con la linea sin \n\r */
const char *getline(const char *str, char *buf);

const char *getline(const char *str, char *buf)
{
	int tmp = 0;
	bool found = FALSE;

	while (*str)
	{
		if (*str == '\n')
		{
			found = TRUE;
			break;
		}

		buf[tmp++] = *(str++);
	}

	if (found)
	{
		if (*(str + 1) == '\r')
			str += 2;
		else
			str += 1;
	}							/* para que quedemos en el inicio de la prox linea */

	buf[tmp] = '\0';

	return str;
}

const char *numlineas(const char *string)
{
	int cnt = 1;
	static char buf[MAX_STRING_LENGTH * 2];
	char buf2[MAX_STRING_LENGTH], tmpb[MAX_STRING_LENGTH];

	buf[0] = '\0';

	while (*string)
	{
		string = getline(string, tmpb);
		sprintf(buf2, "%2d. %s\n\r", cnt++, tmpb);
		strcat(buf, buf2);
	}

	return buf;
}

bool is_ansi_printed_char(char c)
{
	switch (c)
	{
	case ' ':
	case '-':
	case ANSI_KEY:
		return TRUE;
	default:
		return FALSE;
	}
}

/* Converts a ~ into a colour code for ~ */
const char *tilde_to_color(const char *str)
{
	int i = 0;
	char buf[MSL];
	static char buf_new[5][MSL];
	static int p;
	char *result = buf_new[p];

	// rotate buffers
	++p;
	p %= 5;

	memset(buf, 0, MSL);

	if (IS_NULLSTR(str))
		return "";

	while (*str != '\0')
	{
		if (*str == '~')
		{
			buf[i] = ANSI_KEY;
			i++;
			buf[i] = '-';
			i++;
			str++;
		}
		else
		{
			buf[i] = *str;
			i++;
			str++;
		}
	}
	strcpy(result, buf);
	return (result);
}

const char *smash_colour(const char *str)
{
	int i = 0;
	char buf[MSL];
	static char buf_new[5][MSL];
	static int p;
	char *result = buf_new[p];

	// rotate buffers
	++p;
	p %= 5;

	memset(buf, 0, MSL);

	if (IS_NULLSTR(str))
		return "";

	while (*str != '\0')
	{
		if (*str == ANSI_KEY)
		{
			str++;
			if (*str == '-')
			{
				buf[i] = C_TILDE;
				i++;
			}
			str++;
		}
		else
		{
			buf[i] = *str;
			i++;
			str++;
		}
	}
	strcpy(result, buf);
	return (result);
}

const char *color_to_tilde(const char *str)
{
	int i = 0;
	char buf[MSL];
	static char buf_new[5][MSL];
	static int p;
	char *result = buf_new[p];

	// rotate buffers
	++p;
	p %= 5;

	memset(buf, 0, MSL);

	if (IS_NULLSTR(str))
		return "";

	while (*str != '\0')
	{
		if (*str == ANSI_KEY)
		{
			if (*(str + 1) == '-')
			{
				buf[i] = '~';
				i++;
				str++;
				str++;
			}
			else
			{
				buf[i] = *str;
				i++;
				str++;
			}
		}
		else
		{
			buf[i] = *str;
			i++;
			str++;
		}
	}
	strcpy(result, buf);
	return (result);
}

const char *stringf(int length, int align, const char *fill, const char *string)
{
	const char *count_string;
	char temp;
	int count = 0, nCount = 0;
	int pos = 0;
	char *result;
	static char buf_new[5][MSL];
	static int i;

	// rotate buffers
	++i;
	i %= 5;

	result = buf_new[i];

	count_string = string;

	if (!fill)
		fill = " ";

	if (length <= 0)
	{
		length = 79;
	}

	while (*count_string && nCount != length)
	{
		temp = *count_string++;

		if (temp == ANSI_KEY)
		{
			temp = *count_string++;
			if (is_ansi_printed_char(temp))
				nCount++;
			continue;
		}
		nCount++;
	}

	if (align == ALIGN_RIGHT)
	{
		count = (length - ++nCount);
		while (nCount++ <= length)
		{
			result[pos++] = *fill;
		}
	}

	if (align == ALIGN_CENTER)
	{
		nCount = (length - nCount) / 2;
		count = nCount;
		while (nCount-- > 0)
		{
			result[pos++] = *fill;
		}
	}

	while (*string && count != length)
	{
		temp = *string++;
		result[pos++] = temp;

		if (temp == ANSI_KEY)
		{
			temp = result[pos] = *string++;

			if (is_ansi_printed_char(temp))
				count++;

			pos++;
			continue;
		}
		count++;
	}

	while (count++ < length)
		result[pos++] = *fill;

	result[pos] = '\0';
	return (result);
}

unsigned int strlen_color(const char *string)
{
	unsigned int count = 0;

	if (IS_NULLSTR(string))
		return count;

	while (*string != '\0')
	{
		if (*string != ANSI_KEY)
		{
			count++;
			string++;
			continue;
		}
		else if (*string == ANSI_KEY && is_ansi_printed_char(*(string + 1)))
		{
			string++;
			string++;
			count++;
			continue;
		}
		else if (*string == ANSI_KEY && *(string + 1) != '\0')
		{
			string++;
			string++;
			continue;
		}
		else if (*string == ANSI_KEY && *(string + 1) == '\0')
		{
			break;
		}

		else
		{
			count++;
			string++;
			continue;
		}
	}

	return count;
}

/* returns TRUE if the last char in a string i a colour code */
bool check_cstring(char *string)
{
	unsigned int i;

	if (string[0] == '\0')
	{
		return FALSE;
	}

	for (i = 0; i < strlen(string); i++)
	{

		if (string[i] == '\0')
		{
			return FALSE;
		}

		else if (string[i] != ANSI_KEY)
		{
			continue;
		}

		else if (string[i] == ANSI_KEY && string[i + 1] != '\0')
		{
			i++;
			continue;
		}

		else if (string[i] == ANSI_KEY && string[i + 1] == '\0')
		{
			return TRUE;
		}

		else
		{
			continue;
		}
	}

	return FALSE;
}

/* Can use colour coded fills like eg. draw_line("{w-{W+{R*{W+", 0); */

const char *draw_line(char *fill, int len)
{
	static char buf_new[5][MSL];
	static int i;
	int mod;
	int l;
	char lbuf[MSL];
	int count;
	unsigned int m;
	char *result = buf_new[i];

	// rotate buffers
	++i;
	i %= 5;

	result[0] = '\0';

	if (!fill)
		fill = "-";

	if (len <= 0)
	{
		len = 79;
	}
	mod = len % strlen_color(fill);
	len /= strlen_color(fill);

	for (l = 0; l < len; l++)
		strcat(result, fill);

	count = 0;
	lbuf[0] = '\0';

	for (m = 0; m < strlen(fill); m++)
	{
		if (fill[m] == ANSI_KEY)
		{
			lbuf[m] = fill[m];
			m++;
			lbuf[m] = fill[m];
			if (is_ansi_printed_char(lbuf[m]))
				count++;
			if (count >= mod)
				break;
			continue;
		}
		else
		{
			lbuf[m] = fill[m];
			count++;
			if (count >= mod)
				break;
		}
	}
	if (lbuf[m - 1] == ANSI_KEY)
	{
		lbuf[m] = 'x';
	}
	else
	{
		lbuf[m] = ANSI_KEY;
		lbuf[++m] = 'x';
	}
	lbuf[++m] = '\0';

	strcat(result, lbuf);
	return result;
}

CH_CMD(do_strkey)
{
	if (IS_NPC(ch))
		return;

	if (IS_NULLSTR(argument) || strlen(argument) > 1)
	{
		chprintln(ch, "Syntax: strkey <key>");
		chprintln(ch, "Where <key> can be any 1 letter you want ");
		chprintln(ch, "to use for string editor commands.");
		return;
	}

	if (!isascii(argument[0]) || argument[0] == ' ' || argument[0] == '\\')
	{
		chprintln(ch, "Invalid string editor key.");
		return;
	}

	ch->pcdata->str_ed_key = argument[0];
	chprintlnf(ch, "The string editor now uses %c for commands.",
			   STR_EDIT_KEY(ch));
}

char *FORMATF(const char *formatbuf, ...)
{
	static int i;
	static char buf[10][MSL * 3];
	va_list args;

	++i;
	i %= 10;

	va_start(args, formatbuf);
	vsnprintf(buf[i], MSL * 3, formatbuf, args);
	va_end(args);

	return buf[i];
}