Skip to content

Papyrus

Ryan edited this page May 9, 2019 · 14 revisions

Summary

SKSEPapyrusInterface can be accessed from SKSEInterface by calling SKSEInterface::QueryInterface(kInterface_Papyrus). The papyrus interface allows plugin authors to register functions with the virtual machine which users can take advantage of in the Papyrus scripting language.

Class Interface

  • interfaceVersion: This is the version of the exported interface. Plugin authors should assert on this field if they require a certain version.
  • Register: This method is used to delay function registration with the virtual machine until it's ready.

Usage

Authors should define a registration function matching the declared typedef for RegisterFunctions within the class. Note that SKSE does not register your functions for you; this is simply a delay functor. You must use the VMClassRegistry* passed as the first argument to register your functions.

  • The Papyrus Functions
#include "common/ITypes.h"  // SInt32
#include "skse64/GameTypes.h"  // BSFixedString
#include "skse64/PapyrusNativeFunctions.h"  // StaticFunctionTag

BSFixedString HelloWorld(StaticFunctionTag*)
{
    return BSFixedString("Hello world!");
}

SInt32 Sum(StaticFunctionTag*, SInt32 a_num1, SInt32 a_num2)
{
    return a_num1 + a_num2;
}

These are the functions we will be registering with the virtual machine and which will be accessible from a .psc file to Papyrus scripters.

  • The Registration Function
#include "common/ITypes.h"  // SInt32
#include "skse64/GameTypes.h"  // BSFixedString
#include "skse64/PapyrusNativeFunctions.h"  // NativeFunction, StaticFunctionTag
#include "skse64/PapyrusVM.h"  // VMClassRegistry

bool RegisterFuncs(VMClassRegistry* a_registry)
{
    a_registry->RegisterFunction(new NativeFunction0<StaticFunctionTag, BSFixedString>("HelloWorld", "MyClass", HelloWorld, a_registry));
    a_registry->RegisterFunction(new NativeFunction2<StaticFunctionTag, SInt32, SInt32, SInt32>("Sum", "MyClass", Sum, a_registry));
    return true;
}

This is the function we will pass to SKSEPapyrusInterface::Register as a callback to register our Papyrus functions.

  • Note that NativeFunction0, NativeFunction2, etc. act as wrappers for our functions and do the conversion from vm handles Papyrus scripters interact with to C++ data types we can interact with. The new keyword means these wrappers are allocated on the heap and ownership is passed out to the the virtual machine.
  • The NativeFunction template takes template parameters in the following order:
    • Base: This is base type of the function, which represents the object the function is called on. In our examples, it is StaticFunctionTag, indicating it is not called on any object. However, it could be any form type, such as TESForm (equivalent to a Papyrus Form) or TESObjectREFR (equivalent to a Papyrus ObjectReference).
    • Return: This is the return type of the function. In our examples, we have a BSFixedString (equivalent to a Papyrus String) and an SInt32 return type (equivalent to a Papyrus Int). The return type could be some other arithmetic type (i.e. float), a form type, or return nothing (in which case the return type is void).
    • Parameters: These are the types of the parameters of the function. NativeFunction0 takes no parameters, and as such lists no parameter types. NativeFunction2 takes 2 parameters, and as such lists 2 parmeter types. If you wish to write a function that takes 3 parameters, use NativeFunction3, if you wish to write a function that takes 4 parameters, use NativeFunction4, etc.
  • The NativeFunction constructor takes 4 arguments:
    • fnName: This is the name of the function as defined in the .psc file.
    • className: This is the name of the papyrus class as defined by the .psc file.
    • callback: This is the C++ callback and is the implementation of the function.
    • registry: This is the virtual machine used to register the functions. You can simply forward the one SKSE passed you.

We end the function by returning true to SKSE to indicate that registration was successful.

  • The .psc File MyClass.psc
ScriptName MyClass

String Function HelloWorld() Global Native
Int Function Sum(Int a_num1, Int a_num2) Global Native
  • Global indicates that the function is called independently of any object.
  • Native indicates that the function has a C++ implementation.

Here is a complete implementation of a plugin using SKSEPapyrusInterface.

CommonLib Extensions

  • SKSE implements NativeFunction by using awkward macro hacking to provide implementations for a limited number of arguments.
    • CommonLib does away with this by using template meta-programming to implement a NativeFunction wrapper that's easy to read, and extends to an unlimited number of arguments.
  • SKSE requires the plugin author to explicitly define the signature of their function upon registration, which they must update each time they modify the signature.
    • CommonLib does away with this by using template meta-programming to automatically determine the signature of an author's function, and generates the correct wrapper based on that type. Thus, registration is as simple as passing the function pointer to the registration function.

Here is a complete implementation of a plugin using CommonLib's SKSE::PapyrusInterface.

Clone this wiki locally