Skip to content

Registering Functions

alexdunn edited this page Sep 13, 2014 · 11 revisions

Once you've written a function in SKSE, you'll have to register it if you want to access it from within Papyrus. Because every function you might want to register will have a different number of parameters, SKSE has several different functions to register functions with various numbers of parameters.

Function with 0 parameters

float myFunction(StaticFunctionTag *base);
// ... Register function elsewhere in code:
registry->RegisterFunction(
		new NativeFunction0<StaticFunctionTag, float>("myFunction", "MyClass", MyClassNamespace::myFunction, registry));

In Papyrus:

float Function myFunction() global native

Function with 1 parameter

void my1ParamFunction(StaticFunctionTag *base, float f);
registry->RegisterFunction(
		new NativeFunction1<StaticFunctionTag, void, float>("my1ParamFunction", "MyClass", MyClassNamespace::my1ParamFunction, registry));

In Papyrus:

Function my1ParamFunction(float f) global native

Function with 2 parameters

void my2ParamFunction(StaticFunctionTag *base, float f, double d);
registry->RegisterFunction(
		new NativeFunction2<StaticFunctionTag, void, float, double>("my2ParamFunction", "MyClass", MyClassNamespace::my2ParamFunction, registry));

In Papyrus:

Function my2ParamFunction(float myFloat, double myDouble) global native

Function with 3 parameters

void my3ParamFunction(StaticFunctionTag* base, UInt32 n, float f, double d);
registry->RegisterFunction(
		new NativeFunction3 <StaticFunctionTag, void, float, double, UInt32>("my3ParamFunction", "MyClass", myClassNamespace::my3ParamFunction, registry));
Function my3ParamFunction(int n, float myFloat, double myDouble) global native

Function with 4 parameters

void my4ParamFunction(StaticFunctionTag *base, float f1, double d, UInt32 n, float f2);
	registry->RegisterFunction(
		new NativeFunction4 <StaticFunctionTag, void, float, double, UInt32, float>("my4ParamFunction", "MyClass", MyClassNamespace::my4ParamFunction, registry));
Function my4ParamFunction(float f1, double d, int n, float f2) global native

At this point, the pattern should be clear: Use the NativeFunction with the number of parameters that you want your function to have in Papyrus. The NativeFunction methods provided by SKSE go up to 10 parameters (NativeFunction10). Then, inside the <>, the first parameter will be StaticFunctionTag, the second parameter will be your function's return type, and every parameter after will be the types of your function's parameters.

Notes

  • The first parameter in your function must be the base class that your function belongs to. In all of the above examples, StaticFunctionTag represents a function that has no base class. In the SKSE functions, you can see examples of functions that have a base class of TESObjectREFR or TESSoulGem. A function with base type StaticFunctionTag will be a global function in Papyrus. It will be called stand-alone: MyGlobalFunction(param1, param2, ...). A function that has a base class will be a local function in Papyrus, meaning it's called like MyCustomObject.MyLocalFunction(param1, param2, ...)
  • The above code examples go in a dedicated function with signature void papyrusObjectReference::RegisterFuncs(VMClassRegistry* registry) inside your class. This function is called in your main.m to register the functions when SKSE loads your plugin. See SKSE: Getting Started.
  • In the above code examples, MyClass is the name of the class that these functions will be defined inside in Papyrus. MyClassNamespace is the namespace you defined in C++ that contains the chosen function. Example:
namespace myClassNamespace {
void myFunction () {
}
}

Returning More than one Value

Sometimes you need to send back to your Papyrus script more than just one piece of information. Although it isn't possible to return an array from an SKSE function that's exported to Papyrus, you can actually change the values of an array or FormList that Papyrus passes into your SKSE function. Then inside the SKSE function you can edit the values of that array or FormList and . An example in a Papyrus script:

Form[] myReturnValues = new Form[2]
mySKSEMethod(param1, param2, myReturnValues)
; Now use the return values that were set inside the SKSE function:
Form returnValue1 = myReturnValues[0]
Form returnValue2 = myReturnValues[1]

In your SKSE's implementation for mySKSEMethod, simply set the values of the myReturnValues array that's passed in and those values will be set later in your Papyrus script.

1. Arrays as parameters to an SKSE function

In your SKSE plugin, you'll need to declare your function that takes a parameter like the following example that takes a Papyrus Form array:

void myFunction(StaticFunctionTag *base, float myParam1, VMArray<TESForm*> returnValues) {
  // My implementation
}

For this to link properly, you'll need to define an UnPack template like the one's SKSE already defines in PapyrusArgs.cpp. In the case of TESForm, the template you can add to your SKSE class will be:

template <> void UnpackValue(VMArray<TESForm*> * dst, VMValue * src) {
	UnpackArray(dst, src, GetTypeIDFromFormTypeID(TESForm::kTypeID, (*g_skyrimVM)->GetClassRegistry()) | VMValue::kType_Identifier);
}
2. FormLists as parameters to an SKSE function

TODO

Troubleshooting

  • Make sure that your function's declaration in your .h file matches the definition in your .cpp file. Otherwise, compilation will fail.
  • If you're always getting a return value of None for your function, look in the Papyrus logs for mention of your function. It might say, "Native static function MyFunction does not match existing signature on linked type MyPluginScript. Function will not be bound." This indicates that the function declaration you wrote in your Papyrus script does not match how you declared it in your SKSE plugin. Double check them.

Clone this wiki locally