no we don't have a nwndatabase....is that really required?
No. I just use an item the players have that they can't drop. Things such as dicebag, autofollow widget, rulebook work perfect for this. You don't need to give them an extra item just for the quests. However some people prefer giving a separate item. This way a DM can take the item away and give them a brandnew one and the player can do the quests over. Totaly up to you.
One other note. I hope that you are working directly with the module and not planning on exporting/importing quests. As far as I know you still can not export the journal and it wold be a pain in the butt for the other person to redo it.
Ok so I suppose we could start with the easiest thing first. We plan the quest out. For this simple example I will use the example already posted above. We'll say an NPC (Betty) needs a wolf pelt.
So let's open up the journal editor first.
-Make sure "Root" is highlighted and click the "Add" icon on the left.
-Now we have a category or a new quest basically. You'll see the different fields for name, tag, priority, XP and comments below. All we're going to worry about is the name and the tag. (If this quest will only be giving XP you can put a value in the xp field and then you can skip the reward script part that we will talk about below) however I've noticed that the amount you put in that field is not always the amount you actuall get. I think that it is altered based on your game difficulty)
-For examples sake we'll name this category/quest Betty's Wolf Pelt.
-For the tag we can call it "BETTY_PELT"(remember this. This will also be the tag of the NPC)
-Click apply.
-Now while the name of the quest is highlighted, click the "Add" icon on the left again.
-We just made the first ID (ID0001)of the quest. Notice the ID, finish category, and text fields below.
-All we need to worry about is the text. This this is the first ID of the quest this will be the part that says we agreed to help Betty find a wolf pelt. So we can just type that in..literaly if you want. "You have agreed to help Betty find a wolf pelt". Now when a player looks at their quest journal they will see this for one of their current quests.
-Now highlight the name of the quest again and click "Add" once more. This will add and ID 0002 node.
-For this quest ID 2 will complete the quest. So we want to check the finish category box and then the text for this one might be, "You gave Betty a wolf pelt. She was so happy she made out with you.";)This will show up in the completed section of the players quest journal.
So that is it for setting up the journal for this particular quest.
Now for the conversation:
You can give the conversation whatever name/tag you want. It won't effect anything.
Note: At the end of the lines i put a three character reference, for the scripts that I will put down below, like "SC1"
NPC Greeting1: Hi there. I was wondering if you could help me find a wolf pelt. I will pay you of course.(Starting conditional, line appears if quest id 1 exist on a database item the player carries, "SC0")
~~PC Response1: Sure I'll get a wolf pelt for you.(ActionsTaken, Add quest ID/Int 1 to database item and journal. Quest has started,"AT1"))
~~~~NPC Response1: Thank you so much. You can find some wolves in the Wild Forest.
~~PC Response2: No way. I love wolfs. I would never hurt one.
NPC Greeting 2: Have you found a wolf pelt for me yet?(Starting conditional, line appears if current quest id is 1,"SC1")
~~PC Response1: Yes I have it right here for you.(starting condtional, line appears only if the player possesses the wolf pet Item, "IC1")(ActionsTaken, advance quest ID/Int to 2 on database item and journal, "AT1")
~~~~NPC Response1: I can finaly finish my coat. Thank you very much and here is your reward...(ActionsTaken, give the player reward, "REW1")
~~PC Response2: I'm sorry no. I haven't found one yet.
NPC Greeting 3: Thanks again for finding that wolf pelt for me.(StartingConditional, line appears when journal ID is 2, "SC2")
And now the scripts:
Note: For all the scripts below I am using the "dmfi_pc_dicebag" as the player database item. Make sure you put in the tag/res ref of whatever item you will use.
First if you want persistence this is what I am currently useing for my persistent journal include type script. You will notice at the bottom it shows you what to add to your mods "OnClientEnter" script:
//:://////////////////////////////////////
// Persistent Journal Include Script.
//:://////////////////////////////////////
// _pw_journal_inc
//
// This version uses an item token carried by the player for persistent storage of
// the journal variables.
//:://////////////////////////////////////
#include "nw_i0_plot"
const string PERSISTENT_JOURNAL_TOKEN_TAG = "dmfi_pc_dicebag";
const string PERSISTENT_JOURNAL_TOKEN_RESREF = "dmfi_pc_dicebag";
//:://////////////////////////////////////
// object GetPersistentJournalToken( object oPC)
// Returns the persistent journal token item carried by the specified player. If the
// player specified is invalid it returns OBJECT_INVALID. If the player is not carrying
// a persistent journal token item, one is created on him and it is returned.
//:://////////////////////////////////////
// Parameters: object oPC - the player to retrieve the token from.
//
// Returns: OBJECT_INVALID if oPC is not a valid PC. Otherwise returns the persistent
// token item he is carrying or the one created on him if he didn't have one.
//:://////////////////////////////////////
object GetPersistentJournalToken( object oPC);
object GetPersistentJournalToken( object oPC)
{ if( !GetIsPC( oPC)) return OBJECT_INVALID;
object oPJToken = (HasItem( oPC, PERSISTENT_JOURNAL_TOKEN_TAG) ? GetItemPossessedBy( oPC, PERSISTENT_JOURNAL_TOKEN_TAG) : CreateItemOnObject( PERSISTENT_JOURNAL_TOKEN_RESREF, oPC, 1));
if( GetIsObjectValid( oPJToken))
{ SetItemCursedFlag( oPJToken, TRUE);
SetPlotFlag( oPJToken, TRUE);
}
return oPJToken;
}
//:://////////////////////////////////////
// void AddPersistentJournalQuestEntry( string sCategoryTag, int nEntryID, object oPC, int bAllPartyMembers = TRUE, int bAllPlayers = FALSE, int bAllowOverrideHigher = FALSE)
// Add a journal entry to a player using the persistent journal system. Works exactly
// like AddJournalQuestEntry.
//:://////////////////////////////////////
// Parameters: string sCategoryTag - the journal category tag.
// int nEntryID - the journal entry ID.
// object oPC - the player to have his journal updated.
// int bAllPartyMembers - TRUE to add the entry to oPC's entire party.
// FALSE to add the entry to oPC only.
// int bAllPlayers - TRUE to add the entry to all PCs in the module.
// FALSE to add the entry to oPC only.
// int bAllowOverrideHigher - TRUE overrides restriction that nState must be > current Journal Entry.
// FALSE enforces restriction that nState must be > current Journal Entry.
//
// Returns: None.
//:://////////////////////////////////////
void AddPersistentJournalQuestEntry( string sCategoryTag, int nEntryID, object oPC, int bAllPartyMembers = FALSE, int bAllPlayers = FALSE, int bAllowOverrideHigher = FALSE);
void AddPersistentJournalQuestEntry( string sCategoryTag, int nEntryID, object oPC, int bAllPartyMembers = FALSE, int bAllPlayers = FALSE, int bAllowOverrideHigher = FALSE)
{ if( (sCategoryTag == "") || (nEntryID < 0) || !GetIsPC( oPC)) return;
object oPJToken = GetPersistentJournalToken( oPC);
if( !GetIsObjectValid( oPJToken)) return;
if( bAllPlayers)
{ object oPlayer = GetFirstPC();
while( GetIsPC( oPlayer))
{ AddPersistentJournalQuestEntry( sCategoryTag, nEntryID, oPlayer, FALSE, FALSE, bAllowOverrideHigher);
oPlayer = GetNextPC();
}
}
else if( bAllPartyMembers)
{ object oParty = GetFirstFactionMember( oPC);
while( GetIsObjectValid( oParty ))
{ AddPersistentJournalQuestEntry( sCategoryTag, nEntryID, oParty, FALSE, FALSE, bAllowOverrideHigher);
oParty = GetNextFactionMember( oPC);
}
}
else
{ AddJournalQuestEntry( sCategoryTag, nEntryID, oPC, bAllPartyMembers, bAllPlayers, bAllowOverrideHigher);
int iEntry = GetLocalInt( oPC, "NW_JOURNAL_ENTRY" +sCategoryTag);
if( iEntry)
{ SetLocalInt( oPJToken, "NW_JOURNAL_ENTRY" +sCategoryTag, iEntry);
string sCategories = GetLocalString( oPJToken, "NW_JOURNAL_CATEGORIES");
string sSearch = sCategories;
while( sSearch != "")
{ int iPos = FindSubString( sSearch, "~#~");
switch( iPos)
{ case -1: if( sSearch == sCategoryTag) return;
sSearch = "";
break;
case 0: sSearch = GetStringRight( sSearch, GetStringLength( sSearch) -3);
break;
default: if( GetStringLeft( sSearch, iPos) == sCategoryTag) return;
sSearch = GetStringRight( sSearch, GetStringLength( sSearch) -(iPos +3));
break;
}
}
sCategories += ((sCategories == "") ? "" : "~#~") +sCategoryTag;
SetLocalString( oPJToken, "NW_JOURNAL_CATEGORIES", sCategories);
}
else DeleteLocalInt( oPJToken, "NW_JOURNAL_ENTRY" +sCategoryTag);
}
}
//:://////////////////////////////////////
// void RemovePersistentJournalQuestEntry( string sCategoryTag, object oPC, int bAllPartyMembers = TRUE, int bAllPlayers = FALSE)
// Removes a journal entry from a player using the persistent journal system. Works
// exactly like RemoveJournalQuestEntry.
//:://////////////////////////////////////
// Parameters: string sCategoryTag - the journal category tag.
// int nEntryID - the journal entry ID.
// object oPC - the player to have his journal updated.
// int bAllPartyMembers - TRUE to remove the entry from oPC's entire party.
// FALSE to remove the entry from oPC only.
// int bAllPlayers - TRUE to remove the entry from all PCs in the module.
// FALSE to remove the entry from oPC only.
//
// Returns: None.
//:://////////////////////////////////////
void RemovePersistentJournalQuestEntry( string sCategoryTag, object oPC, int bAllPartyMembers = TRUE, int bAllPlayers = FALSE);
void RemovePersistentJournalQuestEntry( string sCategoryTag, object oPC, int bAllPartyMembers = TRUE, int bAllPlayers = FALSE)
{ if( (sCategoryTag == "") || !GetIsPC( oPC)) return;
object oPJToken = GetPersistentJournalToken( oPC);
if( !GetIsObjectValid( oPJToken)) return;
if( bAllPlayers)
{ object oPlayer = GetFirstPC();
while( GetIsPC( oPlayer))
{ RemovePersistentJournalQuestEntry( sCategoryTag, oPlayer, FALSE, FALSE);
oPlayer = GetNextPC();
}
}
else if( bAllPartyMembers)
{ object oParty = GetFirstFactionMember( oPC);
while( GetIsObjectValid( oParty))
{ RemovePersistentJournalQuestEntry( sCategoryTag, oParty, FALSE, FALSE);
oParty = GetNextFactionMember( oPC);
}
}
else
{ RemoveJournalQuestEntry( sCategoryTag, oPC, bAllPartyMembers, bAllPlayers);
DeleteLocalInt( oPC, "NW_JOURNAL_ENTRY" +sCategoryTag);
DeleteLocalInt( oPJToken, "NW_JOURNAL_ENTRY" +sCategoryTag);
string sCategories = GetLocalString( oPJToken, "NW_JOURNAL_CATEGORIES");
int iPos = FindSubString( sCategories, "~#~" +sCategoryTag +"~#~");
if( iPos == -1)
{ iPos = FindSubString( sCategories, sCategoryTag +"~#~");
if( iPos == -1)
{ iPos = FindSubString( sCategories, "~#~" +sCategoryTag);
if( iPos == -1) sCategories = ((sCategories == sCategoryTag) ? "" : sCategories);
else sCategories = GetStringLeft( sCategories, iPos);
}
else sCategories = GetStringRight( sCategories, GetStringLength( sCategories) -(GetStringLength( sCategoryTag) +3));
}
else sCategories = GetStringLeft( sCategories, iPos) +GetStringRight( sCategories, GetStringLength( sCategories) -(GetStringLength( sCategoryTag) +3));
if( sCategories == "") DeleteLocalString( oPJToken, "NW_JOURNAL_CATEGORIES");
else SetLocalString( oPJToken, "NW_JOURNAL_CATEGORIES", sCategories);
}
}
//:://////////////////////////////////////
// void RestorePersistentJournal( object oPC)
// Restores a players journal by adding all his journal entries saved on the
// persistent journal token.
//:://////////////////////////////////////
// Parameters: object oPC - the player to have his journal updated.
//
// Returns: None.
//:://////////////////////////////////////
void RestorePersistentJournal( object oPC);
void RestorePersistentJournal( object oPC)
{ if( !GetIsPC( oPC)) return;
object oPJToken = GetPersistentJournalToken( oPC);
if( !GetIsObjectValid( oPJToken)) return;
string sCategories = GetLocalString( oPJToken, "NW_JOURNAL_CATEGORIES");
while( sCategories != "")
{ string sCategory = "";
int iPos = FindSubString( sCategories, "~#~");
switch( iPos)
{ case -1: sCategory = sCategories;
sCategories = "";
break;
case 0: sCategories = GetStringRight( sCategories, GetStringLength( sCategories) -3);
break;
default: sCategory = GetStringLeft( sCategories, iPos);
sCategories = GetStringRight( sCategories, GetStringLength( sCategories) -(iPos +3));
break;
}
if( sCategory != "")
{ int nEntry = GetLocalInt( oPJToken, "NW_JOURNAL_ENTRY" +sCategory);
AddJournalQuestEntry( sCategory, nEntry, oPC, FALSE, FALSE, TRUE);
}
}
}
// OnClientEnter
//#include "_pw_journal_inc"
//
//void main()
//{ object oEntering = GetEnteringObject();
// if( !GetIsObjectValid( oEntering)) return;
//
// RestorePersistentJournal( oEntering);
//}
Next is the script that advances the journal to the next ID and adds/advances an integer variable on a player database item by 1. Adding the separate variable is a way to determine which quest id you are on which we will check in one of the other scripts below "AT1":
//Universal ActionsTaken script. Advances the quest ID by 1 and an integer
//variable on a player database item by 1.
#include "_pw_journal_inc"
void main()
{
object oPC = GetPCSpeaker();
object oItem = GetItemPossessedBy(oPC,"dmfi_pc_dicebag");//Uses item tag.
string sTag = GetTag(OBJECT_SELF);
int iInt = 1 + (GetLocalInt(oItem,sTag));
SetLocalInt(oItem,sTag,iInt);
//Use this line for persistence, if using "_pw_journal_inc", to add journal entry.
AddPersistentJournalQuestEntry(sTag,iInt,oPC,FALSE,FALSE,FALSE);
//Basic NWN fucntion to add journal entry. Non-persistent.
//AddJournalQuestEntry(sTag, iInt, oPC, FALSE, FALSE, FALSE);
}
This starting conditional script is used to check if the quest has been started. "SC0":
int StartingConditional()
{
object oItem = GetItemPossessedBy(GetPCSpeaker(),"dmfi_pc_dicebag");
string sTag = GetTag(OBJECT_SELF);
if (GetLocalInt(oItem,sTag) < 1) return TRUE;
return FALSE;
}
This starting conditional script is used to check if the current quest ID is 1, "SC1":
int StartingConditional()
{
object oItem = GetItemPossessedBy(GetPCSpeaker(),"dmfi_pc_dicebag");
string sTag = GetTag(OBJECT_SELF);
if (GetLocalInt(oItem,sTag) == 1) return TRUE;
return FALSE;
}
This starting conditional script is used to check if the current quest ID is 2, "SC2";
int StartingConditional()
{
object oItem = GetItemPossessedBy(GetPCSpeaker(),"dmfi_pc_dicebag");
string sTag = GetTag(OBJECT_SELF);
if (GetLocalInt(oItem,sTag) == 2) return TRUE;
return FALSE;
}
If you have more parts to your quest you can just keep going with those starting conditionals 3, 4, 5, etc..
Those scripts above are the universal ones. You can use those for all your basic simple quests to advance quest ID's and store the info on a database item.
Now each quest will still have their own unique scripts that pertain to that quest alone. Like the rewards and item possesion checks. So now for the examples of those for this example quest:
First the starting conditional that checks if you have the pelt, "IC1":
int StartingConditional()
{
if (GetItemPossessedBy(oPC, "WOLF_PELT") != OBJECT_INVALID) return FALSE;
return TRUE;
}
And here is an example reward and take item scirpt "REW1":
void main()
{
object oPC = GetPCSpeaker();
object oItemTake = GetItemPossessedBy(oPC, "WOLF_PELT);
GiveXPToCreature(oPC, 1000);
//GiveGoldToCreature(oPC, 1000);
//CreateItemOnObject("resref_of_item", oPC);
DestoryObject(oItemTake, 0.0);
}
You will also of course need a script that drops the wolf pelt when you kill a wolf or whatever. Can help with that too if you need it.
Well. That is pretty much it. Might look like a lot but really it's easier IMO than using the plot wizard and ending up with like 500 scripts that do pretty much the same thing over and over.
So just make sure you fix up your "OnClientEnter" script for the persistence and make sure you got the right tag/res ref for the database item. Dot your t's and cross yer i's and all that.
I hope this helps ya out and I hope I wasnt too confusing. I also hope I didn't forget anything. Good luck.