deltamud/deltamud/
deltamud/deltamud/bin/
deltamud/deltamud/cnf/
deltamud/deltamud/lib/
deltamud/deltamud/lib/etc/
deltamud/deltamud/lib/misc/
deltamud/deltamud/lib/plrobjs/
deltamud/deltamud/lib/text/
deltamud/deltamud/lib/text/help/
deltamud/deltamud/lib/world/
deltamud/deltamud/lib/world/trg/
/* ************************************************************************
   *   File: mail.c                                        Part of CircleMUD *
   *  Usage: Internal funcs and player spec-procs of mud-mail system         *
   *                                                                         *
   *  All rights reserved.  See license.doc for complete information.        *
   *                                                                         *
   *  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
   *  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
   ************************************************************************ */

/******* MUD MAIL SYSTEM MAIN FILE ***************************************

Written by Jeremy Elson (jelson@cs.jhu.edu)

*************************************************************************/

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

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

void postmaster_send_mail (struct char_data *ch, struct char_data *mailman,
			   int cmd, char *arg);
void postmaster_check_mail (struct char_data *ch, struct char_data *mailman,
			    int cmd, char *arg);
void postmaster_receive_mail (struct char_data *ch, struct char_data *mailman,
			      int cmd, char *arg);

extern struct room_data *world;
extern struct index_data *mob_index;
extern int no_mail;
int find_name (char *name);

mail_index_type *mail_index = 0;	/* list of recs in the mail file  */
position_list_type *free_list = 0;	/* list of free positions in file */
long file_end_pos = 0;		/* length of file */


void 
push_free_list (long pos)
{
  position_list_type *new_pos;

  CREATE (new_pos, position_list_type, 1);
  new_pos->position = pos;
  new_pos->next = free_list;
  free_list = new_pos;
}



long 
pop_free_list (void)
{
  position_list_type *old_pos;
  long return_value;

  if ((old_pos = free_list) != 0)
    {
      return_value = free_list->position;
      free_list = old_pos->next;
      free (old_pos);
      return return_value;
    }
  else
    return file_end_pos;
}



mail_index_type *
find_char_in_index (long searchee)
{
  mail_index_type *tmp;

  if (searchee < 0)
    {
      log ("SYSERR: Mail system -- non fatal error #1.");
      return 0;
    }
  for (tmp = mail_index; (tmp && tmp->recipient != searchee); tmp = tmp->next);

  return tmp;
}



void 
write_to_file (void *buf, int size, long filepos)
{
  FILE *mail_file;

  mail_file = fopen (MAIL_FILE, "r+b");

  if (filepos % BLOCK_SIZE)
    {
      log ("SYSERR: Mail system -- fatal error #2!!!");
      no_mail = 1;
      return;
    }
  fseek (mail_file, filepos, SEEK_SET);
  fwrite (buf, size, 1, mail_file);

  /* find end of file */
  fseek (mail_file, 0L, SEEK_END);
  file_end_pos = ftell (mail_file);
  fclose (mail_file);
  return;
}


void 
read_from_file (void *buf, int size, long filepos)
{
  FILE *mail_file;

  mail_file = fopen (MAIL_FILE, "r+b");

  if (filepos % BLOCK_SIZE)
    {
      log ("SYSERR: Mail system -- fatal error #3!!!");
      no_mail = 1;
      return;
    }
  fseek (mail_file, filepos, SEEK_SET);
  fread (buf, size, 1, mail_file);
  fclose (mail_file);
  return;
}




void 
index_mail (long id_to_index, long pos)
{
  mail_index_type *new_index;
  position_list_type *new_position;

  if (id_to_index < 0)
    {
      log ("SYSERR: Mail system -- non-fatal error #4.");
      return;
    }
  if (!(new_index = find_char_in_index (id_to_index)))
    {
      /* name not already in index.. add it */
      CREATE (new_index, mail_index_type, 1);
      new_index->recipient = id_to_index;
      new_index->list_start = NULL;

      /* add to front of list */
      new_index->next = mail_index;
      mail_index = new_index;
    }
  /* now, add this position to front of position list */
  CREATE (new_position, position_list_type, 1);
  new_position->position = pos;
  new_position->next = new_index->list_start;
  new_index->list_start = new_position;
}


/* SCAN_FILE */
/* scan_file is called once during boot-up.  It scans through the mail file
   and indexes all entries currently in the mail file. */
int 
scan_file (void)
{
  FILE *mail_file;
  header_block_type next_block;
  int total_messages = 0, block_num = 0;
  char buf[100];

  if (!(mail_file = fopen (MAIL_FILE, "r")))
    {
      log ("Mail file non-existant... creating new file.");
      mail_file = fopen (MAIL_FILE, "w");
      fclose (mail_file);
      return 1;
    }
  while (fread (&next_block, sizeof (header_block_type), 1, mail_file))
    {
      if (next_block.block_type == HEADER_BLOCK)
	{
	  index_mail (next_block.header_data.to, block_num * BLOCK_SIZE);
	  total_messages++;
	}
      else if (next_block.block_type == DELETED_BLOCK)
	push_free_list (block_num * BLOCK_SIZE);
      block_num++;
    }

  file_end_pos = ftell (mail_file);
  fclose (mail_file);
  sprintf (buf, "   %ld bytes read.", file_end_pos);
  log (buf);
  if (file_end_pos % BLOCK_SIZE)
    {
      log ("SYSERR: Error booting mail system -- Mail file corrupt!");
      log ("SYSERR: Mail disabled!");
      return 0;
    }
  sprintf (buf, "   Mail file read -- %d messages.", total_messages);
  log (buf);
  return 1;
}				/* end of scan_file */


/* HAS_MAIL */
/* a simple little function which tells you if the guy has mail or not */
int 
has_mail (long recipient)
{
  if (find_char_in_index (recipient))
    return 1;
  return 0;
}



/* STORE_MAIL  */
/* call store_mail to store mail.  (hard, huh? :-) )  Pass 3 arguments:
   who the mail is to (long), who it's from (long), and a pointer to the
   actual message text (char *).
 */

void 
store_mail (long to, long from, char *message_pointer)
{
  header_block_type header;
  data_block_type data;
  long last_address, target_address;
  char *msg_txt = message_pointer;
  int bytes_written = 0;
  int total_length = strlen (message_pointer);

  assert (sizeof (header_block_type) == sizeof (data_block_type));
  assert (sizeof (header_block_type) == BLOCK_SIZE);

  if (from < 0 || to < 0 || !*message_pointer)
    {
      log ("SYSERR: Mail system -- non-fatal error #5.");
      return;
    }
  memset ((char *) &header, 0, sizeof (header));	/* clear the record */
  header.block_type = HEADER_BLOCK;
  header.header_data.next_block = LAST_BLOCK;
  header.header_data.from = from;
  header.header_data.to = to;
  header.header_data.mail_time = time (0);
  strncpy (header.txt, msg_txt, HEADER_BLOCK_DATASIZE);
  header.txt[HEADER_BLOCK_DATASIZE] = '\0';

  target_address = pop_free_list ();	/* find next free block */
  index_mail (to, target_address);	/* add it to mail index in memory */
  write_to_file (&header, BLOCK_SIZE, target_address);

  if (strlen (msg_txt) <= HEADER_BLOCK_DATASIZE)
    return;			/* that was the whole message */

  bytes_written = HEADER_BLOCK_DATASIZE;
  msg_txt += HEADER_BLOCK_DATASIZE;	/* move pointer to next bit of text */

  /*
   * find the next block address, then rewrite the header to reflect where
   * the next block is.
   */
  last_address = target_address;
  target_address = pop_free_list ();
  header.header_data.next_block = target_address;
  write_to_file (&header, BLOCK_SIZE, last_address);

  /* now write the current data block */
  memset ((char *) &data, 0, sizeof (data));	/* clear the record */
  data.block_type = LAST_BLOCK;
  strncpy (data.txt, msg_txt, DATA_BLOCK_DATASIZE);
  data.txt[DATA_BLOCK_DATASIZE] = '\0';
  write_to_file (&data, BLOCK_SIZE, target_address);
  bytes_written += strlen (data.txt);
  msg_txt += strlen (data.txt);

  /*
   * if, after 1 header block and 1 data block there is STILL part of the
   * message left to write to the file, keep writing the new data blocks and
   * rewriting the old data blocks to reflect where the next block is.  Yes,
   * this is kind of a hack, but if the block size is big enough it won't
   * matter anyway.  Hopefully, MUD players won't pour their life stories out
   * into the Mud Mail System anyway.
   * 
   * Note that the block_type data field in data blocks is either a number >=0,
   * meaning a link to the next block, or LAST_BLOCK flag (-2) meaning the
   * last block in the current message.  This works much like DOS' FAT.
   */

  while (bytes_written < total_length)
    {
      last_address = target_address;
      target_address = pop_free_list ();

      /* rewrite the previous block to link it to the next */
      data.block_type = target_address;
      write_to_file (&data, BLOCK_SIZE, last_address);

      /* now write the next block, assuming it's the last.  */
      data.block_type = LAST_BLOCK;
      strncpy (data.txt, msg_txt, DATA_BLOCK_DATASIZE);
      data.txt[DATA_BLOCK_DATASIZE] = '\0';
      write_to_file (&data, BLOCK_SIZE, target_address);

      bytes_written += strlen (data.txt);
      msg_txt += strlen (data.txt);
    }
}				/* store mail */




/* READ_DELETE */
/* read_delete takes 1 char pointer to the name of the person whose mail
   you're retrieving.  It returns to you a char pointer to the message text.
   The mail is then discarded from the file and the mail index. */

char *
read_delete (long recipient)
/* recipient is the name as it appears in the index.
   recipient_formatted is the name as it should appear on the mail
   header (i.e. the text handed to the player) */
{
  header_block_type header;
  data_block_type data;
  mail_index_type *mail_pointer, *prev_mail;
  position_list_type *position_pointer;
  long mail_address, following_block;
  char *message, *tmstr, buf[200];
  size_t string_size;

  if (recipient < 0)
    {
      log ("SYSERR: Mail system -- non-fatal error #6.");
      return 0;
    }
  if (!(mail_pointer = find_char_in_index (recipient)))
    {
      log ("SYSERR: Mail system -- post office spec_proc error?  Error #7.");
      return 0;
    }
  if (!(position_pointer = mail_pointer->list_start))
    {
      log ("SYSERR: Mail system -- non-fatal error #8.");
      return 0;
    }
  if (!(position_pointer->next))
    {				/* just 1 entry in list. */
      mail_address = position_pointer->position;
      free (position_pointer);

      /* now free up the actual name entry */
      if (mail_index == mail_pointer)
	{			/* name is 1st in list */
	  mail_index = mail_pointer->next;
	  free (mail_pointer);
	}
      else
	{
	  /* find entry before the one we're going to del */
	  for (prev_mail = mail_index;
	       prev_mail->next != mail_pointer;
	       prev_mail = prev_mail->next);
	  prev_mail->next = mail_pointer->next;
	  free (mail_pointer);
	}
    }
  else
    {
      /* move to next-to-last record */
      while (position_pointer->next->next)
	position_pointer = position_pointer->next;
      mail_address = position_pointer->next->position;
      free (position_pointer->next);
      position_pointer->next = 0;
    }

  /* ok, now lets do some readin'! */
  read_from_file (&header, BLOCK_SIZE, mail_address);

  if (header.block_type != HEADER_BLOCK)
    {
      log ("SYSERR: Oh dear.");
      no_mail = 1;
      log ("SYSERR: Mail system disabled!  -- Error #9.");
      return 0;
    }
  tmstr = asctime (localtime (&header.header_data.mail_time));
  *(tmstr + strlen (tmstr) - 1) = '\0';

  sprintf (buf, " &b-&c=&y Deltanian Postal Service&c =&b-&n\r\n"
	   "&GD&gate&c:&n %s\r\n"
	   "  &GT&go&c:&n %s\r\n"
	   "&GF&grom&c:&n %s\r\n\r\n", tmstr, get_name_by_id (recipient),
	   get_name_by_id (header.header_data.from));

  string_size = (sizeof (char) * (strlen (buf) + strlen (header.txt) + 1));
  CREATE (message, char, string_size);
  strcpy (message, buf);
  strcat (message, header.txt);
  message[string_size - 1] = '\0';
  following_block = header.header_data.next_block;

  /* mark the block as deleted */
  header.block_type = DELETED_BLOCK;
  write_to_file (&header, BLOCK_SIZE, mail_address);
  push_free_list (mail_address);

  while (following_block != LAST_BLOCK)
    {
      read_from_file (&data, BLOCK_SIZE, following_block);

      string_size = (sizeof (char) * (strlen (message) + strlen (data.txt) + 1));
      RECREATE (message, char, string_size);
      strcat (message, data.txt);
      message[string_size - 1] = '\0';
      mail_address = following_block;
      following_block = data.block_type;
      data.block_type = DELETED_BLOCK;
      write_to_file (&data, BLOCK_SIZE, mail_address);
      push_free_list (mail_address);
    }

  return message;
}


/*****************************************************************
** Below is the spec_proc for a postmaster using the above       **
** routines.  Written by Jeremy Elson (jelson@server.cs.jhu.edu) **
*****************************************************************/

SPECIAL (postmaster)
{
  if (!ch->desc || IS_NPC (ch))
    return 0;			/* so mobs don't get caught here */

  if (!(CMD_IS ("mail") || CMD_IS ("check") || CMD_IS ("receive")))
    return 0;

  if (no_mail)
    {
      send_to_char ("Sorry, the mail system is having technical difficulties.\r\n", ch);
      return 0;
    }

  if (CMD_IS ("mail"))
    {
      postmaster_send_mail (ch, me, cmd, argument);
      return 1;
    }
  else if (CMD_IS ("check"))
    {
      postmaster_check_mail (ch, me, cmd, argument);
      return 1;
    }
  else if (CMD_IS ("receive"))
    {
      postmaster_receive_mail (ch, me, cmd, argument);
      return 1;
    }
  else
    return 0;
}


void 
postmaster_send_mail (struct char_data *ch, struct char_data *mailman,
		      int cmd, char *arg)
{
  long recipient;
  char buf[256];

  if (GET_LEVEL (ch) < MIN_MAIL_LEVEL && GET_LEVEL(mailman) != 1)
    {
      sprintf (buf, "$n tells you, 'Sorry, you have to be level %d to send mail!'",
	       MIN_MAIL_LEVEL);
      act (buf, FALSE, mailman, 0, ch, TO_VICT);
      return;
    }
  one_argument (arg, buf);

  if (!*buf)
    {				/* you'll get no argument from me! */
      act ("$n tells you, 'You need to specify an address!'",
	   FALSE, mailman, 0, ch, TO_VICT);
      return;
    }
  if (GET_GOLD (ch) < STAMP_PRICE && GET_LEVEL(ch) < LVL_IMMORT && GET_LEVEL(mailman) != 1)
    {
      sprintf (buf, "$n tells you, 'A stamp costs %d coins.'\r\n"
	   "$n tells you, '...which I see you can't afford.'", STAMP_PRICE);
      act (buf, FALSE, mailman, 0, ch, TO_VICT);
      return;
    }
  if ((recipient = get_id_by_name (buf)) < 0)
    {
      act ("$n tells you, 'No one by that name is registered here!'",
	   FALSE, mailman, 0, ch, TO_VICT);
      return;
    }
  act ("$n starts to write some mail.", TRUE, ch, 0, 0, TO_ROOM);
  if (GET_LEVEL(mailman) != 1)
    sprintf (buf, "$n tells you, 'I'll take %d coins for the stamp.'\r\n"
	   "$n tells you, 'Write your message, (/s saves /h for help)'",
	   STAMP_PRICE);

  else
    sprintf (buf, "$n smiles and tells you, 'The stamp is free.'\r\n"
           "$n tells you, 'Write your message, (/s saves /h for help)'");
  

  act (buf, FALSE, mailman, 0, ch, TO_VICT);

if (GET_LEVEL(ch) < LVL_IMMORT)
  GET_GOLD (ch) -= STAMP_PRICE;

  SET_BIT (PLR_FLAGS (ch), PLR_MAILING | PLR_WRITING);

  ch->desc->mail_to = recipient;
  ch->desc->str = (char **) malloc (sizeof (char *));
  *(ch->desc->str) = NULL;
  ch->desc->max_str = MAX_MAIL_SIZE;
}


void 
postmaster_check_mail (struct char_data *ch, struct char_data *mailman,
		       int cmd, char *arg)
{
  char buf[256];

  if (has_mail (GET_IDNUM (ch)))
    sprintf (buf, "$n tells you, 'You have mail waiting.'");
  else
    sprintf (buf, "$n tells you, 'Sorry, you don't have any mail waiting.'");
  act (buf, FALSE, mailman, 0, ch, TO_VICT);
}


void 
postmaster_receive_mail (struct char_data *ch, struct char_data *mailman,
			 int cmd, char *arg)
{
  char buf[256];
  struct obj_data *obj;

  if (!has_mail (GET_IDNUM (ch)))
    {
      sprintf (buf, "$n tells you, 'Sorry, you don't have any mail waiting.'");
      act (buf, FALSE, mailman, 0, ch, TO_VICT);
      return;
    }
  while (has_mail (GET_IDNUM (ch)))
    {
      obj = create_obj ();
      obj->item_number = NOTHING;
      obj->name = str_dup ("mail paper letter");
      obj->short_description = str_dup ("a piece of mail");
      obj->description = str_dup ("Someone has left a piece of mail here.");

      GET_OBJ_TYPE (obj) = ITEM_NOTE;
      GET_OBJ_WEAR (obj) = ITEM_WEAR_TAKE | ITEM_WEAR_HOLD;
      GET_OBJ_WEIGHT (obj) = 1;
      GET_OBJ_COST (obj) = 30;
      GET_OBJ_RENT (obj) = 10;
      obj->action_description = read_delete (GET_IDNUM (ch));

      if (obj->action_description == NULL)
	obj->action_description =
	  str_dup ("Mail system error - please report.  Error #11.\r\n");

      obj_to_char (obj, ch);

      act ("$n gives you a piece of mail.", FALSE, mailman, 0, ch, TO_VICT);
      act ("$N gives $n a piece of mail.", FALSE, ch, 0, mailman, TO_ROOM);
    }
}