Creating CLR Programs

Jump to: navigation, search

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.

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.