You’re correct—simply calling

registry->RegisterFunction("TakePhoto", "Printscreen_Formula_script", Papyrus_TakePhoto);

will register TakePhoto as an ordinary (non‐latent) Papyrus function. In order for Papyrus to treat it as a latent function, you must use the NativeFunction‐based registration and call its SetLatent() method. In other words, instead of the “string+pointer” overload of RegisterFunction(...), you need to create a NativeFunction object, mark it latent, and hand that to the registry.

Below is exactly how you would change your registration code in plugin.cpp so that TakePhoto becomes a latent Papyrus call:

#include "PCH.h" #include "SKSE/PapyrusNativeFunctions.h" using namespace RE; // for BSFixedString, StaticFunctionTag, VMClassRegistry, etc. static bool Papyrus_TakePhoto( StaticFunctionTag*, BSFixedString basePath, BSFixedString imageType, float GIF_Duration, float Compression, BSFixedString DDS_Mode ) { // … your existing capture logic here … } //--------------------------------------------------------------------------- // Replace your old RegisterPapyrusFunctions(...) with this: static bool RegisterPapyrusFunctions(VMClassRegistry* registry) { // 1) Non‐latent functions can still use the simple form if you like: registry->RegisterFunction( new BSScript::NativeFunction<StaticFunctionTag, BSFixedString, BSFixedString>( "CheckPath", "Printscreen_Formula_script", CheckPath, registry ) ); // 2) **Latent** TakePhoto: use NativeFunction and call SetLatent() registry->RegisterFunction( new BSScript::NativeFunction<StaticFunctionTag, bool, BSFixedString, BSFixedString, float, float, BSFixedString>( "TakePhoto", // Papyrus name "Printscreen_Formula_script", // Papyrus script name Papyrus_TakePhoto, // C++ function pointer registry // VMClassRegistry* )->SetLatent() // ← marks it as latent ); // 3) Any other non‐latent registrations: registry->RegisterFunction( new BSScript::NativeFunction<StaticFunctionTag, BSFixedString>( "GetResult", "Printscreen_Formula_script", GetResult, registry ) ); registry->RegisterFunction( new BSScript::NativeFunction<StaticFunctionTag, BSFixedString>( "Cancel", "Printscreen_Formula_script", Cancel, registry ) ); return true; }

Explanation

  • BSScript::NativeFunction<Owner, Return, Args...>
    This template (located in PapyrusNativeFunctions.h) constructs a new Papyrus‐callable function. The template parameters break down as:

    1. StaticFunctionTag ⇢ “Owner” type (all of your functions are static, so StaticFunctionTag)

    2. bool ⇢ return type (for TakePhoto—you want bool back in Papyrus)

    3. BSFixedString, BSFixedString, float, float, BSFixedString
      ⇢ the Papyrus‐side parameter types, in exactly the same order you declared them.

  • ->SetLatent()
    By default, a NativeFunction is treated like a normal Papyrus call (i.e. Papyrus will block until it returns). Calling .SetLatent() switches it into latent mode, which means:

    1. Papyrus will not freeze the game while your C++ code is running.

    2. From Papyrus’s perspective, TakePhoto(...) now returns immediately but continues “behind the scenes.” Once your C++ side finishes, you fire a “completion” event back to Papyrus (via registry->SetFunctionReturnValue() or by firing a mod‐event).

  • Why the “simple” RegisterFunction("TakePhoto", …, Papyrus_TakePhoto) was wrong
    That overload is only intended for non‐latent functions. Internally, it wraps your C++ pointer inside a non‐latent NativeFunction wrapper. There is no way for Papyrus to know “this one should be latent” unless you explicitly use the NativeFunction<…> constructor and call SetLatent() on it.

What you must do in your code

  1. Include

    #include "SKSE/PapyrusNativeFunctions.h"

    (That header defines RE::BSScript::NativeFunction<…> and ensures that BSFixedString, StaticFunctionTag, VMClassRegistry, etc., are all visible.)

  2. Replace your old, simple registrations:

    registry->RegisterFunction("TakePhoto", "Printscreen_Formula_script", Papyrus_TakePhoto);

    with the new NativeFunction<…>( … )->SetLatent() form shown above.

  3. Keep your other non‐latent functions (like CheckPath, GetResult, and Cancel) either in the “simple” form or in a NativeFunction<…> form without SetLatent(). It works either way. Generally, if you don’t need any special flags, you can still write:

    registry->RegisterFunction( new BSScript::NativeFunction<StaticFunctionTag, BSFixedString, BSFixedString>( "CheckPath", "Printscreen_Formula_script", CheckPath, registry ) );

    or even

    registry->RegisterFunction("CheckPath", "Printscreen_Formula_script", CheckPath);

    but for latent behavior you must use new NativeFunction<…>(…)->SetLatent().

In summary:

  • Yes, you were “missing” the SetLatent() call.

  • Concretely, you cannot register a latent function simply by name + pointer; you must wrap it in a BSScript::NativeFunction<…> and then call SetLatent() before handing it to registry->RegisterFunction(…).

  • Once you do that, Papyrus’s TakePhoto(...) call will be treated as latent, allowing Skyrim to keep running until your capture‐thread finishes and signals back to Papyrus.