An Introduction to Neverwinter Nights
Introduction
This article introduces NeverwinterNights for artifical life (A-Life) developers. It starts with a basic architectural overview of the system and delves into how it processes the scripts and actions of non-player characters.
Client Server Architecture
NeverwinterNights (NWN) consists of a client server architecture, where many clients can connect to a server and control their character within a virtual world. The clients use a set of standard 3d models and textures which are provided by the makers of NWN, Bioware. The server runs a module that holds the maps, objects, and compiled code. The server contains a scheduler and foundation code for simulating the game world.
Construction of a module can be as easy or complex as needed. Bioware ships a graphical toolkit, complete with dialog wizards and a lightwieght IDE. The world is divided into distinct areas with a variety of static content. Trees, buildings and rudimentary terrain can be painted down in a WYSIWYG manner. Dynamic content can be developed in a C-like language, NWScript. There are a variety of slots for scripts to allow the server to react to players and NPCs. For example, a script may be run when a player logs in, or when an NPC dies. Scripts may execute at regular intervals or a time specified by the scripts, themselves.
As you can see, NWN as a simulation environment relies on the power and flexibility of the scripting system. Luckily the language is powerful and the non-player characters have a reasonable amount of script callbacks. Specifically, there are hooks to reflect sensory input and the creature's lifecycle. The server interprets the user's actions and applies a number of precompiled rules. These rules may trigger the interpreter to act on the NWScript scripts.
Independent of the player's actions, a scheduler handles the passing of time and the moment-by-moment actions of the non-player characters. From a user's perspective the world consists of many creatures and scripts all running in parallel. But in reality, a single scheduler processes each script - each NPC action - each player action - one at a time in a single threadded manner.
The NWScript Language
Bioware has produced an object-based language that interfaces with the game world through an extensive collection of functions. While the language does not support the creation of new objects or classes, this can be simulated by piggy-backing off of standard objects. In the case of the MemeticAI Toolkit, first-class objects, like memes and generators, are actually invisible objects with custom variables attached to them. While this adds some overhead, it also simplifies the process of garbage collection. To destroy a memetic object and all of it associated state, a true in-game object is destroyed.
While the language is very C-like, the largest learning curve lies in the functions that are provided with the toolkit. Here are general types of functions that are provided:
- Scheduler functions; these are functions that execute scripts, control the actions of a character, or otherwise give you fundamental scheduling abilities.
- NPC Actions; the largest collection of functions in NWScript are the actions. These cause the NPC to move, speak, engage in conversations and activate the fundamental behaviors supported by the NWN game engine. The scheduler functions can operating on these actions and the "action queue" that is owned by every NPC.
- Game object functions; there are a number functions that help you manipulate fundamental game objects, events and unique game components like spells, sounds, visual effects and the client camera.
- Low level data structures; most of the core objects can be configured and inserted into a module via the graphical toolkit. These functions allow scripts to read (and sometimes change) the properties of the objects. It's important to understand that many properties are read-only. Worse, not every property is accessible via the API; this is an area that needs to be improved.
- High level data structures; as a byte-compiled, interpreted, dynamic language, NWScript allows you to add arbitrary variables to any in-game object. These data structures are not accessed like Java or C++; there are no member operators or pointers. Explicit functions are called to set, get or delete the variables. It should also be noted that while the language does not support complex data structures, like lists, the Memetic AI Toolkit builds these using the basic high level data manipulation functions.
As you can see, we have an environment, object, creatures and scripts. Let's have a look at this from the perspective of the game engine. How and when do these scripts get called...
Callbacks: Orchestrating Behavior
At the heart of the NWN server, is an event loop that processes the game state and updates variables through a combination of compiled code and interpreted scripts. As this data changes, clients may be notified to update their display and keep the users up to date.
If we look at the structure of the system, it is possible to see that there is a clear hierarchy of module
area
object where each component has slots for scripts. It the responsibility of the internal scheduler to call these scripts when it is appropriate.
Remember, this is an object-based system. All the components of the system are true objects. As such, they all have fundamental properties such as a name or tag. But more importantly, every object in the game has an action queue. This queue is list of actions where the first one in, is the first one processed. The next item is not processed until the action is completed. The scheduler preserves the order of these actions, as they are requested, unless the queue is explicitly cleared through the scripting language.
Next, we have the client requests that occur at moment in the game. These requests may cause an internal action (like logging out) to occur, or may be interpret as a request to change the player's action queue. Remember, everything has an action queue.
In fact this action queue is so pervasive, it is possible for a module or area to enqueue actions. Although the vast majority of standard actions will be ignored as objects have no in-game representation. You can still call a script or manipulate datastrctures by calling functions like ActionDoCommand().
As the system processes each action, new internal events may occur. For example, characters may die, causing death scripts to run; they may spring a trap, becoming damaged; or an NPC may sense something. Sensory notification takes up the largest amount of the scheduler's time. Each NPC, each player, and most objects will receive notifications as they are harmed, clicked on, or destroyed.
Finally, the scheduler and the internal system manage combat. Each NPC and player engaged in combat is processed in a "combat round" and given opportunities to strike, parry, response with an attack of opportunity, etc. Regrettably a number of hard-coded responses have been added to NWN. These effective destroy the purity of the script-driven AI by causing NPCs to react to combat automatically. From a developer's perspective actions may appear to spontaneously appear on the action queue; the reality appears to be that, for the sake of efficiency, the low level combat engine "helps out". While this is generally a minor nuisance it has been known to frustrate the purist...
Rounds, Time Slices, and TMI
Stack underflows?
Action Queue
TimeSlices
TMI
ActionDoCommand
DelayCommand
Persistance: The Save Game Fallacy
Development