Bulletin Board System

Moderator: Event DM

Post Reply
User avatar
Gatorade
Lord of Blithering Idiots
Posts: 117
Joined: Wed Sep 19, 2001 1:44 am

Bulletin Board System

Post by Gatorade » Thu Jul 11, 2002 5:23 am

Looky here:
The Bulletin Board System allows players to read and post messages.

The system consists of a Scribe (NPC) and the Bulletin Board (Signpost placeable).

Players can talk to the Scribe and get an item called Bulletin Board Notice. During conversation, the Scribe will ask a player to speak a title and a message, clicking Done after each. To speak, the player has to type a message and press Enter, as if addressing another player. Upon successful input, the item is given to the player. The player can then walk up to the Bulletin Board and select the Post Message dialogue option to have their message entered on the Bulletin Board.

The Bulletin Board displays messages in reverse chronological order so the latest message appears first. Players can page through the listings which consist of the title and name of the poster. The default page length is 5 entries, but it can be set for any number between 1 and 10. When a player posts a message, the Bulletin Board Notice disappears from their inventory. If the player has more than one, one will be taken at random. Once the maximum number of messages for the Bulletin Board has been reached, older messages will be deleted to make space for new ones. The default maximum is 25 messages. To change these defaults,
look at the "void bbs_initiate" function inside the "bbs_include" script.

All Bulletin Boards with the same tag share the messages. If you change the tags you end up with separate Bulletin Boards. For example, you could have it so the Bulletin Boards in the same city show the same messages, but be different from city to city.

The Bulletin Board System uses custom tokens in the range 3671 - 3699. Hopefully these won't conflict with any you might be using.

The "bbs_include" script contains a function "void bbs_add_notice". You can use it to post messages from NPCs to the Bulletin Board and make it seem more lively, perhaps with minor quests. For more information read the comments above the function.

Because the "bbs_include" script is used as an include file, it does not have a "void main", so whenever you compile that script or build your module you will get a compiling error in it. This is normal and does not affect operation in any way.

The code for capturing a PC spoken string came from a post by gsmithcat in the NWN Scripting forum.

Serban Oprescu
serban@pixelsharp.com
http://www.pixelsharp.com/projects/nwn/
Heres the script, ton o files:

bbs_have_notice

int StartingConditional()
{
if (GetItemPossessedBy(GetPCSpeaker(), "bbs_notice") != OBJECT_INVALID) {
return TRUE;
}
return FALSE;
}


bbs_include

//This is an include file. Upon building your module you will get
//a compile error in this file. That is normal and does not
//affect the operation of the bulletin board.

void bbs_do_board_stats();
void bbs_initiate(object oBBS);
int bbs_can_show(int WhichEntry);
void bbs_change_page(int PageChange);
void bbs_select_entry(int WhichEntry);
void bbs_add_notice(object oBBS, string sPoster, string sTitle, string sMessage, string sDate);

//Loads into tokens the stats for a board
void bbs_do_board_stats() {
object oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF));
int PageSize = GetLocalInt(oBBS, "PageSize");
int TotalItems = GetLocalInt(oBBS, "TotalItems");
int PageIndex = GetLocalInt(oBBS, "PageIndex") + 1;
SetCustomToken(3671, IntToString(TotalItems));
if (TotalItems == 0) {PageIndex = 0;}
SetCustomToken(3672, IntToString(PageIndex));
SetCustomToken(3673, IntToString((TotalItems + PageSize - 1) / PageSize));
}

//Initiates a bulletin board's settings if neccessary
void bbs_initiate(object oBBS) {
string sBBS = "BBS_" + GetTag(oBBS);
object myBBS = GetLocalObject(GetModule(), sBBS);
if (!GetIsObjectValid(myBBS)) {
SetLocalObject(GetModule(), sBBS, oBBS);
myBBS = oBBS;
}
if (GetLocalInt(myBBS, "PageSize") == 0) {
SetLocalInt(myBBS, "MaxItems", 25);
SetLocalInt(myBBS, "PageSize", 5);
}
}

//Determines whether a dialogue option is visible in conversation
int bbs_can_show(int WhichEntry) {
object oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF));
int PageSize = GetLocalInt(oBBS, "PageSize");
int nSpot = GetLocalInt(oBBS, "PageIndex") * PageSize + WhichEntry;
if(nSpot <= GetLocalInt(oBBS, "TotalItems") && WhichEntry <= PageSize) {return TRUE;}
return FALSE;
}

//Moves the page by the required PageFlip:
//0 to reload page, -1 for previous page, 1 for next page
void bbs_change_page(int PageFlip) {
object oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF));
int PageSize = GetLocalInt(oBBS, "PageSize");
int TotalItems = GetLocalInt(oBBS, "TotalItems");
int MaxItems = GetLocalInt(oBBS, "MaxItems");
int LatestItem = GetLocalInt(oBBS, "LatestItem");
int PageIndex = GetLocalInt(oBBS, "PageIndex") + 1 * PageFlip;
if (PageIndex < 0) {PageIndex = 0;}
SetLocalInt(oBBS, "PageIndex", PageIndex);

string sInfo;
int iLoop;
int iNotice;

for (iLoop = 0; iLoop < PageSize; iLoop++) {
iNotice = LatestItem - PageIndex * PageSize - iLoop;
if (iNotice < 1) {iNotice = MaxItems + iNotice;}
sInfo = GetLocalString(oBBS, "Title" + IntToString(iNotice));
SetCustomToken(3680 + iLoop, sInfo);
sInfo = GetLocalString(oBBS, "Poster" + IntToString(iNotice));
if (((PageIndex * PageSize + iLoop + 2) > TotalItems) || (iLoop == PageSize - 1)){
sInfo = sInfo + "\n ";
}
SetCustomToken(3690 + iLoop, sInfo);
}
bbs_do_board_stats();
SetCustomToken(3674, "");
SetCustomToken(3675, "");
SetCustomToken(3676, "");
SetCustomToken(3677, "");
SetCustomToken(3678, "");
}

//Displays the selected post
void bbs_select_entry(int WhichEntry) {
object oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF));
int PageSize = GetLocalInt(oBBS, "PageSize");
int MaxItems = GetLocalInt(oBBS, "MaxItems");
int LatestItem = GetLocalInt(oBBS, "LatestItem");
int PageIndex = GetLocalInt(oBBS, "PageIndex");

int iNotice = LatestItem - PageIndex * PageSize - WhichEntry + 1;
if (iNotice < 1) {iNotice = MaxItems + iNotice;}

string sNotice = IntToString(iNotice);
bbs_do_board_stats();
SetCustomToken(3674, "\n\n" + GetLocalString(oBBS, "Title" + sNotice) + "\nBy: ");
SetCustomToken(3675, GetLocalString(oBBS, "Poster" + sNotice));
SetCustomToken(3676, " On: ");
SetCustomToken(3677, GetLocalString(oBBS, "Date" + sNotice));
SetCustomToken(3678, "\n" + GetLocalString(oBBS, "Message" + sNotice));
}

//Adds a post to the bulletin board. This can be called at any time
//so you can insert your own notices. If you don't specify a sDate,
//it will use the current game time. The proper format for sDate is
//something like "6/30/1373 11:58". If you do call this function
//on your own, make sure you do a bbs_initiate() first.
void bbs_add_notice(object oBBS, string sPoster, string sTitle, string sMessage, string sDate)
{
oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(oBBS));
if (sDate == "") {
sDate = IntToString(GetTimeMinute());
if (GetStringLength(sDate) == 1) {sDate = "0" + sDate;}
sDate = IntToString(GetCalendarMonth()) + "/" + IntToString(GetCalendarDay()) + "/" + IntToString(GetCalendarYear()) + " " + IntToString(GetTimeHour()) + ":" + sDate;
}
int MaxItems = GetLocalInt(oBBS, "MaxItems");
int TotalItems = GetLocalInt(oBBS, "TotalItems");
int nSpot = TotalItems + 1;
if (nSpot > MaxItems) {
nSpot = GetLocalInt(oBBS, "LatestItem") + 1;
if (nSpot > MaxItems) {nSpot = nSpot - MaxItems;}
}
SetLocalString(oBBS, "Poster" + IntToString(nSpot), sPoster);
SetLocalString(oBBS, "Date" + IntToString(nSpot), sDate);
SetLocalString(oBBS, "Title" + IntToString(nSpot), sTitle);
SetLocalString(oBBS, "Message" + IntToString(nSpot), sMessage);
SetLocalInt(oBBS, "LatestItem", nSpot);
if (MaxItems > TotalItems) {SetLocalInt(oBBS, "TotalItems", TotalItems + 1);}
}

bbs_page_back

#include "bbs_include"
void main()
{
bbs_change_page(-1);
}

bbs_page_next

#include "bbs_include"
void main()
{
bbs_change_page(1);
}

bbs_post_notice

#include "bbs_include"
void main()
{
object oPC = GetPCSpeaker();
object oNotice = GetItemPossessedBy(oPC, "bbs_notice");
if (GetIsObjectValid(oNotice)) {
string nPoster = GetName(oPC) + " (" + GetPCPlayerName(oPC) + ")";
string nTitle = GetLocalString(oNotice, "Title");
string nMessage = GetLocalString(oNotice, "Message");
ActionTakeItem(oNotice, oPC);
bbs_add_notice(OBJECT_SELF, nPoster, nTitle, nMessage, "");
bbs_change_page(-1000);
}
}

bbs_scrive_conv

//::///////////////////////////////////////////////
//:: SetListeningPatterns
//:: NW_C2_DEFAULT4
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
Determines the course of action to be taken
by the generic script after dialogue or a
shout is initiated.
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Oct 24, 2001
//:://////////////////////////////////////////////

#include "NW_I0_GENERIC"

void main()
{
int nMatch = GetListenPatternNumber();
object oShouter = GetLastSpeaker();
object oIntruder;

if (nMatch == -1 && GetCommandable(OBJECT_SELF))
{
ClearAllActions();
BeginConversation();
}
else
if(nMatch == 777 && GetIsObjectValid(oShouter) && GetIsPC(oShouter))
// && GetIsFriend(oShouter)
{
if (oShouter == GetLocalObject(OBJECT_SELF, "Customer")) {
string sSaid = GetMatchedSubstring(0);
SetLocalString(OBJECT_SELF, "Stack", sSaid);
}
}
}

bbs_scribe_give

void main()
{
object Notice = CreateItemOnObject("bbs_notice_bp", GetPCSpeaker());
if (Notice != OBJECT_INVALID) {
SetLocalString(Notice, "Title", GetLocalString(OBJECT_SELF, "Title"));
SetLocalString(Notice, "Message", GetLocalString(OBJECT_SELF, "Message"));
}
}

bbs_scribe_m_get

void main()
{
string sTalk = GetLocalString(OBJECT_SELF, "Stack");
if (sTalk != "") {
if (GetStringLength(sTalk) > 200) {sTalk = GetStringLeft(sTalk, 200);}
SetLocalString(OBJECT_SELF, "Message", sTalk);
SetLocalString(OBJECT_SELF, "Stack", "");
}
}

bbs_scribe_m_yes

int StartingConditional()
{
if (GetLocalString(OBJECT_SELF, "Message") != "") {
return TRUE;
}
return FALSE;
}

bbs_scribe_spawn

//::///////////////////////////////////////////////
//:: Default: On Spawn In
//:: NW_C2_DEFAULT9
//:: Copyright (c) 2001 Bioware Corp.
//:://////////////////////////////////////////////
/*
Determines the course of action to be taken
after having just been spawned in
*/
//:://////////////////////////////////////////////
//:: Created By: Preston Watamaniuk
//:: Created On: Oct 25, 2001
//:://////////////////////////////////////////////
#include "NW_O2_CONINCLUDE"
#include "NW_I0_GENERIC"

void main()
{
// OPTIONAL BEHAVIORS (Comment In or Out to Activate ) ****************************************************************************
//SetSpawnInCondition(NW_FLAG_SPECIAL_CONVERSATION);
//SetSpawnInCondition(NW_FLAG_SPECIAL_COMBAT_CONVERSATION);
// This causes the creature to say a special greeting in their conversation file
// upon Perceiving the player. Attach the [NW_D2_GenCheck.nss] script to the desired
// greeting in order to designate it. As the creature is actually saying this to
// himself, don't attach any player responses to the greeting.

//SetSpawnInCondition(NW_FLAG_SHOUT_ATTACK_MY_TARGET);
// This will set the listening pattern on the NPC to attack when allies call
//SetSpawnInCondition(NW_FLAG_STEALTH);
// If the NPC has stealth and they are a rogue go into stealth mode
//SetSpawnInCondition(NW_FLAG_SEARCH);
// If the NPC has Search go into Search Mode
//SetSpawnInCondition(NW_FLAG_SET_WARNINGS);
// This will set the NPC to give a warning to non-enemies before attacking

//SetSpawnInCondition(NW_FLAG_SLEEP);
//Creatures that spawn in during the night will be asleep.
//SetSpawnInCondition(NW_FLAG_DAY_NIGHT_POSTING);
//SetSpawnInCondition(NW_FLAG_APPEAR_SPAWN_IN_ANIMATION);
//SetSpawnInCondition(NW_FLAG_IMMOBILE_AMBIENT_ANIMATIONS);
//SetSpawnInCondition(NW_FLAG_AMBIENT_ANIMATIONS);
//This will play Ambient Animations until the NPC sees an enemy or is cleared.
//NOTE that these animations will play automatically for Encounter Creatures.

// NOTE: ONLY ONE OF THE FOLOOWING ESCAPE COMMANDS SHOULD EVER BE ACTIVATED AT ANY ONE TIME.
//SetSpawnInCondition(NW_FLAG_ESCAPE_RETURN); // OPTIONAL BEHAVIOR (Flee to a way point and return a short time later.)
//SetSpawnInCondition(NW_FLAG_ESCAPE_LEAVE); // OPTIONAL BEHAVIOR (Flee to a way point and do not return.)
//SetSpawnInCondition(NW_FLAG_TELEPORT_LEAVE); // OPTIONAL BEHAVIOR (Teleport to safety and do not return.)
//SetSpawnInCondition(NW_FLAG_TELEPORT_RETURN); // OPTIONAL BEHAVIOR (Teleport to safety and return a short time later.)

// CUSTOM USER DEFINED EVENTS
/*
The following settings will allow the user to fire one of the blank user defined events in the NW_D2_DefaultD. Like the
On Spawn In script this script is meant to be customized by the end user to allow for unique behaviors. The user defined
events user 1000 - 1010
*/
//SetSpawnInCondition(NW_FLAG_HEARTBEAT_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1001
//SetSpawnInCondition(NW_FLAG_PERCIEVE_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1002
//SetSpawnInCondition(NW_FLAG_ATTACK_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1005
//SetSpawnInCondition(NW_FLAG_DAMAGED_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1006
//SetSpawnInCondition(NW_FLAG_DISTURBED_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1008
//SetSpawnInCondition(NW_FLAG_END_COMBAT_ROUND_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1003
//SetSpawnInCondition(NW_FLAG_ON_DIALOGUE_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1004
//SetSpawnInCondition(NW_FLAG_DEATH_EVENT); //OPTIONAL BEHAVIOR - Fire User Defined Event 1007

// DEFAULT GENERIC BEHAVIOR (DO NOT TOUCH) *****************************************************************************************
SetListenPattern(OBJECT_SELF, "**", 777); //listen to all text
SetListening(OBJECT_SELF, TRUE); //be sure NPC is listening

//SetListeningPatterns(); // Goes through and sets up which shouts the NPC will listen to.
WalkWayPoints(); // Optional Parameter: void WalkWayPoints(int nRun = FALSE, float fPause = 1.0)
// 1. Looks to see if any Way Points in the module have the tag "WP_" + NPC TAG + "_0X", if so walk them
// 2. If the tag of the Way Point is "POST_" + NPC TAG the creature will return this way point after
// combat.
GenerateNPCTreasure(); //* Use this to create a small amount of treasure on the creature
}

bbs_scribe_start

void main()
{
SetLocalObject(OBJECT_SELF, "Customer", GetPCSpeaker());
SetLocalString(OBJECT_SELF, "Stack", "");
SetLocalString(OBJECT_SELF, "Title", "");
SetLocalString(OBJECT_SELF, "Message", "");
}

bbs_scribe_stop

void main()
{
SetLocalObject(OBJECT_SELF, "Customer", OBJECT_INVALID);
}

bbs_scribe_t_get

void main()
{
string sTalk = GetLocalString(OBJECT_SELF, "Stack");
if (sTalk != "") {
if (GetStringLength(sTalk) > 30) {sTalk = GetStringLeft(sTalk, 30);}
SetLocalString(OBJECT_SELF, "Title", sTalk);
SetLocalString(OBJECT_SELF, "Stack", "");
}
}

bbs_scribe_t_yes

int StartingConditional()
{
if (GetLocalString(OBJECT_SELF, "Title") != "") {
return TRUE;
}
return FALSE;
}

bbs_select_01

#include "bbs_include"

void main()
{
bbs_select_entry(1);
}

bbs_select_02

#include "bbs_include"

void main()
{
bbs_select_entry(2);
}

bbs_select_03

#include "bbs_include"

void main()
{
bbs_select_entry(3);
}

bbs_select_04

#include "bbs_include"

void main()
{
bbs_select_entry(4);
}

bbs_select_05

#include "bbs_include"

void main()
{
bbs_select_entry(5);
}

bbs_select_06

#include "bbs_include"

void main()
{
bbs_select_entry(6);
}

bbs_select_07

#include "bbs_include"

void main()
{
bbs_select_entry(7);
}

bbs_select_08

#include "bbs_include"

void main()
{
bbs_select_entry(8);
}

bbs_select_09

#include "bbs_include"

void main()
{
bbs_select_entry(9);
}

bbs_select_10

#include "bbs_include"

void main()
{
bbs_select_entry(10);
}

bbs_show_01

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(1);
}

bbs_show_02

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(2);
}

bbs_show_03

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(3);
}

bbs_show_04

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(4);
}

bbs_show_05

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(5);
}

bbs_show_06

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(6);
}

bbs_show_07

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(7);
}

bbs_show_08

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(8);
}

bbs_show_09

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(9);
}

bbs_show_10

#include "bbs_include"
int StartingConditional()
{
return bbs_can_show(10);
}

bbs_show_back

int StartingConditional()
{
object oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF));
int PageIndex = GetLocalInt(oBBS, "PageIndex");
if (PageIndex == 0) {
return FALSE;
}
return TRUE;
}

bbs_show_next

int StartingConditional()
{
object oBBS = GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF));
int PageSize = GetLocalInt(oBBS, "PageSize");
int PageIndex = GetLocalInt(oBBS, "PageIndex");
int TotalItems = GetLocalInt(oBBS, "TotalItems");
if (TotalItems > (PageIndex + 1) * PageSize) {
return TRUE;
}
return FALSE;
}

bbs_start

#include "bbs_include"

void main()
{
object oPC = GetLastUsedBy();
if (GetIsPC(oPC)) {
bbs_initiate(OBJECT_SELF);
//SetLocalInt(GetLocalObject(GetModule(), "BBS_" + GetTag(OBJECT_SELF)), "PageIndex", 0);

bbs_change_page(-1000);
ActionStartConversation(oPC);
}
}
<img src="http://rumandmonkey.com/widgets/tests/g ... sprime.jpg" width="200" height="150"><br><a href="http://rumandmonkey.com/widgets/tests/g ... ot/">Which Colossal Death Robot Are <i>You</i>?</a>
Orleron
World Advisor, Co-founder
World Advisor, Co-founder
Posts: 15149
Joined: Fri Sep 14, 2001 9:48 pm
Timezone: GMT-5
Contact:

Post by Orleron » Thu Jul 11, 2002 6:42 am

That's pretty damn cool. Good starting point for adventures too.

Silk, you wanna take this one?
"Truth has no form."
--Idries Shah
User avatar
Silk
Co-Founder
Posts: 6662
Joined: Fri Sep 14, 2001 6:47 pm
Contact:

Post by Silk » Thu Jul 11, 2002 2:24 pm

What happens when the server restarts/crashes?
Silk

Member of the MadK@t lover's group.
User avatar
Gatorade
Lord of Blithering Idiots
Posts: 117
Joined: Wed Sep 19, 2001 1:44 am

Post by Gatorade » Thu Jul 11, 2002 2:33 pm

I couldnt tell you, I have never gotten anymorem details than what you see here.
<img src="http://rumandmonkey.com/widgets/tests/g ... sprime.jpg" width="200" height="150"><br><a href="http://rumandmonkey.com/widgets/tests/g ... ot/">Which Colossal Death Robot Are <i>You</i>?</a>
Orleron
World Advisor, Co-founder
World Advisor, Co-founder
Posts: 15149
Joined: Fri Sep 14, 2001 9:48 pm
Timezone: GMT-5
Contact:

Post by Orleron » Thu Jul 11, 2002 2:47 pm

Well I imagine if the server restarts everything is lost. However, couldn't we put some code into the OnModule load event that would repost all the messages that WE put up, i.e. the standing quest messages? Player messages would of course be lost.
"Truth has no form."
--Idries Shah
User avatar
Gatorade
Lord of Blithering Idiots
Posts: 117
Joined: Wed Sep 19, 2001 1:44 am

Post by Gatorade » Thu Jul 11, 2002 4:32 pm

Another way to post Designer Messsages would be to create a "Town Crier" that would tell the local player characters what is going on, hints yada yada yada.

Just a thought.
<img src="http://rumandmonkey.com/widgets/tests/g ... sprime.jpg" width="200" height="150"><br><a href="http://rumandmonkey.com/widgets/tests/g ... ot/">Which Colossal Death Robot Are <i>You</i>?</a>
Corlandith
Whiney Peasant
Posts: 14
Joined: Fri Jul 19, 2002 1:49 pm
Location: Worden, IL
Contact:

Post by Corlandith » Fri Jul 19, 2002 3:10 pm

I like this script, it fits in with something I am working on.
A setup we used to run via Prodigy boards, were we all wrote or parts in response to the DM's postings used a Bulletin Board in the Adventurer's hall to recruit for campaigns and this would be perfect if it can write to an outside file for data to be saved in the event of a server outage.

Corlandith
Orleron
World Advisor, Co-founder
World Advisor, Co-founder
Posts: 15149
Joined: Fri Sep 14, 2001 9:48 pm
Timezone: GMT-5
Contact:

Post by Orleron » Fri Jul 19, 2002 3:42 pm

Too bad you can't write to a file w/ NWScript. :(
"Truth has no form."
--Idries Shah
Post Reply