Guest User

Untitled

a guest
Apr 20th, 2025
185
0
Never
Not a member of Pastebin yet? Sign Up, it unlocks many cool features!
C++ 11.67 KB | None | 0 0
  1. #include "PCH.h"
  2.  
  3. #include <thread>
  4. #include <chrono>
  5. #include <atomic>
  6.  
  7. namespace ExpressionIndices {
  8.     // From MfgFix::BSFaceGenKeyframeMultiple::Expression
  9.     constexpr std::uint32_t kDialogueAnger = 0;
  10.     constexpr std::uint32_t kDialogueFear = 1;
  11.     constexpr std::uint32_t kDialogueHappy = 2;
  12.     constexpr std::uint32_t kDialogueSad = 3;
  13.     constexpr std::uint32_t kDialogueSurprise = 4;
  14.     constexpr std::uint32_t kDialoguePuzzled = 5;
  15.     constexpr std::uint32_t kDialogueDisgusted = 6;
  16.     constexpr std::uint32_t kMoodNeutral = 7;
  17.     constexpr std::uint32_t kMoodAnger = 8;
  18.     constexpr std::uint32_t kMoodFear = 9;
  19.     constexpr std::uint32_t kMoodHappy = 10;
  20.     constexpr std::uint32_t kMoodSad = 11;
  21.     constexpr std::uint32_t kMoodSurprise = 12;
  22.     constexpr std::uint32_t kMoodPuzzled = 13;
  23.     constexpr std::uint32_t kMoodDisgusted = 14;
  24.     constexpr std::uint32_t kCombatAnger = 15;
  25.     constexpr std::uint32_t kCombatShout = 16;
  26. }
  27.  
  28. namespace PhonemeIndices {
  29.     // From MfgFix::BSFaceGenKeyframeMultiple::Phoneme
  30.     constexpr std::uint32_t kAah = 0;
  31.     constexpr std::uint32_t kBigAah = 1;
  32.     constexpr std::uint32_t kBMP = 2;
  33.     constexpr std::uint32_t kChJSh = 3;
  34.     constexpr std::uint32_t kDST = 4;
  35.     constexpr std::uint32_t kEee = 5;
  36.     constexpr std::uint32_t kEh = 6;
  37.     constexpr std::uint32_t kFV = 7;
  38.     constexpr std::uint32_t kI = 8;
  39.     constexpr std::uint32_t kK = 9;
  40.     constexpr std::uint32_t kN = 10;
  41.     constexpr std::uint32_t kOh = 11;
  42.     constexpr std::uint32_t kOohQ = 12;
  43.     constexpr std::uint32_t kR = 13;
  44.     constexpr std::uint32_t kTh = 14;
  45.     constexpr std::uint32_t kW = 15;
  46. }
  47.  
  48.  
  49. namespace {
  50.     void InitializeLogging() {
  51.         auto path = SKSE::log::log_directory();
  52.         if (!path) {
  53.             return;
  54.         }
  55.  
  56.         *path /= PLUGIN_NAME;
  57.         *path += ".log";
  58.         auto sink = std::make_shared<spdlog::sinks::basic_file_sink_mt>(path->string(), true);
  59.         auto log = std::make_shared<spdlog::logger>("global", std::move(sink));
  60.  
  61.         log->set_level(spdlog::level::info);
  62.         log->flush_on(spdlog::level::info);
  63.  
  64.         spdlog::set_default_logger(std::move(log));
  65.         spdlog::set_pattern("[%H:%M:%S] [%l] %v");
  66.  
  67.         SKSE::log::info("{} v{} - Initialized", PLUGIN_NAME, PLUGIN_VERSION);
  68.     }
  69.  
  70.  
  71.     RE::TESObjectREFR* GetConsoleSelectedReference() {
  72.         //storing the handle of the reference selected in the console.
  73.         //AE (1.6.1130+): 504099 | SE (pre-1.6.1130): 405935 | VR: ???
  74.         REL::Relocation<RE::ObjectRefHandle*> selectedRefHandleGlobal{
  75.             RELOCATION_ID(519394, REL::Module::get().version().patch() < 1130 ? 405935 : 504099)};
  76.  
  77.         if (!selectedRefHandleGlobal.get()) {
  78.             SKSE::log::warn("Could not find global variable for selected console reference handle.");
  79.             return nullptr;
  80.         }
  81.  
  82.         RE::ObjectRefHandle selectedRefHandle = *selectedRefHandleGlobal;
  83.         RE::NiPointer<RE::TESObjectREFR> selectedRefPtr = selectedRefHandle.get();
  84.         return selectedRefPtr.get();
  85.     }
  86.  
  87.     //Function to execute a console command string
  88.     //Couldn't figure out a cleaner way of doing this ¯\_(ツ)_/¯
  89.     //a_targetRef: Optional target for the command. If nullptr, uses the console's selected reference.
  90.     void ExecuteConsoleCommand(const std::string& a_command, RE::TESObjectREFR* a_targetRef = nullptr) {
  91.         if (a_command.empty()) {
  92.             SKSE::log::warn("ExecuteConsoleCommand: Attempted to execute empty command.");
  93.             return;
  94.         }
  95.  
  96.         const auto scriptFactory = RE::IFormFactory::GetConcreteFormFactoryByType<RE::Script>();
  97.         if (!scriptFactory) {
  98.             SKSE::log::error("ExecuteConsoleCommand: Cannot get Script factory.");
  99.             return;
  100.         }
  101.  
  102.         //Temporary script object
  103.         RE::Script* script = scriptFactory->Create();
  104.         if (!script) {
  105.             SKSE::log::error("ExecuteConsoleCommand: Cannot create Script object for command: {}", a_command);
  106.             return;
  107.         }
  108.  
  109.         script->SetCommand(a_command);
  110.  
  111.         RE::TESObjectREFR* targetRef = a_targetRef ? a_targetRef : GetConsoleSelectedReference();
  112.         //Note:: targetRef can be nullptr if nothing is selected or provided
  113.         //The game's CompileAndRun function seems to handle this.
  114.  
  115.         using CompileAndRunImpl_t = void(RE::Script*, RE::ScriptCompiler*, RE::COMPILER_NAME, RE::TESObjectREFR*);
  116.  
  117.         //Get the address of the internal CompileAndRun function using relocation
  118.         //Use the same relocation ID ConsoleUtil uses.
  119.         //AE (1.6.1130+): 441582 | SE (pre-1.6.1130): 21890 | VR: ???
  120.         REL::Relocation<CompileAndRunImpl_t> compileAndRunFunc{
  121.             RELOCATION_ID(21416, REL::Module::get().version().patch() < 1130 ? 21890 : 441582)};
  122.  
  123.         if (!compileAndRunFunc.get()) {
  124.             SKSE::log::error("ExecuteConsoleCommand: Cannot find CompileAndRun function address.");
  125.             delete script;  // Clean up the created script object
  126.             return;
  127.         }
  128.  
  129.         RE::ScriptCompiler compiler;
  130.  
  131.         SKSE::log::info(
  132.             "Executing console command: \"{}\" with target: {}", a_command,
  133.             targetRef ? (targetRef->GetName() ? targetRef->GetName() : "Unnamed Ref") : "None (or Console Selected)");
  134.  
  135.         //Call the internal game function to execute the command
  136.         compileAndRunFunc(script,                                    //The script object containing the command
  137.                           &compiler,                                 //The compiler instance
  138.                           RE::COMPILER_NAME::kSystemWindowCompiler,  //Context, kSystemWindowCompiler is appropriate
  139.                           targetRef);                                //The target reference
  140.  
  141.         //Clean up the temporary script object
  142.         delete script;
  143.     }
  144.  
  145.     class MouthStateHandler {
  146.     public:
  147.         static MouthStateHandler* GetSingleton() {
  148.             static MouthStateHandler singleton;
  149.             return &singleton;
  150.         }
  151.  
  152.         void Update() {
  153.             if (!IsPlayerLoaded()) {
  154.                 RE::ConsoleLog::GetSingleton()->Print("Player not loaded ");
  155.  
  156.                 return;
  157.             }
  158.  
  159.             bool currentMouthOpen = IsPlayerMouthOpen();
  160.  
  161.             // If mouth state changed
  162.             if (currentMouthOpen != wasMouthOpen) {
  163.                 SKSE::log::info("mouth toggled");
  164.                 RE::ConsoleLog::GetSingleton()->Print("Mouth toggled> " + currentMouthOpen);
  165.  
  166.                 if (currentMouthOpen) {
  167.                     ExecuteConsoleCommand("Smp Reset");
  168.                 } else {
  169.                     //ExecuteConsoleCommand("player.EquipSpell IllusionLight LeftHand");
  170.                 }
  171.                 wasMouthOpen = currentMouthOpen;
  172.             }
  173.         }
  174.  
  175.     private:
  176.         bool wasMouthOpen = false;
  177.  
  178.  
  179.         bool IsPlayerMouthOpen() {
  180.             auto player = RE::PlayerCharacter::GetSingleton();
  181.             if (!player) {
  182.                 RE::ConsoleLog::GetSingleton()->Print("IsPlayerMouthOpen: PlayerCharacter null?");
  183.                 return false;
  184.             }
  185.  
  186.             auto animData = player->GetFaceGenAnimationData();
  187.             if (!animData) {
  188.                 RE::ConsoleLog::GetSingleton()->Print("IsPlayerMouthOpen: animData null?");
  189.                 return false;
  190.             }
  191.  
  192.             //--- Check Final Expressions (unk0C0) ---
  193.             //This is where merged expressions often end up
  194.             auto& finalExpression = animData->unk0C0;
  195.             float expressionThreshold = 0.5f;          //Todo: Tune this threshold
  196.  
  197.             if (finalExpression.values && finalExpression.count > 0) {
  198.                 //Indices likely to involve an open mouth
  199.                 const std::vector<std::uint32_t> openMouthExprIndices = {
  200.                     ExpressionIndices::kCombatShout
  201.                     //Add others?
  202.                 };
  203.  
  204.                 for (const auto indexToCheck : openMouthExprIndices) {
  205.                     if (finalExpression.count > indexToCheck) {
  206.                         float mouthValue = finalExpression.values[indexToCheck];
  207.                         if (mouthValue > expressionThreshold) {
  208.                             RE::ConsoleLog::GetSingleton()->Print("Mouth open via Final Expression %u: %.2f",
  209.                                                                   indexToCheck, mouthValue);
  210.                             return true;  //Mouth open due to expression?
  211.                         }
  212.                     }
  213.                 }
  214.             } else {
  215.                 RE::ConsoleLog::GetSingleton()->Print(
  216.                     "IsPlayerMouthOpen: Final Expression (unk0C0) values invalid or count is 0");
  217.             }
  218.  
  219.             //--- Check Final Phonemes (unk120) ---
  220.             //This is where merged phonemes often end up
  221.             auto& finalPhoneme = animData->unk120;
  222.             float phonemeThreshold = 0.3f;          //Todo: Tune this threshold, might be lower than expressions
  223.  
  224.             if (finalPhoneme.values && finalPhoneme.count > 0) {
  225.  
  226.                 //Indices for open-mouth Phonemes
  227.                 const std::vector<std::uint32_t> openMouthPhonemeIndices = {
  228.                     PhonemeIndices::kAah, PhonemeIndices::kBigAah, PhonemeIndices::kOh, PhonemeIndices::kOohQ,
  229.                     PhonemeIndices::kEh  //Often slightly open
  230.                 };
  231.  
  232.                 for (const auto indexToCheck : openMouthPhonemeIndices) {
  233.                     if (finalPhoneme.count > indexToCheck) {
  234.                         float mouthValue = finalPhoneme.values[indexToCheck];
  235.                         if (mouthValue > phonemeThreshold) {
  236.                             RE::ConsoleLog::GetSingleton()->Print("Mouth open via Final Phoneme %u: %.2f", indexToCheck,
  237.                                                                   mouthValue);
  238.                             return true;  //Mouth open due to phoneme
  239.                         }
  240.                     }
  241.                 }
  242.             } else {
  243.                 RE::ConsoleLog::GetSingleton()->Print(
  244.                     "IsPlayerMouthOpen: Final Phoneme (unk120) values invalid or count is 0");
  245.             }
  246.  
  247.             //If neither check found an open mouth
  248.             RE::ConsoleLog::GetSingleton()->Print("Mouth closed (checked expressions and phonemes)");
  249.             return false;
  250.         }
  251.  
  252.         //Check if player is loaded
  253.         bool IsPlayerLoaded() { return RE::PlayerCharacter::GetSingleton() != nullptr; }
  254.        
  255.     };
  256. }
  257.  
  258. std::atomic<bool> g_threadRunning(false);
  259. std::thread g_updateThread;
  260.  
  261. //Todo: Rename this. Maybe just DoTick? FrameUpdate makes no sense
  262. void FrameUpdate() {
  263.     MouthStateHandler::GetSingleton()->Update();
  264. }
  265.  
  266. void UpdateThreadFunction() {
  267.     while (g_threadRunning) {
  268.         SKSE::GetTaskInterface()->AddTask(FrameUpdate); //Queue to game thread
  269.         std::this_thread::sleep_for(std::chrono::seconds(1));
  270.     }
  271. }
  272.  
  273. void MessageHandler(SKSE::MessagingInterface::Message* message) {
  274.     if (message->type == SKSE::MessagingInterface::kDataLoaded) {
  275.         if (!g_threadRunning) {
  276.             g_threadRunning = true;
  277.             g_updateThread = std::thread(UpdateThreadFunction);
  278.             g_updateThread.detach();
  279.             SKSE::log::info("Registered update task thread");
  280.         }
  281.     }
  282. }
  283.  
  284. SKSEPluginLoad(const SKSE::LoadInterface* skse) {
  285.     InitializeLogging();
  286.  
  287.     SKSE::Init(skse);
  288.  
  289.     auto messaging = SKSE::GetMessagingInterface();
  290.     if (messaging) {
  291.         messaging->RegisterListener("SKSE", MessageHandler);
  292.         SKSE::log::info("Registered messaging listener");
  293.     }
  294.  
  295.     return true;
  296. }
Advertisement
Add Comment
Please, Sign In to add comment