SmaugWizard/Backup/
SmaugWizard/Backup/L/
SmaugWizard/Boards/
SmaugWizard/Building/
SmaugWizard/Corpses/
SmaugWizard/Councils/
SmaugWizard/Deity/
SmaugWizard/Gods/
SmaugWizard/MudProgs/
SmaugWizard/Player/L/
SmaugWizard/Src/
SmaugWizard/Src/res/
ssss/****************************************************************************
 * [S]imulated [M]edieval [A]dventure multi[U]ser [G]ame      |				*
 * -----------------------------------------------------------|   \\._.//	*
 * SmaugWiz (C) 1998 by Russ Pillsbury (Windows NT version)   |   (0...0)	*
 * -----------------------------------------------------------|    ).:.(	*
 * SMAUG (C) 1994, 1995, 1996 by Derek Snider                 |    {o o}	*
 * -----------------------------------------------------------|   / ' ' \	*
 * SMAUG code team: Thoric, Altrag, Blodkai, Narn, Haus,      |~'~.VxvxV.~'~*
 * Scryn, Swordbearer, Rennard, Tricops, and Gorog.           |				*
 * ------------------------------------------------------------------------ *
 * Merc 2.1 Diku Mud improvments copyright (C) 1992, 1993 by Michael        *
 * Chastain, Michael Quan, and Mitchell Tse.                                *
 * Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer,          *
 * Michael Seifert, Hans Henrik Staerfeldt, Tom Madsen, and Katja Nyboe.    *
 ****************************************************************************/

#include	"stdafx.h"
#include	"smaug.h"
#include	"Smaugx.h"
#include	"SysData.h"
#include	"objects.h"
#include	"rooms.h"
#include	"boards.h"
#include	"SmaugWizDoc.h"
#include	"SmaugFiles.h"
#include	"descriptor.h"
#include	"character.h"

/* Defines for voting on notes. -- Narn */
#define VOTE_NONE 0
#define VOTE_OPEN 1
#define VOTE_CLOSED 2

BOOL	is_note_to (CCharacter *ch, CNoteData *pnote);
void	note_attach (CCharacter *ch);
void  	do_note (CCharacter *ch, char *arg_passed, BOOL IS_MAIL);
CNoteData *read_note (const char *notefile, FILE *fp);



CBoardData::~CBoardData ()
{
    delete note_file;
    delete read_group;
    delete post_group;
    delete extra_readers;
    delete extra_removers;

	while (! m_NList.IsEmpty ()) {
		delete (CNoteData*) m_NList.RemoveTail ();
	}
	m_NList.RemoveAll ();
}


void CBoardList::RemoveAll ()
{
	while (!IsEmpty ()) {
		delete (CBoardData*) RemoveTail ();
	}
	CPtrList::RemoveAll ();
}


BOOL can_remove (CCharacter *ch, CBoardData *board)
{
  /* If your trust is high enough, you can remove it. */
  if (ch->GetTrustLevel () >= board->min_remove_level)
    return TRUE;
  
  if (board->extra_removers[0] != '\0')
  {
    if (is_name (ch->GetName (), board->extra_removers))
      return TRUE;
  }
  return FALSE;
}
 
BOOL can_read (CCharacter *ch, CBoardData *board)
{
  /* If your trust is high enough, you can read it. */
  if (ch->GetTrustLevel () >= board->min_read_level)
    return TRUE;

  /* Your trust wasn't high enough, so check if a read_group or extra
     readers have been set up. */
  if (board->read_group[0] != '\0')
  {
    if (ch->GetPcData ()->GetClan ()
		&& !str_cmp (ch->GetPcData ()->GetClan ()->GetName (), board->read_group)) 
			return TRUE; 
    if (ch->GetPcData ()->council && !str_cmp (ch->GetPcData ()->council->name, board->read_group))
      return TRUE; 
  }
  if (board->extra_readers[0] != '\0')
  {
    if (is_name (ch->GetName (), board->extra_readers))
      return TRUE;
  } 
  return FALSE;
}

BOOL can_post (CCharacter *ch, CBoardData *board)
{
  /* If your trust is high enough, you can post. */
  if (ch->GetTrustLevel () >= board->min_post_level)
    return TRUE;

  /* Your trust wasn't high enough, so check if a post_group has been set up. */
  if (board->post_group[0] != '\0')
  {
    if (ch->GetPcData ()->GetClan ()
		&& !str_cmp (ch->GetPcData ()->GetClan ()->GetName (), board->post_group)) 
			return TRUE; 
    if (ch->GetPcData ()->council && !str_cmp (ch->GetPcData ()->council->name, board->post_group))
      return TRUE; 
  }
  return FALSE;
}


// board commands.
void CBoardList::WriteBoardsFile ()
{
	FILE	*fp;

	fp = fopen (FileTable.GetName (SM_BOARD_FILE), "w");
	if (! fp) {
		bug ("FATAL: cannot open board.txt for writing!\n\r");
		return;
	}	  

	POSITION	pos = GetHeadPosition ();
	while (pos) {
		CBoardData	&Bd = *(CBoardData*) GetNext (pos);
		fprintf (fp, "Filename          %s~\n", Bd.note_file);
		fprintf (fp, "Vnum              %d\n",  Bd.board_obj);
		fprintf (fp, "Min_read_level    %d\n",  Bd.min_read_level);
		fprintf (fp, "Min_post_level    %d\n",  Bd.min_post_level);
		fprintf (fp, "Min_remove_level  %d\n",  Bd.min_remove_level);
		fprintf (fp, "Max_posts         %d\n",  Bd.max_posts);
		fprintf (fp, "Type 	            %d\n",  Bd.type); 
		fprintf (fp, "Read_group        %s~\n", Bd.read_group);
		fprintf (fp, "Post_group        %s~\n", Bd.post_group);
		fprintf (fp, "Extra_readers     %s~\n", Bd.extra_readers);
		fprintf (fp, "Extra_removers    %s~\n", Bd.extra_removers);

		fprintf (fp, "End\n");
	}
	fclose (fp);
}


CBoardData *CBoardList::GetBoard (CObjData *obj)
{
	CBoardData	*board;

	POSITION	pos = GetHeadPosition ();
	while (pos) {
		board = (CBoardData*) GetNext (pos);
		if (board->board_obj == obj->pIndexData->vnum)
			return board;
	}
	return NULL;	
}


CBoardData *find_board (CCharacter *ch)
{
	CObjData	*obj;
	CBoardData	*board;

	POSITION	pos = ch->GetInRoom ()->GetHeadContentPos ();
	while (obj = ch->GetInRoom ()->GetNextContent (pos)) {
		if ((board = BoardList.GetBoard (obj)) != NULL)
			return board;
	}

	return NULL;
}


BOOL is_note_to (CCharacter *ch, CNoteData *pnote)
{
    if (!str_cmp (ch->GetName (), pnote->sender))
	return TRUE;

    if (is_name ("all", pnote->to_list))
	return TRUE;

    if (ch->IsHero () && is_name ("immortal", pnote->to_list))
	return TRUE;

    if (is_name (ch->GetName (), pnote->to_list))
	return TRUE;

    return FALSE;
}



void note_attach (CCharacter *ch)
{
    CNoteData *pnote;

    if (ch->GetNotes ())
	return;

    pnote = new CNoteData;
    pnote->SetNext (NULL);
    pnote->SetPrev (NULL);
    pnote->sender	= QUICKLINK (ch->GetName ());
    pnote->date		= STRALLOC ("");
    pnote->to_list	= STRALLOC ("");
    pnote->subject	= STRALLOC ("");
    pnote->text		= STRALLOC ("");
    ch->SetNotes (pnote);
}


void CBoardData::WriteAllNotes ()
{
	FILE	*fp;

	// Rewrite entire note list.
	fclose (fpReserve);
	CString nfn = FileTable.MakeName (SD_BOARD_DIR, note_file);
	if ((fp = fopen (nfn, "w")) == NULL) {
		perror (nfn);
	} else {
		POSITION	pos = m_NList.GetHeadPosition ();
		while (pos) {
			CNoteData	&Nd = *(CNoteData*) m_NList.GetNext (pos);
			fprintf (fp, "Sender  %s~\nDate    %s~\nTo      %s~\nSubject "
				"%s~\nVoting %d\nYesvotes %s~\nNovotes %s~\nAbstentions "
				"%s~\nText\n%s~\n\n",
				Nd.sender, Nd.date, Nd.to_list, Nd.subject, Nd.voting,
				Nd.yesvotes, Nd.novotes, Nd.abstentions,
				strip_cr (Nd.text));
		}
		fclose (fp);
	}
	fpReserve = fopen (FileTable.GetName (SM_NULL_FILE), "r");
}


void CBoardData::RemoveNote (CNoteData *pnote)
{
	ASSERT (this);

	POSITION	pos = m_NList.Find (pnote);
	if (! pos) {
		bug ("CBoardData::RemoveNote: pnote not in list");
		return;
	}

	m_NList.RemoveAt (pos);			// Remove note from linked list.

    // for now STRFREE's are not done in destructors...
	STRFREE (pnote->text);
    STRFREE (pnote->subject);
    STRFREE (pnote->to_list);
    STRFREE (pnote->date);
    STRFREE (pnote->sender);
	delete pnote;

	--num_posts;
	WriteAllNotes ();				// update the note file
}


CObjData *find_quill (CCharacter *ch)
{
	CObjData	*quill;
	POSITION	pos = ch->GetHeadCarryPos ();

	while (quill = ch->GetNextCarrying (pos))
		if (quill->item_type == ITEM_PEN && can_see_obj (ch, *quill))
			return quill;

	return NULL;
}


void do_noteroom (CCharacter *ch, char *argument)
{
    CBoardData *board;
    char arg[MAX_STRING_LENGTH];
    char arg_passed[MAX_STRING_LENGTH];

    strcpy (arg_passed, argument);

    switch (ch->GetSubstate ())
    {
	case SUB_WRITING_NOTE:
	do_note (ch, arg_passed, FALSE);
 	break;

	default:

    argument = one_argument (argument, arg);  
    smash_tilde (argument);
    if (!str_cmp (arg, "write") || !str_cmp (arg, "to") 
    ||  !str_cmp (arg, "subject") || !str_cmp (arg, "show"))        
    {
        do_note (ch, arg_passed, FALSE);
        return;  
    }
 	    
    board = find_board (ch);
    if (!board)
    {
        ch->SendText ("There is no bulletin board here to look at.\n\r");
        return;
    }

    if (board->type != BOARD_NOTE)
    {
      ch->SendText ("You can only use note commands on a note board.\n\r");
      return;
    }
    else
    {
      do_note (ch, arg_passed, FALSE);
      return;
    }
  }
}

void do_mailroom (CCharacter *ch, char *argument)
{
    CBoardData *board;
    char arg[MAX_STRING_LENGTH];
    char arg_passed[MAX_STRING_LENGTH];

    strcpy (arg_passed, argument);

    switch (ch->GetSubstate ())
    {
	case SUB_WRITING_NOTE:
	do_note (ch, arg_passed, TRUE);
 	break;

	default:

    argument = one_argument (argument, arg);
    smash_tilde (argument);
    if (!str_cmp (arg, "write") || !str_cmp (arg, "to") 
    ||  !str_cmp (arg, "subject") || !str_cmp (arg, "show"))        
    {
        do_note (ch, arg_passed, TRUE);
	return;  
    }

    board = find_board (ch);
    if (!board)
    {
        ch->SendText ("There is no mail facility here.\n\r");
        return;
    }

    if (board->type != BOARD_MAIL)
    {
	ch->SendText ("You can only use mail commands in a post office.\n\r");
	return;
    }
    else
    {
	do_note (ch, arg_passed, TRUE);
	return;
    }
  }
}


void do_note (CCharacter *ch, char *arg_passed, BOOL IS_MAIL)
{
	char		buf [MAX_STRING_LENGTH];
	char		arg [MAX_INPUT_LENGTH];
	CNoteData	*pnote;
	CBoardData	*board;
	int			vnum;
	int			anum;
	int			first_list;
	CObjData	*quill, *paper, *tmpobj = NULL;
	char		notebuf[MAX_STRING_LENGTH];  
	char		short_desc_buf[MAX_STRING_LENGTH];
	char		long_desc_buf[MAX_STRING_LENGTH];
	char		keyword_buf[MAX_STRING_LENGTH];
	BOOL		mfound = FALSE;
	CExtraDescrData	*ed = NULL;

	if (ch->IsNpc ())
		return;

	if (!ch->GetDesc ()) {
		bug ("do_note: no descriptor", 0);
		return;
	}

	switch (ch->GetSubstate ()) {
	  default:
		break;
	  case SUB_WRITING_NOTE:
		if ((paper = get_eq_char (ch, WEAR_HOLD)) == NULL
		  || paper->item_type != ITEM_PAPER) {
			bug ("do_note: player not holding paper");
			ch->StopEditing ();
			return;
		}
		ed = (CExtraDescrData*) ch->dest_buf;
		STRFREE (ed->description);
		ed->description = ch->GetEditBuffer ();
		ch->StopEditing ();
		return;
		break;
	}

	set_char_color (AT_NOTE, ch);
	arg_passed = one_argument (arg_passed, arg);
	smash_tilde (arg_passed);

	if (!str_cmp (arg, "list")) {
		board = find_board (ch);
		if (!board) {
			ch->SendText ("There is no board here to look at.\n\r");
			return;
		}
		if (! can_read (ch, board)) {
			ch->SendText ("You cannot make any sense of the cryptic "
				"scrawl on this board...\n\r");
			return;
		}

		first_list = atoi (arg_passed);
		if (first_list) {
			if (IS_MAIL) {
				ch->SendText ("You cannot use a list number (at this time) "
					"with mail.\n\r");
				return;
			}

			if (first_list < 1) {
				ch->SendText ("You can't read a note before 1!\n\r");
				return;
			}
		}


		if (!IS_MAIL) {
			int nNote = 0;
			set_pager_color (AT_NOTE, ch);
			POSITION	pos = board->GetFirstNotePosition ();
			while (pos) {
				CNoteData	&Nd = *board->GetNextNote (pos);
				++nNote;
				if ((first_list && nNote >= first_list) || !first_list)
					pager_printf (ch, "%2d%c %-12s%c %-12s %s\n\r", nNote,
						is_note_to (ch, &Nd) ? ')' : '}',
						Nd.sender, (Nd.voting != VOTE_NONE)
						? (Nd.voting == VOTE_OPEN ? 'V' : 'C') : ':',
						Nd.to_list, Nd.subject);
			}
			act (AT_ACTION, "$n glances over the notes.", ch, NULL, NULL,
				TO_ROOM);
			return;
		} else {
			if (IS_MAIL) {		// SB Mail check for Brit
				POSITION	pos = board->GetFirstNotePosition ();
				while (pos) {
					pnote = board->GetNextNote (pos);
					if (is_note_to (ch, pnote)) {
						mfound = TRUE;
						break;
					}
				}

				if (!mfound && ch->GetTrustLevel() < SysData.ReadAllMailLev) {
					ch->SendTextf ("You have no mail.\n\r");
					return;
				}
			}

			POSITION	pos = board->GetFirstNotePosition ();
			int			nNote = 0;
			while (pos) {
				pnote = board->GetNextNote (pos);
				if (is_note_to (ch, pnote)
				  || ch->GetTrustLevel () > SysData.ReadAllMailLev)
					ch->SendTextf ("%2d%c %s: %s\n\r", ++nNote,
						is_note_to (ch, pnote) ? '-' : '}',
						pnote->sender, pnote->subject);
			}
			return;
		}
	}

	if (!str_cmp (arg, "read")) {
		BOOL fAll;

		board = find_board (ch);
		if (!board) {
			ch->SendText ("There is no board here to look at.\n\r");
			return;
		}
		if (!can_read (ch, board)) {
			ch->SendText ("You cannot make any sense of the cryptic scrawl "
				"on this board...\n\r");
			return;
		}

		if (!str_cmp (arg_passed, "all")) {
			fAll = TRUE;
			anum = 0;
		}
		else if (is_number (arg_passed)) {
			fAll = FALSE;
			anum = atoi (arg_passed);
		} else {
			ch->SendText ("Note read which number?\n\r");
			return;
		}

		set_pager_color (AT_NOTE, ch);
		if (!IS_MAIL) {
			POSITION	pos = board->GetFirstNotePosition ();
			int			nNote = 0;
			while (pos) {
				pnote = board->GetNextNote (pos);
				++nNote;
				if (nNote == anum || fAll) {
					pager_printf (ch, "[%3d] %s: %s\n\r%s\n\rTo: %s\n\r%s",
						nNote, pnote->sender, pnote->subject, pnote->date,
						pnote->to_list, pnote->text);

					if (pnote->yesvotes[0] != '\0'
					  || pnote->novotes[0] != '\0'
					  || pnote->abstentions[0] != '\0') {
						send_to_pager ("---------------------------------"
							"---------------------------\n\r", ch);
						pager_printf (ch, "Votes:\n\rYes:     %s\n\rNo:   "
							"   %s\n\rAbstain: %s\n\r",
							pnote->yesvotes, pnote->novotes,
							pnote->abstentions);
					}
					act (AT_ACTION, "$n reads a note.", ch, NULL, NULL,
						TO_ROOM);
					return;
				}
			}
			ch->SendText ("No such note.\n\r");
			return;
		} else {
			vnum = 0;
			POSITION	pos = board->GetFirstNotePosition ();
			int			nNote = 0;
			while (pos) {
				pnote = board->GetNextNote (pos);
				if (is_note_to (ch, pnote)
				  || ch->GetTrustLevel () > SysData.ReadAllMailLev) {
					++nNote;
					if (nNote == anum || fAll) {
						if (ch->GetGold () < 10
						  && ch->GetTrustLevel () < SysData.ReadMailFreeLev) {
							ch->SendText ("It costs 10 gold coins to read a "
								"message.\n\r");
							return;
						}
						if (ch->GetTrustLevel () < SysData.ReadMailFreeLev)
							ch->AddGold (-10);
						pager_printf (ch,
							"[%3d] %s: %s\n\r%s\n\rTo: %s\n\r%s", nNote,
							pnote->sender, pnote->subject, pnote->date,
							pnote->to_list, pnote->text);
						return;
					}     
				}
			}
			ch->SendText ("No such message.\n\r");
			return;
		}
	}

	// Voting added by Narn, June '96
	if (!str_cmp (arg, "vote")) {
		char arg2 [MAX_INPUT_LENGTH];
		arg_passed = one_argument (arg_passed, arg2); 

		board = find_board (ch);
		if (!board) {
			ch->SendText ("There is no bulletin board here.\n\r");
			return;
		}
		if (!can_read (ch, board)) {
			ch->SendText ("You cannot vote on this board.\n\r");
			return;
		}

		if (is_number (arg2))
			anum = atoi (arg2);
		else {
			ch->SendText ("Note vote which number?\n\r");
			return;
		}

		pnote = board->GetNoteByNumber (anum-1);
		if (!pnote) {
			ch->SendText ("No such note.\n\r");
			return;
		}

		// Options: open close yes no abstain
		// If you're the author of the note and can read the board you can
		// open and close voting.
		// If you can read it and voting is open you can vote.
		if (!str_cmp (arg_passed, "open")) {
			if (str_cmp (ch->GetName (), pnote->sender)) {
				ch->SendText ("You are not the author of this note.\n\r");
				return;
			}
			pnote->voting = VOTE_OPEN;
			act (AT_ACTION, "$n opens voting on a note.", ch, NULL,
				NULL, TO_ROOM);
			ch->SendText ("Voting opened.\n\r");
			board->WriteAllNotes ();
			return;
		}  
		if (!str_cmp (arg_passed, "close")) {
			if (str_cmp (ch->GetName (), pnote->sender)) {
				ch->SendText ("You are not the author of this note.\n\r");
				return;
			}
			pnote->voting = VOTE_CLOSED;
			act (AT_ACTION, "$n closes voting on a note.", ch, NULL,
				NULL, TO_ROOM);
			ch->SendText ("Voting closed.\n\r");
			board->WriteAllNotes ();
			return;
		}  

		// Make sure the note is open for voting before going on.
		if (pnote->voting != VOTE_OPEN) {
			ch->SendText ("Voting is not open on this note.\n\r");
			return;
		}

		// Can only vote once on a note.
		sprintf (buf, "%s %s %s", 
			pnote->yesvotes, pnote->novotes, pnote->abstentions);

		if (is_name (ch->GetName (), buf)) {
			ch->SendText ("You have already voted on this note.\n\r");
			return;
		}

		if (!str_cmp (arg_passed, "yes")) {
			sprintf (buf, "%s %s", pnote->yesvotes, ch->GetName ());
			delete pnote->yesvotes;
			pnote->yesvotes = str_dup (buf);
			act (AT_ACTION, "$n votes on a note.", ch, NULL, NULL, TO_ROOM);
			ch->SendText ("Ok.\n\r");
			board->WriteAllNotes ();
			return;
		}  

		if (!str_cmp (arg_passed, "no")) {
			sprintf (buf, "%s %s", pnote->novotes, ch->GetName ());
			delete pnote->novotes;
			pnote->novotes = str_dup (buf);
			act (AT_ACTION, "$n votes on a note.", ch, NULL, NULL, TO_ROOM);
			ch->SendText ("Ok.\n\r");
			board->WriteAllNotes ();
			return;
		}  

		if (!str_cmp (arg_passed, "abstain")) {
			sprintf (buf, "%s %s", pnote->abstentions, ch->GetName ());
			delete pnote->abstentions;
			pnote->abstentions = str_dup (buf);
			act (AT_ACTION, "$n votes on a note.", ch, NULL, NULL, TO_ROOM);
			ch->SendText ("Ok.\n\r");
			board->WriteAllNotes ();
			return;
		}  
		do_note (ch, "", FALSE);
	}

	if (!str_cmp (arg, "write")) {
		if (ch->GetSubstate () == SUB_RESTRICTED)
		{
			ch->SendText ("You cannot write a note from within another command.\n\r");
			return;
		}
		if (ch->GetTrustLevel () < SysData.WriteMailFreeLev)
		{
			quill = find_quill (ch);
				if (!quill)
			{
			ch->SendText ("You need a quill to write a note.\n\r");
			return;
			}
			if (quill->value[0] < 1)
			{
			ch->SendText ("Your quill is dry.\n\r");
			return;
			}
		}
		if ((paper = get_eq_char (ch, WEAR_HOLD)) == NULL
		||     paper->item_type != ITEM_PAPER)
		{
			if (ch->GetTrustLevel () < SysData.WriteMailFreeLev)
			{
			ch->SendText ("You need to be holding a fresh piece of parchment to write a note.\n\r");
			return;
			}
			paper = create_object (OIdxTable.GetObj (OBJ_VNUM_NOTE), 0);
			if ((tmpobj = get_eq_char (ch, WEAR_HOLD)) != NULL)
			  unequip_char (ch, tmpobj); 
			paper = obj_to_char (paper, ch);
			equip_char (ch, paper, WEAR_HOLD);
			act (AT_MAGIC, "A piece of parchment magically appears in $n's hands!",
      				ch, NULL, NULL, TO_ROOM);
  			act (AT_MAGIC, "A piece of parchment appears in your hands.",
    				ch, NULL, NULL, TO_CHAR);
		}
		if (paper->value[0] < 2)
		{
			paper->value[0] = 1;
			ed = SetOExtra (paper, "_text_");
			ch->SetSubstate (SUB_WRITING_NOTE);
			ch->dest_buf = ed;
			if (ch->GetTrustLevel () < SysData.WriteMailFreeLev)
			--quill->value[0];
			start_editing (ch, ed->description);
			return;
		}
		else
		{
			ch->SendText ("You cannot modify this note.\n\r");
			return;
		}
	}

	if (!str_cmp (arg, "subject")) {
		if (ch->GetTrustLevel () < SysData.WriteMailFreeLev)
		{
			quill = find_quill (ch);
			if (!quill)
			{
			ch->SendText ("You need a quill to write a note.\n\r");
			return;
			}
			if (quill->value[0] < 1)
			{
			ch->SendText ("Your quill is dry.\n\r");
			return;
			}
		}
		if (!arg_passed || arg_passed[0] == '\0')
		{
			ch->SendText ("What do you wish the subject to be?\n\r");
			return;
		}
		if ((paper = get_eq_char (ch, WEAR_HOLD)) == NULL
		||     paper->item_type != ITEM_PAPER)
		{
			if (ch->GetTrustLevel () < SysData.WriteMailFreeLev)
			{
			ch->SendText ("You need to be holding a fresh piece of parchment to write a note.\n\r");
			return;
			}
			paper = create_object (OIdxTable.GetObj (OBJ_VNUM_NOTE), 0);
    			if ((tmpobj = get_eq_char (ch, WEAR_HOLD)) != NULL)
			unequip_char (ch, tmpobj); 
			paper = obj_to_char (paper, ch);
			equip_char (ch, paper, WEAR_HOLD);
			act (AT_MAGIC, "A piece of parchment magically appears in $n's hands!",
			ch, NULL, NULL, TO_ROOM);
			act (AT_MAGIC, "A piece of parchment appears in your hands.",
			ch, NULL, NULL, TO_CHAR);
		}
		if (paper->value[1] > 1)
			{
			ch->SendText ("You cannot modify this note.\n\r");
			return;
			}
			else
		{
			paper->value[1] = 1;
			ed = SetOExtra (paper, "_subject_");
			STRFREE (ed->description);
			ed->description = STRALLOC (arg_passed);
			ch->SendText ("Ok.\n\r");
			return;
		}
	}

	if (!str_cmp (arg, "to")) {
		struct	stat fst;
		char	fname [1024];
		if (ch->GetTrustLevel () < SysData.WriteMailFreeLev) {
			quill = find_quill (ch);
			if (!quill) {
				ch->SendText ("You need a quill to write a note.\n\r");
				return;
			}
			if (quill->value[0] < 1) {
				ch->SendText ("Your quill is dry.\n\r");
				return;
			}
		}
		if (!arg_passed || arg_passed[0] == '\0') {
			ch->SendText ("Please specify an addressee.\n\r");
			return;
		}
		if ((paper = get_eq_char (ch, WEAR_HOLD)) == NULL
		  || paper->item_type != ITEM_PAPER) {
			if (ch->GetTrustLevel () < SysData.WriteMailFreeLev) {
				ch->SendText ("You need to be holding a fresh piece of "
					"parchment to write a note.\n\r");
				return;
			}
			paper = create_object (OIdxTable.GetObj (OBJ_VNUM_NOTE), 0);
    		if ((tmpobj = get_eq_char (ch, WEAR_HOLD)) != NULL)
				unequip_char (ch, tmpobj);
			paper = obj_to_char (paper, ch);
			equip_char (ch, paper, WEAR_HOLD);
			act (AT_MAGIC, "A piece of parchment magically appears in $n's hands!",
			  ch, NULL, NULL, TO_ROOM);
			act (AT_MAGIC, "A piece of parchment appears in your hands.",
      			  ch, NULL, NULL, TO_CHAR);
		}

		if (paper->value[2] > 1) {
			ch->SendText ("You cannot modify this note.\n\r");
			return;
		}

		arg_passed [0] = UPPER (arg_passed [0]);

		strcpy (fname, FileTable.PlayerName (capitalize (arg_passed)));

		if (!IS_MAIL || stat (fname, &fst) != -1
		  || !str_cmp (arg_passed, "all")) {                                       
			paper->value [2] = 1;
			ed = SetOExtra (paper, "_to_");
			STRFREE (ed->description);
			ed->description = STRALLOC (arg_passed);
			ch->SendText ("Ok.\n\r");
			return;
		} else {
			ch->SendText ("No player exists by that name.\n\r");
			return;
		}
	}

	if (!str_cmp (arg, "show")) {
		char	*subject, *to_list, *text;

		if ((paper = get_eq_char (ch, WEAR_HOLD)) == NULL
		  || paper->item_type != ITEM_PAPER) {
			ch->SendText ("You are not holding a note.\n\r");
			return;
		}

		if ((subject = GetExtraDescr ("_subject_", paper->ExDesList)) == NULL)
			subject = "(no subject)";
		if ((to_list = GetExtraDescr ("_to_", paper->ExDesList)) == NULL)
			to_list = "(nobody)";
		sprintf (buf, "%s: %s\n\rTo: %s\n\r", ch->GetName (), subject,
			to_list);
		ch->SendText (buf);
		if ((text = GetExtraDescr ("_text_", paper->ExDesList)) == NULL)
			text = "The note is blank.\n\r";
		ch->SendText (text);
		return;
	}

	if (!str_cmp (arg, "post")) {
		if ((paper = get_eq_char (ch, WEAR_HOLD)) == NULL
		  || paper->item_type != ITEM_PAPER) {
			ch->SendText ("You are not holding a note.\n\r");
			return;
		}

		if (paper->value[0] == 0) {
			ch->SendText ("There is nothing written on this note.\n\r");
			return;
		}

		if (paper->value[1] == 0) {
			ch->SendText ("This note has no subject.\n\r");
			return;
		}

		if (paper->value[2] == 0) {
			ch->SendText ("This note is addressed to no one!\n\r");
			return;
		}

		board = find_board (ch);
		if (!board) {
			ch->SendText ("There is no bulletin board here to post "
				"your note on.\n\r");
			return;
		}
		if (!can_post (ch, board)) {
			ch->SendText ("A magical force prevents you from posting "
				"your note here...\n\r");
			return;
		}

		if (board->num_posts >= board->max_posts) {
			ch->SendText ("There is no room on this board to post your note.\n\r");
			return;
		}

		act (AT_ACTION, "$n posts a note.", ch, NULL, NULL, TO_ROOM);

		pnote = new CNoteData;
		pnote->date = STRALLOC (CurrentTime.GetString ());

		char	*text = GetExtraDescr ("_text_", paper->ExDesList);
		pnote->text = text ? STRALLOC (text) : STRALLOC ("");
		text = GetExtraDescr ("_to_", paper->ExDesList);
		pnote->to_list = text ? STRALLOC (text) : STRALLOC ("all");
		text = GetExtraDescr ("_subject_", paper->ExDesList);
		pnote->subject = text ? STRALLOC (text) : STRALLOC ("");
		pnote->sender  = QUICKLINK (ch->GetName ());

		pnote->voting      = 0;
		pnote->yesvotes    = str_dup ("");
		pnote->novotes     = str_dup ("");
		pnote->abstentions = str_dup ("");

		board->AddNote (pnote);
		board->WriteAllNotes ();
		ch->SendText ("You post your note on the board.\n\r");
		extract_obj (paper);
		return;
	}

	if (!str_cmp (arg, "remove")
	  || !str_cmp (arg, "take") || !str_cmp (arg, "copy")) {
		char take;

		board = find_board (ch);
		if (!board) {
			ch->SendText ("There is no board here to take a note from!\n\r");
			return;
		}
		if (!str_cmp (arg, "take"))
			take = 1;
		else if (!str_cmp (arg, "copy")) {
			if (ch->IsMortal ()) {
				ch->SendText ("Huh?  Type 'help note' for usage.\n\r");
				return;
			}
			take = 2;
		}
		else take = 0;

		if (!is_number (arg_passed)) {
			ch->SendText ("Note remove which number?\n\r");
			return;
		}

		if (!can_read (ch, board)) {
			ch->SendText ("You can't make any sense of what's posted here, "
				"let alone remove anything!\n\r");
			return;
		}

		anum = atoi (arg_passed);
		POSITION	pos = board->GetFirstNotePosition ();
		int			nNote = 0;
		while (pos) {
			pnote = board->GetNextNote (pos);
			if (IS_MAIL && ((is_note_to (ch, pnote)) 
			  || ch->GetTrustLevel () >= SysData.TakeOthersMailLev))
				++nNote;
			else if (!IS_MAIL) then ++nNote;

			if ((is_note_to (ch, pnote)
			  || can_remove (ch, board)) && (nNote == anum)) {
				if ((is_name ("all", pnote->to_list))
				  && (ch->GetTrustLevel () < SysData.TakeOthersMailLev)
				  && (take == 1)) {
					ch->SendText ("Notes addressed to 'all' can not be taken.\n\r");
					return;
				}

 				if (take != 0) {
					if (ch->GetGold () < 50
					  && ch->GetTrustLevel () < SysData.ReadMailFreeLev) {
						if (take == 1)
						  ch->SendText ("It costs 50 coins to take your mail.\n\r");
						else
						  ch->SendText ("It costs 50 coins to copy your mail.\n\r");
						return;
					}
					if (ch->GetTrustLevel () < SysData.ReadMailFreeLev)
						ch->AddGold (-50);
					paper = create_object (OIdxTable.GetObj (OBJ_VNUM_NOTE), 0);
					ed = SetOExtra (paper, "_sender_");
					STRFREE (ed->description);
					ed->description = QUICKLINK (pnote->sender);
					ed = SetOExtra (paper, "_text_");
					STRFREE (ed->description);
					ed->description = QUICKLINK (pnote->text);
					ed = SetOExtra (paper, "_to_");
					STRFREE (ed->description);
					ed->description = QUICKLINK (pnote->to_list);
					ed = SetOExtra (paper, "_subject_");
					STRFREE (ed->description);
					ed->description = QUICKLINK (pnote->subject);
					ed = SetOExtra (paper, "_date_");
					STRFREE (ed->description);
					ed->description = QUICKLINK (pnote->date);
					ed = SetOExtra (paper, "note");
					STRFREE (ed->description);
					sprintf (notebuf, "From: ");
					strcat (notebuf, pnote->sender);		 
					strcat (notebuf, "\r\nTo: ");
					strcat (notebuf, pnote->to_list);
					strcat (notebuf, "\r\nSubject: ");
					strcat (notebuf, pnote->subject);
					strcat (notebuf, "\r\n\r\n");
					strcat (notebuf, strip_cr (pnote->text));
					strcat (notebuf, "\n\r");
					ed->description = STRALLOC (notebuf);
					paper->value[0] = 2;
					paper->value[1] = 2;
	 				paper->value[2] = 2;
					sprintf (short_desc_buf, "a note from %s to %s",
						pnote->sender, pnote->to_list);
					paper->SetShortDescr (short_desc_buf);
					sprintf (long_desc_buf,
						"A note from %s to %s lies on the ground.",
						pnote->sender, pnote->to_list);
					paper->SetDescription (long_desc_buf);
					sprintf (keyword_buf, "note parchment paper %s", 
						pnote->to_list);
					paper->SetName (keyword_buf);
				}
				if (take != 2)
					board->RemoveNote (pnote);
				ch->SendText ("Ok.\n\r");

				if (take == 1) {
					act (AT_ACTION, "$n takes a note.", ch, NULL, NULL, TO_ROOM);
					obj_to_char (paper, ch);
				}
				else if (take == 2) {
					act (AT_ACTION, "$n copies a note.", ch, NULL, NULL, TO_ROOM);
					obj_to_char (paper, ch);
				}
				else
					act (AT_ACTION, "$n removes a note.", ch, NULL, NULL, TO_ROOM);
				return;
			}
		}

		ch->SendText ("No such note.\n\r");
		return;
	}

	ch->SendText ("Huh?  Type 'help note' for usage.\n\r");
	return;
}



void CBoardData::Read (char* pLine, FILE* fp)
{
	#ifdef KEY
		#undef KEY
	#endif
	#define KEY(literal,field,value)	\
	if (!str_cmp (word, literal)) {		\
		field = value;					\
		fMatch = TRUE;					\
		break;							\
	}

	if (str_cmp (ParseWord (pLine), "filename")) {
		bug ("CBoardData::Read: No notefile name in boards.txt");
		return;
	}
	note_file = ParseStringNohash (pLine, fp);

	char	*word;
	char	buf [MAX_STRING_LENGTH];
	BOOL	fMatch;

	for (;;) {
		fMatch = FALSE;
		if (! feof (fp)) {
			pLine = fread_line (fp);
			word = ParseWord (pLine);
		}
		else word = "End";

		switch (UPPER (word [0])) {
		  case '*':
			fMatch = TRUE;
			break;
		  case 'E':
			KEY ("Extra_readers", extra_readers, ParseStringNohash (pLine, fp));
			KEY ("Extra_removers", extra_removers, ParseStringNohash (pLine, fp));
			if (!str_cmp (word, "End")) {
				num_posts = 0;
				SetNext (NULL);
				SetPrev (NULL);
				if (!read_group)
					read_group = str_dup ("");
				if (!post_group)
					post_group = str_dup ("");
				if (!extra_readers)
					extra_readers = str_dup ("");
				if (!extra_removers)
					extra_removers = str_dup ("");
				return;
			}
		  case 'M':
			KEY ("Min_read_level", min_read_level, ParseNumber (pLine));
			KEY ("Min_post_level", min_post_level, ParseNumber (pLine));
			KEY ("Min_remove_level", min_remove_level,ParseNumber (pLine));
			KEY ("Max_posts", max_posts, ParseNumber (pLine));
		  case 'P':
			KEY ("Post_group", post_group, ParseStringNohash (pLine, fp));
		  case 'R':
			KEY ("Read_group", read_group, ParseStringNohash (pLine, fp));
		  case 'T':
			KEY ("Type", type, ParseNumber (pLine));
		  case 'V':
			KEY ("Vnum", board_obj, ParseNumber (pLine));
		}
		if (! fMatch) {
			sprintf (buf, "CBoardData::Read: no match: %s", word);
			bug (buf);
		}
	}
}


CBoardData *CBoardList::ReadBoard (FILE *fp)
{

	if (! feof (fp)) {
		char	*pLine = fread_line (fp);
		if (*pLine) {
			CBoardData	*pBoard = new CBoardData;
			pBoard->Read (pLine, fp);
			return pBoard;
		}
	}
	return NULL;
}


// Load boards file.
void CBoardList::Load ()
{
	FILE		*board_fp;
	FILE		*note_fp;
	CBoardData	*board;
	CNoteData	*pnote;

	gpDoc->LogString ("Loading boards...", LOG_BOOT);

	if (! (board_fp = fopen (FileTable.GetName (SM_BOARD_FILE), "r")))
		return;

	while ((board = ReadBoard (board_fp))) {
		AddTail (board);
		CString	nfn = FileTable.MakeName (SD_BOARD_DIR, board->note_file);
		gpDoc->LogString (nfn, LOG_BOOT);
		if ((note_fp = fopen (nfn, "r")) != NULL) {
			while ((pnote = read_note (nfn, note_fp))) {
				board->m_NList.AddTail (pnote);
				board->num_posts++;
			}
		}
	}
	fclose (board_fp);
}


void do_makeboard (CCharacter *ch, char *argument)
{
	CBoardData *board;

	if (!argument || argument[0] == '\0') {
		ch->SendText ("Usage: makeboard <filename>\n\r");
		return;
	}

	smash_tilde (argument);

	board = new CBoardData;
	board->note_file	   = str_dup (strlower (argument));
	board->read_group      = str_dup ("");
	board->post_group      = str_dup ("");
	board->extra_readers   = str_dup ("");
	board->extra_removers  = str_dup ("");

	BoardList.AddBoard (board);
}


CNoteData *read_note (const char *notefile, FILE *fp)
{
	char	*word, *pLine;

	CNoteData	*pNote = new CNoteData;

	for (;;) {
		if (feof (fp)) {
			fclose (fp);
			delete pNote;
			return NULL;
		}
		pLine = fread_line (fp);
		if (*pLine) {
			word = ParseWord (pLine);

			if (! str_cmp (word, "sender"))
				pNote->sender = ParseString (pLine, fp);

			else if (! str_cmp (word, "date"))
				pNote->date	= ParseString (pLine, fp);

			else if (! str_cmp (word, "to"))
				pNote->to_list = ParseString (pLine, fp);

			else if (! str_cmp (word, "subject"))
				pNote->subject = ParseString (pLine, fp);

			else if (! str_cmp (word, "voting"))
				pNote->voting = ParseNumber (pLine);

			else if (! str_cmp (word, "yesvotes"))
				pNote->yesvotes	= ParseStringNohash (pLine, fp);

			else if (! str_cmp (word, "novotes"))
				pNote->novotes = ParseStringNohash (pLine, fp);

			else if (! str_cmp (word, "abstentions"))
				pNote->abstentions = ParseStringNohash (pLine, fp);

			else if (! str_cmp (word, "text")) {
				pLine = fread_line (fp);
				pNote->text	= ParseString (pLine, fp);
				break;
			}
			
			else {
				bug ("read_note: Bad keyword (%s)", word);
				ThrowSmaugException (SE_NOTES);
			}
		}
	}

	if (! pNote->yesvotes)    pNote->yesvotes		= str_dup ("");
	if (! pNote->novotes)     pNote->novotes		= str_dup ("");
	if (! pNote->abstentions) pNote->abstentions	= str_dup ("");
	pNote->SetNext (NULL);
	pNote->SetPrev (NULL);
	return pNote;
}


void do_bset (CCharacter *ch, char *argument)
{
	CBoardData	*board;
	BOOL		found;
	char		arg1 [MAX_INPUT_LENGTH];
	char		arg2 [MAX_INPUT_LENGTH];
	char		buf [MAX_STRING_LENGTH];
	int			value;

	argument = one_argument (argument, arg1);
	argument = one_argument (argument, arg2);

	if (arg1[0] == '\0' || arg2[0] == '\0') {
		ch->SendText ("Usage: bset <board filename> <field> value\n\r");
		ch->SendText ("\n\rField being one of:\n\r");
		ch->SendText ("  vnum read post remove maxpost filename type\n\r");
		ch->SendText ("  read_group post_group extra_readers extra_removers\n\r");
		return;
	}

	value = atoi (argument);
	found = FALSE;
	POSITION	pos = BoardList.GetHeadPosition ();
	while (pos) {
		board = BoardList.GetNext (pos);
		if (!str_cmp (arg1, board->note_file)) {
			found = TRUE;
			break;
		}
	}
	
	if (!found) {
		ch->SendText ("Board not found.\n\r");
		return;
	}

	if (!str_cmp (arg2, "vnum")) {
		if (!OIdxTable.GetObj (value)) {
			ch->SendText ("No such object.\n\r");
			return;
		}
		board->board_obj = value;
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "read")) {
		if (value < 0 || value > MAX_LEVEL) {
			ch->SendText ("Value out of range.\n\r");
			return;
		}
		board->min_read_level = value;
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "read_group")) {
		if (!argument || argument[0] == '\0') {
			ch->SendText ("No group specified.\n\r");
			return;
		}
		delete board->read_group;
		if (!str_cmp (argument, "none"))
			board->read_group = str_dup ("");
		else
			board->read_group = str_dup (argument);

		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "post_group")) {
		if (!argument || argument[0] == '\0') {
			ch->SendText ("No group specified.\n\r");
			return;
		}
		delete board->post_group;
		if (!str_cmp (argument, "none"))
			board->post_group = str_dup ("");
		else
			board->post_group = str_dup (argument);

		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "extra_removers")) {
		if (!argument || argument[0] == '\0') {   
			ch->SendText ("No names specified.\n\r");
			return;
		}
		if (!str_cmp (argument, "none"))
			buf[0] = '\0';
		else
			sprintf (buf, "%s %s", board->extra_removers, argument);

		delete board->extra_removers;
		board->extra_removers = str_dup (buf); 
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "extra_readers")) {
		if (!argument || argument[0] == '\0') {
			ch->SendText ("No names specified.\n\r");
			return;
		}
		if (!str_cmp (argument, "none"))
			buf[0] = '\0';
		else
			sprintf (buf, "%s %s", board->extra_readers, argument);        

		delete board->extra_readers;
		board->extra_readers = str_dup (buf);
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "filename")) {
		if (!argument || argument[0] == '\0') {
			ch->SendText ("No filename specified.\n\r");
			return;
		}
		delete board->note_file;
		board->note_file = str_dup (argument);
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "post")) {
		if (value < 0 || value > MAX_LEVEL) {
			ch->SendText ("Value out of range.\n\r");
			return;
		}
		board->min_post_level = value;
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "remove")) {
		if (value < 0 || value > MAX_LEVEL) {
			ch->SendText ("Value out of range.\n\r");
			return;
		}
		board->min_remove_level = value;
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "maxpost")) {
		if (value < 1 || value > 1000) {
			ch->SendText ("Value out of range.\n\r");
			return;
		}
		board->max_posts = value;
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	if (!str_cmp (arg2, "type")) {
		if (value < 0 || value > 1) {
			ch->SendText ("Value out of range.\n\r");
			return;
		}
		board->type = value;
		BoardList.WriteBoardsFile ();
		ch->SendText ("Done.\n\r");
		return;
	}

	do_bset (ch, "");
}


void do_bstat (CCharacter *ch, char *argument)
{
	CBoardData	*board;
	BOOL		found;
	char		arg [MAX_INPUT_LENGTH];

	argument = one_argument (argument, arg);

	if (arg[0] == '\0') {
		ch->SendText ("Usage: bstat <board filename>\n\r");
		return;
	}

	found = FALSE;
	POSITION	pos = BoardList.GetHeadPosition ();
	while (pos) {
		board = BoardList.GetNext (pos);
		if (!str_cmp (arg, board->note_file)) {
			found = TRUE;
			break;
		}
	}

	if (!found) {
		ch->SendText ("Board not found.\n\r");
		return;
	}

	ch->SendTextf ("%-12s Vnum: %5d Read: %2d Post: %2d Rmv: %2d Max: %2d "
		"Posts: %d Type: %d\n\r", board->note_file, board->board_obj,
		board->min_read_level, board->min_post_level,
		board->min_remove_level, board->max_posts, 
		board->num_posts, board->type);

	ch->SendTextf (
		"Read_group: %-15s Post_group: %-15s \n\rExtra_readers: %-10s\n\r",
		board->read_group, board->post_group, board->extra_readers);
}


void do_boards (CCharacter *ch, char *argument)
{
	if (BoardList.IsEmpty ()) {
		ch->SendText ("There are no boards.\n\r");
		return;
	}

	set_char_color (AT_NOTE, ch);
	POSITION	pos = BoardList.GetHeadPosition ();
	while (pos) {
		CBoardData	&Bd = *BoardList.GetNext (pos);
		ch->SendTextf ("%-16s Vnum: %5d Read: %2d Post: %2d Rmv: %2d "
			"Max: %2d Posts: %d Type: %d\n\r", Bd.note_file,
			Bd.board_obj, Bd.min_read_level, Bd.min_post_level,
			Bd.min_remove_level, Bd.max_posts, Bd.num_posts, Bd.type);
	}
}


void mail_count (CCharacter *ch)
{
	int		Count = 0;

	POSITION	pos = BoardList.GetHeadPosition ();
	while (pos) {
		CBoardData	&Bd = *BoardList.GetNext (pos);
		if (Bd.type == BOARD_MAIL && can_read (ch, &Bd)) {
			POSITION	pos = Bd.GetFirstNotePosition ();
			while (pos) {
				if (is_note_to (ch, Bd.GetNextNote (pos)))
					++Count;
			}
		}
	}
	if (Count)
		ch->SendTextf ("You have %d mail messages waiting.\n\r", Count);
}


CNoteData::~CNoteData ()
{
	delete yesvotes;
	delete novotes;
	delete abstentions;
}