/* Crimson2 Mud Server
* All source written/copyright Ryan Haksi 1995 *
* This source code is proprietary. Use in whole or in part without
* explicity permission by the author is strictly prohibited
*
* Current email address(es): cryogen@infoserve.net
* Phone number: (604) 591-5295
*
* C4 Script Language written/copyright Cam Lesiuk 1995
* Email: clesiuk@engr.uvic.ca
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#include "crimson2.h"
#include "macro.h"
#include "log.h"
#include "mem.h"
#include "str.h"
#include "extra.h"
#include "file.h"
#include "ini.h"
#include "send.h"
#include "thing.h"
#include "index.h"
#include "edit.h"
#include "history.h"
#include "socket.h"
#include "social.h"
#include "base.h"
#include "object.h"
#include "board.h"
#define BOARD_DEFAULT_TEXT "<Message Currently being Written>\n"
BOARDLIST *boardList = NULL;
BYTE boardReadLog;
LWORD boardListMax = 0;
LWORD boardListByte = BOARD_LIST_SIZE;
FLAG boardListFlag;
BYTE *bFlagList[] = {
"UNSAVED",
""
};
/*
* Sorts by sequence number's
*/
/* returns same values as strcmp */
INDEXPROC(BoardCompareProc) { /* BYTE IndexProc(INDEX *i, void *index1, void *index2) */
if (BoardMsg(index1)->bSequence < BoardMsg(index2)->bSequence)
return -1;
if (BoardMsg(index1)->bSequence > BoardMsg(index2)->bSequence)
return 1;
return 0;
}
/* END - INTERNAL FUNCTIONS */
void BoardInit(void) {
FILE *boardFile;
BYTE buf[256];
BYTE tmp[128];
LWORD virtual;
LWORD board;
LWORD boardNum = 0;
boardReadLog = INILWordRead("crimson2.ini", "boardReadLog", 0);
boardFile = fopen("board/board.tbl", "rb");
if (!boardFile) {
Log(LOG_BOOT, "Unable to read board.tbl file, killing server\n");
exit(ERROR_BADFILE);
}
/* report on initialization */
sprintf(buf, "Initial boardList allocation of %ld entries\n", boardListByte/sizeof(BOARDLIST));
Log(LOG_BOOT, buf);
/* note allways keep totally null entry at end of board list so I can
use it as a type array */
REALLOC("BoardInit(board.c): boardList reallocation\n", boardList, BOARDLIST, boardListMax+2, boardListByte);
memset(&boardList[boardListMax+1], 0, sizeof(BOARDLIST)); /* init to zeros */
/* okay we opened it up so read it.... */
while (!feof(boardFile)) {
fscanf(boardFile, " %s", tmp); /* get file name */
if (tmp[0] == '$' || feof(boardFile))
break; /* Dikumud file format EOF character */
if (tmp[0] != '#' && tmp[0] != '*') { /* ignore comments */
/* note allways keep totally null entry at end of board list so I can
use it as a type array */
strcpy(boardList[boardListMax].bFileName, tmp);
virtual = -1;
fscanf(boardFile, " %ld ", &virtual);
board = BoardCreate(tmp, virtual);
fscanf(boardFile, " "); /* strip out spaces */
boardList[board].bEditor = FileStrRead(boardFile);
/* load the board file for this board */
BoardRead(board);
BoardMsgDeleteCheck(board);
boardNum+=boardList[board].bIndex.iNum;
if (boardReadLog) {
sprintf(buf,
"Read Board:[%-18s] Msg[%5ld] [%s]\n",
boardList[board].bFileName,
boardList[board].bIndex.iNum,
boardList[board].bEditor->sText
);
Log(LOG_BOOT, buf);
}
} else { /* read comment */
fgets(buf, 256, boardFile);
}
}
/* all done close up shop */
fclose(boardFile);
sprintf(buf,
"Board Totals: Boards[%2ld] Messages[%5ld]\n\n",
boardListMax,
boardNum
);
Log(LOG_BOOT, buf);
}
void BoardTableWrite(void) {
FILE *boardFile;
LWORD board;
boardListFlag = 0; /* mark it as up to date */
boardFile = fopen("board/board.tbl", "wb");
if (!boardFile) {
Log(LOG_BOOT, "Unable to write board.tbl file\n");
PERROR("BoardTableWrite");
return;
}
/* okay we opened it up so read it.... */
for(board=0; board<boardListMax; board++) {
fprintf(boardFile, "%-15s %5ld ", boardList[board].bFileName, boardList[board].bVirtual);
FileStrWrite(boardFile, boardList[board].bEditor);
}
fprintf(boardFile, "$\n");
/* all done close up shop */
fclose(boardFile);
}
void BoardRead(BYTE board) {
FILE *boardFile;
BYTE buf[256];
BYTE tmp[256];
BOARDMSG *boardMsg;
sprintf(buf, "board/%s.brd", boardList[board].bFileName);
boardFile = fopen(buf, "rb");
if (!boardFile) {
sprintf(buf, "Unable to read board/%s, skipping\n", boardList[board].bFileName);
Log(LOG_BOOT, buf);
boardList[board].bName = STRCREATE(boardList[board].bFileName);
boardList[board].bEditor = STRCREATE("");
boardList[board].bMax = 30;
boardList[board].bMinLevel = 1;
sprintf(buf, "boardIndex/%s", boardList[board].bFileName);
IndexInit( &boardList[board].bIndex, 32*sizeof(BOARDMSG*), buf, 0 );
return;
}
boardList[board].bName = FileStrRead(boardFile);
boardList[board].bFlag = FileFlagRead(boardFile, bFlagList);
fscanf(boardFile, " %hd %hd ", &boardList[board].bMax, &boardList[board].bMinLevel);
sprintf(buf, "boardIndex/%s", boardList[board].bFileName);
IndexInit( &boardList[board].bIndex, boardList[board].bMax*sizeof(BOARDMSG*), buf, 0 );
/* okay we opened it up so read it.... */
while (!feof(boardFile)) {
fgets(tmp, 256, boardFile); /* get filename */
if (tmp[0] == '$' || feof(boardFile))
break; /* Dikumud file format EOF character */
if (tmp[0] == '#') { /* ignore comments */
MEMALLOC(boardMsg, BOARDMSG, BOARDMSG_ALLOC_SIZE);
boardMsg->bAuthor = FileStrRead(boardFile);
boardMsg->bTitle = FileStrRead(boardFile);
boardMsg->bText = FileStrRead(boardFile);
fscanf(boardFile, " %lu ", &boardMsg->bCreateTime);
fscanf(boardFile, " %lu ", &boardMsg->bLastRead);
fscanf(boardFile, " %hd ", &boardMsg->bReplyTo);
boardMsg->bSequence = boardList[board].bIndex.iNum;
if (boardMsg->bReplyTo >= boardMsg->bSequence)
boardMsg->bReplyTo = BOARD_NOTREPLY;
if (boardMsg->bReplyTo < 0)
boardMsg->bReplyTo = BOARD_NOTREPLY;
IndexInsert(&boardList[board].bIndex, boardMsg, BoardCompareProc);
} else {
sprintf(buf, "Illegal character found near line:\n");
strcat(tmp, "\n");
Log(LOG_ERROR, buf);
Log(LOG_ERROR, tmp);
}
}
/* all done close up shop */
fclose(boardFile);
}
BYTE BoardWrite(BYTE board) {
LWORD i;
FILE *boardFile;
BYTE buf[256];
BITCLR(boardList[board].bFlag, B_UNSAVED);
sprintf(buf, "board/%s.brd", boardList[board].bFileName);
boardFile = fopen(buf, "wb");
if (!boardFile) {
sprintf(buf, "Unable to write file board/%s.brd\n", boardList[board].bFileName);
Log(LOG_ERROR, buf);
return FALSE;
}
FileStrWrite(boardFile, boardList[board].bName);
FileFlagWrite(boardFile, boardList[board].bFlag, bFlagList, '\n');
fprintf(boardFile, "%hd %hd\n", boardList[board].bMax, boardList[board].bMinLevel);
for (i=0; i<boardList[board].bIndex.iNum; i++) {
fprintf(boardFile, "#%hd\n", BoardMsg(boardList[board].bIndex.iThing[i])->bSequence);
FileStrWrite(boardFile, BoardMsg(boardList[board].bIndex.iThing[i])->bAuthor);
FileStrWrite(boardFile, BoardMsg(boardList[board].bIndex.iThing[i])->bTitle);
FileStrWrite(boardFile, BoardMsg(boardList[board].bIndex.iThing[i])->bText);
fprintf(boardFile, "%lu %lu %hd\n",
BoardMsg(boardList[board].bIndex.iThing[i])->bCreateTime,
BoardMsg(boardList[board].bIndex.iThing[i])->bLastRead,
BoardMsg(boardList[board].bIndex.iThing[i])->bReplyTo);
}
fprintf(boardFile, "$\n");
fclose(boardFile);
return TRUE;
}
/* ouch this is gonna be a costly proc to execute - insertion sort it...*/
LWORD BoardCreate(BYTE *fileName, LWORD virtual){
LWORD board;
BYTE buf[256];
board = 0;
while ( (board < boardListMax)
&& (virtual > boardList[board].bVirtual)
) board++;
/* note allways keep totally null entry at end of board list so I can
use it as a type array */
REALLOC("BoardInit(board.c): boardList reallocation\n", boardList, BOARDLIST, boardListMax+2, boardListByte);
memset(&boardList[boardListMax+1], 0, sizeof(BOARDLIST)); /* init to zeros */
boardListMax++;
/* can use the os memmove instead, thought it was giving me grief but it was something else */
MemMoveHigher(&boardList[board+1], &boardList[board], sizeof(BOARDLIST)*(boardListMax-board+1));
memset(&boardList[board], 0, sizeof(BOARDLIST)); /* init to zeros */
strcpy(boardList[board].bFileName, fileName);
boardList[board].bEditor = STRCREATE("");
boardList[board].bName = STRCREATE(fileName);
boardList[board].bVirtual = virtual;
boardList[board].bMax = 23; /* 23 msgs by default */
sprintf(buf, "boardIndex/%s", boardList[board].bFileName);
IndexInit( &boardList[board].bIndex, boardList[board].bMax*sizeof(BOARDMSG*), buf, 0 );
return board;
}
LWORD BoardOf(LWORD virtual) {
LWORD i;
/* since list is sorted we can actually binary search */
for (i=0; i<boardListMax; i++) {
if (virtual == boardList[i].bVirtual)
return i;
if (virtual < boardList[i].bVirtual)
return -1;
}
return -1;
}
/* delete one as necessary to keep under max */
void BoardMsgDeleteCheck(BYTE board) {
LWORD i;
LWORD lru; /* least recently used strategy */
BOARDMSG *boardMsg;
/* First a bit of editor theory to explain how cancelling a message works:
* while a STR is being edited in the editor its given a private STR struct
* with sNum = STR_PRIVATE, that points to the shared sText buffer. Upon
* exiting the editor the private STR struct is free'd and a global one
* restored/created. Therefore if the sNum is still STR_PRIVATE then it is
* still being edited, and we shouldnt touch it!
*/
/* Check for Cancelled messages and turf them too */
for (i=0; i<boardList[board].bIndex.iNum; ) {
boardMsg = BoardMsg(boardList[board].bIndex.iThing[i]);
if (boardMsg->bText->sNum != STR_PRIVATE
&& !strcmp(BOARD_DEFAULT_TEXT, boardMsg->bText->sText)) {
BoardMsgDelete(board, i);
} else {
i++;
}
}
/* Turf excess messages */
while ( boardList[board].bIndex.iNum > 0
&& boardList[board].bIndex.iNum > boardList[board].bMax) {
lru = 0;
for (i=0; i<boardList[board].bIndex.iNum; i++) {
if (BoardMsg(boardList[board].bIndex.iThing[i])->bCreateTime < BoardMsg(boardList[board].bIndex.iThing[lru])->bCreateTime)
lru = i;
}
BoardMsgDelete(board, lru);
}
}
BOARDMSG *BoardMsgCreate(BYTE board, THING *thing, BYTE *title, WORD replyTo){
BOARDMSG *boardMsg;
BOARDMSG *root;
BOARDMSG *msg;
LWORD i;
LWORD insert;
/* Validate replyTo so it cant crash mud if board file corrupt */
BOUNDSET(-1, replyTo, boardList[board].bIndex.iNum-1);
MEMALLOC(boardMsg, BOARDMSG, BOARDMSG_ALLOC_SIZE);
boardMsg->bAuthor = StrAlloc(thing->tSDesc);
boardMsg->bTitle = STRCREATE(title);
boardMsg->bText = STRCREATE(BOARD_DEFAULT_TEXT);
boardMsg->bCreateTime = time(0);
boardMsg->bLastRead = time(0);
boardMsg->bReplyTo = replyTo;
boardMsg->bNotify = TRUE;
if (replyTo == BOARD_NOTREPLY) {
/* thank god no insertion required */
insert = boardList[board].bIndex.iNum;
} else {
/* find where to insert the reply */
insert = replyTo+1;
while (insert < boardList[board].bIndex.iNum) {
root = BoardMsg(boardList[board].bIndex.iThing[insert]);
while (root->bReplyTo != BOARD_NOTREPLY && root->bReplyTo!=replyTo) {
root = BoardMsg(boardList[board].bIndex.iThing[root->bReplyTo]);
}
if (root->bReplyTo==replyTo)
insert++;
else
break;
}
}
/* insert as required */
if (insert < boardList[board].bIndex.iNum) {
/* re-arrange sequence numbers to leave a gap for the new message */
for (i=0; i<boardList[board].bIndex.iNum; i++) {
msg = BoardMsg(boardList[board].bIndex.iThing[i]);
if (msg->bReplyTo >= insert)
msg->bReplyTo++;
if (msg->bSequence >= insert)
msg->bSequence++;
}
}
boardMsg->bSequence = insert;
IndexInsert(&boardList[board].bIndex, boardMsg, BoardCompareProc);
/*
* Make sure you set this when editing is complete
* BITSET(boardList[board].bFlag, B_UNSAVED);
*/
return boardMsg;
}
void BoardMsgDelete(LWORD board, LWORD msgNum){
LWORD i;
BOARDMSG *boardMsg;
/* fix up reply to's and sequence numbers */
for (i=0; i<boardList[board].bIndex.iNum; i++) {
boardMsg = BoardMsg(boardList[board].bIndex.iThing[i]);
if (boardMsg->bReplyTo == msgNum) {
boardMsg->bReplyTo = BoardMsg(boardList[board].bIndex.iThing[msgNum])->bReplyTo;
} else if (boardMsg->bReplyTo > msgNum) {
boardMsg->bReplyTo--;
}
if (boardMsg->bSequence > msgNum)
boardMsg->bSequence--;
}
IndexDelete(&boardList[board].bIndex, boardList[board].bIndex.iThing[msgNum], BoardCompareProc);
BITSET(boardList[board].bFlag, B_UNSAVED);
}
BYTE *BoardMsgGetHeader(BYTE *buf, LWORD board, LWORD msgNum) {
LWORD reply;
BOARDMSG *boardMsg;
struct tm *theTm;
boardMsg = BoardMsg(boardList[board].bIndex.iThing[msgNum]);
theTm = localtime(&boardMsg->bCreateTime);
sprintf(buf, "^c%02d/%02d ", theTm->tm_mon+1, theTm->tm_mday);
reply = boardMsg->bReplyTo;
while (reply != BOARD_NOTREPLY) {
strcat(buf, " ");
reply = BoardMsg(boardList[board].bIndex.iThing[reply])->bReplyTo;
}
sprintf(buf+strlen(buf),
"^g%s ^Y(^y%s^Y)\n",
boardMsg->bTitle->sText,
boardMsg->bAuthor->sText);
return buf;
}
void BoardNotify(LWORD board) {
LWORD i;
LWORD beep;
beep = FALSE;
for (i=0; i<boardList[board].bIndex.iNum; i++) {
if ( BoardMsg(boardList[board].bIndex.iThing[i])->bText->sNum != STR_PRIVATE
&& BoardMsg(boardList[board].bIndex.iThing[i])->bNotify) {
BoardMsg(boardList[board].bIndex.iThing[i])->bNotify = FALSE;
SendChannel("^:BRD: [", NULL, SP_CHANBOARD);
SendChannel(boardList[board].bName->sText, NULL, SP_CHANBOARD);
SendChannel("] ", NULL, SP_CHANBOARD);
SendChannel(BoardMsg(boardList[board].bIndex.iThing[i])->bAuthor->sText, NULL, SP_CHANBOARD);
SendChannel(" has written a note\n", NULL, SP_CHANBOARD);
/* search all objectIndex for board objects and emit a beep? */
beep = TRUE;
}
}
if (beep) {
for (i=0; i<objectIndex.iNum; i++) {
/* Check otype */
if (Obj(objectIndex.iThing[i])->oTemplate->oType != OTYPE_BOARD) continue;
/* check board vnum */
if ( OBJECTGETFIELD(objectIndex.iThing[i], OF_BOARD_BVIRTUAL) != boardList[board].bVirtual) continue;
/* Skip it if its not in a room/player */
if (!Base(objectIndex.iThing[i])->bInside) continue;
if (Base(objectIndex.iThing[i])->bInside->tType==TTYPE_WLD) {
SendAction("^b$n beeps quietly\n",
objectIndex.iThing[i], NULL, SEND_ROOM|SEND_AUDIBLE|SEND_VISIBLE|SEND_CAPFIRST);
}
if (Base(objectIndex.iThing[i])->bInside->tType>=TTYPE_CHARACTER) {
SendAction("^b$N beeps quietly\n",
Base(objectIndex.iThing[i])->bInside, objectIndex.iThing[i], SEND_SRC|SEND_AUDIBLE|SEND_VISIBLE|SEND_CAPFIRST);
}
}
}
}
void BoardIdle(void) {
LWORD i;
if (boardListFlag)
BoardTableWrite();
for (i=0; i<boardListMax; i++) {
if (BIT(boardList[i].bFlag, B_UNSAVED)) {
/* Notification */
BoardNotify(i);
BoardMsgDeleteCheck(i);
BoardWrite(i);
}
}
}
void BoardShow(BYTE board, THING *thing) {
BYTE buf[256];
LWORD i;
/*
* turf any over the limit or cancelled messages
* Actually, this should never do anything here,
* The call in BoardIdle should catch it and do it there
* or at boot time it will be caught by the call in BoardRead
*/
BoardMsgDeleteCheck(board);
sprintf(buf,
"^gBoard Title:^G[^b%s^G]\n",
boardList[board].bName->sText);
SendThing(buf, thing);
for (i=0; i<boardList[board].bIndex.iNum; i++) {
/* sprintf(buf, "^r%2ld. ", i+1); */
/* realized it would be better if we can observe seq.# screwups */
sprintf(buf, "^r%2hd. ", BoardMsg(boardList[board].bIndex.iThing[i])->bSequence+1);
SendThing(buf, thing);
BoardMsgGetHeader(buf, board, i);
SendThing(buf, thing);
}
sprintf(buf, "^g%ld ^pMessages shown.\n", boardList[board].bIndex.iNum);
SendThing(buf, thing);
}
void BoardShowMessage(BYTE board, LWORD msgNum, THING *thing) {
BOARDMSG *boardMsg;
struct tm *theTm;
BYTE buf[256];
/*
* turf any over the limit or cancelled messages
* Actually, this should never do anything here,
* The call in BoardIdle should catch it and do it there
* or at boot time it will be caught by the call in BoardRead
*/
BoardMsgDeleteCheck(board);
boardMsg = BoardMsg(boardList[board].bIndex.iThing[msgNum]);
sprintf(buf, "^gBoard Title: ^G[^c%s^G]\n", boardList[board].bName->sText);
SendThing(buf, thing);
theTm = localtime(&boardMsg->bCreateTime);
sprintf(buf, "^gCreated: ^G[^C%02d^b/^C%02d^G]\n",
theTm->tm_mon+1,
theTm->tm_mday);
SendThing(buf, thing);
theTm = localtime(&boardMsg->bLastRead);
sprintf(buf, "^gLast Read: ^G[^C%02d^b/^C%02d^G]\n",
theTm->tm_mon+1,
theTm->tm_mday);
SendThing(buf, thing);
sprintf(buf, "^gMessage Number: ^G[^C%ld^b/^C%ld^G]\n", msgNum+1, boardList[board].bIndex.iNum);
SendThing(buf, thing);
sprintf(buf, "^gAuthor: ^G[^w%s^G]\n",
boardMsg->bAuthor->sText);
SendThing(buf, thing);
SendThing( "^gMessage Title: ^G[^c", thing);
SendThing(boardMsg->bTitle->sText, thing);
SendThing("^G]\n", thing);
SendThing("^G------------------------------------------------------------------------------\n^b", thing);
SendThing(boardMsg->bText->sText, thing);
SendThing("\n", thing);
boardMsg->bLastRead = time(0);
}