/**************************************************************************
* Original Diku Mud copyright (C) 1990, 1991 by Sebastian Hammer, *
* Michael Seifert, Hans Henrik St{rfeldt, Tom Madsen, and Katja Nyboe. *
* *
* Merc Diku Mud improvements copyright (C) 1992, 1993 by Michael *
* Chastain, Michael Quan, and Mitchell Tse. *
* *
* In order to use any part of this Merc Diku Mud, you must comply with *
* both the original Diku license in 'license.doc' as well the Merc *
* license in 'license.txt'. In particular, you may not remove either of *
* these copyright notices. *
* *
* Much time and thought has gone into this software and you are *
* benefiting. We hope that you share your changes too. What goes *
* around, comes around. *
***************************************************************************
* ROM 2.4 is copyright 1993-1998 Russ Taylor *
* ROM has been brought to you by the ROM consortium *
* Russ Taylor (rtaylor@hypercube.org) *
* Gabrielle Taylor (gtaylor@hypercube.org) *
* Brian Moore (zump@rom.org) *
* By using this code, you have agreed to follow the terms of the *
* ROM license, in the file Rom24/doc/rom.license *
***************************************************************************
* Note Board system, (c) 1995-96 Erwin S. Andreasen, erwin@andreasen.org *
* Basically, the notes are split up into several boards. The boards do *
* not exist physically, they can be read anywhere and in any position. *
* Each of the note boards has its own file. Each of the boards can have *
* its own "rights": who can read/write. *
* Each character has an extra field added, namele the timestamp of the *
* last note read by him/her on a certain board. *
* The note entering system is changed too, making it more interactive. *
* entering a note, a character is put AFK and into a special CON_ state. *
* Everything typed goes into the note. *
* For the immortals it is possible to purge notes based on age. An *
* archive options is available which moves the notes older than X days *
* into a special board. The file of this board should then be moved into *
* some other directory during e.g. the startup script and perhaps renamed *
* depending on date. *
* Note that write_level MUST be >= read_level or else there will be *
* strange output in certain functions. *
* Board DEFAULT_BOARD must be at least readable by *everyone*. *
***************************************************************************
* 1stMud ROM Derivative (c) 2001-2004 by Markanth *
* http://www.firstmud.com/ <markanth@firstmud.com> *
* By using this code you have agreed to follow the term of *
* the 1stMud license in ../doc/1stMud/LICENSE *
***************************************************************************/
#include "merc.h"
#include "interp.h"
#include "recycle.h"
#include "tables.h"
#include "data_table.h"
#define L_SUP (MAX_LEVEL - 1)
Proto(const char *format_sender, (NoteData *));
Proto(const char *format_to_list, (NoteData *));
BoardData boards[MAX_BOARD] = {
{"Announce", "Announcements from Immortals", 0, L_SUP, "all", DEF_NORMAL,
60, NULL, NULL, BOARD_NONE}
,
{"General", "General discussion", 0, 2, "all", DEF_INCLUDE, 21, NULL,
NULL,
BOARD_NONE}
,
{"Ideas", "Suggestion for improvement", 0, 2, "all", DEF_NORMAL, 60, NULL,
NULL,
BOARD_NONE}
,
{"Bugs", "Typos, bugs, errors", 0, 1, "imm", DEF_NORMAL, 60, NULL, NULL,
BOARD_NONE}
,
{"Games", "Mud Games, Global Quests, ect.", 0, 1, "all", DEF_NORMAL,
10, NULL, NULL,
BOARD_NONE}
,
{"Personal", "Personal messages", 0, 1, "all", DEF_EXCLUDE, 28, NULL,
NULL,
BOARD_NOWEB}
,
{"Immortal", "Immortals only", LEVEL_IMMORTAL, LEVEL_IMMORTAL, "imm",
DEF_INCLUDE, 21, NULL, NULL, BOARD_NOWEB}
};
const char *szFinishPrompt =
"({WC{x)ontinue, ({WV{x)iew, ({WP{x)ost or ({WF{x)orget it?";
long last_note_stamp = 0;
void finish_note(BoardData * board, NoteData * note)
{
if (!NullStr(note->reply_text))
replace_strf(¬e->text, "%s" NEWLINE "%s", note->reply_text,
note->text);
if (last_note_stamp >= current_time)
note->date_stamp = ++last_note_stamp;
else
{
note->date_stamp = current_time;
last_note_stamp = current_time;
}
Link(note, board->note, next, prev);
SetBit(board->flags, BOARD_CHANGED);
#ifdef HAVE_SENDMAIL
if (!NullStr(note->email_addr))
{
FILE *mail;
if ((mail = popen("sendmail -t", "w")) != NULL)
{
fprintf(mail, "To: %s\n", format_to_list(note));
fprintf(mail, "From: %s\n", format_sender(note));
if (!NullStr(note->reply_addr))
fprintf(mail, "Reply-to: %s\n", note->reply_addr);
fprintf(mail, "X-Mailer: %s Mailer\n", mud_info.name);
fprintf(mail, "Subject: %s\n\n", note->subject);
fprintf(mail, note->text);
pclose(mail);
}
}
#endif
}
int board_number(const BoardData * board)
{
int i;
for (i = 0; i < MAX_BOARD; i++)
if (board == &boards[i])
return i;
return -1;
}
Lookup_Fun(board_lookup)
{
int i;
for (i = 0; i < MAX_BOARD; i++)
if (!str_cmp(boards[i].short_name, name))
return i;
return -1;
}
static void unlink_note(BoardData * board, NoteData * note)
{
UnLink(note, board->note, next, prev);
}
static NoteData *find_note(CharData * ch, BoardData * board, int num)
{
int count = 0;
NoteData *p;
for (p = board->note_first; p; p = p->next)
if (++count == num)
break;
if ((count == num) && is_note_to(ch, p))
return p;
else
return NULL;
}
const char *format_to_list(NoteData * note)
{
if (!NullStr(note->email_addr))
{
if (!NullStr(note->to_list))
{
static char buf[MSL];
sprintf(buf, "%s <%s>", note->to_list, note->email_addr);
return buf;
}
else
return note->email_addr;
}
return note->to_list;
}
const char *format_sender(NoteData * note)
{
if (!NullStr(note->reply_addr))
{
if (!NullStr(note->sender))
{
static char buf[MSL];
sprintf(buf, "%s <%s>", note->sender, note->reply_addr);
return buf;
}
else
return note->reply_addr;
}
return note->sender;
}
static void show_note_to_char(CharData * ch, NoteData * note, int num)
{
Buffer *buffer;
buffer = new_buf();
bprintlnf(buffer,
NEWLINE "{YBoard{x: %s" NEWLINE "[{x%4d{x] {Y%s{x: {g%s{x"
NEWLINE "{YDate{x: %s" NEWLINE "{YTo{x: %s",
ch->pcdata->board->short_name, num, format_sender(note),
note->subject, note->date, format_to_list(note));
bprintlnf(buffer, "{g%s{x" NEWLINE "%s{g%s{x", draw_line(ch, "{W={w=", 0),
note->text, draw_line(ch, "{W={w=", 0));
sendpage(ch, buf_string(buffer));
free_buf(buffer);
}
static void check_notes(BoardData * b)
{
NoteData *p, *p_next;
for (p = b->note_first; p; p = p_next)
{
p_next = p->next;
if (p->expire < current_time)
{
UnLink(p, b->note, next, prev);
free_note(p);
SetBit(b->flags, BOARD_CHANGED);
}
}
}
TableSave_Fun(rw_note_data)
{
char file[MIL];
BoardData *board;
int i;
for (i = 0; i < MAX_BOARD; i++)
{
board = &boards[i];
if (type == act_write && !IsSet(board->flags, BOARD_CHANGED))
continue;
sprintf(file, NOTE_DIR "%s", board->short_name);
RemBit(board->flags, BOARD_CHANGED);
logf("%s %s data...", type == act_read ? "Loading" : "Saving",
board->short_name);
rw_sublist(type, file, NoteData, board, note);
if (type == act_read)
check_notes(board);
}
}
bool is_note_to(CharData * ch, NoteData * note)
{
if (!str_cmp(ch->name, note->sender))
return true;
if (is_ignoring(ch, note->to_list, IGNORE_NOTES))
return false;
if (is_full_name("all", note->to_list))
return true;
if (IsImmortal(ch)
&& (is_full_name("imm", note->to_list)
|| is_full_name("imms", note->to_list)
|| is_full_name("immortal", note->to_list)
|| is_full_name("god", note->to_list)
|| is_full_name("gods", note->to_list)
|| is_full_name("immortals", note->to_list)))
return true;
if ((get_trust(ch) == MAX_LEVEL)
&& (is_full_name("imp", note->to_list)
|| is_full_name("imps", note->to_list)
|| is_full_name("implementor", note->to_list)
|| is_full_name("implementors", note->to_list)))
return true;
if (is_clan(ch)
&& (is_exact_name("clan", note->to_list)
|| is_exact_name(CharClan(ch)->name, note->to_list)))
return true;
if (is_full_name(ch->name, note->to_list))
return true;
if (!NullStr(ch->pcdata->email))
{
if (!NullStr(note->email_addr)
&& is_name(ch->pcdata->email, note->email_addr))
return true;
if (!NullStr(note->reply_addr)
&& is_name(ch->pcdata->email, note->reply_addr))
return true;
}
if (is_number(note->to_list) && get_trust(ch) >= atoi(note->to_list))
return true;
return false;
}
int unread_notes(CharData * ch, BoardData * board)
{
NoteData *note;
time_t last_read;
int count = 0;
if (board->read_level > get_trust(ch))
return BOARD_NOACCESS;
last_read = ch->pcdata->last_note[board_number(board)];
for (note = board->note_first; note; note = note->next)
if (is_note_to(ch, note)
&& ((long) last_read < (long) note->date_stamp))
count++;
return count;
}
NoteData *last_note(CharData * ch, BoardData * board)
{
NoteData *note;
if (board->read_level > get_trust(ch))
return NULL;
for (note = board->note_last; note; note = note->prev)
if (is_note_to(ch, note))
return note;
return NULL;
}
Do_Fun(do_ncheck)
{
int i, count = 0, unread = 0;
for (i = 0; i < MAX_BOARD; i++)
{
unread = unread_notes(ch, &boards[i]);
if (unread != BOARD_NOACCESS && !ch->pcdata->unsubscribed[i])
count += unread;
}
if (count < 1)
chprintln(ch, "You have no new notes on the board.");
else
chprintlnf(ch, "You have %s on the board. Type 'board'.",
intstr(count, "note"));
if (ch->pcdata->in_progress)
chprintln(ch,
"You have a note in progress. Type 'NOTE WRITE' to continue it.");
}
const char *format_recipient(CharData * ch, const char *names)
{
if (is_exact_name("clan", names) && is_clan(ch))
return str_rep(names, "clan", CharClan(ch)->name);
return names;
}
static void begin_note(CharData * ch)
{
chprintlnf(ch,
"Enter text. Type {W%cq{x or {W@{x on an empty line to end the note, or {W%ch{x for help.",
StrEdKey(ch), StrEdKey(ch));
chprintln(ch, draw_line(ch, "{W={w=", 0));
}
static Do_Fun(do_nwrite)
{
if (IsNPC(ch))
return;
if (get_trust(ch) < ch->pcdata->board->write_level)
{
chprintln(ch, "You cannot post notes on this board.");
return;
}
if (get_trust(ch) < ch->pcdata->board->write_level
|| ch->pcdata->board->force_type == DEF_READONLY)
{
chprintln(ch, "You cannot post notes on this board.");
return;
}
if (ch->pcdata->in_progress && (!ch->pcdata->in_progress->text))
{
chprintln(ch,
"Note in progress cancelled because you did not manage to write any text "
NEWLINE "before losing link.");
free_note(ch->pcdata->in_progress);
ch->pcdata->in_progress = NULL;
}
if (!ch->pcdata->in_progress)
{
ch->pcdata->in_progress = new_note();
replace_str(&ch->pcdata->in_progress->sender, ch->name);
replace_str(&ch->pcdata->in_progress->date, str_time(-1, -1, NULL));
}
act("{G$n starts writing a note.{x", ch, NULL, NULL, TO_ROOM);
chprintlnf(ch, "You are now %s a new note on the {W%s{x board.",
ch->pcdata->in_progress->text ? "continuing" : "posting",
ch->pcdata->board->short_name);
chprintlnf(ch, "{YFrom{x: %s", ch->name);
if (!ch->pcdata->in_progress->text)
{
switch (ch->pcdata->board->force_type)
{
case DEF_NORMAL:
chprintlnf(ch,
"If you press Return, default recipient \"{W%s{x\" will be chosen.",
ch->pcdata->board->names);
break;
case DEF_INCLUDE:
chprintlnf(ch,
"The recipient list MUST include \"{W%s{x\". If not, it will be added automatically.",
ch->pcdata->board->names);
break;
case DEF_EXCLUDE:
chprintlnf(ch,
"The recipient of this note must NOT include: \"{W%s{x\".",
ch->pcdata->board->names);
chprintln(ch,
"You may use a valid email address on this board to send this note as an email.");
break;
default:
break;
}
chprintln(ch, "{YTo{x: ");
ch->desc->connected = CON_NOTE_TO;
}
else
{
chprintlnf(ch,
"{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE
"{YSubject{x: %s", format_to_list(ch->pcdata->in_progress),
str_time(ch->pcdata->in_progress->expire, GetTzone(ch),
NULL), ch->pcdata->in_progress->subject);
chprintln(ch, "{GYour note so far:{x");
chprint(ch, ch->pcdata->in_progress->text);
begin_note(ch);
ch->desc->connected = CON_NOTE_TEXT;
}
}
static Do_Fun(do_nforward)
{
NoteData *p;
char buf[MSL];
char arg[MIL];
const char *names;
if (IsNPC(ch))
return;
if (get_trust(ch) < 2)
{
chprintln(ch, "You can't seem to write a note.");
return;
}
argument = one_argument(argument, arg);
if (NullStr(arg) || !is_number(arg) || atoi(arg) < 1)
{
chprintln(ch, "Forward which note?");
return;
}
p = find_note(ch, ch->pcdata->board, atoi(arg));
if (!p)
{
chprintln(ch, "No such note.");
return;
}
if (NullStr(argument))
{
chprintln(ch, "Forward the note to who?");
return;
}
if (get_trust(ch) < ch->pcdata->board->write_level)
{
chprintln(ch, "You cannot forward notes on this board.");
return;
}
if (ch->pcdata->in_progress)
{
if (!ch->pcdata->in_progress->text)
{
chprintln(ch,
"Note in progress cancelled because you did not manage to write any text "
NEWLINE "before losing link.");
free_note(ch->pcdata->in_progress);
ch->pcdata->in_progress = NULL;
}
else
{
chprintlnf(ch,
"You already have a note in progress. type 'note write' to continue it.");
return;
}
}
ch->pcdata->in_progress = new_note();
replace_str(&ch->pcdata->in_progress->sender, ch->name);
replace_str(&ch->pcdata->in_progress->date, str_time(-1, -1, NULL));
act("{G$n starts writing a note.{x", ch, NULL, NULL, TO_ROOM);
chprintlnf(ch, "{YFrom{x: %s", GetName(ch));
names = format_recipient(ch, ch->pcdata->board->names);
switch (ch->pcdata->board->force_type)
{
case DEF_INCLUDE:
if (!is_full_name(argument, names))
{
sprintf(buf, "%s %s", names, argument);
chprintlnf(ch,
"You did not include the default recipient '%s' and was included automatically.",
names);
}
else
strcpy(buf, argument);
break;
default:
case DEF_NORMAL:
strcpy(buf, argument);
break;
case DEF_EXCLUDE:
if (is_full_name(names, argument))
{
chprintlnf(ch,
"You are not allowed to send notes to %s on this board. Try again.",
names);
return;
}
if (strchr(argument, '@'))
{
const char *invalid = is_invalid_email(argument);
if (invalid)
{
chprintln(ch, invalid);
chprint(ch,
"You must specify a valid email address. (ie. bob@aol.com). Try again.");
return;
}
replace_str(&ch->pcdata->in_progress->email_addr, argument);
if (!NullStr(ch->pcdata->email))
replace_str(&ch->pcdata->in_progress->reply_addr,
ch->pcdata->email);
}
break;
}
replace_str(&ch->pcdata->in_progress->to_list, buf);
replace_strf(&ch->pcdata->in_progress->subject, "FWD: %s", p->subject);
ch->pcdata->in_progress->expire =
current_time + ch->pcdata->board->purge_days * DAY;
chprintlnf(ch,
"{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE
"{YSubject{x: %s", format_to_list(ch->pcdata->in_progress),
str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL),
ch->pcdata->in_progress->subject);
replace_str(&ch->pcdata->in_progress->reply_text, replines(p->text));
sendpage(ch, ch->pcdata->in_progress->reply_text);
begin_note(ch);
ch->desc->connected = CON_NOTE_TEXT;
}
static Do_Fun(do_nreply)
{
NoteData *p;
char buf[MSL];
char arg[MIL];
const char *names;
if (IsNPC(ch))
return;
if (get_trust(ch) < 2)
{
chprintln(ch, "You can't seem to write a note.");
return;
}
argument = one_argument(argument, arg);
if (NullStr(arg) || !is_number(arg) || atoi(arg) < 1)
{
chprintln(ch, "Reply to which note?");
return;
}
p = find_note(ch, ch->pcdata->board, atoi(arg));
if (!p)
{
chprintln(ch, "No such note.");
return;
}
if (get_trust(ch) < ch->pcdata->board->write_level)
{
chprintln(ch, "You cannot reply to notes on this board.");
return;
}
if (ch->pcdata->in_progress)
{
if (!ch->pcdata->in_progress->text)
{
chprintln(ch,
"Note in progress cancelled because you did not manage to write any text "
NEWLINE "before losing link.");
free_note(ch->pcdata->in_progress);
ch->pcdata->in_progress = NULL;
}
else
{
chprintln(ch,
"You already have a note in progress. type 'note write' to continue it.");
return;
}
}
ch->pcdata->in_progress = new_note();
replace_str(&ch->pcdata->in_progress->sender, ch->name);
replace_str(&ch->pcdata->in_progress->date,
str_time(-1, GetTzone(ch), NULL));
act("{G$n starts writing a note.{x", ch, NULL, NULL, TO_ROOM);
chprintlnf(ch, "{YFrom{x: %s", GetName(ch));
names = format_recipient(ch, ch->pcdata->board->names);
switch (ch->pcdata->board->force_type)
{
case DEF_INCLUDE:
if (!is_full_name(p->sender, names))
{
sprintf(buf, "%s %s", p->sender, names);
chprintlnf(ch,
"You did not include the default recipient '%s' and was automatically added.",
names);
}
break;
default:
case DEF_NORMAL:
strcpy(buf, p->sender);
break;
case DEF_EXCLUDE:
if (is_full_name(names, argument))
{
chprintlnf(ch,
"You are not allowed to send notes to %s on this board. Try again.",
names);
return;
}
strcpy(buf, p->sender);
break;
}
replace_str(&ch->pcdata->in_progress->to_list, buf);
replace_strf(&ch->pcdata->in_progress->subject, "RE: %s", p->subject);
ch->pcdata->in_progress->expire =
current_time + ch->pcdata->board->purge_days * DAY;
chprintlnf(ch,
"{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE
"{YSubject{x: %s", format_to_list(ch->pcdata->in_progress),
str_time(ch->pcdata->in_progress->expire, GetTzone(ch), NULL),
ch->pcdata->in_progress->subject);
replace_str(&ch->pcdata->in_progress->reply_text, replines(p->text));
sendpage(ch, ch->pcdata->in_progress->reply_text);
begin_note(ch);
ch->desc->connected = CON_NOTE_TEXT;
}
static void next_board(CharData * ch)
{
int i = board_number(ch->pcdata->board) + 1;
while ((i < MAX_BOARD)
&& (unread_notes(ch, &boards[i]) == BOARD_NOACCESS
|| ch->pcdata->unsubscribed[i]))
i++;
if (i == MAX_BOARD)
{
chprint(ch, "End of Boards. ");
i = 0;
}
ch->pcdata->board = &boards[i];
return;
}
static Do_Fun(do_nread)
{
NoteData *p;
int count = 0, number;
time_t *last_note =
&ch->pcdata->last_note[board_number(ch->pcdata->board)];
if (!str_cmp(argument, "again"))
{
count = 1;
for (p = ch->pcdata->board->note_first; p; p = p->next, count++)
if (p == ch->pcdata->last_read)
break;
if (!p || !is_note_to(ch, p))
{
chprintln(ch, "No such note.");
}
else
show_note_to_char(ch, p, count);
}
else if (is_number(argument))
{
number = atoi(argument);
for (p = ch->pcdata->board->note_first; p; p = p->next)
if (++count == number)
break;
if (!p || !is_note_to(ch, p))
chprintln(ch, "No such note.");
else
{
show_note_to_char(ch, p, count);
*last_note = Max(*last_note, p->date_stamp);
ch->pcdata->last_read = p;
}
}
else
{
count = 1;
for (p = ch->pcdata->board->note_first; p; p = p->next, count++)
if ((p->date_stamp > *last_note) && is_note_to(ch, p))
{
show_note_to_char(ch, p, count);
*last_note = Max(*last_note, p->date_stamp);
ch->pcdata->last_read = p;
return;
}
chprintln(ch, "No new notes in this board.");
next_board(ch);
chprintlnf(ch, "Changed to next subscribed board, %s.",
ch->pcdata->board->short_name);
}
}
static Do_Fun(do_nremove)
{
NoteData *p;
if (!str_cmp(argument, "all") && IsImmortal(ch))
{
NoteData *p_next;
for (p = ch->pcdata->board->note_first; p; p = p_next)
{
p_next = p->next;
if (str_cmp(ch->name, p->sender) && (get_trust(ch) < MAX_LEVEL))
continue;
unlink_note(ch->pcdata->board, p);
free_note(p);
}
chprintln(ch, "ALL Notes removed!");
}
else
{
if (!is_number(argument))
{
chprintln(ch, "Remove which note?");
return;
}
p = find_note(ch, ch->pcdata->board, atoi(argument));
if (!p)
{
chprintln(ch, "No such note.");
return;
}
if (str_cmp(ch->name, p->sender) && (get_trust(ch) < MAX_LEVEL))
{
chprintln(ch, "You are not authorized to remove this note.");
return;
}
unlink_note(ch->pcdata->board, p);
free_note(p);
chprintln(ch, "Note removed!");
}
SetBit(ch->pcdata->board->flags, BOARD_CHANGED);
}
static Do_Fun(do_nlist)
{
int count = 0, show = 0, num = 0, has_shown = 0;
time_t last_note;
NoteData *p;
Column Cd;
Buffer *buf;
if (is_number(argument))
{
show = atoi(argument);
for (p = ch->pcdata->board->note_first; p; p = p->next)
if (is_note_to(ch, p))
count++;
}
buf = new_buf();
set_cols(&Cd, ch, 2, COLS_BUF, buf);
bprintln(buf, "{WNotes on this board:{x");
cols_header(&Cd, "{rNum> Author Subject");
last_note = ch->pcdata->last_note[board_number(ch->pcdata->board)];
for (p = ch->pcdata->board->note_first; p; p = p->next)
{
num++;
if (is_note_to(ch, p))
{
has_shown++;
if (!show || ((count - show) < has_shown))
{
print_cols(&Cd, "{W%3d{x>{B%c{%c%-12.12s{Y %s{x ", num,
last_note < p->date_stamp ? '*' : ' ',
!NullStr(p->email_addr) ? 'M' : 'G', p->sender,
p->subject);
}
}
}
cols_nl(&Cd);
sendpage(ch, buf_string(buf));
free_buf(buf);
}
static Do_Fun(do_ncatchup)
{
NoteData *p;
if (argument[0] == '\0')
{
for (p = ch->pcdata->board->note_first; p && p->next; p = p->next)
;
if (!p)
chprintln(ch, "Alas, there are no notes in that board.");
else
{
ch->pcdata->last_note[board_number(ch->pcdata->board)] =
p->date_stamp;
chprintln(ch, "All messages skipped.");
}
}
else
{
if (is_name("all", argument))
{
int i, c = 0;
BoardData *board;
for (i = 0; i < MAX_BOARD; i++)
{
board = &boards[i];
if (unread_notes(ch, board) == BOARD_NOACCESS)
continue;
if (unread_notes(ch, board) == 0
|| ch->pcdata->unsubscribed[i])
continue;
c++;
for (p = board->note_first; p && p->next; p = p->next)
;
if (p)
{
ch->pcdata->last_note[board_number(board)] =
p->date_stamp;
chprintlnf(ch, "All notes in {W%s{x board skipped.",
board->short_name);
}
}
if (c > 0)
chprintlnf(ch, "All notes in {W%s{x were skipped.",
intstr(c, "minute"));
else
chprintln(ch, "There is no new notes to skip.");
}
else
{
chprintlnf(ch, "Only argument supported after '%s' is 'all'.",
n_fun);
}
}
}
static Do_Fun(do_nclear)
{
if (ch->pcdata->in_progress)
{
free_note(ch->pcdata->in_progress);
chprintln(ch, "Note cleared.");
}
else
chprintln(ch, "You dont have any notes in progress.");
}
static Do_Fun(do_npurge)
{
int i;
if (!IsImmortal(ch))
return;
for (i = 0; i < MAX_BOARD; i++)
{
check_notes(&boards[i]);
}
chprintln(ch, "Old notes cleaned.");
return;
}
static Do_Fun(do_nreset)
{
int pos;
if (IsNPC(ch))
return;
for (pos = 0; pos < MAX_BOARD; pos++)
ch->pcdata->last_note[pos] = 0;
chprintln(ch, "All notes marked as unread.");
return;
}
static Do_Fun(do_nhelp)
{
cmd_syntax(ch, n_fun,
"read [again] - read all notes 1 board at a time.",
"write - write a note on your current board.",
"list - list all notes on your current board.",
"remove [number] - remove a note.",
"print - print a note to a usable object.",
"reply [number] - reply to a note.",
"forward [number] [arg] - forward a note to a specific person.",
"catchup - mark all notes on current board as read.",
"reset - mark all notes on all boards as unread.",
"check - count how many unread notes you have on all boards.",
"clear - clear current note in progress.",
NULL);
if (IsImmortal(ch))
cmd_syntax(ch, n_fun,
"purge - purges expired notes from current board.",
NULL);
}
Do_Fun(do_note)
{
if (IsNPC(ch))
return;
vinterpret(ch, n_fun, argument, "read", do_nread, "list", do_nlist,
"write", do_nwrite, "to", do_nwrite, "remove", do_nremove,
"reply", do_nreply, "forward", do_nforward, "purge", do_npurge,
"reset", do_nreset, "clear", do_nclear, "check", do_ncheck,
"catchup", do_ncatchup, "help", do_nhelp, NULL, do_nread);
}
Do_Fun(do_subscribe)
{
int i, count, number;
if (IsNPC(ch))
return;
if (NullStr(argument))
{
count = 1;
chprintln(ch,
"{RNum Name Subscribed Description{x" NEWLINE
"{R=== ============ ========== ==========={x");
for (i = 0; i < MAX_BOARD; i++)
{
if (unread_notes(ch, &boards[i]) == BOARD_NOACCESS)
continue;
chprintlnf(ch, "{W%2d{x> {g%12s{x [ %-8s{x] %s{x", count,
boards[i].short_name,
ch->pcdata->unsubscribed[i] ? "{rNO" : "{gYES",
boards[i].long_name);
count++;
}
return;
}
if (ch->pcdata->in_progress)
{
chprintln(ch, "Please finish your interrupted note first.");
return;
}
if (is_number(argument))
{
count = 0;
number = atoi(argument);
for (i = 0; i < MAX_BOARD; i++)
if (unread_notes(ch, &boards[i]) != BOARD_NOACCESS)
if (++count == number)
break;
if (i == board_lookup("Announce") || i == board_lookup("Personal"))
{
chprintln(ch,
"You cannot un-subscribe from the Announce or Personal boards.");
return;
}
if (count == number)
{
if (ch->pcdata->unsubscribed[i])
{
ch->pcdata->unsubscribed[i] = false;
chprintlnf(ch, "You are now subscribed to the {W%s{x board.",
boards[i].short_name);
}
else
{
ch->pcdata->unsubscribed[i] = true;
chprintlnf(ch,
"You are no longer subscribed to the {W%s{x board.",
boards[i].short_name);
}
}
else
chprintln(ch, "No such board.");
return;
}
for (i = 0; i < MAX_BOARD; i++)
if (!str_prefix(argument, boards[i].short_name))
break;
if (i == MAX_BOARD)
{
chprintln(ch, "No such board.");
return;
}
if (unread_notes(ch, &boards[i]) == BOARD_NOACCESS)
{
chprintln(ch, "No such board.");
return;
}
if (i == board_lookup("Announce") || i == board_lookup("Personal"))
{
chprintln(ch,
"You cannot un-subscribe from the Announce or Personal boards.");
return;
}
if (ch->pcdata->unsubscribed[i])
{
ch->pcdata->unsubscribed[i] = false;
chprintlnf(ch, "You are now subscribed to the {W%s{x board.",
boards[i].short_name);
}
else
{
ch->pcdata->unsubscribed[i] = true;
chprintlnf(ch, "You are no longer subscribed to the {W%s{x board.",
boards[i].short_name);
}
}
void show_board(CharData * ch, bool fAll)
{
int unread, count, i, last;
NoteData *p;
BoardData *b;
bool found = true;
count = 0;
if (IsImmortal(ch))
chprintln(ch,
"{RNum Name Flags Unread Last Description{x"
NEWLINE
"{R=== ============ =========== ====== ==== ==========={x");
else
chprintln(ch,
"{RNum Name Unread Last Description{x" NEWLINE
"{R=== ============ ====== ==== ==========={x");
for (i = 0; i < MAX_BOARD; i++)
{
unread = unread_notes(ch, &boards[i]);
if (unread == BOARD_NOACCESS)
continue;
count++;
if (ch->pcdata->unsubscribed[i])
continue;
if (unread == 0 && fAll == true)
continue;
last = 0;
b = &boards[i];
for (p = b->note_first; p; p = p->next)
if (is_note_to(ch, p))
last++;
found = false;
chprintlnf(ch, "{W%2d{x> {G%12s{x [{%c%4d{x] {G%4d {Y%s{x", count,
boards[i].short_name, unread == 0 ? 'r' : 'R', unread,
last, boards[i].long_name);
}
if (!found)
chprintln(ch,
"You have no unread notes on any subscribed board." NEWLINE
"(Use 'board all' to see a list of boards.)");
chprintf(ch, NEWLINE "Your current board is {W%s{x",
ch->pcdata->board->short_name);
if ((p = last_note(ch, ch->pcdata->board)) != NULL)
chprintlnf(ch, ". Last message was from {W%s{x.", p->sender);
else
chprintln(ch, ".");
if (ch->pcdata->board->read_level > get_trust(ch))
chprintln(ch, "You cannot read nor write notes on this board.");
else if (ch->pcdata->board->write_level > get_trust(ch))
chprintln(ch, "You can only read notes from this board.");
else
chprintln(ch, "You can both read and write on this board.");
chprintln(ch, "Use 'board all' to see all subscribed boards.");
chprintln(ch,
"Use 'subscribe' to see what boards you are subscribed to.");
return;
}
Do_Fun(do_board)
{
int i, number, count;
NoteData *p;
if (IsNPC(ch))
return;
if (NullStr(argument))
{
show_board(ch, true);
return;
}
else if (!str_cmp(argument, "all"))
{
show_board(ch, false);
return;
}
else if (IsImmortal(ch) && !str_cmp(argument, "save"))
{
rw_note_data(act_write);
chprintln(ch, "Notes saved.");
return;
}
if (ch->pcdata->in_progress)
{
chprintln(ch, "Please finish your interrupted note first.");
return;
}
if (is_number(argument))
{
count = 0;
number = atoi(argument);
for (i = 0; i < MAX_BOARD; i++)
if (unread_notes(ch, &boards[i]) != BOARD_NOACCESS)
if (++count == number)
break;
if (count == number)
{
ch->pcdata->board = &boards[i];
chprintlnf(ch, "Current board changed to {W%s{x. %s.",
boards[i].short_name,
(get_trust(ch) <
boards[i].write_level) ? "You can only read here" :
"You can both read and write here");
if ((p = last_note(ch, &boards[i])) != NULL)
chprintlnf(ch,
"Last message was from {W%s{x concerning {W%s{x.",
p->sender, p->subject);
}
else
chprintln(ch, "No such board.");
return;
}
for (i = 0; i < MAX_BOARD; i++)
if (!str_prefix(argument, boards[i].short_name))
break;
if (i == MAX_BOARD)
{
chprintln(ch, "No such board.");
return;
}
if (unread_notes(ch, &boards[i]) == BOARD_NOACCESS)
{
chprintln(ch, "No such board.");
return;
}
ch->pcdata->board = &boards[i];
chprintlnf(ch, "Current board changed to {W%s{x. %s.",
boards[i].short_name,
(get_trust(ch) <
boards[i].write_level) ? "You can only read here" :
"You can both read and write here");
if ((p = last_note(ch, &boards[i])) != NULL)
chprintlnf(ch, "Last message was from {W%s{x concerning {W%s{x.",
p->sender, p->subject);
}
void personal_message(const char *sender, const char *to, const char *subject,
const int expire_days, const char *text)
{
make_note("Personal", sender, to, subject, expire_days, text, NULL, NULL);
}
void email_note(const char *sender, const char *email, const char *subject,
const int expire_days, const char *text)
{
make_note("Personal", sender, email, subject, expire_days, text, email,
NULL);
}
void make_note(const char *board_name, const char *sender, const char *to,
const char *subject, const int expire_days, const char *text,
const char *email, const char *reply)
{
int board_index = board_lookup(board_name);
BoardData *board;
NoteData *note;
if (board_index == BOARD_NOTFOUND)
{
bug("make_note: board not found");
return;
}
if (strlen(text) > MAX_NOTE_TEXT)
{
bugf("make_note: text too long (%d bytes)", strlen(text));
return;
}
board = &boards[board_index];
note = new_note();
note->sender = str_dup(sender);
note->to_list = str_dup(to);
note->subject = str_dup(subject);
note->expire = current_time + expire_days * 60 * 60 * 24;
note->text = str_dup(text);
note->email_addr = str_dup(email);
note->reply_addr = str_dup(reply);
note->date = str_dup(str_time(-1, -1, NULL));
finish_note(board, note);
}
void append_to_note(CharData * ch, const char *board_name, const char *sender,
const char *to, const char *subject,
const int expire_days, const char *text)
{
int board_index = board_lookup(board_name);
BoardData *board;
if (board_index == BOARD_NOTFOUND)
{
bug("board not found");
return;
}
if (strlen(text) > (size_t) MAX_NOTE_TEXT)
{
bugf("text too long (%d bytes)", strlen(text));
return;
}
board = &boards[board_index];
ch->pcdata->in_progress = new_note();
replace_str(&ch->pcdata->in_progress->sender, sender);
replace_str(&ch->pcdata->in_progress->to_list, to);
replace_str(&ch->pcdata->in_progress->subject, subject);
if (!is_invalid_email(to))
{
replace_str(&ch->pcdata->in_progress->email_addr, to);
if (!NullStr(ch->pcdata->email))
replace_str(&ch->pcdata->in_progress->reply_addr,
ch->pcdata->email);
}
ch->pcdata->in_progress->expire =
current_time + (time_t) (expire_days * DAY);
replace_str(&ch->pcdata->in_progress->date, str_time(-1, -1, NULL));
replace_str(&ch->pcdata->in_progress->text, text);
chprintlnf(ch,
"{YTo{x: %s" NEWLINE "{YExpires{x: %s" NEWLINE
"{YSubject{x: %s" NEWLINE "%s",
format_to_list(ch->pcdata->in_progress),
(ch->pcdata->board->purge_days ==
-1) ? "Never" : str_time(ch->pcdata->in_progress->expire,
GetTzone(ch), NULL),
ch->pcdata->in_progress->subject,
ch->pcdata->in_progress->text);
begin_note(ch);
ch->desc->connected = CON_NOTE_TEXT;
}
Nanny_Fun(HANDLE_CON_NOTE_TO)
{
CharData *ch = d->character;
const char *names;
if (!ch->pcdata->in_progress)
{
d->connected = CON_PLAYING;
bugf("nanny: In CON_NOTE_TO, but no note in progress");
return;
}
names = format_recipient(ch, ch->pcdata->board->names);
switch (ch->pcdata->board->force_type)
{
case DEF_NORMAL:
if (NullStr(argument))
{
replace_str(&ch->pcdata->in_progress->to_list, names);
d_printlnf(d, "Assumed default recipient: {W%s{x", names);
}
else
replace_str(&ch->pcdata->in_progress->to_list, argument);
break;
default:
break;
case DEF_INCLUDE:
if (!is_full_name(names, argument))
{
replace_strf(&ch->pcdata->in_progress->to_list, "%s %s",
argument, names);
d_printlnf(d,
NEWLINE
"You did not specify %s as recipient, so it was automatically added."
NEWLINE "{YNew To{x : %s", names,
ch->pcdata->in_progress->to_list);
}
else
replace_str(&ch->pcdata->in_progress->to_list, argument);
break;
case DEF_EXCLUDE:
if (NullStr(argument))
{
d_print(d,
"You must specify a recipient." NEWLINE
"{YTo{x: ");
return;
}
if (is_full_name(names, argument))
{
d_printf(d,
"You are not allowed to send notes to %s on this board. Try again."
NEWLINE "{YTo{x: ", names);
return;
}
if (strchr(argument, '@'))
{
const char *invalid = is_invalid_email(argument);
if (invalid)
{
d_println(d, invalid);
d_print(d,
"You must specify a valid email address. (ie. bob@aol.com)."
NEWLINE "{YTo{x: ");
return;
}
replace_str(&ch->pcdata->in_progress->email_addr, argument);
}
replace_str(&ch->pcdata->in_progress->to_list, argument);
break;
}
d_print(d, NEWLINE "{YSubject{x: ");
d->connected = CON_NOTE_SUBJECT;
}
Nanny_Fun(HANDLE_CON_NOTE_SUBJECT)
{
CharData *ch = d->character;
if (!ch->pcdata->in_progress)
{
d->connected = CON_PLAYING;
bugf("nanny: In CON_NOTE_SUBJECT, but no note in progress");
return;
}
if (NullStr(argument))
{
d_println(d, "Please find a meaningful subject!");
d_print(d, "{YSubject{x: ");
}
else if (strlen(argument) > 60)
{
d_println(d,
"No, no. This is just the Subject. You're not writing the note yet. Twit.");
}
else
{
replace_str(&ch->pcdata->in_progress->subject, argument);
if (IsImmortal(ch))
{
d_printf(d,
NEWLINE
"How many days do you want this note to expire in?"
NEWLINE
"Press Enter for default value for this board, {W%d{x days."
NEWLINE "{YExpire{x: ", ch->pcdata->board->purge_days);
d->connected = CON_NOTE_EXPIRE;
}
else
{
ch->pcdata->in_progress->expire =
current_time + ch->pcdata->board->purge_days * DAY;
d_printlnf(d, "This note will expire %s",
str_time(ch->pcdata->in_progress->expire, GetTzone(ch),
NULL));
d_printlnf(d,
NEWLINE
"Enter text. Type {W%cq{x or {W@{x on an empty line to end the note, or {W%ch{x for help.",
StrEdKey(ch), StrEdKey(ch));
d_println(d, draw_line(ch, "{W={w=", 0));
d->connected = CON_NOTE_TEXT;
}
}
}
Nanny_Fun(HANDLE_CON_NOTE_EXPIRE)
{
CharData *ch = d->character;
time_t expire;
int days;
if (!ch->pcdata->in_progress)
{
d->connected = CON_PLAYING;
bugf("nanny: In CON_NOTE_EXPIRE, but no note in progress");
return;
}
if (NullStr(argument))
days = ch->pcdata->board->purge_days;
else if (!is_number(argument))
{
d_println(d, "Write the number of days!");
d_print(d, "{YExpire{x: ");
return;
}
else
{
days = atoi(argument);
if (days <= 0)
{
d_println(d,
"This is a positive Mud. Use positive numbers only! :)");
d_print(d, "{YExpire{x: ");
return;
}
}
expire = current_time + (days * DAY);
ch->pcdata->in_progress->expire = expire;
d_printlnf(d,
NEWLINE
"Enter text. Type {W%cq{x or {W@{x on an empty line to end the note, or {W%ch{x for help.",
StrEdKey(ch), StrEdKey(ch));
d_println(d, draw_line(ch, "{W={w=", 0));
d->connected = CON_NOTE_TEXT;
}
Nanny_Fun(HANDLE_CON_NOTE_TEXT)
{
strshow_t action;
CharData *ch = d->character;
char buf[MAX_STRING_LENGTH * 5];
if (!ch->pcdata->in_progress)
{
d->connected = CON_PLAYING;
bugf("nanny: In CON_NOTE_TEXT, but no note in progress");
return;
}
action =
parse_string_command(&ch->pcdata->in_progress->text, argument, ch);
switch (action)
{
case STRING_END:
d_printf(d, NEWLINE "%s", szFinishPrompt);
d->connected = CON_NOTE_FINISH;
return;
case STRING_FOUND:
return;
default:
case STRING_NONE:
if (ch->pcdata->in_progress->text)
{
strcpy(buf, ch->pcdata->in_progress->text);
replace_str(&ch->pcdata->in_progress->text, "");
}
else
strcpy(buf, "");
if ((strlen(argument) + strlen(buf)) > MAX_NOTE_TEXT)
{
d_println(d, "Note too long, bailing out!");
free_note(ch->pcdata->in_progress);
ch->pcdata->in_progress = NULL;
d->connected = CON_PLAYING;
return;
}
strcat(buf, argument);
strcat(buf, NEWLINE);
replace_str(&ch->pcdata->in_progress->text, buf);
return;
}
}
Nanny_Fun(HANDLE_CON_NOTE_FINISH)
{
CharData *ch = d->character;
if (!ch->pcdata->in_progress)
{
d->connected = CON_PLAYING;
bugf("nanny: In CON_NOTE_FINISH, but no note in progress");
return;
}
switch (tolower(argument[0]))
{
case 'c':
d_println(d, "Continuing note...");
d->connected = CON_NOTE_TEXT;
break;
case 'v':
if (ch->pcdata->in_progress->text)
{
d_println(d, "{gText of your note so far:{x");
d_print(d, ch->pcdata->in_progress->text);
}
else
d_println(d, "You haven't written a thing!" NEWLINE);
d_println(d, szFinishPrompt);
break;
case 'p':
announce(ch, INFO_NOTE, "New note on %s board from $n. Subj: %s",
ch->pcdata->board->short_name,
ch->pcdata->in_progress->subject);
finish_note(ch->pcdata->board, ch->pcdata->in_progress);
d_print(d, "Note posted");
if (!NullStr(ch->pcdata->in_progress->email_addr))
d_printlnf(d, " and sent to %s.",
ch->pcdata->in_progress->email_addr);
else
d_println(d, ".");
mud_info.stats.notes++;
d->connected = CON_PLAYING;
ch->pcdata->in_progress = NULL;
act("{G$n finishes $s note.{x", ch, NULL, NULL, TO_ROOM);
break;
case 'f':
d_println(d, "Note cancelled!");
free_note(ch->pcdata->in_progress);
ch->pcdata->in_progress = NULL;
d->connected = CON_PLAYING;
break;
default:
d_println(d, "Huh? Valid answers are:" NEWLINE);
d_println(d, szFinishPrompt);
}
}