empiremud/cnf/
empiremud/doc/
empiremud/lib/boards/
empiremud/lib/etc/
empiremud/lib/misc/
empiremud/lib/plralias/F-J/
empiremud/lib/plralias/K-O/
empiremud/lib/plralias/P-T/
empiremud/lib/plralias/U-Z/
empiremud/lib/plrobjs/
empiremud/lib/plrobjs/F-J/
empiremud/lib/plrobjs/K-O/
empiremud/lib/plrobjs/P-T/
empiremud/lib/plrobjs/U-Z/
empiremud/lib/world/
empiremud/lib/world/mob/
empiremud/lib/world/obj/
empiremud/log/
/* ************************************************************************
*   File: boards.c                                       EmpireMUD AD 1.0 *
*  Usage: handling of multiple bulletin boards                            *
*                                                                         *
*  All rights reserved.  See license.doc for complete information.        *
*                                                                         *
*  Code base by Paul Clarke.  EmpireMUD Project, a tbgMUD Production.     *
*  Based upon CircleMUD 3.0, beta patch level 17, by Jeremy Elson.        *
*                                                                         *
*  Copyright (C) 1993, 94 by the Trustees of the Johns Hopkins University *
*  CircleMUD is based on DikuMUD, Copyright (C) 1990, 1991.               *
************************************************************************ */

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

#include "structs.h"
#include "utils.h"
#include "comm.h"
#include "db.h"
#include "boards.h"
#include "interpreter.h"
#include "handler.h"
#include "vnums.h"

/* Board appearance order. */
#define	NEWEST_AT_TOP	TRUE

int board_loaded = 0;


/* FEATURES & INSTALLATION INSTRUCTIONS ***********************************
 *
 * This board code has many improvements over the infamously buggy standard
 * Diku board code.  Features include:
 *
 * - Arbitrary number of boards handled by one set of generalized routines.
 *   Adding a new board is as easy as adding another entry to an array.
 * - Safe removal of messages while other messages are being written.
 * - Does not allow messages to be removed by someone of a level less than
 *   the poster's level.
 * - Messages listed from bottom to top, or top to bottom
 * - Replies to messages
 *
 *
 * TO ADD A NEW BOARD, simply follow our easy 2-step program:
 *
 * 1 - Increase the NUM_OF_BOARDS constant in boards.h
 *
 * 2 - Add a new line to the board_info array below.  The fields, in order, are:
 *   Board's virtual number.
 *   Min level one must be to look at this board or read messages on it.
 *   Min level one must be to post a message to the board.
 *   Min level one must be to remove other people's messages from this
 *     board (but you can always remove your own message).
 *   Filename of this board, in quotes.
 *   Last field must always be 0.
 *
 */


struct board_info_type board_info[NUM_OF_BOARDS] = {

	{	BOARD_MORT,	0,				0,				LVL_IMPL,	LIB_BOARD "mort",		0 },
	{	BOARD_IMM,	LVL_START_IMM,	LVL_START_IMM,	LVL_IMPL,	LIB_BOARD "immort",		0 }

	};


char *msg_storage[INDEX_SIZE];
int msg_storage_taken[INDEX_SIZE];
int num_of_msgs[NUM_OF_BOARDS];
struct board_msginfo msg_index[NUM_OF_BOARDS][MAX_BOARD_MESSAGES];


int find_slot(void) {
	int i;

	for (i = 0; i < INDEX_SIZE; i++)
		if (!msg_storage_taken[i]) {
			msg_storage_taken[i] = 1;
			return (i);
			}
	return (-1);
	}


/* search the room ch is standing in to find which board he's looking at */
int find_board(Creature ch) {
	Object obj;
	int i;

	for (obj = world[ch->in_room].contents; obj; obj = obj->next_content)
		for (i = 0; i < NUM_OF_BOARDS; i++)
			if (BOARD_RNUM(i) == GET_OBJ_RNUM(obj))
				return (i);

	return (-1);
	}


void init_boards(void) {
	int i, j, fatal_error = 0;

	for (i = 0; i < INDEX_SIZE; i++) {
		msg_storage[i] = 0;
		msg_storage_taken[i] = 0;
		}

	for (i = 0; i < NUM_OF_BOARDS; i++) {
		if ((BOARD_RNUM(i) = real_object(BOARD_VNUM(i))) == -1) {
			log("SYSERR: Fatal board error: board vnum %d does not exist!", BOARD_VNUM(i));
			fatal_error = 1;
			}
		num_of_msgs[i] = 0;
		for (j = 0; j < MAX_BOARD_MESSAGES; j++) {
			memset((char *) &(msg_index[i][j]), 0, sizeof(struct board_msginfo));
			msg_index[i][j].slot_num = -1;
			}
		Board_load_board(i);
		}

	if (fatal_error)
		exit(1);
	}


ACMD(do_write) {
	Object board;
	int board_type;

	if (!board_loaded) {
		init_boards();
		board_loaded = 1;
		}
	if (!ch->desc)
		return;

	for (board = world[ch->in_room].contents; board; board = board->next_content)
		if (GET_OBJ_TYPE(board) == ITEM_BOARD)
			break;
	if (!board)
		msg_to_char(ch, "There's no board here.\r\n");
	else if ((board_type = find_board(ch)) == -1) {
		log("SYSERR:  degenerate board!  (what the hell...)");
		msg_to_char(ch, "There's no board here.\r\n");
		}
	else if (PLR_FLAGGED(ch, PLR_MUTED))
		msg_to_char(ch, "You can't write on boards while muted.\r\n");
	else if (!Board_write_message(board_type, ch, argument, board))
		msg_to_char(ch, "There's no board here.\r\n");
	}


ACMD(do_read) {
	Object board;
	int board_type;

	if (!board_loaded) {
		init_boards();
		board_loaded = 1;
		}
	if (!ch->desc)
		return;

	for (board = world[ch->in_room].contents; board; board = board->next_content)
		if (GET_OBJ_TYPE(board) == ITEM_BOARD)
			break;
	if (!board)
		msg_to_char(ch, "There's no board here.\r\n");
	else if ((board_type = find_board(ch)) == -1) {
		log("SYSERR:  degenerate board!  (what the hell...)");
		msg_to_char(ch, "There's no board here.\r\n");
		}
	else if (!Board_display_msg(board_type, ch, argument, board))
		msg_to_char(ch, "There's no board here.\r\n");
	}


ACMD(do_respond) {
	Object board;
	int board_type;

	if (!board_loaded) {
		init_boards();
		board_loaded = 1;
		}
	if (!ch->desc)
		return;

	for (board = world[ch->in_room].contents; board; board = board->next_content)
		if (GET_OBJ_TYPE(board) == ITEM_BOARD)
			break;
	if (!board)
		msg_to_char(ch, "There's no board here.\r\n");
	else if ((board_type = find_board(ch)) == -1) {
		log("SYSERR:  degenerate board!  (what the hell...)");
		msg_to_char(ch, "There's no board here.\r\n");
		}
	else if (PLR_FLAGGED(ch, PLR_MUTED))
		msg_to_char(ch, "You can't write on boards while muted.\r\n");
	else if (!Board_respond_message(board_type, ch, argument, board))
		msg_to_char(ch, "There's no board here.\r\n");
	}


int Board_write_message(int board_type, Creature ch, char *arg, Object board) {
	char *tmstr;
	time_t ct;
	char buf[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH];

	if (GET_LEVEL(ch) < WRITE_LVL(board_type)) {
		send_to_char("You are not holy enough to write on this board.\r\n", ch);
		return (1);
		}
	if (num_of_msgs[board_type] >= MAX_BOARD_MESSAGES) {
		send_to_char("The board is full.\r\n", ch);
		return (1);
		}
	if ((NEW_MSG_INDEX(board_type).slot_num = find_slot()) == -1) {
		send_to_char("The board is malfunctioning - sorry.\r\n", ch);
		log("SYSERR: Board: failed to find empty slot on write.");
		return (1);
		}
	/* skip blanks */
	skip_spaces(&arg);
	delete_doubledollar(arg);

	/* JE 27 Oct 95 - Truncate headline at 80 chars if it's longer than that */
	arg[80] = '\0';

	if (!*arg) {
		send_to_char("We must have a headline!\r\n", ch);
		return (1);
		}
	ct = time(0);
	tmstr = (char *) asctime(localtime(&ct));
	*(tmstr + strlen(tmstr) - 1) = '\0';

	sprintf(buf2, "(%s%s&0)", GET_LEVEL(ch) == LVL_IMPL ? "&3" : GET_LEVEL(ch) == LVL_GOD ? "&6" : "&0", GET_NAME(ch));
	sprintf(buf, "%6.10s %-14s :: %s", tmstr, buf2, arg);
	NEW_MSG_INDEX(board_type).heading = str_dup(buf);
	NEW_MSG_INDEX(board_type).level = GET_LEVEL(ch);
	NEW_MSG_INDEX(board_type).reply_num = -1;

	send_to_char("Write your message (/s saves, /h for help)\r\n\r\n", ch);
	act("$n starts to write a message.", TRUE, ch, 0, 0, TO_ROOM);

	string_write(ch->desc, &(msg_storage[NEW_MSG_INDEX(board_type).slot_num]), MAX_MESSAGE_LENGTH, board_type + BOARD_MAGIC, NULL);

	num_of_msgs[board_type]++;
	return (1);
	}


int Board_show_board(int board_type, Creature ch, char *arg, Object board) {
	int i;
	char tmp[MAX_STRING_LENGTH], buf[MAX_STRING_LENGTH];

	if (!ch->desc)
		return (0);

	one_argument(arg, tmp);

	if (!*tmp || !isname(tmp, board->name))
		return (0);

	if (GET_LEVEL(ch) < READ_LVL(board_type)) {
		send_to_char("You try but fail to understand the holy words.\r\n", ch);
		return (1);
		}
	act("$n studies the board.", TRUE, ch, 0, 0, TO_ROOM);

	strcpy(buf,
		"This is a bulletin board.  Usage: READ/REMOVE <messg #>, WRITE <header>.\r\n"
		"You will need to look at the board to save your message.\r\n");
	if (!num_of_msgs[board_type])
		strcat(buf, "The board is empty.\r\n");
	else {
		sprintf(buf + strlen(buf), "There are %d messages on the board.\r\n", num_of_msgs[board_type]);
#if NEWEST_AT_TOP
		for (i = num_of_msgs[board_type] - 1; i >= 0; i--) {
#else
		for (i = 0; i < num_of_msgs[board_type]; i++) {
#endif
			if (MSG_HEADING(board_type, i)) {
				if (MSG_REPLY(board_type, i) == -1)
					Board_display_response(board_type, i, buf, board, 0);
				}
			else {
				log("SYSERR: The board is fubar'd.");
				send_to_char("Sorry, the board isn't working.\r\n", ch);
				return (1);
				}
			}
		}
	page_string(ch->desc, buf, 1);

	return (1);
	}


void Board_display_response(int board_type, int slot_num, char *output, Object board, bool reply) {
	int k;
	char buf[MAX_STRING_LENGTH];

#if NEWEST_AT_TOP
	sprintf(buf, "%-2d : %s\r\n", num_of_msgs[board_type] - slot_num, MSG_HEADING(board_type, slot_num));
#else
	sprintf(buf, "%-2d : %s\r\n", slot_num + 1, MSG_HEADING(board_type, slot_num));
#endif
	strcat(output, buf);

	for (k = slot_num; k < num_of_msgs[board_type]; k++)
		if (MSG_REPLY(board_type, k) == slot_num)
			Board_display_response(board_type, k, output, board, 1);
	}


int Board_display_msg(int board_type, Creature ch, char *arg, Object board) {
  char number[MAX_STRING_LENGTH], buffer[MAX_STRING_LENGTH];
  int msg, ind;

  one_argument(arg, number);
  if (!*number)
    return (0);
  if (isname(number, board->name))	/* so "read board" works */
    return (Board_show_board(board_type, ch, arg, board));
  if (strchr(number, '.'))	/* read 2.mail, look 2.sword */
    return (0);
  if (!isdigit(*number) || (!(msg = atoi(number))))
    return (0);

  if (GET_LEVEL(ch) < READ_LVL(board_type)) {
    send_to_char("You try but fail to understand the holy words.\r\n", ch);
    return (1);
  }
  if (!num_of_msgs[board_type]) {
    send_to_char("The board is empty!\r\n", ch);
    return (1);
  }
  if (msg < 1 || msg > num_of_msgs[board_type]) {
    send_to_char("That message exists only in your imagination.\r\n",
		 ch);
    return (1);
  }
#if NEWEST_AT_TOP
  ind = num_of_msgs[board_type] - msg;
#else
  ind = msg - 1;
#endif
  if (MSG_SLOTNUM(board_type, ind) < 0 ||
      MSG_SLOTNUM(board_type, ind) >= INDEX_SIZE) {
    send_to_char("Sorry, the board is not working.\r\n", ch);
    log("SYSERR: Board is screwed up. (Room #%d)", GET_ROOM_VNUM(IN_ROOM(ch)));
    return (1);
  }
  if (!(MSG_HEADING(board_type, ind))) {
    send_to_char("That message appears to be screwed up.\r\n", ch);
    return (1);
  }
  if (!(msg_storage[MSG_SLOTNUM(board_type, ind)])) {
    send_to_char("That message seems to be empty.\r\n", ch);
    return (1);
  }
  sprintf(buffer, "Message %d : %s\r\n\r\n%s\r\n", msg,
	  MSG_HEADING(board_type, ind),
	  msg_storage[MSG_SLOTNUM(board_type, ind)]);

  page_string(ch->desc, buffer, 1);

  return (1);
}


void perform_remove_message(int board_type, int slot_num, int ind) {
	int k;
	bool found = FALSE;
	Descr d;

	for (k = 0; k < num_of_msgs[board_type]; k++, found = FALSE) {
		if (MSG_REPLY(board_type, k) > ind)
			MSG_REPLY(board_type, k) -= 1;
		else if (MSG_REPLY(board_type, k) == slot_num) {
			for (d = descriptor_list; d; d = d->next)
				if (STATE(d) == CON_PLAYING && d->str == &(msg_storage[MSG_SLOTNUM(board_type, k)]))
					found = TRUE;
			if (!found)
				perform_remove_message(board_type, MSG_SLOTNUM(board_type, k), k);
			}
		}

	if (msg_storage[slot_num])
		free(msg_storage[slot_num]);
	msg_storage[slot_num] = 0;
	msg_storage_taken[slot_num] = 0;
	if (MSG_HEADING(board_type, ind))
		free(MSG_HEADING(board_type, ind));
	MSG_REPLY(board_type, ind) = -1;

	for (; ind < num_of_msgs[board_type] - 1; ind++) {
		MSG_HEADING(board_type, ind) = MSG_HEADING(board_type, ind + 1);
		MSG_SLOTNUM(board_type, ind) = MSG_SLOTNUM(board_type, ind + 1);
		MSG_LEVEL(board_type, ind) = MSG_LEVEL(board_type, ind + 1);
		MSG_REPLY(board_type, ind) = MSG_REPLY(board_type, ind + 1);
		}
	num_of_msgs[board_type]--;
	}


int Board_remove_msg(int board_type, Creature ch, char *arg, Object board) {
	int ind, msg, slot_num;
	char number[MAX_INPUT_LENGTH], buf[MAX_INPUT_LENGTH];
	Descr d;

	one_argument(arg, number);

	if (!*number || !isdigit(*number))
		return (0);
	if (!(msg = atoi(number)))
		return (0);

	if (!num_of_msgs[board_type]) {
		send_to_char("The board is empty!\r\n", ch);
		return (1);
		}
	if (msg < 1 || msg > num_of_msgs[board_type]) {
		send_to_char("That message exists only in your imagination.\r\n", ch);
		return (1);
		}
#if NEWEST_AT_TOP
	ind = num_of_msgs[board_type] - msg;
#else
	ind = msg - 1;
#endif
	if (!MSG_HEADING(board_type, ind)) {
		send_to_char("That message appears to be screwed up.\r\n", ch);
		return (1);
		}
	sprintf(buf, "%s", GET_NAME(ch));
	if (GET_LEVEL(ch) < REMOVE_LVL(board_type) && !(strstr(MSG_HEADING(board_type, ind), buf))) {
		send_to_char("You are not holy enough to remove other people's messages.\r\n", ch);
		return (1);
		}
	if (GET_LEVEL(ch) < MSG_LEVEL(board_type, ind)) {
		send_to_char("You can't remove a message holier than yourself.\r\n", ch);
		return (1);
		}
	slot_num = MSG_SLOTNUM(board_type, ind);
	if (slot_num < 0 || slot_num >= INDEX_SIZE) {
		send_to_char("That message is majorly screwed up.\r\n", ch);
		log("SYSERR: The board is seriously screwed up. (Room #%d)", GET_ROOM_VNUM(IN_ROOM(ch)));
		return (1);
		}
	for (d = descriptor_list; d; d = d->next)
		if (STATE(d) == CON_PLAYING && d->str == &(msg_storage[slot_num])) {
			send_to_char("At least wait until the author is finished before removing it!\r\n", ch);
			return (1);
			}
	perform_remove_message(board_type, slot_num, ind);
	send_to_char("Message removed.\r\n", ch);
	sprintf(buf, "$n just removed message %d.", msg);
	act(buf, FALSE, ch, 0, 0, TO_ROOM);
	Board_save_board(board_type);

	return (1);
	}


void Board_save_board(int board_type)
{
  FILE *fl;
  int i;
  char *tmp1, *tmp2 = NULL;

  if (!num_of_msgs[board_type]) {
    remove(FILENAME(board_type));
    return;
  }
  if (!(fl = fopen(FILENAME(board_type), "wb"))) {
    perror("SYSERR: Error writing board");
    return;
  }
  fwrite(&(num_of_msgs[board_type]), sizeof(int), 1, fl);

  for (i = 0; i < num_of_msgs[board_type]; i++) {
    if ((tmp1 = MSG_HEADING(board_type, i)) != NULL)
      msg_index[board_type][i].heading_len = strlen(tmp1) + 1;
    else
      msg_index[board_type][i].heading_len = 0;

    if (MSG_SLOTNUM(board_type, i) < 0 ||
	MSG_SLOTNUM(board_type, i) >= INDEX_SIZE ||
	(!(tmp2 = msg_storage[MSG_SLOTNUM(board_type, i)])))
      msg_index[board_type][i].message_len = 0;
    else
      msg_index[board_type][i].message_len = strlen(tmp2) + 1;

    fwrite(&(msg_index[board_type][i]), sizeof(struct board_msginfo), 1, fl);
    if (tmp1)
      fwrite(tmp1, sizeof(char), msg_index[board_type][i].heading_len, fl);
    if (tmp2)
      fwrite(tmp2, sizeof(char), msg_index[board_type][i].message_len, fl);
  }

  fclose(fl);
}


void Board_load_board(int board_type)
{
  FILE *fl;
  int i, len1, len2;
  char *tmp1, *tmp2;

  if (!(fl = fopen(FILENAME(board_type), "rb"))) {
    if (errno != ENOENT)
      perror("SYSERR: Error reading board");
    return;
  }
  fread(&(num_of_msgs[board_type]), sizeof(int), 1, fl);
  if (num_of_msgs[board_type] < 1 || num_of_msgs[board_type] > MAX_BOARD_MESSAGES) {
    log("SYSERR: Board file %d corrupt.  Resetting.", board_type);
    Board_reset_board(board_type);
    return;
  }
  for (i = 0; i < num_of_msgs[board_type]; i++) {
    fread(&(msg_index[board_type][i]), sizeof(struct board_msginfo), 1, fl);
    if ((len1 = msg_index[board_type][i].heading_len) <= 0) {
      log("SYSERR: Board file %d corrupt!  Resetting.", board_type);
      Board_reset_board(board_type);
      return;
    }
    CREATE(tmp1, char, len1);
    fread(tmp1, sizeof(char), len1, fl);
    MSG_HEADING(board_type, i) = tmp1;

    if ((MSG_SLOTNUM(board_type, i) = find_slot()) == -1) {
      log("SYSERR: Out of slots booting board %d!  Resetting...", board_type);
      Board_reset_board(board_type);
      return;
    }
    if ((len2 = msg_index[board_type][i].message_len) > 0) {
      CREATE(tmp2, char, len2);
      fread(tmp2, sizeof(char), len2, fl);
      msg_storage[MSG_SLOTNUM(board_type, i)] = tmp2;
    } else
      msg_storage[MSG_SLOTNUM(board_type, i)] = NULL;
  }

  fclose(fl);
}


void Board_reset_board(int board_type)
{
  int i;

  for (i = 0; i < MAX_BOARD_MESSAGES; i++) {
    if (MSG_HEADING(board_type, i))
      free(MSG_HEADING(board_type, i));
    if (msg_storage[MSG_SLOTNUM(board_type, i)])
      free(msg_storage[MSG_SLOTNUM(board_type, i)]);
    msg_storage_taken[MSG_SLOTNUM(board_type, i)] = 0;
    memset((char *)&(msg_index[board_type][i]),0,sizeof(struct board_msginfo));
    msg_index[board_type][i].slot_num = -1;
  }
  num_of_msgs[board_type] = 0;
  remove(FILENAME(board_type));
}


int Board_respond_message(int board_type, Creature ch, char *arg, Object board) {
	char *tmstr;
	time_t ct;
	int msg, ind;
	char buf[MAX_INPUT_LENGTH], buf2[MAX_INPUT_LENGTH], number[MAX_STRING_LENGTH];

	if (GET_LEVEL(ch) < WRITE_LVL(board_type)) {
		send_to_char("You are not holy enough to write on this board.\r\n", ch);
		return (1);
		}
	if (num_of_msgs[board_type] >= MAX_BOARD_MESSAGES) {
		send_to_char("The board is full.\r\n", ch);
		return (1);
		}
	if ((NEW_MSG_INDEX(board_type).slot_num = find_slot()) == -1) {
		send_to_char("The board is malfunctioning - sorry.\r\n", ch);
		log("SYSERR: Board: failed to find empty slot on write.");
		return (1);
		}
	/* skip blanks */
	skip_spaces(&arg);
	delete_doubledollar(arg);

	arg = one_argument(arg, number);

	/* JE 27 Oct 95 - Truncate headline at 80 chars if it's longer than that */
	arg[80] = '\0';

	if (!*number) {
		msg_to_char(ch, "Usage: respond <number> <headline>\r\n", ch);
		return 1;
		}
	if (!*arg) {
		send_to_char("We must have a headline!\r\n", ch);
		return (1);
		}

	if (!isdigit(*number) || (!(msg = atoi(number)))) {
		msg_to_char(ch, "What message are you trying to respong to?\r\n");
		return 1;
		}
	if (msg < 1 || msg > num_of_msgs[board_type]) {
		msg_to_char(ch, "That message exists only in your imagination.\r\n");
		return 1;
		}

#if NEWEST_AT_TOP
	ind = num_of_msgs[board_type] - msg;
#else
	ind = msg - 1;
#endif

	if (MSG_SLOTNUM(board_type, ind) < 0 || MSG_SLOTNUM(board_type, ind) >= INDEX_SIZE) {
		msg_to_char(ch, "Sorry, the board is not working.\r\n");
		log("SYSERR: Board is screwed up.");
		return 1;
		}
	if (MSG_REPLY(board_type, ind) != -1) {
		msg_to_char(ch, "You can't respond to a response.\r\n");
		return 1;
		}

	ct = time(0);
	tmstr = (char *) asctime(localtime(&ct));
	*(tmstr + strlen(tmstr) - 1) = '\0';

	sprintf(buf2, "(%s%s&0)", GET_LEVEL(ch) == LVL_IMPL ? "&3" : GET_LEVEL(ch) == LVL_GOD ? "&6" : "&0", GET_NAME(ch));
	sprintf(buf, "%6.10s %-14s :: -%s", tmstr, buf2, arg);
	NEW_MSG_INDEX(board_type).heading = str_dup(buf);
	NEW_MSG_INDEX(board_type).level = GET_LEVEL(ch);
	NEW_MSG_INDEX(board_type).reply_num = MSG_SLOTNUM(board_type, ind);

	send_to_char("Write your message (/s saves, /h for help)\r\n\r\n", ch);
	act("$n starts to write a message.", TRUE, ch, 0, 0, TO_ROOM);

	string_write(ch->desc, &(msg_storage[NEW_MSG_INDEX(board_type).slot_num]), MAX_MESSAGE_LENGTH, board_type + BOARD_MAGIC, NULL);

	num_of_msgs[board_type]++;
	return (1);
	}