Everyone

This Content can be viewed by everyone, even people with no account.

Building Server Portals

To set up a Server Portal:

Triggers, Doors, and Usable Placeables are the customary objects used for inter-server travel in ALFA. 

 

Scripts:

In the OnClick or OnUsed script of the object, set the script = acr_trg_portalstart

Remaining scripts should be the default acr_* scripts for the portal object type.

 

Portal Waypoints:

Create a Waypoint with a Tag of the form <SourceServerId>_PortalTo_<DestinationServerId>_<PortalNumber>, where the ServerIds are 3 digit integers, and the PortalNumber is a 2 digit integer, i.e. for the Arrival Waypoint of a portal from Baldur's Gate (Server ID 010) to The Silver Marches (Server ID 003), the first such arrival WP is given the Tag: 010_PortalTo_003_01

 

Portal Variables:

int ACR_PORTAL_DEST_SERVER = (1 - 999 ) should be set to the Destination Server ID#

 

int ACR_PORTAL_NUM =  (1 - 99) the destination waypoint Portal Number

 

int ACR_PORTAL_ADJACENT =  If set to 1, the portal doesn't use a 24-hour cooldown, otherwise it uses the cooldown.

 

-Thanks to the ALFA Tech Dept.

ACR Area Instancing System

ACR Area Instancing Overview

The ACR supports an "Instanced Area" system that allows multiple copies of an area to be instantiated at runtime, based on a "template" area that you design in the toolset.  The template can be any standard area, except its content and associated scripting logic should be aware that there might be multiple instances of the area.

Area instances are useful if you want to provide multiple, isolated copies of an area.  One use might be to instance an area based on player party, for example, so multiple parties get their own private "copy" of an area.  Other uses might be for player housing, player "travel areas" (like a caravan or a vehicle interior), certain special spells like "Rope Trick", or anywhere else that creating multiple copies of the same area might be valuable.

Anything that happens in one instance doesn't directly impact other instances.

There isn't any hard limit on how many instances of an area can be created, although there is some memory overhead.  The overhead on the server is much less than creating static copies of an area in the toolset, though, because the server just re-uses the walkmesh information from the template area when the instancing system is used.

Script code is needed to manage the creation of area instances and moving players into an area instance (as a static transition will only go to one instance).  There are a series of script functions in the acr_area_instance_i header that are used to create and manage area instances.

Area instance lifetimes

It is currently not possible to delete an area instance once it has been created.  As a result, the area instancing system keeps a "cache" of instances that have been created for a given template area.  When a new instance is requested with ACR_CreateAreaInstance, the instancing system first checks whether there was a previously created instance; if so, it is used.  Otherwise, a brand new area instance is set up.

The first time a brand new area instance is created, all of the statically placed objects are spawned (if you had any).  Subsequently, if the area instance is cleaned up and reused, then statically placed objects are assumed to be in the right state.  It's a good idea to use the spawn system and not statically place objects that are destroyable by the player.

In order to deal with this, areas that are instanceable should be designed to be "resettable", that is, returned to their default state by script code.  There is a local variable attached to the template area, ACR_AREA_INSTANCE_ON_DELETE_SCRIPT, which names a script to run when an area is eligible to be cleaned up. The job of this script is to determine whether the instance should be cleaned up or not, and if so, to "reset" the area to a pristine state.  For example, the script might delete all spawned creatures or treasure on the area so that the next player using the area gets a pristine state.  Once the on delete script has cleaned up an area instance, a future call to ACR_CreateAreaInstance can return that same area object.  It's your responsibility to delete any unwanted objects that might be lingering in the instance from the area cleanup script.

Areas have an associated on create script that is designated with a local variable on the area template, ACR_AREA_INSTANCE_ON_CREATE_SCRIPT.  This script is called every time ACR_CreateAreaInstance secures an area to return.  The area might be a previously reused cached instance, or a brand new instance.  The on create script gives you a chance to set up any state that needs to be created when an area is going to be used.

Instanced area cleanup can be handled either automatically or by script code.  By default, area instances are automatically cleaned up after 60 seconds   To enable automatic cleanup, set the ACR_AREA_INSTANCE_CLEANUP_DELAY variable to something other than -1 (or leave it at the default of zero to trigger the default time of 60 seconds).  This variable specifies a delay (whole or fractional count of second) after which an area instance that is empty of all players will be automatically cleaned up.

When an area is going to be cleaned up, either automatically or by an explicit call to ACR_ReleaseAreaInstance, the on delete script is called.  If the on delete script returns FALSE, then the area isn't cleaned up (and if automatic cleanup was enabled, the cleanup script will be called again after the cleanup delay to check if the area should be kept alive).  Otherwise, the area is made available for future use once the cleanup script returns.

Getting into (and out of) an area instance

Normal areas often have static connection points in the form of area transitions that link to an area.  This doesn't work as normal for an instanced area, though, as there might be multiple copies of an area instance active at a given time.

You will need to write some script code to transfer the player into an appropriate area instance (possibly creating one if necessary), when an instance is called for.  This code might run as a part of a dialog or transition onuse script; it would typically find an appropriate already existing area instance (say the one used by the party leader), or create a new area instance with ACR_CreateAreaInstance.  Then, this script code would jump the player into an appropriate location within the area. 

Another option might be to have an area OnClientEnter script that checks if the entering player is supposed to be in a particular instance of an area (maybe with a local variable set on the PC object to designate this), if the current area is not an instance as returned by ACR_IsInstancedArea.  If the player had an instance, the script could move the PC into the instance, otherwise it could create an instance for the PC and move the PC into that instance.  That would let you continue to use a conventional static transition into the area (but you'll area transition twice, which is a bit slower, when entering it).

Remember that waypoints are relative to an area instance, so if you want to move a player into an area by jumping to a waypoint, you'll have to find a way to get the waypoint for the right area instance.  One way to do this would be to have the area on create script find the waypoint in the newly created area instance, and store it as a local object variable on the area instance object.  Then, you could use GetLocalObject() to find the appropriate entry waypoint for a given area instance.

Area linkages outside of an area instance will work like normal (as long as they don't link to another area instance, in which case the above rules apply).  You might find it useful to provide dynamic linkages in some cases, though, like in an instance travel caravan.  These linkages could be customized by the area on create script.

It's a good idea to turn on automatic area cleanup for your area instances.  This allows them to be deleted after a "cool down" time once nobody is in the area.  A "cool down" time enables automatic area cleanup, and is optional (specified by ACR_AREA_INSTANCE_CLEANUP_DELAY).  If enabled, the area instancing system will keep track of when nobody is in the area and after the "cool down" time elapses, the area will be automatically cleaned up.  This frees you from having to worry about manually getting rid of your area instances when they aren't in use.

It can sometimes be useful to keep an empty area instance around for a short time, like several minutes.  For example, you might want to let a player return to a previously created instance in case they had forgotten something on the ground, if they remember soon enough.  It's up to you to design how this should work for your area (and how the player gets back into their old instance).

Instanced area local variables

There are a series of controlling local variables that you can set on an area object that will be used as an instanced area template.  Make sure to use the correct case (and variable type) for these variables when you set them in the toolset!

string ACR_AREA_INSTANCE_ON_CREATE_SCRIPT  : This variable gives the name of a script that runs when the area instance is being "created" and will be returned from ACR_CreateAreaInstance().  The script name shouldn't have an extension.  It's optional to specify an on create script, but if you need to do some work when the area is going to be used, then the on create script may come in handy.

string ACR_AREA_INSTANCE_ON_DELETE_SCRIPT  : This variable gives the name of a script that runs when the area instance is being "cleaned up".  The script name shouldn't have an extension.  It's optional to specify an on delete script, but if the area can at all have dynamically created objects spawned or removed from it, then you should provide an on delete script that resets the area to a clean state.

float ACR_AREA_INSTANCE_CLEANUP_DELAY  : This variable expresses the time after which an area instance with no players is automatically cleaned up (unless vetoed by the on delete script).  The time is expressed as a count of seconds (fractional values permitted), just like you would use for a DelayCommand time.  If the variable is zero or isn't set, then automatic cleanup isn't enabled for the area.  If you want to enable automatic cleanup and clean up an instanced area right away, just set the cleanup delay to 1 second.

Area instance on create script

An area on create script (optional) should looks like this:

void main()
{
   object Area = OBJECT_SELF; // The area being created
   // Set up the new area instance as desired.  This
   // example finds the entry waypoint for the area and
   // saves it on the instanced area object so that it can
   // be referred to for jumping players into the area.
   //
   // It is up to you to decide how this should work for
   // your area.  This example uses a waypoint with the
   // tag of "wp_instance_start" that is intended to be
   // placed in the toolset, but you can do that however
   // you would prefer.
   object Obj = GetFirstObjectInArea(Area);
   while (Obj != OBJECT_INVALID)
   {
      int Type = GetObjectType(Obj);
      if (Type == OBJECT_TYPE_WAYPOINT &&
          GetTag(Obj) == "wp_instance_start")
      {
         SetLocalObject(Area, "AREA_START_WP", Obj);
         break;
      }
      Obj = GetNextObjectInArea(Area);
   }
   if (Obj == OBJECT_INVALID)
      WriteTimestampedLogEntry("No start WP for area!");
}

The script is called when ACR_CreateAreaInstance is called.  The job of this script is to perform any instance setup tasks that your area instance needs.  For example, spawning objects that should be statically spawned, setting up transition links for exit transitions (if they are dynamic), etc.

The example script looks for a waypoint object that is intended to be statically placed in the area template, with the tag "wp_instance_start".  It then stores this object as a local variable on the area instance object (which will be returned by GetArea() for objects in the instance).  If you wanted to jump a player into the area, to find the right waypoint, you'd call GetLocalObject(AreaInstance, "AREA_START_WP").  You can't just use a tag lookup as the object might be in a different area instance.

Area instance on delete script

An area on delete script (optional) should look like this:

int StartingConditional()
{
   object Area = OBJECT_SELF; // The area being created

   // Clean up spawned objects in the area and remove treasure, etc.
   // It is up to you to decide how what to do here.  This example
   // script just looks for any freestanding items or creatures in
   // the area and deletes them.  You're guaranteed that there are
   // no PCs in the area when the on delete script runs.

   object Obj = GetFirstObjectInArea(Area);
   while (Obj != OBJECT_INVALID)
   {
      int Type = GetObjectType(Obj);

      if (Type == OBJECT_TYPE_CREATURE || Type == OBJECT_TYPE_ITEM)
         DestroyObject(Obj);

      Obj = GetNextObjectInArea(Area);
   }

   // The script can now return TRUE to allow the area to be considered
   // cleaned up (and available for re-use).  Otherwise, the script can
   // return FALSE and the cleanup attempt is dismissed.  If automatic
   // cleanup was enabled, cleanup may run again if the area remains
   // empty, once the cleanup timer elapses next time.

   return TRUE;
}

The example script scans for any loose creature or item objects in the area, and deletes them.  You can perform other cleanup tasks here as appropriate.  The general idea is that after the on delete script runs, the area is in the same state as if it were just loaded on module startup (so that it is "fresh" the next time the that instance is used).  Don't forget to clean up any local variables on the instanced area object itself that you used to maintain state, if those need to be reset.

Area instance management script functions

These functions are used to manage the creation or deletion of an area.  Typically, script code uses these functions to acquire a new area instance to transport a player into, or to remove an area instance that isn't needed anymore.

//! Create an instanced area.  If an available instance in the free pool can be
//  found, it will be reused.
//!  - TemplateArea: Supplies the area that serves as the template for the new
//                   instanced area.
//!  - CleanupDelay: Supplies the area cleanup delay.  If < 0.1f, then the
//                   value from the design time configuration setting on the
//                   template area is used instead.  If the design time
//                   setting did not specify a value then cleanup is not
//                   enabled for this instance.
//!  - Returns: The area instance, else OBJECT_INVALID on failure.
object ACR_CreateAreaInstance(object TemplateArea, float CleanupDelay = 0.0f);

//! Release an instanced area.  It will be put back onto the free list of the
//  area supports free list pooling, otherwise the area is deleted.
//!  - InstancedArea: Supplies the area instance to release.
void ACR_ReleaseAreaInstance(object InstancedArea);

Area instance utility script functions

These functions are most useful for scripts that are called in response to events within an area instance.  They can be used anywhere, as long as the "InstancedArea" parameter is actually an area instance (for functions that operate on an area instance).

//! Check whether an area is an instanced area or not.  It can be used for the
//  client enter script of a template area, for example, to create an instance
//  of the area and move the entering player to the instance.
//!  - Area: Supplies the area object to query.
//!  - Returns: TRUE if the area is an instance, else FALSE if it is a static
//              created area (which might be the template for an instance, or
//              might be any other static area).
int ACR_IsInstancedArea(object Area);

//! Return the template instance for an instanced area.  This is the area
//  object that had been created from.  Returns OBJECT_INVALID if the area was
//  not really an instanced area.
//!  - InstancedArea: Supplies the instanced area object to query.
//!  - Returns: The template (parent) area, else OBJECT_INVALID if the area
//     was not a valid instanced area.
object ACR_GetInstancedAreaTemplate(object Area);

//! Check whether an area has any player controlled objects in it.  The area
//  can be a normal or instanced area.
//!  - Area: Supplies the area to inquire about.
//!  - ExcludeObject: Optionally supplies an object to exclude from checking.
//!  - Returns: TRUE if the area has no player controlled objects.
int ACR_IsAreaEmptyOfPlayers(object Area, object ExcludeObject = OBJECT_INVALID);

 

ACR configuration settings

There are several configuration settings that alter the behavior of the ACR on a global or per-module basis.

Global settings are stored in the "config" table in the database, whereas per-module settings are stored as module local variables that are intended to be configured in the toolset.

 

Module local variable configuration settings

These settings are stored as local variables on the module.  They can be set by the toolset.

 

int ACR_PLAYER_LOCATIONS  : If set to 1, the PC Tools button that allows a player to see what areas their party members are in is enabled.  This feature is defunct and doesn't do anything right now.

int ACR_DISABLE_STATISTICS:  If set to 1, then the ACR doesn't update the stat_counters table in the database.  This feature is primarily in place to allow statistics to be disabled if they proved to be a performance problem.  This setting can be globally configured via the database config table.

int ACR_HEALTHMONITOR_GAMEOBJUPDATE_BACKOFF  : If set to 1, then the server will detect when responsiveness is poor due to excessive server load.  If the server appears to be overloaded, then the ACR (ACR_HealthMonitor) will increase the time between game world updates sent to game clients.  This frees up a significant amount of CPU time for other tasks on the server, preserving responsiveness (but players will see course corrections or other movement changes less frequently).  If the server is performing well, the game world update timer is reduced to its default value.  This setting can be globally configured via the database config table.

int ACR_SERVER_IPC_DISABLE_LATENCY_CHECK  : If set to 1, then the server won't periodically, about every 30 seconds, refresh the player to server ping times of connected players (players can check their ping with #ping if this feature isn't enabled).

int ACR_MODULERESOURCEFILES  : If set to 1, then the server will record each resource file present within the module proper in the server_resource_files table.  This setting can be globally configured via the database config table.

Recommended module default settings

int ACR_PLAYER_LOCATIONS = 1

int ACR_HEALTHMONITOR_GAMEOBJUPDATE_BACKOFF = 1

 

Global (database config table) configuration settings

These settings are stored in the "config" table in the database, which is a key/value pair mapping.  They change the behavior of all modules connected to the database.

PlayerPassword  : This setting contains the player password that is used when portaling a player between servers.  It must match the configured player password value on all servers.  Changing this setting doesn't actually change the player password required to connect to a server (which has to be done separately), just what password is used for outbound portals.  This setting is updated every 5 minutes while a server is running.

ACR_DISABLE_STATISTICS  : If set to 1, this setting turns off stat_counters table updates on all servers by setting the ACR_DISABLE_STATISTICS module local variable to 1 during server startup.  This setting is only updated at module startup time.

GameObjUpdateBackoff  : If set to 1, this setting enables game world update timer management by setting the ACR_HEALTHMONITOR_GAMEOBJUPDATE_BACKOFF module local variable to 1 during server startup.  This setting is only updated at module startup time.

RestartWatchdogTimeout  : The number of second after which a remote server restart request, from the Restart Server tool, forcibly terminates the server if a clean shutdown hasn't happened yet.  This setting is designed to allow a server that is deadlocked or hung to be forcibly restarted remotely (as the remote restart mechanism uses a dedicated thread that is more likely to be responsive even if the main server thread is stuck).  If not set or set to zero, then the restart watchdog feature isn't used on remote restart requests.  This setting is updated every 5 minutes while a server is running.

AccountAssociationSecret  : The shared secret string used to generate the URL to send the user to for linking their in-game community account with their ALFA forum account.  The value can be any string (hopefully a relatively random one).  The forum system uses the shared secret to validate that URLs containing account linking information were only generated by an authorized source, like a live server.  More details on the account association system can be found here. This setting is updated every 5 minutes while a server is running.

ModuleResourceFiles  : If set to 1, this setting enables module resource file logging by setting the ACR_MODULERESOURCEFILES module local variable to 1 during server startup.  This setting is only updated at module startup time.

AccountAssociationUrl  : The base URL of the ALFA account association web service.  This setting is updated every 5 minutes while a server is running.

GetHostnameUrl  : The base URL of the ALFA get hostname web service.  This setting is updated every 5 minutes while a server is running.

ContentPatchPath  : The path relative to the sshfs mount point to use as the base location for content patch files.  Content patch files are stored under <sshfs_mount>\ContentPatchPath\<HakVersion>\.  This setting is only updated at module startup time.

DefaultIrcGatewayId  : The default IRC gateway to use for server-to-IRC messages.  Typically, this should be zero, which is the IRC gateway ID customarily used by the production ALFA IRC bot.  The setting is paired with an ALFA IRC Bot instance's IRCGateway config.xml entry.  This setting is updated every 5 minutes while a server is running.

DefaultIrcRecipient  : The default IRC recipient (i.e. channel) to use for server-to-IRC messages.  Typically, this should be #alfa-players.  This setting is updated every 5 minutes while a server is running.

WerDisabled  : If set to 1, Windows Error Reporting is disabled for nwn2server.exe.  This setting is only updated at module startup time.

DisableSaveInQuarantine  : If set to 1, character files are not saved by a player in quarantine (preventing a pending save from an offline server from accidentally being overwritten by logging in to the wrong server).  This is updated every 5 minutes while a server is running.

CompilerOptions  : Compiler options that the content patcher system uses when a module recompile is required by a content hotfix.  This is usually set to "-e -o -v1.70 -y", to match the option set used by the ACR build system (in NWScript.mk) and to continue past scripts that fail to compile, as some modules have non-compileable scripts checked in.

NotifyServiceCid  : The character ID of the dummy character that is used to send notification messages from infrastructure to the IRC gateway.  Typically, this should be the dedicated NotifyService character (CID 3905).  This is used, for example, by the content patcher, so as to send notifications as to server restarts due to the content patch system.  If set to zero, messages are not sent to the IRC gateway.  Infrastructure messages are sent to the default IRC recipient as configured by the DefaultIrcRecipient config table variable.

ErrorNotifyIrcRecipient  : The default IRC recipient (i.e. channel) to use for diagnostic and error reports sent via server-to-IRC messages.  Typically, this should be #alfa-tech.  This setting is updated every 5 minutes while a server is running.

GameDifficultyLevel  : The game difficulty level that all servers will be configured to. This is an integer value that should match the value of one of the GAME_DIFFICULTY_* nwscript.nss constants, and should generally be set to 3 (meaning GAME_DIFFICULTY_CORE_RULES). If a server has an incorrect difficulty level, the ACR system will automatically adjust it to the target, global difficulty level specified with this variable.

ProtectionLevel  : The anti-abuse protection level that all servers adhere to (an integer).  Each protection level represents an incremental anti-abuse defense over the previous level.  Protection levels can be applied in the case of an abuse event, but generally the system should run at ProtectionLevel 0 (the default, no special restrictions).  Valid values correspond to the following table (the value should be set to the integer on the left, the symbolic constant in script source code is supplied in parenthesis):

  • 0   (MEMBER_PROTECTION_LEVEL_OPEN):  The default, no special anti-abuse provisions for non-member accounts.
  • 1   (MEMBER_PROTECTION_LEVEL_QUARANTINE):  Non-member accounts are immediately quarantined on login.
  • 2   (MEMBER_PROTECTION_LEVEL_QUARANTINE_DM_ONLY_TELLS):  Non-member accounts are immediately quarantined on login and the only function permitted in-game is to send a tell to a DM (no cross-server tells, no IRC gateway use, no (non-DM) player tells, etc.).
  • 3   (MEMBER_PROTECTION_LEVEL_BOOT_5S_DELAY):  Non-member accounts are quarantined, can't use tells (even to a DM), and are booted after 5 seconds.  A short message explaining that the player should fill out the application form at http://www.alandfaraway.org is displayed before the player is booted.
  • 4  (MEMBER_PROTECTION_LEVEL_BOOT_IMMEDIATELY):  Non-member accounts are immediately booted on log in and can't perform any function in-game whatsoever.  No appreciable delay even to send an explanation as to what happened is allowed for, for use in extreme abuse events only.

This setting is updated every 5 minutes while a server is running.

 

New gameserver VM setup

This document describes in rough detail the recommended checklist for setting up a new game server host VM in the standard configuration.

Details can also be found on this forum thread: http://www.alandfaraway.org/forums/viewtopic.php?f=95&t=47550

OS configuration:

- Install Windows Server
- Copy standard tools over
- Create SG "NWN2 Servers"
- Create SG "NWN2 Admins"
- Create user "NWN2Server"
○ Add to "NWN2 Servers" SG
- Create C:\NWN2, xcopy NWN2 install over
○ Grant "NWN2 Servers" create files, create folders to "This folder only"
○ Grant "NWn2 Admins" modify to folder, subfolders, and files
○ Import NWN2.reg
- Create C:\NWN2User
○ Grant full control to "NWN2 Servers"
○ Grant modify/execute/list/read/write to "NWN2 Admins"
- Runas /profile /user:NWN2Server cmd.exe
○ cd /d "%userprofile%\Documents"
○ mklink /j "Neverwinter Nights 2" C:\NWN2User
- Create C:\NWNX4, xcopy NWNX4 installation over
○ Grant modify/execute/list/read/write to "NWN2 Admins"
○ Grant modify/execute/list/read/write to "NWN2 Servers" for:
§ AuroraServerNWScript.log
§ AuroraServerVault.log
§ nwnx.txt
§ nwnx_controller.txt
§ xp_mysql.txt
§ xp_bugfix.txt
§ xp_objectattributes.txt
§ xp_srvadmin.txt
§ xp_system.txt
§ xp_time.txt
- Create C:\Users\Public\Desktop\Credentials.txt
- Copy standard links to C:\Users\Public\Desktop
- Copy standard links to C:\ProgramData\Microsoft\Windows\Start Menu\Programs\Tools
- Copy standard scripts to C:\Scripts
- Copy standard NWN2 tools to C:\Tools
- Copy NWNServerConsole to C:\NWN2\Console
- Fix PowerShell signing
○ Set-ExecutionPolicy -Scope LocalMachine -ExecutionPolicy Unrestricted -Force
- Install Git
- Install FileZilla
- Install Win8 Performance Toolkit http://msdn.microsoft.com/en-us/windows/apps/br229516
- Install 7-zip
- Install gvim
- Install WinDbg
- Set _NT_SYMBOL_PATH to SRV*C:\Symbols*http://msdl.microsoft.com/download/symbols

Prerequisites installation:

• VC2010 SP1 CRT http://www.microsoft.com/download/en/de ... x?id=26999
• VC2005 SP1 CRT http://www.microsoft.com/download/en/de ... x?id=26347
• .NET Framework v4.0 http://www.microsoft.com/download/en/de ... x?id=17851
• DirectX 9.0c http://www.microsoft.com/download/en/details.aspx?id=35

NWNX4 installation:

• nwnx4_controller.exe -installservice
• Get SID for NWN2Server user with PowerShell:
○ ([wmi]"win32_Group.Domain='$env:ComputerName',Name='NWN2 Admins'").sid
• Set SD on nwnx4-1 service to allow NWN2 Admins to start/stop:
○ sc sdset nwnx4-1 D:(A;;CCLCSWRPWPDTLOCRRC;;;SY)(A;;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;BA)(A;;CCLCSWLOCRRC;;;IU)(A;;CCLCSWLOCRRC;;;SU)(A;;RPWP;;;<>)S:(AU;FA;CCDCLCSWRPWPDTLOCRSDRCWDWO;;;WD)
• Set configuration in C:\NWNX4\nwnx.ini
• Set configuration in C:\NWNX4\AuroraServerNWScript.ini
• Set configuration in C:\NWNX4\xp_mysql.ini

Item pricing rules

See here for the updated pricing documentation:

https://www.alandfaraway.info/wiki/Pricing_Documentation

Creating (and getting started with) a new C# script program

In order to create a new C# script, it's recommended to make a copy of the 'ACR_EmptyScript' project (an empty template/base script), make small alterations to the project file, and begin adding new code. The following checklist describes the necessary steps to get started (assumed to be using the ACR build system from a git checkout):

1) Pick a name for the new script, like ACR_MyScript.

2) Make a directory under alfa2_acr.hak named off of your script. In this case, ACR_MyScript.

3) Copy *.cs and *.csproj from ACR_EmptyScript into your new directory.

4) Rename ACR_EmptyScript.cs to ACR_MyScript.cs, and rename ACR_EmptyScript.csproj to ACR_MyScript.cs.

5) Edit StandardEntrypoints.cs, replace all instances of ACR_EmptyScript with ACR_MyScript.

6) Edit ACR_MyScript.cs, replace all instances of ACR_EmptyScript.

7) Edit ACR_MyScript.csproj, replace all instances of ACR_EmptyScript with ACR_MyScript.

8) Generate a new GUID in registry form and replace the existing GUID in the tag within ACR_MyScript.csproj. GUID Generator (guidgen.exe, ships with Visual Studio and the .NET 4 SDK) is recommended. This step isn't strictly required, but highly recommended. Ask for help in from a tech department member (or the ACR/tech forum) if you don't have these tools and someone should be able to help out.

9) Edit AssemblyInfo.cs, replace all instances of ACR_EmptyScript with ACR_MyScript.

10) Generate another new GUID in C# attribute form and replace instances of the previous GUID in AssemblyInfo.cs with the new one. This step is highly recommended but isn't strictly required.

11) Edit the makefile in the alfa2_acr.hak directory and add the new script in the following sections:

- CLRSCRIPT_OBJECTS= (don't forget to add a trailing \ for the previous line!)

- makeclrscripts:

- clean:

Just follow the format of the existing entries; this step should be relatively straightforward.

12) Optionally, add the script to the Visual Studio workspace (solution) under alfa2_acr.hak\ACR_ManagedScripts\ACR_ManagedScripts.sln. This isn't required to make the script build, but it makes it easier to use the Visual Studio IDE as your source code editor.


An example git commit showing what should be touched for adding a new script can be found here:

https://github.com/ALandFarAway/ALFA-Base-Resources/commit/3f4e75cc53095...

Note that if you want to link a library source file (in the repository root\CLRScript directory), it's recommended to edit the script program's .csproj project file by hand. If you add the file within the Visual Studio IDE, the editor creates a private copy of the library file, which means that you won't get bug fixes or future changes made to the common file. The example ACR_EmptyScript references several of the common library source files.

If you want to pass arguments to the ScriptMain() of the C# script, add the types to the ScriptParameterTypes[] array in ACR_MyScript.cs. Note that you can only pass in int (Int32), float (Single), or string (String) types as arguments to ScriptMain().

Only the class containing the ScriptMain() can directly call NWScript APIs. You can pass the current working instance around to functions in other classes if needed. Use caution when it comes to saving a reference to a main class instance in a global or static variable; a DelayCommand (or other 'action' continuation) initiated from such an instance won't work properly.

It is only possible to call NWScript APIs if you are within a context that has been called back by the engine first, i.e. ultimately called by ScriptMain() or by a DelayCommand or AssignCommand (or other 'action' type) continuation. Other threads can't call NWScript APIs directly, and the main thread can't call them unless entered by the engine (so don't call NWScript APIs from a window procedure or other non-engine callbacks on the game's main thread). One way to work around these limitations is to have a periodically scheduled engine script callback on the main thread that checks for work to do on behalf of other threads; the ACR_ServerCommunicator project demonstrates this.

A DelayCommand continuation can be started like this:

DelayCommand(1.0f, delegate()
{
PrintString("In a delay command");
});

Just like in NWScript, you can use variables in the 'containing' function within the DelayCommand.

If you want to attach complicated state to a game object, one option is to use a Dictionary that indexes on the game object id (the 'object' in NWScript). Be sure to decide how to handle the underlying game object dying or getting cleaned up.

It is possible to access functionality written in NWScript source code from a C# script with some limitations. This can be useful in some cases where complex functionality is already written in NWScript and it's desirable to avoid duplicating code. The ALFA.Database library class (CLRScript\ALFADatabase.cs) demonstrates the use of NWScript interop to project some of the acr_db_persist_i library functions (such as those for SQL queries) into C# for use by C# scripts. If you're going to add a wrapper for a NWScript-language function, you should reuse the same IGeneratedScriptProgram object that ALFA.Database uses. Otherwise, if the NWScript-language function uses a DelayCommand or AssignCommand (or other 'action' wrappers), they won't fire properly.

There are two main options for calling a C# script. You can either directly assign the C# script as the event handler for a script event for an object, or you can call it explicitly from NWScript using ExecuteScript/ExecuteScriptEnhanced. The latter is usually how we interface with C# scripts today; for example, check out acr_server_misc_i or acr_server_ipc_i. These headers create NWScript-language wrappers around ExecuteScriptEnhanced to pass some data to the ScriptMain() of a C# script.

Syndicate content