/
rogue24b3/
rogue24b3/data/
/***************************************************************************
 *  Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,        *
 *  Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe.   *
 *                                                                         *
 *  Merc Diku Mud improvments 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          *
 *  benefitting.  We hope that you share your changes too.  What goes      *
 *  around, comes around.                                                  *
 ***************************************************************************/

#include "merc.h"
#include "interp.h"

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

BOARD_DATA boards[MAX_BOARD] = {
// Short, Desc, Read, Write, Def Recip, Forced, Expire, NULL, FALSE
{ "General",	"General discussion",		0,     2,     "all", DEF_INCLUDE,21, NULL, FALSE },
{ "Ideas",	"Suggestion for improvement",	0,     2,     "all", DEF_NORMAL, 60, NULL, FALSE }, 
{ "Announce",	"Announcements from Immortals",	0,     LEVEL_IMMORTAL, "all", DEF_NORMAL, 60, NULL, FALSE },
{ "Bugs",	"Typos, bugs, errors",		0,     1,     "imm", DEF_NORMAL, 60, NULL, FALSE },
{ "Personal",	"Personal messages",		0,     1, "all", DEF_EXCLUDE,28, NULL, FALSE },
{ "Security",	"PR/Security", LEVEL_IMMORTAL, LEVEL_IMMORTAL, "imm", DEF_NORMAL, 31, NULL, FALSE }
};

const char * szFinishPrompt =
	"(`wC`n)ontinue, (`wV`n)iew, (`wP`n)ost or (`wF`n)orget it? ";

long last_note_stamp = 0; /* To generate unique timestamps on notes */

#define BOARD_NOACCESS -1
#define BOARD_NOTFOUND -1

static bool next_board (CHAR_DATA *ch);


/* recycle a note */
void free_note (NOTE_DATA *note)
{
	if (note->sender)
		free_string (note->sender);
	if (note->to_list)
		free_string (note->to_list);
	if (note->subject)
		free_string (note->subject);
	if (note->date) /* was note->datestamp for some reason */
		free_string (note->date);
	if (note->text)
		free_string (note->text);
		
	note->next = note_free;
	note_free = note;	
}

/* allocate memory for a new note or recycle */
NOTE_DATA *new_note ()
{
	NOTE_DATA *note;
	
	if (note_free)
	{
		note = note_free;
		note_free = note_free->next;
	}
	else
		alloc_mem(note, NOTE_DATA, 1);

	/* Zero all the fields - does not gurantee zeroed memory */	
	note->next = NULL;
	note->sender = NULL;		
	note->expire = 0;
	note->to_list = NULL;
	note->subject = NULL;
	note->date = NULL;
	note->date_stamp = 0;
	note->text = NULL;
	
	return note;
}

/* append this note to the given file */
static void append_note (FILE *fp, NOTE_DATA *note)
{
	fprintf (fp, "Sender  %s~\n", note->sender);
	fprintf (fp, "Date    %s~\n", note->date);
	fprintf (fp, "Stamp   %ld\n", note->date_stamp);
	fprintf (fp, "Expire  %ld\n", note->expire);
	fprintf (fp, "To      %s~\n", note->to_list);
	fprintf (fp, "Subject %s~\n", note->subject);
	fprintf (fp, "Text\n%s~\n\n", note->text);
}

/* Save a note in a given board */
void finish_note (BOARD_DATA *board, NOTE_DATA *note)
{
	FILE *fp;
	NOTE_DATA *p;
	char filename[200];
	
	/* The following is done in order to generate unique date_stamps */
	if (last_note_stamp >= current_time)
		note->date_stamp = ++last_note_stamp;
	else
	{
	    note->date_stamp = current_time;
	    last_note_stamp = current_time;
	}
	
	if (board->note_first) /* are there any notes in there now? */
	{
		for (p = board->note_first; p->next; p = p->next);
		/* empty */
		p->next = note;
	}
	else /* nope. empty list. */
		board->note_first = note;

	/* append note to note file */
	sprintf (filename, "%s%s", NOTE_DIR, board->short_name);

	fp = fopen (filename, "a");
	if (!fp)
	{
		mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: Could not open note file in append mode");
		board->changed = TRUE; /* set it to TRUE hope it will be OK later? */
		return;
	}
	
	append_note (fp, note);
	fclose (fp);
}

/* Find the number of a board */
int board_number (const BOARD_DATA *board)
{
	int i;
	
	for (i = 0; i < MAX_BOARD; i++)
		if (board == &boards[i])
			return i;

	return -1;
}

/* Find a board number based on  a string */
int board_lookup (const char *name)
{
	int i;
	
	for (i = 0; i < MAX_BOARD; i++)
		if (!str_cmp (boards[i].short_name, name))
			return i;

	return -1;
}

/* Remove list from the list. Do not free note */
static void unlink_note (BOARD_DATA *board, NOTE_DATA *note)
{
	NOTE_DATA *p;
	
	if (board->note_first == note)
		board->note_first = note->next;
	else
	{
		for (p = board->note_first; p && p->next != note; p = p->next);
		if (!p)
			mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: unlink_note: could not find note.");
		else
			p->next = note->next;
	}
}

/* Find the nth note on a board. Return NULL if ch has no access to that note */
static NOTE_DATA* find_note (CHAR_DATA *ch, BOARD_DATA *board, int num)
{
	int count = 0;
	NOTE_DATA *p;
	
	for (p = board->note_first; p ; p = p->next)
			if (++count == num)
				break;
	
	if ( (count == num) && is_note_to (ch, p))
		return p;
	else
		return NULL;
	
}

/* save a single board */
static void save_board (BOARD_DATA *board)
{
	FILE *fp;
	char filename[200];
	NOTE_DATA *note;
	
	sprintf (filename, "%s%s", NOTE_DIR, board->short_name);
	
	fp = fopen (filename, "w");
	if (!fp)
		mudlogf(BRF, LVL_CODER, TRUE, "SYSERR: Error writing to: %s", filename);
	else
	{
		for (note = board->note_first; note ; note = note->next)
			append_note (fp, note);
			
		fclose (fp);
	}
}

/* Show one not to a character */
static void show_note_to_char(CHAR_DATA *ch, NOTE_DATA *note, int num)
{
	char buf[4*MAX_STRING_LENGTH];

	/* Ugly colors ? */	
	sprintf( buf, 
		"`yTo`w:`n    %s\r\n"
		"`yDate`w:`n  %s\r\n"
		"[`w%3d`n]  %s :: %s\r\n"
		"`G===============================`n\r\n"
		"%s\r\n", note->to_list, note->date,
		num, note->sender, note->subject, note->text);
	send_to_char(buf,ch);
}

/* Save changed boards */
void save_notes ()
{
	int i;
	 
	for (i = 0; i < MAX_BOARD; i++)
		if (boards[i].changed) /* only save changed boards */
			save_board (&boards[i]);
}

/* Load a single board */
static void load_board (BOARD_DATA *board)
{
	FILE *fp, *fp_archive;
	NOTE_DATA *last_note;
	char filename[200];
	
	sprintf (filename, "%s%s", NOTE_DIR, board->short_name);
	
	fp = fopen (filename, "r");
	
	/* Silently return */
	if (!fp)
		return;		
		
    last_note = NULL;

    for ( ; ; )
    {
        NOTE_DATA *pnote;
        char letter;

        do
        {
            letter = getc( fp );
            if ( feof(fp) )
            {
                fclose( fp );
                return;
            }
        }
        while ( isspace(letter) );
        ungetc( letter, fp );

        pnote             = (NOTE_DATA *)alloc_perm(sizeof(*pnote));

        if ( str_cmp( fread_word( fp ), "sender" ) )
            break;
        pnote->sender     = fread_string( fp );

        if ( str_cmp( fread_word( fp ), "date" ) )
            break;
        pnote->date       = fread_string( fp );

        if ( str_cmp( fread_word( fp ), "stamp" ) )
            break;
        pnote->date_stamp = fread_number( fp );

        if ( str_cmp( fread_word( fp ), "expire" ) )
            break;
        pnote->expire = fread_number( fp );

        if ( str_cmp( fread_word( fp ), "to" ) )
            break;
        pnote->to_list    = fread_string( fp );

        if ( str_cmp( fread_word( fp ), "subject" ) )
            break;
        pnote->subject    = fread_string( fp );

        if ( str_cmp( fread_word( fp ), "text" ) )
            break;
        pnote->text       = fread_string( fp );
        
        pnote->next = NULL; /* jic */
        
        /* Should this note be archived right now ? */
        
        if (pnote->expire < current_time)
        {
			char archive_name[200];

			sprintf (archive_name, "%s%s.old", NOTE_DIR, board->short_name);
			fp_archive = fopen (archive_name, "a");
			if (!fp_archive)
				mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: Could not open archive boards for writing.");
			else
			{
				append_note (fp_archive, pnote);
				fclose (fp_archive); /* it might be more efficient to close this later */
			}

			free_note (pnote);
			board->changed = TRUE;
			continue;
			
        }
        

        if ( board->note_first == NULL )
            board->note_first = pnote;
        else
            last_note->next     = pnote;

        last_note         = pnote;
    }

    mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: load_notes: bad key word.");
    return;
}

/* Initialize structures. Load all boards. */
void load_boards ()
{
	int i;
	
	for (i = 0; i < MAX_BOARD; i++)
		load_board (&boards[i]);
}

/* Returns TRUE if the specified note is address to ch */
bool is_note_to (CHAR_DATA *ch, NOTE_DATA *note)
{
	if (!str_cmp (ch->name, note->sender))
		return TRUE;
	
	if (is_exact_name ("all", note->to_list))
		return TRUE;
		
	if (IS_IMMORTAL(ch) && ( 
		is_exact_name ("imm", note->to_list) ||
		is_exact_name ("imms", note->to_list) ||
		is_exact_name ("immortal", note->to_list) ||
		is_exact_name ("god", note->to_list) ||
		is_exact_name ("gods", note->to_list) ||
		is_exact_name ("immortals", note->to_list)))
		return TRUE;

	if ((get_trust(ch) == MAX_LEVEL) && (
		is_exact_name ("imp", note->to_list) ||
		is_exact_name ("imps", note->to_list) ||
		is_exact_name ("implementor", note->to_list) ||
		is_exact_name ("implementors", note->to_list)))
		return TRUE;
		
	if (is_exact_name (ch->name, note->to_list))
		return TRUE;

	/* Allow a note to e.g. 40 to send to characters level 40 and above */		
	if (is_number(note->to_list) && get_trust(ch) >= atoi(note->to_list))
		return TRUE;
		
	return FALSE;
}

/* Return the number of unread notes 'ch' has in 'board' */
/* Returns BOARD_NOACCESS if ch has no access to board */
int unread_notes(CHAR_DATA *ch, BOARD_DATA *board)
{
	NOTE_DATA *note;
	time_t last_read;
	int count = 0;
	
	if (board->read_level > get_trust(ch))
		return BOARD_NOACCESS;
		
	last_read = ch->pcdata->last_note[board_number(board)];
	
	for (note = board->note_first; note; note = note->next)
		if (is_note_to(ch, note) && ((long)last_read < (long)note->date_stamp))
			count++;
			
	return count;
}

/*
 * COMMANDS
 */

// Created to substitute do_board while entering the game - Mend
ACMD(do_checknote) {
	int i, count=0, unread=0;

	for (i = 0; i < MAX_BOARD; i++) {
		unread = unread_notes(ch, &boards[i]);
		if (unread != BOARD_NOACCESS)
			count += unread;
	}

    if (count < 1)
	send_to_char("You have no new notes on the board.\r\n",ch);
    else
	ch->Send(
		"You have %d unread note%s on the board. Type 'board'.\r\n",
		count, (count != 1) ? "s" : "");
}

/* Start writing a note */
//static void do_nwrite (CHAR_DATA *ch, char *argument)
ACMD(do_nwrite) {
	char *strtime;
	char buf[200];
	
	if (IS_NPC(ch)) /* NPC cannot post notes */
		return;
		
	if (get_trust(ch) < ch->pcdata->board->write_level)
	{
		send_to_char ("You cannot post notes on this board.\n\r",ch);
		return;
	}

	/* continue previous note, if any text was written*/ 
	if (ch->pcdata->in_progress && (!ch->pcdata->in_progress->text))
	{
		send_to_char("Note in progress cancelled because you did not manage to write any text \n\r"
		              "before losing link.\n\r\n\r",ch);
		free_note (ch->pcdata->in_progress);		              
		ch->pcdata->in_progress = NULL;
	}
	
	
	if (!ch->pcdata->in_progress)
	{
		ch->pcdata->in_progress = new_note();
		ch->pcdata->in_progress->sender = str_dup (ch->name);

		/* convert to ascii. ctime returns a string which last character is \n, so remove that */	
		strtime = ctime (&current_time);
		strtime[strlen(strtime)-1] = '\0';
	
		ch->pcdata->in_progress->date = str_dup (strtime);
	}

	act("`g$n starts writing a note.`n", ch, NULL, NULL, TO_ROOM);
	if (!IS_NPC(ch))
		SET_BIT(PLR_FLAGS(ch), PLR_WRITING);

	/* Begin writing the note ! */
	sprintf (buf, "You are now %s a new note on the `w%s`n board.\n\r"
			"If you are using tintin, type #verbose to turn off alias expansion!\n\r\n\r",
	               ch->pcdata->in_progress->text ? "continuing" : "posting",
	               ch->pcdata->board->short_name);
	send_to_char (buf,ch);
	
	sprintf (buf, "`yFrom`w:`n %s\n\r\n\r", ch->name);
	send_to_char (buf,ch);

	if (!ch->pcdata->in_progress->text) /* Are we continuing an old note or not? */
	{
		switch (ch->pcdata->board->force_type)
		{
		case DEF_NORMAL:
			sprintf (buf, "If you press Return, default recipient `w%s`n will be chosen.\n\r",
					  ch->pcdata->board->names);
			break;
		case DEF_INCLUDE:
			sprintf (buf, "The recipient list MUST include `w%s`n. If not, it will be added automatically.\n\r",
						   ch->pcdata->board->names);
			break;	
		case DEF_EXCLUDE:
			sprintf (buf, "The recipient of this note must NOT include:  `w%s`n.",
						   ch->pcdata->board->names);
	
			break;
		}			
		
		send_to_char (buf,ch);
		send_to_char ("\n\r`yTo`w:`n ",ch);
	
		ch->desc->connected = CON_NOTE_TO;
		/* nanny takes over from here */
		
	}
	else /* we are continuing, print out all the fields and the note so far*/
	{
		sprintf (buf, "`yTo`w:`n      %s\n\r"
		              "`yExpires`w:`n %s\n\r"
		              "`ySubject`w:`n %s\n\r", 
		               ch->pcdata->in_progress->to_list,
		               ctime(&ch->pcdata->in_progress->expire),
		               ch->pcdata->in_progress->subject);
		send_to_char (buf,ch);
		send_to_char ("`gYour note so far:`n\n\r",ch);
		send_to_char (ch->pcdata->in_progress->text,ch);
		
	send_to_char ("\n\r"
	"Enter text. Type `w/s`n on an empty line to end note.\n\r"
	"=====================================================\n\r",ch);
		ch->desc->connected = CON_NOTE_TEXT;
	}
	
}


/* Read next note in current group. If no more notes, go to next board */
ACMD(do_nread) {
	NOTE_DATA *p;
	int count = 0, number;
	time_t *last_note = &ch->pcdata->last_note[board_number(ch->pcdata->board)];
	
	if (!str_cmp(argument, "again"))
	{ /* read last note again */
	
	}
	else if (is_number (argument))
	{
		number = atoi(argument);
		
		for (p = ch->pcdata->board->note_first; p; p = p->next)
			if (++count == number)
				break;
		
		if (!p || !is_note_to(ch, p))
			send_to_char ("No such note.\n\r",ch);
		else
		{
			show_note_to_char (ch,p,count);
			*last_note =  UMAX (*last_note, p->date_stamp);
		}
	}
	else /* just next one */
	{
		char buf[200];
		
		count = 1;
		for (p = ch->pcdata->board->note_first; p ; p = p->next, count++)
			if ((p->date_stamp > *last_note) && is_note_to(ch,p))
			{
				show_note_to_char (ch,p,count);
				/* Advance if new note is newer than the currently newest for that char */
				*last_note =  UMAX (*last_note, p->date_stamp);
				return;
			}
		
		send_to_char ("No new notes in this board.\n\r",ch);
		
		if (next_board (ch))
			sprintf (buf, "Changed to next board, %s.\n\r", ch->pcdata->board->short_name);
		else
			sprintf (buf, "There are no more boards.\n\r");			
			
		send_to_char (buf,ch);
	}
}

/* Remove a note */
ACMD(do_nremove) {
	NOTE_DATA *p;
	
	if (!is_number(argument))
	{
		send_to_char ("Remove which note?\n\r",ch);
		return;
	}

	p = find_note(ch, ch->pcdata->board, atoi(argument));
	if (!p)
	{
		send_to_char ("No such note.\n\r",ch);
		return;
	}
	
	if (str_cmp(ch->name,p->sender) && (get_trust(ch) < MAX_LEVEL))
	{
		send_to_char ("You are not authorized to remove this note.\n\r",ch);
		return;
	}
	
	unlink_note (ch->pcdata->board,p);
	free_note (p);
	send_to_char ("Note removed!\n\r",ch);	
	save_board(ch->pcdata->board); /* save the board */
}


/* List all notes or if argument given, list N of the last notes */
/* Shows REAL note numbers! */
ACMD(do_nlist) {
	int count= 0, show = 0, num = 0, has_shown = 0;
	time_t last_note;
	NOTE_DATA *p;
	char buf[MAX_STRING_LENGTH];
	
	
	if (is_number(argument))	 /* first, count the number of notes */
	{
		show = atoi(argument);
		
		for (p = ch->pcdata->board->note_first; p; p = p->next)
			if (is_note_to(ch,p))
				count++;
	}
	
	send_to_char(	"`wNotes on this board:`n\n\r"
			"`rNum>   Author        Subject`n\n\r"
			"`G============================`n\n\r",ch);

	last_note = ch->pcdata->last_note[board_number (ch->pcdata->board)];
	
	for (p = ch->pcdata->board->note_first; p; p = p->next)
	{
		num++;
		if (is_note_to(ch,p))
		{
			has_shown++; /* note that we want to see X VISIBLE note, not just last X */
			if (!show || ((count-show) < has_shown))
			{
				sprintf (buf, "`w%3d`n> `b%c `y%-13s %s`n\n\r",
				               num, 
				               last_note < p->date_stamp ? '*' : ' ',
				               p->sender, p->subject);
				send_to_char (buf,ch);
			}
		}
				              
	}
}

/* catch up with some notes */
ACMD(do_ncatchup) {
	NOTE_DATA *p;

	/* Find last note */	
	for (p = ch->pcdata->board->note_first; p && p->next; p = p->next);
	
	if (!p)
		send_to_char ("Alas, there are no notes in that board.\n\r",ch);
	else
	{
		ch->pcdata->last_note[board_number(ch->pcdata->board)] = p->date_stamp;
		send_to_char ("All mesages skipped.\n\r",ch);
	}
}

/* Dispatch function for backwards compatibility */
ACMD(do_note) {
    char arg[MAX_INPUT_LENGTH];

    if (IS_NPC(ch))
	return;

    argument = one_argument(argument, arg);

    if ((!arg[0]) || (!str_prefix(arg, "read")))
	do_nread(ch, argument, 0);
    else if (!str_prefix(arg, "list"))
	do_nlist(ch, argument, 0);
    else if (!str_prefix(arg, "write"))
	do_nwrite(ch, argument, 0);
    else if (!str_prefix(arg, "remove"))
	do_nremove(ch, argument, 0);
    else if (!str_prefix(arg, "purge"))
	send_to_char("Obsolete.\n\r",ch);
    else if (!str_prefix(arg, "archive"))
	send_to_char("Obsolete.\n\r",ch);
    else if (!str_prefix(arg, "catchup"))
	do_ncatchup(ch, argument, 0);
    else 
	do_help(ch, "note", 0);
}

/* Show all accessible boards with their numbers of unread messages OR
   change board. New board name can be given as a number or as a name (e.g.
    board personal or board 4 */
ACMD(do_board) {
    int i, count, number;
    char buf[200];

    if (IS_NPC(ch))
	return;

    if (!argument[0]) /* show boards */
    {
	int unread;
	count = 1;

	send_to_char(	"`rNum Name         Unread Description`n\n\r"
			"`G=== ============ ====== ===========`n\n\r",ch);
	for (i = 0; i < MAX_BOARD; i++)
	{
		unread = unread_notes (ch,&boards[i]); /* how many unread notes? */
		if (unread != BOARD_NOACCESS)
		{ 
			ch->Send("`w%2d`n> `w%12s `n[%s%4d`n] `y%s`n\n\r", 
			count, boards[i].short_name, unread ? "`r" : "`g", 
			unread, boards[i].long_name);
			count++;
		} /* if has access */
	} /* for each board */
	ch->Send("\n\rYou current board is `w%s.`n\n\r",
		ch->pcdata->board->short_name);
	/* Inform of rights */		
	if (ch->pcdata->board->read_level > get_trust(ch))
		ch->Send("You cannot read nor write notes on this board.\n\r");
	else if (ch->pcdata->board->write_level > get_trust(ch))
		ch->Send("You can only read notes from this board.\n\r");
	else
		ch->Send("You can both read and write on this board.\n\r");
	return;			
    } /* if empty argument */
	
    if (ch->pcdata->in_progress)
    {
	send_to_char("Please finish your interrupted note first.\n\r",ch);
	return;
    }

    /* Change board based on its number */
    if (is_number(argument))
    {
	count = 0;
	number = atoi(argument);
	for (i = 0; i < MAX_BOARD; i++)
		if (unread_notes(ch,&boards[i]) != BOARD_NOACCESS)
			if (++count == number)
				break;
		if (count == number) /* found the board.. change to it */
		{
			ch->pcdata->board = &boards[i];
			sprintf (buf, "Current board changed to `w%s.`n %s.\n\r",boards[i].short_name,
			              (get_trust(ch) < boards[i].write_level) 
			              ? "You can only read here" 
			              : "You can both read and write here");
			send_to_char (buf,ch);
		}			
		else /* so such board */
			send_to_char ("No such board number.\n\r",ch);
			
		return;
	}

	/* Non-number given, find board with that name */
	
	for (i = 0; i < MAX_BOARD; i++) {
		if (boards[i].short_name == NULL)
			break;
		if (LOWER(argument[0]) == LOWER(boards[i].short_name[0])
		&& !str_prefix(argument, boards[i].short_name))
			break;
	}
	if (i == MAX_BOARD)
	{
		send_to_char ("No such board.\n\r",ch);
		return;
	}

	/* Does ch have access to this board? */	
	if (unread_notes(ch,&boards[i]) == BOARD_NOACCESS)
	{
		send_to_char ("No such board.\n\r",ch);
		return;
	}
	
	ch->pcdata->board = &boards[i];
	sprintf (buf, "Current board changed to `w%s.`n %s.\n\r",boards[i].short_name,
	              (get_trust(ch) < boards[i].write_level) 
	              ? "You can only read here" 
	              : "You can both read and write here");
	send_to_char (buf,ch);
}

/* Send a note to someone on the personal board */
void personal_message (const char *sender, const char *to, const char *subject, const int expire_days, const char *text)
{
	make_note ("Personal", sender, to, subject, expire_days, text);
}

void make_note (const char* board_name, const char *sender, const char *to, const char *subject, const int expire_days, const char *text)
{
	int board_index = board_lookup (board_name);
	BOARD_DATA *board;
	NOTE_DATA *note;
	char *strtime;
	
	if (board_index == BOARD_NOTFOUND)
	{
		mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: make_note: board not found.");
		return;
	}
	
	if (strlen(text) > MAX_NOTE_TEXT)
	{
		mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: make_note: text too long (%d bytes)", strlen(text));
		return;
	}
	
	
	board = &boards [board_index];
	
	note = new_note(); /* allocate new note */
	
	note->sender = str_dup (sender);
	note->to_list = str_dup(to);
	note->subject = str_dup (subject);
	note->expire = current_time + expire_days * 60 * 60 * 24;
	note->text = str_dup (text);

	/* convert to ascii. ctime returns a string which last character is \n, so remove that */	
	strtime = ctime (&current_time);
	strtime[strlen(strtime)-1] = '\0';
	
	note->date = str_dup (strtime);
	
	finish_note (board, note);
	
}

/* tries to change to the next accessible board */
static bool next_board (CHAR_DATA *ch)
{
	int i = board_number (ch->pcdata->board) + 1;
	
	while ((i < MAX_BOARD) && (unread_notes(ch,&boards[i]) == BOARD_NOACCESS))
		i++;
		
	if (i == MAX_BOARD)
		return FALSE;
	else
	{
		ch->pcdata->board = &boards[i];
		return TRUE;
	}
}

void handle_con_note_to(DESCRIPTOR_DATA *d, char * argument)
{
	char buf [MAX_INPUT_LENGTH];
	CHAR_DATA *ch = d->character;

	if (!ch->pcdata->in_progress)
	{
		d->connected = CON_PLAYING;
		mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: nanny: In CON_NOTE_TO, but no note in progress.");
		return;
	}

	strcpy (buf, argument);
	smash_tilde (buf); /* change ~ to - as we save this field as a string later */

	switch (ch->pcdata->board->force_type)
	{
		case DEF_NORMAL: /* default field */
			if (!buf[0]) /* empty string? */
			{
				ch->pcdata->in_progress->to_list = str_dup (ch->pcdata->board->names);
				sprintf (buf, "`yAssumed default recipient`w: %s`n\n\r", ch->pcdata->board->names);
				send_to_char(buf,ch);
			}
			else
				ch->pcdata->in_progress->to_list = str_dup (buf);
				
			break;
		
		case DEF_INCLUDE: /* forced default */
			if (!is_exact_name (ch->pcdata->board->names, buf))
			{
				strcat (buf, " ");
				strcat (buf, ch->pcdata->board->names);
				ch->pcdata->in_progress->to_list = str_dup(buf);

				sprintf (buf, "\n\rYou did not specify %s as recipient, so it was automatically added.\n\r"
				               "`yNew To`w:`n %s\n\r",
						 ch->pcdata->board->names, ch->pcdata->in_progress->to_list);
				send_to_char(buf,ch);
			}
			else
				ch->pcdata->in_progress->to_list = str_dup (buf);
			break;
		
		case DEF_EXCLUDE: /* forced exclude */
			if (!buf[0])
			{
				send_to_char("You must specify a recipient.\n\r"
					"`yTo`w:`n", ch);
				return;
			}
			
			if (is_exact_name (ch->pcdata->board->names, buf))
			{
				sprintf (buf, "You are not allowed to send notes to %s on this board. Try again.\n\r"
				"`yTo`w:`n ", ch->pcdata->board->names);
				send_to_char(buf,ch);
				return; /* return from nanny, not changing to the next state! */
			}
			else
				ch->pcdata->in_progress->to_list = str_dup (buf);
			break;
		
	}		

	send_to_char("\n\r`ySubject`w:`n ", ch);
	d->connected = CON_NOTE_SUBJECT;
}

void handle_con_note_subject (DESCRIPTOR_DATA *d, char * argument)
{
	char buf [MAX_INPUT_LENGTH];
	CHAR_DATA *ch = d->character;

	if (!ch->pcdata->in_progress)
	{
		d->connected = CON_PLAYING;
		mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: nanny: In CON_NOTE_SUBJECT, but no note in progress.");
		return;
	}

	strcpy(buf, argument);
	/* change ~ to - as we save this field as a string later */
	smash_tilde(buf);
	/* Do not allow empty subjects */
	if (!buf[0])		
	{
		write_to_buffer (d, "Please find a meaningful subject!\n\r",0);
		send_to_char("`ySubject`w:`n ", ch);
	}
	else  if (strlen(buf)>60)
	{
		write_to_buffer (d, "No, no. This is just the Subject. You're not writing the note yet. Twit.\n\r",0);
	}
	else
	/* advance to next stage */
	{
		ch->pcdata->in_progress->subject = str_dup(buf);
		if (IS_IMMORTAL(ch)) /* immortals get to choose number of expire days */
		{
			sprintf (buf,"\n\rHow many days do you want this note to expire in?\n\r"
			             "Press Enter for default value for this board, `w%d`n days.\n\r"
           			      "`yExpire`w:`n ",
		                 ch->pcdata->board->purge_days);
			send_to_char(buf,ch);
			d->connected = CON_NOTE_EXPIRE;
		}
		else
		{
			ch->pcdata->in_progress->expire = 
				current_time + ch->pcdata->board->purge_days * 24L * 3600L;				
			sprintf (buf, "This note will expire %s\r",ctime(&ch->pcdata->in_progress->expire));
			write_to_buffer (d,buf,0);
			send_to_char("\n\r"
			"Enter text. Type `w/s`n on an empty line to end note.\n\r"
			"=====================================================\n\r",ch);
			d->connected = CON_NOTE_TEXT;
		}
	}
}

void handle_con_note_expire(DESCRIPTOR_DATA *d, char * argument)
{
	CHAR_DATA *ch = d->character;
	char buf[MAX_STRING_LENGTH];
	time_t expire;
	int days;

	if (!ch->pcdata->in_progress)
	{
		d->connected = CON_PLAYING;
		mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: nanny: In CON_NOTE_EXPIRE, but no note in progress.");
		return;
	}
	
	/* Numeric argument. no tilde smashing */
	strcpy (buf, argument);
	if (!buf[0]) /* assume default expire */
		days = 	ch->pcdata->board->purge_days;
	else /* use this expire */
		if (!is_number(buf))
		{
			write_to_buffer (d,"Write the number of days!\n\r",0);
			send_to_char("`yExpire`w:`n  ",ch);
			return;
		}
		else
		{
			days = atoi (buf);
			if (days <= 0)
			{
				write_to_buffer (d, "This is a positive MUD. Use positive numbers only! :)\n\r",0);
				send_to_char("`yExpires`w:`n",ch);
				return;
			}
		}
			
	expire = current_time + (days*24L*3600L); /* 24 hours, 3600 seconds */

	ch->pcdata->in_progress->expire = expire;
	
	/* note that ctime returns XXX\n so we only need to add an \r */

	send_to_char("\n\r"
		"Enter text. Type `w/s`n on an empty line to end note.\n\r"
		"=====================================================\n\r",ch);

	d->connected = CON_NOTE_TEXT;
}

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

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

    switch (command) {
	case PARSE_HELP:
		send_to_char("Edit help (commands on blank line):\n\r", ch);
		send_to_char("/r 'old' 'new'   - replace a substring\n\r", ch);
		send_to_char("                   (requires '', \"\")\n\r", ch);
		send_to_char("/h               - get help (this info)\n\r", ch);
		send_to_char("/l               - show string so far\n\r", ch);
		send_to_char("/n               - show numbered string so far\n\r", ch);
		send_to_char("/f               - (word wrap) string\n\r", ch);
		send_to_char("/c               - clear string so far\n\r", ch);
		send_to_char("/d#              - delete line number <num>\n\r", ch);
		send_to_char("/i# <str>        - insert <str> on line <num>\n\r", ch);
		send_to_char("/e# <str>        - replace line <num> with <str>\n\r", ch);
		send_to_char("/s               - end string\n\r", ch);
		break;
	case PARSE_EDIT:
		string = one_argument(string, arg1);
		if (arg1[0] == '\0') {
			send_to_char("You must specify a line number.\n\r", ch);
			return;
		}
		ch->pcdata->in_progress->text = string_linedel(ch->pcdata->in_progress->text, atoi(arg1));
		ch->pcdata->in_progress->text = string_lineadd(ch->pcdata->in_progress->text, string, atoi(arg1));
		send_to_char("Line replaced.\n\r", ch);
		break;
	case PARSE_DELETE:
		ch->pcdata->in_progress->text = string_linedel(ch->pcdata->in_progress->text, atoi(string));
		send_to_char("Line deleted.\n\r", ch);
		break;
	case PARSE_REPLACE:
		string = first_arg(string, arg1, FALSE);
		string = first_arg(string, arg2, FALSE);
		if (arg1[0] == '\0') {
			ch->Send("Usage: /r 'old string' 'new string'\n\r");
			return;
		}
		ch->pcdata->in_progress->text = string_replace(ch->pcdata->in_progress->text, arg1, arg2);
		ch->Send("'%s' replaced with '%s'.\n\r", arg1, arg2);
		break;
	case PARSE_INSERT:
		string = first_arg(string, arg1, FALSE);
		ch->pcdata->in_progress->text = string_lineadd(ch->pcdata->in_progress->text, string, atoi(arg1));
		send_to_char("Line inserted.\n\r", ch);
		break;
	case PARSE_LIST_NORM:
		ch->Send("String so far:\n\r");
		ch->Send(ch->pcdata->in_progress->text);
		break;
	case PARSE_LIST_NUM:
		ch->Send("String so far:\n\r");
		ch->Send(numlineas(ch->pcdata->in_progress->text));
		break;
	default:
		ch->Send("Invalid command.\n\r");
		mudlogf(BRF, LVL_CODER, TRUE, "SYSERR: invalid command passed to parse_note_text");
		break;
    }
}

void handle_con_note_text (DESCRIPTOR_DATA *d, char *argument)
{
    bool terminate = FALSE;
    int i = 2, j = 0, action = 0;
    CHAR_DATA *ch = d->character;
    char buf[MAX_STRING_LENGTH];
    char actions[MAX_INPUT_LENGTH];

    if (!ch->pcdata->in_progress)
    {
	d->connected = CON_PLAYING;
	mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: nanny: In CON_NOTE_TEXT, buf no note in progress.");
	return;
    }

    smash_tilde(argument);

    if ((action = (*argument == '/'))) {
	while (argument[i] != '\0') {
	    actions[j] = argument[i];
	    i++; j++;
	}
	actions[j] = '\0';
	*argument = '\0';
	switch (argument[1]) {
	case 's':
		terminate = TRUE;
		*argument = '\0';
		break;
	case 'c':
		send_to_char("String cleared.\n\r", ch);
		free_string(ch->pcdata->in_progress->text);
		ch->pcdata->in_progress->text = str_dup("");
		return;
	case 'l':
		parse_note_text(PARSE_LIST_NORM, actions, ch);
		return;
	case 'n':
		parse_note_text(PARSE_LIST_NUM, actions, ch);
		return;
	case 'r':
		parse_note_text(PARSE_REPLACE, actions, ch);
		return;
	case 'f':
		ch->pcdata->in_progress->text = format_string(ch->pcdata->in_progress->text);
		send_to_char("String formatted.\n\r", ch);
		return;
	case 'd':
		parse_note_text(PARSE_DELETE, actions, ch);
		return;
	case 'i':
		parse_note_text(PARSE_INSERT, actions, ch);
		return;
	case 'e':
		parse_note_text(PARSE_EDIT, actions, ch);
		return;
	case 'h':
		parse_note_text(PARSE_HELP, actions, ch);
		return;
	default:
		send_to_char("Invalid command.\n\r", ch);
		return;
	}
    }

    if (terminate) {
	ch->Send("\n\r%s", szFinishPrompt);
	d->connected = CON_NOTE_FINISH;
	return;
    }

    if (ch->pcdata->in_progress->text) {
	strcpy(buf, ch->pcdata->in_progress->text);
	free_string(ch->pcdata->in_progress->text);
	ch->pcdata->in_progress->text = NULL;
    } else
	strcpy(buf, "");

    if ((strlen(argument) + strlen (buf)) > MAX_NOTE_TEXT) {
	ch->Send("Note too long, bailing out!\n\r");
	free_note(ch->pcdata->in_progress);
	ch->pcdata->in_progress = NULL;
	d->connected = CON_PLAYING;
	return;			
    }

    smash_tilde(argument);
    strcat(buf, argument);
    strcat(buf, "\n\r");
    ch->pcdata->in_progress->text = str_dup(buf);
}

void handle_con_note_finish (DESCRIPTOR_DATA *d, char * argument)
{
    CHAR_DATA *ch = d->character;

    if (!ch->pcdata->in_progress) {
	d->connected = CON_PLAYING;
	mudlogf(BRF, LVL_STAFF, TRUE, "SYSERR: nanny: In CON_NOTE_FINISH, but no note in progress.");
	return;
    }

    switch(tolower(argument[0])) {
	case 'c': /* keep writing */
		send_to_char("Continuing note...\n\r\n\r", ch);
		send_to_char(ch->pcdata->in_progress->text, ch);
		d->connected = CON_NOTE_TEXT;
		break;
	case 'v': /* view note so far */
		if (ch->pcdata->in_progress->text) {
			send_to_char("Text of your note so far:\n\r\n\r",ch);
			send_to_char(ch->pcdata->in_progress->text, ch);
			write_to_buffer(d, "\r\n", 0);
		} else
			write_to_buffer (d,"You haven't written a thing!\n\r\n\r",0);
		send_to_char(szFinishPrompt,ch);
		break;
	case 'p': /* post note */
		finish_note(ch->pcdata->board, ch->pcdata->in_progress);
		write_to_buffer(d, "Note posted.\n\r",0);
		if (!IS_NPC(ch))
			REMOVE_BIT(PLR_FLAGS(ch), PLR_WRITING);
		d->connected = CON_PLAYING;
		ch->pcdata->in_progress = NULL;
		act("`g$n finishes $s note.`n", ch, NULL, NULL, TO_ROOM);
		break;
	case 'f':
		write_to_buffer(d, "Note cancelled!\n\r",0);
		free_note(ch->pcdata->in_progress);
		ch->pcdata->in_progress = NULL;
		if (!IS_NPC(ch))
			REMOVE_BIT(PLR_FLAGS(ch), PLR_WRITING);
		d->connected = CON_PLAYING;
		break;
	default: /* invalid response */
		write_to_buffer(d, "Huh? Valid answers are:\n\r\n\r",0);
		send_to_char(szFinishPrompt, ch);
	}
}