diff --git a/src/Forms/Forms.h b/src/Forms/Forms.h index ad27ce2..e3d45b5 100644 --- a/src/Forms/Forms.h +++ b/src/Forms/Forms.h @@ -1,33 +1,33 @@ -#pragma once - -namespace Forms -{ - static constexpr auto PLUGIN_NAME{ "BakaAutoLockpicking.esm"sv }; - - inline RE::BGSListForm* BakaAutoLock_Perks_Base{ nullptr }; - inline RE::BGSListForm* BakaAutoLock_Perks_Unbreakable{ nullptr }; - inline RE::BGSListForm* BakaAutoLock_Perks_WaxKey{ nullptr }; - inline RE::BGSListForm* BakaAutoLock_Items_Lockpick{ nullptr }; - inline RE::BGSListForm* BakaAutoHack_Perks_Base{ nullptr }; - inline RE::BGSListForm* BakaAutoHack_Perks_WaxKey{ nullptr }; - inline RE::ActorValueInfo* BakaAutoHack_Attempts{ nullptr }; - inline RE::ActorValueInfo* LGND_LockPickSweetSpot{ nullptr }; - - static void Register() - { - if (auto TESDataHandler = RE::TESDataHandler::GetSingleton()) - { - if (TESDataHandler->LookupLoadedLightModByName(PLUGIN_NAME)) - { - BakaAutoLock_Perks_Base = TESDataHandler->LookupForm(0x802, PLUGIN_NAME); - BakaAutoLock_Perks_Unbreakable = TESDataHandler->LookupForm(0x803, PLUGIN_NAME); - BakaAutoLock_Perks_WaxKey = TESDataHandler->LookupForm(0x804, PLUGIN_NAME); - BakaAutoLock_Items_Lockpick = TESDataHandler->LookupForm(0x805, PLUGIN_NAME); - BakaAutoHack_Perks_Base = TESDataHandler->LookupForm(0x806, PLUGIN_NAME); - BakaAutoHack_Perks_WaxKey = TESDataHandler->LookupForm(0x807, PLUGIN_NAME); - BakaAutoHack_Attempts = TESDataHandler->LookupForm(0x900, PLUGIN_NAME); - LGND_LockPickSweetSpot = TESDataHandler->LookupForm(0x1F3CAB, "Fallout4.esm"sv); - } - } - } -} +#pragma once + +namespace Forms +{ + static constexpr auto PLUGIN_NAME{ "BakaAutoLockpicking.esm"sv }; + + inline RE::BGSListForm* BakaAutoLock_Perks_Base{ nullptr }; + inline RE::BGSListForm* BakaAutoLock_Perks_Unbreakable{ nullptr }; + inline RE::BGSListForm* BakaAutoLock_Perks_WaxKey{ nullptr }; + inline RE::BGSListForm* BakaAutoLock_Items_Lockpick{ nullptr }; + inline RE::BGSListForm* BakaAutoHack_Perks_Base{ nullptr }; + inline RE::BGSListForm* BakaAutoHack_Perks_WaxKey{ nullptr }; + inline RE::ActorValueInfo* BakaAutoHack_Attempts{ nullptr }; + inline RE::ActorValueInfo* LGND_LockPickSweetSpot{ nullptr }; + + static void Register() + { + if (auto TESDataHandler = RE::TESDataHandler::GetSingleton()) + { + if (TESDataHandler->LookupLoadedLightModByName(PLUGIN_NAME)) + { + BakaAutoLock_Perks_Base = TESDataHandler->LookupForm(0x802, PLUGIN_NAME); + BakaAutoLock_Perks_Unbreakable = TESDataHandler->LookupForm(0x803, PLUGIN_NAME); + BakaAutoLock_Perks_WaxKey = TESDataHandler->LookupForm(0x804, PLUGIN_NAME); + BakaAutoLock_Items_Lockpick = TESDataHandler->LookupForm(0x805, PLUGIN_NAME); + BakaAutoHack_Perks_Base = TESDataHandler->LookupForm(0x806, PLUGIN_NAME); + BakaAutoHack_Perks_WaxKey = TESDataHandler->LookupForm(0x807, PLUGIN_NAME); + BakaAutoHack_Attempts = TESDataHandler->LookupForm(0x900, PLUGIN_NAME); + LGND_LockPickSweetSpot = TESDataHandler->LookupForm(0x1F3CAB, "Fallout4.esm"sv); + } + } + } +} diff --git a/src/Hooks/Hooks.h b/src/Hooks/Hooks.h index f95f99d..ac66bcd 100644 --- a/src/Hooks/Hooks.h +++ b/src/Hooks/Hooks.h @@ -1,959 +1,959 @@ -#pragma once - -#include "MCM/MCM.h" - -namespace Hooks -{ - class BakaAutoShared - { - public: - static bool PlayerHasItem(RE::TESForm* a_form) - { - if (!a_form) - { - return false; - } - - if (auto list = a_form->As()) - { - return PlayerHasItem(list); - } - - if (auto object = a_form->As()) - { - return PlayerHasItem(object); - } - - return false; - } - - static bool PlayerHasItem(RE::BGSListForm* a_list) - { - if (!a_list) - { - return false; - } - - for (auto& iter : a_list->arrayOfForms) - { - if (PlayerHasItem(iter)) - { - return true; - } - } - - return false; - } - - static bool PlayerHasItem(RE::TESBoundObject* a_object) - { - if (!a_object) - { - return false; - } - - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - return (PlayerCharacter->GetInventoryObjectCount(a_object) > 0); - } - - static bool PlayerHasPerk(RE::TESForm* a_form) - { - if (!a_form) - { - return false; - } - - if (auto list = a_form->As()) - { - return PlayerHasPerk(list); - } - - if (auto perk = a_form->As()) - { - return PlayerHasPerk(perk); - } - - return false; - } - - static bool PlayerHasPerk(RE::BGSListForm* a_list) - { - if (!a_list) - { - return false; - } - - for (auto& iter : a_list->arrayOfForms) - { - if (PlayerHasPerk(iter)) - { - return true; - } - } - - return false; - } - - static bool PlayerHasPerk(RE::BGSPerk* a_perk) - { - if (!a_perk) - { - return false; - } - - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - return (PlayerCharacter->GetPerkRank(a_perk) > 0); - } - - static void ShowMessage(const char* a_settingName, std::string_view a_formatText = "", const char* a_sound = nullptr) - { - auto GameSettingCollection = RE::GameSettingCollection::GetSingleton(); - if (!GameSettingCollection) - { - return; - } - - auto setting = GameSettingCollection->settings.find(a_settingName); - if (setting != GameSettingCollection->settings.end()) - { - if (setting->second) - { - auto message = setting->second->GetString(); - if (!message.empty()) - { - if (!a_formatText.empty()) - { - auto formatted = std::vformat(message, std::make_format_args(a_formatText)); - RE::SendHUDMessage::ShowHUDMessage(formatted.data(), a_sound, true, true); - return; - } - - RE::SendHUDMessage::ShowHUDMessage(message.data(), a_sound, true, true); - } - } - } - } - }; - - class BakaAutoHack - { - public: - static void Install() - { - REL::Relocation target{ RE::BGSTerminal::VTABLE[0] }; - _Activate = target.write_vfunc(0x40, reinterpret_cast(hkActivate)); - } - - static void ShowRollModifiers() - { - MCM::Settings::Load(false); - - auto SkillVal = GetRollModifier_Skill(); - auto SkillMsg = std::vformat(MCM::Settings::Formatting::sSkill, std::make_format_args(SkillVal)); - - auto LuckyVal = GetRollModifier_Lucky(); - auto LuckyMsg = std::vformat(MCM::Settings::Formatting::sLucky, std::make_format_args(LuckyVal)); - - auto PerksVal = GetRollModifier_Perks(); - auto PerksMsg = std::vformat(MCM::Settings::Formatting::sPerks, std::make_format_args(PerksVal)); - - auto BonusVal = GetRollModifier_Bonus(); - auto BonusMsg = std::vformat(MCM::Settings::Formatting::sBonus, std::make_format_args(BonusVal)); - - auto TotalVal = SkillVal + LuckyVal + PerksVal + BonusVal; - auto TotalMsg = std::vformat(MCM::Settings::Formatting::sTotal, std::make_format_args(TotalVal)); - - auto msg = std::vformat("{}\n{}\n{}\n{}\n{}"sv, std::make_format_args(SkillMsg, LuckyMsg, PerksMsg, BonusMsg, TotalMsg)); - if (auto MessageMenuManager = RE::MessageMenuManager::GetSingleton()) - { - MessageMenuManager->Create("", msg.data(), nullptr, RE::WARNING_TYPES::kDefault, "$OK"); - } - } - - private: - static bool hkActivate(RE::BGSTerminal* a_this, RE::TESObjectREFR* a_itemActivated, RE::TESObjectREFR* a_actionRef, RE::TESBoundObject* a_objectToGet, std::int32_t a_count) - { - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - if (!PlayerCharacter || PlayerCharacter != a_actionRef) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - if (!MCM::Settings::HackGeneral::bModEnabled.GetValue()) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - if (PlayerCharacter->interactingState != RE::INTERACTING_STATE::kNotInteracting) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - if (RE::BGSTerminal::IsTerminalRefInUse(a_itemActivated)) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - if (auto Lock = a_itemActivated->GetLock()) - { - if ((Lock->flags & 1) == 0) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - auto LockKey = Lock->key; - auto LockLvl = a_this->GetHackDifficultyLockLevel(a_itemActivated); - switch (LockLvl) - { - case RE::LOCK_LEVEL::kEasy: - case RE::LOCK_LEVEL::kAverage: - case RE::LOCK_LEVEL::kHard: - case RE::LOCK_LEVEL::kVeryHard: - { - if (!MCM::Settings::HackGeneral::bIgnoreHasPass.GetValue()) - { - if (LockKey && BakaAutoShared::PlayerHasItem(LockKey)) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - } - break; - } - - default: - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - if (!MCM::Settings::HackGeneral::bNoTimeouts.GetValue()) - { - if (PlayerCharacter->IsLockedOutOfTerminal(a_itemActivated->GetHandle())) - { - BakaAutoShared::ShowMessage("sTerminalLockout"); - return false; - } - } - - if (!MCM::Settings::HackGeneral::bIgnoreHackGates.GetValue()) - { - if (!RE::GamePlayFormulas::CanHackGateCheck(LockLvl)) - { - BakaAutoShared::ShowMessage("sHackingGateFail"); - return false; - } - } - - auto LockVal = GetLockDifficultyClass(LockLvl); - auto RollMin = MCM::Settings::HackRolls::iPlayerDiceMin.GetValue(); - auto RollMax = std::max(RollMin, MCM::Settings::HackRolls::iPlayerDiceMax.GetValue()); - auto RollMod = GetRollModifier(); - auto RollVal = effolkronium::random_thread_local::get(RollMin, RollMax); - - if (MCM::Settings::HackGeneral::bShowRollResults.GetValue()) - { - auto results = std::vformat(MCM::Settings::Formatting::sShowRollResults, std::make_format_args(LockVal, RollVal, RollMod)); - F4SE::log::info("{}"sv, results); - RE::SendHUDMessage::ShowHUDMessage(results.data(), nullptr, false, false); - } - - RollVal += RollMod; - if (RollVal >= LockVal) - { - UnlockObject(a_itemActivated); - HandleExperience(LockLvl); - HandleWaxKey(LockKey); - - if (MCM::Settings::HackGeneral::iDetectionEventSuccessLevel.GetValue()) - { - PlayerCharacter->currentProcess->SetActorsDetectionEvent(PlayerCharacter, a_itemActivated->data.location, MCM::Settings::HackGeneral::iDetectionEventSuccessLevel.GetValue(), a_itemActivated); - } - - if (MCM::Settings::HackGeneral::bActivateTermAfterHack.GetValue()) - { - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - else - { - HandlePostActivate(a_itemActivated); - return true; - } - } - else - { - UnlockObjectFail(a_itemActivated); - - if (MCM::Settings::HackGeneral::iDetectionEventFailureLevel.GetValue()) - { - PlayerCharacter->currentProcess->SetActorsDetectionEvent(PlayerCharacter, a_itemActivated->data.location, MCM::Settings::HackGeneral::iDetectionEventFailureLevel.GetValue(), a_itemActivated); - } - - HandlePostActivate(a_itemActivated); - return true; - } - } - - return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); - } - - static std::int32_t GetLockDifficultyClass(RE::LOCK_LEVEL a_lockLevel) - { - switch (a_lockLevel) - { - case RE::LOCK_LEVEL::kAverage: - return MCM::Settings::HackRolls::iDCAdvanced.GetValue(); - case RE::LOCK_LEVEL::kHard: - return MCM::Settings::HackRolls::iDCExpert.GetValue(); - case RE::LOCK_LEVEL::kVeryHard: - return MCM::Settings::HackRolls::iDCMaster.GetValue(); - default: - return MCM::Settings::HackRolls::iDCNovice.GetValue(); - } - } - - static std::int32_t GetRollModifier_Skill() - { - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - auto SkillLvl = PlayerCharacter->GetActorValue(*GetSkillFromIndex()); - return (MCM::Settings::HackRolls::iBonusPerSkill.GetValue() > 0) ? static_cast(floorf(SkillLvl / MCM::Settings::HackRolls::iBonusPerSkill.GetValue())) : 0; - } - - static std::int32_t GetRollModifier_Lucky() - { - auto LuckyVal = RE::PlayerCharacter::GetSingleton()->GetActorValue(*(RE::ActorValue::GetSingleton()->luck)); - return (MCM::Settings::HackRolls::iBonusPerLucky.GetValue() > 0) ? static_cast(floorf(LuckyVal / MCM::Settings::HackRolls::iBonusPerLucky.GetValue())) : 0; - } - - static std::int32_t GetRollModifier_Perks() - { - std::int32_t result{ 0 }; - for (auto& form : Forms::BakaAutoHack_Perks_Base->arrayOfForms) - { - if (BakaAutoShared::PlayerHasPerk(form)) - { - result += MCM::Settings::HackRolls::iBonusPerPerks.GetValue(); - } - } - - return result; - } - - static std::int32_t GetRollModifier_Bonus() - { - return MCM::Settings::HackRolls::iBonusPerBonus.GetValue(); - } - - static std::int32_t GetRollModifier() - { - std::int32_t result{ 0 }; - result += GetRollModifier_Skill(); - result += GetRollModifier_Perks(); - result += GetRollModifier_Bonus(); - return result; - } - - static RE::ActorValueInfo* GetSkillFromIndex() - { - auto ActorValue = RE::ActorValue::GetSingleton(); - switch (MCM::Settings::HackGeneral::iSkillIndex.GetValue()) - { - case 0: - return ActorValue->strength; - case 1: - return ActorValue->perception; - case 2: - return ActorValue->endurance; - case 3: - return ActorValue->charisma; - case 4: - return ActorValue->intelligence; - case 5: - return ActorValue->agility; - case 6: - return ActorValue->luck; - default: - return RE::TESForm::GetFormByEditorID(MCM::Settings::HackGeneral::sSkillName.GetValue()); - } - } - - static void HandleCrime(RE::TESObjectREFR* a_terminal) - { - if (!a_terminal) - { - return; - } - - if (!a_terminal->GetHasOwner()) - { - return; - } - - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - if (a_terminal->IsAnOwner(PlayerCharacter, true, false)) - { - return; - } - - if (auto ProcessLists = RE::ProcessLists::GetSingleton()) - { - std::uint32_t LOSCount{ 1 }; - if (ProcessLists->RequestHighestDetectionLevelAgainstActor(PlayerCharacter, LOSCount) > 0) - { - auto owner = a_terminal->GetOwner(); - PlayerCharacter->TrespassAlarm(a_terminal, owner, -1); - } - } - } - - static void HandleExperience(RE::LOCK_LEVEL a_lockLevel) - { - RE::DIFFICULTY_LEVEL Difficulty{ RE::DIFFICULTY_LEVEL::kEasy }; - switch (a_lockLevel) - { - case RE::LOCK_LEVEL::kAverage: - Difficulty = RE::DIFFICULTY_LEVEL::kNormal; - break; - case RE::LOCK_LEVEL::kHard: - Difficulty = RE::DIFFICULTY_LEVEL::kHard; - break; - case RE::LOCK_LEVEL::kVeryHard: - Difficulty = RE::DIFFICULTY_LEVEL::kVeryHard; - break; - default: - break; - } - - auto reward = - RE::GamePlayFormulas::GetExperienceReward( - RE::GamePlayFormulas::EXPERIENCE_ACTIVITY::kHackComputer, - Difficulty, - -1.0f); - RE::PlayerCharacter::GetSingleton()->RewardExperience(reward, false, nullptr, nullptr); - } - - static void HandlePostActivate(RE::TESObjectREFR* a_terminal) - { - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - PlayerCharacter->currentProcess->ProcessGreet( - PlayerCharacter, - RE::DIALOGUE_TYPE::kMiscellaneous, - RE::DIALOGUE_SUBTYPE::kMisc_Player_Activate_Terminals, - a_terminal, - nullptr, - false, - false, - false, - false); - - if (MCM::Settings::HackGeneral::bHackingCrimeCheck.GetValue()) - { - HandleCrime(a_terminal); - } - } - - static void HandleWaxKey(RE::TESKey* a_key) - { - if (a_key && PlayerHasWaxKey()) - { - if (!BakaAutoShared::PlayerHasItem(a_key)) - { - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - PlayerCharacter->AddObjectToContainer(a_key, nullptr, 1, nullptr, RE::ITEM_REMOVE_REASON::kNone); - } - } - } - - static bool PlayerHasWaxKey() - { - if (MCM::Settings::HackGeneral::bGivePass.GetValue()) - { - return true; - } - - return BakaAutoShared::PlayerHasPerk(Forms::BakaAutoHack_Perks_WaxKey); - } - - static void UnlockObject(RE::TESObjectREFR* a_terminal) - { - a_terminal->GetLock()->SetLocked(false); - a_terminal->AddLockChange(); - - // if (auto TerminalHacked = RE::TerminalHacked::GetEventSource()) - // { - // TerminalHacked->Notify(RE::TerminalHacked::Event{ a_terminal->GetHandle() }); - // } - - if (auto BGSStoryEventManager = RE::BGSStoryEventManager::GetSingleton()) - { - RE::BGSHackTerminal BGSHackTerminal{ a_terminal, 1 }; - BGSStoryEventManager->AddEvent(BGSHackTerminal); - } - - RE::UIUtils::PlayMenuSound("UITerminalPasswordGood"); - } - - static void UnlockObjectFail(RE::TESObjectREFR* a_terminal) - { - if (!MCM::Settings::HackGeneral::bNoTimeouts.GetValue()) - { - float HackingGuesses = 4.0f; - RE::BGSEntryPoint::HandleEntryPoint(RE::BGSEntryPoint::ENTRY_POINT::kModHackingGuesses, RE::PlayerCharacter::GetSingleton(), &HackingGuesses); - - float HackingAttempt = a_terminal->GetActorValue(*Forms::BakaAutoHack_Attempts); - if (++HackingAttempt >= HackingGuesses) - { - a_terminal->SetActorValue(*Forms::BakaAutoHack_Attempts, 0.0f); - RE::PlayerCharacter::GetSingleton()->LockOutOfTerminal(a_terminal->GetHandle()); - BakaAutoShared::ShowMessage("sTerminalLockout"); - } - else - { - a_terminal->SetActorValue(*Forms::BakaAutoHack_Attempts, HackingAttempt); - } - } - - if (auto BGSStoryEventManager = RE::BGSStoryEventManager::GetSingleton()) - { - RE::BGSHackTerminal BGSHackTerminal{ a_terminal, 0 }; - BGSStoryEventManager->AddEvent(BGSHackTerminal); - } - - RE::UIUtils::PlayMenuSound("UITerminalPasswordBad"); - } - - protected: - inline static REL::Relocation _Activate; - }; - - class BakaAutoLock - { - public: - static void Install() - { - REL::Relocation targetCONT{ REL::ID(879097), 0x1E9 }; - REL::Relocation targetDOOR{ REL::ID(1278339), 0x35D }; - - auto& trampoline = F4SE::GetTrampoline(); - trampoline.write_call<5>(targetCONT.address(), TryUnlockObjectImpl); - trampoline.write_call<5>(targetDOOR.address(), TryUnlockObjectImpl); - - hkHasObjects<879097, 0x114>::Install(); - hkHasObjects<1278339, 0x26C>::Install(); - } - - static void ShowRollModifiers() - { - MCM::Settings::Load(false); - - auto SkillVal = GetRollModifier_Skill(); - auto SkillMsg = std::vformat(MCM::Settings::Formatting::sSkill, std::make_format_args(SkillVal)); - - auto LuckyVal = GetRollModifier_Lucky(); - auto LuckyMsg = std::vformat(MCM::Settings::Formatting::sLucky, std::make_format_args(LuckyVal)); - - auto PerksVal = GetRollModifier_Perks(); - auto PerksMsg = std::vformat(MCM::Settings::Formatting::sPerks, std::make_format_args(PerksVal)); - - auto BonusVal = GetRollModifier_Bonus(); - auto BonusMsg = std::vformat(MCM::Settings::Formatting::sBonus, std::make_format_args(BonusVal)); - - auto TotalVal = SkillVal + LuckyVal + PerksVal + BonusVal; - auto TotalMsg = std::vformat(MCM::Settings::Formatting::sTotal, std::make_format_args(TotalVal)); - - auto msg = std::vformat("{}\n{}\n{}\n{}\n{}"sv, std::make_format_args(SkillMsg, LuckyMsg, PerksMsg, BonusMsg, TotalMsg)); - if (auto MessageMenuManager = RE::MessageMenuManager::GetSingleton()) - { - MessageMenuManager->Create("", msg.data(), nullptr, RE::WARNING_TYPES::kDefault, "$OK"); - } - } - - private: - template - class hkHasObjects - { - public: - static void Install() - { - REL::Relocation target{ REL::ID(ID), OFFSET }; - auto& trampoline = F4SE::GetTrampoline(); - _HasObjects = trampoline.write_call<5>(target.address(), HasObjects); - } - - private: - static bool HasObjects(RE::Actor* a_this, void* a_arg2, std::int32_t a_arg3, std::int32_t a_arg4, std::uint32_t a_arg5, std::int32_t& a_arg6) - { - if (MCM::Settings::LockGeneral::bIgnoreHasKey.GetValue()) - { - if (a_this == RE::PlayerCharacter::GetSingleton()) - { - return false; - } - } - - return _HasObjects(a_this, a_arg2, a_arg3, a_arg4, a_arg5, a_arg6); - } - - inline static REL::Relocation _HasObjects; - }; - - static bool TryUnlockObjectImpl(RE::PlayerCharacter* a_this, RE::TESObjectREFR* a_refr, bool) - { - if (!a_refr) - { - return false; - } - - auto Lock = a_refr->GetLock(); - if (!Lock) - { - return false; - } - - auto LockKey = Lock->key; - auto LockLvl = Lock->GetLockLevel(nullptr); - switch (LockLvl) - { - case RE::LOCK_LEVEL::kEasy: - case RE::LOCK_LEVEL::kAverage: - case RE::LOCK_LEVEL::kHard: - case RE::LOCK_LEVEL::kVeryHard: - break; - - case RE::LOCK_LEVEL::kRequiresKey: - { - if (BakaAutoShared::PlayerHasItem(LockKey)) - { - UnlockObject(a_refr, false); - BakaAutoShared::ShowMessage("sOpenWithKey", LockKey->GetFullName()); - return false; - } - - auto SettingName = LockKey ? "sAutoLockPickKeyOnly" : "sAutoLockPickNoKey"; - BakaAutoShared::ShowMessage(SettingName, LockKey ? LockKey->GetFullName() : ""); - return false; - } - - case RE::LOCK_LEVEL::kTerminal: - { - BakaAutoShared::ShowMessage("sAutoLockPickTerminal"); - return false; - } - - case RE::LOCK_LEVEL::kInaccessible: - case RE::LOCK_LEVEL::kBarred: - case RE::LOCK_LEVEL::kChained: - { - BakaAutoShared::ShowMessage("sAutoLockPickInaccessible"); - return false; - } - - default: - return false; - } - - if (!PlayerHasLockpicks()) - { - BakaAutoShared::ShowMessage("sAutoLockPickNoPicks"); - - if (BakaAutoShared::PlayerHasItem(LockKey)) - { - UnlockObject(a_refr, false); - BakaAutoShared::ShowMessage("sOpenWithKey", LockKey->GetFullName()); - } - - return false; - } - - if (!MCM::Settings::LockGeneral::bIgnoreLockGates.GetValue()) - { - if (!RE::GamePlayFormulas::CanPickLockGateCheck(LockLvl)) - { - BakaAutoShared::ShowMessage("sAutoLockPickGateFail"); - return false; - } - } - - if (!MCM::Settings::LockGeneral::bModEnabled.GetValue()) - { - RE::LockpickingMenu::OpenLockpickingMenu(a_refr); - return false; - } - - auto LockVal = GetLockDifficultyClass(LockLvl); - auto RollMin = MCM::Settings::LockRolls::iPlayerDiceMin.GetValue(); - auto RollMax = std::max(RollMin, MCM::Settings::LockRolls::iPlayerDiceMax.GetValue()); - auto RollMod = GetRollModifier(); - auto RollVal = effolkronium::random_thread_local::get(RollMin, RollMax); - - if (MCM::Settings::LockGeneral::bShowRollResults.GetValue()) - { - auto results = std::vformat(MCM::Settings::Formatting::sShowRollResults, std::make_format_args(LockVal, RollVal, RollMod)); - F4SE::log::info("{}"sv, results); - RE::SendHUDMessage::ShowHUDMessage(results.data(), nullptr, false, false); - } - - RollVal += RollMod; - if (RollVal >= LockVal) - { - UnlockObject(a_refr); - HandleExperience(LockLvl); - HandleWaxKey(LockKey); - - if (MCM::Settings::LockGeneral::iDetectionEventSuccessLevel.GetValue()) - { - a_this->currentProcess->SetActorsDetectionEvent(a_this, a_refr->data.location, MCM::Settings::LockGeneral::iDetectionEventSuccessLevel.GetValue(), a_refr); - } - } - else - { - HandleLockpickRemoval(); - - if (MCM::Settings::LockGeneral::iDetectionEventFailureLevel.GetValue()) - { - a_this->currentProcess->SetActorsDetectionEvent(a_this, a_refr->data.location, MCM::Settings::LockGeneral::iDetectionEventFailureLevel.GetValue(), a_refr); - } - } - - if (MCM::Settings::LockGeneral::bLockpickingCrimeCheck.GetValue()) - { - HandleCrime(a_refr); - } - - return false; - } - - static std::int32_t GetLockDifficultyClass(RE::LOCK_LEVEL a_lockLevel) - { - switch (a_lockLevel) - { - case RE::LOCK_LEVEL::kAverage: - return MCM::Settings::LockRolls::iDCAdvanced.GetValue(); - case RE::LOCK_LEVEL::kHard: - return MCM::Settings::LockRolls::iDCExpert.GetValue(); - case RE::LOCK_LEVEL::kVeryHard: - return MCM::Settings::LockRolls::iDCMaster.GetValue(); - default: - return MCM::Settings::LockRolls::iDCNovice.GetValue(); - } - } - - static std::int32_t GetRollModifier_Skill() - { - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - auto SkillMod = PlayerCharacter->GetActorValue(*Forms::LGND_LockPickSweetSpot); - auto SkillLvl = PlayerCharacter->GetActorValue(*GetSkillFromIndex()); - auto SkillVal = SkillLvl * (1.0f + ((SkillMod) / 10.0f)); - return MCM::Settings::LockRolls::iBonusPerSkill.GetValue() > 0 ? static_cast(floorf(SkillVal / MCM::Settings::LockRolls::iBonusPerSkill.GetValue())) : 0; - } - - static std::int32_t GetRollModifier_Lucky() - { - auto LuckyVal = RE::PlayerCharacter::GetSingleton()->GetActorValue(*(RE::ActorValue::GetSingleton()->luck)); - return MCM::Settings::LockRolls::iBonusPerLucky.GetValue() > 0 ? static_cast(floorf(LuckyVal / MCM::Settings::LockRolls::iBonusPerLucky.GetValue())) : 0; - } - - static std::int32_t GetRollModifier_Perks() - { - std::int32_t result{ 0 }; - for (auto& form : Forms::BakaAutoLock_Perks_Base->arrayOfForms) - { - if (BakaAutoShared::PlayerHasPerk(form)) - { - result += MCM::Settings::LockRolls::iBonusPerPerks.GetValue(); - } - } - - return result; - } - - static std::int32_t GetRollModifier_Bonus() - { - return MCM::Settings::LockRolls::iBonusPerBonus.GetValue(); - } - - static std::int32_t GetRollModifier() - { - std::int32_t result{ 0 }; - result += GetRollModifier_Skill(); - result += GetRollModifier_Perks(); - result += GetRollModifier_Bonus(); - return result; - } - - static RE::ActorValueInfo* GetSkillFromIndex() - { - auto ActorValue = RE::ActorValue::GetSingleton(); - switch (MCM::Settings::LockGeneral::iSkillIndex.GetValue()) - { - case 0: - return ActorValue->strength; - case 1: - return ActorValue->perception; - case 2: - return ActorValue->endurance; - case 3: - return ActorValue->charisma; - case 4: - return ActorValue->intelligence; - case 5: - return ActorValue->agility; - case 6: - return ActorValue->luck; - default: - return RE::TESForm::GetFormByEditorID(MCM::Settings::LockGeneral::sSkillName.GetValue()); - } - } - - static void HandleCrime(RE::TESObjectREFR* a_refr) - { - auto owner = a_refr->GetOwner(); - if (!owner) - { - if (a_refr->GetFormType() != RE::ENUM_FORM_ID::kDOOR) - { - return; - } - - if (auto ExtraTeleport = a_refr->extraList->GetByType()) - { - if (ExtraTeleport->teleportData) - { - if (auto LinkedDoor = ExtraTeleport->teleportData->linkedDoor.get()) - { - if (auto ParentCell = LinkedDoor->GetParentCell()) - { - owner = ParentCell->GetOwner(); - } - } - } - } - } - - if (!owner) - { - return; - } - - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - if (auto ProcessLists = RE::ProcessLists::GetSingleton()) - { - std::uint32_t LOSCount{ 1 }; - if (ProcessLists->RequestHighestDetectionLevelAgainstActor(PlayerCharacter, LOSCount) > 0) - { - auto CrimeChance{ 1.0f }; - RE::BGSEntryPoint::HandleEntryPoint(RE::BGSEntryPoint::ENTRY_POINT::kModLockpickingCrimeChance, PlayerCharacter, a_refr, &CrimeChance); - - auto Chance = 0.0f; - if (Chance < CrimeChance) - { - auto Prison = PlayerCharacter->currentPrisonFaction; - if (Prison && Prison->crimeData.crimevalues.escapeCrimeGold) - { - PlayerCharacter->SetEscaping(true, false); - } - else - { - PlayerCharacter->TrespassAlarm(a_refr, owner, -1); - BakaAutoShared::ShowMessage("sLockpickingCaught"); - } - } - } - } - } - - static void HandleExperience(RE::LOCK_LEVEL a_lockLevel) - { - auto reward = RE::GamePlayFormulas::GetLockXPReward(a_lockLevel); - RE::PlayerCharacter::GetSingleton()->RewardExperience(reward, false, nullptr, nullptr); - } - - static void HandleLockpickRemoval() - { - if (PlayerHasBreakable()) - { - for (auto& iter : Forms::BakaAutoLock_Items_Lockpick->arrayOfForms) - { - if (BakaAutoShared::PlayerHasItem(iter)) - { - RE::UIUtils::PlayMenuSound("UILockpickingPickBreak"); - - RE::TESObjectREFR::RemoveItemData removeItemData{ iter, 1 }; - RE::PlayerCharacter::ScopedInventoryChangeMessageContext ctx{ true, true }; - RE::PlayerCharacter::GetSingleton()->RemoveItem(removeItemData); - } - } - } - else - { - RE::UIUtils::PlayMenuSound("UILockpickingPickMovement"); - } - } - - static void HandleWaxKey(RE::TESKey* a_key) - { - if (a_key && PlayerHasWaxKey()) - { - if (!BakaAutoShared::PlayerHasItem(a_key)) - { - auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); - PlayerCharacter->AddObjectToContainer(a_key, nullptr, 1, nullptr, RE::ITEM_REMOVE_REASON::kNone); - } - } - } - - static bool PlayerHasBreakable() - { - if (MCM::Settings::LockGeneral::bUnbreakableLockpicks.GetValue()) - { - return false; - } - - if (BakaAutoShared::PlayerHasPerk(Forms::BakaAutoLock_Perks_Unbreakable)) - { - return false; - } - - return true; - } - - static bool PlayerHasLockpicks() - { - return BakaAutoShared::PlayerHasItem(Forms::BakaAutoLock_Items_Lockpick); - } - - static bool PlayerHasWaxKey() - { - if (MCM::Settings::LockGeneral::bGiveWaxKeys.GetValue()) - { - return true; - } - - return BakaAutoShared::PlayerHasPerk(Forms::BakaAutoLock_Perks_WaxKey); - } - - static void UnlockObject(RE::TESObjectREFR* a_refr, bool a_picked = true) - { - a_refr->GetLock()->SetLocked(false); - a_refr->AddLockChange(); - - if (a_picked) - { - // if (auto LocksPicked = RE::LocksPicked::GetEventSource()) - // { - // LocksPicked->Notify(RE::LocksPicked::Event{}); - // } - - if (auto BGSStoryEventManager = RE::BGSStoryEventManager::GetSingleton()) - { - auto bIsCrime = a_refr->IsCrimeToActivate(); - RE::BGSPickLockEvent BGSLockPickEvent{ RE::PlayerCharacter::GetSingleton(), a_refr, bIsCrime }; - BGSStoryEventManager->AddEvent(BGSLockPickEvent); - } - } - - RE::UIUtils::PlayMenuSound("UILockpickingUnlock"); - if ((MCM::Settings::LockGeneral::bActivateContAfterPick.GetValue() && (a_refr->data.objectReference && a_refr->data.objectReference->GetFormType() == RE::ENUM_FORM_ID::kCONT)) || (MCM::Settings::LockGeneral::bActivateDoorAfterPick.GetValue() && (a_refr->data.objectReference && a_refr->data.objectReference->GetFormType() == RE::ENUM_FORM_ID::kDOOR))) - { - a_refr->ActivateRef(RE::PlayerCharacter::GetSingleton(), nullptr, 1, false, false, false); - } - } - }; -} +#pragma once + +#include "MCM/MCM.h" + +namespace Hooks +{ + class BakaAutoShared + { + public: + static bool PlayerHasItem(RE::TESForm* a_form) + { + if (!a_form) + { + return false; + } + + if (auto list = a_form->As()) + { + return PlayerHasItem(list); + } + + if (auto object = a_form->As()) + { + return PlayerHasItem(object); + } + + return false; + } + + static bool PlayerHasItem(RE::BGSListForm* a_list) + { + if (!a_list) + { + return false; + } + + for (auto& iter : a_list->arrayOfForms) + { + if (PlayerHasItem(iter)) + { + return true; + } + } + + return false; + } + + static bool PlayerHasItem(RE::TESBoundObject* a_object) + { + if (!a_object) + { + return false; + } + + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + return (PlayerCharacter->GetInventoryObjectCount(a_object) > 0); + } + + static bool PlayerHasPerk(RE::TESForm* a_form) + { + if (!a_form) + { + return false; + } + + if (auto list = a_form->As()) + { + return PlayerHasPerk(list); + } + + if (auto perk = a_form->As()) + { + return PlayerHasPerk(perk); + } + + return false; + } + + static bool PlayerHasPerk(RE::BGSListForm* a_list) + { + if (!a_list) + { + return false; + } + + for (auto& iter : a_list->arrayOfForms) + { + if (PlayerHasPerk(iter)) + { + return true; + } + } + + return false; + } + + static bool PlayerHasPerk(RE::BGSPerk* a_perk) + { + if (!a_perk) + { + return false; + } + + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + return (PlayerCharacter->GetPerkRank(a_perk) > 0); + } + + static void ShowMessage(const char* a_settingName, std::string_view a_formatText = "", const char* a_sound = nullptr) + { + auto GameSettingCollection = RE::GameSettingCollection::GetSingleton(); + if (!GameSettingCollection) + { + return; + } + + auto setting = GameSettingCollection->settings.find(a_settingName); + if (setting != GameSettingCollection->settings.end()) + { + if (setting->second) + { + auto message = setting->second->GetString(); + if (!message.empty()) + { + if (!a_formatText.empty()) + { + auto formatted = std::vformat(message, std::make_format_args(a_formatText)); + RE::SendHUDMessage::ShowHUDMessage(formatted.data(), a_sound, true, true); + return; + } + + RE::SendHUDMessage::ShowHUDMessage(message.data(), a_sound, true, true); + } + } + } + } + }; + + class BakaAutoHack + { + public: + static void Install() + { + REL::Relocation target{ RE::BGSTerminal::VTABLE[0] }; + _Activate = target.write_vfunc(0x40, reinterpret_cast(hkActivate)); + } + + static void ShowRollModifiers() + { + MCM::Settings::Load(false); + + auto SkillVal = GetRollModifier_Skill(); + auto SkillMsg = std::vformat(MCM::Settings::Formatting::sSkill, std::make_format_args(SkillVal)); + + auto LuckyVal = GetRollModifier_Lucky(); + auto LuckyMsg = std::vformat(MCM::Settings::Formatting::sLucky, std::make_format_args(LuckyVal)); + + auto PerksVal = GetRollModifier_Perks(); + auto PerksMsg = std::vformat(MCM::Settings::Formatting::sPerks, std::make_format_args(PerksVal)); + + auto BonusVal = GetRollModifier_Bonus(); + auto BonusMsg = std::vformat(MCM::Settings::Formatting::sBonus, std::make_format_args(BonusVal)); + + auto TotalVal = SkillVal + LuckyVal + PerksVal + BonusVal; + auto TotalMsg = std::vformat(MCM::Settings::Formatting::sTotal, std::make_format_args(TotalVal)); + + auto msg = std::vformat("{}\n{}\n{}\n{}\n{}"sv, std::make_format_args(SkillMsg, LuckyMsg, PerksMsg, BonusMsg, TotalMsg)); + if (auto MessageMenuManager = RE::MessageMenuManager::GetSingleton()) + { + MessageMenuManager->Create("", msg.data(), nullptr, RE::WARNING_TYPES::kDefault, "$OK"); + } + } + + private: + static bool hkActivate(RE::BGSTerminal* a_this, RE::TESObjectREFR* a_itemActivated, RE::TESObjectREFR* a_actionRef, RE::TESBoundObject* a_objectToGet, std::int32_t a_count) + { + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + if (!PlayerCharacter || PlayerCharacter != a_actionRef) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + if (!MCM::Settings::HackGeneral::bModEnabled.GetValue()) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + if (PlayerCharacter->interactingState != RE::INTERACTING_STATE::kNotInteracting) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + if (RE::BGSTerminal::IsTerminalRefInUse(a_itemActivated)) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + if (auto Lock = a_itemActivated->GetLock()) + { + if ((Lock->flags & 1) == 0) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + auto LockKey = Lock->key; + auto LockLvl = a_this->GetHackDifficultyLockLevel(a_itemActivated); + switch (LockLvl) + { + case RE::LOCK_LEVEL::kEasy: + case RE::LOCK_LEVEL::kAverage: + case RE::LOCK_LEVEL::kHard: + case RE::LOCK_LEVEL::kVeryHard: + { + if (!MCM::Settings::HackGeneral::bIgnoreHasPass.GetValue()) + { + if (LockKey && BakaAutoShared::PlayerHasItem(LockKey)) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + } + break; + } + + default: + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + if (!MCM::Settings::HackGeneral::bNoTimeouts.GetValue()) + { + if (PlayerCharacter->IsLockedOutOfTerminal(a_itemActivated->GetHandle())) + { + BakaAutoShared::ShowMessage("sTerminalLockout"); + return false; + } + } + + if (!MCM::Settings::HackGeneral::bIgnoreHackGates.GetValue()) + { + if (!RE::GamePlayFormulas::CanHackGateCheck(LockLvl)) + { + BakaAutoShared::ShowMessage("sHackingGateFail"); + return false; + } + } + + auto LockVal = GetLockDifficultyClass(LockLvl); + auto RollMin = MCM::Settings::HackRolls::iPlayerDiceMin.GetValue(); + auto RollMax = std::max(RollMin, MCM::Settings::HackRolls::iPlayerDiceMax.GetValue()); + auto RollMod = GetRollModifier(); + auto RollVal = effolkronium::random_thread_local::get(RollMin, RollMax); + + if (MCM::Settings::HackGeneral::bShowRollResults.GetValue()) + { + auto results = std::vformat(MCM::Settings::Formatting::sShowRollResults, std::make_format_args(LockVal, RollVal, RollMod)); + F4SE::log::info("{}"sv, results); + RE::SendHUDMessage::ShowHUDMessage(results.data(), nullptr, false, false); + } + + RollVal += RollMod; + if (RollVal >= LockVal) + { + UnlockObject(a_itemActivated); + HandleExperience(LockLvl); + HandleWaxKey(LockKey); + + if (MCM::Settings::HackGeneral::iDetectionEventSuccessLevel.GetValue()) + { + PlayerCharacter->currentProcess->SetActorsDetectionEvent(PlayerCharacter, a_itemActivated->data.location, MCM::Settings::HackGeneral::iDetectionEventSuccessLevel.GetValue(), a_itemActivated); + } + + if (MCM::Settings::HackGeneral::bActivateTermAfterHack.GetValue()) + { + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + else + { + HandlePostActivate(a_itemActivated); + return true; + } + } + else + { + UnlockObjectFail(a_itemActivated); + + if (MCM::Settings::HackGeneral::iDetectionEventFailureLevel.GetValue()) + { + PlayerCharacter->currentProcess->SetActorsDetectionEvent(PlayerCharacter, a_itemActivated->data.location, MCM::Settings::HackGeneral::iDetectionEventFailureLevel.GetValue(), a_itemActivated); + } + + HandlePostActivate(a_itemActivated); + return true; + } + } + + return _Activate(a_this, a_itemActivated, a_actionRef, a_objectToGet, a_count); + } + + static std::int32_t GetLockDifficultyClass(RE::LOCK_LEVEL a_lockLevel) + { + switch (a_lockLevel) + { + case RE::LOCK_LEVEL::kAverage: + return MCM::Settings::HackRolls::iDCAdvanced.GetValue(); + case RE::LOCK_LEVEL::kHard: + return MCM::Settings::HackRolls::iDCExpert.GetValue(); + case RE::LOCK_LEVEL::kVeryHard: + return MCM::Settings::HackRolls::iDCMaster.GetValue(); + default: + return MCM::Settings::HackRolls::iDCNovice.GetValue(); + } + } + + static std::int32_t GetRollModifier_Skill() + { + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + auto SkillLvl = PlayerCharacter->GetActorValue(*GetSkillFromIndex()); + return (MCM::Settings::HackRolls::iBonusPerSkill.GetValue() > 0) ? static_cast(floorf(SkillLvl / MCM::Settings::HackRolls::iBonusPerSkill.GetValue())) : 0; + } + + static std::int32_t GetRollModifier_Lucky() + { + auto LuckyVal = RE::PlayerCharacter::GetSingleton()->GetActorValue(*(RE::ActorValue::GetSingleton()->luck)); + return (MCM::Settings::HackRolls::iBonusPerLucky.GetValue() > 0) ? static_cast(floorf(LuckyVal / MCM::Settings::HackRolls::iBonusPerLucky.GetValue())) : 0; + } + + static std::int32_t GetRollModifier_Perks() + { + std::int32_t result{ 0 }; + for (auto& form : Forms::BakaAutoHack_Perks_Base->arrayOfForms) + { + if (BakaAutoShared::PlayerHasPerk(form)) + { + result += MCM::Settings::HackRolls::iBonusPerPerks.GetValue(); + } + } + + return result; + } + + static std::int32_t GetRollModifier_Bonus() + { + return MCM::Settings::HackRolls::iBonusPerBonus.GetValue(); + } + + static std::int32_t GetRollModifier() + { + std::int32_t result{ 0 }; + result += GetRollModifier_Skill(); + result += GetRollModifier_Perks(); + result += GetRollModifier_Bonus(); + return result; + } + + static RE::ActorValueInfo* GetSkillFromIndex() + { + auto ActorValue = RE::ActorValue::GetSingleton(); + switch (MCM::Settings::HackGeneral::iSkillIndex.GetValue()) + { + case 0: + return ActorValue->strength; + case 1: + return ActorValue->perception; + case 2: + return ActorValue->endurance; + case 3: + return ActorValue->charisma; + case 4: + return ActorValue->intelligence; + case 5: + return ActorValue->agility; + case 6: + return ActorValue->luck; + default: + return RE::TESForm::GetFormByEditorID(MCM::Settings::HackGeneral::sSkillName.GetValue()); + } + } + + static void HandleCrime(RE::TESObjectREFR* a_terminal) + { + if (!a_terminal) + { + return; + } + + if (!a_terminal->GetHasOwner()) + { + return; + } + + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + if (a_terminal->IsAnOwner(PlayerCharacter, true, false)) + { + return; + } + + if (auto ProcessLists = RE::ProcessLists::GetSingleton()) + { + std::uint32_t LOSCount{ 1 }; + if (ProcessLists->RequestHighestDetectionLevelAgainstActor(PlayerCharacter, LOSCount) > 0) + { + auto owner = a_terminal->GetOwner(); + PlayerCharacter->TrespassAlarm(a_terminal, owner, -1); + } + } + } + + static void HandleExperience(RE::LOCK_LEVEL a_lockLevel) + { + RE::DIFFICULTY_LEVEL Difficulty{ RE::DIFFICULTY_LEVEL::kEasy }; + switch (a_lockLevel) + { + case RE::LOCK_LEVEL::kAverage: + Difficulty = RE::DIFFICULTY_LEVEL::kNormal; + break; + case RE::LOCK_LEVEL::kHard: + Difficulty = RE::DIFFICULTY_LEVEL::kHard; + break; + case RE::LOCK_LEVEL::kVeryHard: + Difficulty = RE::DIFFICULTY_LEVEL::kVeryHard; + break; + default: + break; + } + + auto reward = + RE::GamePlayFormulas::GetExperienceReward( + RE::GamePlayFormulas::EXPERIENCE_ACTIVITY::kHackComputer, + Difficulty, + -1.0f); + RE::PlayerCharacter::GetSingleton()->RewardExperience(reward, false, nullptr, nullptr); + } + + static void HandlePostActivate(RE::TESObjectREFR* a_terminal) + { + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + PlayerCharacter->currentProcess->ProcessGreet( + PlayerCharacter, + RE::DIALOGUE_TYPE::kMiscellaneous, + RE::DIALOGUE_SUBTYPE::kMisc_Player_Activate_Terminals, + a_terminal, + nullptr, + false, + false, + false, + false); + + if (MCM::Settings::HackGeneral::bHackingCrimeCheck.GetValue()) + { + HandleCrime(a_terminal); + } + } + + static void HandleWaxKey(RE::TESKey* a_key) + { + if (a_key && PlayerHasWaxKey()) + { + if (!BakaAutoShared::PlayerHasItem(a_key)) + { + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + PlayerCharacter->AddObjectToContainer(a_key, nullptr, 1, nullptr, RE::ITEM_REMOVE_REASON::kNone); + } + } + } + + static bool PlayerHasWaxKey() + { + if (MCM::Settings::HackGeneral::bGivePass.GetValue()) + { + return true; + } + + return BakaAutoShared::PlayerHasPerk(Forms::BakaAutoHack_Perks_WaxKey); + } + + static void UnlockObject(RE::TESObjectREFR* a_terminal) + { + a_terminal->GetLock()->SetLocked(false); + a_terminal->AddLockChange(); + + // if (auto TerminalHacked = RE::TerminalHacked::GetEventSource()) + // { + // TerminalHacked->Notify(RE::TerminalHacked::Event{ a_terminal->GetHandle() }); + // } + + if (auto BGSStoryEventManager = RE::BGSStoryEventManager::GetSingleton()) + { + RE::BGSHackTerminal BGSHackTerminal{ a_terminal, 1 }; + BGSStoryEventManager->AddEvent(BGSHackTerminal); + } + + RE::UIUtils::PlayMenuSound("UITerminalPasswordGood"); + } + + static void UnlockObjectFail(RE::TESObjectREFR* a_terminal) + { + if (!MCM::Settings::HackGeneral::bNoTimeouts.GetValue()) + { + float HackingGuesses = 4.0f; + RE::BGSEntryPoint::HandleEntryPoint(RE::BGSEntryPoint::ENTRY_POINT::kModHackingGuesses, RE::PlayerCharacter::GetSingleton(), &HackingGuesses); + + float HackingAttempt = a_terminal->GetActorValue(*Forms::BakaAutoHack_Attempts); + if (++HackingAttempt >= HackingGuesses) + { + a_terminal->SetActorValue(*Forms::BakaAutoHack_Attempts, 0.0f); + RE::PlayerCharacter::GetSingleton()->LockOutOfTerminal(a_terminal->GetHandle()); + BakaAutoShared::ShowMessage("sTerminalLockout"); + } + else + { + a_terminal->SetActorValue(*Forms::BakaAutoHack_Attempts, HackingAttempt); + } + } + + if (auto BGSStoryEventManager = RE::BGSStoryEventManager::GetSingleton()) + { + RE::BGSHackTerminal BGSHackTerminal{ a_terminal, 0 }; + BGSStoryEventManager->AddEvent(BGSHackTerminal); + } + + RE::UIUtils::PlayMenuSound("UITerminalPasswordBad"); + } + + protected: + inline static REL::Relocation _Activate; + }; + + class BakaAutoLock + { + public: + static void Install() + { + REL::Relocation targetCONT{ REL::ID(879097), 0x1E9 }; + REL::Relocation targetDOOR{ REL::ID(1278339), 0x35D }; + + auto& trampoline = F4SE::GetTrampoline(); + trampoline.write_call<5>(targetCONT.address(), TryUnlockObjectImpl); + trampoline.write_call<5>(targetDOOR.address(), TryUnlockObjectImpl); + + hkHasObjects<879097, 0x114>::Install(); + hkHasObjects<1278339, 0x26C>::Install(); + } + + static void ShowRollModifiers() + { + MCM::Settings::Load(false); + + auto SkillVal = GetRollModifier_Skill(); + auto SkillMsg = std::vformat(MCM::Settings::Formatting::sSkill, std::make_format_args(SkillVal)); + + auto LuckyVal = GetRollModifier_Lucky(); + auto LuckyMsg = std::vformat(MCM::Settings::Formatting::sLucky, std::make_format_args(LuckyVal)); + + auto PerksVal = GetRollModifier_Perks(); + auto PerksMsg = std::vformat(MCM::Settings::Formatting::sPerks, std::make_format_args(PerksVal)); + + auto BonusVal = GetRollModifier_Bonus(); + auto BonusMsg = std::vformat(MCM::Settings::Formatting::sBonus, std::make_format_args(BonusVal)); + + auto TotalVal = SkillVal + LuckyVal + PerksVal + BonusVal; + auto TotalMsg = std::vformat(MCM::Settings::Formatting::sTotal, std::make_format_args(TotalVal)); + + auto msg = std::vformat("{}\n{}\n{}\n{}\n{}"sv, std::make_format_args(SkillMsg, LuckyMsg, PerksMsg, BonusMsg, TotalMsg)); + if (auto MessageMenuManager = RE::MessageMenuManager::GetSingleton()) + { + MessageMenuManager->Create("", msg.data(), nullptr, RE::WARNING_TYPES::kDefault, "$OK"); + } + } + + private: + template + class hkHasObjects + { + public: + static void Install() + { + REL::Relocation target{ REL::ID(ID), OFFSET }; + auto& trampoline = F4SE::GetTrampoline(); + _HasObjects = trampoline.write_call<5>(target.address(), HasObjects); + } + + private: + static bool HasObjects(RE::Actor* a_this, void* a_arg2, std::int32_t a_arg3, std::int32_t a_arg4, std::uint32_t a_arg5, std::int32_t& a_arg6) + { + if (MCM::Settings::LockGeneral::bIgnoreHasKey.GetValue()) + { + if (a_this == RE::PlayerCharacter::GetSingleton()) + { + return false; + } + } + + return _HasObjects(a_this, a_arg2, a_arg3, a_arg4, a_arg5, a_arg6); + } + + inline static REL::Relocation _HasObjects; + }; + + static bool TryUnlockObjectImpl(RE::PlayerCharacter* a_this, RE::TESObjectREFR* a_refr, bool) + { + if (!a_refr) + { + return false; + } + + auto Lock = a_refr->GetLock(); + if (!Lock) + { + return false; + } + + auto LockKey = Lock->key; + auto LockLvl = Lock->GetLockLevel(nullptr); + switch (LockLvl) + { + case RE::LOCK_LEVEL::kEasy: + case RE::LOCK_LEVEL::kAverage: + case RE::LOCK_LEVEL::kHard: + case RE::LOCK_LEVEL::kVeryHard: + break; + + case RE::LOCK_LEVEL::kRequiresKey: + { + if (BakaAutoShared::PlayerHasItem(LockKey)) + { + UnlockObject(a_refr, false); + BakaAutoShared::ShowMessage("sOpenWithKey", LockKey->GetFullName()); + return false; + } + + auto SettingName = LockKey ? "sAutoLockPickKeyOnly" : "sAutoLockPickNoKey"; + BakaAutoShared::ShowMessage(SettingName, LockKey ? LockKey->GetFullName() : ""); + return false; + } + + case RE::LOCK_LEVEL::kTerminal: + { + BakaAutoShared::ShowMessage("sAutoLockPickTerminal"); + return false; + } + + case RE::LOCK_LEVEL::kInaccessible: + case RE::LOCK_LEVEL::kBarred: + case RE::LOCK_LEVEL::kChained: + { + BakaAutoShared::ShowMessage("sAutoLockPickInaccessible"); + return false; + } + + default: + return false; + } + + if (!PlayerHasLockpicks()) + { + BakaAutoShared::ShowMessage("sAutoLockPickNoPicks"); + + if (BakaAutoShared::PlayerHasItem(LockKey)) + { + UnlockObject(a_refr, false); + BakaAutoShared::ShowMessage("sOpenWithKey", LockKey->GetFullName()); + } + + return false; + } + + if (!MCM::Settings::LockGeneral::bIgnoreLockGates.GetValue()) + { + if (!RE::GamePlayFormulas::CanPickLockGateCheck(LockLvl)) + { + BakaAutoShared::ShowMessage("sAutoLockPickGateFail"); + return false; + } + } + + if (!MCM::Settings::LockGeneral::bModEnabled.GetValue()) + { + RE::LockpickingMenu::OpenLockpickingMenu(a_refr); + return false; + } + + auto LockVal = GetLockDifficultyClass(LockLvl); + auto RollMin = MCM::Settings::LockRolls::iPlayerDiceMin.GetValue(); + auto RollMax = std::max(RollMin, MCM::Settings::LockRolls::iPlayerDiceMax.GetValue()); + auto RollMod = GetRollModifier(); + auto RollVal = effolkronium::random_thread_local::get(RollMin, RollMax); + + if (MCM::Settings::LockGeneral::bShowRollResults.GetValue()) + { + auto results = std::vformat(MCM::Settings::Formatting::sShowRollResults, std::make_format_args(LockVal, RollVal, RollMod)); + F4SE::log::info("{}"sv, results); + RE::SendHUDMessage::ShowHUDMessage(results.data(), nullptr, false, false); + } + + RollVal += RollMod; + if (RollVal >= LockVal) + { + UnlockObject(a_refr); + HandleExperience(LockLvl); + HandleWaxKey(LockKey); + + if (MCM::Settings::LockGeneral::iDetectionEventSuccessLevel.GetValue()) + { + a_this->currentProcess->SetActorsDetectionEvent(a_this, a_refr->data.location, MCM::Settings::LockGeneral::iDetectionEventSuccessLevel.GetValue(), a_refr); + } + } + else + { + HandleLockpickRemoval(); + + if (MCM::Settings::LockGeneral::iDetectionEventFailureLevel.GetValue()) + { + a_this->currentProcess->SetActorsDetectionEvent(a_this, a_refr->data.location, MCM::Settings::LockGeneral::iDetectionEventFailureLevel.GetValue(), a_refr); + } + } + + if (MCM::Settings::LockGeneral::bLockpickingCrimeCheck.GetValue()) + { + HandleCrime(a_refr); + } + + return false; + } + + static std::int32_t GetLockDifficultyClass(RE::LOCK_LEVEL a_lockLevel) + { + switch (a_lockLevel) + { + case RE::LOCK_LEVEL::kAverage: + return MCM::Settings::LockRolls::iDCAdvanced.GetValue(); + case RE::LOCK_LEVEL::kHard: + return MCM::Settings::LockRolls::iDCExpert.GetValue(); + case RE::LOCK_LEVEL::kVeryHard: + return MCM::Settings::LockRolls::iDCMaster.GetValue(); + default: + return MCM::Settings::LockRolls::iDCNovice.GetValue(); + } + } + + static std::int32_t GetRollModifier_Skill() + { + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + auto SkillMod = PlayerCharacter->GetActorValue(*Forms::LGND_LockPickSweetSpot); + auto SkillLvl = PlayerCharacter->GetActorValue(*GetSkillFromIndex()); + auto SkillVal = SkillLvl * (1.0f + ((SkillMod) / 10.0f)); + return MCM::Settings::LockRolls::iBonusPerSkill.GetValue() > 0 ? static_cast(floorf(SkillVal / MCM::Settings::LockRolls::iBonusPerSkill.GetValue())) : 0; + } + + static std::int32_t GetRollModifier_Lucky() + { + auto LuckyVal = RE::PlayerCharacter::GetSingleton()->GetActorValue(*(RE::ActorValue::GetSingleton()->luck)); + return MCM::Settings::LockRolls::iBonusPerLucky.GetValue() > 0 ? static_cast(floorf(LuckyVal / MCM::Settings::LockRolls::iBonusPerLucky.GetValue())) : 0; + } + + static std::int32_t GetRollModifier_Perks() + { + std::int32_t result{ 0 }; + for (auto& form : Forms::BakaAutoLock_Perks_Base->arrayOfForms) + { + if (BakaAutoShared::PlayerHasPerk(form)) + { + result += MCM::Settings::LockRolls::iBonusPerPerks.GetValue(); + } + } + + return result; + } + + static std::int32_t GetRollModifier_Bonus() + { + return MCM::Settings::LockRolls::iBonusPerBonus.GetValue(); + } + + static std::int32_t GetRollModifier() + { + std::int32_t result{ 0 }; + result += GetRollModifier_Skill(); + result += GetRollModifier_Perks(); + result += GetRollModifier_Bonus(); + return result; + } + + static RE::ActorValueInfo* GetSkillFromIndex() + { + auto ActorValue = RE::ActorValue::GetSingleton(); + switch (MCM::Settings::LockGeneral::iSkillIndex.GetValue()) + { + case 0: + return ActorValue->strength; + case 1: + return ActorValue->perception; + case 2: + return ActorValue->endurance; + case 3: + return ActorValue->charisma; + case 4: + return ActorValue->intelligence; + case 5: + return ActorValue->agility; + case 6: + return ActorValue->luck; + default: + return RE::TESForm::GetFormByEditorID(MCM::Settings::LockGeneral::sSkillName.GetValue()); + } + } + + static void HandleCrime(RE::TESObjectREFR* a_refr) + { + auto owner = a_refr->GetOwner(); + if (!owner) + { + if (a_refr->GetFormType() != RE::ENUM_FORM_ID::kDOOR) + { + return; + } + + if (auto ExtraTeleport = a_refr->extraList->GetByType()) + { + if (ExtraTeleport->teleportData) + { + if (auto LinkedDoor = ExtraTeleport->teleportData->linkedDoor.get()) + { + if (auto ParentCell = LinkedDoor->GetParentCell()) + { + owner = ParentCell->GetOwner(); + } + } + } + } + } + + if (!owner) + { + return; + } + + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + if (auto ProcessLists = RE::ProcessLists::GetSingleton()) + { + std::uint32_t LOSCount{ 1 }; + if (ProcessLists->RequestHighestDetectionLevelAgainstActor(PlayerCharacter, LOSCount) > 0) + { + auto CrimeChance{ 1.0f }; + RE::BGSEntryPoint::HandleEntryPoint(RE::BGSEntryPoint::ENTRY_POINT::kModLockpickingCrimeChance, PlayerCharacter, a_refr, &CrimeChance); + + auto Chance = 0.0f; + if (Chance < CrimeChance) + { + auto Prison = PlayerCharacter->currentPrisonFaction; + if (Prison && Prison->crimeData.crimevalues.escapeCrimeGold) + { + PlayerCharacter->SetEscaping(true, false); + } + else + { + PlayerCharacter->TrespassAlarm(a_refr, owner, -1); + BakaAutoShared::ShowMessage("sLockpickingCaught"); + } + } + } + } + } + + static void HandleExperience(RE::LOCK_LEVEL a_lockLevel) + { + auto reward = RE::GamePlayFormulas::GetLockXPReward(a_lockLevel); + RE::PlayerCharacter::GetSingleton()->RewardExperience(reward, false, nullptr, nullptr); + } + + static void HandleLockpickRemoval() + { + if (PlayerHasBreakable()) + { + for (auto& iter : Forms::BakaAutoLock_Items_Lockpick->arrayOfForms) + { + if (BakaAutoShared::PlayerHasItem(iter)) + { + RE::UIUtils::PlayMenuSound("UILockpickingPickBreak"); + + RE::TESObjectREFR::RemoveItemData removeItemData{ iter, 1 }; + RE::PlayerCharacter::ScopedInventoryChangeMessageContext ctx{ true, true }; + RE::PlayerCharacter::GetSingleton()->RemoveItem(removeItemData); + } + } + } + else + { + RE::UIUtils::PlayMenuSound("UILockpickingPickMovement"); + } + } + + static void HandleWaxKey(RE::TESKey* a_key) + { + if (a_key && PlayerHasWaxKey()) + { + if (!BakaAutoShared::PlayerHasItem(a_key)) + { + auto PlayerCharacter = RE::PlayerCharacter::GetSingleton(); + PlayerCharacter->AddObjectToContainer(a_key, nullptr, 1, nullptr, RE::ITEM_REMOVE_REASON::kNone); + } + } + } + + static bool PlayerHasBreakable() + { + if (MCM::Settings::LockGeneral::bUnbreakableLockpicks.GetValue()) + { + return false; + } + + if (BakaAutoShared::PlayerHasPerk(Forms::BakaAutoLock_Perks_Unbreakable)) + { + return false; + } + + return true; + } + + static bool PlayerHasLockpicks() + { + return BakaAutoShared::PlayerHasItem(Forms::BakaAutoLock_Items_Lockpick); + } + + static bool PlayerHasWaxKey() + { + if (MCM::Settings::LockGeneral::bGiveWaxKeys.GetValue()) + { + return true; + } + + return BakaAutoShared::PlayerHasPerk(Forms::BakaAutoLock_Perks_WaxKey); + } + + static void UnlockObject(RE::TESObjectREFR* a_refr, bool a_picked = true) + { + a_refr->GetLock()->SetLocked(false); + a_refr->AddLockChange(); + + if (a_picked) + { + // if (auto LocksPicked = RE::LocksPicked::GetEventSource()) + // { + // LocksPicked->Notify(RE::LocksPicked::Event{}); + // } + + if (auto BGSStoryEventManager = RE::BGSStoryEventManager::GetSingleton()) + { + auto bIsCrime = a_refr->IsCrimeToActivate(); + RE::BGSPickLockEvent BGSLockPickEvent{ RE::PlayerCharacter::GetSingleton(), a_refr, bIsCrime }; + BGSStoryEventManager->AddEvent(BGSLockPickEvent); + } + } + + RE::UIUtils::PlayMenuSound("UILockpickingUnlock"); + if ((MCM::Settings::LockGeneral::bActivateContAfterPick.GetValue() && (a_refr->data.objectReference && a_refr->data.objectReference->GetFormType() == RE::ENUM_FORM_ID::kCONT)) || (MCM::Settings::LockGeneral::bActivateDoorAfterPick.GetValue() && (a_refr->data.objectReference && a_refr->data.objectReference->GetFormType() == RE::ENUM_FORM_ID::kDOOR))) + { + a_refr->ActivateRef(RE::PlayerCharacter::GetSingleton(), nullptr, 1, false, false, false); + } + } + }; +} diff --git a/src/MCM/MCM.h b/src/MCM/MCM.h index 5dafc55..29d7500 100644 --- a/src/MCM/MCM.h +++ b/src/MCM/MCM.h @@ -1,118 +1,118 @@ -#pragma once - -namespace MCM::Settings -{ - namespace LockGeneral - { - static REX::INI::Bool bActivateContAfterPick{ "LockGeneral", "bActivateContAfterPick", false }; - static REX::INI::Bool bActivateDoorAfterPick{ "LockGeneral", "bActivateDoorAfterPick", true }; - static REX::INI::Bool bGiveWaxKeys{ "LockGeneral", "bGiveWaxKeys", false }; - static REX::INI::Bool bIgnoreHasKey{ "LockGeneral", "bIgnoreHasKey", false }; - static REX::INI::Bool bIgnoreLockGates{ "LockGeneral", "bIgnoreLockGates", false }; - static REX::INI::Bool bLockpickingCrimeCheck{ "LockGeneral", "bLockpickingCrimeCheck", true }; - static REX::INI::Bool bModEnabled{ "LockGeneral", "bModEnabled", true }; - static REX::INI::Bool bShowRollResults{ "LockGeneral", "bShowRollResults", false }; - static REX::INI::Bool bUnbreakableLockpicks{ "LockGeneral", "bUnbreakableLockpicks", false }; - - static REX::INI::I32 iDetectionEventFailureLevel{ "LockGeneral", "iDetectionEventFailureLevel", 0 }; - static REX::INI::I32 iDetectionEventSuccessLevel{ "LockGeneral", "iDetectionEventSuccessLevel", 0 }; - static REX::INI::I32 iSkillIndex{ "LockGeneral", "iSkillIndex", 1 }; - static REX::INI::Str sSkillName{ "LockGeneral", "sSkillName", "Perception"s }; - } - - namespace LockRolls - { - static REX::INI::I32 iDCNovice{ "LockRolls", "iDCNovice", 8 }; - static REX::INI::I32 iDCAdvanced{ "LockRolls", "iDCAdvanced", 12 }; - static REX::INI::I32 iDCExpert{ "LockRolls", "iDCExpert", 16 }; - static REX::INI::I32 iDCMaster{ "LockRolls", "iDCMaster", 20 }; - static REX::INI::I32 iPlayerDiceMin{ "LockRolls", "iPlayerDiceMin", 1 }; - static REX::INI::I32 iPlayerDiceMax{ "LockRolls", "iPlayerDiceMax", 20 }; - static REX::INI::I32 iBonusPerBonus{ "LockRolls", "iBonusPerBonus", 0 }; - static REX::INI::I32 iBonusPerLucky{ "LockRolls", "iBonusPerLucky", 5 }; - static REX::INI::I32 iBonusPerPerks{ "LockRolls", "iBonusPerPerks", 1 }; - static REX::INI::I32 iBonusPerSkill{ "LockRolls", "iBonusPerSkill", 2 }; - } - - namespace HackGeneral - { - static REX::INI::Bool bActivateTermAfterHack{ "HackGeneral", "bActivateTermAfterHack", true }; - static REX::INI::Bool bGivePass{ "HackGeneral", "bGivePass", false }; - static REX::INI::Bool bIgnoreHasPass{ "HackGeneral", "bIgnoreHasPass", false }; - static REX::INI::Bool bIgnoreHackGates{ "HackGeneral", "bIgnoreHackGates", false }; - static REX::INI::Bool bHackingCrimeCheck{ "HackGeneral", "bHackingCrimeCheck", true }; - static REX::INI::Bool bModEnabled{ "HackGeneral", "bModEnabled", true }; - static REX::INI::Bool bShowRollResults{ "HackGeneral", "bShowRollResults", false }; - static REX::INI::Bool bNoTimeouts{ "HackGeneral", "bNoTimeouts", false }; - - static REX::INI::I32 iDetectionEventFailureLevel{ "HackGeneral", "iDetectionEventFailureLevel", 0 }; - static REX::INI::I32 iDetectionEventSuccessLevel{ "HackGeneral", "iDetectionEventSuccessLevel", 0 }; - static REX::INI::I32 iSkillIndex{ "HackGeneral", "iSkillIndex", 4 }; - static REX::INI::Str sSkillName{ "HackGeneral", "sSkillName", "Intelligence"s }; - } - - namespace HackRolls - { - static REX::INI::I32 iDCNovice{ "HackRolls", "iDCNovice", 8 }; - static REX::INI::I32 iDCAdvanced{ "HackRolls", "iDCAdvanced", 12 }; - static REX::INI::I32 iDCExpert{ "HackRolls", "iDCExpert", 16 }; - static REX::INI::I32 iDCMaster{ "HackRolls", "iDCMaster", 20 }; - static REX::INI::I32 iPlayerDiceMin{ "HackRolls", "iPlayerDiceMin", 1 }; - static REX::INI::I32 iPlayerDiceMax{ "HackRolls", "iPlayerDiceMax", 20 }; - static REX::INI::I32 iBonusPerBonus{ "HackRolls", "iBonusPerBonus", 0 }; - static REX::INI::I32 iBonusPerLucky{ "HackRolls", "iBonusPerLucky", 5 }; - static REX::INI::I32 iBonusPerPerks{ "HackRolls", "iBonusPerPerks", 1 }; - static REX::INI::I32 iBonusPerSkill{ "HackRolls", "iBonusPerSkill", 2 }; - } - - namespace Formatting - { - inline static std::string sBonus; - inline static std::string sLucky; - inline static std::string sPerks; - inline static std::string sSkill; - inline static std::string sTotal; - inline static std::string sShowRollResults; - - static void Load() - { - auto BSScaleformManager = RE::BSScaleformManager::GetSingleton(); - if (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"$BakaAL_Message_Bonus", sBonus); - FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Lucky", sLucky); - FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Perks", sPerks); - FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Skill", sSkill); - FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Total", sTotal); - FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_ShowRollResults", sShowRollResults); - } - } - } - } - - static void Load(bool a_firstRun) - { - const auto ini = REX::INI::SettingStore::GetSingleton(); - if (a_firstRun) - { - ini->Init( - "Data/F4SE/plugins/BakaAutoLockpicking.ini", - "Data/F4SE/plugins/BakaAutoLockpickingCustom.ini"); - Formatting::Load(); - } - - ini->Load(); - } -} +#pragma once + +namespace MCM::Settings +{ + namespace LockGeneral + { + static REX::INI::Bool bActivateContAfterPick{ "LockGeneral", "bActivateContAfterPick", false }; + static REX::INI::Bool bActivateDoorAfterPick{ "LockGeneral", "bActivateDoorAfterPick", true }; + static REX::INI::Bool bGiveWaxKeys{ "LockGeneral", "bGiveWaxKeys", false }; + static REX::INI::Bool bIgnoreHasKey{ "LockGeneral", "bIgnoreHasKey", false }; + static REX::INI::Bool bIgnoreLockGates{ "LockGeneral", "bIgnoreLockGates", false }; + static REX::INI::Bool bLockpickingCrimeCheck{ "LockGeneral", "bLockpickingCrimeCheck", true }; + static REX::INI::Bool bModEnabled{ "LockGeneral", "bModEnabled", true }; + static REX::INI::Bool bShowRollResults{ "LockGeneral", "bShowRollResults", false }; + static REX::INI::Bool bUnbreakableLockpicks{ "LockGeneral", "bUnbreakableLockpicks", false }; + + static REX::INI::I32 iDetectionEventFailureLevel{ "LockGeneral", "iDetectionEventFailureLevel", 0 }; + static REX::INI::I32 iDetectionEventSuccessLevel{ "LockGeneral", "iDetectionEventSuccessLevel", 0 }; + static REX::INI::I32 iSkillIndex{ "LockGeneral", "iSkillIndex", 1 }; + static REX::INI::Str sSkillName{ "LockGeneral", "sSkillName", "Perception"s }; + } + + namespace LockRolls + { + static REX::INI::I32 iDCNovice{ "LockRolls", "iDCNovice", 8 }; + static REX::INI::I32 iDCAdvanced{ "LockRolls", "iDCAdvanced", 12 }; + static REX::INI::I32 iDCExpert{ "LockRolls", "iDCExpert", 16 }; + static REX::INI::I32 iDCMaster{ "LockRolls", "iDCMaster", 20 }; + static REX::INI::I32 iPlayerDiceMin{ "LockRolls", "iPlayerDiceMin", 1 }; + static REX::INI::I32 iPlayerDiceMax{ "LockRolls", "iPlayerDiceMax", 20 }; + static REX::INI::I32 iBonusPerBonus{ "LockRolls", "iBonusPerBonus", 0 }; + static REX::INI::I32 iBonusPerLucky{ "LockRolls", "iBonusPerLucky", 5 }; + static REX::INI::I32 iBonusPerPerks{ "LockRolls", "iBonusPerPerks", 1 }; + static REX::INI::I32 iBonusPerSkill{ "LockRolls", "iBonusPerSkill", 2 }; + } + + namespace HackGeneral + { + static REX::INI::Bool bActivateTermAfterHack{ "HackGeneral", "bActivateTermAfterHack", true }; + static REX::INI::Bool bGivePass{ "HackGeneral", "bGivePass", false }; + static REX::INI::Bool bIgnoreHasPass{ "HackGeneral", "bIgnoreHasPass", false }; + static REX::INI::Bool bIgnoreHackGates{ "HackGeneral", "bIgnoreHackGates", false }; + static REX::INI::Bool bHackingCrimeCheck{ "HackGeneral", "bHackingCrimeCheck", true }; + static REX::INI::Bool bModEnabled{ "HackGeneral", "bModEnabled", true }; + static REX::INI::Bool bShowRollResults{ "HackGeneral", "bShowRollResults", false }; + static REX::INI::Bool bNoTimeouts{ "HackGeneral", "bNoTimeouts", false }; + + static REX::INI::I32 iDetectionEventFailureLevel{ "HackGeneral", "iDetectionEventFailureLevel", 0 }; + static REX::INI::I32 iDetectionEventSuccessLevel{ "HackGeneral", "iDetectionEventSuccessLevel", 0 }; + static REX::INI::I32 iSkillIndex{ "HackGeneral", "iSkillIndex", 4 }; + static REX::INI::Str sSkillName{ "HackGeneral", "sSkillName", "Intelligence"s }; + } + + namespace HackRolls + { + static REX::INI::I32 iDCNovice{ "HackRolls", "iDCNovice", 8 }; + static REX::INI::I32 iDCAdvanced{ "HackRolls", "iDCAdvanced", 12 }; + static REX::INI::I32 iDCExpert{ "HackRolls", "iDCExpert", 16 }; + static REX::INI::I32 iDCMaster{ "HackRolls", "iDCMaster", 20 }; + static REX::INI::I32 iPlayerDiceMin{ "HackRolls", "iPlayerDiceMin", 1 }; + static REX::INI::I32 iPlayerDiceMax{ "HackRolls", "iPlayerDiceMax", 20 }; + static REX::INI::I32 iBonusPerBonus{ "HackRolls", "iBonusPerBonus", 0 }; + static REX::INI::I32 iBonusPerLucky{ "HackRolls", "iBonusPerLucky", 5 }; + static REX::INI::I32 iBonusPerPerks{ "HackRolls", "iBonusPerPerks", 1 }; + static REX::INI::I32 iBonusPerSkill{ "HackRolls", "iBonusPerSkill", 2 }; + } + + namespace Formatting + { + inline static std::string sBonus; + inline static std::string sLucky; + inline static std::string sPerks; + inline static std::string sSkill; + inline static std::string sTotal; + inline static std::string sShowRollResults; + + static void Load() + { + auto BSScaleformManager = RE::BSScaleformManager::GetSingleton(); + if (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"$BakaAL_Message_Bonus", sBonus); + FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Lucky", sLucky); + FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Perks", sPerks); + FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Skill", sSkill); + FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_Total", sTotal); + FetchTranslation(BSScaleformTranslator, L"$BakaAL_Message_ShowRollResults", sShowRollResults); + } + } + } + } + + static void Load(bool a_firstRun) + { + const auto ini = REX::INI::SettingStore::GetSingleton(); + if (a_firstRun) + { + ini->Init( + "Data/F4SE/plugins/BakaAutoLockpicking.ini", + "Data/F4SE/plugins/BakaAutoLockpickingCustom.ini"); + Formatting::Load(); + } + + ini->Load(); + } +} diff --git a/src/PCH.h b/src/PCH.h index afe655a..ba725eb 100644 --- a/src/PCH.h +++ b/src/PCH.h @@ -1,9 +1,9 @@ -#pragma once - -#include "F4SE/F4SE.h" -#include "RE/Fallout.h" -#include "REX/REX/INI.h" - -#include - -using namespace std::literals; +#pragma once + +#include "F4SE/F4SE.h" +#include "RE/Fallout.h" +#include "REX/REX/INI.h" + +#include + +using namespace std::literals; diff --git a/src/Scripts/Papyrus.h b/src/Scripts/Papyrus.h index 6594ced..e08e9c8 100644 --- a/src/Scripts/Papyrus.h +++ b/src/Scripts/Papyrus.h @@ -1,45 +1,45 @@ -#pragma once - -#include "Hooks/Hooks.h" -#include "MCM/MCM.h" - -namespace Papyrus::BakaAutoLock -{ - static constexpr auto CLASS_NAME{ "BakaAutoLock"sv }; - - enum - { - kVersion = 1 - }; - - static std::int32_t GetVersion(std::monostate) - { - return kVersion; - } - - static void ShowRollModifiers(std::monostate) - { - Hooks::BakaAutoLock::ShowRollModifiers(); - } - - static void ShowRollModifiersHack(std::monostate) - { - Hooks::BakaAutoHack::ShowRollModifiers(); - } - - static void UpdateSettings(std::monostate) - { - MCM::Settings::Load(false); - } - - static bool Register(RE::BSScript::IVirtualMachine* a_vm) - { - a_vm->BindNativeMethod(CLASS_NAME, "GetVersion", GetVersion, true); - a_vm->BindNativeMethod(CLASS_NAME, "ShowRollModifiers", ShowRollModifiers); - a_vm->BindNativeMethod(CLASS_NAME, "ShowRollModifiersHack", ShowRollModifiersHack); - a_vm->BindNativeMethod(CLASS_NAME, "UpdateSettings", UpdateSettings); - F4SE::log::info("Registered funcs for class {}"sv, CLASS_NAME); - - return true; - } -} +#pragma once + +#include "Hooks/Hooks.h" +#include "MCM/MCM.h" + +namespace Papyrus::BakaAutoLock +{ + static constexpr auto CLASS_NAME{ "BakaAutoLock"sv }; + + enum + { + kVersion = 1 + }; + + static std::int32_t GetVersion(std::monostate) + { + return kVersion; + } + + static void ShowRollModifiers(std::monostate) + { + Hooks::BakaAutoLock::ShowRollModifiers(); + } + + static void ShowRollModifiersHack(std::monostate) + { + Hooks::BakaAutoHack::ShowRollModifiers(); + } + + static void UpdateSettings(std::monostate) + { + MCM::Settings::Load(false); + } + + static bool Register(RE::BSScript::IVirtualMachine* a_vm) + { + a_vm->BindNativeMethod(CLASS_NAME, "GetVersion", GetVersion, true); + a_vm->BindNativeMethod(CLASS_NAME, "ShowRollModifiers", ShowRollModifiers); + a_vm->BindNativeMethod(CLASS_NAME, "ShowRollModifiersHack", ShowRollModifiersHack); + a_vm->BindNativeMethod(CLASS_NAME, "UpdateSettings", UpdateSettings); + F4SE::log::info("Registered funcs for class {}"sv, CLASS_NAME); + + return true; + } +} diff --git a/src/main.cpp b/src/main.cpp index fdb66e1..9769ea0 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -1,45 +1,45 @@ -#include "Forms/Forms.h" -#include "Hooks/Hooks.h" -#include "Scripts/Papyrus.h" - -namespace -{ - void MessageHandler(F4SE::MessagingInterface::Message* a_msg) - { - switch (a_msg->type) - { - case F4SE::MessagingInterface::kPostLoad: - { - Hooks::BakaAutoHack::Install(); - Hooks::BakaAutoLock::Install(); - break; - } - case F4SE::MessagingInterface::kInputLoaded: - { - MCM::Settings::Load(true); - break; - } - case F4SE::MessagingInterface::kGameDataReady: - { - if (static_cast(a_msg->data)) - { - Forms::Register(); - } - break; - } - default: - break; - } - } -} - -F4SEPluginLoad(const F4SE::LoadInterface* a_F4SE) -{ - F4SE::Init(a_F4SE); - F4SE::AllocTrampoline(256); - - F4SE::GetMessagingInterface()->RegisterListener(MessageHandler); - F4SE::GetPapyrusInterface()->Register(Papyrus::BakaAutoLock::Register); - - return true; -} +#include "Forms/Forms.h" +#include "Hooks/Hooks.h" +#include "Scripts/Papyrus.h" + +namespace +{ + void MessageHandler(F4SE::MessagingInterface::Message* a_msg) + { + switch (a_msg->type) + { + case F4SE::MessagingInterface::kPostLoad: + { + Hooks::BakaAutoHack::Install(); + Hooks::BakaAutoLock::Install(); + break; + } + case F4SE::MessagingInterface::kInputLoaded: + { + MCM::Settings::Load(true); + break; + } + case F4SE::MessagingInterface::kGameDataReady: + { + if (static_cast(a_msg->data)) + { + Forms::Register(); + } + break; + } + default: + break; + } + } +} + +F4SEPluginLoad(const F4SE::LoadInterface* a_F4SE) +{ + F4SE::Init(a_F4SE); + F4SE::AllocTrampoline(256); + + F4SE::GetMessagingInterface()->RegisterListener(MessageHandler); + F4SE::GetPapyrusInterface()->Register(Papyrus::BakaAutoLock::Register); + + return true; +}