Introduction
This tutorial is intended to get the reader familiar with writing their own custom NWNX plugin for Windows. I assume that the reader has basic C/C++ knowledge and is comfortable with his/her development environment as we work on a simple plugin. During this tutorial we will implement a simple datastructure namely the linked list.
For the wannabee programmers among us, I have started a new developer series on my website which teaches you how to program in C/C++. It is aimed at people with no previous experience. The series can be found here.
The NWNX base class
First I will explain the base class. I has three pure virtual functions who will be called by NWNX, namely OnCreate, OnRelease and OnRequest. The first is called at startup of the server to give the plugin a chance to initialize itself (you could ofcourse also do that in the constructor, but NWNX needs to know if the plugin started up correctly). The second function (OnRelease) is called when the user is shutting down the server. It gives the plugin some time to quit it's jobs and free any memory it uses. The last function OnRequest is called everytime the script asks for a function of your plugin. So, lets assume your plugin is called LIST, then a call in NWScript of NWNX!LIST!ADD will result in a call to the OnRequest function with the parameter ADD.
Besides that, there is also a Log function available, which you can call from your plugin to write a message to the log file. It works the same as the standard C printf function (lookup printf in the MSDN documentation for it's parameters).
The list plugin
Now we know what the base class is, we are going to start developing the list plugin. But before we are going to write any line of code, we first should think about what kind of list we should implement. There are all kinds of list structures available with their pros and cons. In this tutorial I will use the double linked list. In this type of list every item keeps track of the item before and after itself. This way we can easily add and remove items from the list.
Now let's think about what operations should be available. We should at least be able to insert an item in the list, how can we otherwise populate this list? Ofcourse we also want to retreive the stored value some time, so a fetch function would be nice too. And to save memory, it would also be nice if we could remove the items later when we don't need it anymore. So, in total we need at least 3 functions: add, get and remove.
The item structure
You would ofcourse think "why the hell isn't he using the STL vector or list class?". This is a perfectly valid question. For a simple plugin like this, it would make work even easier. But I also want to show you some actual code :-).
So, lets start with defining a structure for our list plugin. To keep things simple we will only support strings (yes, here I will use the STL string class). As defined above we are using a double linked list. This means that we need a least two pointers to the other items. Below I listed the item structure we will use for this plugin:
struct Item {
std::string value;
Item *prev;
Item *next;
}
In this structure we have a field called value. In this field we will save the string before adding this item to the list. The two pointers are used for the list itself.
The list class
Now it is time to create the list class itself. Strictly taken it is not neccessary to overload all three functions, but for educational purposes I will do so. So the final list class will become:
class CNWNXList : public CNWNXBase {
public:
CNWNXList();
~CNWNXList();
// overloaded functions
BOOL OnCreate (const char* LogDir);
char* OnRequest(char* gameObject, char* Request, char* Parameters);
BOOL OnRelease();
protected:
// add item to the list
void Add (char* value);
// remove item from the list
void Remove (char* pos);
// store pointer to first item in pos
void First (char* pos);
// store pointer of next item in pos
void Next (char* pos);
// get the value of the item
std::string Get (char* pos);
Item* head;
};
The first three functions should ring a bell. Those are the overloaded functions which I talked about above. The two protected functions Add, Remove, First, Next and Get are used for respectively adding a new item, removing an item, retreival of the first item in the list, moving to the next item and getting the value of the current item. I will tell you some more about them later. First lets check the implementation of the overloaded functions.
The OnCreate function
It gets one parameter: the logDir. This is the directory in which the log file will be written. For a list class we don't realy need logging functionality and the initialization could also savely done in the constructor. But as I said before, for educational purposes I will give you a simple function. The code below should speak for itself.
BOOL CNWNXList::OnCreate(const char* logDir)
{
char buf[256];
// initialize the base class
sprintf (buf, "%s\\nwnx_list.txt", logDir);
if (!CNWNXBase::OnCreate (buf))
return FALSE;
// write copy information to the log file
Log ("NWNX2 List version 1.0 for Windows.\n");
Log ("Copyright 2004 (C) Jeroen Broekhuizen\n\n");
return TRUE;
}
The OnRelease function
Before we are going to look at the OnRequest, I first want to discuss the OnRelease function. It should release all items currently in the list. You should be able to follow the code below. All it does is walking through the list and releasing the items.
BOOL CNWNXList::OnRelease(void)
{
Item *cur = head;
// loop through the list
while (cur != NULL) {
// save the pointer & move to next item
Item *temp = cur;
cur = cur->next;
// now it is save to release the item
delete temp;
}
head = NULL;
return CNWNXBase::OnRelease ();;
}
Finally the OnRequest function
Now it is time to start working on the OnRequest function. In this function it all happens. The OnRequest function gets three parameters. The first is a characterisation of the object which you are using SetLocalInt with. This parameter falls outside the scope of this tutorial. The second parameter is the request, better said it contains the name of the function the user wants to perform. The last parameter is a which which is passed through to the called function as a parameter. In our case we are going to use it so send a position to the next function. So, lets look at some code:
char* CNWNXList::OnRequest(char* gameObject, char* request, char* parameters)
{
if (stricmp (request, "ADD") == 0)
Add (parameters);
else if (stricmp (request, "REMOVE") == 0)
Remove (parameters);
else if (stricmp (request, "NEXT") == 0) {
// move to next item & return new pointer
stl::string val = Next (parameters);
sprintf (parameters, "%s", val.c_str());
}
else if (stricmp (request, "GET") == 0) {
// get the value & return it
std::string val = Get (parameters);
sprintf (parameters, "%s", val.c_str());
}
return NULL;
}
In the function above you can see that the plugin supports the four functions we defined above. Maybe the NEXT could use a little explaination. It calls the Next function which should return the next value in the list. To return that to NWScript, you should write that into the 'parameters' parameter , which is done with sprintf in this case.
Lets now look at the implementation of the other functions. While reading the code, read the comments carefully as they explain what happens:
void CNWNXList::Add (char* value)
{
// create & fill a new item
Item* newItem = new Item;
newItem->value = value;
newItem->next = head;
newItem->prev = NULL;
if (head != NULL) {
// first item in the list
head->prev = newItem
}
// make the new item the head of the list
head = newItem;
}
void CNWNXList::Remove (char* pos)
{
// convert the position to a pointer
int item_pos = atoi (pos);
Item* item = (Item*)item_pos;
if (item != NULL) {
// save pointer to next item
itoa ((int)item->next, pos, 10);
// get item out of the list
if (item->next) item->next->prev = item->prev;
if (item->prev) item->prev->next = item->next;
// make sure the head of the list keeps valid
if (head == item) head = item->next;
// it is now save to delete the item
delete item;
}
}
void CNWNXList::First (char* pos)
{
// return pointer to first item
itoa ((int)head, pos, 10);
}
void CNWNXList::Next (char* pos)
{
// convert the position to a pointer
int item_pos = atoi (pos);
Item* item = (Item*)item_pos;
if (item == NULL)
return 0;
// move to the next item
item = item->next;
itoa ((int)item, pos, 10);
return val;
}
std::string CNWNXList::Get (char* pos)
{
// convert the position to a pointer
int item_pos = atoi (pos);
Item* item = (Item*)item_pos;
if (item == NULL)
return "";
// return the value of this item
return item->value;
}
As you have seen all functions get a character array pointer with them (except the Add function). In this parameter a pointer to the current item is stored. You must have to live with this type cast, as only strings are supported in NWNX.
Sample usage
So, the plugin is now completely finished! All that is left to do is adding support in your module for this list class. Here are some helper functions you can use in your module.
void List_Add (string s)
{
// add the string to the list
SetLocalString (GetModule(), "NWNX!LIST!ADD", s);
}
void List_Remove (string curpos)
{
object oModule = GetModule();
// get the pointer to the first item
SetLocalString (oModule, "NWNX!LIST!REMOVE", curpos);
return GetLocalString (oModule, "NWNX!LIST!FIRST");
}
string List_Get (string s)
{
object oModule = GetModule();
// retreive the string from the list
SetLocalString (oModule, "NWNX!LIST!GET", s);
return GetLocalString (oModule, "NWNX!LIST!GET");
}
string List_First ()
{
string pos = " ";
object oModule = GetModule();
// get the pointer to the first item
SetLocalString (oModule, "NWNX!LIST!FIRST", pos);
return GetLocalString (oModule, "NWNX!LIST!FIRST");
}
string List_Next (string curpos)
{
object oModule = GetModule();
// get the pointer to the first item
SetLocalString (oModule, "NWNX!LIST!NEXT", curpos);
return GetLocalString (oModule, "NWNX!LIST!NEXT");
}
These functions do exactly what you expect them to do. Next you will find a sample script that uses these functions:
void main ()
{
object oPC = GetEnteringObject ();
List_Add ("first string");
List_Add ("second string");
// list all items
string pos = List_First ();
while (pos != "0") {
string value = List_Get (pos);
SendMessageToPC (oPC, value);
pos = List_Next (pos);
}
// remove all the items
pos = List_First ();
while (pos != "0") {
pos = List_Remove (pos);
}
}
This function above assumes that is it called when a player enters a certain portal or something similar. It than populates the list with to items. In the first loop, the script loops through the list and sends all items as messages to the entering player. Finally, in the second loop, the script removes the strings again (when you don't do that, and you enter the portal twice, the strings will be send to you twice too, as the list still got them from the first time you enter it).
So, we arrived at the end of this tutorial. You have learned how to create a NWNX plugin from scratch and how to call it from NWScript. If something above is not clear, please let me know on the forums!
Good luck!
JeroenB