/***************************************************************************
* file: board.c , Implementation of boards. Part of DIKUMUD *
* Usage : Board Commands. *
* Copyright (C) 1990, 1991 - see 'license.doc' for complete information. *
* *
* Copyright (C) 1992, 1993 Michael Chastain, Michael Quan, Mitchell Tse *
* Performance optimization and bug fixes by MERC Industries. *
* You can use our stuff in any way you like whatsoever so long as this *
* copyright notice remains intact. If you like it please drop a line *
* to mec@garnet.berkeley.edu. *
* *
* This is free software and you are benefitting. We hope that you *
* share your changes too. What goes around, comes around. *
***************************************************************************/
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#include "structs.h"
#include "mob.h"
#include "utils.h"
extern struct room_data *world;
#define MAX_MSGS 40 /* Max number of messages. */
#define SAVE_FILE "boards/board" /* Name of file for saving messages */
#define MAX_MESSAGE_LENGTH 2048 /* that should be enough */
struct board
{
struct board *next;
int room;
char filename[MAX_INPUT_LENGTH];
int has_loaded;
int has_saved;
char *msgs[MAX_MSGS]; /* message */
char *head[MAX_MSGS]; /* heading line */
int msg_num;
int flag; /* 1=being written to; 0=safe */
} *brd=0;
struct board * gpointer=0;
void board_write_msg(struct board *,CHAR_DATA *, char *);
int board_display_msg(struct board *brd,CHAR_DATA *ch, char *arg);
int board_remove_msg(struct board *brd,CHAR_DATA *ch, char *arg);
void board_save_board(struct board *brd);
void board_load_board(struct board *brd);
void board_reset_board(struct board *brd);
void error_log();
void board_fix_long_desc(int num, char *headers[MAX_MSGS]);
int board_show_board(struct board *brd,CHAR_DATA *ch, char *arg);
/* I have used cmd number 180-182 as the cmd numbers here. */
/* The commands would be, in order : NOTE <header> */
/* READ <message number>, REMOVE <message number> */
/* LOOK AT BOARD should give the long desc of the board */
/* and that should equal a list of message numbers and */
/* headers. This is done by calling a function that sets */
/* the long desc in the board object. This function is */
/* called when someone does a REMOVE or NOTE command. */
/* I have named the function board_fix_long_desc(). In the */
/* board_write_msg() function there is a part that should */
/* be replaced with a call to some dreadful routine used */
/* by the STRING command to receive player input. It is */
/* reputed to lurk somewhere within the limits of an evil */
/* file named act.comm.c... or is that modify.c?..*/
/* saving the board after the addition of a new messg */
/* poses a slight problem, since the text isn't actually */
/* entered in board_write. What I'll do is to let board() */
/* save the first time a 'look' is issued in the room.. */
/* ugh! that's ugly - gotta think of something better. */
/* -quinn */
/* And here is the board...correct me if I'm wrong. */
struct board *create_board(int room,int fix)
{
struct board *board;
int a;
log("creating board");
board=(struct board *)malloc(sizeof (struct board));
if(board==0)
log("cannot allocate board structure.");
else
{
board->next=brd;
board->room=room;
board->has_loaded=0;
board->has_saved=1;
if(fix)
{
sprintf(board->filename,"%s.global",SAVE_FILE);
gpointer=board;
}
else
sprintf(board->filename,"%s.%d",SAVE_FILE,room);
log(board->filename);
for(a=0;a<MAX_MSGS;a++)
{
board->head[a]=0;
board->msgs[a]=0;
}
board->msg_num=0;
board->flag=0;
brd=board;
}
return(board);
}
struct board *get_board(CHAR_DATA *ch, int fix)
{
struct board *board;
if(brd==0 || (fix && !gpointer))
{
if(fix)
return(gpointer=create_board(world[ch->in_room].number,fix));
else
return(create_board(world[ch->in_room].number,fix));
}
if(fix) return(gpointer);
for(board=brd;board!=0;board=board->next)
if(board->room == world[ch->in_room].number) return(board);
return(create_board(world[ch->in_room].number,fix));
}
int being_written(struct board *board)
{
return(board->flag);
}
int board(CHAR_DATA *ch, int cmd, char *arg, struct obj_data *object)
{
struct board *board;
static int isbusy=0;
if (isbusy) return(FALSE);
if (!ch->desc)
return(FALSE);
/* By MS or all NPC's will be trapped at the board */
if(!(board=get_board(ch,FALSE))) return(FALSE);
/* note: I'll let display and remove return 0 if the arg was non-board- */
/* related. Thus, it'll be possible to read other things than the board */
/* while you're in the room. Conceiveably, you could do this for write, */
/* too, but I'm not in the mood for such hacking. */
if (!(board->has_loaded)) board_load_board(board);
if (!(board->has_saved)) board_save_board(board);
switch (cmd) {
case 15: /* look */
return(board_show_board(board, ch, arg));
case 149: /* write */
isbusy=1;
board_write_msg(board, ch, arg);
isbusy=0;
return 1;
case 63: /* read */
return(board_display_msg(board, ch, arg));
case 66: /* remove */
return(board_remove_msg(board, ch, arg));
default:
return 0;
}
}
int board_general(CHAR_DATA *ch, int cmd, char *arg, struct obj_data *object)
{
struct board *board;
static int isbusy=0;
if (isbusy) return(FALSE);
if (!ch->desc)
return(FALSE);
/* By MS or all NPC's will be trapped at the board */
if(!(board=get_board(ch,TRUE))) return(FALSE);
/* note: I'll let display and remove return 0 if the arg was non-board- */
/* related. Thus, it'll be possible to read other things than the board */
/* while you're in the room. Conceiveably, you could do this for write, */
/* too, but I'm not in the mood for such hacking. */
if (!(board->has_loaded)) board_load_board(board);
if (!(board->has_saved)) board_save_board(board);
switch (cmd)
{
case 15: /* look */
if (being_written(board))
{
send_to_char("Someone else is writing... Wait your turn.\r\n", ch);
return(FALSE);
}
board_load_board(board);
return(board_show_board(board, ch, arg));
case 149: /* write */
isbusy=1;
board_write_msg(board, ch, arg);
isbusy=0;
return 1;
case 63: /* read */
if (being_written(board))
{
send_to_char("Someone else is writing... Wait your turn.\r\n", ch);
return(TRUE);
}
return(board_display_msg(board, ch, arg));
case 66: /* remove */
if (being_written(board))
{
send_to_char("Someone else is writing... Wait your turn.\r\n", ch);
return(FALSE);
}
return(board_remove_msg(board, ch, arg));
default:
return 0;
}
}
void board_write_msg(struct board *brd, CHAR_DATA *ch, char *arg)
{
if (being_written(brd)) {
send_to_char("Someone else is writing... Wait your turn.\r\n", ch);
return;
}
if (brd->msg_num > MAX_MSGS - 1) {
send_to_char("The board is full already.\r\n", ch);
return;
}
/* skip blanks */
for(; isspace(*arg); arg++);
if (!*arg) {
send_to_char("We must have a headline!\r\n", ch);
return;
}
brd->head[brd->msg_num] = (char *)malloc(strlen(arg) + strlen(GET_NAME(ch)) + 4);
/* +4 is for a space and '()' around the character name. */
if (!brd->head[brd->msg_num]) {
error_log("Malloc for board header failed.\r\n");
send_to_char("The board is malfunctioning - sorry.\r\n", ch);
return;
}
strcpy(brd->head[brd->msg_num], arg);
/* Is this clumsy? - four strcat() in a row I mean..*/
strcat(brd->head[brd->msg_num], " (");
strcat(brd->head[brd->msg_num], GET_NAME(ch));
strcat(brd->head[brd->msg_num], ")");
brd->msgs[brd->msg_num] = NULL;
send_to_char("Write your message. Terminate with a @.\r\n\r\n", ch);
act("$n starts to write a message.", TRUE, ch, 0, 0, TO_ROOM);
ch->desc->str = &(brd->msgs[brd->msg_num]);
ch->desc->max_str = MAX_MESSAGE_LENGTH;
ch->desc->flag = &(brd->flag);
brd->msg_num++;
brd->has_saved=0;
brd->flag = 1;
}
int board_remove_msg(struct board *brd,CHAR_DATA *ch, char *arg)
{
int ind, msg;
char buf[256],number[MAX_INPUT_LENGTH];
if (being_written(brd)) {
send_to_char("Someone is writing on it now... Please wait.\r\n", ch);
return(TRUE);
}
one_argument(arg, number);
if (!*number || !isdigit(*number))
return(0);
if (!(msg = atoi(number))) return(0);
if (!brd->msg_num) {
send_to_char("The board is empty!\r\n", ch);
return(1);
}
if (msg < 1 || msg > brd->msg_num)
{
send_to_char("That message exists only in your imagination.\r\n",ch);
return(1);
}
if (!strncmp(brd->head[msg-1], "to:", 3))
{
send_to_char("Private message.\r\n", ch);
if(IS_NPC(ch) ||
(GET_LEVEL(ch)<33 && !isname(GET_NAME(ch), brd->head[msg-1]+3)))
{
send_to_char("And it is for someone else!\r\n", ch);
return(1);
}
}
else if (GET_LEVEL(ch)<31 && !isname(GET_NAME(ch),brd->head[msg-1]) )
{
send_to_char("Only 10th level characters and higher may\r\n", ch);
send_to_char("remove messages which they did not write.\r\n", ch);
return(TRUE);
}
ind = msg;
free(brd->head[--ind]);
sprintf(buf, "$n just removed message %d.", ind + 1);
if (brd->msgs[ind])
free(brd->msgs[ind]);
for (; ind < brd->msg_num -1; ind++) {
brd->head[ind] = brd->head[ind + 1];
brd->msgs[ind] = brd->msgs[ind + 1];
}
brd->msg_num--;
send_to_char("Message removed.\r\n", ch);
act(buf, FALSE, ch, 0, 0, TO_ROOM);
brd->has_saved=0;
board_save_board(brd);
return(1);
}
void board_save_board(struct board *brd)
{
FILE *the_file;
int ind, len;
if (being_written(brd)) return;
log("saving board");
if (!brd->msg_num) {
error_log("No messages to save.\r\n");
brd->has_saved=1;
return;
}
the_file = fopen(brd->filename, "wb");
if (!the_file) {
error_log("Unable to open/create savefile..\r\n");
return;
}
fwrite(&(brd->msg_num), sizeof(int), 1, the_file);
for (ind = 0; ind < brd->msg_num; ind++) {
len = strlen(brd->head[ind]) + 1;
fwrite(&len, sizeof(int), 1, the_file);
fwrite(brd->head[ind], sizeof(char), len, the_file);
len = strlen(brd->msgs[ind]) + 1;
fwrite(&len, sizeof(int), 1, the_file);
fwrite(brd->msgs[ind], sizeof(char), len, the_file);
}
fclose(the_file);
board_fix_long_desc(brd->msg_num, brd->head);
brd->has_saved=1;
return;
}
void board_load_board(struct board *brd)
{
FILE *the_file;
int ind, len = 0;
log("loading board");
board_reset_board(brd);
the_file = fopen(brd->filename, "rb");
if (!the_file) {
error_log("Can't open message file. Board will be empty.\r\n",0);
brd->has_loaded=1;
return;
}
fread(&(brd->msg_num), sizeof(int), 1, the_file);
if (brd->msg_num < 1 || brd->msg_num > MAX_MSGS || feof(the_file)) {
error_log("Board-message file corrupt or nonexistent.\r\n");
fclose(the_file);
brd->msg_num=0;
brd->has_loaded=1;
return;
}
for (ind = 0; ind < brd->msg_num; ind++) {
fread(&len, sizeof(int), 1, the_file);
brd->head[ind] = (char *)malloc(len + 1);
if (!brd->head[ind]) {
error_log("Malloc for board header failed.\r\n");
board_reset_board(brd);
fclose(the_file);
brd->has_loaded=1;
return;
}
fread(brd->head[ind], sizeof(char), len, the_file);
fread(&len, sizeof(int), 1, the_file);
brd->msgs[ind] = (char *)malloc(len + 1);
if (!brd->msgs[ind]) {
error_log("Malloc for board msg failed..\r\n");
board_reset_board(brd);
fclose(the_file);
brd->has_loaded=1;
return;
}
fread(brd->msgs[ind], sizeof(char), len, the_file);
}
fclose(the_file);
board_fix_long_desc(brd->msg_num, brd->head);
brd->has_loaded=1;
return;
}
void board_reset_board(struct board *brd)
{
int ind;
for (ind = 0; ind < MAX_MSGS; ind++)
{
if(brd->head[ind]!=0) free(brd->head[ind]);
if(brd->msgs[ind]!=0) free(brd->msgs[ind]);
}
brd->msg_num = 0;
board_fix_long_desc(0, 0);
return;
}
void error_log(char *str)
{ /* The original error-handling was MUCH */
fputs("Board : ", stderr); /* more competent than the current but */
fputs(str, stderr); /* I got the advice to cut it out..;) */
return;
}
int board_display_msg(struct board *brd,CHAR_DATA *ch, char *arg)
{
char number[MAX_INPUT_LENGTH], buffer[MAX_STRING_LENGTH];
int msg;
if (being_written(brd)) {
send_to_char("Someone is writing on it... Please wait.\r\n",ch);
return(1);
}
one_argument(arg, number);
if (!*number || !isdigit(*number))
return(0);
if (!(msg = atoi(number))) return(0);
if (!brd->msg_num) {
send_to_char("The board is empty!\r\n", ch);
return(1);
}
if (msg < 1 || msg > brd->msg_num) {
send_to_char("That message exists only in your imagination.\r\n",ch);
return(1);
}
if (!strncmp(brd->head[msg-1], "to:", 3))
{
send_to_char("Private message.\r\n", ch);
if(IS_NPC(ch) ||
(GET_LEVEL(ch)<32 && !isname(GET_NAME(ch), brd->head[msg-1]+3)))
{
send_to_char("And it is for someone else!\r\n", ch);
return(1);
}
}
sprintf(buffer, "Message %d : %s\r\n\r\n%s", msg,brd->head[msg - 1],
brd->msgs[msg - 1]);
page_string(ch->desc, buffer, 1);
return(1);
}
void board_fix_long_desc(int num, char *headers[MAX_MSGS])
{
/**** Assign the right value to this pointer..how? ****/
/**** It should point to the bulletin board object ****/
/**** Then make ob.description point to a malloced ****/
/**** space containing itoa(msg_num) and all the ****/
/**** headers. In the format :
This is a bulletin board. Usage : READ/REMOVE <message #>, NOTE <header>
There are 12 messages on the board.
1 : Re : Whatever and something else too.
2 : I don't agree with Rainbird.
3 : Me neither.
4 : Groo got hungry again - bug or sabotage?
Well...something like that..;) ****/
/**** It is always to contain the first line and ****/
/**** the second line will vary in how many notes ****/
/**** the board has. Then the headers and message ****/
/**** numbers will be listed. ****/
return;
}
int board_show_board(struct board *brd,CHAR_DATA *ch, char *arg)
{
int i;
char buf[MAX_STRING_LENGTH], tmp[MAX_INPUT_LENGTH];
one_argument(arg, tmp);
if (!*tmp || !isname(tmp, "board bulletin"))
return(0);
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\nUse WRITE TO: <name> to write a private message.\r\n");
if (!brd->msg_num)
strcat(buf, "The board is empty.\r\n");
else
{
sprintf(buf + strlen(buf), "There are %d messages on the board.\r\n",
brd->msg_num);
for (i = 0; i < brd->msg_num; i++)
sprintf(buf + strlen(buf), "%-2d : %s\r\n", i + 1,
brd->head[i]);
}
if(being_written(brd)) strcat(buf, "It is currently being written on.\r\n");
page_string(ch->desc, buf, 1);
return(1);
}