/
roa/
roa/lib/boards/
roa/lib/config/
roa/lib/edits/
roa/lib/help/
roa/lib/misc/
roa/lib/plrobjs/
roa/lib/quests/
roa/lib/socials/
roa/lib/www/
roa/lib/www/LEDSign/
roa/lib/www/LEDSign/fonts/
roa/lib/www/LEDSign/scripts/
roa/src/s_inc/
roa/src/sclient/
roa/src/sclient/binary/
roa/src/sclient/text/
roa/src/util/
/************************************************************************
	Realms of Aurealis 		James Rhone aka Vall of RoA

mail.c					Internal functions and procs of
					mudmail system.

		    ******** Modified and expanded ********
		*** BE AWARE OF ALL RIGHTS AND RESERVATIONS ***
		    ******** Modified and expanded ********
		        All rights reserved henceforth. 

    Please note that no guarantees are associated with any code from
Realms of Aurealis.  All code which has been released to the general
public has been done so with an 'as is' pretense.  RoA is based on both
Diku and CircleMUD and ALL licenses from both *MUST* be adhered to as well
as the RoA license.   *** Read, Learn, Understand, Improve ***
*************************************************************************/
/******* MUD MAIL SYSTEM MAIN FILE ***************************************

Originally written by Jeremy Elson (jelson@cs.jhu.edu)

// RoAMUD modifications -jtrhone
*************************************************************************/

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

#include "structures.h"
#include "utils.h"
#include "comm.h"
#include "db.h"
#include "interpreter.h"
#include "handler.h"
#include "mail.h"
#include "lists.h"
#include "global.h"
#include "descmenu.h"

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

extern int no_mail;
extern char *get_name_by_id(long idnum);
extern long get_id_by_name(char *name);
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 % MBLOCK_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 % MBLOCK_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 * MBLOCK_SIZE);
      total_messages++;
    } else if (next_block.block_type == DELETED_BLOCK)
      push_free_list(block_num * MBLOCK_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 % MBLOCK_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) == MBLOCK_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, MBLOCK_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, MBLOCK_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, MBLOCK_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, MBLOCK_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, MBLOCK_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. */

// recipient is now the idnum of the receiver  -jtrhone
char *read_delete(long recipient)
{
  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, MBLOCK_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* * * * %%6%s Mail System%%0%%B * * * *%%0\r\n"
	  "%%BDate%%0: %s\r\n"
	  "  %%BTo%%0: %s\r\n"
	  "%%BFrom%%0: %%B%%6%s%%0\r\n\r\n", longmudname, 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, MBLOCK_SIZE, mail_address);
  push_free_list(mail_address);

  while (following_block != LAST_BLOCK) {
    read_from_file(&data, MBLOCK_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, MBLOCK_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) **
*****************************************************************/
// updated for new command structure 4/10/98 -jtrhone
BOOL postmaster(chdata *ch, chdata *mailman, int cmd, char *arg)
{
  if (!ch->desc)
    return 0; /* so mobs don't get caught here */

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

// a descriptor menu for easier mail writing and allowance of roa client usage 7/18/98 -jtrhone
// will get called after do_var_string_arg finishes and pops and calls
ROA_DESCMENU(donemailing)
{
  dsdata *t;
  BOOL valid = FALSE;

  if (*(*d->str))
  {
    valid = TRUE;
    store_mail(d->mail_to, GET_IDNUM(d->character), *d->str);
    free(*d->str);
  }

  // this gets freed regardless...
  free(d->str);

  REMOVE_BIT(PLR_FLAGS(d->character), PLR_MAILING);

  // scan current descriptor list and notify recipient of arriving mail
  if (valid)
    for (t = descriptor_list; t; t=t->next)
      if (D_CHECK(t) && GET_IDNUM(t->character) == d->mail_to)
      {
        act("You have new mail from %B%6$N%0.",FALSE,t->character,0, d->character,TO_CHAR);
        break;
      }

  d->mail_to = -1;

  if (valid)
    send_to_char("Mail message complete.\n\r", d->character);
  else
    send_to_char("Mail message aborted.\n\r", d->character);

  // no more menus...
  DESCMENU_PROMPT(d)  = NULL;
  DESCMENU_HANDLER(d) = NULL;
  DESCMENU_DEPTH(d)   = 0;
}

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

  if (GET_LEVEL(ch) < MIN_MAIL_LEVEL) {
    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 addressee!'", FALSE, mailman, 0, ch, TO_VICT);
    return;
  }

  if (!IS_IMMORTAL(ch) && GET_GOLD(ch) < mailcost) 
  {
    sprintf(buf, "$n tells you, 'A stamp costs %d %s.'\r\n"
	    "$n tells you, '...which I see you can't afford.'", mailcost, currency_name_plural);
    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;
  }

  if (!IS_IMMORTAL(ch))
  {
    sprintf(buf, "$n tells you, 'I'll take %d %s for the stamp.'", mailcost, currency_name_plural);
    act(buf, FALSE, mailman, 0, ch, TO_VICT);
  }

  act("$n starts to write some mail.", TRUE, ch, 0, 0, TO_ROOM);

  GET_GOLD(ch) -= mailcost;
  SET_BIT(PLR_FLAGS(ch), PLR_MAILING);

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

  descmenu_next(ch->desc, donemailing);
  do_var_string_arg_desc(ch->desc, "Write your message.  Terminate with a '@':\n\r",
                         ch->desc->str, MAX_MAIL_SIZE);
}

void postmaster_check_mail(chdata *ch, chdata *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(chdata *ch, chdata *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))) 
  {
    CREATE(obj, obdata, 1);
    clear_object(obj);
    obj->item_number = -1;
    obj->name = str_dup("mail paper letter parchment");
    obj->shdesc = str_dup("a piece of parchment");
    obj->description = str_dup("A withered piece of parchment lies here.");

    ITEM_TYPE(obj) = ITEM_NOTE;
    OBJ_WEARS(obj) = ITEM_TAKE | ITEM_HOLD;
    GET_OBJ_WEIGHT(obj) = 1;
    obj->actdesc = read_delete(GET_IDNUM(ch));

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

    obj_to_char(obj, ch);

    act("$n gives you $p.", FALSE, mailman, obj, ch, TO_VICT);
    act("$N gives $n $p.", FALSE, ch, obj, mailman, TO_ROOM);
  }
}