### F4SE::Init — Library Initialization Source: https://context7.com/libxse/commonlibf4/llms.txt `F4SE::Init` must be called at the start of `F4SEPlugin_Load` (or `F4SEPlugin_Preload`). It queries all F4SE interfaces, optionally configures logging via REX, and optionally allocates a trampoline pool for function hooks. ```APIDOC ## F4SE::Init — Library Initialization `F4SE::Init` must be called at the start of `F4SEPlugin_Load` (or `F4SEPlugin_Preload`). It queries all F4SE interfaces, optionally configures logging via REX, and optionally allocates a trampoline pool for function hooks. ```cpp #include F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { // Full initialization with all options F4SE::Init(a_f4se, F4SE::InitInfo{ .log = true, // Enable REX logging .logLevel = REX::ELogLevel::Info, // Log level (Debug/Info/Warn/Error) .logName = "MyPlugin", // Log file name (no extension) .logPattern = "[%H:%M:%S] [%l] %v", // spdlog pattern .logRotate = 5, // Max rotated log files .trampoline = true, // Allocate a trampoline pool .trampolineSize = 128, // Size in bytes .hook = true // Install F4SE hooks }); // After Init, all getters are valid REX::INFO("Running on F4SE {}", F4SE::GetF4SEVersion()); REX::INFO("Plugin: {} v{}", F4SE::GetPluginName(), F4SE::GetPluginVersion()); REX::INFO("Save folder: {}", F4SE::GetSaveFolderName()); return true; } ``` ``` -------------------------------- ### Initialize CommonLibF4 Library Source: https://context7.com/libxse/commonlibf4/llms.txt The `F4SE::Init` function must be called at the start of `F4SEPlugin_Load` or `F4SEPlugin_Preload`. It configures logging, queries F4SE interfaces, and optionally allocates a trampoline pool for function hooks. ```cpp #include F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { // Full initialization with all options F4SE::Init(a_f4se, F4SE::InitInfo{ .log = true, .logLevel = REX::ELogLevel::Info, .logName = "MyPlugin", .logPattern = "[%H:%M:%S] [%l] %v", .logRotate = 5, .trampoline = true, .trampolineSize = 128, .hook = true }); // After Init, all getters are valid REX::INFO("Running on F4SE {}", F4SE::GetF4SEVersion()); REX::INFO("Plugin: {} v{}", F4SE::GetPluginName(), F4SE::GetPluginVersion()); REX::INFO("Save folder: {}", F4SE::GetSaveFolderName()); return true; } ``` -------------------------------- ### Basic Actor State and Property Management Source: https://context7.com/libxse/commonlibf4/llms.txt Demonstrates how to query basic actor states like combat status, sneaking, and visibility. Also shows access to actor values and perk management. Ensure the actor pointer is valid before use. ```cpp #include void ExampleActorUsage(RE::Actor* a_actor) { if (!a_actor) return; // Basic state queries REX::INFO("Level: {}", a_actor->GetLevel()); REX::INFO("In combat: {}", a_actor->IsInCombat()); REX::INFO("Is sneaking: {}", a_actor->IsSneaking()); REX::INFO("Is jumping: {}", a_actor->IsJumping()); REX::INFO("Is crippled: {}", a_actor->IsCrippled()); REX::INFO("Is visible: {}", a_actor->IsVisible()); REX::INFO("Is following: {}", a_actor->IsFollowing()); // Actor value access via ActorValueOwner interface const auto* avi = RE::TESForm::GetFormByEditorID( RE::BSFixedString("Health")); if (avi) { const float hp = a_actor->GetActorValue(*avi); const float maxHp = a_actor->GetPermanentActorValue(*avi); REX::INFO("HP: {{:.1f}} / {{:.1f}}", hp, maxHp); a_actor->RestoreActorValue(*avi, 25.0f); // Restore 25 HP } // Perk management const auto* perk = RE::TESForm::GetFormByEditorID( RE::BSFixedString("LifeGiver01")); if (perk) { const auto rank = a_actor->GetPerkRank(perk); REX::INFO("LifeGiver rank: {}", rank); a_actor->AddPerk(perk, 1); // a_actor->RemovePerk(perk); } // Combat helpers if (a_actor->IsInCombat()) { a_actor->StopCombat(); } // Faction check const auto* faction = RE::TESForm::GetFormByID(0x0001CBED); if (faction && a_actor->IsInFaction(faction)) { REX::INFO("Actor is in the specified faction"); } // Aiming vector RE::NiPoint3 aimVec; a_actor->GetAimVector(aimVec); REX::INFO("Aim direction: ({{:.2f}}, {{:.2f}}, {{:.2f}})", aimVec.x, aimVec.y, aimVec.z); // Reset 3D (force model reload) a_actor->Reset3D(false, 0, true, 0); } ``` -------------------------------- ### Build CommonLibF4 Project Source: https://github.com/libxse/commonlibf4/blob/main/README.md Build the CommonLibF4 project using xmake. This command generates build output in the `build/windows/` directory. ```bat xmake build ``` -------------------------------- ### F4SE::InputMap Key Code Constants and Conversions Source: https://context7.com/libxse/commonlibf4/llms.txt Demonstrates the use of offset constants for keyboard, mouse, and gamepad keys, and provides utilities to convert between XInput masks and internal key codes. Ensure F4SE is initialized before using these functions. ```cpp #include void ExampleInputMap() { // Key code offset constants // Keyboard keys: 0..255 // Mouse buttons: 256..263 // Mouse wheel up/down: 264..265 // Gamepad buttons: 266..281 REX::INFO("Keyboard offset: {}", F4SE::InputMap::kMacro_KeyboardOffset); // 0 REX::INFO("Mouse offset: {}", F4SE::InputMap::kMacro_MouseButtonOffset); // 256 REX::INFO("Gamepad offset: {}", F4SE::InputMap::kMacro_GamepadOffset); // 266 REX::INFO("Max macros: {}", F4SE::InputMap::kMaxMacros); // 282 // Convert XInput mask (e.g., XINPUT_GAMEPAD_A = 0x1000) to internal key code const std::uint32_t aButtonMask = 0x1000; // XINPUT_GAMEPAD_A const std::uint32_t keyCode = F4SE::InputMap::GamepadMaskToKeycode(aButtonMask); REX::INFO("A button key code: {}", keyCode); // Convert back to XInput mask const std::uint32_t mask = F4SE::InputMap::GamepadKeycodeToMask(keyCode); REX::INFO("Mask: {:X}", mask); // Named gamepad button offsets (for use in input event handlers) REX::INFO("DPAD_UP offset: {}", F4SE::InputMap::kGamepadButtonOffset_DPAD_UP); // 266 REX::INFO("A button offset: {}", F4SE::InputMap::kGamepadButtonOffset_A); // 276 REX::INFO("Left trigger offset: {}", F4SE::InputMap::kGamepadButtonOffset_LT); // 280 } ``` -------------------------------- ### Declare Plugin Entry Point and Version Data Source: https://context7.com/libxse/commonlibf4/llms.txt Every F4SE plugin must export version data and a load callback using F4SE macros. The `F4SE_PLUGIN_VERSION` macro defines plugin metadata, and `F4SE_PLUGIN_LOAD` is the entry point called by the F4SE runtime. ```cpp #include #include // Declare and populate the plugin version info (exported as F4SEPlugin_Version) F4SE_PLUGIN_VERSION = []() noexcept { F4SE::PluginVersionData data{}; data.PluginVersion({ 1, 0, 0, 0 }); data.PluginName("MyPlugin"); data.AuthorName("AuthorName"); data.UsesAddressLibrary(true); data.IsLayoutDependent(true); data.CompatibleVersions({ F4SE::RUNTIME_1_10_163, F4SE::RUNTIME_1_10_984, F4SE::RUNTIME_1_11_191 }); return data; }(); // Entry point called by F4SE after all plugins are loaded F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { // Initialize the library: sets up logging, queries interfaces, and optionally allocates trampoline F4SE::Init(a_f4se, { .log = true, .logName = "MyPlugin", .trampoline = true, .trampolineSize = 64 }); // Register for F4SE lifecycle messages const auto messaging = F4SE::GetMessagingInterface(); messaging->RegisterListener([](F4SE::MessagingInterface::Message* a_msg) { switch (a_msg->type) { case F4SE::MessagingInterface::kGameDataReady: // Safe to access TESDataHandler, forms, etc. break; case F4SE::MessagingInterface::kPostLoadGame: // A save game was loaded break; } }); return true; } ``` -------------------------------- ### Generate Visual Studio Project Files Source: https://github.com/libxse/commonlibf4/blob/main/README.md Generate project files for Visual Studio using xmake. This command creates a `vsxmakeXXXX/` directory with the latest Visual Studio version. ```bat xmake project -k vsxmake ``` -------------------------------- ### Registering Native Papyrus Functions with IVirtualMachine Source: https://context7.com/libxse/commonlibf4/llms.txt Demonstrates how to expose C++ functions to Papyrus scripts using `IVirtualMachine::BindNativeMethod`. This includes instance methods, methods returning values, and global functions. ```cpp #include // Example: Expose inventory manipulation to Papyrus // Papyrus: MyMod.SetActorAlpha(akTarget, afAlpha) static void SetActorAlpha(RE::Actor& a_actor, float a_alpha) { a_actor.SetAlpha(a_alpha); } // Papyrus: MyMod.GetActorLevel(akTarget) -> int static std::int32_t GetActorLevel(RE::Actor& a_actor) { return static_cast(a_actor.GetLevel()); } // Papyrus: MyMod.LookupForm(aiFormID, asModName) -> Form static RE::TESForm* LookupFormNative( std::monostate, // static (global) function marker std::int32_t a_formID, std::string_view a_modName) { return RE::TESDataHandler::GetSingleton()->LookupForm( static_cast(a_formID), a_modName); } bool RegisterPapyrusFunctions(RE::BSScript::IVirtualMachine* a_vm) { // Instance method (first arg is actor self) a_vm->BindNativeMethod("MyMod", "SetActorAlpha", SetActorAlpha, /*taskletCallable=*/std::nullopt, /*isLatent=*/false); // Instance method returning a value a_vm->BindNativeMethod("MyMod", "GetActorLevel", GetActorLevel, std::nullopt, false); // Global (static) Papyrus function a_vm->BindNativeMethod("MyMod", "LookupForm", LookupFormNative, std::nullopt, false); return true; } ``` -------------------------------- ### RE::Actor Usage Source: https://context7.com/libxse/commonlibf4/llms.txt Demonstrates basic state queries, actor value access, perk management, combat helpers, faction checks, aiming vector retrieval, and resetting the 3D model for an actor. ```APIDOC ## RE::Actor Usage ### Description This section provides examples of how to interact with the `RE::Actor` class to manage various aspects of an actor's state, including combat, AI, equipment, and locomotion. It covers querying actor status, accessing and modifying actor values, managing perks, controlling combat behavior, checking faction allegiance, obtaining the aiming vector, and resetting the actor's 3D model. ### Methods Demonstrated - `GetLevel()` - `IsInCombat()` - `IsSneaking()` - `IsJumping()` - `IsCrippled()` - `IsVisible()` - `IsFollowing()` - `GetActorValue(const ActorValueInfo&)` - `GetPermanentActorValue(const ActorValueInfo&)` - `RestoreActorValue(const ActorValueInfo&, float)` - `GetPerkRank(const BGSPerk*)` - `AddPerk(const BGSPerk*, std::uint8_t)` - `StopCombat()` - `IsInFaction(const TESFaction*)` - `GetAimVector(NiPoint3&)` - `Reset3D(bool, std::uint32_t, bool, std::uint32_t)` ### Example Usage ```cpp #include void ExampleActorUsage(RE::Actor* a_actor) { if (!a_actor) return; // Basic state queries REX::INFO("Level: {}", a_actor->GetLevel()); REX::INFO("In combat: {}", a_actor->IsInCombat()); REX::INFO("Is sneaking: {}", a_actor->IsSneaking()); REX::INFO("Is jumping: {}", a_actor->IsJumping()); REX::INFO("Is crippled: {}", a_actor->IsCrippled()); REX::INFO("Is visible: {}", a_actor->IsVisible()); REX::INFO("Is following: {}", a_actor->IsFollowing()); // Actor value access via ActorValueOwner interface const auto* avi = RE::TESForm::GetFormByEditorID( RE::BSFixedString("Health")); if (avi) { const float hp = a_actor->GetActorValue(*avi); const float maxHp = a_actor->GetPermanentActorValue(*avi); REX::INFO("HP: {{:.1f}} / {{:.1f}}", hp, maxHp); a_actor->RestoreActorValue(*avi, 25.0f); // Restore 25 HP } // Perk management const auto* perk = RE::TESForm::GetFormByEditorID( RE::BSFixedString("LifeGiver01")); if (perk) { const auto rank = a_actor->GetPerkRank(perk); REX::INFO("LifeGiver rank: {}", rank); a_actor->AddPerk(perk, 1); // a_actor->RemovePerk(perk); } // Combat helpers if (a_actor->IsInCombat()) { a_actor->StopCombat(); } // Faction check const auto* faction = RE::TESForm::GetFormByID(0x0001CBED); if (faction && a_actor->IsInFaction(faction)) { REX::INFO("Actor is in the specified faction"); } // Aiming vector RE::NiPoint3 aimVec; a_actor->GetAimVector(aimVec); REX::INFO("Aim direction: ({{:.2f}}, {{:.2f}}, {{:.2f}})", aimVec.x, aimVec.y, aimVec.z); // Reset 3D (force model reload) a_actor->Reset3D(false, 0, true, 0); } ``` ``` -------------------------------- ### RE::ActorEquipManager Usage Source: https://context7.com/libxse/commonlibf4/llms.txt Illustrates how to use the RE::ActorEquipManager singleton to equip and unequip items on an actor, including specifying equip slots and controlling equip behavior. ```APIDOC ## RE::ActorEquipManager Usage ### Description This section demonstrates the usage of `RE::ActorEquipManager`, the singleton responsible for managing item equipping and unequipping for actors. It shows how to equip an object to a specific slot, control the equip process (e.g., queuing, forcing, playing sounds), and unequip objects. ### Methods Demonstrated - `GetSingleton()` - `EquipObject(Actor*, BGSObjectInstance, std::uint32_t, std::uint32_t, EquipSlot*, bool, bool, bool, bool, bool)` - `UnequipObject(Actor*, const BGSObjectInstance*, std::uint32_t, EquipSlot*, std::uint32_t, bool, bool, bool, bool, EquipSlot*) ### Example Usage ```cpp #include void ExampleEquipManager(RE::Actor* a_actor) { const auto* em = RE::ActorEquipManager::GetSingleton(); // Look up a weapon form const auto* weapon = RE::TESForm::GetFormByID(0x0001F4A8); if (!weapon || !a_actor) return; // Build a BGSObjectInstance for the weapon (no mod instance data) RE::BGSObjectInstance weapInst(weapon, nullptr); // Equip the weapon in the right-hand slot (slot == nullptr means default slot) const bool equipped = em->EquipObject( a_actor, weapInst, 0, // stack ID 1, // count nullptr, // equip slot (nullptr = default) true, // queue equip false, // force equip true, // play sounds false, // apply now false // locked ); REX::INFO("Equip result: {}", equipped); // Unequip from the default slot em->UnequipObject( a_actor, &weapInst, 1, // count nullptr, // equip slot 0, // stack ID true, // queue equip false, // force equip true, // play sounds false, // apply now nullptr // slot being replaced ); } ``` ``` -------------------------------- ### Plugin Entry Point Declaration Source: https://context7.com/libxse/commonlibf4/llms.txt Every F4SE plugin DLL must export a PluginVersionData structure and a load callback using the provided macros. The F4SE_PLUGIN_VERSION macro declares the exported version data, and F4SE_PLUGIN_LOAD declares the DLL entry point called by the F4SE runtime. ```APIDOC ## Plugin Entry Point Declaration Every F4SE plugin DLL must export a `PluginVersionData` structure and a load callback using the provided macros. The `F4SE_PLUGIN_VERSION` macro declares the exported version data, and `F4SE_PLUGIN_LOAD` declares the DLL entry point called by the F4SE runtime. ```cpp #include #include // Declare and populate the plugin version info (exported as F4SEPlugin_Version) F4SE_PLUGIN_VERSION = []() noexcept { F4SE::PluginVersionData data{}; data.PluginVersion({ 1, 0, 0, 0 }); data.PluginName("MyPlugin"); data.AuthorName("AuthorName"); data.UsesAddressLibrary(true); data.IsLayoutDependent(true); data.CompatibleVersions({ F4SE::RUNTIME_1_10_163, F4SE::RUNTIME_1_10_984, F4SE::RUNTIME_1_11_191 }); return data; }(); // Entry point called by F4SE after all plugins are loaded F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { // Initialize the library: sets up logging, queries interfaces, and optionally allocates trampoline F4SE::Init(a_f4se, { .log = true, .logName = "MyPlugin", .trampoline = true, .trampolineSize = 64 }); // Register for F4SE lifecycle messages const auto messaging = F4SE::GetMessagingInterface(); messaging->RegisterListener([](F4SE::MessagingInterface::Message* a_msg) { switch (a_msg->type) { case F4SE::MessagingInterface::kGameDataReady: // Safe to access TESDataHandler, forms, etc. break; case F4SE::MessagingInterface::kPostLoadGame: // A save game was loaded break; } }); return true; } ``` ``` -------------------------------- ### Equip and Unequip Items with ActorEquipManager Source: https://context7.com/libxse/commonlibf4/llms.txt Shows how to use the RE::ActorEquipManager singleton to equip and unequip items for an actor. Ensure the weapon form is valid and the actor pointer is not null. The equip slot can be specified or left null for the default. ```cpp #include void ExampleEquipManager(RE::Actor* a_actor) { const auto* em = RE::ActorEquipManager::GetSingleton(); // Look up a weapon form const auto* weapon = RE::TESForm::GetFormByID(0x0001F4A8); if (!weapon || !a_actor) return; // Build a BGSObjectInstance for the weapon (no mod instance data) RE::BGSObjectInstance weapInst(weapon, nullptr); // Equip the weapon in the right-hand slot (slot == nullptr means default slot) const bool equipped = em->EquipObject( a_actor, weapInst, 0, // stack ID 1, // count nullptr, // equip slot (nullptr = default) true, // queue equip false, // force equip true, // play sounds false, // apply now false // locked ); REX::INFO("Equip result: {}", equipped); // Unequip from the default slot em->UnequipObject( a_actor, &weapInst, 1, // count nullptr, // equip slot 0, // stack ID true, // queue equip false, // force equip true, // play sounds false, // apply now nullptr // slot being replaced ); } ``` -------------------------------- ### F4SE::InputMap - Input Key Mappings Source: https://context7.com/libxse/commonlibf4/llms.txt Provides offset constants and conversion utilities for keyboard, mouse, and gamepad key codes within the F4SE input event system. ```APIDOC ## F4SE::InputMap — Input Key Mappings `F4SE::InputMap` provides offset constants and conversion utilities for keyboard, mouse, and gamepad key codes used throughout the F4SE input event system. ```cpp #include void ExampleInputMap() { // Key code offset constants // Keyboard keys: 0..255 // Mouse buttons: 256..263 // Mouse wheel up/down: 264..265 // Gamepad buttons: 266..281 REX::INFO("Keyboard offset: {}", F4SE::InputMap::kMacro_KeyboardOffset); // 0 REX::INFO("Mouse offset: {}", F4SE::InputMap::kMacro_MouseButtonOffset); // 256 REX::INFO("Gamepad offset: {}", F4SE::InputMap::kMacro_GamepadOffset); // 266 REX::INFO("Max macros: {}", F4SE::InputMap::kMaxMacros); // 282 // Convert XInput mask (e.g., XINPUT_GAMEPAD_A = 0x1000) to internal key code const std::uint32_t aButtonMask = 0x1000; // XINPUT_GAMEPAD_A const std::uint32_t keyCode = F4SE::InputMap::GamepadMaskToKeycode(aButtonMask); REX::INFO("A button key code: {}", keyCode); // Convert back to XInput mask const std::uint32_t mask = F4SE::InputMap::GamepadKeycodeToMask(keyCode); REX::INFO("Mask: {:X}", mask); // Named gamepad button offsets (for use in input event handlers) REX::INFO("DPAD_UP offset: {}", F4SE::InputMap::kGamepadButtonOffset_DPAD_UP); // 266 REX::INFO("A button offset: {}", F4SE::InputMap::kGamepadButtonOffset_A); // 276 REX::INFO("Left trigger offset: {}", F4SE::InputMap::kGamepadButtonOffset_LT); // 280 } ``` ``` -------------------------------- ### F4SE MessagingInterface: Registering and Dispatching Messages Source: https://context7.com/libxse/commonlibf4/llms.txt Subscribe to game lifecycle events and send/receive custom inter-plugin messages. Ensure F4SE is initialized before accessing the MessagingInterface. ```cpp #include F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { F4SE::Init(a_f4se); const auto* msg = F4SE::GetMessagingInterface(); // Listen to all F4SE events msg->RegisterListener([](F4SE::MessagingInterface::Message* a_msg) { switch (a_msg->type) { case F4SE::MessagingInterface::kPostLoad: // All plugins have loaded their DLLs break; case F4SE::MessagingInterface::kGameDataReady: // Forms, data handlers, etc. are accessible break; case F4SE::MessagingInterface::kPreLoadGame: // About to load a save; a_msg->data is the save file path (char*) REX::INFO("Loading save: {}", static_cast(a_msg->data)); break; case F4SE::MessagingInterface::kPostLoadGame: // Save loaded; a_msg->data is bool (success) break; case F4SE::MessagingInterface::kPreSaveGame: // About to save break; case F4SE::MessagingInterface::kNewGame: // New game started break; } }); // Dispatch a custom message to another plugin const char payload[] = "hello"; msg->Dispatch(0x1234, (void*)payload, sizeof(payload), "OtherPlugin"); // Listen for messages from a specific sender msg->RegisterListener([](F4SE::MessagingInterface::Message* a_msg) { REX::INFO("Got msg type {} from {}", a_msg->type, a_msg->sender); }, "OtherPlugin"); return true; } ``` -------------------------------- ### BSScript::NativeFunction and IVirtualMachine::BindNativeMethod Source: https://context7.com/libxse/commonlibf4/llms.txt Explains how `NativeFunction` wraps C++ functions for Papyrus and how `IVirtualMachine::BindNativeMethod` registers these functions, including compile-time validation. ```APIDOC ## BSScript::NativeFunction and IVirtualMachine::BindNativeMethod `NativeFunction` is the type-erased wrapper for C++ functions exposed to Papyrus. The `IVirtualMachine::BindNativeMethod` template constructs and registers a `NativeFunction` automatically, performing compile-time validation of all parameter and return types. ```cpp #include // Example: Expose inventory manipulation to Papyrus // Papyrus: MyMod.SetActorAlpha(akTarget, afAlpha) static void SetActorAlpha(RE::Actor& a_actor, float a_alpha) { a_actor.SetAlpha(a_alpha); } // Papyrus: MyMod.GetActorLevel(akTarget) -> int static std::int32_t GetActorLevel(RE::Actor& a_actor) { return static_cast(a_actor.GetLevel()); } // Papyrus: MyMod.LookupForm(aiFormID, asModName) -> Form static RE::TESForm* LookupFormNative( std::monostate, // static (global) function marker std::int32_t a_formID, std::string_view a_modName) { return RE::TESDataHandler::GetSingleton()->LookupForm( static_cast(a_formID), a_modName); } bool RegisterPapyrusFunctions(RE::BSScript::IVirtualMachine* a_vm) { // Instance method (first arg is actor self) a_vm->BindNativeMethod("MyMod", "SetActorAlpha", SetActorAlpha, /*taskletCallable=*/std::nullopt, /*isLatent=*/false); // Instance method returning a value a_vm->BindNativeMethod("MyMod", "GetActorLevel", GetActorLevel, std::nullopt, false); // Global (static) Papyrus function a_vm->BindNativeMethod("MyMod", "LookupForm", LookupFormNative, std::nullopt, false); return true; } ``` ``` -------------------------------- ### Schedule Tasks with F4SE TaskInterface Source: https://context7.com/libxse/commonlibf4/llms.txt Use TaskInterface to schedule one-shot, UI, or permanent tasks. Tasks can be lambdas or custom ITaskDelegate subclasses. Access game objects only on the main thread. ```cpp #include #include F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { F4SE::Init(a_f4se); const auto* tasks = F4SE::GetTaskInterface(); // Schedule a one-shot main-thread task using a lambda tasks->AddTask([]() { // Safe to access game objects here const auto player = RE::PlayerCharacter::GetSingleton(); if (player) { REX::INFO("Player form ID: {:X}", player->GetFormID()); } }); // Schedule a UI task (runs on the render/UI thread) tasks->AddUITask([]() { REX::INFO("Running on UI thread"); }); // Schedule a permanent repeating task (runs every frame) tasks->AddTaskPermanent([]() { // Called each game frame - keep this lightweight }); // Alternatively, use a custom ITaskDelegate subclass class MyTask : public F4SE::ITaskDelegate { public: void Run() override { REX::INFO("Custom task delegate running"); } }; tasks->AddTask(new MyTask()); return true; } ``` -------------------------------- ### F4SE SerializationInterface: Save/Load Data Persistence Source: https://context7.com/libxse/commonlibf4/llms.txt Persist plugin data across game saves using a typed record system. Register a unique plugin ID and define callbacks for saving, loading, and reverting data. Ensure F4SE is initialized before accessing the SerializationInterface. ```cpp #include static constexpr std::uint32_t kPluginUID = 'MPLG'; static constexpr std::uint32_t kRecordType = 'DATA'; struct MyData { float value; std::uint32_t count; }; static MyData g_data{ 1.0f, 42 }; void OnSave(const F4SE::SerializationInterface* a_intfc) { if (a_intfc->OpenRecord(kRecordType, 1)) { a_intfc->WriteRecordData(g_data); // template overload for non-pointer types } } void OnLoad(const F4SE::SerializationInterface* a_intfc) { std::uint32_t type, version, length; while (a_intfc->GetNextRecordInfo(type, version, length)) { if (type == kRecordType) { a_intfc->ReadRecordData(g_data); } } } void OnRevert(const F4SE::SerializationInterface*) { g_data = { 1.0f, 42 }; } F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { F4SE::Init(a_f4se); const auto* serial = F4SE::GetSerializationInterface(); serial->SetUniqueID(kPluginUID); serial->SetSaveCallback(OnSave); serial->SetLoadCallback(OnLoad); serial->SetRevertCallback(OnRevert); // Resolve a potentially stale form handle after loading // e.g., if you persisted a handle from a previous session: std::uint64_t oldHandle = 0x123456789; if (auto newHandle = serial->ResolveHandle(oldHandle)) { REX::INFO("Resolved handle: {:X}", *newHandle); } return true; } ``` -------------------------------- ### Generate Compile Commands JSON Source: https://github.com/libxse/commonlibf4/blob/main/README.md Generate a `compile_commands.json` file for use with language servers like clangd. This is an alternative to generating IDE-specific project files. ```bat xmake project -k compile_commands ``` -------------------------------- ### F4SE PapyrusInterface: Registering Native Functions Source: https://context7.com/libxse/commonlibf4/llms.txt Expose C++ functions to Papyrus scripts for use in game logic. Register instance methods and static functions using `IVirtualMachine::BindNativeMethod`. Ensure F4SE is initialized before accessing the PapyrusInterface. ```cpp #include #include // A native function bound to a Papyrus script class "MyScript" static std::int32_t AddValues(RE::TESObjectREFR& a_self, std::int32_t a_x, std::int32_t a_y) { REX::INFO("AddValues called on {:X}: {} + {}", a_self.GetFormID(), a_x, a_y); return a_x + a_y; } // A static (global) Papyrus function static float GetGlobalValue(std::monostate, float a_scale) { return 42.0f * a_scale; } bool RegisterFunctions(RE::BSScript::IVirtualMachine* a_vm) { // Instance method: Papyrus "MyScript.AddValues(akRef, x, y)" maps to C++ AddValues a_vm->BindNativeMethod("MyScript", "AddValues", AddValues, false, false); // Global function: Papyrus "MyScript.GetGlobalValue(scale)" a_vm->BindNativeMethod("MyScript", "GetGlobalValue", GetGlobalValue, false, false); return true; } F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { F4SE::Init(a_f4se); const auto* papyrus = F4SE::GetPapyrusInterface(); papyrus->Register(RegisterFunctions); return true; } ``` -------------------------------- ### RE::TESForm Source: https://context7.com/libxse/commonlibf4/llms.txt Provides static methods for looking up game forms by their runtime form ID or editor ID, and for safely downcasting to derived types. ```APIDOC ## TESForm — Base Game Object Lookup and Inspection `RE::TESForm` is the root of Fallout 4's form hierarchy. It provides static methods to look up forms by ID or editor ID and template methods to safely downcast to derived types. ### Usage Examples - **Look up a form by its runtime form ID:** ```cpp const auto* form = RE::TESForm::GetFormByID(0x0001F4A8); ``` - **Type-safe lookup by form ID:** ```cpp const auto* weapon = RE::TESForm::GetFormByID(0x0001F4A8); ``` - **Look up by editor ID:** ```cpp const auto* perk = RE::TESForm::GetFormByEditorID( RE::BSFixedString("LifeGiver01")); ``` - **Inspect form properties:** ```cpp if (form && form->IsDeleted()) { /* ... */ } if (form && form->IsDisabled()) { /* ... */ } if (form && form->IsActor()) { /* ... */ } if (form && form->IsWeapon()) { /* ... */ } ``` - **Get form type as string:** ```cpp RE::TESForm::GetFormTypeString(RE::ENUM_FORM_ID::kWEAP); ``` ``` -------------------------------- ### Pack and Unpack Papyrus Variables in C++ Source: https://context7.com/libxse/commonlibf4/llms.txt Demonstrates packing various C++ types (primitives, game objects, arrays) into a Papyrus Variable and unpacking them back. Ensure the correct C++ type is used for unpacking to match the packed type. ```cpp #include void ExampleVariableMarshalling() { // Pack a C++ value into a Papyrus Variable RE::BSScript::Variable var; // Primitives RE::BSScript::PackVariable(var, 42); // int32 RE::BSScript::PackVariable(var, 3.14f); // float RE::BSScript::PackVariable(var, true); // bool RE::BSScript::PackVariable(var, std::string_view("Hello")); // string // Game object pointer const auto* player = RE::PlayerCharacter::GetSingleton(); RE::BSScript::PackVariable(var, player); // Actor* -> Object // Array (std::vector) std::vector nums{ 1, 2, 3, 4 }; RE::BSScript::PackVariable(var, nums); // int[] in Papyrus // Unpack back to C++ types const auto i = RE::BSScript::UnpackVariable(var); const auto f = RE::BSScript::UnpackVariable(var); const auto b = RE::BSScript::UnpackVariable(var); const auto s = RE::BSScript::UnpackVariable(var); const auto act = RE::BSScript::UnpackVariable(var); // Actor* const auto arr = RE::BSScript::UnpackVariable>(var); REX::INFO("Unpacked int: {}", i); REX::INFO("Unpacked float: {:.2f}", f); } ``` -------------------------------- ### F4SE::PapyrusInterface — Registering Native Functions Source: https://context7.com/libxse/commonlibf4/llms.txt The PapyrusInterface allows plugins to expose C++ functions to Papyrus scripts. Functions are registered via `IVirtualMachine::BindNativeMethod` within a registration callback. ```APIDOC ## F4SE::PapyrusInterface — Registering Native Functions `PapyrusInterface` allows plugins to expose C++ functions to Papyrus scripts. Register functions via `IVirtualMachine::BindNativeMethod` inside the registration callback. ### Registering instance and global native functions: ```cpp #include #include // A native function bound to a Papyrus script class "MyScript" static std::int32_t AddValues(RE::TESObjectREFR& a_self, std::int32_t a_x, std::int32_t a_y) { REX::INFO("AddValues called on {:X}: {} + {}", a_self.GetFormID(), a_x, a_y); return a_x + a_y; } // A static (global) Papyrus function static float GetGlobalValue(std::monostate, float a_scale) { return 42.0f * a_scale; } bool RegisterFunctions(RE::BSScript::IVirtualMachine* a_vm) { // Instance method: Papyrus "MyScript.AddValues(akRef, x, y)" maps to C++ AddValues a_vm->BindNativeMethod("MyScript", "AddValues", AddValues, false, false); // Global function: Papyrus "MyScript.GetGlobalValue(scale)" a_vm->BindNativeMethod("MyScript", "GetGlobalValue", GetGlobalValue, false, false); return true; } F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { F4SE::Init(a_f4se); const auto* papyrus = F4SE::GetPapyrusInterface(); papyrus->Register(RegisterFunctions); return true; } ``` ``` -------------------------------- ### F4SE::TaskInterface Source: https://context7.com/libxse/commonlibf4/llms.txt Schedules tasks to run on the game's main thread or UI thread, ensuring thread safety when accessing game objects or UI elements. ```APIDOC ## F4SE::TaskInterface — Thread-Safe Task Scheduling `TaskInterface` schedules work to run on the game's main thread (for game object access) or the UI thread (for HUD/Scaleform), avoiding race conditions from plugin background threads. ### Usage Examples - **Schedule a one-shot main-thread task:** ```cpp tasks->AddTask([]() { // Safe to access game objects here }); ``` - **Schedule a UI task (runs on the render/UI thread):** ```cpp tasks->AddUITask([]() { // Code to run on the UI thread }); ``` - **Schedule a permanent repeating task (runs every frame):** ```cpp tasks->AddTaskPermanent([]() { // Called each game frame - keep this lightweight }); ``` - **Using a custom ITaskDelegate subclass:** ```cpp class MyTask : public F4SE::ITaskDelegate { public: void Run() override { // Custom task logic } }; tasks->AddTask(new MyTask()); ``` ``` -------------------------------- ### F4SE::SerializationInterface — Save/Load Persistence Source: https://context7.com/libxse/commonlibf4/llms.txt The SerializationInterface enables plugins to persist data across saves using a typed record system. Each plugin registers a unique ID and then writes/reads records in callbacks. ```APIDOC ## F4SE::SerializationInterface — Save/Load Persistence `SerializationInterface` enables plugins to persist data across saves using a typed record system. Each plugin registers a unique 4-byte ID, then writes/reads records in callbacks. ### Registering save, load, and revert callbacks: ```cpp static constexpr std::uint32_t kPluginUID = 'MPLG'; static constexpr std::uint32_t kRecordType = 'DATA'; struct MyData { float value; std::uint32_t count; }; static MyData g_data{ 1.0f, 42 }; void OnSave(const F4SE::SerializationInterface* a_intfc) { if (a_intfc->OpenRecord(kRecordType, 1)) { a_intfc->WriteRecordData(g_data); // template overload for non-pointer types } } void OnLoad(const F4SE::SerializationInterface* a_intfc) { std::uint32_t type, version, length; while (a_intfc->GetNextRecordInfo(type, version, length)) { if (type == kRecordType) { a_intfc->ReadRecordData(g_data); } } } void OnRevert(const F4SE::SerializationInterface*) { g_data = { 1.0f, 42 }; } F4SE_PLUGIN_LOAD(const F4SE::LoadInterface* a_f4se) { F4SE::Init(a_f4se); const auto* serial = F4SE::GetSerializationInterface(); serial->SetUniqueID(kPluginUID); serial->SetSaveCallback(OnSave); serial->SetLoadCallback(OnLoad); serial->SetRevertCallback(OnRevert); // Resolve a potentially stale form handle after loading std::uint64_t oldHandle = 0x123456789; if (auto newHandle = serial->ResolveHandle(oldHandle)) { REX::INFO("Resolved handle: {:X}", *newHandle); } return true; } ``` ```