diff --git a/Plugin/src/Forms/Forms.h b/Plugin/src/Forms/Forms.h index b157fd1..53bca10 100644 --- a/Plugin/src/Forms/Forms.h +++ b/Plugin/src/Forms/Forms.h @@ -1,136 +1,136 @@ -#pragma once - -namespace Forms -{ - class PANameScheme - { - public: - static void Install() - { - map.clear(); - LoadSetting(); - } - - static std::string Get(RE::TESObjectARMO* a_armo) - { - for (auto& [kywd, name] : map) - { - if (a_armo->HasKeyword(kywd)) - { - return name; - } - } - - return std::string{}; - } - - private: - static void LoadSetting() - { - const auto root = std::filesystem::path{ "Data/F4SE/plugins/BakaPowerArmorStorage/"sv }; - if (auto TESDataHandler = RE::TESDataHandler::GetSingleton()) - { - for (auto iter : TESDataHandler->compiledFileCollection.files) - { - auto path = root / iter->GetFilename(); - path.replace_extension("ini"sv); - LoadSetting(path); - } - - for (auto iter : TESDataHandler->compiledFileCollection.smallFiles) - { - auto path = root / iter->GetFilename(); - path.replace_extension("ini"sv); - LoadSetting(path); - } - } - } - - static void LoadSetting(std::filesystem::path& a_path) - { - if (!std::filesystem::exists(a_path)) - { - return; - } - - ini_file.LoadFile(a_path.c_str()); - std::list Sections; - ini_file.GetAllSections(Sections); - - bool bActive{ false }; - for (auto& iter : Sections) - { - if (auto form = ini_file.GetValue(iter.pItem, "keyword", nullptr)) - { - if (auto kywd = RE::TESForm::GetFormByEditorID(form)) - { - map.emplace(kywd, iter.pItem); - bActive = true; - } - } - } - - if (bActive) - { - logger::info("Loaded from: {:s}"sv, a_path.string()); - } - - ini_file.Reset(); - } - - inline static CSimpleIniA ini_file{ true }; - inline static RE::BSTHashMap map; - }; - - RE::BGSDefaultObject* PAFrameWorkshop_DO{ nullptr }; - RE::BGSDefaultObject* PAFrameToken_DO{ nullptr }; - RE::BGSDefaultObject* PAFramePerk_DO{ nullptr }; - - RE::BGSKeyword* ap_PowerArmor_BodyMod{ nullptr }; - - namespace - { - RE::BGSDefaultObject* WorkshopItemPlaceFailSound{ nullptr }; - - std::uint32_t hkDefaultObject() - { - // Initializer override - WorkshopItemPlaceFailSound = - RE::DefaultObjectFormFactory::Create( - "WorkshopItemPlaceFailSound", - "Default sound played when an item can not be placed.", - RE::ENUM_FORM_ID::kSNDR); - - // Add new - PAFrameWorkshop_DO = - RE::DefaultObjectFormFactory::Create( - "PAFrameWorkshop_DO", - RE::ENUM_FORM_ID::kCONT); - - PAFrameToken_DO = - RE::DefaultObjectFormFactory::Create( - "PAFrameToken_DO", - RE::ENUM_FORM_ID::kARMO); - - PAFramePerk_DO = - RE::DefaultObjectFormFactory::Create( - "PAFramePerk_DO", - RE::ENUM_FORM_ID::kPERK); - - logger::debug("Injected DefaultObjects."sv); - return 1; - } - } - - void Install() - { - REL::Relocation targetDFOB{ REL::ID(599538) }; - stl::asm_replace(targetDFOB.address(), 0x2C, reinterpret_cast(hkDefaultObject)); - } - - void InstallDataReady() - { - PANameScheme::Install(); - ap_PowerArmor_BodyMod = RE::TESForm::GetFormByID(0x00055F8C); - } -}; +#pragma once + +namespace Forms +{ + class PANameScheme + { + public: + static void Install() + { + map.clear(); + LoadSetting(); + } + + static std::string Get(RE::TESObjectARMO* a_armo) + { + for (auto& [kywd, name] : map) + { + if (a_armo->HasKeyword(kywd)) + { + return name; + } + } + + return std::string{}; + } + + private: + static void LoadSetting() + { + const auto root = std::filesystem::path{ "Data/F4SE/plugins/BakaPowerArmorStorage/"sv }; + if (auto TESDataHandler = RE::TESDataHandler::GetSingleton()) + { + for (auto iter : TESDataHandler->compiledFileCollection.files) + { + auto path = root / iter->GetFilename(); + path.replace_extension("ini"sv); + LoadSetting(path); + } + + for (auto iter : TESDataHandler->compiledFileCollection.smallFiles) + { + auto path = root / iter->GetFilename(); + path.replace_extension("ini"sv); + LoadSetting(path); + } + } + } + + static void LoadSetting(std::filesystem::path& a_path) + { + if (!std::filesystem::exists(a_path)) + { + return; + } + + ini_file.LoadFile(a_path.c_str()); + std::list Sections; + ini_file.GetAllSections(Sections); + + bool bActive{ false }; + for (auto& iter : Sections) + { + if (auto form = ini_file.GetValue(iter.pItem, "keyword", nullptr)) + { + if (auto kywd = RE::TESForm::GetFormByEditorID(form)) + { + map.emplace(kywd, iter.pItem); + bActive = true; + } + } + } + + if (bActive) + { + logger::info("Loaded from: {:s}"sv, a_path.string()); + } + + ini_file.Reset(); + } + + inline static CSimpleIniA ini_file{ true }; + inline static RE::BSTHashMap map; + }; + + RE::BGSDefaultObject* PAFrameWorkshop_DO{ nullptr }; + RE::BGSDefaultObject* PAFrameToken_DO{ nullptr }; + RE::BGSDefaultObject* PAFramePerk_DO{ nullptr }; + + RE::BGSKeyword* ap_PowerArmor_BodyMod{ nullptr }; + + namespace + { + RE::BGSDefaultObject* WorkshopItemPlaceFailSound{ nullptr }; + + std::uint32_t hkDefaultObject() + { + // Initializer override + WorkshopItemPlaceFailSound = + RE::DefaultObjectFormFactory::Create( + "WorkshopItemPlaceFailSound", + "Default sound played when an item can not be placed.", + RE::ENUM_FORM_ID::kSNDR); + + // Add new + PAFrameWorkshop_DO = + RE::DefaultObjectFormFactory::Create( + "PAFrameWorkshop_DO", + RE::ENUM_FORM_ID::kCONT); + + PAFrameToken_DO = + RE::DefaultObjectFormFactory::Create( + "PAFrameToken_DO", + RE::ENUM_FORM_ID::kARMO); + + PAFramePerk_DO = + RE::DefaultObjectFormFactory::Create( + "PAFramePerk_DO", + RE::ENUM_FORM_ID::kPERK); + + logger::debug("Injected DefaultObjects."sv); + return 1; + } + } + + void Install() + { + REL::Relocation targetDFOB{ REL::ID(599538) }; + stl::asm_replace(targetDFOB.address(), 0x2C, reinterpret_cast(hkDefaultObject)); + } + + void InstallDataReady() + { + PANameScheme::Install(); + ap_PowerArmor_BodyMod = RE::TESForm::GetFormByID(0x00055F8C); + } +}; diff --git a/Plugin/src/MCM/MCM.h b/Plugin/src/MCM/MCM.h index 5cebfcb..a2221de 100644 --- a/Plugin/src/MCM/MCM.h +++ b/Plugin/src/MCM/MCM.h @@ -1,128 +1,127 @@ -#pragma once - -namespace MCM -{ - class Settings - { - public: - class General - { - public: - inline static bool bAutoAutoReturn{ true }; - }; - - class Formatting - { - public: - inline static std::string sPAAdded; - inline static std::string sPAChassis; - inline static std::string sPARecall; - inline static std::string sPARecallTimer; - }; - - class Runtime - { - public: - inline static std::int32_t iKeyCode; - }; - - static void Update() - { - if (m_FirstRun) - { - GetTranslationStrings(); - m_FirstRun = false; - } - - m_ini_base.LoadFile("Data/MCM/Config/BakaPowerArmorStorage/settings.ini"); - m_ini_user.LoadFile("Data/MCM/Settings/BakaPowerArmorStorage.ini"); - - GetModSettingBool("General", "bAutoAutoReturn", General::bAutoAutoReturn); - - HandleKeybinds(); - - m_ini_base.Reset(); - m_ini_user.Reset(); - } - - inline static bool m_FirstRun{ true }; - - private: - static void GetModSettingChar(const std::string& a_section, const std::string& a_setting, std::string_view& a_value) - { - auto base = m_ini_base.GetValue(a_section.c_str(), a_setting.c_str(), a_value.data()); - auto user = m_ini_user.GetValue(a_section.c_str(), a_setting.c_str(), base); - a_value = user; - } - - static void GetModSettingBool(const std::string& a_section, const std::string& a_setting, bool& a_value) - { - auto base = m_ini_base.GetBoolValue(a_section.c_str(), a_setting.c_str(), a_value); - auto user = m_ini_user.GetBoolValue(a_section.c_str(), a_setting.c_str(), base); - a_value = user; - } - - static void GetModSettingLong(const std::string& a_section, const std::string& a_setting, std::int32_t& a_value) - { - auto base = m_ini_base.GetLongValue(a_section.c_str(), a_setting.c_str(), a_value); - auto user = m_ini_user.GetLongValue(a_section.c_str(), a_setting.c_str(), base); - a_value = static_cast(user); - } - - static void GetTranslationStrings() - { - if (auto BSScaleformManager = RE::BSScaleformManager::GetSingleton(); BSScaleformManager && BSScaleformManager->loader) - { - if (auto BSScaleformTranslator = - static_cast( - BSScaleformManager->loader->GetStateAddRef( - RE::Scaleform::GFx::State::StateType::kTranslator))) - { - auto FetchTranslation = [](RE::BSScaleformTranslator* a_trns, const wchar_t* a_key, std::string& a_output) - { - auto it = a_trns->translator.translationMap.find(a_key); - if (it != a_trns->translator.translationMap.end()) - { - a_output.resize(512); - sprintf_s(a_output.data(), 512, "%ws", it->second.data()); - } - }; - - FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PAAdded", Formatting::sPAAdded); - FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PAChassis", Formatting::sPAChassis); - FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PARecall", Formatting::sPARecall); - FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PARecallTimer", Formatting::sPARecallTimer); - } - } - } - - static void HandleKeybinds() - { - try - { - std::ifstream fstream{ "Data/MCM/Settings/Keybinds.json" }; - nlohmann::json data = - nlohmann::json::parse(fstream); - fstream.close(); - - for (auto& iter : data["keybinds"sv]) - { - if (iter["id"sv] == "ModifierKey" - && iter["modName"sv] == "BakaPowerArmorStorage") - { - Runtime::iKeyCode = iter["keycode"sv]; - break; - } - } - } - catch (std::exception& a_exception) - { - Runtime::iKeyCode = 0; - logger::debug("{:s}"sv, a_exception.what()); - } - } - - inline static CSimpleIniA m_ini_base{ true }; - inline static CSimpleIniA m_ini_user{ true }; - }; -} +#pragma once + +namespace MCM +{ + class Settings + { + public: + class General + { + public: + inline static bool bAutoAutoReturn{ true }; + }; + + class Formatting + { + public: + inline static std::string sPAAdded; + inline static std::string sPAChassis; + inline static std::string sPARecall; + inline static std::string sPARecallTimer; + }; + + class Runtime + { + public: + inline static std::int32_t iKeyCode; + }; + + static void Update() + { + if (m_FirstRun) + { + GetTranslationStrings(); + m_FirstRun = false; + } + + m_ini_base.LoadFile("Data/MCM/Config/BakaPowerArmorStorage/settings.ini"); + m_ini_user.LoadFile("Data/MCM/Settings/BakaPowerArmorStorage.ini"); + + GetModSettingBool("General", "bAutoAutoReturn", General::bAutoAutoReturn); + + HandleKeybinds(); + + m_ini_base.Reset(); + m_ini_user.Reset(); + } + + inline static bool m_FirstRun{ true }; + + private: + static void GetModSettingChar(const std::string& a_section, const std::string& a_setting, std::string_view& a_value) + { + auto base = m_ini_base.GetValue(a_section.c_str(), a_setting.c_str(), a_value.data()); + auto user = m_ini_user.GetValue(a_section.c_str(), a_setting.c_str(), base); + a_value = user; + } + + static void GetModSettingBool(const std::string& a_section, const std::string& a_setting, bool& a_value) + { + auto base = m_ini_base.GetBoolValue(a_section.c_str(), a_setting.c_str(), a_value); + auto user = m_ini_user.GetBoolValue(a_section.c_str(), a_setting.c_str(), base); + a_value = user; + } + + static void GetModSettingLong(const std::string& a_section, const std::string& a_setting, std::int32_t& a_value) + { + auto base = m_ini_base.GetLongValue(a_section.c_str(), a_setting.c_str(), a_value); + auto user = m_ini_user.GetLongValue(a_section.c_str(), a_setting.c_str(), base); + a_value = static_cast(user); + } + + static void GetTranslationStrings() + { + if (auto BSScaleformManager = RE::BSScaleformManager::GetSingleton(); BSScaleformManager && BSScaleformManager->loader) + { + if (auto BSScaleformTranslator = + static_cast( + BSScaleformManager->loader->GetStateAddRef( + RE::Scaleform::GFx::State::StateType::kTranslator))) + { + auto FetchTranslation = [](RE::BSScaleformTranslator* a_trns, const wchar_t* a_key, std::string& a_output) + { + auto it = a_trns->translator.translationMap.find(a_key); + if (it != a_trns->translator.translationMap.end()) + { + a_output.resize(512); + sprintf_s(a_output.data(), 512, "%ws", it->second.data()); + } + }; + + FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PAAdded", Formatting::sPAAdded); + FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PAChassis", Formatting::sPAChassis); + FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PARecall", Formatting::sPARecall); + FetchTranslation(BSScaleformTranslator, L"$BakaPAS_Message_PARecallTimer", Formatting::sPARecallTimer); + } + } + } + + static void HandleKeybinds() + { + try + { + std::ifstream fstream{ "Data/MCM/Settings/Keybinds.json" }; + nlohmann::json data = + nlohmann::json::parse(fstream); + fstream.close(); + + for (auto& iter : data["keybinds"sv]) + { + if (iter["id"sv] == "ModifierKey" && iter["modName"sv] == "BakaPowerArmorStorage") + { + Runtime::iKeyCode = iter["keycode"sv]; + break; + } + } + } + catch (std::exception& a_exception) + { + Runtime::iKeyCode = 0; + logger::debug("{:s}"sv, a_exception.what()); + } + } + + inline static CSimpleIniA m_ini_base{ true }; + inline static CSimpleIniA m_ini_user{ true }; + }; +} diff --git a/Plugin/src/PCH.cpp b/Plugin/src/PCH.cpp index 8607860..3b889e8 100644 --- a/Plugin/src/PCH.cpp +++ b/Plugin/src/PCH.cpp @@ -1,37 +1,37 @@ -#include "PCH.h" - -namespace stl -{ - namespace detail - { - struct asm_patch : - Xbyak::CodeGenerator - { - asm_patch(std::uintptr_t a_dst) - { - Xbyak::Label dst; - - jmp(ptr[rip + dst]); - - L(dst); - dq(a_dst); - } - }; - } - - void asm_jump(std::uintptr_t a_from, [[maybe_unused]] std::size_t a_size, std::uintptr_t a_to) - { - detail::asm_patch p{ a_to }; - p.ready(); - assert(p.getSize() <= a_size); - REL::safe_write( - a_from, - std::span{ p.getCode(), p.getSize() }); - } - - void asm_replace(std::uintptr_t a_from, std::size_t a_size, std::uintptr_t a_to) - { - REL::safe_fill(a_from, REL::INT3, a_size); - asm_jump(a_from, a_size, a_to); - } -} +#include "PCH.h" + +namespace stl +{ + namespace detail + { + struct asm_patch : + Xbyak::CodeGenerator + { + asm_patch(std::uintptr_t a_dst) + { + Xbyak::Label dst; + + jmp(ptr[rip + dst]); + + L(dst); + dq(a_dst); + } + }; + } + + void asm_jump(std::uintptr_t a_from, [[maybe_unused]] std::size_t a_size, std::uintptr_t a_to) + { + detail::asm_patch p{ a_to }; + p.ready(); + assert(p.getSize() <= a_size); + REL::safe_write( + a_from, + std::span{ p.getCode(), p.getSize() }); + } + + void asm_replace(std::uintptr_t a_from, std::size_t a_size, std::uintptr_t a_to) + { + REL::safe_fill(a_from, REL::INT3, a_size); + asm_jump(a_from, a_size, a_to); + } +} diff --git a/Plugin/src/PCH.h b/Plugin/src/PCH.h index f525730..3cf0c2f 100644 --- a/Plugin/src/PCH.h +++ b/Plugin/src/PCH.h @@ -1,31 +1,31 @@ -#pragma once - -#define WIN32_LEAN_AND_MEAN -#define NOMINMAX - -#include "F4SE/F4SE.h" -#include "RE/Fallout.h" - -#include -#include -#include -#include -#include - -#define DLLEXPORT __declspec(dllexport) - -using namespace std::literals; - -namespace logger = F4SE::log; - -namespace stl -{ - using namespace F4SE::stl; - - void asm_replace(std::uintptr_t a_from, std::size_t a_size, std::uintptr_t a_to); -} - +#pragma once + +#define WIN32_LEAN_AND_MEAN +#define NOMINMAX + +#include "F4SE/F4SE.h" +#include "RE/Fallout.h" + +#include +#include +#include +#include +#include + +#define DLLEXPORT __declspec(dllexport) + +using namespace std::literals; + +namespace logger = F4SE::log; + +namespace stl +{ + using namespace F4SE::stl; + + void asm_replace(std::uintptr_t a_from, std::size_t a_size, std::uintptr_t a_to); +} + // clang-format off #include "Version.h" -#include "Settings.h" -// clang-format on +#include "Settings.h" +// clang-format on diff --git a/Plugin/src/Scripts/Papyrus.h b/Plugin/src/Scripts/Papyrus.h index 681dbe7..bc3db23 100644 --- a/Plugin/src/Scripts/Papyrus.h +++ b/Plugin/src/Scripts/Papyrus.h @@ -1,91 +1,91 @@ -#pragma once - -#include "MCM/MCM.h" -#include "Workshop/Workshop.h" - -namespace Papyrus -{ - namespace BakaPowerArmorStorage - { - constexpr auto SCRIPT_NAME{ "BakaPowerArmorStorage"sv }; - - bool CreateToken(std::monostate, RE::TESObjectREFR* a_refr) - { - return Workshop::PlacementMode::CreateToken(a_refr); - } - - bool HandleToken(std::monostate, RE::TESObjectREFR* a_refr) - { - return Workshop::PlacementMode::HandleToken(a_refr); - } - - bool AttachScript(std::monostate, RE::TESObjectREFR* a_refr) - { - return Workshop::PlacementMode::AttachScript(a_refr); - } - - bool RemoveScript(std::monostate, RE::TESObjectREFR* a_refr) - { - return Workshop::PlacementMode::RemoveScript(a_refr); - } - - void ShowNotification(std::monostate, std::int32_t a_time) - { - if (a_time < 0) - { - RE::SendHUDMessage::ShowHUDMessage( - MCM::Settings::Formatting::sPAAdded.c_str(), - nullptr, - true, - true); - } - else if (a_time == 0) - { - RE::SendHUDMessage::ShowHUDMessage( - MCM::Settings::Formatting::sPARecall.c_str(), - nullptr, - false, - true); - } - else - { - auto msg = fmt::format(fmt::runtime(MCM::Settings::Formatting::sPARecallTimer.data()), a_time); - RE::SendHUDMessage::ShowHUDMessage( - msg.c_str(), - nullptr, - false, - true); - } - } - - void UpdateSettings(std::monostate) - { - MCM::Settings::Update(); - } - - std::int32_t GetKeyCode(std::monostate) - { - return MCM::Settings::Runtime::iKeyCode; - } - - void FunctionStub(std::monostate) - { - return; - } - } - - bool RegisterFunctions(RE::BSScript::IVirtualMachine* a_VM) - { - // BakaPowerArmorStorage - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "CreateToken", BakaPowerArmorStorage::CreateToken, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "HandleToken", BakaPowerArmorStorage::HandleToken, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "AttachScript", BakaPowerArmorStorage::AttachScript, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "RemoveScript", BakaPowerArmorStorage::RemoveScript, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "ShowNotification", BakaPowerArmorStorage::ShowNotification, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "UpdateSettings", BakaPowerArmorStorage::UpdateSettings, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "GetKeyCode", BakaPowerArmorStorage::GetKeyCode, true); - a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "FunctionStub", BakaPowerArmorStorage::FunctionStub, true); - - return true; - } -} +#pragma once + +#include "MCM/MCM.h" +#include "Workshop/Workshop.h" + +namespace Papyrus +{ + namespace BakaPowerArmorStorage + { + constexpr auto SCRIPT_NAME{ "BakaPowerArmorStorage"sv }; + + bool CreateToken(std::monostate, RE::TESObjectREFR* a_refr) + { + return Workshop::PlacementMode::CreateToken(a_refr); + } + + bool HandleToken(std::monostate, RE::TESObjectREFR* a_refr) + { + return Workshop::PlacementMode::HandleToken(a_refr); + } + + bool AttachScript(std::monostate, RE::TESObjectREFR* a_refr) + { + return Workshop::PlacementMode::AttachScript(a_refr); + } + + bool RemoveScript(std::monostate, RE::TESObjectREFR* a_refr) + { + return Workshop::PlacementMode::RemoveScript(a_refr); + } + + void ShowNotification(std::monostate, std::int32_t a_time) + { + if (a_time < 0) + { + RE::SendHUDMessage::ShowHUDMessage( + MCM::Settings::Formatting::sPAAdded.c_str(), + nullptr, + true, + true); + } + else if (a_time == 0) + { + RE::SendHUDMessage::ShowHUDMessage( + MCM::Settings::Formatting::sPARecall.c_str(), + nullptr, + false, + true); + } + else + { + auto msg = fmt::format(fmt::runtime(MCM::Settings::Formatting::sPARecallTimer.data()), a_time); + RE::SendHUDMessage::ShowHUDMessage( + msg.c_str(), + nullptr, + false, + true); + } + } + + void UpdateSettings(std::monostate) + { + MCM::Settings::Update(); + } + + std::int32_t GetKeyCode(std::monostate) + { + return MCM::Settings::Runtime::iKeyCode; + } + + void FunctionStub(std::monostate) + { + return; + } + } + + bool RegisterFunctions(RE::BSScript::IVirtualMachine* a_VM) + { + // BakaPowerArmorStorage + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "CreateToken", BakaPowerArmorStorage::CreateToken, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "HandleToken", BakaPowerArmorStorage::HandleToken, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "AttachScript", BakaPowerArmorStorage::AttachScript, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "RemoveScript", BakaPowerArmorStorage::RemoveScript, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "ShowNotification", BakaPowerArmorStorage::ShowNotification, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "UpdateSettings", BakaPowerArmorStorage::UpdateSettings, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "GetKeyCode", BakaPowerArmorStorage::GetKeyCode, true); + a_VM->BindNativeMethod(BakaPowerArmorStorage::SCRIPT_NAME, "FunctionStub", BakaPowerArmorStorage::FunctionStub, true); + + return true; + } +} diff --git a/Plugin/src/Settings.h b/Plugin/src/Settings.h index ce29340..728eeeb 100644 --- a/Plugin/src/Settings.h +++ b/Plugin/src/Settings.h @@ -1,46 +1,46 @@ -#pragma once - -namespace Settings -{ - namespace - { - using bSetting = AutoTOML::bSetting; - using ISetting = AutoTOML::ISetting; - } - - namespace General - { - inline bSetting EnableDebugLogging{ "General"s, "EnableDebugLogging"s, false }; - } - - inline void Load() - { - try - { - const auto table = toml::parse_file( - fmt::format(FMT_STRING("Data/F4SE/Plugins/{:s}.toml"sv), Version::PROJECT)); - for (const auto& setting : ISetting::get_settings()) - { - setting->load(table); - } - } - catch (const toml::parse_error& e) - { - std::ostringstream ss; - ss - << "Error parsing file \'" << *e.source().path << "\':\n" - << '\t' << e.description() << '\n' - << "\t\t(" << e.source().begin << ')'; - logger::error(FMT_STRING("{:s}"sv), ss.str()); - stl::report_and_fail("Failed to load settings."sv); - } - catch (const std::exception& e) - { - stl::report_and_fail(e.what()); - } - catch (...) - { - stl::report_and_fail("Unknown failure."sv); - } - } -} +#pragma once + +namespace Settings +{ + namespace + { + using bSetting = AutoTOML::bSetting; + using ISetting = AutoTOML::ISetting; + } + + namespace General + { + inline bSetting EnableDebugLogging{ "General"s, "EnableDebugLogging"s, false }; + } + + inline void Load() + { + try + { + const auto table = toml::parse_file( + fmt::format(FMT_STRING("Data/F4SE/Plugins/{:s}.toml"sv), Version::PROJECT)); + for (const auto& setting : ISetting::get_settings()) + { + setting->load(table); + } + } + catch (const toml::parse_error& e) + { + std::ostringstream ss; + ss + << "Error parsing file \'" << *e.source().path << "\':\n" + << '\t' << e.description() << '\n' + << "\t\t(" << e.source().begin << ')'; + logger::error(FMT_STRING("{:s}"sv), ss.str()); + stl::report_and_fail("Failed to load settings."sv); + } + catch (const std::exception& e) + { + stl::report_and_fail(e.what()); + } + catch (...) + { + stl::report_and_fail("Unknown failure."sv); + } + } +} diff --git a/Plugin/src/Workshop/Workshop.h b/Plugin/src/Workshop/Workshop.h index 480161c..4242e1e 100644 --- a/Plugin/src/Workshop/Workshop.h +++ b/Plugin/src/Workshop/Workshop.h @@ -1,826 +1,813 @@ -#pragma once - -#include "MCM/MCM.h" - -namespace Workshop -{ - class PlacementMode : - public RE::BSTEventSink, - public RE::BSTEventSink, - public RE::BSTEventSink - { - public: - class Hooks - { - public: - static void Install() - { - // Disable selection in UI - hkCanNavigate<119865, 0x37A>::Install(); - hkCanNavigate<119865, 0x3EA>::Install(); - hkCanNavigate<119865, 0x4C5>::Install(); - hkCanNavigate<119865, 0x768>::Install(); - hkCanNavigate<985073, 0x0C>::Install(); - hkCanNavigate<1130413, 0x0C>::Install(); - - // Prevent Workshops marked as deleted as being valid - hkIsReferenceWithinBuildableArea<2562, 0x156>::Install(); - hkIsReferenceWithinBuildableArea<978467, 0x07E>::Install(); - hkIsReferenceWithinBuildableArea<90862, 0x039>::Install(); - hkIsReferenceWithinBuildableArea<939377, 0x090>::Install(); - hkIsReferenceWithinBuildableArea<552874, 0x024>::Install(); - hkIsReferenceWithinBuildableArea<525394, 0x017>::Install(); - hkIsReferenceWithinBuildableArea<311311, 0x0A6>::Install(); - hkIsReferenceWithinBuildableArea<566990, 0x05C>::Install(); - hkIsReferenceWithinBuildableArea<286947, 0x047>::Install(); - hkIsReferenceWithinBuildableArea<1371490, 0x091>::Install(); - hkIsReferenceWithinBuildableArea<931840, 0x165>::Install(); - hkIsReferenceWithinBuildableArea<1515428, 0x036>::Install(); - - // Disable Workshop Startup/End sounds - hkPlayMenuSound<598489, 0x1195>::Install(); - hkPlayMenuSound<98443, 0x01B5>::Install(); - - // Prevent tagging for search in FreeBuild mode - hkShouldShowTagForSearch<119865, 0xEBB>::Install(); - hkShouldShowTagForSearch<1089189, 0x574>::Install(); - - // Prevent a stupid textbox from showing up for a split second after placing an item - hkUpdateRequirements<598489, 0x1144>::Install(); - hkUpdateRequirements<1280212, 0x1F0>::Install(); - hkUpdateRequirements<119865, 0x05C5>::Install(); - hkUpdateRequirements<119865, 0x0868>::Install(); - hkUpdateRequirements<119865, 0x0A16>::Install(); - hkUpdateRequirements<119865, 0x0A42>::Install(); - hkUpdateRequirements<119865, 0x0A65>::Install(); - hkUpdateRequirements<119865, 0x0A88>::Install(); - hkUpdateRequirements<119865, 0x0C64>::Install(); - hkUpdateRequirements<119865, 0x0CD4>::Install(); - hkUpdateRequirements<119865, 0x0F53>::Install(); - hkUpdateRequirements<119865, 0x10FD>::Install(); - hkUpdateRequirements<119865, 0x1181>::Install(); - hkUpdateRequirements<119865, 0x13F6>::Install(); - hkUpdateRequirements<119865, 0x1764>::Install(); - - // Prevent stored frames from stacking - hkCompareImpl::Install(); - - // Redirect Cancel input, block other buttons - hkHandleEvent::Install(); - - // Enable ExtraStartingWorldOrCell as a stacking condition - hkUIQualifier::Install(); - } - - private: - template - class hkCanNavigate - { - public: - static void Install() - { - static REL::Relocation target{ REL::ID(ID), OFF }; - auto& trampoline = F4SE::GetTrampoline(); - _CanNavigate = trampoline.write_call<5>(target.address(), CanNavigate); - } - - private: - static bool CanNavigate() - { - return PlacementMode::IsActive() ? false : _CanNavigate(); - } - - inline static REL::Relocation _CanNavigate; - }; - - template - class hkIsReferenceWithinBuildableArea - { - public: - static void Install() - { - static REL::Relocation target{ REL::ID(ID), OFF }; - auto& trampoline = F4SE::GetTrampoline(); - _IsReferenceWithinBuildableArea = trampoline.write_call<5>(target.address(), IsReferenceWithinBuildableArea); - } - - private: - static bool IsReferenceWithinBuildableArea( - const RE::TESObjectREFR& a_workshop, - const RE::TESObjectREFR& a_refr) - { - if (PlacementMode::IsActive()) - { - return true; - } - - if (a_workshop.formFlags & 0x20) - { - return false; - } - - return _IsReferenceWithinBuildableArea(a_workshop, a_refr); - } - - inline static REL::Relocation _IsReferenceWithinBuildableArea; - }; - - template - class hkPlayMenuSound - { - public: - static void Install() - { - static REL::Relocation target{ REL::ID(ID), OFF }; - auto& trampoline = F4SE::GetTrampoline(); - _PlayMenuSound = trampoline.write_call<5>(target.address(), PlayMenuSound); - } - - private: - static bool PlayMenuSound( - const char* a_soundName) - { - if (PlacementMode::IsActive()) - { - PlacementMode::GetSingleton()->m_hasSound = true; - return _PlayMenuSound(nullptr); - } - - if (PlacementMode::GetSingleton()->m_hasSound) - { - if (_stricmp(a_soundName, "UIWorkshopModeExit") == 0) - { - PlacementMode::GetSingleton()->m_hasSound = false; - return _PlayMenuSound(nullptr); - } - } - - return _PlayMenuSound(a_soundName); - } - - inline static REL::Relocation _PlayMenuSound; - }; - - template - class hkShouldShowTagForSearch - { - public: - static void Install() - { - static REL::Relocation target{ REL::ID(ID), OFF }; - auto& trampoline = F4SE::GetTrampoline(); - _ShouldShowTagForSearch = trampoline.write_call<5>(target.address(), ShouldShowTagForSearch); - } - - private: - static std::uint64_t ShouldShowTagForSearch( - RE::WorkshopMenu* a_this) - { - return PlacementMode::IsActive() ? 0 : _ShouldShowTagForSearch(a_this); - } - - inline static REL::Relocation _ShouldShowTagForSearch; - }; - - template - class hkUpdateRequirements - { - public: - static void Install() - { - static REL::Relocation target{ REL::ID(ID), OFF }; - auto& trampoline = F4SE::GetTrampoline(); - _UpdateRequirements = trampoline.write_call<5>(target.address(), UpdateRequirements); - } - - private: - static void UpdateRequirements( - RE::WorkshopMenu* a_this, - bool a_stringingWire) - { - if (PlacementMode::IsActive()) - { - return; - } - - _UpdateRequirements(a_this, a_stringingWire); - } - - inline static REL::Relocation _UpdateRequirements; - }; - - class hkCompareImpl - { - public: - static void Install() - { - static REL::Relocation target{ RE::ExtraStartingWorldOrCell::VTABLE[0] }; - target.write_vfunc(0x01, CompareImpl); - } - - private: - static bool CompareImpl( - RE::ExtraStartingWorldOrCell* a_this, - const RE::ExtraStartingWorldOrCell& a_compare) - { - if (!a_this) - { - return true; - } - - if (a_this->type != RE::ExtraStartingWorldOrCell::TYPE - || a_compare.type != RE::ExtraStartingWorldOrCell::TYPE) - { - return true; - } - - if (a_this->startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kCELL - || a_this->startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kWRLD - || a_compare.startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kCELL - || a_compare.startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kWRLD) - { - return false; - } - - return a_this->startingWorldOrCell != a_compare.startingWorldOrCell; - } - }; - - class hkHandleEvent - { - public: - static void Install() - { - static REL::Relocation target{ RE::WorkshopMenu::VTABLE[1] }; - _HandleEvent = target.write_vfunc(0x08, HandleEvent); - } - - private: - static void HandleEvent( - RE::BSInputEventUser* a_this, - const RE::ButtonEvent* a_event) - { - if (a_event && PlacementMode::IsActive()) - { - if (a_event->QUserEvent() == "XButton" - || a_event->QUserEvent() == "YButton" - || a_event->QUserEvent() == "LShoulder" - || a_event->QUserEvent() == "RShoulder" - || a_event->QUserEvent() == "LTrigger" - || a_event->QUserEvent() == "RTrigger" - || a_event->QUserEvent() == "Sprint" - || a_event->QUserEvent() == "Jump") - { - return; - } - - if (a_event->QUserEvent() == "Cancel") - { - auto ButtonEvent = stl::unrestricted_cast(a_event); - ButtonEvent->strUserEvent = "CloseMenu"; - return _HandleEvent(a_this, ButtonEvent); - } - } - - _HandleEvent(a_this, a_event); - } - - inline static REL::Relocation _HandleEvent; - }; - - class hkUIQualifier - { - public: - static void Install() - { - static REL::Relocation target{ REL::ID(179412) }; - stl::asm_replace(target.address(), 0x1C7, reinterpret_cast(UIQualifier)); - } - - private: - static bool UIQualifier( - const RE::BSExtraData* a_extra) - { - if (!a_extra) - { - return false; - } - - switch (a_extra->type.get()) - { - // case RE::EXTRA_DATA_TYPE::kPersistentCell: - // case RE::EXTRA_DATA_TYPE::kKeywords: - // case RE::EXTRA_DATA_TYPE::kStartingPosition: - // case RE::EXTRA_DATA_TYPE::kReferenceHandle: - // case RE::EXTRA_DATA_TYPE::kOwnership: - // case RE::EXTRA_DATA_TYPE::kGlobal: - // case RE::EXTRA_DATA_TYPE::kRank: - case RE::EXTRA_DATA_TYPE::kHealth: - // case RE::EXTRA_DATA_TYPE::kTimeLeft: - case RE::EXTRA_DATA_TYPE::kCharge: - // case RE::EXTRA_DATA_TYPE::kLevelItem: - // case RE::EXTRA_DATA_TYPE::kScale: - case RE::EXTRA_DATA_TYPE::kObjectInstance: - case RE::EXTRA_DATA_TYPE::kCannotWear: - case RE::EXTRA_DATA_TYPE::kPoison: - case RE::EXTRA_DATA_TYPE::kBoundArmor: - case RE::EXTRA_DATA_TYPE::kStartingWorldOrCell: - // case RE::EXTRA_DATA_TYPE::kFavorite: - // case RE::EXTRA_DATA_TYPE::kAliasInstanceArray: - // case RE::EXTRA_DATA_TYPE::kPromotedRef: - // case RE::EXTRA_DATA_TYPE::kOutfitItem: - // case RE::EXTRA_DATA_TYPE::kFromAlias: - // case RE::EXTRA_DATA_TYPE::kShouldWear: - case RE::EXTRA_DATA_TYPE::kTextDisplayData: - case RE::EXTRA_DATA_TYPE::kEnchantment: - // case RE::EXTRA_DATA_TYPE::kUniqueID: - // case RE::EXTRA_DATA_TYPE::kFlags: - case RE::EXTRA_DATA_TYPE::kInstanceData: - case RE::EXTRA_DATA_TYPE::kModRank: - return true; - - default: - return false; - } - } - }; - }; - - [[nodiscard]] static PlacementMode* GetSingleton() - { - static PlacementMode singleton; - return std::addressof(singleton); - } - - static void ApplyPerk() - { - auto PAPerk = Forms::PAFramePerk_DO->GetForm(); - if (!PAPerk) - { - return; - } - - if (auto Player = RE::TESForm::GetFormByID(0x00000007)->As()) - { - Player->AddPerk(PAPerk, 1); - } - } - - static bool CreateToken(RE::TESObjectREFR* a_refr) - { - if (a_refr && a_refr->HasKeyword(RE::TESForm::GetFormByID(0x0003430B))) - { - auto token = Forms::PAFrameToken_DO->GetForm(); - if (!token) - { - return false; - } - - auto name = GetOverrideName(a_refr); - auto extra = RE::BSTSmartPointer(new RE::ExtraDataList()); - extra->SetOverrideName(name.c_str()); - extra->SetStartingWorldOrCell(a_refr); - - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - if (a_refr->GetHandle() == PlayerCharacter->lastUsedPowerArmor) - { - PlayerCharacter->RemoveLastUsedPowerArmor(); - } - - if (auto DPM = RE::BGSDynamicPersistenceManager::GetSingleton()) - { - DPM->PromoteReference(a_refr, PlayerCharacter); - } - - RemoveScript(a_refr); - - const RE::PlayerCharacter::ScopedInventoryChangeMessageContext cmctx{ true, true }; - PlayerCharacter->AddObjectToContainer( - token, - extra, - 1, - nullptr, - RE::ITEM_REMOVE_REASON::kNone); - PlayerCharacter->PlayPickUpSound(token, true, false); - - a_refr->Disable(); - return true; - } - - return false; - } - - static bool HandleToken(RE::TESObjectREFR* a_refr) - { - if (RE::PowerArmor::PlayerInPowerArmor()) - { - const RE::PlayerCharacter::ScopedInventoryChangeMessageContext cmctx{ true, true }; - RE::PlayerCharacter::GetSingleton()->PickUpObject(a_refr, 1, false); - RE::SendHUDMessage::ShowHUDMessage( - sPADisallowed->GetString().data(), - nullptr, - true, - true); - return false; - } - - auto token = Forms::PAFrameToken_DO->GetForm(); - if (!token) - { - return false; - } - - if (a_refr - && a_refr->data.objectReference - && a_refr->data.objectReference == token) - { - if (!a_refr->extraList) - { - return false; - } - - if (auto extra = a_refr->extraList->GetByType()) - { - if (auto p_refr = extra->startingWorldOrCell->As()) - { - SetTokenReference(a_refr); - SetFrameReference(p_refr); - a_refr->Disable(); - - Start(); - return true; - } - } - } - - return false; - } - - static bool AttachScript(RE::TESObjectREFR* a_refr) - { - if (auto GameVM = RE::GameVM::GetSingleton()) - { - if (auto IVirtualMachine = GameVM->GetVM()) - { - auto Handle = IVirtualMachine->GetObjectHandlePolicy() - .GetHandleForObject( - a_refr->formType.underlying(), - a_refr); - - RE::BSTSmartPointer Object; - if (IVirtualMachine->CreateObject("BakaPowerArmorStoragePlacedScript"sv, Object)) - { - IVirtualMachine->GetObjectBindPolicy() - .BindObject(Object, Handle); - IVirtualMachine->DispatchMethodCall( - Handle, - "BakaPowerArmorStoragePlacedScript"sv, - "DoRegister"sv, - nullptr); - - return true; - } - } - } - - return false; - } - - static bool RemoveScript(RE::TESObjectREFR* a_refr) - { - if (auto GameVM = RE::GameVM::GetSingleton()) - { - if (auto IVirtualMachine = GameVM->GetVM()) - { - auto Handle = IVirtualMachine->GetObjectHandlePolicy() - .GetHandleForObject( - a_refr->formType.underlying(), - a_refr); - - RE::BSTSmartPointer Object; - if (IVirtualMachine->FindBoundObject( - Handle, - "BakaPowerArmorStoragePlacedScript", - true, - Object, - true)) - { - IVirtualMachine->GetObjectBindPolicy() - .UnbindObject(Object); - - return true; - } - } - } - - return false; - } - - virtual RE::BSEventNotifyControl ProcessEvent(const RE::MenuOpenCloseEvent& a_event, RE::BSTEventSource* a_source) override - { - if (a_event.menuName == RE::WorkshopMenu::MENU_NAME) - { - if (a_event.opening) - { - if (auto menu = RE::UI::GetSingleton()->GetMenu()) - { - menu->workshopMenuBase->itemName->SetMember("visible", false); - menu->workshopMenuBase->selectionBracket->SetMember("visible", false); - menu->workshopMenuBase->itemCounts->SetMember("visible", false); - menu->workshopMenuBase->newRecipeIcon->SetMember("visible", false); - menu->workshopMenuBase->rowBrackets->SetMember("visible", false); - menu->workshopMenuBase->displayPath->SetMember("visible", false); - menu->workshopMenuBase->descriptionBase->SetMember("visible", false); - menu->workshopMenuBase->iconBackground->SetMember("visible", false); - - menu->workshopMenuBase->Invoke("HideRequirements"); - menu->workshopMenuBase->Invoke("HideIconCard"); - menu->workshopMenuBase->Invoke("HidePerkPanels"); - - (*RE::Workshop::CurrentRow)++; - - menu->CheckAndSetItemForPlacement(); - menu->UpdateButtonText(); - - if (auto& handle = *RE::Workshop::PlacementItem) - { - if (m_frameRefr && m_frameRefr->inventoryList) - { - handle.get()->CreateInventoryList(nullptr); - - const RE::BSAutoReadLock lockR{ m_frameRefr->inventoryList->rwLock }; - const RE::BSAutoWriteLock lockW{ handle.get()->inventoryList->rwLock }; - for (auto& iter : m_frameRefr->inventoryList->data) - { - handle.get()->inventoryList->data.emplace_back(iter); - } - } - } - } - } - else - { - a_source->UnregisterSink(this); - } - } - - return RE::BSEventNotifyControl::kContinue; - } - - virtual RE::BSEventNotifyControl ProcessEvent(const RE::Workshop::ItemPlacedEvent& a_event, RE::BSTEventSource*) override - { - if (a_event.workshop == m_workshop.get()) - { - if (a_event.placedItem) - { - m_frameRefr->SetLocationOnReference(a_event.placedItem->data.location); - m_frameRefr->SetAngleOnReference(a_event.placedItem->data.angle); - - if (auto frame3D = m_frameRefr->Get3D()) - { - m_frameRefr->Update3DPosition(true); - if (frame3D->flags.flags & 0x4000) - { - if (auto node = frame3D->IsFadeNode()) - { - node->previousMaxA = 1.0f; - node->currentDecalFade = 1.0f; - node->currentFade = 1.0f; - node->flags.flags |= 0x2000000000; - } - } - } - - if (m_frameRefr->parentCell != a_event.placedItem->parentCell) - { - m_frameRefr->MoveRefToNewSpace( - a_event.placedItem->parentCell, - a_event.placedItem->parentCell->worldSpace); - m_frameRefr->AddChange(0x400); - } - - a_event.placedItem->SetDelete(true); - a_event.placedItem->SetWantsDelete(true); - a_event.placedItem->Disable(); - - m_frameRefr->formFlags |= 0x8000000; - m_frameRefr->Enable(false); - - if (auto DPM = RE::BGSDynamicPersistenceManager::GetSingleton()) - { - DPM->DemoteReference( - m_frameRefr.get(), - RE::PlayerCharacter::GetSingleton()); - } - - if (MCM::Settings::General::bAutoAutoReturn) - { - AttachScript(m_frameRefr.get()); - } - } - - m_tokenRefr->SetDelete(true); - m_tokenRefr->SetWantsDelete(true); - m_tokenRefr->Disable(); - - RE::Workshop::RequestExitWorkshop(false); - } - - return RE::BSEventNotifyControl::kContinue; - } - - virtual RE::BSEventNotifyControl ProcessEvent(const RE::Workshop::WorkshopModeEvent& a_event, RE::BSTEventSource*) override - { - if (a_event.start) - { - RE::UI::GetSingleton()->RegisterSink(this); - RE::Workshop::RegisterForItemPlaced(this); - } - else - { - RE::Workshop::UnregisterForItemPlaced(this); - RE::Workshop::UnregisterForWorkshopModeEvent(this); - - m_workshop.get()->SetDelete(true); - m_workshop.get()->SetWantsDelete(true); - m_workshop.get()->Disable(); - m_workshop.reset(); - - if (m_tokenRefr && !m_tokenRefr->GetDelete()) - { - const RE::PlayerCharacter::ScopedInventoryChangeMessageContext cmctx{ true, true }; - RE::PlayerCharacter::GetSingleton()->AddObjectToContainer( - m_tokenRefr->data.objectReference, - m_tokenRefr->extraList, - 1, - nullptr, - RE::ITEM_REMOVE_REASON::kNone); - - m_tokenRefr->SetDelete(true); - m_tokenRefr->SetWantsDelete(true); - m_tokenRefr->Disable(); - } - - m_tokenRefr.reset(); - m_frameRefr.reset(); - m_isActive = false; - } - - return RE::BSEventNotifyControl::kContinue; - } - - private: - inline static REL::Relocation*> sPADisallowed{ REL::ID(1053596) }; - - protected: - [[nodiscard]] static RE::ObjectRefHandle CreateWorkbench(RE::TESBoundObject* a_workbench) - { - if (auto PlayerCharacter = RE::PlayerCharacter::GetSingleton()) - { - auto data = RE::NEW_REFR_DATA(); - data.location = PlayerCharacter->data.location; - data.direction = PlayerCharacter->data.angle; - data.interior = PlayerCharacter->parentCell; - data.world = data.interior ? data.interior->worldSpace : nullptr; - data.object = a_workbench; - - if (auto TESDataHandler = RE::TESDataHandler::GetSingleton()) - { - return TESDataHandler->CreateReferenceAtLocation(data); - } - } - - return RE::ObjectRefHandle(); - } - - static void Start() - { - if (auto workbench = Forms::PAFrameWorkshop_DO->GetForm()) - { - auto singleton = GetSingleton(); - if (singleton->m_workshop = CreateWorkbench(workbench)) - { - if (auto UI = RE::UI::GetSingleton()) - { - if (UI->GetMenuOpen()) - { - RE::UIMessageQueue::GetSingleton()->AddMessage( - "PipboyMenu"sv, - RE::UI_MESSAGE_TYPE::kHide); - } - - if (UI->GetMenuOpen()) - { - RE::UIMessageQueue::GetSingleton()->AddMessage( - "ContainerMenu"sv, - RE::UI_MESSAGE_TYPE::kHide); - } - } - - singleton->m_isActive = true; - RE::Workshop::RegisterForWorkshopModeEvent(singleton); - RE::Workshop::StartWorkshop(singleton->m_workshop.get().get()); - } - } - } - - static void SetFrameReference(RE::TESObjectREFR* a_refr) - { - GetSingleton()->m_frameRefr.reset(a_refr); - } - - static void SetTokenReference(RE::TESObjectREFR* a_refr) - { - GetSingleton()->m_tokenRefr.reset(a_refr); - } - - static bool IsActive() - { - return GetSingleton()->m_isActive; - } - - static RE::BSFixedString GetOverrideName(RE::TESObjectREFR* a_refr) - { - std::string name; - std::int32_t count{ 0 }, health{ 0 }; - if (a_refr->inventoryList) - { - const RE::BSAutoReadLock lock{ a_refr->inventoryList->rwLock }; - for (auto& iter : a_refr->inventoryList->data) - { - if (!iter.object) - { - continue; - } - - switch (iter.object->formType.get()) - { - case RE::ENUM_FORM_ID::kARMO: - count++; - if (iter.object) - { - if (auto armo = iter.object->As()) - { - if (armo->attachParents.HasKeyword(Forms::ap_PowerArmor_BodyMod)) - { - name = Forms::PANameScheme::Get(armo); - } - } - } - break; - - case RE::ENUM_FORM_ID::kAMMO: - if (iter.stackData && iter.stackData->extra) - { - if (auto ExtraHealth = iter.stackData->extra->GetByType()) - { - health = static_cast(ExtraHealth->health * 100.0f); - } - else - { - health = 100; - } - } - break; - - default: - break; - } - } - } - - std::stringstream stream; - stream << MCM::Settings::Formatting::sPAChassis.data(); - - if (!name.empty()) - { - stream << " ["sv - << name - << "]"sv; - } - - if (count > 0) - { - stream << " ["sv - << count - << "pc]"sv; - } - +#pragma once + +#include "MCM/MCM.h" + +namespace Workshop +{ + class PlacementMode : + public RE::BSTEventSink, + public RE::BSTEventSink, + public RE::BSTEventSink + { + public: + class Hooks + { + public: + static void Install() + { + // Disable selection in UI + hkCanNavigate<119865, 0x37A>::Install(); + hkCanNavigate<119865, 0x3EA>::Install(); + hkCanNavigate<119865, 0x4C5>::Install(); + hkCanNavigate<119865, 0x768>::Install(); + hkCanNavigate<985073, 0x0C>::Install(); + hkCanNavigate<1130413, 0x0C>::Install(); + + // Prevent Workshops marked as deleted as being valid + hkIsReferenceWithinBuildableArea<2562, 0x156>::Install(); + hkIsReferenceWithinBuildableArea<978467, 0x07E>::Install(); + hkIsReferenceWithinBuildableArea<90862, 0x039>::Install(); + hkIsReferenceWithinBuildableArea<939377, 0x090>::Install(); + hkIsReferenceWithinBuildableArea<552874, 0x024>::Install(); + hkIsReferenceWithinBuildableArea<525394, 0x017>::Install(); + hkIsReferenceWithinBuildableArea<311311, 0x0A6>::Install(); + hkIsReferenceWithinBuildableArea<566990, 0x05C>::Install(); + hkIsReferenceWithinBuildableArea<286947, 0x047>::Install(); + hkIsReferenceWithinBuildableArea<1371490, 0x091>::Install(); + hkIsReferenceWithinBuildableArea<931840, 0x165>::Install(); + hkIsReferenceWithinBuildableArea<1515428, 0x036>::Install(); + + // Disable Workshop Startup/End sounds + hkPlayMenuSound<598489, 0x1195>::Install(); + hkPlayMenuSound<98443, 0x01B5>::Install(); + + // Prevent tagging for search in FreeBuild mode + hkShouldShowTagForSearch<119865, 0xEBB>::Install(); + hkShouldShowTagForSearch<1089189, 0x574>::Install(); + + // Prevent a stupid textbox from showing up for a split second after placing an item + hkUpdateRequirements<598489, 0x1144>::Install(); + hkUpdateRequirements<1280212, 0x1F0>::Install(); + hkUpdateRequirements<119865, 0x05C5>::Install(); + hkUpdateRequirements<119865, 0x0868>::Install(); + hkUpdateRequirements<119865, 0x0A16>::Install(); + hkUpdateRequirements<119865, 0x0A42>::Install(); + hkUpdateRequirements<119865, 0x0A65>::Install(); + hkUpdateRequirements<119865, 0x0A88>::Install(); + hkUpdateRequirements<119865, 0x0C64>::Install(); + hkUpdateRequirements<119865, 0x0CD4>::Install(); + hkUpdateRequirements<119865, 0x0F53>::Install(); + hkUpdateRequirements<119865, 0x10FD>::Install(); + hkUpdateRequirements<119865, 0x1181>::Install(); + hkUpdateRequirements<119865, 0x13F6>::Install(); + hkUpdateRequirements<119865, 0x1764>::Install(); + + // Prevent stored frames from stacking + hkCompareImpl::Install(); + + // Redirect Cancel input, block other buttons + hkHandleEvent::Install(); + + // Enable ExtraStartingWorldOrCell as a stacking condition + hkUIQualifier::Install(); + } + + private: + template + class hkCanNavigate + { + public: + static void Install() + { + static REL::Relocation target{ REL::ID(ID), OFF }; + auto& trampoline = F4SE::GetTrampoline(); + _CanNavigate = trampoline.write_call<5>(target.address(), CanNavigate); + } + + private: + static bool CanNavigate() + { + return PlacementMode::IsActive() ? false : _CanNavigate(); + } + + inline static REL::Relocation _CanNavigate; + }; + + template + class hkIsReferenceWithinBuildableArea + { + public: + static void Install() + { + static REL::Relocation target{ REL::ID(ID), OFF }; + auto& trampoline = F4SE::GetTrampoline(); + _IsReferenceWithinBuildableArea = trampoline.write_call<5>(target.address(), IsReferenceWithinBuildableArea); + } + + private: + static bool IsReferenceWithinBuildableArea( + const RE::TESObjectREFR& a_workshop, + const RE::TESObjectREFR& a_refr) + { + if (PlacementMode::IsActive()) + { + return true; + } + + if (a_workshop.formFlags & 0x20) + { + return false; + } + + return _IsReferenceWithinBuildableArea(a_workshop, a_refr); + } + + inline static REL::Relocation _IsReferenceWithinBuildableArea; + }; + + template + class hkPlayMenuSound + { + public: + static void Install() + { + static REL::Relocation target{ REL::ID(ID), OFF }; + auto& trampoline = F4SE::GetTrampoline(); + _PlayMenuSound = trampoline.write_call<5>(target.address(), PlayMenuSound); + } + + private: + static bool PlayMenuSound( + const char* a_soundName) + { + if (PlacementMode::IsActive()) + { + PlacementMode::GetSingleton()->m_hasSound = true; + return _PlayMenuSound(nullptr); + } + + if (PlacementMode::GetSingleton()->m_hasSound) + { + if (_stricmp(a_soundName, "UIWorkshopModeExit") == 0) + { + PlacementMode::GetSingleton()->m_hasSound = false; + return _PlayMenuSound(nullptr); + } + } + + return _PlayMenuSound(a_soundName); + } + + inline static REL::Relocation _PlayMenuSound; + }; + + template + class hkShouldShowTagForSearch + { + public: + static void Install() + { + static REL::Relocation target{ REL::ID(ID), OFF }; + auto& trampoline = F4SE::GetTrampoline(); + _ShouldShowTagForSearch = trampoline.write_call<5>(target.address(), ShouldShowTagForSearch); + } + + private: + static std::uint64_t ShouldShowTagForSearch( + RE::WorkshopMenu* a_this) + { + return PlacementMode::IsActive() ? 0 : _ShouldShowTagForSearch(a_this); + } + + inline static REL::Relocation _ShouldShowTagForSearch; + }; + + template + class hkUpdateRequirements + { + public: + static void Install() + { + static REL::Relocation target{ REL::ID(ID), OFF }; + auto& trampoline = F4SE::GetTrampoline(); + _UpdateRequirements = trampoline.write_call<5>(target.address(), UpdateRequirements); + } + + private: + static void UpdateRequirements( + RE::WorkshopMenu* a_this, + bool a_stringingWire) + { + if (PlacementMode::IsActive()) + { + return; + } + + _UpdateRequirements(a_this, a_stringingWire); + } + + inline static REL::Relocation _UpdateRequirements; + }; + + class hkCompareImpl + { + public: + static void Install() + { + static REL::Relocation target{ RE::ExtraStartingWorldOrCell::VTABLE[0] }; + target.write_vfunc(0x01, CompareImpl); + } + + private: + static bool CompareImpl( + RE::ExtraStartingWorldOrCell* a_this, + const RE::ExtraStartingWorldOrCell& a_compare) + { + if (!a_this) + { + return true; + } + + if (a_this->type != RE::ExtraStartingWorldOrCell::TYPE || a_compare.type != RE::ExtraStartingWorldOrCell::TYPE) + { + return true; + } + + if (a_this->startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kCELL || a_this->startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kWRLD || a_compare.startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kCELL || a_compare.startingWorldOrCell->GetFormType() == RE::ENUM_FORM_ID::kWRLD) + { + return false; + } + + return a_this->startingWorldOrCell != a_compare.startingWorldOrCell; + } + }; + + class hkHandleEvent + { + public: + static void Install() + { + static REL::Relocation target{ RE::WorkshopMenu::VTABLE[1] }; + _HandleEvent = target.write_vfunc(0x08, HandleEvent); + } + + private: + static void HandleEvent( + RE::BSInputEventUser* a_this, + const RE::ButtonEvent* a_event) + { + if (a_event && PlacementMode::IsActive()) + { + if (a_event->QUserEvent() == "XButton" || a_event->QUserEvent() == "YButton" || a_event->QUserEvent() == "LShoulder" || a_event->QUserEvent() == "RShoulder" || a_event->QUserEvent() == "LTrigger" || a_event->QUserEvent() == "RTrigger" || a_event->QUserEvent() == "Sprint" || a_event->QUserEvent() == "Jump") + { + return; + } + + if (a_event->QUserEvent() == "Cancel") + { + auto ButtonEvent = stl::unrestricted_cast(a_event); + ButtonEvent->strUserEvent = "CloseMenu"; + return _HandleEvent(a_this, ButtonEvent); + } + } + + _HandleEvent(a_this, a_event); + } + + inline static REL::Relocation _HandleEvent; + }; + + class hkUIQualifier + { + public: + static void Install() + { + static REL::Relocation target{ REL::ID(179412) }; + stl::asm_replace(target.address(), 0x1C7, reinterpret_cast(UIQualifier)); + } + + private: + static bool UIQualifier( + const RE::BSExtraData* a_extra) + { + if (!a_extra) + { + return false; + } + + switch (a_extra->type.get()) + { + // case RE::EXTRA_DATA_TYPE::kPersistentCell: + // case RE::EXTRA_DATA_TYPE::kKeywords: + // case RE::EXTRA_DATA_TYPE::kStartingPosition: + // case RE::EXTRA_DATA_TYPE::kReferenceHandle: + // case RE::EXTRA_DATA_TYPE::kOwnership: + // case RE::EXTRA_DATA_TYPE::kGlobal: + // case RE::EXTRA_DATA_TYPE::kRank: + case RE::EXTRA_DATA_TYPE::kHealth: + // case RE::EXTRA_DATA_TYPE::kTimeLeft: + case RE::EXTRA_DATA_TYPE::kCharge: + // case RE::EXTRA_DATA_TYPE::kLevelItem: + // case RE::EXTRA_DATA_TYPE::kScale: + case RE::EXTRA_DATA_TYPE::kObjectInstance: + case RE::EXTRA_DATA_TYPE::kCannotWear: + case RE::EXTRA_DATA_TYPE::kPoison: + case RE::EXTRA_DATA_TYPE::kBoundArmor: + case RE::EXTRA_DATA_TYPE::kStartingWorldOrCell: + // case RE::EXTRA_DATA_TYPE::kFavorite: + // case RE::EXTRA_DATA_TYPE::kAliasInstanceArray: + // case RE::EXTRA_DATA_TYPE::kPromotedRef: + // case RE::EXTRA_DATA_TYPE::kOutfitItem: + // case RE::EXTRA_DATA_TYPE::kFromAlias: + // case RE::EXTRA_DATA_TYPE::kShouldWear: + case RE::EXTRA_DATA_TYPE::kTextDisplayData: + case RE::EXTRA_DATA_TYPE::kEnchantment: + // case RE::EXTRA_DATA_TYPE::kUniqueID: + // case RE::EXTRA_DATA_TYPE::kFlags: + case RE::EXTRA_DATA_TYPE::kInstanceData: + case RE::EXTRA_DATA_TYPE::kModRank: + return true; + + default: + return false; + } + } + }; + }; + + [[nodiscard]] static PlacementMode* GetSingleton() + { + static PlacementMode singleton; + return std::addressof(singleton); + } + + static void ApplyPerk() + { + auto PAPerk = Forms::PAFramePerk_DO->GetForm(); + if (!PAPerk) + { + return; + } + + if (auto Player = RE::TESForm::GetFormByID(0x00000007)->As()) + { + Player->AddPerk(PAPerk, 1); + } + } + + static bool CreateToken(RE::TESObjectREFR* a_refr) + { + if (a_refr && a_refr->HasKeyword(RE::TESForm::GetFormByID(0x0003430B))) + { + auto token = Forms::PAFrameToken_DO->GetForm(); + if (!token) + { + return false; + } + + auto name = GetOverrideName(a_refr); + auto extra = RE::BSTSmartPointer(new RE::ExtraDataList()); + extra->SetOverrideName(name.c_str()); + extra->SetStartingWorldOrCell(a_refr); + + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + if (a_refr->GetHandle() == PlayerCharacter->lastUsedPowerArmor) + { + PlayerCharacter->RemoveLastUsedPowerArmor(); + } + + if (auto DPM = RE::BGSDynamicPersistenceManager::GetSingleton()) + { + DPM->PromoteReference(a_refr, PlayerCharacter); + } + + RemoveScript(a_refr); + + const RE::PlayerCharacter::ScopedInventoryChangeMessageContext cmctx{ true, true }; + PlayerCharacter->AddObjectToContainer( + token, + extra, + 1, + nullptr, + RE::ITEM_REMOVE_REASON::kNone); + PlayerCharacter->PlayPickUpSound(token, true, false); + + a_refr->Disable(); + return true; + } + + return false; + } + + static bool HandleToken(RE::TESObjectREFR* a_refr) + { + if (RE::PowerArmor::PlayerInPowerArmor()) + { + const RE::PlayerCharacter::ScopedInventoryChangeMessageContext cmctx{ true, true }; + RE::PlayerCharacter::GetSingleton()->PickUpObject(a_refr, 1, false); + RE::SendHUDMessage::ShowHUDMessage( + sPADisallowed->GetString().data(), + nullptr, + true, + true); + return false; + } + + auto token = Forms::PAFrameToken_DO->GetForm(); + if (!token) + { + return false; + } + + if (a_refr && a_refr->data.objectReference && a_refr->data.objectReference == token) + { + if (!a_refr->extraList) + { + return false; + } + + if (auto extra = a_refr->extraList->GetByType()) + { + if (auto p_refr = extra->startingWorldOrCell->As()) + { + SetTokenReference(a_refr); + SetFrameReference(p_refr); + a_refr->Disable(); + + Start(); + return true; + } + } + } + + return false; + } + + static bool AttachScript(RE::TESObjectREFR* a_refr) + { + if (auto GameVM = RE::GameVM::GetSingleton()) + { + if (auto IVirtualMachine = GameVM->GetVM()) + { + auto Handle = IVirtualMachine->GetObjectHandlePolicy() + .GetHandleForObject( + a_refr->formType.underlying(), + a_refr); + + RE::BSTSmartPointer Object; + if (IVirtualMachine->CreateObject("BakaPowerArmorStoragePlacedScript"sv, Object)) + { + IVirtualMachine->GetObjectBindPolicy() + .BindObject(Object, Handle); + IVirtualMachine->DispatchMethodCall( + Handle, + "BakaPowerArmorStoragePlacedScript"sv, + "DoRegister"sv, + nullptr); + + return true; + } + } + } + + return false; + } + + static bool RemoveScript(RE::TESObjectREFR* a_refr) + { + if (auto GameVM = RE::GameVM::GetSingleton()) + { + if (auto IVirtualMachine = GameVM->GetVM()) + { + auto Handle = IVirtualMachine->GetObjectHandlePolicy() + .GetHandleForObject( + a_refr->formType.underlying(), + a_refr); + + RE::BSTSmartPointer Object; + if (IVirtualMachine->FindBoundObject( + Handle, + "BakaPowerArmorStoragePlacedScript", + true, + Object, + true)) + { + IVirtualMachine->GetObjectBindPolicy() + .UnbindObject(Object); + + return true; + } + } + } + + return false; + } + + virtual RE::BSEventNotifyControl ProcessEvent(const RE::MenuOpenCloseEvent& a_event, RE::BSTEventSource* a_source) override + { + if (a_event.menuName == RE::WorkshopMenu::MENU_NAME) + { + if (a_event.opening) + { + if (auto menu = RE::UI::GetSingleton()->GetMenu()) + { + menu->workshopMenuBase->itemName->SetMember("visible", false); + menu->workshopMenuBase->selectionBracket->SetMember("visible", false); + menu->workshopMenuBase->itemCounts->SetMember("visible", false); + menu->workshopMenuBase->newRecipeIcon->SetMember("visible", false); + menu->workshopMenuBase->rowBrackets->SetMember("visible", false); + menu->workshopMenuBase->displayPath->SetMember("visible", false); + menu->workshopMenuBase->descriptionBase->SetMember("visible", false); + menu->workshopMenuBase->iconBackground->SetMember("visible", false); + + menu->workshopMenuBase->Invoke("HideRequirements"); + menu->workshopMenuBase->Invoke("HideIconCard"); + menu->workshopMenuBase->Invoke("HidePerkPanels"); + + (*RE::Workshop::CurrentRow)++; + + menu->CheckAndSetItemForPlacement(); + menu->UpdateButtonText(); + + if (auto& handle = *RE::Workshop::PlacementItem) + { + if (m_frameRefr && m_frameRefr->inventoryList) + { + handle.get()->CreateInventoryList(nullptr); + + const RE::BSAutoReadLock lockR{ m_frameRefr->inventoryList->rwLock }; + const RE::BSAutoWriteLock lockW{ handle.get()->inventoryList->rwLock }; + for (auto& iter : m_frameRefr->inventoryList->data) + { + handle.get()->inventoryList->data.emplace_back(iter); + } + } + } + } + } + else + { + a_source->UnregisterSink(this); + } + } + + return RE::BSEventNotifyControl::kContinue; + } + + virtual RE::BSEventNotifyControl ProcessEvent(const RE::Workshop::ItemPlacedEvent& a_event, RE::BSTEventSource*) override + { + if (a_event.workshop == m_workshop.get()) + { + if (a_event.placedItem) + { + m_frameRefr->SetLocationOnReference(a_event.placedItem->data.location); + m_frameRefr->SetAngleOnReference(a_event.placedItem->data.angle); + + if (auto frame3D = m_frameRefr->Get3D()) + { + m_frameRefr->Update3DPosition(true); + if (frame3D->flags.flags & 0x4000) + { + if (auto node = frame3D->IsFadeNode()) + { + node->previousMaxA = 1.0f; + node->currentDecalFade = 1.0f; + node->currentFade = 1.0f; + node->flags.flags |= 0x2000000000; + } + } + } + + if (m_frameRefr->parentCell != a_event.placedItem->parentCell) + { + m_frameRefr->MoveRefToNewSpace( + a_event.placedItem->parentCell, + a_event.placedItem->parentCell->worldSpace); + m_frameRefr->AddChange(0x400); + } + + a_event.placedItem->SetDelete(true); + a_event.placedItem->SetWantsDelete(true); + a_event.placedItem->Disable(); + + m_frameRefr->formFlags |= 0x8000000; + m_frameRefr->Enable(false); + + if (auto DPM = RE::BGSDynamicPersistenceManager::GetSingleton()) + { + DPM->DemoteReference( + m_frameRefr.get(), + RE::PlayerCharacter::GetSingleton()); + } + + if (MCM::Settings::General::bAutoAutoReturn) + { + AttachScript(m_frameRefr.get()); + } + } + + m_tokenRefr->SetDelete(true); + m_tokenRefr->SetWantsDelete(true); + m_tokenRefr->Disable(); + + RE::Workshop::RequestExitWorkshop(false); + } + + return RE::BSEventNotifyControl::kContinue; + } + + virtual RE::BSEventNotifyControl ProcessEvent(const RE::Workshop::WorkshopModeEvent& a_event, RE::BSTEventSource*) override + { + if (a_event.start) + { + RE::UI::GetSingleton()->RegisterSink(this); + RE::Workshop::RegisterForItemPlaced(this); + } + else + { + RE::Workshop::UnregisterForItemPlaced(this); + RE::Workshop::UnregisterForWorkshopModeEvent(this); + + m_workshop.get()->SetDelete(true); + m_workshop.get()->SetWantsDelete(true); + m_workshop.get()->Disable(); + m_workshop.reset(); + + if (m_tokenRefr && !m_tokenRefr->GetDelete()) + { + const RE::PlayerCharacter::ScopedInventoryChangeMessageContext cmctx{ true, true }; + RE::PlayerCharacter::GetSingleton()->AddObjectToContainer( + m_tokenRefr->data.objectReference, + m_tokenRefr->extraList, + 1, + nullptr, + RE::ITEM_REMOVE_REASON::kNone); + + m_tokenRefr->SetDelete(true); + m_tokenRefr->SetWantsDelete(true); + m_tokenRefr->Disable(); + } + + m_tokenRefr.reset(); + m_frameRefr.reset(); + m_isActive = false; + } + + return RE::BSEventNotifyControl::kContinue; + } + + private: + inline static REL::Relocation*> sPADisallowed{ REL::ID(1053596) }; + + protected: + [[nodiscard]] static RE::ObjectRefHandle CreateWorkbench(RE::TESBoundObject* a_workbench) + { + if (auto PlayerCharacter = RE::PlayerCharacter::GetSingleton()) + { + auto data = RE::NEW_REFR_DATA(); + data.location = PlayerCharacter->data.location; + data.direction = PlayerCharacter->data.angle; + data.interior = PlayerCharacter->parentCell; + data.world = data.interior ? data.interior->worldSpace : nullptr; + data.object = a_workbench; + + if (auto TESDataHandler = RE::TESDataHandler::GetSingleton()) + { + return TESDataHandler->CreateReferenceAtLocation(data); + } + } + + return RE::ObjectRefHandle(); + } + + static void Start() + { + if (auto workbench = Forms::PAFrameWorkshop_DO->GetForm()) + { + auto singleton = GetSingleton(); + if (singleton->m_workshop = CreateWorkbench(workbench)) + { + if (auto UI = RE::UI::GetSingleton()) + { + if (UI->GetMenuOpen()) + { + RE::UIMessageQueue::GetSingleton()->AddMessage( + "PipboyMenu"sv, + RE::UI_MESSAGE_TYPE::kHide); + } + + if (UI->GetMenuOpen()) + { + RE::UIMessageQueue::GetSingleton()->AddMessage( + "ContainerMenu"sv, + RE::UI_MESSAGE_TYPE::kHide); + } + } + + singleton->m_isActive = true; + RE::Workshop::RegisterForWorkshopModeEvent(singleton); + RE::Workshop::StartWorkshop(singleton->m_workshop.get().get()); + } + } + } + + static void SetFrameReference(RE::TESObjectREFR* a_refr) + { + GetSingleton()->m_frameRefr.reset(a_refr); + } + + static void SetTokenReference(RE::TESObjectREFR* a_refr) + { + GetSingleton()->m_tokenRefr.reset(a_refr); + } + + static bool IsActive() + { + return GetSingleton()->m_isActive; + } + + static RE::BSFixedString GetOverrideName(RE::TESObjectREFR* a_refr) + { + std::string name; + std::int32_t count{ 0 }, health{ 0 }; + if (a_refr->inventoryList) + { + const RE::BSAutoReadLock lock{ a_refr->inventoryList->rwLock }; + for (auto& iter : a_refr->inventoryList->data) + { + if (!iter.object) + { + continue; + } + + switch (iter.object->formType.get()) + { + case RE::ENUM_FORM_ID::kARMO: + count++; + if (iter.object) + { + if (auto armo = iter.object->As()) + { + if (armo->attachParents.HasKeyword(Forms::ap_PowerArmor_BodyMod)) + { + name = Forms::PANameScheme::Get(armo); + } + } + } + break; + + case RE::ENUM_FORM_ID::kAMMO: + if (iter.stackData && iter.stackData->extra) + { + if (auto ExtraHealth = iter.stackData->extra->GetByType()) + { + health = static_cast(ExtraHealth->health * 100.0f); + } + else + { + health = 100; + } + } + break; + + default: + break; + } + } + } + + std::stringstream stream; + stream << MCM::Settings::Formatting::sPAChassis.data(); + + if (!name.empty()) + { + stream << " ["sv + << name + << "]"sv; + } + + if (count > 0) + { + stream << " ["sv + << count + << "pc]"sv; + } + /*if (health > 0) { stream << " ["sv << health << "%]"sv; - }*/ - - auto result = RE::BSFixedString{ stream.str() }; - return result; - } - - RE::ObjectRefHandle m_workshop; - RE::BSTSmartPointer m_frameRefr; - RE::BSTSmartPointer m_tokenRefr; - bool m_isActive{ false }; - bool m_hasSound{ false }; - }; -} + }*/ + + auto result = RE::BSFixedString{ stream.str() }; + return result; + } + + RE::ObjectRefHandle m_workshop; + RE::BSTSmartPointer m_frameRefr; + RE::BSTSmartPointer m_tokenRefr; + bool m_isActive{ false }; + bool m_hasSound{ false }; + }; +} diff --git a/Plugin/src/main.cpp b/Plugin/src/main.cpp index 01a6474..7379414 100644 --- a/Plugin/src/main.cpp +++ b/Plugin/src/main.cpp @@ -1,103 +1,103 @@ -#include "Forms/Forms.h" -#include "MCM/MCM.h" -#include "Scripts/Papyrus.h" -#include "Workshop/Workshop.h" - -namespace -{ - void InitializeLog() - { - auto path = logger::log_directory(); - if (!path) - { - stl::report_and_fail("Failed to find standard logging directory"sv); - } - - *path /= fmt::format(FMT_STRING("{:s}.log"sv), Version::PROJECT); - auto sink = std::make_shared(path->string(), true); - - auto log = std::make_shared("global log"s, std::move(sink)); - auto lvl = *Settings::General::EnableDebugLogging - ? spdlog::level::trace - : spdlog::level::info; - - log->set_level(lvl); - log->flush_on(lvl); - - spdlog::set_default_logger(std::move(log)); - spdlog::set_pattern("[%m/%d/%Y - %T] [%^%l%$] %v"s); - - logger::info(FMT_STRING("{:s} v{:s}"sv), Version::PROJECT, Version::NAME); - } - - void MessageHandler(F4SE::MessagingInterface::Message* a_msg) - { - if (!a_msg) - { - return; - } - - switch (a_msg->type) - { - case F4SE::MessagingInterface::kGameLoaded: - Workshop::PlacementMode::ApplyPerk(); - break; - case F4SE::MessagingInterface::kGameDataReady: - Forms::InstallDataReady(); - MCM::Settings::Update(); - break; - default: - break; - } - } -} - -extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Query(const F4SE::QueryInterface* a_F4SE, F4SE::PluginInfo* a_info) -{ - a_info->infoVersion = F4SE::PluginInfo::kVersion; - a_info->name = Version::PROJECT.data(); - a_info->version = Version::MAJOR; - - const auto rtv = a_F4SE->RuntimeVersion(); - if (rtv < F4SE::RUNTIME_LATEST) - { - stl::report_and_fail( - fmt::format( - FMT_STRING("{:s} does not support runtime v{:s}."sv), - Version::PROJECT, - rtv.string())); - } - - return true; -} - -extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Load(const F4SE::LoadInterface* a_F4SE) -{ - Settings::Load(); - InitializeLog(); - - logger::info(FMT_STRING("{:s} loaded."sv), Version::PROJECT); - logger::debug("Debug logging enabled."sv); - - F4SE::Init(a_F4SE); - F4SE::AllocTrampoline(1u << 10); - - const auto messaging = F4SE::GetMessagingInterface(); - if (!messaging || !messaging->RegisterListener(MessageHandler)) - { - logger::critical("Failed to register messaging handler, marking as incompatible."sv); - return false; - } - - const auto papyrus = F4SE::GetPapyrusInterface(); - if (!papyrus || !papyrus->Register(Papyrus::RegisterFunctions)) - { - logger::critical("Failed to register Papyrus functions, marking as incompatible."sv); - return false; - } - - Forms::Install(); - Workshop::PlacementMode::Hooks::Install(); - - return true; -} +#include "Forms/Forms.h" +#include "MCM/MCM.h" +#include "Scripts/Papyrus.h" +#include "Workshop/Workshop.h" + +namespace +{ + void InitializeLog() + { + auto path = logger::log_directory(); + if (!path) + { + stl::report_and_fail("Failed to find standard logging directory"sv); + } + + *path /= fmt::format(FMT_STRING("{:s}.log"sv), Version::PROJECT); + auto sink = std::make_shared(path->string(), true); + + auto log = std::make_shared("global log"s, std::move(sink)); + auto lvl = *Settings::General::EnableDebugLogging + ? spdlog::level::trace + : spdlog::level::info; + + log->set_level(lvl); + log->flush_on(lvl); + + spdlog::set_default_logger(std::move(log)); + spdlog::set_pattern("[%m/%d/%Y - %T] [%^%l%$] %v"s); + + logger::info(FMT_STRING("{:s} v{:s}"sv), Version::PROJECT, Version::NAME); + } + + void MessageHandler(F4SE::MessagingInterface::Message* a_msg) + { + if (!a_msg) + { + return; + } + + switch (a_msg->type) + { + case F4SE::MessagingInterface::kGameLoaded: + Workshop::PlacementMode::ApplyPerk(); + break; + case F4SE::MessagingInterface::kGameDataReady: + Forms::InstallDataReady(); + MCM::Settings::Update(); + break; + default: + break; + } + } +} + +extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Query(const F4SE::QueryInterface* a_F4SE, F4SE::PluginInfo* a_info) +{ + a_info->infoVersion = F4SE::PluginInfo::kVersion; + a_info->name = Version::PROJECT.data(); + a_info->version = Version::MAJOR; + + const auto rtv = a_F4SE->RuntimeVersion(); + if (rtv < F4SE::RUNTIME_LATEST) + { + stl::report_and_fail( + fmt::format( + FMT_STRING("{:s} does not support runtime v{:s}."sv), + Version::PROJECT, + rtv.string())); + } + + return true; +} + +extern "C" DLLEXPORT bool F4SEAPI F4SEPlugin_Load(const F4SE::LoadInterface* a_F4SE) +{ + Settings::Load(); + InitializeLog(); + + logger::info(FMT_STRING("{:s} loaded."sv), Version::PROJECT); + logger::debug("Debug logging enabled."sv); + + F4SE::Init(a_F4SE); + F4SE::AllocTrampoline(1u << 10); + + const auto messaging = F4SE::GetMessagingInterface(); + if (!messaging || !messaging->RegisterListener(MessageHandler)) + { + logger::critical("Failed to register messaging handler, marking as incompatible."sv); + return false; + } + + const auto papyrus = F4SE::GetPapyrusInterface(); + if (!papyrus || !papyrus->Register(Papyrus::RegisterFunctions)) + { + logger::critical("Failed to register Papyrus functions, marking as incompatible."sv); + return false; + } + + Forms::Install(); + Workshop::PlacementMode::Hooks::Install(); + + return true; +}