News Updates

News
Patch for Neverwinter Nights: Enhanced Edition: Apple M1 support, Performance Improvements & more!

Patch for Neverwinter Nights: Enhanced Edition: Apple M1 support, Performance Improvements & more!

September 22 2021 in

Greetings, Neverwinter Nights players and modders!

Today we’re releasing Patch 8193.32 for Neverwinter Nights: Enhanced Edition! This update brings dozens of bug fixes and new features— including some exciting updates for content creators! Curated community content is also getting a new addition: A Hunt Through The Dark, by Markus Schlegel!

Patch Highlights

  • Renderer | Significant performance improvements & better shadows
  • New Curated Content | Play as a drow in Markus Schlegel’s campaign, A Hunt Through The Dark
  • Scriptable UI | Module authors can now create custom UI panels!
  • Apple M1 support | Owners of Apple M1 devices now will have a much better experience playing NWN:EE!
  • More than 100 fixes | Many other new features, script commands & fixes

Changes over build .31

This patch is following the beta build from last week, and includes the following changes on top of it:

  • Renderer: Fixed SetTextureOverride() not working for custom content
  • Nui/Scriptable UI: Fixed combo() widget not decoding UTF8 correctly
  • ResMan: Fixed RESTYPE_JUI having the wrong ID, and added to nwscript.nss
  • nwscript.nss: Minor text/documentation updates

Renderer Improvements

Many improvements have been made to the rendering and shadows engine:

  • Greatly reduced the amount of CPU time spent on rendering shadows (50-90% less) by moving more work to the shaders and just doing things more intelligently.
  • Reduced buffer transfers related to rendering shadows (perf+)
  • Fixed some common shadow rendering issues related to "behind" stencil tests. It does not fix every issue, however, and in rare cases, some issues may become more apparent - but all in all, the benefits should outweigh the drawbacks. The old approach can be restored with using "shadowfliporder 1" in the console.
  • Shadow and beam volumes now use their own unique shaders.
  • Fixed a number of other shadow rendering issues by doing view frustrum clipping in the vertex shaders. This (beyond improving performance) alleviates issues with shadows sometimes being cut off in odd places (and similar).
  • The game now uses tiledata bounding box to determine lower clip of shadow volumes. This fixes shadows not being applied to geometry below the z position of the tile.
  • The game now adjusts shadow alpha based on the vertical distance between the shadow plane used for drawing projections and the projection source. This gives a more correct shading.
  • Removed the “experimental optimized shadow rendering” toggle. It is now always enabled.
  • Added some minor refinements that help reduce the visual oddities caused by shadows being projected by erroneous models.
  • Fixed dynamic shadow rendering performance dropping significantly on certain devices due to the new streaming setup.
  • Added new debug outputs that help content artists to debug models from within the game. This includes rendering mesh bounding boxes, pivots (centers), emitter and light ranges, as well as shadow volumes. The options are accessible through the normal debug panel (ctrl+shift+f12).
  • Optimized generation of shadow volumes to better fit the size of the shadow plane used for rendering shadows. Improves performance in GPU limited situations.
  • Fixed an issue that made shadows fade out too quickly at low view angles.
  • Fixed an issue with the scene manager that made geometry culling less effective.
  • Made the game automatically downsize textures larger than the client GPU supports (instead of just failing to render).
  • Optimized the size of the baked font textures to minimize texture memory footprint.
  • Tweaked the subsurface light algorithm and made the normal debug outputs also apply for water.
  • Removed some redundant GPU data buffer uploads.
  • Fixed soft particles reading the screen depth at an offset position, resulting in occasional halos appearing around objects.
  • Fixed automatic tile texture rotation (`rotatetexture 1`) causing normal and displacement maps to malfunction.
  • Added a new console command ‘printvertexdata’. Prints an overview of the amount of memory currently used for vertex data (model geometry) to the client log.
  • Fixed rendering unlit models with envmap (chargen colour picker shader issue; Halaster model issue).
  • Fixed object view culling not respecting visual transforms, resulting in objects sometimes not appearing if transformed into view.
  • Fixed some static parts being removed after having been auto-combined, resulting in erroneous shadows for some static geometry.
  • Fixed skinmesh bone hierarchy to initial model layout. This solves certain issues caused by skinmeshed bodyparts being dynamically attached and removed.
  • Fixed low quality render mode darkening water too much.

Curated Content: A Hunt Through The Dark

526ed7db848018092e0067e2981d464264801469.jpg

Presenting the classic six-part campaign by Markus Schlegel - A Hunt Through The Dark!

Did you ever want to play the dark side, the bad, the evil, the worst of all, the drow?

Take part in a hunting trip through the Underdark. It is on you to enslave goblins, betray comrades and spoil the plans of your leader Mistress Piwiewien – or not.

Scriptable UI

Modules can now spawn custom UI panels, either defined in NWScript or JSON datafiles.

  • The layouting is done via a constraints solver and currently presents as a row/layout flow.
  • Most native widget types available, including lower-level drawing primitives to create your own.
  • Data binding is two-way between clients and the module, and allows synchronising sliders, input fields, buttons, and window geometry/properties. Mouse input events are supported as well.
  • There is a new include file: nw_inc_nui.nss. There is also a demo script (nw_nui_demo.nss) that showcases some basic widget types. Finally, there is a more complex inspector example (nw_nui_insp.nss) that can show bind values for all open scripted windows.
  • See the mentioned include file and demo for starting points. The new nwscript.nss API is reproduced at the end of this document.

NWScript JSON Datatype Support

NWScript now has a new native datatype, `json`. The API is documented at the end of these notes.

The engine can also serialise game objects to/from json, similar to how the sql data binder works. There is a new data format “.caf” for area objects that combines .are and .git, so you can de/serialise areas into a single json object. The serialisation format is compatible with neverwinter.nim.

There is a small helper library in nw_inc_gff.nss so you can construct GFF data on the fly (e.g. dynamic area/object creation and spawning).

The API also supports JSON Pointer, Diff, Patch and Merge. The SQLite API has been amended for both Campaign DB and custom databases.

Other Features

  • Added more GUI events, some suppressible, when the player interacts.
  • Added device property queries to get player screen width height and scale.
  • Added VM commands to query the game resource manager.
  • PLT textures can now be phenotype-specific.
  • Added a setting that allows players to control their non-PC party members in the same way as DMs do (drag, ctrl+click, etc). This is off by default, and can be enabled in server settings.
  • Mac: Provided universal binary with a build for Apple M1. If you are on M1 and on Steam, you will have to launch the game directly, NOT via Steam, for the binary to run natively. Otherwise, it will run via Rosetta.
  • Mac only: GOG Galaxy SDK no longer supported on x64 and arm64 (no native M1 build for the library).
  • Mac only: Steam SDK/Workshop not supported on M1 (no native M1 build for the binary).
  • Toolset: Added support for custom caster classes.
  • Curated Content UI: Added support for promotion panels to show links to the Vault, Wikis, etc.

Smaller Changes

  • Script compiler now supports `\xFF` style escape sequences to put arbitrary bytes into a string literal. NB: \x00 will terminate the string, do not use.
  • Cleaned up ovr/, moved everything except scripts into keybif.
  • Debug UI: You can now use the RunScript/Chunk Debug UI in SP even when DebugMode is off (for testing convenience).
  • VM: Store/RetrieveCampaignObject, SqlBindObject, SqlGetObject, ObjectToJson, and JsonToObject now support an additional parameter; bSave/LoadObjectState; which handles local variables, effects, action queue and transition info (for doors and triggers).
  • Updated game credits for 2021, moved button out of OC Campaign to top level Movie menu.

Fixes

  • Game: Fixed savegames causing creatures in areas re-triggering the OnAreaEnter event. This was causing HotU chapter 3 to malfunction after saving and loading.
  • Game: Fixed a rare crash when doors were melee-attacked by a null creature (PW-specific crash).
  • Game: Fixed not storing fog clipping distance for areas when saving games.
  • Game: Fixed a crash when showing a polymorphed creature on the party bar.
  • Game: Fixed Steam Workshop modules not showing up if the .mod extension wasn’t all lowercase.
  • Toolset: Fixed inconsistencies with shadow rendering.
  • Toolset: Fixed second story tile fade in “Always” mode not working correctly.
  • Toolset: Areas are now considered modified when undo stack is changed
  • Toolset: Undo now works for mouse wheel object scaling
  • Toolset: Undo now works for Adjust Location dialog
  • Toolset: Fixed a crash when right-clicking a tile with a recently-selected creature on it.
  • Toolset: Fixed Replace All not working in backwards search mode.
  • VM: Fixed crash in CreateArea() due to heap-use-after-free
  • VM: Fixed a crash when SetResourceOverride was targeting a missing/invalid resource.
  • VM: Fixed a memleak every time CopyObject, CreateObject or CreateArea was called.
  • VM: Area serialisation: Do not save item state twice to GFF (this would needlessly duplicate temporary effects and local vars into savegames and serialised objects).
  • Framelimiter: Opening up the radial menu is no longer considered background/modal.
  • Nui: Fixed nuklear receiving input meant for the console.
  • Nui: Fixed nuklear eating mouse clicks that should go to the radial menu instead.

Tyrants of the Moonsea

  • Prevented use of the map inside the navigation cabin after traveling to a destination.
  • Covered some poor camera rotations when transitioning out of the sea map.
  • Removed an xp exploit in Ulblyn's conversation.

NWScript API Additions

int GUIEVENT_COMPASS_CLICK = 15;

int GUIEVENT_LEVELUP_CANCELLED = 16;

int GUIEVENT_AREA_LOADSCREEN_FINISHED = 17;

int GUIEVENT_QUICKCHAT_ACTIVATE = 18;

int GUIEVENT_QUICKCHAT_SELECT = 19;

int GUIEVENT_QUICKCHAT_CLOSE = 20;

int GUIEVENT_SELECT_CREATURE = 21;

int GUIEVENT_UNSELECT_CREATURE = 22;

int GUIEVENT_EXAMINE_OBJECT = 23;

int GUIEVENT_OPTIONS_OPEN = 24;

int GUIEVENT_OPTIONS_CLOSE = 25;

int JSON_TYPE_NULL = 0; // Also invalid

int JSON_TYPE_OBJECT = 1;

int JSON_TYPE_ARRAY = 2;

int JSON_TYPE_STRING = 3;

int JSON_TYPE_INTEGER = 4;

int JSON_TYPE_FLOAT = 5;

int JSON_TYPE_BOOL = 6;

// The player's gui width (inner window bounds).

string PLAYER_DEVICE_PROPERTY_GUI_WIDTH = "gui_width";

// The player's gui height (inner window bounds).

string PLAYER_DEVICE_PROPERTY_GUI_HEIGHT = "gui_height";

// The player's gui scale, in percent (factor 1.4 = 140)

string PLAYER_DEVICE_PROPERTY_GUI_SCALE = "gui_scale";

int PLAYER_LANGUAGE_INVALID = -1;

int PLAYER_LANGUAGE_ENGLISH = 0;

int PLAYER_LANGUAGE_FRENCH = 1;

int PLAYER_LANGUAGE_GERMAN = 2;

int PLAYER_LANGUAGE_ITALIAN = 3;

int PLAYER_LANGUAGE_SPANISH = 4;

int PLAYER_LANGUAGE_POLISH = 5;

int PLAYER_DEVICE_PLATFORM_INVALID = 0;

int PLAYER_DEVICE_PLATFORM_WINDOWS_X86 = 1;

int PLAYER_DEVICE_PLATFORM_WINDOWS_X64 = 2;

int PLAYER_DEVICE_PLATFORM_LINUX_X86 = 10;

int PLAYER_DEVICE_PLATFORM_LINUX_X64 = 11;

int PLAYER_DEVICE_PLATFORM_LINUX_ARM32 = 12;

int PLAYER_DEVICE_PLATFORM_LINUX_ARM64 = 13;

int PLAYER_DEVICE_PLATFORM_MAC_X86 = 20;

int PLAYER_DEVICE_PLATFORM_MAC_X64 = 21;

int PLAYER_DEVICE_PLATFORM_IOS = 30;

int PLAYER_DEVICE_PLATFORM_ANDROID_ARM32 = 40;

int PLAYER_DEVICE_PLATFORM_ANDROID_ARM64 = 41;

int PLAYER_DEVICE_PLATFORM_ANDROID_X64 = 42;

int PLAYER_DEVICE_PLATFORM_NINTENDO_SWITCH = 50;

int PLAYER_DEVICE_PLATFORM_MICROSOFT_XBOXONE = 60;

int PLAYER_DEVICE_PLATFORM_SONY_PS4 = 70;

int RESTYPE_RES = 0;

...

int RESTYPE_CAF = 2082;

// Parse the given string as a valid json value, and returns the corresponding type.

// Returns a JSON_TYPE_NULL on error.

// Check JsonGetError() to see the parse error, if any.

// NB: The parsed string needs to be in game-local encoding, but the generated json structure

// will contain UTF-8 data.

json JsonParse(string sJson);

// Dump the given json value into a string that can be read back in via JsonParse.

// nIndent describes the indentation level for pretty-printing; a value of -1 means no indentation and no linebreaks.

// Returns a string describing JSON_TYPE_NULL on error.

// NB: The dumped string is in game-local encoding, with all non-ascii characters escaped.

string JsonDump(json jValue, int nIndent = -1);

// Describes the type of the given json value.

// Returns JSON_TYPE_NULL if the value is empty.

int JsonGetType(json jValue);

// Returns the length of the given json type.

// For objects, returns the number of top-level keys present.

// For arrays, returns the number of elements.

// Null types are of size 0.

// All other types return 1.

int JsonGetLength(json jValue);

// Returns the error message if the value has errored out.

// Currently only describes parse errors.

string JsonGetError(json jValue);

// Create a NULL json value, seeded with a optional error message for JsonGetError().

json JsonNull(string sError = "");

// Create a empty json object.

json JsonObject();

// Create a empty json array.

json JsonArray();

// Create a json string value.

// NB: Strings are encoded to UTF-8 from the game-local charset.

json JsonString(string sValue);

// Create a json integer value.

json JsonInt(int nValue);

// Create a json floating point value.

json JsonFloat(float fValue);

// Create a json bool valye.

json JsonBool(int bValue);

// Returns a string representation of the json value.

// Returns "" if the value cannot be represented as a string, or is empty.

// NB: Strings are decoded from UTF-8 to the game-local charset.

string JsonGetString(json jValue);

// Returns a int representation of the json value, casting where possible.

// Returns 0 if the value cannot be represented as a float.

// Use this to parse json bool types.

// NB: This will narrow down to signed 32 bit, as that is what NWScript int is.

// If you are trying to read a 64 bit or unsigned integer, you will lose data.

// You will not lose data if you keep the value as a json element (via Object/ArrayGet).

int JsonGetInt(json jValue);

// Returns a float representation of the json value, casting where possible.

// Returns 0.0 if the value cannot be represented as a float.

// NB: This will narrow doubles down to float.

// If you are trying to read a double, you will lose data.

// You will not lose data if you keep the value as a json element (via Object/ArrayGet).

float JsonGetFloat(json jValue);

// Returns a json array containing all keys of jObject.

// Returns a empty array if the object is empty or not a json object, with GetJsonError() filled in.

json JsonObjectKeys(json jObject);

// Returns the key value of sKey on the object jObect.

// Returns a null json value if jObject is not a object or sKey does not exist on the object, with GetJsonError() filled in.

json JsonObjectGet(json jObject, string sKey);

// Returns a modified copy of jObject with the key at sKey set to jValue.

// Returns a json null value if jObject is not a object, with GetJsonError() filled in.

json JsonObjectSet(json jObject, string sKey, json jValue);

// Returns a modified copy of jObject with the key at sKey deleted.

// Returns a json null value if jObject is not a object, with GetJsonError() filled in.

json JsonObjectDel(json jObject, string sKey);

// Gets the json object at jArray index position nIndex.

// Returns a json null value if the index is out of bounds, with GetJsonError() filled in.

json JsonArrayGet(json jArray, int nIndex);

// Returns a modified copy of jArray with position nIndex set to jValue.

// Returns a json null value if jArray is not actually an array, with GetJsonError() filled in.

// Returns a json null value if nIndex is out of bounds, with GetJsonError() filled in.

json JsonArraySet(json jArray, int nIndex, json jValue);

// Returns a modified copy of jArray with jValue inserted at position nIndex.

// All succeeding objects in the array will move by one.

// By default (-1), inserts objects at the end of the array ("push").

// nIndex = 0 inserts at the beginning of the array.

// Returns a json null value if jArray is not actually an array, with GetJsonError() filled in.

// Returns a json null value if nIndex is not 0 or -1 and out of bounds, with GetJsonError() filled in.

json JsonArrayInsert(json jArray, json jValue, int nIndex = -1);

// Returns a modified copy of jArray with the element at position nIndex removed,

// and the array resized by one.

// Returns a json null value if jArray is not actually an array, with GetJsonError() filled in.

// Returns a json null value if nIndex is out of bounds, with GetJsonError() filled in.

json JsonArrayDel(json jArray, int nIndex);

// Transforms the given object into a json structure.

// The json format is compatible with what https://github.com/niv/neverwinter.nim@1.4.3+ emits.

// Returns the null json type on errors, or if oObject is not serializable, with GetJsonError() filled in.

// Supported object types: creature, item, trigger, placeable, door, waypoint, encounter, store, area (combined format)

// If bSaveObjectState is TRUE, local vars, effects, action queue, and transition info (triggers, doors) are saved out

// (except for Combined Area Format, which always has object state saved out).

json ObjectToJson(object oObject, int bSaveObjectState = FALSE);

// Deserializes the game object described in jObject.

// Returns OBJECT_INVALID on errors.

// Supported object types: creature, item, trigger, placeable, door, waypoint, encounter, store, area (combined format)

// For areas, locLocation is ignored.

// If bLoadObjectState is TRUE, local vars, effects, action queue, and transition info (triggers, doors) are read in.

object JsonToObject(json jObject, location locLocation, object oOwner = OBJECT_INVALID, int bLoadObjectState = FALSE);

// Returns the element at the given JSON pointer value.

// See https://datatracker.ietf.org/doc/html/rfc6901 for details.

// Returns a json null value on error, with GetJsonError() filled in.

json JsonPointer(json jData, string sPointer);

// Return a modified copy of jData with jValue inserted at the path described by sPointer.

// See https://datatracker.ietf.org/doc/html/rfc6901 for details.

// Returns a json null value on error, with GetJsonError() filled in.

// jPatch is an array of patch elements, each containing a op, a path, and a value field. Example:

// [

// { "op": "replace", "path": "/baz", "value": "boo" },

// { "op": "add", "path": "/hello", "value": ["world"] },

// { "op": "remove", "path": "/foo"}

// ]

// Valid operations are: add, remove, replace, move, copy, test

json JsonPatch(json jData, json jPatch);

// Returns the diff (described as a json structure you can pass into JsonPatch) between the two objects.

// Returns a json null value on error, with GetJsonError() filled in.

json JsonDiff(json jLHS, json jRHS);

// Returns a modified copy of jData with jMerge merged into it. This is an alternative to

// JsonPatch/JsonDiff, with a syntax more closely resembling the final object.

// See https://datatracker.ietf.org/doc/html/rfc7386 for details.

// Returns a json null value on error, with GetJsonError() filled in.

json JsonMerge(json jData, json jMerge);

// Get oObject's local json variable sVarName

// * Return value on error: json null type

json GetLocalJson(object oObject, string sVarName);

// Set oObject's local json variable sVarName to jValue

void SetLocalJson(object oObject, string sVarName, json jValue);

// Delete oObject's local json variable sVarName

void DeleteLocalJson(object oObject, string sVarName);

// Bind an json to a named parameter of the given prepared query.

// Json values are serialised into a string.

// Example:

// sqlquery v = SqlPrepareQueryObject(GetModule(), "insert into test (col) values (@myjson);");

// SqlBindJson(v, "@myjson", myJsonObject);

// SqlStep(v);

void SqlBindJson(sqlquery sqlQuery, string sParam, json jValue);

// Retrieve a column cast as a json value of the currently stepped row.

// You can call this after SqlStep() returned TRUE.

// In case of error, a json null value will be returned.

// In traditional fashion, nIndex starts at 0.

json SqlGetJson(sqlquery sqlQuery, int nIndex);

// This stores a json out to the specified campaign database

// The database name:

// - is case insensitive and it must be the same for both set and get functions.

// - can only contain alphanumeric characters, no spaces.

// The var name must be unique across the entire database, regardless of the variable type.

// If you want a variable to pertain to a specific player in the game, provide a player object.

void SetCampaignJson(string sCampaignName, string sVarName, json jValue, object oPlayer=OBJECT_INVALID);

// This will read a json from the specified campaign database

// The database name:

// - is case insensitive and it must be the same for both set and get functions.

// - can only contain alphanumeric characters, no spaces.

// The var name must be unique across the entire database, regardless of the variable type.

// If you want a variable to pertain to a specific player in the game, provide a player object.

json GetCampaignJson(string sCampaignName, string sVarName, object oPlayer=OBJECT_INVALID);

// Gets a device property/capability as advertised by the client.

// sProperty is one of PLAYER_DEVICE_PROPERTY_xxx.

// Returns -1 if

// - the property was never set by the client,

// - the the actual value is -1,

// - the player is running a older build that does not advertise device properties,

// - the player has disabled sending device properties (Options->Game->Privacy).

int GetPlayerDeviceProperty(object oPlayer, string sProperty);

// Returns the LANGUAGE_xx code of the given player, or -1 if unavailable.

int GetPlayerLanguage(object oPlayer);

// Returns one of PLAYER_DEVICE_PLATFORM_xxx, or 0 if unavailable.

int GetPlayerDevicePlatform(object oPlayer);

// Deserializes the given resref/template into a JSON structure.

// Supported GFF resource types:

// * RESTYPE_CAF (and RESTYPE_ARE, RESTYPE_GIT, RESTYPE_GIC)

// * RESTYPE_UTC

// * RESTYPE_UTI

// * RESTYPE_UTT

// * RESTYPE_UTP

// * RESTYPE_UTD

// * RESTYPE_UTW

// * RESTYPE_UTE

// * RESTYPE_UTM

// Returns a valid gff-type json structure, or a null value with GetJsonError() set.

json TemplateToJson(string sResRef, int nResType);

// Returns the resource location of sResRef.nResType, as seen by the running module.

// Note for dedicated servers: Checks on the module/server side, not the client.

// Returns "" if the resource does not exist in the search space.

string ResManGetAliasFor(string sResRef, int nResType);

// Finds the nNth available resref starting with sPrefix.

// * Set bSearchBaseData to TRUE to also search base game content stored in your game installation directory.

// WARNING: This can be very slow.

// * Set sOnlyKeyTable to a specific keytable to only search the given named keytable (e.g. "OVERRIDE:").

// Returns "" if no such resref exists.

string ResManFindPrefix(string sPrefix, int nResType, int nNth = 1, int bSearchBaseData = FALSE, string sOnlyKeyTable = "");

// Create a NUI window from the given resref(.jui) for the given player.

// * The resref needs to be available on the client, not the server.

// * The token is a integer for ease of handling only. You are not supposed to do anything with it, except store/pass it.

// * The window ID needs to be alphanumeric and short. Only one window (per client) with the same ID can exist at a time.

// Re-creating a window with the same id of one already open will immediately close the old one.

// * See nw_inc_nui.nss for full documentation.

// Returns the window token on success (>0), or 0 on error.

int NuiCreateFromResRef(object oPlayer, string sResRef, string sWindowId = "");

// Create a NUI window inline for the given player.

// * The token is a integer for ease of handling only. You are not supposed to do anything with it, except store/pass it.

// * The window ID needs to be alphanumeric and short. Only one window (per client) with the same ID can exist at a time.

// Re-creating a window with the same id of one already open will immediately close the old one.

// * See nw_inc_nui.nss for full documentation.

// Returns the window token on success (>0), or 0 on error.

int NuiCreate(object oPlayer, json jNui, string sWindowId = "");

// You can look up windows by ID, if you gave them one.

// * Windows with a ID present are singletons - attempting to open a second one with the same ID

// will fail, even if the json definition is different.

// Returns the token if found, or 0.

int NuiFindWindow(object oPlayer, string sId);

// Destroys the given window, by token, immediately closing it on the client.

// Does nothing if nUiToken does not exist on the client.

// Does not send a close event - this immediately destroys all serverside state.

// The client will close the window asynchronously.

void NuiDestroy(object oPlayer, int nUiToken);

// Returns the originating player of the current event.

object NuiGetEventPlayer();

// Gets the window token of the current event (or 0 if not in a event).

int NuiGetEventWindow();

// Returns the event type of the current event.

// * See nw_inc_nui.nss for full documentation of all events.

string NuiGetEventType();

// Returns the ID of the widget that triggered the event.

string NuiGetEventElement();

// Get the array index of the current event.

// This can be used to get the index into an array, for example when rendering lists of buttons.

// Returns -1 if the event is not originating from within an array.

int NuiGetEventArrayIndex();

// Returns the window ID of the window described by nUiToken.

// Returns "" on error, or if the window has no ID.

string NuiGetWindowId(object oPlayer, int nUiToken);

// Gets the json value for the given player, token and bind.

// * json values can hold all kinds of values; but NUI widgets require specific bind types.

// It is up to you to either handle this in NWScript, or just set compatible bind types.

// No auto-conversion happens.

// Returns a json null value if the bind does not exist.

json NuiGetBind(object oPlayer, int nUiToken, string sBindName);

// Sets a json value for the given player, token and bind.

// The value is synced down to the client and can be used in UI binding.

// When the UI changes the value, it is returned to the server and can be retrieved via NuiGetBind().

// * json values can hold all kinds of values; but NUI widgets require specific bind types.

// It is up to you to either handle this in NWScript, or just set compatible bind types.

// No auto-conversion happens.

// * If the bind is on the watch list, this will immediately invoke the event handler with the "watch"

// even type; even before this function returns. Do not update watched binds from within the watch handler

// unless you enjoy stack overflows.

// Does nothing if the given player+token is invalid.

void NuiSetBind(object oPlayer, int nUiToken, string sBindName, json jValue);

// Swaps out the given element (by id) with the given nui layout (partial).

// * This currently only works with the "group" element type, and the special "_window_" root group.

void NuiSetGroupLayout(object oPlayer, int nUiToken, string sElement, json jNui);

// Mark the given bind name as watched.

// A watched bind will invoke the NUI script event every time it's value changes.

// Be careful with binding nui data inside a watch event handler: It's easy to accidentally recurse yourself into a stack overflow.

int NuiSetBindWatch(object oPlayer, int nUiToken, string sBind, int bWatch);

// Returns the nNth window token of the player, or 0.

// nNth starts at 0.

// Iterator is not write-safe: Calling DestroyWindow() will invalidate move following offsets by one.

int NuiGetNthWindow(object oPlayer, int nNth = 0);

// Return the nNth bind name of the given window, or "".

// If bWatched is TRUE, iterates only watched binds.

// If FALSE, iterates all known binds on the window (either set locally or in UI).

string NuiGetNthBind(object oPlayer, int nToken, int bWatched, int nNth = 0);

// Returns the event payload, specific to the event.

// Returns JsonNull if event has no payload.

json NuiGetEventPayload();

// Get the userdata of the given window token.

// Returns JsonNull if the window does not exist on the given player, or has no userdata set.

json NuiGetUserData(object oPlayer, int nToken);

// Sets an arbitrary json value as userdata on the given window token.

// This userdata is not read or handled by the game engine and not sent to clients.

// This mechanism only exists as a convenience for the programmer to store data bound to a windows' lifecycle.

// Will do nothing if the window does not exist.

void NuiSetUserData(object oPlayer, int nToken, json jUserData);