├── .clang-format ├── .editorconfig ├── .gitignore ├── .gitmodules ├── LICENSE ├── README.md ├── scripts ├── generate_config.py ├── hashing.py └── rotation.py ├── src ├── API │ └── IPluginInterface.h ├── GameForms.h ├── PCH.h ├── Papyrus │ ├── Papyrus.cpp │ ├── Papyrus.h │ ├── SexLabRegistry.cpp │ ├── SexLabRegistry.h │ ├── SexLabUtil.h │ ├── sslActorStats.cpp │ ├── sslActorStats.h │ ├── sslLibrary │ │ ├── LegacyData.h │ │ ├── Serialize.h │ │ ├── sslActorLibrary.cpp │ │ ├── sslActorLibrary.h │ │ ├── sslThreadLibrary.cpp │ │ └── sslThreadLibrary.h │ ├── sslObject │ │ ├── sslAnimationSlots.cpp │ │ ├── sslAnimationSlots.h │ │ ├── sslCreatureAnimationSlots.cpp │ │ ├── sslCreatureAnimationSlots.h │ │ ├── sslExpressionSlots.cpp │ │ ├── sslExpressionSlots.h │ │ ├── sslVoiceSlots.cpp │ │ └── sslVoiceSlots.h │ ├── sslSystemConfig.cpp │ ├── sslSystemConfig.h │ ├── sslThreadModel.cpp │ └── sslThreadModel.h ├── Registry │ ├── CumFx.cpp │ ├── CumFx.h │ ├── Define │ │ ├── Animation.cpp │ │ ├── Animation.h │ │ ├── Expression.cpp │ │ ├── Expression.h │ │ ├── Fragment.cpp │ │ ├── Fragment.h │ │ ├── Furniture.cpp │ │ ├── Furniture.h │ │ ├── RaceKey.cpp │ │ ├── RaceKey.h │ │ ├── Sex.cpp │ │ ├── Sex.h │ │ ├── Tags.cpp │ │ ├── Tags.h │ │ ├── Transform.cpp │ │ ├── Transform.h │ │ ├── Voice.cpp │ │ └── Voice.h │ ├── Library.cpp │ ├── Library.h │ ├── Library_SaveLoad.cpp │ ├── Stats.cpp │ ├── Stats.h │ ├── Util │ │ ├── Decode.h │ │ ├── RayCast.h │ │ ├── RayCast │ │ │ ├── Credits.md │ │ │ ├── Math.h │ │ │ ├── ObjectBound.cpp │ │ │ ├── ObjectBound.h │ │ │ ├── Offsets.h │ │ │ ├── RayCast.cpp │ │ │ ├── bhkLinearCastCollector.h │ │ │ └── bhkRigidBodyT.h │ │ ├── SAT.h │ │ ├── Scale.cpp │ │ └── Scale.h │ ├── Validation.cpp │ └── Validation.h ├── Serialization.h ├── Thread │ ├── Interface │ │ ├── Interface.h │ │ ├── SceneMenu.cpp │ │ ├── SceneMenu.h │ │ ├── SelectionMenu.cpp │ │ └── SelectionMenu.h │ ├── NiNode │ │ ├── NiMath.cpp │ │ ├── NiMath.h │ │ ├── NiPosition.cpp │ │ ├── NiPosition.h │ │ ├── NiUpdate.cpp │ │ ├── NiUpdate.h │ │ ├── Node.cpp │ │ └── Node.h │ ├── Thread.cpp │ ├── Thread.h │ └── ThreadCtor.cpp ├── UserData │ ├── Settings.cpp │ ├── Settings.h │ ├── StripData.cpp │ ├── StripData.h │ ├── config.def │ └── mcm.def ├── Util │ ├── Combinatorics.h │ ├── FormLookup.h │ ├── Misc.h │ ├── Premutation.h │ ├── Random.h │ ├── Script.h │ ├── Singleton.h │ ├── StringUtil.h │ └── World.h └── main.cpp ├── xmake-requires.lock └── xmake.lua /.clang-format: -------------------------------------------------------------------------------- 1 | --- 2 | AccessModifierOffset: -2 3 | AlignAfterOpenBracket: DontAlign 4 | AlignConsecutiveAssignments: 'false' 5 | AlignConsecutiveDeclarations: 'false' 6 | AlignConsecutiveMacros: 'false' 7 | AlignEscapedNewlines: Left 8 | AlignOperands: 'true' 9 | AlignTrailingComments: 'true' 10 | AllowAllArgumentsOnNextLine: 'false' 11 | AllowAllConstructorInitializersOnNextLine: 'false' 12 | AllowAllParametersOfDeclarationOnNextLine: 'false' 13 | AllowShortBlocksOnASingleLine: Empty 14 | AllowShortCaseLabelsOnASingleLine: 'false' 15 | AllowShortFunctionsOnASingleLine: All 16 | AllowShortIfStatementsOnASingleLine: WithoutElse 17 | AllowShortLambdasOnASingleLine: All 18 | AllowShortLoopsOnASingleLine: 'true' 19 | AlwaysBreakAfterReturnType: None 20 | AlwaysBreakBeforeMultilineStrings: 'true' 21 | AlwaysBreakTemplateDeclarations: 'Yes' 22 | BinPackArguments: 'true' 23 | BinPackParameters: 'true' 24 | BraceWrapping: 25 | AfterCaseLabel: 'true' 26 | AfterClass: 'true' 27 | AfterControlStatement: 'false' 28 | AfterEnum: 'true' 29 | AfterExternBlock: 'true' 30 | AfterFunction: 'true' 31 | AfterNamespace: 'true' 32 | AfterStruct: 'true' 33 | AfterUnion: 'true' 34 | BeforeCatch: 'false' 35 | BeforeElse: 'false' 36 | IndentBraces: 'false' 37 | SplitEmptyFunction: 'false' 38 | SplitEmptyNamespace: 'false' 39 | SplitEmptyRecord: 'false' 40 | BreakBeforeBinaryOperators: None 41 | BreakBeforeBraces: Custom 42 | BreakBeforeTernaryOperators: 'false' 43 | BreakConstructorInitializers: AfterColon 44 | BreakInheritanceList: AfterColon 45 | BreakStringLiterals: 'true' 46 | ColumnLimit: 0 47 | CompactNamespaces: 'false' 48 | PackConstructorInitializers : NextLine 49 | ConstructorInitializerIndentWidth: 2 50 | ContinuationIndentWidth: 2 51 | Cpp11BracedListStyle: 'false' 52 | DeriveLineEnding: 'true' 53 | DerivePointerAlignment: 'false' 54 | DisableFormat: 'false' 55 | FixNamespaceComments: 'false' 56 | IncludeBlocks: Preserve 57 | IndentCaseBlocks: 'true' 58 | IndentCaseLabels: 'false' 59 | IndentGotoLabels: 'false' 60 | IndentPPDirectives: None 61 | IndentWidth: 2 62 | IndentWrappedFunctionNames: 'true' 63 | KeepEmptyLinesAtTheStartOfBlocks: 'false' 64 | Language: Cpp 65 | MaxEmptyLinesToKeep: 2 66 | NamespaceIndentation: All 67 | PointerAlignment: Left 68 | ReflowComments : 'false' 69 | SortIncludes: 'true' 70 | SortUsingDeclarations: 'true' 71 | SpaceAfterCStyleCast: 'false' 72 | SpaceAfterLogicalNot: 'false' 73 | SpaceAfterTemplateKeyword: 'true' 74 | SpaceBeforeAssignmentOperators: 'true' 75 | SpaceBeforeCpp11BracedList: 'false' 76 | SpaceBeforeCtorInitializerColon: 'true' 77 | SpaceBeforeInheritanceColon: 'true' 78 | SpaceBeforeParens: ControlStatements 79 | SpaceBeforeRangeBasedForLoopColon: 'true' 80 | SpaceBeforeSquareBrackets: 'false' 81 | SpaceInEmptyBlock: 'false' 82 | SpaceInEmptyParentheses: 'false' 83 | SpacesBeforeTrailingComments: 2 84 | SpacesInAngles: 'false' 85 | SpacesInCStyleCastParentheses: 'false' 86 | SpacesInConditionalStatement: 'false' 87 | SpacesInContainerLiterals: 'true' 88 | SpacesInParentheses: 'false' 89 | SpacesInSquareBrackets: 'false' 90 | Standard: c++20 91 | TabWidth: 2 92 | UseCRLF: 'true' 93 | UseTab: Always 94 | 95 | ... 96 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | [*] 2 | charset = utf-8 3 | insert_final_newline = true 4 | 5 | [*.{h,cmake,cpp}] 6 | indent_style = tab 7 | indent_size = 4 8 | 9 | [*.json] 10 | indent_style = space 11 | indent_size = 4 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .vs 2 | .vscode 3 | *.code-workspace 4 | build/ 5 | /vcpkg_installed 6 | scripts/out/ 7 | .xmake 8 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "lib/CommonLibSSE"] 2 | path = lib/CommonLibSSE 3 | url = https://github.com/powerof3/CommonLibSSE.git 4 | [submodule "lib/CommonLibVR"] 5 | path = lib/CommonLibVR 6 | url = https://github.com/alandtse/CommonLibVR.git 7 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # SexLab P+ 2 | A high performance and stability patch for SexLab for Skyrim SE. 3 | 4 | ## Requirements 5 | * [xmake](https://xmake.io/#/) 6 | * Add this to your `PATH` 7 | * [PowerShell](https://github.com/PowerShell/PowerShell/releases/latest) 8 | * [Visual Studio Community 2022](https://visualstudio.microsoft.com/) 9 | * Desktop development with C++ 10 | * [CommonLibSSE](https://github.com/alandtse/CommonLibVR/tree/ng) 11 | * You need to build from the alandtse/ng branch 12 | * Create Environment Variables: 13 | * `XSE_TES5_MODS_PATH`: Path to your MO2/Vortex `mods` folder 14 | 15 | ## Building 16 | ``` 17 | git clone https://github.com/Scrabx3/SexLabpp.git 18 | cd SexLabpp 19 | git submodule update --init --recursive 20 | xmake f -m release [ 21 | --copy_to_papyrus=(y/n) # copy build to the papyrus mod instance 22 | --skyrim_se=(y/n) # build skyrim se (1.5) 23 | --skyrim_vr=(y/n) # build skyrim vr 24 | ] 25 | xmake 26 | ``` 27 | -------------------------------------------------------------------------------- /scripts/generate_config.py: -------------------------------------------------------------------------------- 1 | import os 2 | import re 3 | 4 | config_def_path = './src/UserData/config.def' 5 | output_ini_path = './scripts/out/SexLab.ini' 6 | 7 | def validate_value(value): 8 | if value == "true": 9 | return "1" 10 | if value == "false": 11 | return "0" 12 | match = re.match(r'^(\d+\.\d+|\d+)f$', value) 13 | if match: 14 | return match.group(1) 15 | else: 16 | return value 17 | 18 | def parse_config_def(file_path): 19 | settings = [] 20 | with open(file_path, 'r') as file: 21 | for line in file: 22 | match = re.match(r'INI_SETTING\(([^,]+),\s*([^,]+),\s*"([^"]+)"\)', line) 23 | if match: 24 | value, default, category = match.groups() 25 | default = validate_value(default) 26 | settings.append((value.strip(), default.strip(), category.strip())) 27 | return settings 28 | 29 | def generate_ini_file(settings, output_path): 30 | categories = {} 31 | for value, default, category in settings: 32 | if category not in categories: 33 | categories[category] = [] 34 | categories[category].append((value, default)) 35 | 36 | os.makedirs(os.path.dirname(output_path), exist_ok=True) 37 | with open(output_path, 'w') as file: 38 | for category, values in categories.items(): 39 | file.write(f'[{category}]\n') 40 | for value, default in values: 41 | file.write(f'{value} = {default}\n') 42 | file.write('\n') 43 | 44 | settings = parse_config_def(config_def_path) 45 | generate_ini_file(settings, output_ini_path) 46 | 47 | print(f"Generated {output_ini_path}") 48 | -------------------------------------------------------------------------------- /scripts/hashing.py: -------------------------------------------------------------------------------- 1 | import argparse 2 | 3 | argparser = argparse.ArgumentParser(description="Read a hash file and print its contents.") 4 | argparser.add_argument("-b", "--binary", type=str, default=0, help="Binary hash key") 5 | argparser.add_argument("-d", "--decimal", type=str, default=0, help="Binary hash key") 6 | args = argparser.parse_args() 7 | if args.binary == 0 and args.decimal == 0: 8 | print("Please provide a hash key using -b or -d") 9 | exit(1) 10 | 11 | # Mapping from \src\Registry\Define\Fragment.h 12 | FRAGMENT_FLAGS = { 13 | "Male": 1 << 0, 14 | "Female": 1 << 1, 15 | "Human": 1 << 2, 16 | "Vampire": 1 << 3, 17 | "Futa": 1 << 4, 18 | "CrtBit0": 1 << 3, 19 | "CrtBit1": 1 << 4, 20 | "CrtBit2": 1 << 5, 21 | "CrtBit3": 1 << 6, 22 | "CrtBit4": 1 << 7, 23 | "CrtBit5": 1 << 8, 24 | "Submissive": 1 << 9, 25 | "Unconscious": 1 << 10, 26 | } 27 | crt_bits = FRAGMENT_FLAGS["CrtBit0"] | FRAGMENT_FLAGS["CrtBit1"] | FRAGMENT_FLAGS["CrtBit2"] | FRAGMENT_FLAGS["CrtBit3"] | FRAGMENT_FLAGS["CrtBit4"] | FRAGMENT_FLAGS["CrtBit5"] 28 | 29 | def print_flags_from_binary(value): 30 | is_human = value & FRAGMENT_FLAGS["Human"] 31 | for name, bit in FRAGMENT_FLAGS.items(): 32 | if not value & bit: 33 | continue 34 | if name.startswith("CrtBit"): 35 | continue 36 | if not is_human and crt_bits & bit: 37 | continue 38 | print(f"{name} ({bit})") 39 | if not is_human: 40 | crt_key = (value & crt_bits) >> 3 41 | print(f"Creature: {crt_key} ({crt_key:b})") 42 | 43 | def read_hash_file(hash_key): 44 | if len(hash_key) == 55: 45 | part_length = len(hash_key) // 5 46 | parts = [hash_key[i*part_length:(i+1)*part_length] for i in range(5)] 47 | else: 48 | parts = [hash_key] 49 | for part in parts: 50 | value = int(part, 2) 51 | if value == 0: 52 | continue 53 | print(f"Value: {part}") 54 | print_flags_from_binary(value) 55 | print("") 56 | 57 | binary_arg = args.binary 58 | if args.decimal != 0: 59 | binary_arg = bin(int(args.decimal))[2:] 60 | 61 | read_hash_file(binary_arg) 62 | -------------------------------------------------------------------------------- /scripts/rotation.py: -------------------------------------------------------------------------------- 1 | import numpy as np 2 | 3 | # Hoping I didnt get this wrong due to the axes in Skyrim defining Z as upward and Y as forward 4 | def euler_to_rotation_matrix(yaw, pitch, roll): 5 | yaw = np.radians(yaw) 6 | pitch = np.radians(pitch) 7 | roll = np.radians(roll) 8 | Rz = np.array([ 9 | [np.cos(yaw), -np.sin(yaw), 0], 10 | [np.sin(yaw), np.cos(yaw), 0], 11 | [0, 0, 1] 12 | ]) 13 | Rx = np.array([ 14 | [1, 0, 0], 15 | [0, np.cos(pitch), -np.sin(pitch)], 16 | [0, np.sin(pitch), np.cos(pitch)] 17 | ]) 18 | Ry = np.array([ 19 | [np.cos(roll), 0, np.sin(roll)], 20 | [0, 1, 0], 21 | [-np.sin(roll), 0, np.cos(roll)] 22 | ]) 23 | R = Rz @ Rx @ Ry 24 | return R 25 | 26 | R_initial = euler_to_rotation_matrix(-158.18, -1.51, -54.54) 27 | R_desired = euler_to_rotation_matrix(0, 0, 90) 28 | 29 | R_initial_inv = np.linalg.inv(R_initial) 30 | T = R_desired @ R_initial_inv 31 | 32 | print(T) 33 | -------------------------------------------------------------------------------- /src/GameForms.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace GameForms 4 | { 5 | #define LOOKUPMOD(form, formid, mod) \ 6 | form = RE::TESDataHandler::GetSingleton()->LookupForm::type>(formid, mod); \ 7 | if (!form) { \ 8 | logger::critical("Failed to lookup form {} in {}: 0x{:X}", #form, mod, formid); \ 9 | return false; \ 10 | } 11 | 12 | #define LOOKUPGAME(form, formid) \ 13 | form = RE::TESForm::LookupByID::type>(formid); \ 14 | if (!form) { \ 15 | logger::critical("Failed to lookup form {}: 0x{:X}", #form, formid); \ 16 | return false; \ 17 | } 18 | 19 | // Vanilla Forms 20 | inline RE::BGSKeyword* FurnitureBedRoll; 21 | inline RE::BGSKeyword* DLC2RieklingMountedKeyword; 22 | 23 | // SexLab Forms 24 | inline const RE::TESFaction* GenderFaction; 25 | inline const RE::TESFaction* AnimatingFaction; 26 | inline const RE::TESFaction* ForbiddenFaction; 27 | 28 | inline const RE::TESQuest* ConfigQuest; 29 | 30 | inline bool LoadData() 31 | { 32 | LOOKUPGAME(FurnitureBedRoll, 0xE4AD6); 33 | LOOKUPMOD(DLC2RieklingMountedKeyword, 0x03A159, "Dragonborn.esm"); 34 | 35 | LOOKUPMOD(GenderFaction, 0x043A43, "SexLab.esm"); 36 | LOOKUPMOD(AnimatingFaction, 0xE50F, "SexLab.esm"); 37 | LOOKUPMOD(ForbiddenFaction, 0x049068, "SexLab.esm"); 38 | LOOKUPMOD(ConfigQuest, 0xD62, "SexLab.esm"); 39 | 40 | return true; 41 | } 42 | } // namespace SLPP 43 | -------------------------------------------------------------------------------- /src/PCH.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #pragma warning(push) 4 | #pragma warning(disable : 4200) 5 | #include "RE/Skyrim.h" 6 | #include "SKSE/SKSE.h" 7 | #pragma warning(pop) 8 | 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | static_assert(magic_enum::is_magic_enum_supported); 17 | 18 | #pragma warning(push) 19 | #ifdef NDEBUG 20 | #include 21 | #else 22 | #include 23 | #endif 24 | #pragma warning(pop) 25 | 26 | namespace logger = SKSE::log; 27 | namespace fs = std::filesystem; 28 | using namespace std::literals; 29 | 30 | #include "GameForms.h" 31 | #include "UserData/Settings.h" 32 | #include "Util/FormLookup.h" 33 | #include "Util/Misc.h" 34 | #include "Util/Random.h" 35 | #include "Util/Singleton.h" 36 | 37 | #define ESPNAME "SexLab.esm" 38 | constexpr auto YAMLPATH{ "Data\\SKSE\\SexLab\\Settings.yaml" }; 39 | constexpr auto INIPATH{ "Data\\SKSE\\Plugins\\SexLab.ini" }; 40 | 41 | #define CONFIGPATH(path) "Data\\SKSE\\SexLab\\" path 42 | #define USER_CONFIGS(path) CONFIGPATH("UserData\\") path 43 | #define SCHLONGPATH CONFIGPATH("SchlongsOfSkyrim.yaml") 44 | #define STRIP_PATH USER_CONFIGS("Stripping.yaml") 45 | #define CUM_FX_PATH "Data/Textures/SexLab/CumFx/" 46 | 47 | #ifdef SKYRIM_SUPPORT_AE 48 | #define OFFSET(SE, AE) AE 49 | #else 50 | #define OFFSET(SE, AE) SE 51 | #endif 52 | 53 | namespace stl 54 | { 55 | using namespace SKSE::stl; 56 | 57 | constexpr std::uint32_t version_pack(REL::Version a_version) noexcept 58 | { 59 | return static_cast( 60 | (a_version[0] & 0x0FF) << 24u | 61 | (a_version[1] & 0x0FF) << 16u | 62 | (a_version[2] & 0xFFF) << 4u | 63 | (a_version[3] & 0x00F) << 0u); 64 | } 65 | 66 | template 67 | void write_thunk_call(std::uintptr_t a_src) 68 | { 69 | auto& trampoline = SKSE::GetTrampoline(); 70 | SKSE::AllocTrampoline(14); 71 | 72 | T::func = trampoline.write_call<5>(a_src, T::thunk); 73 | } 74 | 75 | template 76 | void write_vfunc() 77 | { 78 | REL::Relocation vtbl{ F::VTABLE[0] }; 79 | T::func = vtbl.write_vfunc(T::size, T::thunk); 80 | } 81 | 82 | inline bool read_string(SKSE::SerializationInterface* a_intfc, std::string& a_str) 83 | { 84 | std::size_t size = 0; 85 | if (!a_intfc->ReadRecordData(size)) { 86 | return false; 87 | } 88 | a_str.resize(size); 89 | if (!a_intfc->ReadRecordData(a_str.data(), static_cast(size))) { 90 | return false; 91 | } 92 | a_str.erase(std::find(a_str.cbegin(), a_str.cend(), '\0'), a_str.cend()); 93 | return true; 94 | } 95 | 96 | template 97 | inline bool write_string(SKSE::SerializationInterface* a_intfc, const S& a_str) 98 | { 99 | std::size_t size = a_str.length() + 1; 100 | return a_intfc->WriteRecordData(size) && a_intfc->WriteRecordData(a_str.data(), static_cast(size)); 101 | } 102 | } 103 | 104 | namespace Papyrus 105 | { 106 | #define REGISTERFUNC(func, class, dont_delay) a_vm->RegisterFunction(#func##sv, class, func, dont_delay) 107 | #define STATICARGS VM *a_vm, RE::VMStackID a_stackID, RE::StaticFunctionTag * 108 | #define QUESTARGS VM *a_vm, StackID a_stackID, [[maybe_unused]] RE::TESQuest *a_qst 109 | #define ALIASARGS VM *a_vm, StackID a_stackID, [[maybe_unused]] RE::BGSRefAlias *a_alias 110 | #define TRACESTACK(err) a_vm->TraceStack(err, a_stackID) 111 | 112 | using VM = RE::BSScript::IVirtualMachine; 113 | using StackID = RE::VMStackID; 114 | } 115 | 116 | namespace Registry 117 | { 118 | struct FixedStringCompare 119 | { 120 | bool operator()(const RE::BSFixedString& lhs, const RE::BSFixedString& rhs) const 121 | { 122 | return strcmp(lhs.data(), rhs.data()) < 0; 123 | } 124 | }; 125 | 126 | template 127 | constexpr std::vector FlagToComponents(E a_enum) 128 | { 129 | using underlying = typename std::underlying_type::type; 130 | constexpr auto iterations = sizeof(underlying) * 8; 131 | auto number = static_cast(a_enum); 132 | std::vector ret{}; 133 | for (size_t n = 0; n < iterations; n++) { 134 | size_t i = 1ULL << n; 135 | if (number & i) { 136 | ret.push_back(E(i)); 137 | } 138 | } 139 | return ret; 140 | } 141 | 142 | template 143 | constexpr size_t FlagIndex(E a_enum) 144 | { 145 | using underlying = typename std::underlying_type::type; 146 | constexpr auto iterations = sizeof(underlying) * 8; 147 | auto number = static_cast(a_enum); 148 | for (size_t n = 0; n < iterations; n++) { 149 | size_t i = 1ULL << n; 150 | if (number & i) { 151 | return n; 152 | } 153 | } 154 | return 0; 155 | } 156 | 157 | template 158 | constexpr size_t CountFlagSize() 159 | { 160 | auto max = static_cast(E::Total) - 1, ret = 0; 161 | while ((1 << ret++) < max) {} 162 | return ret; 163 | } 164 | } 165 | 166 | template <> 167 | struct std::formatter : std::formatter 168 | { 169 | template 170 | auto format(const RE::BSFixedString& myStr, FormatContext& ctx) const 171 | { 172 | return std::formatter::format(myStr.data(), ctx); 173 | } 174 | }; 175 | 176 | template <> 177 | struct std::formatter : std::formatter 178 | { 179 | template 180 | auto format(const YAML::Mark& mark, FormatContext& ctx) const 181 | { 182 | auto str = std::format("[Ln {}, Col {}]", mark.line + 1, mark.column + 1); 183 | return std::formatter::format(str, ctx); 184 | } 185 | }; 186 | 187 | #define DLLEXPORT __declspec(dllexport) 188 | -------------------------------------------------------------------------------- /src/Papyrus/Papyrus.cpp: -------------------------------------------------------------------------------- 1 | #include "Papyrus.h" 2 | 3 | #include "SexLabRegistry.h" 4 | #include "SexLabUtil.h" 5 | #include "sslActorStats.h" 6 | #include "sslLibrary/sslActorLibrary.h" 7 | #include "sslLibrary/sslThreadLibrary.h" 8 | #include "sslObject/sslAnimationSlots.h" 9 | #include "sslObject/sslCreatureAnimationSlots.h" 10 | #include "sslObject/sslExpressionSlots.h" 11 | #include "sslObject/sslVoiceSlots.h" 12 | #include "sslSystemConfig.h" 13 | #include "sslThreadModel.h" 14 | 15 | namespace Papyrus 16 | { 17 | bool Register() 18 | { 19 | const auto papyrus = SKSE::GetPapyrusInterface(); 20 | if (!papyrus) { 21 | logger::critical("Failed to get papyurs interface"); 22 | return false; 23 | } 24 | papyrus->Register(Papyrus::SexLabRegistry::Register); 25 | papyrus->Register(Papyrus::SexLabUtil::Register); 26 | papyrus->Register(Papyrus::ActorLibrary::Register); 27 | papyrus->Register(Papyrus::ActorStats::Register); 28 | papyrus->Register(Papyrus::AnimationSlots::Register); 29 | papyrus->Register(Papyrus::CreatureAnimationSlots::Register); 30 | papyrus->Register(Papyrus::ExpressionSlots::Register); 31 | papyrus->Register(Papyrus::SystemConfig::Register); 32 | papyrus->Register(Papyrus::ThreadLibrary::Register); 33 | papyrus->Register(Papyrus::ThreadModel::Register); 34 | papyrus->Register(Papyrus::VoiceSlots::Register); 35 | 36 | return true; 37 | } 38 | } // namespace Papyrus -------------------------------------------------------------------------------- /src/Papyrus/Papyrus.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus 4 | { 5 | bool Register(); 6 | } // namespace Papyrus 7 | -------------------------------------------------------------------------------- /src/Papyrus/SexLabUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::SexLabUtil 4 | { 5 | bool HasKeywordSub(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form, std::string_view a_substring) 6 | { 7 | if (!a_form) { 8 | a_vm->TraceStack("Form is none", a_stackID); 9 | return false; 10 | } 11 | const auto& kwd = a_form->As(); 12 | return kwd && kwd->ContainsKeywordString(a_substring); 13 | } 14 | RE::BSFixedString RemoveSubString(RE::StaticFunctionTag*, std::string a_str, std::string a_substring) 15 | { 16 | const auto where = a_str.find(a_substring); 17 | if (where == std::string::npos) 18 | return a_str; 19 | a_str.erase(where, a_substring.length()); 20 | return a_str; 21 | } 22 | void PrintConsole(RE::StaticFunctionTag*, std::string a_str) 23 | { 24 | Util::PrintConsole(a_str); 25 | } 26 | 27 | int IntMinMaxIndex(RE::StaticFunctionTag*, std::vector arr, bool findHighestValue) 28 | { 29 | if (arr.empty()) { 30 | return 0; 31 | } 32 | size_t w = 0; 33 | int m = 0; 34 | for (size_t i = 0; i < arr.size(); i++) { 35 | if (findHighestValue && arr[i] > m || !findHighestValue && arr[i] < m) { 36 | w = i; 37 | m = arr[i]; 38 | } 39 | } 40 | return static_cast(w); 41 | } 42 | int IntMinMaxValue(RE::StaticFunctionTag*, std::vector arr, bool findHighestValue) 43 | { 44 | if (arr.empty()) { 45 | return 0; 46 | } 47 | return arr[IntMinMaxIndex(nullptr, arr, findHighestValue)]; 48 | } 49 | int FloatMinMaxIndex(RE::StaticFunctionTag*, std::vector arr, bool findHighestValue) 50 | { 51 | if (arr.empty()) { 52 | return 0; 53 | } 54 | size_t w = 0; 55 | float m = 0; 56 | for (size_t i = 0; i < arr.size(); i++) { 57 | if (findHighestValue && arr[i] > m || !findHighestValue && arr[i] < m) { 58 | w = i; 59 | m = arr[i]; 60 | } 61 | } 62 | return static_cast(w); 63 | } 64 | float FloatMinMaxValue(RE::StaticFunctionTag*, std::vector arr, bool findHighestValue) 65 | { 66 | if (arr.empty()) { 67 | return 0; 68 | } 69 | return arr[FloatMinMaxIndex(nullptr, arr, findHighestValue)]; 70 | } 71 | std::vector MakeActorArray(RE::StaticFunctionTag*, RE::Actor* a1, RE::Actor* a2, RE::Actor* a3, RE::Actor* a4, RE::Actor* a5) 72 | { 73 | std::vector ret{}; 74 | ret.reserve(5); 75 | if (a1) 76 | ret.push_back(a1); 77 | if (a2) 78 | ret.push_back(a2); 79 | if (a3) 80 | ret.push_back(a3); 81 | if (a4) 82 | ret.push_back(a4); 83 | if (a5) 84 | ret.push_back(a5); 85 | return ret; 86 | } 87 | 88 | float GetCurrentGameRealTime(RE::StaticFunctionTag*) 89 | { 90 | constexpr auto seconds_per_day = 86400.0f; 91 | const auto calendar = RE::Calendar::GetSingleton(); 92 | const auto timescale = std::max(1, calendar->GetTimescale()); 93 | return (calendar->GetCurrentGameTime() / timescale) * seconds_per_day; 94 | } 95 | 96 | inline bool Register(VM* a_vm) 97 | { 98 | REGISTERFUNC(HasKeywordSub, "SexLabUtil", true); 99 | REGISTERFUNC(RemoveSubString, "SexLabUtil", true); 100 | REGISTERFUNC(PrintConsole, "SexLabUtil", true); 101 | REGISTERFUNC(IntMinMaxIndex, "SexLabUtil", true); 102 | REGISTERFUNC(IntMinMaxValue, "SexLabUtil", true); 103 | REGISTERFUNC(FloatMinMaxIndex, "SexLabUtil", true); 104 | REGISTERFUNC(FloatMinMaxValue, "SexLabUtil", true); 105 | REGISTERFUNC(MakeActorArray, "SexLabUtil", true); 106 | REGISTERFUNC(GetCurrentGameRealTime, "SexLabUtil", true); 107 | 108 | return true; 109 | } 110 | 111 | } // namespace Papyrus::SexLabUtil 112 | -------------------------------------------------------------------------------- /src/Papyrus/sslLibrary/LegacyData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Registry/Define/Sex.h" 4 | 5 | namespace Papyrus 6 | { 7 | enum LegacySex 8 | { 9 | Male = 0, 10 | Female, 11 | CrtMale, 12 | CrtFemale, 13 | 14 | None = -1, 15 | }; 16 | 17 | inline LegacySex GetLegacySex(RE::Actor* a_actor) 18 | { 19 | auto creature = !a_actor->IsHumanoid(); 20 | auto sex = Registry::GetSex(a_actor); 21 | switch (sex) { 22 | case Registry::Sex::None: 23 | case Registry::Sex::Male: 24 | return creature ? CrtMale : Male; 25 | case Registry::Sex::Female: 26 | case Registry::Sex::Futa: 27 | return creature ? (Settings::bCreatureGender ? CrtFemale : CrtMale) : Female; 28 | } 29 | return LegacySex::Male; 30 | } 31 | 32 | inline std::array GetLegacySex(std::vector a_positions) 33 | { 34 | std::array ret{ 0, 0, 0, 0 }; 35 | for (auto&& actor : a_positions) { 36 | auto sex = GetLegacySex(actor); 37 | ret[sex]++; 38 | } 39 | return ret; 40 | } 41 | 42 | } // namespace Papyrus 43 | -------------------------------------------------------------------------------- /src/Papyrus/sslLibrary/Serialize.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus 4 | { 5 | class Tracking : 6 | public Singleton 7 | { 8 | public: 9 | std::map> _factions; 10 | std::map> _actors; 11 | 12 | public: 13 | void Add(std::map>& a_list, RE::FormID a_keyvalue, const RE::BSFixedString& a_callback) 14 | { 15 | auto it = a_list.find(a_keyvalue); 16 | if (it == a_list.end()) { 17 | a_list.insert({ a_keyvalue, { a_callback } }); 18 | } else if (std::find(it->second.begin(), it->second.end(), a_callback) == it->second.end()) { 19 | it->second.push_back(a_callback); 20 | } 21 | } 22 | 23 | void Remove(std::map>& a_list, RE::FormID a_keyvalue, const RE::BSFixedString& a_callback) 24 | { 25 | auto it = a_list.find(a_keyvalue); 26 | if (it == a_list.end()) 27 | return; 28 | 29 | const auto where = std::remove(it->second.begin(), it->second.end(), a_callback); 30 | if (where == it->second.begin()) { 31 | a_list.erase(it); 32 | } else { 33 | it->second.erase(where, it->second.end()); 34 | } 35 | } 36 | 37 | public: 38 | void Save(SKSE::SerializationInterface* a_intfc) 39 | { 40 | const auto save = [&](const T& tosave) { 41 | const size_t numRegs = tosave.size(); 42 | if (!a_intfc->WriteRecordData(numRegs)) { 43 | logger::error("Failed to save number of regs ({})", numRegs); 44 | return; 45 | } 46 | for (auto&& [formid, list] : tosave) { 47 | a_intfc->WriteRecordData(formid); 48 | 49 | const size_t numList = list.size(); 50 | if (!a_intfc->WriteRecordData(numList)) { 51 | logger::error("Failed to save number of events ({})", numList); 52 | return; 53 | } 54 | for (auto&& event : list) { 55 | stl::write_string(a_intfc, event); 56 | } 57 | 58 | a_intfc->WriteRecordData((std::numeric_limits::max)()); 59 | } 60 | }; 61 | 62 | save(_factions); 63 | save(_actors); 64 | logger::info("Saved {} tracked factions and {} actors", _factions.size(), _actors.size()); 65 | } 66 | 67 | void Load(SKSE::SerializationInterface* a_intfc) 68 | { 69 | _factions.clear(); 70 | _actors.clear(); 71 | 72 | const auto load = [&](T& toload) { 73 | size_t numRegs; 74 | a_intfc->ReadRecordData(numRegs); 75 | 76 | for (size_t i = 0; i < numRegs; i++) { 77 | RE::FormID id, newid; 78 | a_intfc->ReadRecordData(id); 79 | if (!a_intfc->ResolveFormID(id, newid)) { 80 | while (a_intfc->ReadRecordData(id) && id != (std::numeric_limits::max)()) {} 81 | continue; 82 | } 83 | 84 | std::vector list{}; 85 | size_t numEvents; 86 | a_intfc->ReadRecordData(numEvents); 87 | list.reserve(numEvents); 88 | 89 | for (size_t n = 0; n < numEvents; n++) { 90 | std::string next; 91 | stl::read_string(a_intfc, next); 92 | list.push_back(next); 93 | } 94 | toload.insert({ newid, list }); 95 | 96 | a_intfc->ReadRecordData(id); 97 | assert(id == (std::numeric_limits::max)()); 98 | } 99 | }; 100 | 101 | load(_factions); 102 | load(_actors); 103 | logger::info("Loaded {} tracked factions and {} actors", _factions.size(), _actors.size()); 104 | } 105 | 106 | void Revert(SKSE::SerializationInterface*) 107 | { 108 | _factions.clear(); 109 | _actors.clear(); 110 | } 111 | }; 112 | 113 | } // namespace Papyrus 114 | -------------------------------------------------------------------------------- /src/Papyrus/sslLibrary/sslActorLibrary.cpp: -------------------------------------------------------------------------------- 1 | #include "sslActorLibrary.h" 2 | 3 | #include "Registry/CumFx.h" 4 | #include "Registry/Validation.h" 5 | #include "UserData/StripData.h" 6 | 7 | namespace Papyrus::ActorLibrary 8 | { 9 | int32_t ValidateActorImpl(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor) 10 | { 11 | if (!a_actor) 12 | { 13 | a_vm->TraceStack("Cannot validate a none reference", a_stackID); 14 | return -1; 15 | } 16 | const auto code = Registry::IsValidActorImpl(a_actor); 17 | return code == 0 ? -2 : code; 18 | } 19 | 20 | void WriteStrip(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form, bool a_neverstrip) 21 | { 22 | if (!a_form) { 23 | a_vm->TraceStack("Cannot write strip settings for a none reference", a_stackID); 24 | return; 25 | } 26 | const auto strip = a_neverstrip ? UserData::Strip::NoStrip : UserData::Strip::Always; 27 | UserData::StripData::GetSingleton()->AddArmor(a_form, strip); 28 | } 29 | 30 | void EraseStrip(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form) 31 | { 32 | if (!a_form) { 33 | a_vm->TraceStack("Cannot erase strip setting of a none reference", a_stackID); 34 | return; 35 | } 36 | UserData::StripData::GetSingleton()->RemoveArmor(a_form); 37 | } 38 | 39 | void EraseStripAll(RE::StaticFunctionTag*) 40 | { 41 | UserData::StripData::GetSingleton()->RemoveArmorAll(); 42 | } 43 | 44 | int32_t CheckStrip(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form) 45 | { 46 | if (!a_form) { 47 | a_vm->TraceStack("Cannot check strip settings for none reference", a_stackID); 48 | return 0; 49 | } 50 | switch (UserData::StripData::GetSingleton()->CheckStrip(a_form)) { 51 | case UserData::Strip::NoStrip: 52 | return -1; 53 | case UserData::Strip::Always: 54 | return 1; 55 | default: 56 | return 0; 57 | } 58 | } 59 | 60 | std::vector UnequipSlots(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_reference, uint32_t a_slotmask) 61 | { 62 | if (!a_reference) { 63 | a_vm->TraceStack("Cannot retrieve hdt spell from a none reference", a_stackID); 64 | return {}; 65 | } 66 | std::vector ret{}; 67 | const auto& manager = RE::ActorEquipManager::GetSingleton(); 68 | const auto cstrip = UserData::StripData::GetSingleton(); 69 | const auto& inventory = a_reference->GetInventory(); 70 | for (const auto& [form, data] : inventory) { 71 | if (form->Is(RE::FormType::LeveledItem) || !data.second->IsWorn()) { 72 | continue; 73 | } 74 | const auto strip = [&]() { 75 | manager->UnequipObject(a_reference, form); 76 | ret.push_back(form); 77 | }; 78 | switch (cstrip->CheckStrip(form)) { 79 | case UserData::Strip::NoStrip: 80 | continue; 81 | case UserData::Strip::Always: 82 | strip(); 83 | continue; 84 | } 85 | const auto& biped = form->As(); 86 | if (biped) { 87 | const auto& slots = static_cast(biped->GetSlotMask()); 88 | if (slots & a_slotmask) { 89 | strip(); 90 | continue; 91 | } 92 | } 93 | } 94 | return ret; 95 | } 96 | 97 | bool HasVehicle(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor) 98 | { 99 | if (!a_actor) { 100 | a_vm->TraceStack("Actor is none", a_stackID); 101 | return false; 102 | } 103 | return a_actor->unk1E8; 104 | } 105 | 106 | RE::BSFixedString PickRandomFxSet(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, int32_t a_type) 107 | { 108 | const auto fx = Registry::CumFx::GetSingleton(); 109 | if (a_type < 0 || a_type >= Registry::CumFx::FxType::Total) { 110 | a_vm->TraceStack("Invalid FX type", a_stackID); 111 | return ""; 112 | } 113 | const auto fxType = static_cast(a_type); 114 | const auto fxSet = fx->PickRandomFxSet(fxType); 115 | if (fxSet.empty()) { 116 | a_vm->TraceStack("FX set is empty or invalid", a_stackID); 117 | return ""; 118 | } 119 | return fxSet; 120 | } 121 | 122 | int32_t GetFxSetCount(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, int32_t a_type, RE::BSFixedString asSet) 123 | { 124 | const auto fx = Registry::CumFx::GetSingleton(); 125 | if (a_type < 0 || a_type >= Registry::CumFx::FxType::Total) { 126 | a_vm->TraceStack("Invalid FX type", a_stackID); 127 | return -1; 128 | } 129 | const auto fxType = static_cast(a_type); 130 | const auto count = fx->GetFxCount(fxType, asSet); 131 | if (count == 0) { 132 | a_vm->TraceStack("FX set is empty or invalid", a_stackID); 133 | return -1; 134 | } 135 | return count; 136 | } 137 | 138 | } // namespace Papyrus::ActorLibrary 139 | -------------------------------------------------------------------------------- /src/Papyrus/sslLibrary/sslActorLibrary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::ActorLibrary 4 | { 5 | int32_t ValidateActorImpl(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor); 6 | 7 | void WriteStrip(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form, bool a_neverstrip); 8 | void EraseStrip(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form); 9 | void EraseStripAll(RE::StaticFunctionTag*); 10 | int32_t CheckStrip(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESForm* a_form); 11 | 12 | std::vector UnequipSlots(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_reference, uint32_t a_slotmask); 13 | 14 | bool HasVehicle(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor); 15 | 16 | RE::BSFixedString PickRandomFxSet(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, int32_t a_type); 17 | int32_t GetFxSetCount(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, int32_t a_type, RE::BSFixedString asSet); 18 | 19 | inline bool Register(VM* a_vm) 20 | { 21 | REGISTERFUNC(ValidateActorImpl, "sslActorLibrary", true); 22 | 23 | REGISTERFUNC(WriteStrip, "sslActorLibrary", true); 24 | REGISTERFUNC(EraseStrip, "sslActorLibrary", true); 25 | REGISTERFUNC(EraseStripAll, "sslActorLibrary", true); 26 | REGISTERFUNC(CheckStrip, "sslActorLibrary", true); 27 | 28 | REGISTERFUNC(UnequipSlots, "sslActorLibrary", false); 29 | 30 | REGISTERFUNC(HasVehicle, "sslActorLibrary", true); 31 | 32 | REGISTERFUNC(PickRandomFxSet, "sslActorLibrary", true); 33 | REGISTERFUNC(GetFxSetCount, "sslActorLibrary", true); 34 | 35 | return true; 36 | } 37 | 38 | } // namespace Papyrus::ActorLibrary 39 | -------------------------------------------------------------------------------- /src/Papyrus/sslLibrary/sslThreadLibrary.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "LegacyData.h" 4 | 5 | namespace Papyrus::ThreadLibrary 6 | { 7 | std::vector FindBeds(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESObjectREFR* a_center, float a_radius, float a_radiusz); 8 | int32_t GetBedTypeImpl(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESObjectREFR* a_reference); 9 | bool IsBed(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESObjectREFR* a_reference); 10 | 11 | std::vector FindAvailableActors(VM* a_vm, StackID a_stackID, RE::TESQuest*, RE::TESObjectREFR* a_center, float a_radius, LegacySex a_targetsex, 12 | RE::Actor* ignore_ref01, RE::Actor* ignore_ref02, RE::Actor* ignore_ref03, RE::Actor* ignore_ref04, RE::BSFixedString a_targetrace); 13 | RE::Actor* FindAvailableActor(VM* a_vm, StackID a_stackID, RE::TESQuest*, RE::TESObjectREFR* a_center, float a_radius, LegacySex a_targetsex, 14 | RE::Actor* ignore_ref01, RE::Actor* ignore_ref02, RE::Actor* ignore_ref03, RE::Actor* ignore_ref04, RE::BSFixedString a_targetrace); 15 | RE::Actor* FindAvailableActorInFaction(VM* a_vm, StackID a_stackID, RE::TESQuest*, RE::TESFaction* a_faction, RE::TESObjectREFR* a_center, float a_radius, LegacySex a_targetsex, 16 | RE::Actor* ignore_ref01, RE::Actor* ignore_ref02, RE::Actor* ignore_ref03, RE::Actor* ignore_ref04, bool a_hasfaction, RE::BSFixedString a_targetrace, bool a_samefloor); 17 | RE::Actor* FindAvailableActorWornForm(VM* a_vm, StackID a_stackID, RE::TESQuest*, uint32_t a_slotmask, RE::TESObjectREFR* a_center, float a_radius, LegacySex a_targetsex, 18 | RE::Actor* ignore_ref01, RE::Actor* ignore_ref02, RE::Actor* ignore_ref03, RE::Actor* ignore_ref04, bool a_recognizenostrip, bool a_iswearing, RE::BSFixedString a_targetrace, 19 | bool a_samefloor); 20 | 21 | std::vector FindAvailablePartners(VM* a_vm, StackID a_stackID, RE::TESQuest*, 22 | std::vector a_positions, int a_total, int a_males, int a_females, float a_radius); 23 | std::vector FindAnimationPartnersImpl(VM* a_vm, StackID a_stackID, RE::TESQuest*, 24 | RE::BSFixedString a_sceneid, RE::TESObjectREFR* a_center, float a_radius, std::vector a_includes); 25 | 26 | std::vector SortActorsByAnimationImpl(VM* a_vm, StackID a_stackID, RE::TESQuest*, 27 | RE::BSFixedString a_sceneid, std::vector a_positions, std::vector a_submissives); 28 | 29 | bool IsActorTrackedImpl(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor); 30 | void TrackActorImpl(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor, RE::BSFixedString a_callback, bool a_dotrack); 31 | void TrackFactionImpl(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::TESFaction* a_faction, RE::BSFixedString a_callback, bool a_dotrack); 32 | std::vector GetAllTrackingEvents(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor, RE::BSFixedString a_hook); 33 | 34 | inline bool Register(VM* a_vm) 35 | { 36 | REGISTERFUNC(FindBeds, "sslThreadLibrary", false); 37 | REGISTERFUNC(GetBedTypeImpl, "sslThreadLibrary", false); 38 | REGISTERFUNC(IsBed, "sslThreadLibrary", false); 39 | 40 | REGISTERFUNC(FindAvailableActors, "sslThreadLibrary", false); 41 | REGISTERFUNC(FindAvailableActor, "sslThreadLibrary", false); 42 | REGISTERFUNC(FindAvailableActorInFaction, "sslThreadLibrary", false); 43 | REGISTERFUNC(FindAvailableActorWornForm, "sslThreadLibrary", false); 44 | 45 | REGISTERFUNC(FindAvailablePartners, "sslThreadLibrary", false); 46 | REGISTERFUNC(FindAnimationPartnersImpl, "sslThreadLibrary", false); 47 | 48 | REGISTERFUNC(SortActorsByAnimationImpl, "sslThreadLibrary", true); 49 | 50 | REGISTERFUNC(IsActorTrackedImpl, "sslThreadLibrary", true); 51 | REGISTERFUNC(TrackActorImpl, "sslThreadLibrary", true); 52 | REGISTERFUNC(TrackFactionImpl, "sslThreadLibrary", true); 53 | REGISTERFUNC(GetAllTrackingEvents, "sslThreadLibrary", true); 54 | 55 | return true; 56 | } 57 | 58 | } // namespace Papyrus::ThreadLibrary 59 | -------------------------------------------------------------------------------- /src/Papyrus/sslObject/sslAnimationSlots.cpp: -------------------------------------------------------------------------------- 1 | #include "sslAnimationSlots.h" 2 | 3 | #include "Registry/Library.h" 4 | 5 | namespace Papyrus::AnimationSlots 6 | { 7 | inline std::vector ScenesToString(const std::vector& a_scenes) 8 | { 9 | std::vector ret{}; 10 | ret.reserve(a_scenes.size()); 11 | for (auto&& scene : a_scenes) { 12 | ret.push_back(scene->id); 13 | } 14 | return ret; 15 | } 16 | 17 | std::vector GetByTagsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, int32_t a_actorcount, std::vector a_tags) 18 | { 19 | if (!a_qst) { 20 | a_vm->TraceStack("Cannot call GetByTagsImpl on a none object", a_stackID); 21 | return {}; 22 | } 23 | if (a_actorcount <= 0 || a_actorcount > Registry::ActorFragment::MAX_ACTOR_COUNT) { 24 | const auto err = std::format("Actorcount should be between 1 and {} but was {}", Registry::ActorFragment::MAX_ACTOR_COUNT, a_actorcount); 25 | a_vm->TraceStack(err.c_str(), a_stackID); 26 | return {}; 27 | } 28 | const auto lib = Registry::Library::GetSingleton(); 29 | const auto scenes = lib->GetByTags(a_actorcount, a_tags); 30 | return ScenesToString(scenes); 31 | } 32 | 33 | std::vector GetByTypeImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, 34 | int32_t a_actorcount, int32_t a_males, int32_t a_females, std::vector a_tags) 35 | { 36 | if (!a_qst) { 37 | a_vm->TraceStack("Cannot call GetByTypeImpl on a none object", a_stackID); 38 | return {}; 39 | } 40 | if (a_actorcount <= 0 || a_actorcount > Registry::ActorFragment::MAX_ACTOR_COUNT) { 41 | const auto err = std::format("Actorcount should be between 1 and {} but was {}", Registry::ActorFragment::MAX_ACTOR_COUNT, a_actorcount); 42 | a_vm->TraceStack(err.c_str(), a_stackID); 43 | return {}; 44 | } 45 | const auto lib = Registry::Library::GetSingleton(); 46 | auto scenes = lib->GetByTags(a_actorcount, a_tags); 47 | const auto size = scenes.size(); 48 | const auto end = std::remove_if(scenes.begin(), scenes.end(), [&](const Registry::Scene* a_scene) { 49 | return !a_scene->Legacy_IsCompatibleSexCount(a_males, a_females); 50 | }); 51 | scenes.erase(end, scenes.end()); 52 | logger::info("Get by type found {} scenes; {}/{} match sex arguments", size, scenes.size(), size); 53 | return ScenesToString(scenes); 54 | } 55 | 56 | std::vector PickByActorsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, std::vector a_positions, std::vector a_tags) 57 | { 58 | if (!a_qst) { 59 | a_vm->TraceStack("Cannot call PickByActorsImpl on a none object", a_stackID); 60 | return {}; 61 | } 62 | if (a_positions.empty() || std::find(a_positions.begin(), a_positions.end(), nullptr) != a_positions.end()) { 63 | a_vm->TraceStack("Cannot find scenes for a none position", a_stackID); 64 | return {}; 65 | } 66 | const auto lib = Registry::Library::GetSingleton(); 67 | std::vector vic{}; 68 | if (std::find(a_tags.begin(), a_tags.end(), "Forced") != a_tags.end()) { 69 | vic.push_back(a_positions[0]); 70 | } 71 | const auto scenes = lib->LookupScenes(a_positions, a_tags, vic); 72 | return ScenesToString(scenes); 73 | } 74 | 75 | std::vector GetAllPackages(RE::StaticFunctionTag*) 76 | { 77 | std::vector ret{}; 78 | Registry::Library::GetSingleton()->ForEachPackage([&](const Registry::AnimPackage* a_package) { 79 | ret.push_back(a_package->GetName()); 80 | return false; 81 | }); 82 | return ret; 83 | } 84 | 85 | std::vector CreateProxyArray(RE::StaticFunctionTag*, uint32_t a_returnsize, uint32_t crt_specifier, RE::BSFixedString a_tags, RE::BSFixedString a_package) 86 | { 87 | std::vector ret{}; 88 | if (a_returnsize > 0) 89 | ret.reserve(a_returnsize); 90 | auto tags = Registry::TagDetails{a_tags}; 91 | const auto lib = Registry::Library::GetSingleton(); 92 | RE::BSFixedString hash = ""; 93 | lib->ForEachPackage([&](const Registry::AnimPackage* package) { 94 | if (package->GetName() == a_package) { 95 | hash = package->GetHash(); 96 | return true; 97 | } 98 | return false; 99 | }); 100 | lib->ForEachScene([&](const Registry::Scene* a_scene) { 101 | if (crt_specifier == 0 && a_scene->HasCreatures()) 102 | return false; 103 | if (crt_specifier == 1 && !a_scene->HasCreatures()) 104 | return false; 105 | if (!hash.empty() && a_scene->GetPackageHash() != hash) 106 | return false; 107 | if (!a_scene->IsCompatibleTags(tags)) 108 | return false; 109 | ret.push_back(a_scene); 110 | return a_returnsize > 0 && ret.size() == a_returnsize; 111 | }); 112 | std::sort(ret.begin(), ret.end(), [](const Registry::Scene*& a, const auto& b) { 113 | return a->name < b->name; 114 | }); 115 | std::vector ids{}; 116 | ids.reserve(ret.size()); 117 | std::ranges::transform(ret, std::back_inserter(ids), [](const auto& it) { return it->id; }); 118 | return ids; 119 | } 120 | 121 | } // namespace Papyrus::AnimationSlots 122 | -------------------------------------------------------------------------------- /src/Papyrus/sslObject/sslAnimationSlots.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::AnimationSlots 4 | { 5 | std::vector GetByTagsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, int32_t a_actorcount, std::vector a_tags); 6 | std::vector GetByTypeImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, int32_t a_actorcount, int32_t a_males, int32_t a_females, std::vector a_tags); 7 | std::vector PickByActorsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, std::vector a_positions, std::vector a_tags); 8 | 9 | std::vector GetAllPackages(RE::StaticFunctionTag*); 10 | std::vector CreateProxyArray(RE::StaticFunctionTag*, uint32_t a_returnsize, uint32_t crt_specifier, RE::BSFixedString a_tags, RE::BSFixedString a_package); 11 | 12 | inline bool Register(VM* a_vm) 13 | { 14 | REGISTERFUNC(GetByTagsImpl, "sslAnimationSlots", true); 15 | REGISTERFUNC(GetByTypeImpl, "sslAnimationSlots", true); 16 | REGISTERFUNC(PickByActorsImpl, "sslAnimationSlots", true); 17 | 18 | REGISTERFUNC(GetAllPackages, "sslAnimationSlots", true); 19 | REGISTERFUNC(CreateProxyArray, "sslAnimationSlots", true); 20 | 21 | return true; 22 | } 23 | } // namespace Papyrus::AnimationSlots 24 | -------------------------------------------------------------------------------- /src/Papyrus/sslObject/sslCreatureAnimationSlots.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::CreatureAnimationSlots 4 | { 5 | std::vector GetByRaceKeyTagsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, 6 | int32_t a_actorcount, RE::BSFixedString a_racekey, std::vector a_tags); 7 | std::vector GetByCreatureActorsTagsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, 8 | int32_t a_actorcount, std::vector a_creatures, std::vector a_tags); 9 | std::vector GetByRaceGendersTagsImpl(VM* a_vm, StackID a_stackID, RE::TESQuest* a_qst, 10 | int32_t a_actorcount, RE::BSFixedString a_racekey, int32_t a_malecrt, int32_t a_femalecrt, std::vector a_tags); 11 | 12 | RE::TESRace* GetRaceByEditorID(RE::StaticFunctionTag*, RE::BSFixedString a_id); 13 | std::vector GetAllRaceIDs(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_racekey); 14 | std::vector GetAllRaces(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_racekey); 15 | 16 | inline bool Register(VM* a_vm) 17 | { 18 | REGISTERFUNC(GetByRaceKeyTagsImpl, "sslCreatureAnimationSlots", true); 19 | REGISTERFUNC(GetByCreatureActorsTagsImpl, "sslCreatureAnimationSlots", true); 20 | REGISTERFUNC(GetByRaceGendersTagsImpl, "sslCreatureAnimationSlots", true); 21 | 22 | REGISTERFUNC(GetRaceByEditorID, "sslCreatureAnimationSlots", true); 23 | REGISTERFUNC(GetAllRaceIDs, "sslCreatureAnimationSlots", true); 24 | REGISTERFUNC(GetAllRaces, "sslCreatureAnimationSlots", true); 25 | 26 | return true; 27 | } 28 | } // namespace Papyrus::CreatureAnimationSlots 29 | -------------------------------------------------------------------------------- /src/Papyrus/sslObject/sslExpressionSlots.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::ExpressionSlots 4 | { 5 | enum ExpressionStatus 6 | { 7 | None = 0, 8 | Submissive = 1, 9 | Dominant = 2 10 | }; 11 | 12 | namespace BaseExpression 13 | { 14 | float GetModifier(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* ActorRef, uint32_t a_id); 15 | float GetPhoneme(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* ActorRef, uint32_t a_id); 16 | float GetExpression(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* ActorRef, bool a_getid); 17 | 18 | int GetVersion(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 19 | std::vector GetExpressionTags(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 20 | void SetExpressionTags(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, std::vector a_newtags); 21 | bool GetEnabled(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 22 | void SetEnabled(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, bool a_enabled); 23 | int GetExpressionScaleMode(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 24 | void SetExpressionScaleMode(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, int a_idx); 25 | std::vector GetLevelCounts(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 26 | std::vector GetValues(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, bool a_female, float a_strength); 27 | std::vector GetNthValues(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, bool a_female, int n); 28 | void SetValues(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, bool a_female, int a_level, std::vector a_values); 29 | 30 | bool CreateEmptyProfile(RE::StaticFunctionTag*, RE::BSFixedString a_id); 31 | void SaveExpression(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 32 | 33 | inline bool Register(VM* a_vm) 34 | { 35 | REGISTERFUNC(GetModifier, "sslBaseExpression", true); 36 | REGISTERFUNC(GetPhoneme, "sslBaseExpression", true); 37 | REGISTERFUNC(GetExpression, "sslBaseExpression", true); 38 | 39 | REGISTERFUNC(GetVersion, "sslBaseExpression", true); 40 | REGISTERFUNC(GetExpressionTags, "sslBaseExpression", true); 41 | REGISTERFUNC(SetExpressionTags, "sslBaseExpression", true); 42 | REGISTERFUNC(GetEnabled, "sslBaseExpression", true); 43 | REGISTERFUNC(SetEnabled, "sslBaseExpression", true); 44 | REGISTERFUNC(GetExpressionScaleMode, "sslBaseExpression", true); 45 | REGISTERFUNC(SetExpressionScaleMode, "sslBaseExpression", true); 46 | REGISTERFUNC(GetLevelCounts, "sslBaseExpression", true); 47 | REGISTERFUNC(GetValues, "sslBaseExpression", true); 48 | REGISTERFUNC(GetNthValues, "sslBaseExpression", true); 49 | REGISTERFUNC(SetValues, "sslBaseExpression", true); 50 | 51 | REGISTERFUNC(CreateEmptyProfile, "sslBaseExpression", true); 52 | REGISTERFUNC(SaveExpression, "sslBaseExpression", true); 53 | 54 | return true; 55 | } 56 | } // namespace BaseExpression 57 | 58 | std::vector GetAllProfileIDs(RE::StaticFunctionTag*); 59 | std::vector GetExpressionsByStatus(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor, int a_status); 60 | std::vector GetExpressionsByTags(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor, std::string_view a_tags); 61 | 62 | inline bool Register(VM* a_vm) 63 | { 64 | REGISTERFUNC(GetAllProfileIDs, "sslExpressionSlots", true); 65 | REGISTERFUNC(GetExpressionsByStatus, "sslExpressionSlots", true); 66 | REGISTERFUNC(GetExpressionsByTags, "sslExpressionSlots", true); 67 | 68 | return BaseExpression::Register(a_vm); 69 | } 70 | } // namespace Papyrus::ExpressionSlots 71 | -------------------------------------------------------------------------------- /src/Papyrus/sslObject/sslVoiceSlots.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::VoiceSlots 4 | { 5 | namespace BaseVoice 6 | { 7 | bool GetEnabled(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 8 | void SetEnabled(RE::StaticFunctionTag*, RE::BSFixedString a_id, bool a_enabled); 9 | 10 | std::vector GetVoiceTags(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 11 | int GetCompatibleSex(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 12 | std::vector GetCompatibleRaces(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 13 | 14 | RE::TESSound* GetSoundObject(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, int a_strength, RE::BSFixedString a_scene, int a_idx, bool a_muffled); 15 | RE::TESSound* GetSoundObjectLeg(RE::StaticFunctionTag*, RE::BSFixedString a_id, int a_idx); 16 | RE::TESSound* GetOrgasmSound(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id, RE::BSFixedString a_scene, int a_idx, bool a_muffled); 17 | 18 | RE::BSFixedString GetDisplayName(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_id); 19 | 20 | bool InitializeVoiceObject(RE::StaticFunctionTag*, RE::BSFixedString a_id); 21 | void FinalizeVoiceObject(RE::StaticFunctionTag*, RE::BSFixedString a_id); 22 | 23 | void SetSoundObjectLeg(RE::StaticFunctionTag*, RE::BSFixedString a_id, int a_idx, RE::TESSound* a_set); 24 | void SetVoiceTags(RE::StaticFunctionTag*, RE::BSFixedString a_id, std::vector a_newtags); 25 | void SetCompatibleSex(RE::StaticFunctionTag*, RE::BSFixedString a_id, int a_set); 26 | void SetCompatibleRaces(RE::StaticFunctionTag*, RE::BSFixedString a_id, std::vector a_set); 27 | 28 | inline bool Register(VM* a_vm) 29 | { 30 | REGISTERFUNC(GetEnabled, "sslBaseVoice", true); 31 | REGISTERFUNC(SetEnabled, "sslBaseVoice", true); 32 | REGISTERFUNC(GetVoiceTags, "sslBaseVoice", true); 33 | REGISTERFUNC(GetCompatibleSex, "sslBaseVoice", true); 34 | REGISTERFUNC(GetCompatibleRaces, "sslBaseVoice", true); 35 | REGISTERFUNC(GetSoundObject, "sslBaseVoice", true); 36 | REGISTERFUNC(GetSoundObjectLeg, "sslBaseVoice", true); 37 | REGISTERFUNC(GetOrgasmSound, "sslBaseVoice", true); 38 | REGISTERFUNC(GetDisplayName, "sslBaseVoice", true); 39 | REGISTERFUNC(InitializeVoiceObject, "sslBaseVoice", true); 40 | REGISTERFUNC(FinalizeVoiceObject, "sslBaseVoice", true); 41 | REGISTERFUNC(SetSoundObjectLeg, "sslBaseVoice", true); 42 | REGISTERFUNC(SetVoiceTags, "sslBaseVoice", true); 43 | REGISTERFUNC(SetCompatibleSex, "sslBaseVoice", true); 44 | REGISTERFUNC(SetCompatibleRaces, "sslBaseVoice", true); 45 | 46 | return true; 47 | } 48 | } 49 | 50 | RE::BSFixedString SelectVoice(RE::StaticFunctionTag*, RE::Actor* a_actor); 51 | RE::BSFixedString SelectVoiceByTags(RE::StaticFunctionTag*, RE::Actor* a_actor, RE::BSFixedString a_tags); 52 | RE::BSFixedString SelectVoiceByTagsA(RE::StaticFunctionTag*, RE::Actor* a_actor, std::vector a_tags); 53 | 54 | RE::BSFixedString GetSavedVoice(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor); 55 | void StoreVoice(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor, RE::BSFixedString a_voice); 56 | void DeleteVoice(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_actor); 57 | 58 | std::vector GetAllVoices(RE::StaticFunctionTag*, RE::BSFixedString a_racekey); 59 | std::vector GetAllCachedUniqueActorsSorted(RE::StaticFunctionTag*, RE::Actor* a_sndprio); 60 | RE::BSFixedString SelectVoiceByRace(RE::StaticFunctionTag*, RE::BSFixedString a_racekey); 61 | 62 | inline bool Register(VM* a_vm) 63 | { 64 | REGISTERFUNC(SelectVoice, "sslVoiceSlots", true); 65 | REGISTERFUNC(SelectVoiceByTags, "sslVoiceSlots", true); 66 | REGISTERFUNC(SelectVoiceByTagsA, "sslVoiceSlots", true); 67 | 68 | REGISTERFUNC(GetSavedVoice, "sslVoiceSlots", true); 69 | REGISTERFUNC(StoreVoice, "sslVoiceSlots", true); 70 | REGISTERFUNC(DeleteVoice, "sslVoiceSlots", true); 71 | 72 | REGISTERFUNC(GetAllVoices, "sslVoiceSlots", true); 73 | REGISTERFUNC(GetAllCachedUniqueActorsSorted, "sslVoiceSlots", true); 74 | REGISTERFUNC(SelectVoiceByRace, "sslVoiceSlots", true); 75 | 76 | return BaseVoice::Register(a_vm); 77 | } 78 | 79 | } // namespace Papyrus::VoiceSlots 80 | -------------------------------------------------------------------------------- /src/Papyrus/sslSystemConfig.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Papyrus::SystemConfig 4 | { 5 | float GetMinSetupTime(RE::StaticFunctionTag*); 6 | 7 | int GetAnimationCount(RE::StaticFunctionTag*); 8 | std::vector GetEnjoymentFactors(RE::StaticFunctionTag*); 9 | int GetEnjoymentSettingInt(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_setting); 10 | float GetEnjoymentSettingFlt(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::BSFixedString a_setting); 11 | std::vector GetStrippableItems(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, RE::Actor* a_target, bool a_wornonly); 12 | 13 | int GetSettingInt(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting); 14 | float GetSettingFlt(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting); 15 | bool GetSettingBool(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting); 16 | std::string GetSettingStr(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting); 17 | int GetSettingIntA(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, size_t n); 18 | float GetSettingFltA(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, size_t n); 19 | 20 | void SetSettingInt(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, int a_value); 21 | void SetSettingFlt(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, float a_value); 22 | void SetSettingBool(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, bool a_value); 23 | void SetSettingStr(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, std::string a_value); 24 | void SetSettingIntA(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, int a_value, int n); 25 | void SetSettingFltA(VM* a_vm, StackID a_stackID, RE::StaticFunctionTag*, std::string a_setting, float a_value, int n); 26 | 27 | inline bool Register(VM* a_vm) 28 | { 29 | REGISTERFUNC(GetMinSetupTime, "sslSystemConfig", true); 30 | 31 | REGISTERFUNC(GetAnimationCount, "sslSystemConfig", true); 32 | REGISTERFUNC(GetEnjoymentFactors, "sslSystemConfig", true); 33 | REGISTERFUNC(GetEnjoymentSettingInt, "sslSystemConfig", true); 34 | REGISTERFUNC(GetEnjoymentSettingFlt, "sslSystemConfig", true); 35 | REGISTERFUNC(GetStrippableItems, "sslSystemConfig", true); 36 | 37 | REGISTERFUNC(GetSettingInt, "sslSystemConfig", true); 38 | REGISTERFUNC(GetSettingFlt, "sslSystemConfig", true); 39 | REGISTERFUNC(GetSettingBool, "sslSystemConfig", true); 40 | REGISTERFUNC(GetSettingStr, "sslSystemConfig", true); 41 | REGISTERFUNC(GetSettingIntA, "sslSystemConfig", true); 42 | REGISTERFUNC(GetSettingFltA, "sslSystemConfig", true); 43 | 44 | REGISTERFUNC(SetSettingInt, "sslSystemConfig", true); 45 | REGISTERFUNC(SetSettingFlt, "sslSystemConfig", true); 46 | REGISTERFUNC(SetSettingBool, "sslSystemConfig", true); 47 | REGISTERFUNC(SetSettingStr, "sslSystemConfig", true); 48 | REGISTERFUNC(SetSettingIntA, "sslSystemConfig", true); 49 | REGISTERFUNC(SetSettingFltA, "sslSystemConfig", true); 50 | 51 | return true; 52 | } 53 | 54 | } // namespace Papyrus 55 | -------------------------------------------------------------------------------- /src/Papyrus/sslThreadModel.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Registry/Define/Animation.h" 4 | 5 | namespace Papyrus::ThreadModel 6 | { 7 | enum FurniStatus 8 | { 9 | Disallow = 0, 10 | Allow = 1, 11 | Prefer = 2, 12 | }; 13 | 14 | namespace ActorAlias 15 | { 16 | enum STATUS05 17 | { 18 | Unconscious = -5, 19 | Dying = -10, 20 | }; 21 | 22 | RE::BSFixedString GetActorVoice(ALIASARGS); 23 | RE::BSFixedString GetActorExpression(ALIASARGS); 24 | void SetActorVoiceImpl(ALIASARGS, RE::BSFixedString a_voice); 25 | void SetActorExpressionImpl(ALIASARGS, RE::BSFixedString a_expression); 26 | 27 | void LockActorImpl(ALIASARGS); 28 | void UnlockActorImpl(ALIASARGS); 29 | 30 | std::vector StripByData(ALIASARGS, int32_t a_stripdata, std::vector a_defaults, std::vector a_overwrite); 31 | std::vector StripByDataEx(ALIASARGS, int32_t a_stripdata, std::vector a_defaults, std::vector a_overwrite, std::vector a_mergewith); 32 | 33 | void UpdateEnjoyment(ALIASARGS, float a_enjoyment); 34 | 35 | inline bool Register(VM* a_vm) 36 | { 37 | REGISTERFUNC(GetActorVoice, "sslActorAlias", false); 38 | REGISTERFUNC(GetActorExpression, "sslActorAlias", false); 39 | REGISTERFUNC(SetActorVoiceImpl, "sslActorAlias", false); 40 | REGISTERFUNC(SetActorExpressionImpl, "sslActorAlias", false); 41 | 42 | REGISTERFUNC(LockActorImpl, "sslActorAlias", false); 43 | REGISTERFUNC(UnlockActorImpl, "sslActorAlias", false); 44 | 45 | REGISTERFUNC(StripByData, "sslActorAlias", false); 46 | REGISTERFUNC(StripByDataEx, "sslActorAlias", false); 47 | 48 | REGISTERFUNC(UpdateEnjoyment, "sslActorAlias", false); 49 | 50 | return true; 51 | } 52 | } // namespace ActorAlias 53 | 54 | RE::BSFixedString GetActiveScene(QUESTARGS); 55 | RE::BSFixedString GetActiveStage(QUESTARGS); 56 | std::vector GetPlayingScenes(QUESTARGS); 57 | std::vector GetPositions(QUESTARGS); 58 | std::vector AddContextExImpl(RE::TESQuest*, std::vector a_oldcontext, std::string a_newcontext); 59 | 60 | void CreateInstance(QUESTARGS, std::vector a_submissives, std::vector a_scenesPrimary, std::vector a_scenesLeadIn, std::vector a_scenesCustom, int a_furniturepref); 61 | void DestroyInstance(RE::TESQuest* a_qst); 62 | std::vector GetLeadInScenes(QUESTARGS); 63 | std::vector GetPrimaryScenes(QUESTARGS); 64 | std::vector GetCustomScenes(QUESTARGS); 65 | std::vector AdvanceScene(QUESTARGS, std::vector a_history, RE::BSFixedString a_nextStage); 66 | int SelectNextStage(QUESTARGS, std::vector a_tags); 67 | bool SetActiveScene(QUESTARGS, RE::BSFixedString a_sceneid); 68 | bool ReassignCenter(QUESTARGS, RE::TESObjectREFR* a_centeron); 69 | void UpdatePlacement(QUESTARGS, RE::Actor* a_position); 70 | 71 | bool IsCollisionRegistered(QUESTARGS); 72 | void UnregisterCollision(QUESTARGS); 73 | std::vector GetCollisionActions(QUESTARGS, RE::Actor* a_position, RE::Actor* a_partner); 74 | bool HasCollisionAction(QUESTARGS, int a_type, RE::Actor* a_position, RE::Actor* a_partner); 75 | RE::Actor* GetPartnerByAction(QUESTARGS, RE::Actor* a_position, int a_type); 76 | std::vector GetPartnersByAction(QUESTARGS, RE::Actor* a_position, int a_type); 77 | RE::Actor* GetPartnerByTypeRev(QUESTARGS, RE::Actor* a_position, int a_type); 78 | std::vector GetPartnersByTypeRev(QUESTARGS, RE::Actor* a_position, int a_type); 79 | float GetActionVelocity(QUESTARGS, RE::Actor* a_position, RE::Actor* a_partner, int a_type); 80 | 81 | void AddExperience(QUESTARGS, std::vector a_positions, RE::BSFixedString a_scene, std::vector a_playedstages); 82 | void UpdateStatistics(QUESTARGS, RE::Actor* a_actor, std::vector a_positions, RE::BSFixedString a_scene, std::vector a_playedstages, float a_time); 83 | 84 | bool IsOwningSceneMenu(QUESTARGS); 85 | bool TryOpenSceneMenu(QUESTARGS); 86 | bool TryCloseSceneMenu(QUESTARGS); 87 | void TryUpdateMenuTimer(QUESTARGS, float a_time); 88 | 89 | inline bool Register(VM* a_vm) 90 | { 91 | REGISTERFUNC(GetActiveScene, "sslThreadModel", true); 92 | REGISTERFUNC(GetActiveStage, "sslThreadModel", true); 93 | REGISTERFUNC(GetPlayingScenes, "sslThreadModel", true); 94 | REGISTERFUNC(GetPositions, "sslThreadModel", true); 95 | REGISTERFUNC(AddContextExImpl, "sslThreadModel", true); 96 | 97 | REGISTERFUNC(CreateInstance, "sslThreadModel", true); 98 | REGISTERFUNC(DestroyInstance, "sslThreadModel", true); 99 | REGISTERFUNC(GetLeadInScenes, "sslThreadModel", true); 100 | REGISTERFUNC(GetPrimaryScenes, "sslThreadModel", true); 101 | REGISTERFUNC(GetCustomScenes, "sslThreadModel", true); 102 | REGISTERFUNC(AdvanceScene, "sslThreadModel", false); 103 | REGISTERFUNC(SelectNextStage, "sslThreadModel", true); 104 | REGISTERFUNC(SetActiveScene, "sslThreadModel", false); 105 | REGISTERFUNC(ReassignCenter, "sslThreadModel", false); 106 | REGISTERFUNC(UpdatePlacement, "sslThreadModel", false); 107 | 108 | REGISTERFUNC(IsCollisionRegistered, "sslThreadModel", true); 109 | REGISTERFUNC(UnregisterCollision, "sslThreadModel", true); 110 | REGISTERFUNC(GetCollisionActions, "sslThreadModel", true); 111 | REGISTERFUNC(HasCollisionAction, "sslThreadModel", true); 112 | REGISTERFUNC(GetPartnerByAction, "sslThreadModel", true); 113 | REGISTERFUNC(GetPartnersByAction, "sslThreadModel", true); 114 | REGISTERFUNC(GetPartnerByTypeRev, "sslThreadModel", true); 115 | REGISTERFUNC(GetPartnersByTypeRev, "sslThreadModel", true); 116 | REGISTERFUNC(GetActionVelocity, "sslThreadModel", true); 117 | 118 | REGISTERFUNC(AddExperience, "sslThreadModel", true); 119 | REGISTERFUNC(UpdateStatistics, "sslThreadModel", true); 120 | 121 | REGISTERFUNC(IsOwningSceneMenu, "sslThreadModel", true); 122 | REGISTERFUNC(TryOpenSceneMenu, "sslThreadModel", true); 123 | REGISTERFUNC(TryCloseSceneMenu, "sslThreadModel", true); 124 | REGISTERFUNC(TryUpdateMenuTimer, "sslThreadModel", true); 125 | 126 | return ActorAlias::Register(a_vm); 127 | } 128 | 129 | } // namespace Papyrus::ThreadModel 130 | -------------------------------------------------------------------------------- /src/Registry/CumFx.cpp: -------------------------------------------------------------------------------- 1 | #include "CumFx.h" 2 | 3 | 4 | RE::BSFixedString Registry::CumFx::PickRandomFxSet(FxType a_type) const 5 | { 6 | if (fxList[a_type].empty()) { 7 | return ""; 8 | } 9 | const auto i = Random::draw(0, fxList[a_type].size() - 1); 10 | return fxList[a_type][i].first; 11 | } 12 | 13 | uint8_t Registry::CumFx::GetFxCount(FxType a_type, RE::BSFixedString a_set) const 14 | { 15 | const auto it = std::find_if(fxList[a_type].begin(), fxList[a_type].end(), [&](const auto& pair) { 16 | return pair.first == a_set; 17 | }); 18 | if (it != fxList[a_type].end()) { 19 | return it->second; 20 | } 21 | logger::error("FX set {} not found", a_set.c_str()); 22 | return 0; 23 | } 24 | 25 | void Registry::CumFx::Initialize() 26 | { 27 | if (!fs::exists(CUM_FX_PATH) || fs::is_empty(CUM_FX_PATH)) { 28 | auto code = REX::W32::MessageBoxA(nullptr, "CumFx path is empty or does not exist. Please check your installation.\n\nExit game now?", "SexLab p+ CumFx", 0x00000004); 29 | logger::error("CumFx path is empty or does not exist: {}", CUM_FX_PATH); 30 | if (code == 6) { 31 | std::_Exit(EXIT_FAILURE); 32 | } 33 | return; 34 | } 35 | for (size_t i = 0; i < FxType::Total; i++) { 36 | const auto fxName = magic_enum::enum_name(static_cast(i)); 37 | const auto path = std::format("{}{}", CUM_FX_PATH, fxName); 38 | if (!fs::exists(path) || fs::is_empty(path)) { 39 | logger::error("FX type path does not exist or is empty: {}", path); 40 | goto AFTER_DIRECTORY_ITERATOR; 41 | } 42 | for (const auto& profileEntry : fs::directory_iterator(path)) { 43 | if (!profileEntry.is_directory()) 44 | continue; 45 | const auto typeCount = ParseType(profileEntry); 46 | if (!typeCount.has_value()) { 47 | logger::error("Failed to parse profile: {}", profileEntry.path().string()); 48 | continue; 49 | } 50 | const auto profileName = profileEntry.path().filename().string(); 51 | fxList[i].emplace_back(RE::BSFixedString(profileName), typeCount.value()); 52 | logger::info("Loaded profile: {}", profileName); 53 | } 54 | AFTER_DIRECTORY_ITERATOR: 55 | if (fxList[i].empty()) { 56 | logger::info("No profiles found for FX type: {}", fxName); 57 | if (i <= FxType::MainThree) { 58 | logger::error("Mandatory FX type {} is missing in {}", fxName, path); 59 | auto code = REX::W32::MessageBoxA(nullptr, "Mandatory Cum FX type is missing. Please check your installation.", "SexLab p+ CumFx", 0x00000004); 60 | if (code == 6) { 61 | std::_Exit(EXIT_FAILURE); 62 | } 63 | } 64 | continue; 65 | } 66 | logger::info("Loaded {} cum fx profiles of type {}", fxList[i].size(), fxName); 67 | } 68 | } 69 | 70 | std::optional Registry::CumFx::ParseType(const fs::directory_entry& a_typePath) 71 | { 72 | std::vector fxFiles; 73 | for (const auto& file : fs::directory_iterator(a_typePath.path())) { 74 | if (!file.is_regular_file() || file.path().extension() != ".dds") { 75 | logger::warn("Invalid file type: {}. Expected .dds", file.path().string()); 76 | continue; 77 | } 78 | std::string fileName = file.path().filename().string(); 79 | size_t dotPos = fileName.find_last_of('.'); 80 | std::string numberPart = fileName.substr(0, dotPos); 81 | try { 82 | size_t number = std::stoul(numberPart); 83 | if (number >= std::numeric_limits::max()) { 84 | logger::warn("File number {} exceeds maximum value of 255", number); 85 | continue; 86 | } 87 | fxFiles.push_back(static_cast(number)); 88 | } catch (const std::exception& e) { 89 | logger::warn("Invalid number in file name: {}. Error: {}", numberPart, e.what()); 90 | continue; 91 | } 92 | } 93 | if (fxFiles.empty()) { 94 | logger::error("No valid files found in directory: {}", a_typePath.path().string()); 95 | return std::nullopt; 96 | } 97 | std::sort(fxFiles.begin(), fxFiles.end()); 98 | if (fxFiles.front() != 1) { 99 | logger::error("First file number is not 1 in directory: {}", a_typePath.path().string()); 100 | return std::nullopt; 101 | } 102 | for (size_t i = 0; i < fxFiles.size(); i++) { 103 | if (fxFiles[i] != (i + 1)) { 104 | logger::error("Missing file number {} in directory: {}", (i + 1), a_typePath.path().string()); 105 | return std::nullopt; 106 | } 107 | } 108 | return static_cast(fxFiles.size()); 109 | } 110 | -------------------------------------------------------------------------------- /src/Registry/CumFx.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // TODO: Merge this into Library.h 4 | 5 | namespace Registry 6 | { 7 | class CumFx : 8 | public Singleton 9 | { 10 | using FxPair = std::pair; 11 | 12 | public: 13 | enum FxType 14 | { 15 | Vaginal = 0, 16 | Anal, 17 | Oral, 18 | 19 | MainThree = Oral, 20 | 21 | Total 22 | }; 23 | 24 | public: 25 | void Initialize(); 26 | RE::BSFixedString PickRandomFxSet(FxType a_type) const; 27 | uint8_t GetFxCount(FxType a_type, RE::BSFixedString a_set) const; 28 | 29 | private: 30 | std::optional ParseType(const fs::directory_entry& a_typePath); 31 | 32 | private: 33 | std::vector fxList[FxType::Total]; 34 | }; 35 | } // namespace Registry 36 | -------------------------------------------------------------------------------- /src/Registry/Define/Expression.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Registry/Define/Tags.h" 4 | 5 | namespace Registry 6 | { 7 | struct Expression 8 | { 9 | enum class DefaultExpression 10 | { 11 | Afraid, 12 | Angry, 13 | Happy, 14 | Joy, 15 | Pained, 16 | Pleasure, 17 | Sad, 18 | Shy 19 | }; 20 | 21 | enum class Scaling 22 | { 23 | Linear, 24 | Square, 25 | Cubic, 26 | Exponential, 27 | 28 | Total 29 | }; 30 | 31 | enum ValueType 32 | { 33 | Phoneme = 0, 34 | Modifier = 16, 35 | MoodType = 30, 36 | MoodValue = 31, 37 | 38 | Total = 32 39 | }; 40 | 41 | public: 42 | Expression(const RE::BSFixedString& a_id) : id(a_id) { assert(!a_id.empty()); }; 43 | Expression(DefaultExpression a_default); 44 | Expression(const YAML::Node& a_src); 45 | Expression(const nlohmann::json& a_src); 46 | ~Expression() = default; 47 | 48 | bool IsEnabled() const { return enabled; } 49 | RE::BSFixedString GetId() const { return id; } 50 | RE::BSFixedString GetName() const { return id; } 51 | const TagData& GetTags() const { return tags; } 52 | std::array GetData(RE::SEXES::SEX a_sex, float a_strength) const; 53 | 54 | void UpdateValues(bool a_female, int a_level, std::vector a_values); 55 | void UpdateTags(const TagData& a_newtags); 56 | void SetScaling(Expression::Scaling a_scaling); 57 | void SetEnabled(bool a_enabled); 58 | 59 | void Save(std::string_view a_fileLocation, bool force) const; 60 | 61 | public: 62 | RE::BSFixedString id; 63 | uint8_t version{ 0 }; 64 | bool enabled{ true }; 65 | mutable bool has_edits{ false }; 66 | 67 | TagData tags{}; 68 | Scaling scaling{ Scaling::Linear }; 69 | std::vector> data[RE::SEXES::kTotal]{}; 70 | }; 71 | 72 | } // namespace Registry 73 | -------------------------------------------------------------------------------- /src/Registry/Define/Fragment.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RaceKey.h" 4 | #include "Sex.h" 5 | 6 | namespace Registry 7 | { 8 | /// @brief Fragments are used to represent important data of an actor in the animation system. 9 | struct ActorFragment 10 | { 11 | static inline constexpr size_t MAX_ACTOR_COUNT = 5; 12 | static inline constexpr size_t MAX_FRAGMENT_BITS = 11; 13 | 14 | enum Value 15 | { 16 | None = 0, 17 | 18 | Male = 1 << 0, 19 | Female = 1 << 1, 20 | 21 | Human = 1 << 2, 22 | Vampire = 1 << 3, 23 | Futa = 1 << 4, 24 | // Unused = 1 << 5, 25 | // Unused = 1 << 6, 26 | // Unused = 1 << 7, 27 | // Unused = 1 << 8, 28 | 29 | CrtBit0 = 1 << 3, 30 | CrtBit1 = 1 << 4, 31 | CrtBit2 = 1 << 5, 32 | CrtBit3 = 1 << 6, 33 | CrtBit4 = 1 << 7, 34 | CrtBit5 = 1 << 8, 35 | 36 | Submissive = 1 << 9, 37 | Unconscious = 1 << 10, 38 | }; 39 | using ValueType = std::underlying_type_t; 40 | using FragmentHash = std::bitset; 41 | 42 | public: 43 | ActorFragment() = default; 44 | ActorFragment(REX::EnumSet a_sex, RaceKey a_race, float a_scale, bool a_vampire, bool a_submissive, bool a_unconscious); 45 | ActorFragment(REX::EnumSet a_value) : actor(nullptr), value(a_value) {} 46 | ActorFragment(RE::Actor* a_actor, bool a_submissive); 47 | ~ActorFragment() = default; 48 | 49 | _NODISCARD RE::Actor* GetActor() const { return actor; } 50 | _NODISCARD float GetScale() const { return scale; } 51 | _NODISCARD RaceKey GetRace() const; 52 | _NODISCARD REX::EnumSet GetSex() const; 53 | 54 | _NODISCARD bool IsValid() const { return value != Value::None; } 55 | _NODISCARD bool IsAbstract() const { return actor == nullptr; } 56 | _NODISCARD bool IsHuman() const { return value.any(Human); } 57 | _NODISCARD bool IsVampire() const { return value.all(Human, Vampire); } 58 | _NODISCARD bool IsSubmissive() const { return value.any(Submissive); }; 59 | _NODISCARD bool IsUnconscious() const { return value.any(Unconscious); }; 60 | _NODISCARD bool IsSex(Sex a_sex) const { return GetSex().all(a_sex); } 61 | _NODISCARD bool IsNotSex(Sex a_sex) const { return GetSex().none(a_sex); } 62 | 63 | /// @brief Checks how compatible the input fragment is to fill a position referenced by this fragment. 64 | /// @param a_fragment The fragment to check compatibility with. 65 | /// @return An integer representing the compatibility score. A higher score indicates better compatibility. 0 indicates no compatibility. 66 | _NODISCARD int32_t GetCompatibilityScore(const ActorFragment& a_fragment) const; 67 | 68 | /// @brief Abstract fragments may represent multiple distinct actors, so we need to split them into separate fragments. 69 | /// @return A vector of fragments, each representing a distinct data instance. 70 | _NODISCARD std::vector Split() const; 71 | 72 | public: 73 | /// @brief Creates a hash from a vector of ActorFragment objects. 74 | /// @param a_fragments A vector of ActorFragment objects to be merged into a single hash. 75 | /// @return A FragmentHash representing the combined attributes of the input fragments. 76 | /// @note The order of the fragments does not matter, as the hash is designed to be independent of the order of the input fragments. 77 | static FragmentHash MakeFragmentHash(std::vector a_fragments); 78 | 79 | /// @brief QoL function to convert a vector of actors to a vector of ActorFragment objects. 80 | /// @param a_actors A vector of RE::Actor pointers to be converted. 81 | /// @param a_submissives A vector of RE::Actor pointers representing submissive actors. 82 | /// @return A vector of ActorFragment objects representing the input actors. 83 | static std::vector MakeFragmentList(std::vector a_actors, std::vector a_submissives); 84 | 85 | public: 86 | constexpr bool operator== (const ActorFragment& a_other) const noexcept { return value == a_other.value; } 87 | constexpr bool operator< (const ActorFragment& a_other) const noexcept { return value < a_other.value; } 88 | 89 | private: 90 | RE::Actor* actor{ nullptr }; 91 | REX::EnumSet value{ Value::None }; 92 | float scale{ 1.0f }; 93 | }; 94 | 95 | } // namespace Registry 96 | -------------------------------------------------------------------------------- /src/Registry/Define/Furniture.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Transform.h" 4 | 5 | namespace Registry 6 | { 7 | struct FurnitureType 8 | { 9 | enum Value : uint32_t 10 | { 11 | None = 0, 12 | 13 | BedRoll = 1 << 0, 14 | BedSingle = 1 << 1, 15 | BedDouble = 1 << 2, 16 | 17 | Wall = 1 << 3, 18 | Railing = 1 << 4, 19 | 20 | CraftCookingPot = 1 << 5, 21 | CraftAlchemy = 1 << 6, 22 | CraftEnchanting = 1 << 7, 23 | CraftSmithing = 1 << 8, 24 | CraftAnvil = 1 << 9, 25 | CraftWorkbench = 1 << 10, 26 | CraftGrindstone = 1 << 11, 27 | 28 | Table = 1 << 12, 29 | TableCounter = 1 << 13, 30 | 31 | Chair = 1 << 14, // No arm, high back 32 | ChairCommon = 1 << 15, // Common chair 33 | ChairWood = 1 << 16, // Wooden Chair 34 | ChairBar = 1 << 17, // Bar stool 35 | ChairNoble = 1 << 18, // Noble Chair 36 | ChairMisc = 1 << 19, // Unspecified 37 | 38 | Bench = 1 << 20, // With back 39 | BenchNoble = 1 << 21, // Noble Bench (no back, with arm) 40 | BenchMisc = 1 << 20, // No specification on back or arm 41 | 42 | Throne = 1 << 22, 43 | ThroneRiften = 1 << 23, 44 | ThroneNordic = 1 << 24, 45 | // COMEBACK: These might want to be removed? 46 | XCross = 1 << 25, 47 | Pillory = 1 << 26, 48 | 49 | All = static_cast>(-1) 50 | }; 51 | 52 | public: 53 | constexpr FurnitureType() = default; 54 | constexpr FurnitureType(Value a_value) : 55 | value(a_value) {} 56 | FurnitureType(const RE::BSFixedString& a_value); 57 | 58 | _NODISCARD RE::BSFixedString ToString() const; 59 | _NODISCARD constexpr bool Is(FurnitureType::Value a_value) const { return value == a_value; } 60 | _NODISCARD constexpr bool IsBed() const { return value == Value::BedSingle || value == Value::BedDouble || value == Value::BedRoll; } 61 | _NODISCARD constexpr bool IsNone() const { return value == Value::None; } 62 | 63 | public: 64 | _NODISCARD static FurnitureType GetBedType(const RE::TESObjectREFR* a_reference); 65 | _NODISCARD static bool IsBedType(const RE::TESObjectREFR* a_reference); 66 | 67 | template 68 | _NODISCARD static constexpr std::string_view ToString() 69 | { 70 | return magic_enum::enum_name(); 71 | } 72 | 73 | public: 74 | _NODISCARD constexpr bool operator==(const FurnitureType& a_rhs) const { return value == a_rhs.value; } 75 | _NODISCARD constexpr bool operator!=(const FurnitureType& a_rhs) const { return value != a_rhs.value; } 76 | _NODISCARD constexpr bool operator<(const FurnitureType& a_rhs) const { return value < a_rhs.value; } 77 | 78 | constexpr operator Value() const { return value; } 79 | 80 | public: 81 | Value value{ Value::None }; 82 | }; 83 | 84 | struct FurnitureOffset 85 | { 86 | FurnitureType type{ FurnitureType::None }; 87 | Coordinate offset{ 0.0f, 0.0f, 0.0f, 0.0f }; 88 | }; 89 | 90 | class FurnitureDetails 91 | { 92 | public: 93 | FurnitureDetails(const YAML::Node& a_node); 94 | FurnitureDetails(FurnitureType a_type, Coordinate a_coordinate) : 95 | data({ { a_type, a_coordinate } }) {} 96 | ~FurnitureDetails() = default; 97 | 98 | std::vector GetCoordinatesInBound(RE::TESObjectREFR* a_ref, REX::EnumSet a_filter) const; 99 | std::vector GetClosestCoordinatesInBound(RE::TESObjectREFR* a_ref, REX::EnumSet a_filter, RE::TESObjectREFR* a_center) const; 100 | 101 | template 102 | bool HasType(T a_container, S a_projection) const 103 | { 104 | return std::ranges::any_of(data, a_container, [a_projection](auto&& it) { 105 | FurnitureType type = a_projection(it); 106 | return HasType(type); 107 | }); 108 | } 109 | bool HasType(FurnitureType a_type) const 110 | { 111 | return std::ranges::contains(data, a_type, [](auto&& it) { return it.type; }); 112 | } 113 | REX::EnumSet GetTypes() const 114 | { 115 | REX::EnumSet ret{ FurnitureType::None }; 116 | for (auto&& it : data) { 117 | ret.set(it.type.value); 118 | } 119 | return ret; 120 | } 121 | 122 | private: 123 | std::vector data; 124 | }; 125 | 126 | } // namespace Registry 127 | -------------------------------------------------------------------------------- /src/Registry/Define/RaceKey.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Registry 4 | { 5 | // NOTE: Canine Race can be used to animate both dog and wolf; Dog can only animate Dog; Wolf can only animate Wolf. 6 | // BoarAny can animate both BoarSingle and BoarMounted; BoarMounted can only animate BoarMounted and BoarSingle; BoarSingle can only animate BoarSingle. 7 | 8 | struct RaceKey 9 | { 10 | enum Value : uint8_t 11 | { 12 | Human = 0, 13 | AshHopper, 14 | Bear, 15 | BoarAny, 16 | BoarMounted, 17 | BoarSingle, 18 | Canine, 19 | Chaurus, 20 | ChaurusHunter, 21 | ChaurusReaper, 22 | Chicken, 23 | Cow, 24 | Deer, 25 | Dog, 26 | Dragon, 27 | DragonPriest, 28 | Draugr, 29 | DwarvenBallista, 30 | DwarvenCenturion, 31 | DwarvenSphere, 32 | DwarvenSpider, 33 | Falmer, 34 | FlameAtronach, 35 | Fox, 36 | FrostAtronach, 37 | Gargoyle, 38 | Giant, 39 | GiantSpider, 40 | Goat, 41 | Hagraven, 42 | Hare, 43 | Horker, 44 | Horse, 45 | IceWraith, 46 | LargeSpider, 47 | Lurker, 48 | Mammoth, 49 | Mudcrab, 50 | Netch, 51 | Riekling, 52 | Sabrecat, 53 | Seeker, 54 | Skeever, 55 | Slaughterfish, 56 | Spider, 57 | Spriggan, 58 | StormAtronach, 59 | Troll, 60 | VampireLord, 61 | Werewolf, 62 | Wisp, 63 | Wispmother, 64 | Wolf, 65 | 66 | None = static_cast>(-1), 67 | }; 68 | 69 | constexpr RaceKey() = default; 70 | constexpr RaceKey(Value a_value) : 71 | value(a_value) {} 72 | RaceKey(RE::Actor* a_actor); 73 | RaceKey(const RE::BSFixedString& a_raceStr); 74 | RaceKey(const RE::TESRace* a_race, RE::SEXES::SEX a_sex = RE::SEXES::SEX::kMale); 75 | 76 | _NODISCARD RaceKey GetMetaRace() const; 77 | _NODISCARD RE::BSFixedString AsString() const; 78 | _NODISCARD bool IsCompatibleWith(RaceKey a_other) const; 79 | 80 | template 81 | _NODISCARD constexpr bool IsAnyOf(T... a_values) const 82 | requires(std::same_as&&...) 83 | { 84 | return ((a_values == value) || ...); 85 | } 86 | _NODISCARD constexpr bool Is(Value a_value) const { return value == a_value; } 87 | _NODISCARD constexpr bool IsValid() const { return value != Value::None; } 88 | 89 | public: 90 | _NODISCARD static std::vector GetAllRaceKeys(bool a_ignoreAmbiguous); 91 | 92 | public: 93 | constexpr bool operator==(const RaceKey& a_rhs) const { return value == a_rhs.value; } 94 | constexpr bool operator!=(const RaceKey& a_rhs) const { return value != a_rhs.value; } 95 | constexpr bool operator<(const RaceKey& a_rhs) const { return value < a_rhs.value; } 96 | 97 | constexpr operator Value() const { return value; } 98 | 99 | public: 100 | Value value{ Value::None }; 101 | }; 102 | 103 | } // namespace Registry 104 | -------------------------------------------------------------------------------- /src/Registry/Define/Sex.cpp: -------------------------------------------------------------------------------- 1 | #include "Sex.h" 2 | 3 | #include "Util/StringUtil.h" 4 | 5 | namespace Registry 6 | { 7 | Sex GetSex(RE::Actor* a_actor, bool a_skipfactions) 8 | { 9 | Sex ret = Sex::None; 10 | if (!a_skipfactions) { 11 | a_actor->VisitFactions([&](auto a_faction, auto a_rank) { 12 | if (a_faction == GameForms::GenderFaction) { 13 | switch (a_rank) { 14 | case 0: 15 | ret = Sex::Male; 16 | break; 17 | case 1: 18 | ret = Sex::Female; 19 | break; 20 | case 2: 21 | ret = Sex::Futa; 22 | break; 23 | default: 24 | logger::info("Actor {} has invalid gender faction rank ({})", a_actor->GetFormID(), a_rank); 25 | break; 26 | } 27 | return true; 28 | } 29 | return false; 30 | }); 31 | if (ret != Sex::None) { 32 | return ret; 33 | } 34 | } 35 | 36 | const auto base = a_actor->GetActorBase(); 37 | if (!base) { 38 | logger::error("Unable to retrieve actor base for actor {:X}", a_actor->formID); 39 | return Sex::None; 40 | } 41 | switch (base->GetSex()) { 42 | default: 43 | case RE::SEXES::kMale: 44 | return Sex::Male; 45 | case RE::SEXES::kFemale: 46 | if (!a_actor->IsHumanoid()) { 47 | return Settings::bCreatureGender ? Sex::Female : Sex::Male; 48 | } 49 | return IsFuta(a_actor) ? Sex::Futa : Sex::Female; 50 | } 51 | } 52 | 53 | bool IsFuta(RE::Actor* a_actor) 54 | { 55 | static const auto tngkeyword = RE::TESForm::LookupByEditorID("TNG_SkinWithPenis"); 56 | if (tngkeyword) { 57 | if (auto skin = a_actor->GetSkin(); skin && skin->HasKeyword(tngkeyword)) { 58 | return true; 59 | } 60 | } 61 | 62 | static const auto sosfaction = RE::TESDataHandler::GetSingleton()->LookupForm(0x00AFF8, "Schlongs of Skyrim.esp"); 63 | if (sosfaction) { 64 | bool ret = false; 65 | a_actor->VisitFactions([&ret](RE::TESFaction* a_faction, int8_t a_rank) -> bool { 66 | if (!a_faction || a_rank < 0) 67 | return false; 68 | 69 | if (a_faction == sosfaction) { 70 | ret = true; 71 | return false; 72 | } else if (std::ranges::contains(Settings::SOS_ExcludeFactions, a_faction->formID)) { 73 | ret = false; 74 | return true; 75 | } else if (std::string name{ a_faction->GetFullName() }; !name.empty()) { 76 | Util::ToLower(name); 77 | if (name.find("pubic") != std::string::npos) { 78 | ret = false; 79 | return true; 80 | } 81 | } 82 | return false; 83 | }); 84 | return ret; 85 | } 86 | return false; 87 | 88 | } 89 | 90 | } // namespace Registry 91 | -------------------------------------------------------------------------------- /src/Registry/Define/Sex.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Registry 4 | { 5 | enum class PapyrusSex 6 | { 7 | None = 0, 8 | 9 | Male = 1 << 0, 10 | Female = 1 << 1, 11 | Futa = 1 << 2, 12 | CrtMale = 1 << 3, 13 | CrtFemale = 1 << 4, 14 | }; 15 | 16 | enum class Sex : uint8_t 17 | { 18 | None = 0, 19 | 20 | Male = 1 << 0, 21 | Female = 1 << 1, 22 | Futa = 1 << 2, 23 | }; 24 | 25 | enum class Sexuality : uint8_t 26 | { 27 | Hetero = 0, 28 | Homo = 1, 29 | Bi = 2, 30 | 31 | None 32 | }; 33 | 34 | /// @brief Get the (1 dimensional) sex for this actor 35 | Sex GetSex(RE::Actor* a_actor, bool a_skipfactions = false); 36 | /// @brief If this (female) actor is a futa 37 | bool IsFuta(RE::Actor* a_actor); 38 | 39 | } // namespace Registry 40 | -------------------------------------------------------------------------------- /src/Registry/Define/Tags.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Registry 4 | { 5 | enum class Tag : uint64_t 6 | { 7 | SixtyNine = 1ULL << 0, 8 | Anal = 1ULL << 1, 9 | Asphyxiation = 1ULL << 2, 10 | Blowjob = 1ULL << 3, 11 | Boobjob = 1ULL << 4, 12 | BreastSucking = 1ULL << 5, 13 | Buttjob = 1ULL << 6, 14 | Cowgirl = 1ULL << 7, 15 | Cunnilingus = 1ULL << 8, 16 | Deepthroat = 1ULL << 9, 17 | Doggy = 1ULL << 10, 18 | Dominant = 1ULL << 11, 19 | DoublePenetration = 1ULL << 12, 20 | FaceSitting = 1ULL << 13, 21 | Facial = 1ULL << 14, 22 | Feet = 1ULL << 15, 23 | Fingering = 1ULL << 16, 24 | Fisting = 1ULL << 17, 25 | Footjob = 1ULL << 18, 26 | Forced = 1ULL << 19, 27 | Grinding = 1ULL << 20, 28 | Handjob = 1ULL << 21, 29 | Humiliation = 1ULL << 22, 30 | LeadIn = 1ULL << 23, 31 | LotusPosition = 1ULL << 24, 32 | Masturbation = 1ULL << 25, 33 | Missionary = 1ULL << 26, 34 | Oral = 1ULL << 27, 35 | Penetration = 1ULL << 28, 36 | ProneBone = 1ULL << 29, 37 | ReverseCowgirl = 1ULL << 30, 38 | ReverseSpitroast = 1ULL << 31, 39 | Rimming = 1ULL << 32, 40 | Spanking = 1ULL << 33, 41 | Spitroast = 1ULL << 34, 42 | Teasing = 1ULL << 35, 43 | Toys = 1ULL << 36, 44 | Tribadism = 1ULL << 37, 45 | TriplePenetration = 1ULL << 38, 46 | Vaginal = 1ULL << 39, 47 | 48 | Behind = 1ULL << 40, 49 | Facing = 1ULL << 41, 50 | Holding = 1ULL << 42, 51 | Hugging = 1ULL << 43, 52 | Kissing = 1ULL << 44, 53 | Kneeling = 1ULL << 45, 54 | Loving = 1ULL << 46, 55 | Lying = 1ULL << 47, 56 | Magic = 1ULL << 48, 57 | Sitting = 1ULL << 49, 58 | Spooning = 1ULL << 50, 59 | Standing = 1ULL << 51, 60 | 61 | Ryona = 1ULL << 52, 62 | Gore = 1ULL << 53, 63 | Oviposition = 1ULL << 54, 64 | }; 65 | 66 | class TagData 67 | { 68 | public: 69 | template 70 | TagData(const std::vector& a_tags) 71 | { 72 | for (auto&& it : a_tags) { 73 | AddTag(it); 74 | } 75 | } 76 | TagData(std::ifstream& a_stream); 77 | TagData() = default; 78 | ~TagData() = default; 79 | 80 | public: 81 | /// @brief Add (all of) the arguments tags to this 82 | void AddTag(Tag a_tag); 83 | void AddTag(const TagData& a_tag); 84 | void AddTag(RE::BSFixedString a_tag); 85 | 86 | /// @brief Remove (all of) the arguments tags from this 87 | void RemoveTag(Tag a_tag); 88 | void RemoveTag(const TagData& a_tag); 89 | void RemoveTag(const RE::BSFixedString& a_tag); 90 | 91 | /// @brief If this has (all of) the arguments tags 92 | _NODISCARD bool HasTag(Tag a_tag) const; 93 | _NODISCARD bool HasTag(const RE::BSFixedString& a_tag) const; 94 | 95 | /// @brief Checks if this has any or all of the arguments tags 96 | _NODISCARD bool HasTags(const TagData& a_tag, bool a_all) const; 97 | _NODISCARD uint32_t CountTags(const TagData& a_tag) const; 98 | 99 | /// @brief If this data contains any tags 100 | _NODISCARD bool IsEmpty() const; 101 | 102 | public: 103 | bool HasAnnotation(const RE::BSFixedString& a_tag) const; 104 | void AddAnnotation(RE::BSFixedString a_tag); 105 | void RemoveAnnotation(const RE::BSFixedString& a_tag); 106 | void SetAnnotations(const std::vector& a_tags) { _annotations = a_tags; } 107 | 108 | /// @brief Get the annotated (editable) tags 109 | std::vector& GetAnnotations() { return _annotations; } 110 | const std::vector& GetAnnotations() const { return _annotations; } 111 | 112 | public: 113 | /// @brief visitor returns true to stop cycling 114 | void ForEachExtra(std::function a_visitor) const; 115 | 116 | /// @brief get all tags in this data in a single vector 117 | std::vector AsVector() const; 118 | 119 | private: 120 | void AddExtraTag(const RE::BSFixedString& a_tag); 121 | void RemoveExtraTag(const RE::BSFixedString& a_tag); 122 | bool HasExtraTag(const RE::BSFixedString& a_tag) const; 123 | 124 | stl::enumeration _basetags; 125 | std::vector _extratags; 126 | std::vector _annotations; 127 | }; 128 | 129 | class TagDetails 130 | { 131 | public: 132 | enum TagType 133 | { 134 | Required = 0, 135 | Disallow, 136 | Optional, 137 | 138 | Total 139 | }; 140 | 141 | public: 142 | TagDetails(const std::string_view a_tags); 143 | TagDetails(const std::vector a_tags); 144 | TagDetails(const std::array a_tags); 145 | ~TagDetails() = default; 146 | 147 | /// @brief If the given tag data matches all of the this's tags 148 | _NODISCARD bool MatchTags(const TagData& a_data) const; 149 | 150 | private: 151 | TagData _tags[TagType::Total]; 152 | }; 153 | 154 | } // namespace Registry 155 | -------------------------------------------------------------------------------- /src/Registry/Define/Transform.cpp: -------------------------------------------------------------------------------- 1 | #include "Transform.h" 2 | 3 | #include 4 | 5 | #include "Registry/Util/Decode.h" 6 | 7 | namespace Registry 8 | { 9 | Coordinate::Coordinate(const RE::TESObjectREFR* a_ref) : 10 | location(a_ref->data.location.x, a_ref->data.location.y, a_ref->data.location.z), rotation(a_ref->data.angle.z) {} 11 | Coordinate::Coordinate(const RE::NiPoint3& a_point, float a_rotation) : 12 | location(a_point.x, a_point.y, a_point.z), rotation(a_rotation) {} 13 | Coordinate::Coordinate(float a_x, float a_y, float a_z, float a_rotation) : 14 | location(a_x, a_y, a_z), rotation(a_rotation) {} 15 | Coordinate::Coordinate(const std::vector& a_coordinates) : 16 | location(glm::vec3{ a_coordinates[0], a_coordinates[1], a_coordinates[2] }), rotation(a_coordinates[3]) {} 17 | Coordinate::Coordinate(std::ifstream& a_stream) : 18 | location([&]() { 19 | glm::vec3 ret{}; 20 | Decode::Read(a_stream, ret.x); 21 | Decode::Read(a_stream, ret.y); 22 | Decode::Read(a_stream, ret.z); 23 | return ret; 24 | }()), 25 | rotation(Decode::Read(a_stream)) {} 26 | 27 | void Coordinate::Apply(Coordinate& a_coordinate) const 28 | { 29 | const float cosAngle = std::cos(-a_coordinate.rotation); 30 | const float sinAngle = std::sin(-a_coordinate.rotation); 31 | const glm::vec3 transform{ 32 | location.x * cosAngle - location.y * sinAngle, 33 | location.x * sinAngle + location.y * cosAngle, 34 | location.z 35 | }; 36 | a_coordinate.location += transform; 37 | a_coordinate.rotation += rotation; 38 | } 39 | 40 | Coordinate Coordinate::ApplyReturn(const Coordinate& a_coordinate) const 41 | { 42 | Coordinate ret{ a_coordinate }; 43 | return (Apply(ret), ret); 44 | } 45 | 46 | Transform::Transform(const Coordinate& a_rawoffset) : 47 | _raw(a_rawoffset), _offset(a_rawoffset) {} 48 | 49 | Transform::Transform(std::ifstream& a_binarystream) : 50 | _raw(a_binarystream), _offset(_raw) {} 51 | 52 | const Coordinate& Transform::GetRawOffset() const 53 | { 54 | return _raw; 55 | } 56 | 57 | const Coordinate& Transform::GetOffset() const 58 | { 59 | return _offset; 60 | } 61 | 62 | float Transform::GetOffset(CoordinateType a_type) const 63 | { 64 | switch (a_type) { 65 | case CoordinateType::X: 66 | return _offset.location.x; 67 | case CoordinateType::Y: 68 | return _offset.location.y; 69 | case CoordinateType::Z: 70 | return _offset.location.z; 71 | case CoordinateType::R: 72 | return _offset.rotation; 73 | } 74 | logger::error("Invalid offset type: {}", std::to_underlying(a_type)); 75 | return 0.0f; 76 | } 77 | 78 | void Transform::SetOffset(const Coordinate& a_newoffset) 79 | { 80 | _offset = a_newoffset; 81 | } 82 | 83 | void Transform::SetOffset(float x, float y, float z, float rot) 84 | { 85 | _offset.location = { x, y, z }; 86 | _offset.rotation = glm::radians(rot); 87 | } 88 | 89 | void Transform::SetOffset(float a_value, CoordinateType a_type) 90 | { 91 | switch (a_type) { 92 | case CoordinateType::X: 93 | _offset.location.x = a_value; 94 | break; 95 | case CoordinateType::Y: 96 | _offset.location.y = a_value; 97 | break; 98 | case CoordinateType::Z: 99 | _offset.location.z = a_value; 100 | break; 101 | case CoordinateType::R: 102 | _offset.rotation = glm::radians(a_value); 103 | break; 104 | default: 105 | logger::error("Invalid offset type: {}", std::to_underlying(a_type)); 106 | break; 107 | } 108 | } 109 | 110 | void Transform::ResetOffset() 111 | { 112 | _offset = _raw; 113 | } 114 | 115 | Coordinate Transform::ApplyReturn(const Coordinate& a_coordinate) const 116 | { 117 | Coordinate ret{ a_coordinate }; 118 | Apply(ret); 119 | return ret; 120 | } 121 | 122 | void Transform::Save(YAML::Node& a_node) const 123 | { 124 | auto loc = a_node["Location"]; 125 | loc[0] = _offset.location.x; 126 | loc[1] = _offset.location.y; 127 | loc[2] = _offset.location.z; 128 | 129 | a_node["Rotation"] = _offset.rotation; 130 | } 131 | 132 | void Transform::Load(const YAML::Node& a_node) 133 | { 134 | if (auto loc = a_node["Location"]; loc.IsDefined() && loc.size() == 3) { 135 | _offset.location.x = loc[0].as(); 136 | _offset.location.y = loc[1].as(); 137 | _offset.location.z = loc[2].as(); 138 | } 139 | if (auto rot = a_node["Rotation"]; rot.IsDefined()) { 140 | _offset.rotation = rot.as(); 141 | } 142 | } 143 | 144 | bool Transform::HasChanges() const 145 | { 146 | return _offset != _raw; 147 | } 148 | 149 | } 150 | -------------------------------------------------------------------------------- /src/Registry/Define/Transform.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Registry 4 | { 5 | enum CoordinateType : uint8_t 6 | { 7 | X = 0, 8 | Y, 9 | Z, 10 | R, 11 | 12 | Total 13 | }; 14 | 15 | struct Coordinate 16 | { 17 | Coordinate() = default; 18 | Coordinate(const RE::TESObjectREFR* a_ref); 19 | Coordinate(const RE::NiPoint3& a_point, float a_rotation); 20 | Coordinate(float a_x, float a_y, float a_z, float a_rotation); 21 | Coordinate(const std::vector& a_coordinates); 22 | Coordinate(std::ifstream& a_stream); 23 | ~Coordinate() = default; 24 | 25 | void Apply(Coordinate& a_coordinate) const; 26 | Coordinate ApplyReturn(const Coordinate& a_coordinate) const; 27 | 28 | RE::NiPoint3 AsNiPoint() const { return { location.x, location.y, location.z }; } 29 | glm::vec4 AsVec4(float w = 0.0f) const { return { location.x, location.y, location.z, w }; } 30 | std::vector AsVector() const { return { location.x, location.y, location.z, rotation }; } 31 | float GetDistance(const Coordinate& a_other) const { return glm::distance(location, a_other.location); } 32 | 33 | public: 34 | bool operator==(const Coordinate& a_rhs) const { return location == a_rhs.location && rotation == a_rhs.rotation; } 35 | 36 | public: 37 | glm::vec3 location; 38 | float rotation; 39 | }; 40 | 41 | class Transform 42 | { 43 | public: 44 | Transform(const Coordinate& a_rawcoordinates); 45 | Transform(std::ifstream& a_binarystream); 46 | Transform() = default; 47 | ~Transform() = default; 48 | 49 | public: 50 | void Apply(Coordinate& a_coordinate) const { _offset.Apply(a_coordinate); } 51 | Coordinate ApplyReturn(const Coordinate& a_coordinate) const; 52 | bool HasChanges() const; 53 | 54 | const Coordinate& GetRawOffset() const; 55 | const Coordinate& GetOffset() const; 56 | float GetOffset(CoordinateType a_type) const; 57 | void SetOffset(float a_value, CoordinateType a_type); 58 | void SetOffset(const Coordinate& a_coordinate); 59 | void SetOffset(float x, float y, float z, float rot); 60 | void ResetOffset(); 61 | 62 | void Save(YAML::Node& a_node) const; 63 | void Load(const YAML::Node& a_node); 64 | 65 | private: 66 | Coordinate _raw; 67 | Coordinate _offset; 68 | }; 69 | 70 | } // namespace Registry 71 | -------------------------------------------------------------------------------- /src/Registry/Define/Voice.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Animation.h" 6 | #include "RaceKey.h" 7 | 8 | namespace Registry 9 | { 10 | enum class LegacyVoice 11 | { 12 | Hot, 13 | Mild, 14 | Medium 15 | }; 16 | 17 | enum class VoiceAnnotation 18 | { 19 | None = 0, 20 | Submissive = 1 << 0, 21 | Dominant = 1 << 1, 22 | Muffled = 1 << 7, 23 | }; 24 | 25 | enum class Pitch 26 | { 27 | Unknown, 28 | Normal, 29 | High, 30 | Low, 31 | }; 32 | 33 | struct VoiceSet 34 | { 35 | VoiceSet(const YAML::Node& a_node); 36 | VoiceSet(bool a_aslegacyextra); 37 | ~VoiceSet() = default; 38 | 39 | bool IsValid(REX::EnumSet a_annotations) const { return annotations == a_annotations; } 40 | RE::TESSound* GetOrgasm() const { return orgasm; } 41 | RE::TESSound* Get(uint32_t a_excitement) const; 42 | RE::TESSound* Get(LegacyVoice a_setting) const; 43 | 44 | public: 45 | void SetSound(bool front, RE::TESSound* a_sound); 46 | YAML::Node AsYaml() const; 47 | 48 | private: 49 | RE::TESSound* orgasm{ nullptr }; 50 | std::vector> data{}; 51 | REX::EnumSet annotations{ VoiceAnnotation::None }; 52 | }; 53 | 54 | class Voice 55 | { 56 | public: 57 | Voice(const YAML::Node& a_node); 58 | Voice(RE::BSFixedString a_name) : 59 | name(a_name), defaultset(false), extrasets({ { true } }) {} 60 | ~Voice() = default; 61 | 62 | RE::BSFixedString GetId() const { return name; } 63 | RE::BSFixedString GetDisplayName() const { return displayName.empty() ? name : displayName; } 64 | bool HasRace(RaceKey a_race) const 65 | { 66 | return std::ranges::find_if(races, [&](const auto& rk) { return a_race.IsCompatibleWith(rk); }) != races.end(); 67 | } 68 | const VoiceSet& GetApplicableSet(REX::EnumSet a_annotation) const; 69 | 70 | RE::TESSound* PickSound(LegacyVoice a_legacysetting) const; 71 | RE::TESSound* PickSound(uint32_t a_excitement, REX::EnumSet a_annotation) const; 72 | RE::TESSound* PickOrgasmSound(REX::EnumSet a_annotation) const; 73 | 74 | public: 75 | void SaveToFile(std::string_view a_fileLocation) const; 76 | void Save(YAML::Node& a_node) const; 77 | void Load(const YAML::Node& a_node); 78 | 79 | public: 80 | bool operator==(const Voice& a_rhs) const noexcept { return name == a_rhs.name; } 81 | bool operator!=(const Voice& a_rhs) const noexcept { return name != a_rhs.name; } 82 | bool operator<(const Voice& a_rhs) const noexcept { return strcmp(name.data(), a_rhs.name.data()) < 0; } 83 | 84 | public: 85 | RE::BSFixedString name; 86 | RE::BSFixedString displayName; 87 | bool enabled{ true }; 88 | 89 | TagData tags{}; 90 | RE::SEXES::SEX sex{ RE::SEXES::SEX::kNone }; 91 | std::vector races{}; 92 | Pitch pitch{ Pitch::Unknown }; 93 | 94 | VoiceSet defaultset; 95 | std::vector extrasets{}; 96 | }; 97 | 98 | struct VoicePitch 99 | { 100 | VoicePitch(const Voice* a_voice) : 101 | voice(a_voice) {} 102 | VoicePitch(Pitch a_pitch) : 103 | pitch(a_pitch) {} 104 | ~VoicePitch() = default; 105 | 106 | Pitch pitch{ Pitch::Unknown }; 107 | const Voice* voice{ nullptr }; 108 | }; 109 | 110 | } // namespace Registry::Voice 111 | -------------------------------------------------------------------------------- /src/Registry/Stats.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | #include "Registry/Define/Sex.h" 6 | #include "Registry/Define/RaceKey.h" 7 | 8 | namespace Registry::Statistics 9 | { 10 | struct ActorStats 11 | { 12 | enum StatisticID 13 | { 14 | LastUpdate_GameTime, 15 | SecondsInScene, 16 | 17 | XP_Vaginal, 18 | XP_Anal, 19 | XP_Oral, 20 | 21 | PartnersMale, 22 | PartnersFemale, 23 | PartnersFuta, 24 | PartnersCreature, 25 | TimesOral, 26 | TimesVaginal, 27 | TimesAnal, 28 | TimesMasturbated, 29 | TimesSubmissive, 30 | TimesDominant, 31 | TimesTotal, 32 | 33 | Sexuality, 34 | Arousal, 35 | 36 | Total 37 | }; 38 | ActorStats(SKSE::SerializationInterface* a_intfc); 39 | ActorStats(RE::Actor* owner); 40 | ~ActorStats() = default; 41 | 42 | void SetStatistic(StatisticID key, float value); 43 | void AddStatistic(StatisticID key, float value); 44 | float GetStatistic(StatisticID key) const; 45 | std::vector GetEveryCustomID() const; 46 | 47 | bool HasCustom(const RE::BSFixedString& key) const; 48 | std::optional GetCustomFlt(const RE::BSFixedString& key) const; 49 | std::optional GetCustomStr(const RE::BSFixedString& key) const; 50 | void SetCustomFlt(const RE::BSFixedString& key, float value); 51 | void SetCustomStr(const RE::BSFixedString& key, RE::BSFixedString value); 52 | void RemoveCustomStat(const RE::BSFixedString& key); 53 | 54 | void Save(SKSE::SerializationInterface* a_intfc); 55 | 56 | private: 57 | template 58 | std::optional GetCustom(const RE::BSFixedString& key) const 59 | { 60 | const auto it = _custom.find(key); 61 | if (it == _custom.end()) 62 | return std::nullopt; 63 | 64 | auto& ret = it->second; 65 | if (!std::holds_alternative(ret)) 66 | return std::nullopt; 67 | 68 | return std::get(ret); 69 | } 70 | 71 | std::vector _stats{}; 72 | std::map, FixedStringCompare> _custom{}; 73 | }; 74 | 75 | struct ActorEncounter 76 | { 77 | /// Types goes id1 -> id2. E.g. if Victim, then id1 = Victim => id2 = aggressor 78 | enum class EncounterType 79 | { 80 | Any = 0, 81 | Victim = 1, 82 | Aggressor = 2, 83 | Submissive = 3, 84 | Dominant = 4 85 | }; 86 | 87 | struct EncounterObj 88 | { 89 | EncounterObj(RE::Actor* obj); 90 | EncounterObj(SKSE::SerializationInterface* a_intfc); 91 | 92 | void Save(SKSE::SerializationInterface* a_intfc); 93 | 94 | RE::FormID id; 95 | RaceKey race; 96 | Sex sex; 97 | }; 98 | 99 | public: 100 | ActorEncounter(RE::Actor* fst, RE::Actor* snd, EncounterType a_type); 101 | ActorEncounter(SKSE::SerializationInterface* a_intfc); 102 | ~ActorEncounter() = default; 103 | 104 | void Update(EncounterType a_type); 105 | 106 | std::pair GetParticipants() const { return { npc1, npc2 }; } 107 | const ActorEncounter::EncounterObj* GetPartner(RE::Actor* a_actor) const; 108 | float GetLastTimeMet() const { return _lastmet; } 109 | uint8_t GetTimesMet() const { return _timesmet; } 110 | uint8_t GetTimesSubmissive(RE::FormID a_id) const; 111 | uint8_t GetTimesDominant(RE::FormID a_id) const; 112 | uint8_t GetTimesVictim(RE::FormID a_id) const; 113 | uint8_t GetTimesAssailant(RE::FormID a_id) const; 114 | 115 | void Save(SKSE::SerializationInterface* a_intfc); 116 | 117 | private: 118 | EncounterObj npc1; 119 | EncounterObj npc2; 120 | 121 | float _lastmet; 122 | uint8_t _timesmet; 123 | uint8_t _timessubmissive; 124 | uint8_t _timesdominant; 125 | uint8_t _timesvictim; 126 | uint8_t _timesaggressor; 127 | }; 128 | 129 | class StatisticsData : 130 | public Singleton, 131 | public RE::BSTEventSink, 132 | public RE::BSTEventSink 133 | { 134 | using EventResult = RE::BSEventNotifyControl; 135 | 136 | public: 137 | std::vector GetTrackedActors() const; 138 | ActorStats& GetStatistics(RE::Actor* a_key); 139 | ActorEncounter* GetEncounter(RE::Actor* fst, RE::Actor* snd); 140 | std::vector::iterator GetEncounterIter(RE::Actor* fst, RE::Actor* snd); 141 | void DeleteStatistics(RE::FormID a_key); 142 | 143 | bool ForEachStatistic(std::function a_func); 144 | bool ForEachEncounter(std::function a_func); 145 | 146 | void AddEncounter(RE::Actor* fst, RE::Actor* snd, ActorEncounter::EncounterType a_type); 147 | RE::Actor* GetMostRecentEncounter(RE::Actor* a_actor, ActorEncounter::EncounterType a_type); 148 | 149 | int GetNumberEncounters(RE::Actor* a_actor); 150 | int GetNumberEncounters(RE::Actor* a_actor, ActorEncounter::EncounterType a_type); 151 | int GetNumberEncounters(RE::Actor* a_actor, std::function a_pred); 152 | int GetNumberEncounters(RE::Actor* a_actor, ActorEncounter::EncounterType a_type, std::function a_pred); 153 | 154 | EventResult ProcessEvent(const RE::TESDeathEvent* a_event, RE::BSTEventSource*) override; 155 | EventResult ProcessEvent(const RE::TESResetEvent* a_event, RE::BSTEventSource*) override; 156 | 157 | void Register(); 158 | void Save(SKSE::SerializationInterface* a_intfc); 159 | void Load(SKSE::SerializationInterface* a_intfc); 160 | void Revert(SKSE::SerializationInterface* a_intfc); 161 | 162 | private: 163 | mutable std::shared_mutex _m{}; 164 | std::vector _encounters; 165 | std::map _data; 166 | }; 167 | 168 | } // namespace Registry::Statistics 169 | -------------------------------------------------------------------------------- /src/Registry/Util/Decode.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | namespace Decode 9 | { 10 | static inline constexpr size_t HASH_SIZE = 4; 11 | static inline constexpr size_t ID_SIZE = 8; 12 | 13 | template ::value, bool> = true> 14 | void Read(std::ifstream& a_stream, I& a_out) 15 | { 16 | constexpr size_t n = sizeof(I); 17 | uint8_t buffer[n]; 18 | a_stream.read(reinterpret_cast(buffer), n); 19 | a_out = 0; 20 | for (int32_t i = 0; i < n; i++) { 21 | a_out = (a_out << 8) | buffer[i]; 22 | } 23 | } 24 | 25 | template ::value, bool> = true> 26 | void Read(std::ifstream& a_stream, F& a_out) 27 | { 28 | int32_t tmp; 29 | Read(a_stream, tmp); 30 | a_out = static_cast(tmp) / 1000.0f; 31 | } 32 | 33 | template || std::is_same_v, bool> = true> 34 | void Read(std::ifstream& a_stream, S& a_out) 35 | { 36 | uint64_t u64; 37 | Read(a_stream, u64); 38 | std::vector buffer; 39 | buffer.resize(u64); 40 | a_stream.read(buffer.data(), u64); 41 | a_out = std::string{ buffer.begin(), buffer.end() }; 42 | } 43 | 44 | template 45 | T Read(std::ifstream& a_stream) 46 | { 47 | T ret; 48 | Read(a_stream, ret); 49 | return ret; 50 | } 51 | 52 | } // namespace Decode 53 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | // https://gitlab.com/Shrimperator/skyrim-mod-betterthirdpersonselection/-/blob/main/src/SmoothCamStuff/RayCast.h?ref_type=heads 4 | 5 | /* From SmoothCam */ 6 | namespace Raycast 7 | { 8 | struct hkpGenericShapeData 9 | { 10 | intptr_t* unk; 11 | uint32_t shapeType; 12 | }; 13 | 14 | struct rayHitShapeInfo 15 | { 16 | hkpGenericShapeData* hitShape; 17 | uint32_t unk; 18 | }; 19 | 20 | typedef __declspec(align(16)) struct bhkRayResult 21 | { 22 | // The actual param given to the engine 23 | union 24 | { 25 | uint32_t data[16]; 26 | struct 27 | { 28 | // Might be surface normal 29 | glm::vec4 rayUnkPos; 30 | // The normal vector of the ray 31 | glm::vec4 rayNormal; 32 | 33 | rayHitShapeInfo colA; 34 | rayHitShapeInfo colB; 35 | }; 36 | }; 37 | 38 | // Custom utility data, not part of the game 39 | 40 | // True if the trace hit something before reaching it's end position 41 | bool hit; 42 | // If the ray hit an object, this will point to it 43 | RE::NiAVObject* hitObject; 44 | // The length of the ray from start to hitPos 45 | float rayLength; 46 | // The position the ray hit, in world space 47 | glm::vec4 hitPos; 48 | 49 | // pad to 128 50 | uint64_t _pad; 51 | 52 | bhkRayResult() noexcept : 53 | hit(false), hitObject(nullptr) {} 54 | } RayResult; 55 | //static_assert(sizeof(RayResult) == 128); 56 | 57 | // Cast a ray from 'start' to 'end', returning the first thing it hits 58 | // This variant is used by the camera to test with the world for clipping 59 | // Params: 60 | // glm::vec4 start: Starting position for the trace in world space 61 | // glm::vec4 end: End position for the trace in world space 62 | // float traceHullSize: Size of the collision hull used for the trace 63 | // 64 | // Returns: 65 | // RayResult: 66 | // A structure holding the results of the ray cast. 67 | // If the ray hit something, result.hit will be true. 68 | RayResult CastRay(glm::vec4 start, glm::vec4 end, float traceHullSize) noexcept; 69 | 70 | // Cast a ray from 'start' to 'end', returning the first thing it hits 71 | // This variant collides with pretty much any solid geometry 72 | // Params: 73 | // glm::vec4 start: Starting position for the trace in world space 74 | // glm::vec4 end: End position for the trace in world space 75 | // 76 | // Returns: 77 | // RayResult: 78 | // A structure holding the results of the ray cast. 79 | // If the ray hit something, result.hit will be true. 80 | RayResult hkpCastRay(const glm::vec4& start, const glm::vec4& end) noexcept; 81 | RayResult hkpCastRay(const glm::vec4& start, const glm::vec4& end, std::initializer_list a_filter) noexcept; 82 | RayResult hkpCastRay(const glm::vec4& start, const glm::vec4& end, const std::vector& a_filter) noexcept; 83 | } 84 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/Credits.md: -------------------------------------------------------------------------------- 1 | Code in this folder and `RayCast.h` is taken or strongly inspired from `Better Third Person Selection` 2 | https://gitlab.com/Shrimperator/skyrim-mod-betterthirdpersonselection 3 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/Math.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #define GLM_ENABLE_EXPERIMENTAL 4 | 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | 11 | // values smaller / larger than this will not be counted as valid by IsValid 12 | inline constexpr float POSITIVE_INVALID_THRESHHOLD = 999999.0f; 13 | inline constexpr float NEGATIVE_INVALID_THRESHHOLD = -999999.0f; 14 | 15 | namespace Misc::Math 16 | { 17 | glm::vec3 QuatToEuler(glm::quat q) 18 | { 19 | auto matrix = glm::toMat4(q); 20 | 21 | glm::vec3 rotOut; 22 | glm::extractEulerAngleXYZ(matrix, rotOut.x, rotOut.y, rotOut.z); 23 | 24 | return rotOut; 25 | } 26 | 27 | glm::vec3 RotMatrixToEuler(RE::NiMatrix3 matrixIn) 28 | { 29 | auto ent = matrixIn.entry; 30 | auto rotMat = glm::mat4( 31 | { ent[0][0], ent[1][0], ent[2][0], 32 | ent[0][1], ent[1][1], ent[2][1], 33 | ent[0][2], ent[1][2], ent[2][2] }); 34 | 35 | glm::vec3 rotOut; 36 | glm::extractEulerAngleXYZ(rotMat, rotOut.x, rotOut.y, rotOut.z); 37 | 38 | return rotOut; 39 | } 40 | 41 | bool IsValid(float numIn) 42 | { 43 | return !(numIn < NEGATIVE_INVALID_THRESHHOLD || 44 | numIn > POSITIVE_INVALID_THRESHHOLD || 45 | std::isinf(numIn) || 46 | std::isinf(-numIn) || 47 | std::isnan(numIn) || 48 | std::isnan(-numIn)); 49 | } 50 | 51 | bool IsValid(glm::vec3 vecIn) 52 | { 53 | return IsValid(vecIn.x) && IsValid(vecIn.y) && IsValid(vecIn.z); 54 | } 55 | 56 | float MakeValid(float numIn, float setInvalidTo) 57 | { 58 | if (IsValid(numIn)) 59 | return numIn; 60 | return setInvalidTo; 61 | } 62 | 63 | glm::vec3 MakeValid(glm::vec3 vecIn, float setInvalidTo) 64 | { 65 | return glm::vec3( 66 | MakeValid(vecIn.x, setInvalidTo), 67 | MakeValid(vecIn.y, setInvalidTo), 68 | MakeValid(vecIn.z, setInvalidTo)); 69 | } 70 | 71 | glm::vec3 RotateVector(glm::quat quatIn, glm::vec3 vecIn) 72 | { 73 | float num = quatIn.x * 2.0f; 74 | float num2 = quatIn.y * 2.0f; 75 | float num3 = quatIn.z * 2.0f; 76 | float num4 = quatIn.x * num; 77 | float num5 = quatIn.y * num2; 78 | float num6 = quatIn.z * num3; 79 | float num7 = quatIn.x * num2; 80 | float num8 = quatIn.x * num3; 81 | float num9 = quatIn.y * num3; 82 | float num10 = quatIn.w * num; 83 | float num11 = quatIn.w * num2; 84 | float num12 = quatIn.w * num3; 85 | glm::vec3 result; 86 | result.x = (1.0f - (num5 + num6)) * vecIn.x + (num7 - num12) * vecIn.y + (num8 + num11) * vecIn.z; 87 | result.y = (num7 + num12) * vecIn.x + (1.0f - (num4 + num6)) * vecIn.y + (num9 - num10) * vecIn.z; 88 | result.z = (num8 - num11) * vecIn.x + (num9 + num10) * vecIn.y + (1.0f - (num4 + num5)) * vecIn.z; 89 | return result; 90 | } 91 | 92 | glm::vec3 RotateVector(glm::vec3 eulerIn, glm::vec3 vecIn) 93 | { 94 | glm::vec3 glmVecIn(vecIn.x, vecIn.y, vecIn.z); 95 | glm::mat3 rotationMatrix = glm::eulerAngleXYZ(eulerIn.x, eulerIn.y, eulerIn.z); 96 | 97 | return rotationMatrix * glmVecIn; 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/ObjectBound.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct ObjectBound 4 | { 5 | static RE::NiPointer GetCollisionNodeRecurse(RE::NiNode* a_node, size_t a_recurse); 6 | static std::optional MakeBoundingBox(RE::NiNode* a_niobj); 7 | 8 | public: 9 | ObjectBound() = default; 10 | ObjectBound(glm::vec3 pBoundMin, glm::vec3 pBoundMax, glm::vec3 pWorldBoundMin, glm::vec3 pWorldBoundMax, glm::vec3 pRotation) : 11 | boundMin(pBoundMin), boundMax(pBoundMax), worldBoundMin(pWorldBoundMin), worldBoundMax(pWorldBoundMax), rotation(pRotation) {} 12 | ~ObjectBound() = default; 13 | 14 | public: 15 | glm::vec3 GetCenterWorld() const; 16 | bool IsPointInside(float a_x, float a_y, float a_z) const; 17 | bool IsPointInside(const glm::vec3& a_point) const; 18 | bool IsPointInside(const RE::NiPoint3& a_point) const; 19 | bool IsValid() const; 20 | 21 | glm::vec3 boundMin{ glm::vec3() }; 22 | glm::vec3 boundMax{ glm::vec3() }; 23 | glm::vec3 worldBoundMin{ glm::vec3() }; 24 | glm::vec3 worldBoundMax{ glm::vec3() }; 25 | glm::vec3 rotation{ glm::vec3() }; 26 | }; 27 | 28 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/Offsets.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Offsets 4 | { 5 | // SE: 2F4C910 6 | // AE: ??? 7 | static constexpr auto WorldToCamMatrix = RELOCATION_ID(519579, 406126); 8 | 9 | // SE: 2F4DED0 10 | // AE: ??? 11 | static constexpr auto ViewPort = RELOCATION_ID(519618, 406160); 12 | 13 | // Raycast (from SmoothCam) 14 | static constexpr auto CameraCaster = RELOCATION_ID(32270, 33007); 15 | static constexpr auto GetNiAVObject = RELOCATION_ID(76160, 77988); 16 | 17 | // SE: 605f70 18 | // AE: 62E390 19 | static constexpr auto DismountActor = RELOCATION_ID(36882, 37906); 20 | typedef __int64(__fastcall RE::Actor::* DismountActor_func)() const; 21 | } 22 | 23 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/RayCast.cpp: -------------------------------------------------------------------------------- 1 | #include "../RayCast.h" 2 | #include "Offsets.h" 3 | #include "bhkLinearCastCollector.h" 4 | 5 | /* From SmoothCam */ 6 | SkyrimSE::bhkLinearCastCollector* getCastCollector() noexcept 7 | { 8 | static SkyrimSE::bhkLinearCastCollector collector = SkyrimSE::bhkLinearCastCollector(); 9 | return &collector; 10 | } 11 | 12 | #ifdef SKYRIMVR 13 | Raycast::RayResult Raycast::CastRay(glm::vec4, glm::vec4, float) noexcept 14 | { 15 | logger::critical("No VR implementation for RayCast::CastRay"); 16 | return {}; 17 | } 18 | #else 19 | Raycast::RayResult Raycast::CastRay(glm::vec4 start, glm::vec4 end, float traceHullSize) noexcept 20 | { 21 | RayResult res; 22 | 23 | const auto ply = RE::PlayerCharacter::GetSingleton(); 24 | const auto cam = RE::PlayerCamera::GetSingleton(); 25 | if (!ply->parentCell || !cam->unk120) 26 | return res; 27 | 28 | auto physicsWorld = ply->parentCell->GetbhkWorld(); 29 | if (physicsWorld) { 30 | typedef bool(__fastcall * RayCastFunType)( 31 | decltype(RE::PlayerCamera::unk120) physics, RE::bhkWorld * world, glm::vec4 & rayStart, 32 | glm::vec4 & rayEnd, uint32_t * rayResultInfo, RE::NiAVObject * *hitActor, float traceHullSize); 33 | 34 | static auto cameraCaster = REL::Relocation(Offsets::CameraCaster); 35 | res.hit = cameraCaster( 36 | cam->unk120, physicsWorld, 37 | start, end, static_cast(res.data), &res.hitObject, 38 | traceHullSize); 39 | } 40 | 41 | if (res.hit) { 42 | res.hitPos = end; 43 | res.rayLength = glm::length(static_cast(res.hitPos) - static_cast(start)); 44 | } 45 | 46 | return res; 47 | } 48 | #endif 49 | 50 | 51 | Raycast::RayResult Raycast::hkpCastRay(const glm::vec4& start, const glm::vec4& end) noexcept { 52 | return hkpCastRay(start, end, std::vector{}); 53 | } 54 | 55 | Raycast::RayResult Raycast::hkpCastRay(const glm::vec4& start, const glm::vec4& end, std::initializer_list a_filter) noexcept 56 | { 57 | std::vector filter{}; 58 | filter.reserve(a_filter.size()); 59 | for (auto&& ref : a_filter) { 60 | auto niobj = ref->Get3D(); 61 | if (niobj) { 62 | filter.push_back(niobj->AsNode()); 63 | } 64 | } 65 | return hkpCastRay(start, end, filter); 66 | } 67 | 68 | Raycast::RayResult Raycast::hkpCastRay(const glm::vec4& start, const glm::vec4& end, const std::vector& a_filter) noexcept 69 | { 70 | const auto hkpScale = RE::bhkWorld::GetWorldScale(); 71 | const auto dif = end - start; 72 | 73 | SkyrimSE::bhkRayCastInfo info; 74 | info.start = start * hkpScale; 75 | info.end = dif * hkpScale; 76 | info.collector = getCastCollector(); 77 | info.collector->reset(); 78 | 79 | for (auto&& niobj : a_filter) { 80 | info.collector->addFilter(niobj); 81 | } 82 | 83 | const auto player = RE::PlayerCharacter::GetSingleton(); 84 | if (!player->parentCell) 85 | return {}; 86 | 87 | auto physicsWorld = player->parentCell->GetbhkWorld(); 88 | if (physicsWorld) { 89 | typedef void (__thiscall RE::bhkWorld::*CastRay)(SkyrimSE::hkpRayCastInfo*) const; 90 | 91 | (physicsWorld->*reinterpret_cast(&RE::bhkWorld::PickObject))(&info); 92 | //physicsWorld->CastRay(&info); // <-- use this instead, fix param 93 | } 94 | 95 | SkyrimSE::bhkRayHitResult best = {}; 96 | best.hitFraction = 1.0f; 97 | glm::vec4 bestPos = {}; 98 | 99 | for (auto& hit : info.collector->results) { 100 | const auto pos = (dif * hit.hitFraction) + start; 101 | if (best.hit == nullptr) { 102 | best = hit; 103 | bestPos = pos; 104 | continue; 105 | } 106 | 107 | if (hit.hitFraction < best.hitFraction) { 108 | best = hit; 109 | bestPos = pos; 110 | } 111 | } 112 | 113 | RayResult result; 114 | result.hitPos = bestPos; 115 | result.rayLength = glm::length(bestPos - start); 116 | 117 | if (!best.hit) 118 | return result; 119 | 120 | typedef RE::NiAVObject* (*_GetUserData)(SkyrimSE::bhkShapeList*); 121 | auto getAVObject = REL::Relocation<_GetUserData>(Offsets::GetNiAVObject); 122 | auto av = getAVObject(best.hit); 123 | 124 | result.hit = av != nullptr; 125 | result.hitObject = av; 126 | 127 | return result; 128 | } 129 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/bhkLinearCastCollector.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | #include "Offsets.h" 3 | 4 | /* From SmoothCam */ 5 | namespace SkyrimSE { 6 | using vec2u = glm::vec<2, float, glm::highp>; 7 | using vec3u = glm::vec<3, float, glm::highp>; 8 | using vec4u = glm::vec<4, float, glm::highp>; 9 | 10 | using vec2ui = glm::vec<2, int, glm::highp>; 11 | using vec3ui = glm::vec<3, int, glm::highp>; 12 | using vec4ui = glm::vec<4, int, glm::highp>; 13 | 14 | typedef struct bhkShapeList { 15 | RE::hkpShape* shape; 16 | uint64_t unk0; 17 | void* shapeInfo; 18 | bhkShapeList* next; 19 | vec3u unk1; 20 | uint32_t flags; 21 | uint32_t unk2; 22 | uint32_t unk3; 23 | uint32_t unk4; 24 | } bhkShapeList; 25 | 26 | struct bhkRayHitResult { 27 | glm::vec3 normal; 28 | float hitFraction; 29 | bhkShapeList* hit; 30 | }; 31 | 32 | struct bhkAllCdPointTempResult { 33 | glm::vec4 normal; 34 | float hitFraction; 35 | }; 36 | 37 | class bhkLinearCastCollector { 38 | public: 39 | bhkLinearCastCollector() noexcept { 40 | results.reserve(64); 41 | } 42 | 43 | inline void reset() noexcept { 44 | results.clear(); 45 | objectFilter.clear(); 46 | m_hitFraction = 0.0f; 47 | m_earlyOutHitFraction = 1.0f; 48 | } 49 | 50 | inline void addFilter(const RE::NiAVObject* obj) noexcept { 51 | objectFilter.push_back(obj); 52 | } 53 | 54 | virtual void addRayHit(bhkShapeList* list, const bhkAllCdPointTempResult* hitInfo) { 55 | bhkRayHitResult hitResult; 56 | hitResult.hitFraction = hitInfo->hitFraction; 57 | hitResult.normal = static_cast(hitInfo->normal); 58 | 59 | while (list) { 60 | if (!list->next) break; 61 | list = list->next; 62 | } 63 | 64 | hitResult.hit = list; 65 | if (hitResult.hit) { 66 | const uint64_t m = 1ULL << static_cast(hitResult.hit->flags & 0x7F); 67 | constexpr uint64_t filter = 0x40122716; //@TODO 68 | if ((m & filter) != 0) { 69 | if (objectFilter.size() > 0) 70 | for (const auto obj : objectFilter) { 71 | typedef RE::NiAVObject*(*GetUserData)(SkyrimSE::bhkShapeList*); 72 | if (REL::Relocation(Offsets::GetNiAVObject)(hitResult.hit) == obj) 73 | return; 74 | } 75 | 76 | // We only want further hits to be closer than this 77 | m_earlyOutHitFraction = hitResult.hitFraction; 78 | results.push_back(hitResult); 79 | } 80 | } 81 | } 82 | 83 | public: 84 | enum { MAX_HIERARCHY_DEPTH = 8 }; 85 | 86 | float m_earlyOutHitFraction = 1.0f; //0x08 87 | uint32_t pad0; //0x0C 88 | uint64_t pad1[2]; //0x10, 0x18 89 | float m_hitFraction = 0.0f; //0x20 90 | uint32_t unk0 = 0; //0x24 91 | RE::hkpShapeKey shapeKey; //0x28 92 | uint32_t pad2; //0x2C 93 | RE::hkpShapeKey m_shapeKeys[MAX_HIERARCHY_DEPTH] = {}; 94 | uint32_t m_shapeKeyIndex = 0; 95 | uint32_t pad3; 96 | uint64_t unk1 = 0; 97 | RE::hkpCollidable* m_rootCollidable = nullptr; 98 | uint64_t unk2 = 0; 99 | 100 | //eastl::vector results; 101 | //eastl::vector objectFilter; 102 | 103 | std::vector results; 104 | std::vector objectFilter; 105 | }; 106 | 107 | typedef __declspec(align(16)) struct hkpRayCastInfo { 108 | glm::vec4 start = {}; // 0x0 109 | glm::vec4 unkVec = {}; // 0x10 110 | bool unk0 = false; // 0x20 111 | uint32_t flags = 0; // 0x24 112 | uint64_t unk1_0 = 0; 113 | uint64_t unk1_1 = 0; 114 | uint64_t unk1_2 = 0; 115 | float unk2 = 1.0f; // 0x40 116 | vec3ui unk3 = { -1, -1, -1 }; // 0x44 117 | uint64_t unk4_0 = 0; 118 | uint64_t unk4_1 = 0; 119 | uint64_t unk4_2 = 0; 120 | uint64_t unk4_3 = 0; 121 | uint32_t unk5 = 0; // 0x70 122 | uintptr_t unk6 = 0; 123 | uint64_t unk7 = 0; // 0x80 124 | uintptr_t unk8 = 0; 125 | glm::vec4 end = {}; // 0x90 126 | uint64_t unk9 = 0; // 0xA0 127 | bhkLinearCastCollector* collector = nullptr; // 0xA8 128 | uint64_t unk10 = 0; 129 | uint64_t unk11 = 0; 130 | bool unk12 = false; // 0xC0 131 | uint64_t unk13[11] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 }; 132 | } bhkRayCastInfo; 133 | static_assert(offsetof(bhkRayCastInfo, start.w) == 0xC); 134 | static_assert(offsetof(bhkRayCastInfo, unk0) == 0x20); 135 | static_assert(offsetof(bhkRayCastInfo, flags) == 0x24); 136 | static_assert(offsetof(bhkRayCastInfo, unk2) == 0x40); 137 | static_assert(offsetof(bhkRayCastInfo, unk5) == 0x70); 138 | static_assert(offsetof(bhkRayCastInfo, unk7) == 0x80); 139 | static_assert(offsetof(bhkRayCastInfo, end) == 0x90); 140 | static_assert(offsetof(bhkRayCastInfo, end.w) == 0x9C); 141 | static_assert(offsetof(bhkRayCastInfo, unk9) == 0xA0); 142 | static_assert(offsetof(bhkRayCastInfo, collector) == 0xA8); 143 | static_assert(offsetof(bhkRayCastInfo, unk12) == 0xC0); 144 | static_assert(sizeof(bhkRayCastInfo) == 0x120); 145 | } 146 | -------------------------------------------------------------------------------- /src/Registry/Util/RayCast/bhkRigidBodyT.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | class bhkRigidBodyT : public RE::bhkRigidBody 4 | { 5 | public: 6 | inline static constexpr auto RTTI = RE::RTTI_bhkRigidBodyT; 7 | inline static constexpr auto Ni_RTTI = RE::NiRTTI_bhkRigidBodyT; 8 | 9 | ~bhkRigidBodyT() override; // 00 10 | 11 | RE::hkQuaternion rotation; // 40 12 | RE::hkVector4 translation; // 50 13 | }; 14 | 15 | static_assert(offsetof(bhkRigidBodyT, rotation) == 0x40); 16 | static_assert(offsetof(bhkRigidBodyT, translation) == 0x50); 17 | static_assert(sizeof(bhkRigidBodyT) == 0x60); 18 | 19 | -------------------------------------------------------------------------------- /src/Registry/Util/SAT.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "RayCast/ObjectBound.h" 4 | 5 | namespace SAT 6 | { 7 | struct OrientedObjectBound 8 | { 9 | OrientedObjectBound(RE::NiNode* a_origin, const ObjectBound& a_bound) : 10 | origin(a_origin), box(a_bound) {} 11 | OrientedObjectBound(RE::NiNode* a_origin) : 12 | origin(a_origin), box([&]() { 13 | const auto ret = ObjectBound::MakeBoundingBox(a_origin); 14 | return ret ? *ret : ObjectBound{}; }()) {} 15 | ~OrientedObjectBound() = default; 16 | 17 | std::vector GetCorners() const 18 | { 19 | const auto center = box.GetCenterWorld(); 20 | const auto halfsize = glm::abs(box.worldBoundMax - center); 21 | const auto& rotate = origin->world.rotate; 22 | const auto x = rotate.GetVectorX(), y = rotate.GetVectorY(), z = rotate.GetVectorZ(); 23 | const auto ex = halfsize.x * glm::vec3{ x.x, x.y, x.z }; 24 | const auto ey = halfsize.y * glm::vec3{ y.x, y.y, y.z }; 25 | const auto ez = halfsize.z * glm::vec3{ z.x, z.y, z.z }; 26 | 27 | std::vector corners; 28 | corners.push_back(center - ex - ey - ez); 29 | corners.push_back(center + ex - ey - ez); 30 | corners.push_back(center - ex + ey - ez); 31 | corners.push_back(center + ex + ey - ez); 32 | corners.push_back(center - ex - ey + ez); 33 | corners.push_back(center + ex - ey + ez); 34 | corners.push_back(center - ex + ey + ez); 35 | corners.push_back(center + ex + ey + ez); 36 | return corners; 37 | } 38 | 39 | RE::NiNode* origin; 40 | ObjectBound box; 41 | }; 42 | 43 | struct SATResult 44 | { 45 | float mtv{ std::numeric_limits::max() }; 46 | glm::vec3 mtv_axis{}; 47 | }; 48 | 49 | inline std::vector GetAxes(const OrientedObjectBound& a_obb1, const OrientedObjectBound& a_obb2) 50 | { 51 | const auto& rot1 = a_obb1.origin->world.rotate.entry; 52 | const auto& rot2 = a_obb2.origin->world.rotate.entry; 53 | std::array, 2> rotations; 54 | for (int i = 0; i < rotations[0].size(); i++) { 55 | rotations[0][i] = glm::vec3(rot1[i][0], rot1[i][1], rot1[i][2]); 56 | rotations[1][i] = glm::vec3(rot2[i][0], rot2[i][1], rot2[i][2]); 57 | } 58 | 59 | std::vector axes; 60 | for (size_t n = 0; n < rotations.size(); n++) { 61 | for (int i = 0; i < rotations[0].size(); i++) { 62 | axes.push_back(glm::normalize(rotations[n][i])); 63 | } 64 | } 65 | for (int i = 0; i < rotations[0].size(); i++) { 66 | for (int j = 0; j < rotations[0].size(); j++) { 67 | glm::vec3 axis = glm::cross(rotations[0][i], rotations[1][j]); 68 | if (glm::length(axis) > 0) { 69 | axes.push_back(glm::normalize(axis)); 70 | } 71 | } 72 | } 73 | return axes; 74 | } 75 | 76 | inline std::optional SAT(const OrientedObjectBound& a_obb1, const OrientedObjectBound& a_obb2) 77 | { 78 | const auto corner1 = a_obb1.GetCorners(), corner2 = a_obb2.GetCorners(); 79 | const auto axes = GetAxes(a_obb1, a_obb2); 80 | 81 | SATResult ret; 82 | for (auto&& axis : axes) { 83 | const auto project = [&axis](const std::vector& points) -> std::pair { 84 | float min = std::numeric_limits::max(), max = 0.0f; 85 | for (const auto& point : points) { 86 | float projection = glm::dot(axis, point); 87 | min = std::min(projection, min); 88 | max = std::max(projection, max); 89 | } 90 | return { min, max }; 91 | }; 92 | 93 | const auto [start1, end1] = project(corner1); 94 | const auto [start2, end2] = project(corner2); 95 | 96 | const auto minend = std::min(end1, end2), maxstart = std::max(start1, start2); 97 | const auto overlap = minend - maxstart; 98 | if (overlap > 0) 99 | return std::nullopt; 100 | 101 | if (ret.mtv > overlap) { 102 | ret.mtv = overlap; 103 | ret.mtv_axis = axis; 104 | } 105 | } 106 | return ret; 107 | } 108 | 109 | } -------------------------------------------------------------------------------- /src/Registry/Util/Scale.cpp: -------------------------------------------------------------------------------- 1 | #include "Scale.h" 2 | 3 | namespace Registry 4 | { 5 | float Scale::GetScale(RE::TESObjectREFR* a_reference) 6 | { 7 | assert(a_reference); 8 | const auto node = a_reference->GetNodeByName(basenode); 9 | const auto baseScale = a_reference->GetScale(); 10 | const auto nodeScale = node ? node->local.scale : 1.0f; 11 | const auto retVal = baseScale * nodeScale; 12 | logger::info("GetScale: {:X} -> Base = {}, Skeleton = {} => {}", a_reference->GetFormID(), baseScale, nodeScale, retVal); 13 | return retVal; 14 | } 15 | 16 | void Scale::SetScale(RE::Actor* a_actor, float a_absolutescale) 17 | { 18 | SetScale(a_actor, { a_actor }, a_absolutescale); 19 | } 20 | 21 | void Scale::SetScale(RE::Actor* a_actor, RaceKey a_racekey, float a_absolutescale) 22 | { 23 | assert(a_actor && a_absolutescale > 0.0f); 24 | if (Settings::bDisableScale) { 25 | return; 26 | } else if (!transformInterface) { 27 | logger::error("Missing interface, scaling disabled"); 28 | Settings::bDisableScale = true; 29 | return; 30 | } 31 | switch (a_racekey) { 32 | case RaceKey::AshHopper: 33 | // a_absolutescale *= 0.5f; 34 | break; 35 | case RaceKey::Chaurus: 36 | a_absolutescale *= 0.5f; 37 | break; 38 | case RaceKey::ChaurusHunter: 39 | // a_absolutescale *= 0.69f; 40 | break; 41 | case RaceKey::Chicken: 42 | // a_absolutescale *= 1.3f; 43 | break; 44 | case RaceKey::Fox: 45 | // a_absolutescale *= 0.72f; 46 | break; 47 | case RaceKey::FrostAtronach: 48 | // a_absolutescale *= 1.3f; 49 | break; 50 | case RaceKey::Spider: 51 | a_absolutescale *= 0.75f; 52 | break; 53 | case RaceKey::LargeSpider: 54 | a_absolutescale *= 1.2f; 55 | break; 56 | case RaceKey::GiantSpider: 57 | a_absolutescale *= 1.9f; 58 | break; 59 | case RaceKey::Horker: 60 | // a_absolutescale *= 1.2f; 61 | break; 62 | case RaceKey::Mudcrab: 63 | // a_absolutescale *= 0.75f; 64 | break; 65 | default: 66 | // a_absolutescale *= 1.0f; 67 | break; 68 | } 69 | float basescale = GetScale(a_actor); 70 | if (std::abs(basescale - a_absolutescale) < 0.03) { 71 | logger::info("Attempted Node Transform to Actor = {:X}, Scale = {} -> {}", a_actor->GetFormID(), basescale, a_absolutescale); 72 | return; 73 | } 74 | 75 | const auto base = a_actor->GetActorBase(); 76 | const auto female = base ? base->GetSex() == RE::SEXES::kFemale : false; 77 | transformInterface->AddNodeTransformScaleMode(a_actor, false, female, basenode, namekey, ScaleModes::Multiplicative); 78 | if (transformInterface->RemoveNodeTransformScale(a_actor, false, female, basenode, namekey)) { 79 | transformInterface->UpdateNodeTransforms(a_actor, false, female, basenode); 80 | basescale = GetScale(a_actor); 81 | } 82 | // base * x = absolute <=> x = absolute / base 83 | float x = a_absolutescale / basescale; 84 | 85 | logger::info("Applying Node Transform to Actor = {:X}, Scale = {} -> {}, x = {}", a_actor->GetFormID(), basescale, a_absolutescale, x); 86 | transformInterface->AddNodeTransformScale(a_actor, false, female, basenode, namekey, x); 87 | transformInterface->UpdateNodeTransforms(a_actor, false, female, basenode); 88 | } 89 | 90 | void Scale::RemoveScale(RE::Actor* a_actor) 91 | { 92 | assert(a_actor); 93 | if (Settings::bDisableScale) { 94 | return; 95 | } else if (!transformInterface) { 96 | logger::error("Missing interface, scaling disabled"); 97 | Settings::bDisableScale = true; 98 | return; 99 | } 100 | 101 | const auto base = a_actor->GetActorBase(); 102 | const auto female = base ? base->GetSex() == RE::SEXES::kFemale : false; 103 | if (transformInterface->RemoveNodeTransformScale(a_actor, false, female, basenode, namekey)) { 104 | logger::info("Removed Transform Scale from {:X}", a_actor->GetFormID()); 105 | transformInterface->UpdateNodeTransforms(a_actor, false, female, basenode); 106 | } 107 | } 108 | 109 | } // namespace Registry 110 | -------------------------------------------------------------------------------- /src/Registry/Util/Scale.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "API/IPluginInterface.h" 4 | #include "Registry/Define/RaceKey.h" 5 | 6 | namespace Registry 7 | { 8 | class Scale : 9 | public Singleton 10 | { 11 | static constexpr const char* namekey{ "SexLabRegistry" }; 12 | static constexpr const char* basenode{ "NPC" }; 13 | static constexpr const char* extranode{ "NPC Root [Root]" }; 14 | 15 | enum ScaleModes 16 | { 17 | Multiplicative = 0, 18 | Averaged = 1, 19 | Additive = 2, 20 | Maximum = 3, 21 | }; 22 | 23 | public: 24 | float GetScale(RE::TESObjectREFR* a_reference); 25 | void SetScale(RE::Actor* a_reference, float a_absolutescale); 26 | void SetScale(RE::Actor* a_reference, RaceKey a_racekey, float a_absolutescale); 27 | void RemoveScale(RE::Actor* a_reference); 28 | 29 | private: 30 | struct ScaleNodeVisitor : public SKEE::INiTransformInterface::NodeVisitor 31 | { 32 | virtual bool VisitPosition(const char* node, const char* key, SKEE::INiTransformInterface::Position& position) override; 33 | virtual bool VisitRotation(const char* node, const char* key, SKEE::INiTransformInterface::Rotation& rotation) override; 34 | virtual bool VisitScale(const char* node, const char* key, float scale) override; 35 | virtual bool VisitScaleMode(const char* node, const char* key, uint32_t scaleMode) override; 36 | }; 37 | 38 | SKEE::INiTransformInterface* transformInterface = []() { 39 | const auto intfc = SKEE::GetInterfaceMap(); 40 | return intfc ? SKEE::GetNiTransformInterface(intfc) : nullptr; 41 | }(); 42 | }; 43 | 44 | } // namespace Registry 45 | -------------------------------------------------------------------------------- /src/Registry/Validation.cpp: -------------------------------------------------------------------------------- 1 | #include "Validation.h" 2 | 3 | #include "Define/RaceKey.h" 4 | #include "Util/Scale.h" 5 | 6 | bool Registry::IsValidActor(RE::Actor* a_actor) 7 | { 8 | return IsValidActorImpl(a_actor) > 0; 9 | } 10 | 11 | int32_t Registry::IsValidActorImpl(RE::Actor* a_actor) 12 | { 13 | if (!a_actor->Is3DLoaded()) 14 | return -12; 15 | else if (a_actor->IsDisabled() || !a_actor->IsAIEnabled()) 16 | return -14; 17 | 18 | const auto lifestate = a_actor->GetLifeState(); 19 | if (!Settings::bAllowDead && (lifestate == RE::ACTOR_LIFE_STATE::kDead || lifestate == RE::ACTOR_LIFE_STATE::kDying)) 20 | return -13; 21 | else if (a_actor->IsFlying()) 22 | return -15; 23 | else if (a_actor->IsOnMount() || a_actor->GetActorValue(RE::ActorValue::kVariable05) > 0) 24 | return -16; 25 | 26 | auto validfaction = 1; 27 | a_actor->VisitFactions([&validfaction](RE::TESFaction* a_faction, int8_t a_rank) { 28 | if (!a_faction || a_rank < 0) 29 | return false; 30 | if (a_faction == GameForms::AnimatingFaction) { 31 | validfaction = -10; 32 | return true; 33 | } 34 | if (a_faction == GameForms::ForbiddenFaction) { 35 | validfaction = -11; 36 | return true; 37 | } 38 | return false; 39 | }); 40 | if (validfaction != 1) 41 | return validfaction; 42 | 43 | 44 | const RaceKey race{ a_actor }; 45 | if (!Settings::bAllowCreatures && !race.Is(RaceKey::Human)) { 46 | return -17; 47 | } 48 | switch (race) { 49 | case RaceKey::Human: 50 | { 51 | if (a_actor->IsChild()) 52 | return -11; 53 | if (Scale::GetSingleton()->GetScale(a_actor) < Settings::fMinScale) 54 | return -11; 55 | 56 | /* below might be interesting to investigate if GetScale() isnt working reliably 57 | The function calculates the height difference between left foot and head, 58 | but may be unreliable if there are mods changing proportions or if an animation puts head close to foot 59 | */ 60 | // const auto foot = a_actor->GetNodeByName("CME L Foot [Lft ]"); 61 | // const auto head = a_actor->GetNodeByName("NPC Head [Head]"); 62 | // if (!foot || !head) 63 | // return true; 64 | // const auto& footZ = foot->world.translate.z; 65 | // const auto& headZ = head->world.translate.z; 66 | // const auto& difference = headZ - footZ; 67 | // if (difference < 95) { 68 | // return false; 69 | // } 70 | } 71 | return true; 72 | case RaceKey::AshHopper: 73 | return Settings::bAshHopper; 74 | case RaceKey::Bear: 75 | return Settings::bBear; 76 | case RaceKey::BoarAny: 77 | return Settings::bBoar; 78 | case RaceKey::BoarMounted: 79 | return Settings::bBoarMounted; 80 | case RaceKey::BoarSingle: 81 | return Settings::bBoarSingle; 82 | case RaceKey::Canine: 83 | return Settings::bCanine; 84 | case RaceKey::Chaurus: 85 | return Settings::bChaurus; 86 | case RaceKey::ChaurusHunter: 87 | return Settings::bChaurusHunter; 88 | case RaceKey::ChaurusReaper: 89 | return Settings::bChaurusReaper; 90 | case RaceKey::Chicken: 91 | return Settings::bChicken; 92 | case RaceKey::Cow: 93 | return Settings::bCow; 94 | case RaceKey::Deer: 95 | return Settings::bDeer; 96 | case RaceKey::Dog: 97 | return Settings::bDog; 98 | case RaceKey::Dragon: 99 | return Settings::bDragon; 100 | case RaceKey::DragonPriest: 101 | return Settings::bDragonPriest; 102 | case RaceKey::Draugr: 103 | return Settings::bDraugr; 104 | case RaceKey::DwarvenBallista: 105 | return Settings::bDwarvenBallista; 106 | case RaceKey::DwarvenCenturion: 107 | return Settings::bDwarvenCenturion; 108 | case RaceKey::DwarvenSphere: 109 | return Settings::bDwarvenSphere; 110 | case RaceKey::DwarvenSpider: 111 | return Settings::bDwarvenSpider; 112 | case RaceKey::Falmer: 113 | return Settings::bFalmer; 114 | case RaceKey::FlameAtronach: 115 | return Settings::bFlameAtronach; 116 | case RaceKey::Fox: 117 | return Settings::bFox; 118 | case RaceKey::FrostAtronach: 119 | return Settings::bFrostAtronach; 120 | case RaceKey::Gargoyle: 121 | return Settings::bGargoyle; 122 | case RaceKey::Giant: 123 | return Settings::bGiant; 124 | case RaceKey::GiantSpider: 125 | return Settings::bGiantSpider; 126 | case RaceKey::Goat: 127 | return Settings::bGoat; 128 | case RaceKey::Hagraven: 129 | return Settings::bHagraven; 130 | case RaceKey::Hare: 131 | return Settings::bHare; 132 | case RaceKey::Horker: 133 | return Settings::bHorker; 134 | case RaceKey::Horse: 135 | return Settings::bHorse; 136 | case RaceKey::IceWraith: 137 | return Settings::bIceWraith; 138 | case RaceKey::LargeSpider: 139 | return Settings::bLargeSpider; 140 | case RaceKey::Lurker: 141 | return Settings::bLurker; 142 | case RaceKey::Mammoth: 143 | return Settings::bMammoth; 144 | case RaceKey::Mudcrab: 145 | return Settings::bMudcrab; 146 | case RaceKey::Netch: 147 | return Settings::bNetch; 148 | case RaceKey::Riekling: 149 | return Settings::bRiekling; 150 | case RaceKey::Sabrecat: 151 | return Settings::bSabrecat; 152 | case RaceKey::Seeker: 153 | return Settings::bSeeker; 154 | case RaceKey::Skeever: 155 | return Settings::bSkeever; 156 | case RaceKey::Slaughterfish: 157 | return Settings::bSlaughterfish; 158 | case RaceKey::Spider: 159 | return Settings::bSpider; 160 | case RaceKey::Spriggan: 161 | return Settings::bSpriggan; 162 | case RaceKey::StormAtronach: 163 | return Settings::bStormAtronach; 164 | case RaceKey::Troll: 165 | return Settings::bTroll; 166 | case RaceKey::VampireLord: 167 | return Settings::bVampireLord; 168 | case RaceKey::Werewolf: 169 | return Settings::bWerewolf; 170 | case RaceKey::Wisp: 171 | return Settings::bWisp; 172 | case RaceKey::Wispmother: 173 | return Settings::bWispmother; 174 | case RaceKey::Wolf: 175 | return Settings::bWolf; 176 | default: 177 | return -18; 178 | } 179 | } 180 | -------------------------------------------------------------------------------- /src/Registry/Validation.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Registry 4 | { 5 | /// @brief Validate the given actor 6 | /// @return some code (-inf; 1]. 1 if the actor is valid. See implementation for details 7 | int32_t IsValidActorImpl(RE::Actor* a_actor); 8 | bool IsValidActor(RE::Actor* a_actor); 9 | } // namespace Registry 10 | -------------------------------------------------------------------------------- /src/Serialization.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Papyrus/sslLibrary/Serialize.h" 4 | #include "Registry/Stats.h" 5 | 6 | namespace Serialization 7 | { 8 | inline std::string GetTypeName(uint32_t a_type) 9 | { 10 | constexpr auto size = sizeof(uint32_t); 11 | std::string ret{}; 12 | ret.resize(size); 13 | const char* it = reinterpret_cast(&a_type); 14 | for (size_t i = 0, j = size - 2; i < size - 1; i++, j--) 15 | ret[j] = it[i]; 16 | 17 | return ret; 18 | } 19 | 20 | class Serialize final : 21 | public Singleton 22 | { 23 | public: 24 | enum : std::uint32_t 25 | { 26 | _Version = 1, 27 | 28 | _Statistics = 'stcs', 29 | _Tracking = 'trcn' 30 | }; 31 | 32 | static void SaveCallback(SKSE::SerializationInterface* a_intfc) 33 | { 34 | #define SAVE(type, func) \ 35 | if (!a_intfc->OpenRecord(type, _Version)) { \ 36 | std::string insert = #type##s; \ 37 | logger::error("Failed to open record {}", insert); \ 38 | } else { \ 39 | func; \ 40 | } 41 | SAVE(_Statistics, Registry::Statistics::StatisticsData::GetSingleton()->Save(a_intfc)) 42 | SAVE(_Tracking, Papyrus::Tracking::GetSingleton()->Save(a_intfc)) 43 | 44 | #undef SAVE 45 | } 46 | 47 | static void LoadCallback(SKSE::SerializationInterface* a_intfc) 48 | { 49 | const auto v = static_cast(_Version); 50 | uint32_t type, version, length; 51 | while (a_intfc->GetNextRecordInfo(type, version, length)) { 52 | if (version != v) { 53 | logger::info("Invalid Version for loaded Data of Type = {}. Expected = {}; Got = {}", GetTypeName(type), v, version); 54 | continue; 55 | } 56 | logger::info("Loading record {}", GetTypeName(type)); 57 | switch (type) { 58 | case _Statistics: 59 | Registry::Statistics::StatisticsData::GetSingleton()->Load(a_intfc); 60 | break; 61 | case _Tracking: 62 | Papyrus::Tracking::GetSingleton()->Load(a_intfc); 63 | break; 64 | default: 65 | break; 66 | } 67 | } 68 | 69 | logger::info("Finished loading data from cosave"); 70 | } 71 | 72 | static void RevertCallback(SKSE::SerializationInterface* a_intfc) 73 | { 74 | Registry::Statistics::StatisticsData::GetSingleton()->Revert(a_intfc); 75 | Papyrus::Tracking::GetSingleton()->Revert(a_intfc); 76 | } 77 | 78 | static void FormDeleteCallback(RE::VMHandle) 79 | { 80 | } 81 | 82 | }; // class Serialize 83 | 84 | } // namespace Serialization 85 | -------------------------------------------------------------------------------- /src/Thread/Interface/Interface.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Thread::Interface 4 | { 5 | struct SKSEScaleform_OpenMenu; 6 | struct SKSEScaleform_CloseMenu; 7 | struct SKSEScaleform_SendModEvent; 8 | struct SKSEScaleform_AllowInput; 9 | 10 | template 11 | class FlashLogger : public RE::GFxLog 12 | { 13 | public: 14 | void LogMessageVarg(LogMessageType, const char* str, std::va_list a_argList) override 15 | { 16 | std::string msg(str ? str : ""); 17 | while (!msg.empty() && msg.back() == '\n') 18 | msg.pop_back(); 19 | 20 | auto length = std::vsnprintf(0, 0, msg.c_str(), a_argList) + 1; 21 | char* buffer = (char*)malloc(sizeof(*buffer) * length); 22 | if (!buffer) 23 | return; 24 | std::vsnprintf(buffer, length, msg.c_str(), a_argList); 25 | 26 | logger::info("{} -> {}", T::MENU_NAME, buffer); 27 | free(buffer); 28 | } 29 | }; 30 | 31 | struct FunctionManager 32 | { 33 | template 34 | static inline void AttachFunction(RE::GPtr a_view, RE::GFxValue& a_scope, const char* a_methodname) 35 | { 36 | RE::GFxFunctionHandler* fn; 37 | const auto where = gfx_functions.find(&typeid(F)); 38 | if (where != gfx_functions.end()) { 39 | fn = where->second; 40 | } else { 41 | fn = new F; 42 | gfx_functions[&typeid(F)] = fn; 43 | } 44 | RE::GFxValue fnValue; 45 | a_view->CreateFunction(&fnValue, fn); 46 | bool success = a_scope.SetMember(a_methodname, fnValue); 47 | if (!success) { 48 | logger::error("Unable to bind function {}", a_methodname); 49 | } 50 | } 51 | 52 | static inline std::optional MakeFunctionObject(RE::GPtr a_view, const char* a_classname) 53 | { 54 | RE::GFxValue global; 55 | bool success = a_view->GetVariable(&global, "_global"); 56 | if (!success) { 57 | logger::error("Unable to get _global from view"); 58 | return std::nullopt; 59 | } 60 | RE::GFxValue skse; 61 | a_view->CreateObject(&skse); 62 | success = global.SetMember(a_classname, skse); 63 | if (!success) { 64 | logger::error("Failed to attach function object {} to global instance", a_classname); 65 | return std::nullopt; 66 | } 67 | return skse; 68 | } 69 | 70 | static inline void AttachSKSEFunctions(RE::GPtr a_view) 71 | { 72 | auto skse = MakeFunctionObject(a_view, "skse"); 73 | if (!skse) { 74 | logger::error("Failed to create SKSE function object"); 75 | return; 76 | } 77 | AttachFunction(a_view, *skse, "OpenMenu"); 78 | AttachFunction(a_view, *skse, "CloseMenu"); 79 | AttachFunction(a_view, *skse, "SendModEvent"); 80 | AttachFunction(a_view, *skse, "AllowTextInput"); 81 | } 82 | 83 | private: 84 | static inline std::map gfx_functions{}; 85 | }; 86 | 87 | struct SKSEScaleform_OpenMenu : public RE::GFxFunctionHandler 88 | { 89 | void Call(Params& a_args) override 90 | { 91 | assert(a_args.argCount > 0); 92 | 93 | const auto name = a_args.args->GetString(); 94 | RE::UIMessageQueue::GetSingleton()->AddMessage( 95 | name, RE::UI_MESSAGE_TYPE::kShow, nullptr); 96 | } 97 | }; 98 | 99 | struct SKSEScaleform_CloseMenu : public RE::GFxFunctionHandler 100 | { 101 | void Call(Params& a_args) override 102 | { 103 | assert(a_args.argCount > 0); 104 | 105 | const auto name = a_args.args->GetString(); 106 | RE::UIMessageQueue::GetSingleton()->AddMessage( 107 | name, RE::UI_MESSAGE_TYPE::kHide, nullptr); 108 | } 109 | }; 110 | 111 | struct SKSEScaleform_SendModEvent : public RE::GFxFunctionHandler 112 | { 113 | void Call(Params& a_args) override 114 | { 115 | assert(a_args.argCount > 0); 116 | 117 | const auto evt = a_args.args->GetString(); 118 | 119 | const auto argStr = a_args.argCount > 1 && a_args.args[1].IsString() ? a_args.args[1].GetString() : ""; 120 | const auto argNum = a_args.argCount > 2 && a_args.args[2].IsNumber() ? static_cast(a_args.args[2].GetNumber()) : 0.0f; 121 | const auto argForm = a_args.argCount > 3 && a_args.args[3].IsNumber() ? static_cast(a_args.args[3].GetNumber()) : 0; 122 | 123 | SKSE::ModCallbackEvent modEvent{ 124 | evt, 125 | argStr, 126 | argNum, 127 | argForm ? RE::TESForm::LookupByID(argForm) : nullptr 128 | }; 129 | SKSE::GetModCallbackEventSource()->SendEvent(&modEvent); 130 | } 131 | }; 132 | 133 | struct SKSEScaleform_AllowInput : public RE::GFxFunctionHandler 134 | { 135 | void Call(Params& a_args) override 136 | { 137 | assert(a_args.argCount > 0); 138 | 139 | const auto enable = a_args.args->GetBool(); 140 | RE::ControlMap::GetSingleton()->AllowTextInput(enable); 141 | } 142 | }; 143 | } -------------------------------------------------------------------------------- /src/Thread/Interface/SceneMenu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Registry/Define/Animation.h" 4 | #include "Thread/Thread.h" 5 | 6 | namespace Thread::Interface 7 | { 8 | class SceneMenu : 9 | public RE::IMenu, 10 | public RE::BSTEventSink 11 | { 12 | using GRefCountBaseStatImpl::operator new; 13 | using GRefCountBaseStatImpl::operator delete; 14 | 15 | public: 16 | static constexpr std::string_view MENU_NAME{ "SLSceneMenu" }; 17 | static constexpr std::string_view FILEPATH{ "SexLab\\SceneMenu" }; 18 | static constexpr int8_t DEPTH_PRIORITY{ 4 }; 19 | 20 | SceneMenu(); 21 | ~SceneMenu() = default; 22 | static void Register() { (RE::UI::GetSingleton()->Register(MENU_NAME, Create), logger::info("Registered Menu: {}", MENU_NAME)); } 23 | static RE::IMenu* Create() { return new SceneMenu(); } 24 | 25 | public: 26 | static void Show(Instance* instance) { (threadInstance = instance, RE::UIMessageQueue::GetSingleton()->AddMessage(MENU_NAME, RE::UI_MESSAGE_TYPE::kShow, nullptr)); } 27 | static void Hide() { RE::UIMessageQueue::GetSingleton()->AddMessage(MENU_NAME, RE::UI_MESSAGE_TYPE::kHide, nullptr); } 28 | static bool IsOpen() { return RE::UI::GetSingleton()->IsMenuOpen(MENU_NAME); } 29 | 30 | static bool IsInstance(Instance* a_instance) { return threadInstance == a_instance; } 31 | static void UpdateSlider(RE::FormID a_actorId, float a_enjoyment); 32 | static void SetSliderTo(RE::FormID a_actorId, float a_enjoyment); 33 | static void UpdatePositions(); 34 | static void UpdateStageInfo(); 35 | static void UpdateActiveScene(); 36 | static void UpdateTimer(float a_time); 37 | static void DisableTimer(); 38 | 39 | protected: 40 | // IMenu 41 | RE::UI_MESSAGE_RESULTS ProcessMessage(RE::UIMessage& a_message) override; 42 | 43 | // Events 44 | RE::BSEventNotifyControl ProcessEvent(RE::InputEvent* const* a_event, RE::BSTEventSource*) override; 45 | 46 | private: 47 | static inline Instance* threadInstance{ nullptr }; 48 | 49 | private: 50 | void AttachSexLabAPIFunctions(RE::GPtr a_view); 51 | 52 | struct GFxFunctionHandlerWrapper : public RE::GFxFunctionHandler 53 | { 54 | protected: 55 | RE::Actor* GetActorByReferenceId(Params& a_args, size_t argIdx); 56 | }; 57 | 58 | // clang-format off 59 | struct SLAPI_GetHotkeyCombination : public RE::GFxFunctionHandler { void Call(Params& a_args) override; }; 60 | struct SLAPI_GetActiveFurnitureName : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 61 | struct SLAPI_GetOffset : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 62 | struct SLAPI_ResetOffsets : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 63 | struct SLAPI_GetOffsetStepSize : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 64 | struct SLAPI_AdjustOffsetStepSize : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 65 | struct SLAPI_GetAdjustStageOnly : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 66 | struct SLAPI_SetAdjustStageOnly : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 67 | struct SLAPI_GetAlternateScenes : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 68 | struct SLAPI_ToggleAutoPlay : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 69 | struct SLAPI_IsAutoPlay : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 70 | struct SLAPI_SetHideHUD : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 71 | struct SLAPI_GetHideHUD : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 72 | struct SLAPI_GetPermutationData : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 73 | struct SLAPI_SelectNextPermutation : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 74 | struct SLAPI_GetGhostMode : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 75 | struct SLAPI_SetGhostMode : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 76 | struct SLAPI_GetExpressionName : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 77 | struct SLAPI_SetExpression : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 78 | struct SLAPI_GetExpressions : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 79 | struct SLAPI_GetVoiceName : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 80 | struct SLAPI_SetVoice : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 81 | struct SLAPI_GetVoices : public GFxFunctionHandlerWrapper { void Call(Params& a_args) override; }; 82 | // clang-format on 83 | 84 | private: 85 | struct HUDMenu_ShowMessageEx : public RE::GFxFunctionHandler 86 | { 87 | void Call(Params& a_args) override; 88 | }; 89 | }; 90 | } 91 | -------------------------------------------------------------------------------- /src/Thread/Interface/SelectionMenu.cpp: -------------------------------------------------------------------------------- 1 | #include "SelectionMenu.h" 2 | 3 | #include "Interface.h" 4 | 5 | namespace Thread::Interface 6 | { 7 | SelectionMenu::SelectionMenu() : 8 | RE::IMenu() 9 | { 10 | this->inputContext = Context::kMenuMode; 11 | this->depthPriority = 3; 12 | this->menuFlags.set( 13 | Flag::kPausesGame, 14 | Flag::kUsesMenuContext, 15 | Flag::kCustomRendering, 16 | Flag::kApplicationMenu); 17 | 18 | auto dmanager = RE::BSInputDeviceManager::GetSingleton(); 19 | if (!dmanager->IsGamepadEnabled()) { 20 | this->menuFlags.set(Flag::kUsesCursor); 21 | } 22 | 23 | auto scaleform = RE::BSScaleformManager::GetSingleton(); 24 | [[maybe_unused]] bool success = scaleform->LoadMovieEx(this, FILEPATH, [](RE::GFxMovieDef* a_def) -> void { 25 | a_def->SetState( 26 | RE::GFxState::StateType::kLog, 27 | RE::make_gptr>().get()); 28 | }); 29 | assert(success); 30 | 31 | auto view = this->uiMovie; 32 | view->SetMouseCursorCount(this->menuFlags.all(Flag::kUsesCursor) ? 1 : 0); 33 | FunctionManager::AttachSKSEFunctions(view); 34 | 35 | RE::GFxValue main; 36 | success = view->GetVariable(&main, "_root.main"); 37 | assert(success); 38 | FunctionManager::AttachFunction(view, main, "SetSelection"); 39 | } 40 | 41 | std::vector::const_iterator SelectionMenu::CreateSelectionAndWait(const std::vector& a_items) 42 | { 43 | std::unique_lock lock{ _m }; 44 | items = &a_items; 45 | _cvDone = false; 46 | Show(); 47 | _cv.wait(lock, []() { return _cvDone; }); 48 | return selectedItem; 49 | } 50 | 51 | RE::UI_MESSAGE_RESULTS SelectionMenu::ProcessMessage(RE::UIMessage& a_message) 52 | { 53 | using Type = RE::UI_MESSAGE_TYPE; 54 | using Result = RE::UI_MESSAGE_RESULTS; 55 | 56 | switch (*a_message.type) { 57 | case Type::kShow: 58 | { 59 | assert(items); 60 | std::vector values{}; 61 | values.reserve(items->size()); 62 | for (size_t i = 0; i < items->size(); i++) { 63 | const auto& item = items->at(i); 64 | RE::GFxValue value; 65 | this->uiMovie->CreateObject(&value); 66 | RE::GFxValue index{ static_cast(i) }; 67 | value.SetMember("name", item.GetGFxName()); 68 | value.SetMember("type", item.GetGFxValue()); 69 | value.SetMember("index", index); 70 | values.push_back(value); 71 | } 72 | SelectionMenu::selectedItem = items->end(); 73 | this->uiMovie->InvokeNoReturn("_root.main.loadList", values.data(), static_cast(values.size())); 74 | } 75 | return Result::kHandled; 76 | case Type::kForceHide: 77 | case Type::kHide: 78 | { 79 | std::unique_lock lock{ _m }; 80 | _cvDone = true; 81 | _cv.notify_all(); 82 | } 83 | return Result::kHandled; 84 | default: 85 | return RE::IMenu::ProcessMessage(a_message); 86 | } 87 | } 88 | 89 | void SelectionMenu::SetSelection::Call(Params& a_args) 90 | { 91 | assert(a_args.argCount > 0); 92 | const auto index = a_args.args->GetUInt(); 93 | if (index >= items->size()) { 94 | logger::error("Invalid index {} for selection menu", index); 95 | SelectionMenu::selectedItem = items->end(); 96 | } else { 97 | SelectionMenu::selectedItem = items->begin() + index; 98 | } 99 | SelectionMenu::Hide(); 100 | } 101 | 102 | } // namespace Thread::Interface 103 | -------------------------------------------------------------------------------- /src/Thread/Interface/SelectionMenu.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Thread::Interface 4 | { 5 | template 6 | concept Iterable = requires (T a) { 7 | typename T::iterator; 8 | { a.begin() }->std::input_iterator; 9 | { a.end() }->std::input_iterator; 10 | }; 11 | 12 | class SelectionMenu : 13 | public RE::IMenu 14 | { 15 | using GRefCountBaseStatImpl::operator new; 16 | using GRefCountBaseStatImpl::operator delete; 17 | 18 | public: 19 | struct Item 20 | { 21 | private: 22 | std::string name; 23 | std::string value; 24 | 25 | public: 26 | Item(const std::string& a_name, const std::string& a_value) : 27 | name(a_name), value(a_value) {} 28 | 29 | RE::GFxValue GetGFxName() const { return { std::string_view{ name } }; } 30 | RE::GFxValue GetGFxValue() const { return { std::string_view{ value } }; } 31 | }; 32 | 33 | public: 34 | static constexpr std::string_view MENU_NAME{ "SLSelectionMenu" }; 35 | static constexpr std::string_view FILEPATH{ "SexLab\\SelectionMenu" }; 36 | 37 | SelectionMenu(); 38 | ~SelectionMenu() = default; 39 | static void Register() { (RE::UI::GetSingleton()->Register(MENU_NAME, Create), logger::info("Registered Menu: {}", MENU_NAME)); } 40 | static RE::IMenu* Create() { return new SelectionMenu(); } 41 | 42 | static std::vector::const_iterator CreateSelectionAndWait(const std::vector& a_items); 43 | 44 | public: 45 | static void Show() { RE::UIMessageQueue::GetSingleton()->AddMessage(MENU_NAME, RE::UI_MESSAGE_TYPE::kShow, nullptr); } 46 | static void Hide() { RE::UIMessageQueue::GetSingleton()->AddMessage(MENU_NAME, RE::UI_MESSAGE_TYPE::kHide, nullptr); } 47 | static bool IsOpen() { return RE::UI::GetSingleton()->IsMenuOpen(MENU_NAME); } 48 | 49 | protected: 50 | // IMenu 51 | RE::UI_MESSAGE_RESULTS ProcessMessage(RE::UIMessage& a_message) override; 52 | 53 | private: 54 | struct SetSelection : public RE::GFxFunctionHandler 55 | { 56 | void Call(Params& a_args) override; 57 | }; 58 | 59 | private: 60 | static inline std::mutex _m; 61 | static inline std::condition_variable _cv; 62 | static inline bool _cvDone{ false }; 63 | static inline const std::vector* items; 64 | static inline std::vector::const_iterator selectedItem; 65 | }; 66 | } 67 | -------------------------------------------------------------------------------- /src/Thread/NiNode/NiMath.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | namespace Thread::NiNode::NiMath 6 | { 7 | struct Segment : public std::pair { 8 | Segment(RE::NiPoint3 fst, RE::NiPoint3 snd) : 9 | std::pair(fst, snd), isPoint(fst == snd) {} 10 | Segment(RE::NiPoint3 fst) : 11 | std::pair(fst, fst), isPoint(true) {} 12 | 13 | float Length() const { return first.GetDistance(second); } 14 | RE::NiPoint3 Vector() const { return second - first; } 15 | 16 | bool isPoint; 17 | }; 18 | 19 | /// @brief Obtain the shortest segment connecting u to v 20 | Segment ClosestSegmentBetweenSegments(const Segment& u, const Segment& v); 21 | 22 | /// @brief For 3 aligned segments s, u and v if s is between u and v 23 | /// @return if s is between u and v 24 | bool IsSegmentBetweenSegments(const Segment& s, const Segment& u, const Segment& v); 25 | 26 | Eigen::Vector3f ToEigen(const RE::NiPoint3& a_point); 27 | Eigen::Matrix3f ToEigen(const RE::NiMatrix3& a_matrix); 28 | RE::NiPoint3 ToNiPoint(const Eigen::Vector3f& a_point); 29 | RE::NiMatrix3 ToNiMatrix(const Eigen::Matrix3f& a_matrix); 30 | 31 | /// @brief Obtain an angle s.t. ret * vector aligns with ideal (0°) 32 | /// @param vector The vector to align 33 | /// @param ideal The ideal rotation 34 | /// @return A rotation to transform vector with 35 | Eigen::AngleAxisf AlignAxis(const Eigen::Vector3f& vector, const Eigen::Vector3f& ideal); 36 | 37 | /// @brief Create a rotation matrix using Rodrigues Formula 38 | /// @param v The vector to align, normalized 39 | /// @param i The ideal rotation of the vector, normalized 40 | /// @return Rotation matrix s.t. angle(v * M, i) == 0 41 | RE::NiMatrix3 Rodrigue(const RE::NiPoint3& v, const RE::NiPoint3& i); 42 | Eigen::Matrix3f Rodrigue(const Eigen::Vector3f& v, const Eigen::Vector3f& i); 43 | 44 | /// @brief Perform least squares on the given set of points 45 | /// @param a_points The points to perform the analysis on 46 | /// @param a_minlen The returned segments minimum norm 47 | /// @return A best-fit segment reaching from the first point and ending at the last 48 | Segment LeastSquares(const std::vector& a_points, float a_minlen); 49 | Segment LeastSquares(const std::vector& a_points, float a_minlen); 50 | 51 | /// @brief Compute the Angle between v1 and v2, in radians 52 | /// @param v1 The first vector 53 | /// @param v2 The second vector 54 | /// @return The angle, in radians 55 | float GetAngle(const Eigen::Vector3f& v1, const Eigen::Vector3f& v2); 56 | float GetAngleDegree(const RE::NiPoint3& v1, const RE::NiPoint3& v2); 57 | 58 | /// @brief Get the angle when projecting the matrix onto a specific plane 59 | /// @param rot The rotation matrix to extract the angle from 60 | /// @return The rotation when viewed from the specified plane 61 | float GetAngleXZ(const Eigen::Matrix3f& rot); 62 | float GetAngleXY(const Eigen::Matrix3f& rot); 63 | float GetAngleYZ(const Eigen::Matrix3f& rot); 64 | float GetAngleXZ(const RE::NiMatrix3& rot); 65 | float GetAngleXY(const RE::NiMatrix3& rot); 66 | float GetAngleYZ(const RE::NiMatrix3& rot); 67 | 68 | /// @brief Compute the projected component of U relative to V 69 | RE::NiPoint3 ProjectedComponent(RE::NiPoint3 U, RE::NiPoint3 V); 70 | 71 | /// @brief Compute the orthogonal component of U relative to V 72 | RE::NiPoint3 OrthogonalComponent(RE::NiPoint3 U, RE::NiPoint3 V); 73 | 74 | /// @brief constexpr ceil() function 75 | constexpr int IntCeil(float f) 76 | { 77 | const int i = static_cast(f); 78 | return f > i ? i + 1 : i; 79 | } 80 | 81 | } // namespace Thread::NiNode::NiMath 82 | -------------------------------------------------------------------------------- /src/Thread/NiNode/NiPosition.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Node.h" 4 | #include "Registry/Define/Animation.h" 5 | #include "Registry/Define/Sex.h" 6 | #include "Registry/Util/RayCast/ObjectBound.h" 7 | 8 | namespace Thread::NiNode 9 | { 10 | /// @brief Rotate a_schlong s.t. a_target is on the line drawn by its orientation 11 | bool RotateNode(RE::NiPointer niNode, const NiMath::Segment& sNode, const RE::NiPoint3& pTarget, float maxAngleAdjust); 12 | 13 | struct Interaction 14 | { 15 | enum class Action 16 | { 17 | None = 0, 18 | Vaginal, 19 | Anal, 20 | Oral, 21 | Grinding, 22 | Deepthroat, 23 | Skullfuck, 24 | LickingShaft, 25 | FootJob, 26 | HandJob, 27 | Kissing, 28 | Facial, 29 | AnimObjFace, 30 | ToeSucking, 31 | 32 | Total, 33 | }; 34 | 35 | Interaction(RE::ActorPtr a_partner, Action a_action, float a_distance) : 36 | partner(a_partner), action(a_action), distance(a_distance) {} 37 | ~Interaction() = default; 38 | 39 | public: 40 | RE::ActorPtr partner{ 0 }; 41 | Action action{ Action::None }; 42 | float distance{ 0.0f }; 43 | float velocity{ 0.0f }; 44 | 45 | public: 46 | bool operator==(const Interaction& a_rhs) const { return a_rhs.partner == partner && a_rhs.action == action; } 47 | bool operator<(const Interaction& a_rhs) const 48 | { 49 | const auto cmp = partner->GetFormID() <=> a_rhs.partner->GetFormID(); 50 | return cmp == 0 ? action < a_rhs.action : cmp < 0; 51 | } 52 | }; 53 | 54 | struct NiPosition 55 | { 56 | struct Snapshot 57 | { 58 | Snapshot(NiPosition& a_position); 59 | ~Snapshot() = default; 60 | 61 | // This interacting with partner penis 62 | bool GetHeadPenisInteractions(const Snapshot& a_partner, std::shared_ptr a_schlong); 63 | bool GetCrotchPenisInteractions(const Snapshot& a_partner, std::shared_ptr a_schlong); 64 | bool GetHandPenisInteractions(const Snapshot& a_partner, std::shared_ptr a_schlong); 65 | bool GetFootPenisInteractions(const Snapshot& a_partner, std::shared_ptr a_schlong); 66 | // This interacting with partner vagina 67 | bool GetHeadVaginaInteractions(const Snapshot& a_partner); 68 | bool GetVaginaVaginaInteractions(const Snapshot& a_partner); 69 | bool GetVaginaLimbInteractions(const Snapshot& a_partner); 70 | // Misc/Non Sexual 71 | bool GetHeadHeadInteractions(const Snapshot& a_partner); 72 | bool GetHeadFootInteractions(const Snapshot& a_partner); 73 | bool GetHeadAnimObjInteractions(const Snapshot& a_partner); 74 | 75 | public: 76 | std::optional GetMouthStartPoint() const; 77 | std::optional GetThroatPoint() const; 78 | 79 | public: 80 | NiPosition& position; 81 | ObjectBound bHead; 82 | std::vector interactions{}; 83 | 84 | public: 85 | bool operator==(const Snapshot& a_rhs) const { return position == a_rhs.position; } 86 | }; 87 | 88 | public: 89 | NiPosition(RE::Actor* a_owner, Registry::Sex a_sex) : 90 | actor(a_owner), nodes(a_owner, a_sex != Registry::Sex::Female && Registry::GetSex(a_owner) == Registry::Sex::Female), sex(a_sex) {} 91 | ~NiPosition() = default; 92 | 93 | public: 94 | RE::ActorPtr actor; 95 | Node::NodeData nodes; 96 | stl::enumeration sex; 97 | std::set interactions{}; 98 | 99 | public: 100 | bool operator==(const NiPosition& a_rhs) const { return actor == a_rhs.actor; } 101 | }; 102 | 103 | } // namespace Thread::NiNode 104 | -------------------------------------------------------------------------------- /src/Thread/NiNode/NiUpdate.cpp: -------------------------------------------------------------------------------- 1 | #include "NiUpdate.h" 2 | 3 | #include "NiMath.h" 4 | 5 | namespace Thread::NiNode 6 | { 7 | NiInstance::NiInstance(const std::vector& a_positions, const Registry::Scene* a_scene) : 8 | positions([&]() { 9 | std::vector v{}; 10 | v.reserve(a_positions.size()); 11 | for (size_t i = 0; i < a_positions.size(); i++) { 12 | auto& it = a_positions[i]; 13 | auto sex = a_scene->GetNthPosition(i)->data.GetSex().get(); 14 | v.emplace_back(it, sex); 15 | } 16 | return v; 17 | }()) {} 18 | 19 | bool NiInstance::VisitPositions(std::function a_visitor) const 20 | { 21 | std::scoped_lock lk{ _m }; 22 | for (auto&& pos : positions) { 23 | if (a_visitor(pos)) 24 | return true; 25 | } 26 | return false; 27 | } 28 | 29 | void NiInstance::UpdateInteractions(float a_delta) 30 | { 31 | std::unique_lock lk{ _m, std::defer_lock }; 32 | if (!lk.try_lock()) { 33 | return; 34 | } 35 | std::vector snapshots{}; 36 | snapshots.reserve(positions.size()); 37 | for (auto&& it : positions) { 38 | snapshots.emplace_back(it); 39 | } 40 | for (auto&& fst : snapshots) { 41 | GetInteractionsMale(snapshots, fst); 42 | GetInteractionsFemale(snapshots, fst); 43 | GetInteractionsNeutral(snapshots, fst); 44 | } 45 | for (size_t i = 0; i < positions.size(); i++) { 46 | auto& pos = positions[i]; 47 | for (auto&& act : snapshots[i].interactions) { 48 | auto where = pos.interactions.find(act); 49 | if (where == pos.interactions.end()) { 50 | continue; 51 | } 52 | const float delta_dist = act.distance - where->distance; 53 | if (a_delta != 0.0f) { 54 | act.velocity = (where->velocity + (delta_dist / a_delta)) / 2; 55 | } else { 56 | act.velocity = where->velocity; 57 | } 58 | } 59 | positions[i].interactions = { snapshots[i].interactions.begin(), snapshots[i].interactions.end() }; 60 | } 61 | } 62 | 63 | void NiInstance::GetInteractionsMale(std::vector& list, const NiPosition::Snapshot& it) 64 | { 65 | if (it.position.sex.any(Registry::Sex::Female)) 66 | return; 67 | for (auto&& schlong : it.position.nodes.schlongs) { 68 | for (auto&& act : list) { 69 | if (act.GetHeadPenisInteractions(it, schlong)) 70 | break; 71 | if (act.GetHandPenisInteractions(it, schlong)) 72 | break; 73 | if (it == act) 74 | continue; 75 | if (act.GetCrotchPenisInteractions(it, schlong)) { 76 | break; 77 | } 78 | act.GetFootPenisInteractions(it, schlong); 79 | } 80 | } 81 | } 82 | 83 | void NiInstance::GetInteractionsFemale(std::vector& list, const NiPosition::Snapshot& it) 84 | { 85 | if (it.position.sex.any(Registry::Sex::Male)) 86 | return; 87 | for (auto&& snd : list) { 88 | if (it != snd) { 89 | snd.GetVaginaVaginaInteractions(it); 90 | } 91 | snd.GetHeadVaginaInteractions(it); 92 | snd.GetVaginaLimbInteractions(it); 93 | } 94 | } 95 | 96 | void NiInstance::GetInteractionsNeutral(std::vector& list, const NiPosition::Snapshot& it) 97 | { 98 | for (auto&& snd : list) { 99 | if (it != snd) { 100 | snd.GetHeadHeadInteractions(it); 101 | } 102 | snd.GetHeadFootInteractions(it); 103 | snd.GetHeadAnimObjInteractions(it); 104 | } 105 | } 106 | 107 | void NiUpdate::Install() 108 | { 109 | // UpdateThirdPerson 110 | REL::Relocation addr{ RELOCATION_ID(39446, 40522), 0x94 }; 111 | stl::write_thunk_call(addr.address()); 112 | logger::info("Registered Functions"); 113 | } 114 | 115 | void NiUpdate::thunk(RE::NiAVObject* a_obj, RE::NiUpdateData* updateData) 116 | { 117 | func(a_obj, updateData); 118 | static const auto gameDaysPassed = RE::Calendar::GetSingleton()->gameDaysPassed; 119 | if (!gameDaysPassed) { 120 | return; 121 | } 122 | std::scoped_lock lk{ _m }; 123 | const auto ms_passed = gameDaysPassed->value * 24 * 60'000; 124 | static float ms_passed_last = ms_passed; 125 | const auto delta = ms_passed - ms_passed_last; 126 | ms_passed_last = ms_passed; 127 | for (auto&& [_, process] : processes) { 128 | process->UpdateInteractions(delta); 129 | } 130 | } 131 | 132 | std::shared_ptr NiUpdate::Register(RE::FormID a_id, std::vector a_positions, const Registry::Scene* a_scene) noexcept 133 | { 134 | try { 135 | const auto where = std::ranges::find(processes, a_id, [](auto& it) { return it.first; }); 136 | if (where != processes.end()) { 137 | logger::info("Object with ID {:X} already registered. Resetting NiInstance.", a_id); 138 | std::swap(*where, processes.back()); 139 | processes.pop_back(); 140 | } 141 | auto process = std::make_shared(a_positions, a_scene); 142 | return processes.emplace_back(a_id, process).second; 143 | } catch (const std::exception& e) { 144 | logger::error("Failed to register NiInstance: {}", e.what()); 145 | return nullptr; 146 | } catch (...) { 147 | logger::error("Failed to register NiInstance: Unknown error"); 148 | return nullptr; 149 | } 150 | } 151 | 152 | void NiUpdate::Unregister(RE::FormID a_id) noexcept 153 | { 154 | const auto where = std::ranges::find(processes, a_id, [](auto& it) { return it.first; }); 155 | if (where == processes.end()) { 156 | logger::error("No object registered using ID {:X}", a_id); 157 | return; 158 | } 159 | processes.erase(where); 160 | } 161 | 162 | } // namespace Thread::NiNode 163 | -------------------------------------------------------------------------------- /src/Thread/NiNode/NiUpdate.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "NiPosition.h" 4 | #include "Node.h" 5 | #include "Registry/Define/Animation.h" 6 | 7 | namespace Thread::NiNode 8 | { 9 | struct NiInstance 10 | { 11 | friend class NiUpdate; 12 | 13 | public: 14 | NiInstance(const std::vector& a_positions, const Registry::Scene* a_scene); 15 | ~NiInstance() = default; 16 | 17 | bool VisitPositions(std::function a_visitor) const; 18 | 19 | private: 20 | void UpdateInteractions(float a_delta); 21 | void GetInteractionsMale(std::vector& list, const NiPosition::Snapshot& it); 22 | void GetInteractionsFemale(std::vector& list, const NiPosition::Snapshot& it); 23 | void GetInteractionsNeutral(std::vector& list, const NiPosition::Snapshot& it); 24 | 25 | std::vector positions; 26 | mutable std::mutex _m{}; 27 | }; 28 | 29 | class NiUpdate 30 | { 31 | public: 32 | static void Install(); 33 | 34 | static std::shared_ptr Register(RE::FormID a_id, std::vector a_positions, const Registry::Scene* a_scene) noexcept; 35 | static void Unregister(RE::FormID a_id) noexcept; 36 | 37 | private: 38 | friend void stl::write_thunk_call(std::uintptr_t); 39 | static void thunk(RE::NiAVObject* a_obj, RE::NiUpdateData* updateData); 40 | static inline REL::Relocation func; 41 | static inline constexpr std::size_t size{ 5 }; 42 | 43 | static inline std::mutex _m{}; 44 | static inline std::vector>> processes; 45 | }; 46 | 47 | } // namespace Thread::Collision 48 | -------------------------------------------------------------------------------- /src/Thread/Thread.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include "Registry/Library.h" 4 | #include "Thread/NiNode/NiUpdate.h" 5 | 6 | namespace Thread 7 | { 8 | class Instance 9 | { 10 | constexpr static const char* CENTER_REF_NAME{ "CenterAlias" }; 11 | 12 | public: 13 | enum class FurniturePreference 14 | { 15 | Disallow = 0, 16 | Allow = 1, 17 | Prefer = 2, 18 | }; 19 | 20 | enum SceneType 21 | { 22 | Primary = 0, 23 | LeadIn, 24 | Custom, 25 | 26 | Total 27 | }; 28 | 29 | using FurnitureMapping = std::vector>; 30 | using SceneMapping = std::array, SceneType::Total>; 31 | 32 | public: 33 | struct Position 34 | { 35 | Position(RE::BGSRefAlias* alias, RE::Actor* actor, bool submissive, bool dominant); 36 | 37 | RE::BGSRefAlias* alias; 38 | Registry::ActorFragment data; 39 | const Registry::Voice* voice{ nullptr }; 40 | const Registry::Expression* expression{ nullptr }; 41 | std::optional ghostAlpha{ std::nullopt }; 42 | uint8_t uniquePermutations{ 0 }; 43 | }; 44 | 45 | struct Center 46 | { 47 | Center(RE::BGSRefAlias* alias) : 48 | alias(alias) {} 49 | ~Center() = default; 50 | 51 | void SetReference(RE::TESObjectREFR* a_ref, Registry::FurnitureOffset a_offset); 52 | RE::TESObjectREFR* GetRef() { return alias->GetReference(); } 53 | 54 | RE::BGSRefAlias* alias; 55 | Registry::FurnitureOffset offset{}; 56 | const Registry::FurnitureDetails* details{ nullptr }; 57 | }; 58 | 59 | public: 60 | Instance(RE::TESQuest* a_linkedQst, const std::vector& a_submissives, const SceneMapping& a_scenes, FurniturePreference a_furniturePreference); 61 | ~Instance() = default; 62 | 63 | static bool CreateInstance(RE::TESQuest* a_linkedQst, const std::vector a_submissives, const SceneMapping& a_scenes, FurniturePreference a_furniturePreference); 64 | static void DestroyInstance(RE::TESQuest* a_linkedQst); 65 | static Instance* GetInstance(RE::TESQuest* a_linkedQst); 66 | 67 | public: 68 | bool HasNiInstance() const { return niInstance != nullptr; } 69 | NiNode::NiInstance* GetNiInstance() { return niInstance.get(); } 70 | void UnregisterNiInstance() { (NiNode::NiUpdate::Unregister(linkedQst->GetFormID()), niInstance = nullptr); } 71 | 72 | bool ControlsMenu(); 73 | bool TryOpenMenu(); 74 | bool TryCloseMenu(); 75 | void UpdateTimer(float a_timer); 76 | 77 | void AdvanceScene(const Registry::Stage* a_nextStage); 78 | bool SetActiveScene(const Registry::Scene* a_scene); 79 | const Registry::Scene* GetActiveScene() { return activeScene; } 80 | const Registry::Stage* GetActiveStage() { return activeStage; } 81 | std::vector GetThreadScenes(SceneType a_type); 82 | std::vector GetThreadScenes(); 83 | 84 | const std::vector& GetActors(); 85 | Position* GetPosition(RE::Actor* a_actor); 86 | const Registry::PositionInfo* GetPositionInfo(RE::Actor* a_actor); 87 | void UpdatePlacement(RE::Actor* a_actor); 88 | 89 | RE::TESObjectREFR* GetCenterRef() { return center.GetRef(); } 90 | Registry::FurnitureType GetFurnitureType() { return center.offset.type; } 91 | bool ReplaceCenterRef(RE::TESObjectREFR* a_ref); 92 | 93 | bool GetAutoplayEnabled(); 94 | void SetAutoplayEnabled(bool a_enabled); 95 | 96 | void SetEnjoyment(RE::Actor* a_position, float a_enjoyment); 97 | const Registry::Expression* GetExpression(RE::Actor* a_position); 98 | void SetExpression(RE::Actor* a_position, const Registry::Expression* a_expression); 99 | const Registry::Voice* GetVoice(RE::Actor* a_position); 100 | void SetVoice(RE::Actor* a_position, const Registry::Voice* a_voice); 101 | bool IsGhostMode(RE::Actor* a_position); 102 | void SetGhostMode(RE::Actor* a_position, bool a_ghostMode); 103 | int32_t GetUniquePermutations(RE::Actor* a_position); 104 | int32_t GetCurrentPermutation(RE::Actor* a_position); 105 | void SetNextPermutation(RE::Actor* a_position); 106 | 107 | private: 108 | RE::TESQuest* linkedQst; 109 | std::shared_ptr niInstance{ nullptr }; 110 | 111 | Center center; 112 | std::vector positions; 113 | Registry::Coordinate baseCoordinates{}; 114 | std::vector> assignments{}; 115 | std::vector>::iterator activeAssignment{ assignments.end() }; 116 | const Registry::Scene* activeScene{ nullptr }; 117 | const Registry::Stage* activeStage{ nullptr }; 118 | SceneMapping scenes{}; 119 | 120 | private: 121 | enum class CenterSelection 122 | { 123 | Actor, 124 | Furniture, 125 | SelectionMenu, 126 | }; 127 | 128 | RE::Actor* InitializeReferences(const std::vector& a_submissives); 129 | std::vector InitializeScenes(const SceneMapping& a_scenes, FurniturePreference a_furniturePreference); 130 | std::vector& InitializeCenter(RE::Actor* centerAct, FurniturePreference furniturePreference); 131 | bool InitializeFixedCenter(RE::Actor* centerAct, std::vector& prioScenes, REX::EnumSet sceneTypes); 132 | CenterSelection GetSelectionMethod(FurniturePreference furniturePreference); 133 | FurnitureMapping::value_type SelectCenterRefMenu(const FurnitureMapping& a_furnitures, RE::Actor* a_tmpCenter); 134 | FurnitureMapping GetUniqueFurnituesOfTypeInBound(RE::Actor* a_centerAct, REX::EnumSet a_furnitureTypes); 135 | 136 | private: 137 | static inline std::vector> instances{}; 138 | }; 139 | 140 | } // namespace Thread 141 | -------------------------------------------------------------------------------- /src/UserData/Settings.cpp: -------------------------------------------------------------------------------- 1 | #include "Settings.h" 2 | 3 | #include 4 | 5 | void Settings::Initialize() 6 | { 7 | InitializeYAML(); 8 | InitializeINI(); 9 | } 10 | 11 | template 12 | struct is_vector : std::false_type 13 | {}; 14 | 15 | template 16 | struct is_vector> : std::true_type 17 | {}; 18 | 19 | void Settings::InitializeYAML() 20 | { 21 | if (!fs::exists(YAMLPATH)) { 22 | logger::error("No Settings file (yaml) in {}", YAMLPATH); 23 | return; 24 | } 25 | 26 | try { 27 | const auto yaml = YAML::LoadFile(YAMLPATH); 28 | const auto ReadMCM = [&yaml](const char* a_key, T& a_out) { 29 | if (!yaml[a_key].IsDefined()) 30 | return; 31 | const auto val = yaml[a_key].as(); 32 | if constexpr (is_vector>::value) { 33 | if (a_out.size() != val.size()) { 34 | logger::error("Invalid array length for setting {}, expected {} but got {}", a_key, a_out.size(), val.size()); 35 | return; 36 | } 37 | } 38 | a_out = val; 39 | }; 40 | #define MCM_SETTING(STR, DEFAULT) ReadMCM(#STR, STR); 41 | #include "mcm.def" 42 | #undef MCM_SETTING 43 | 44 | logger::info("Finished loading yaml settings"); 45 | } catch (const std::exception& e) { 46 | logger::error("Unable to laod settings, error: {}", e.what()); 47 | } 48 | } 49 | 50 | void Settings::InitializeINI() 51 | { 52 | if (!fs::exists(INIPATH)) { 53 | logger::error("No Settings file (ini) in {}", INIPATH); 54 | return; 55 | } 56 | CSimpleIniA inifile{}; 57 | inifile.SetUnicode(); 58 | const auto ec = inifile.LoadFile(INIPATH); 59 | if (ec < 0) { 60 | logger::error("Failed to read .ini Settings, Error: {}", ec); 61 | return; 62 | } 63 | const auto ReadIni = [&inifile](const char* a_section, const char* a_option, T& a_out) { 64 | if (!inifile.GetValue(a_section, a_option)) 65 | return; 66 | if constexpr (std::is_integral_v) { 67 | a_out = static_cast(inifile.GetLongValue(a_section, a_option)); 68 | } else if constexpr (std::is_floating_point_v) { 69 | a_out = static_cast(inifile.GetDoubleValue(a_section, a_option)); 70 | } else { 71 | logger::error("Unknown Type for option {} in section {}", a_option, a_section); 72 | } 73 | }; 74 | #define INI_SETTING(STR, DEFAULT, CAT) ReadIni(CAT, #STR, STR); 75 | #include "config.def" 76 | #undef INI_SETTING 77 | 78 | if (fPercentageHetero + fPercentageHomo > 100) { 79 | logger::error("Sexuality Percentage Settings must be at most 100.0"); 80 | const auto total = fPercentageHetero + fPercentageHomo; 81 | fPercentageHetero = (fPercentageHetero / total) * 100; 82 | fPercentageHomo = (fPercentageHomo / total) * 100; 83 | logger::info("Adjusted fPercentageHetero to {} and fPercentageHomo to {}", fPercentageHetero, fPercentageHomo); 84 | } 85 | logger::info("Finished loading .ini settings"); 86 | } 87 | 88 | void Settings::InitializeData() 89 | { 90 | const auto& handler = RE::TESDataHandler::GetSingleton(); 91 | const auto root = YAML::LoadFile(SCHLONGPATH); 92 | for (auto&& i : root["Blacklist"]) { 93 | const auto esp = i["ESP"].as(); 94 | logger::info("Looking for non-schlongs in esp {}", esp); 95 | const auto node = i["ID"]; 96 | if (node.IsSequence()) { 97 | for (auto&& id : node) { 98 | const auto formid = id.as(); 99 | const auto fac = handler->LookupFormID(formid, esp); 100 | if (fac) { 101 | logger::info("Adding {} / {}", esp, formid); 102 | SOS_ExcludeFactions.push_back(fac); 103 | } 104 | } 105 | } else { // no sequence, simple entry 106 | const auto formid = node.as(); 107 | const auto fac = handler->LookupFormID(formid, esp); 108 | if (fac) { 109 | logger::info("Adding {} / {}", esp, formid); 110 | SOS_ExcludeFactions.push_back(fac); 111 | } 112 | } 113 | } 114 | } 115 | 116 | void Settings::Save() 117 | { 118 | YAML::Node settings{}; 119 | #define MCM_SETTING(STR, DEFAULT) settings[#STR] = STR; 120 | #include "mcm.def" 121 | #undef MCM_SETTING 122 | 123 | std::ofstream fout{ YAMLPATH }; 124 | fout << settings; 125 | logger::info("Finished saving user settings"); 126 | } 127 | 128 | Settings::KeyType Settings::GetKeyType(uint32_t a_keyCode) 129 | { 130 | const auto get = [](uint32_t key) { 131 | return key >= SKSE::InputMap::kMacro_GamepadOffset ? SKSE::InputMap::GamepadKeycodeToMask(key) : key; 132 | }; 133 | if (a_keyCode == get(Settings::iKeyUp)) 134 | return KeyType::Up; 135 | if (a_keyCode == get(Settings::iKeyDown)) 136 | return KeyType::Down; 137 | if (a_keyCode == get(Settings::iKeyLeft)) 138 | return KeyType::Left; 139 | if (a_keyCode == get(Settings::iKeyRight)) 140 | return KeyType::Right; 141 | if (a_keyCode == get(Settings::iKeyAdvance)) 142 | return KeyType::Select; 143 | if (a_keyCode == get(Settings::iKeyEnd)) 144 | return KeyType::End; 145 | if (a_keyCode == get(Settings::iKeyExtra2)) 146 | return KeyType::Extra2; 147 | if (a_keyCode == get(Settings::iKeyMod)) 148 | return KeyType::Modes; 149 | if (a_keyCode == get(Settings::iKeyReset)) 150 | return KeyType::Reset; 151 | return KeyType::None; 152 | } 153 | 154 | uint32_t Settings::GetKeyCode(KeyType a_keyType) 155 | { 156 | const auto get = [](uint32_t key) { 157 | return key >= SKSE::InputMap::kMacro_GamepadOffset ? SKSE::InputMap::GamepadKeycodeToMask(key) : key; 158 | }; 159 | switch (a_keyType) { 160 | case KeyType::Up: 161 | return get(Settings::iKeyUp); 162 | case KeyType::Down: 163 | return get(Settings::iKeyDown); 164 | case KeyType::Left: 165 | return get(Settings::iKeyLeft); 166 | case KeyType::Right: 167 | return get(Settings::iKeyRight); 168 | case KeyType::Select: 169 | return get(Settings::iKeyAdvance); 170 | case KeyType::End: 171 | return get(Settings::iKeyEnd); 172 | case KeyType::Extra2: 173 | return get(Settings::iKeyExtra2); 174 | case KeyType::Modes: 175 | return get(Settings::iKeyMod); 176 | case KeyType::Reset: 177 | return get(Settings::iKeyReset); 178 | default: 179 | logger::warn("GetKeyCode: Invalid KeyType {}", static_cast(a_keyType)); 180 | return 0; // Return 0 for KeyType::None or invalid KeyType 181 | } 182 | } 183 | -------------------------------------------------------------------------------- /src/UserData/Settings.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | struct StringCmp 4 | { 5 | bool operator()(const std::string& a_lhs, const std::string& a_rhs) const { return _strcmpi(a_lhs.c_str(), a_rhs.c_str()) < 0; } 6 | }; 7 | 8 | struct Settings 9 | { 10 | static void Initialize(); // Pre LoadData 11 | static void InitializeData(); // Post LoadData 12 | static void Save(); 13 | 14 | // --- Key Codes 15 | enum class KeyType 16 | { 17 | None, 18 | Up, 19 | Down, 20 | Left, 21 | Right, 22 | Select, 23 | End, 24 | Extra2, 25 | Modes, 26 | Reset, 27 | }; 28 | static KeyType GetKeyType(uint32_t a_keyCode); 29 | static uint32_t GetKeyCode(KeyType a_keyType); 30 | 31 | // --- MCM 32 | enum class FurnitureSlection 33 | { 34 | Never = 0, 35 | Sometimes = 1, 36 | Always = 2, 37 | AskAlways = 3, 38 | IfNotSubmissive = 4, 39 | }; 40 | 41 | #define MCM_SETTING(STR, DEFAULT) static inline decltype(DEFAULT) STR{ DEFAULT }; 42 | #include "mcm.def" 43 | #undef MCM_SETTING 44 | 45 | using SettingsVariants = std::variant*, std::vector*>; 46 | static inline std::map table{ 47 | #define MCM_SETTING(STR, DEFAULT) { #STR##s, &STR }, 48 | #include "mcm.def" 49 | #undef MCM_SETTING 50 | }; 51 | 52 | // --- INI 53 | #define INI_SETTING(STR, DEFAULT, CAT) static inline decltype(DEFAULT) STR{ DEFAULT }; 54 | #include "config.def" 55 | #undef INI_SETTING 56 | 57 | // --- Misc 58 | static inline std::vector SOS_ExcludeFactions{}; 59 | 60 | private: 61 | static void InitializeYAML(); 62 | static void InitializeINI(); 63 | }; 64 | -------------------------------------------------------------------------------- /src/UserData/StripData.cpp: -------------------------------------------------------------------------------- 1 | #include "StripData.h" 2 | 3 | namespace UserData 4 | { 5 | void StripData::Load() 6 | { 7 | try { 8 | const auto handler = RE::TESDataHandler::GetSingleton(); 9 | _root = YAML::LoadFile(STRIP_PATH); 10 | for (const auto&& mod : _root) { 11 | const auto esp = mod.first.as(); 12 | if (handler->LookupModByName(esp) == nullptr) { 13 | logger::info("Cannot load strip settings for {}. Plugin is not found", esp); 14 | continue; 15 | } 16 | for (const auto&& i : mod.second) { 17 | const auto id = i.first.as(); 18 | const auto formid = handler->LookupFormID(id, esp); 19 | strips[formid] = Strip(i.second.as()); 20 | } 21 | } 22 | } catch (const std::exception& e) { 23 | logger::error("Unable to load data. Error: {}", e.what()); 24 | } 25 | logger::info("Loaded {} custom strip settings", strips.size()); 26 | } 27 | 28 | void StripData::Save() 29 | { 30 | try { 31 | for (auto&& [formid, strip] : strips) { 32 | const auto entry = [&]() -> std::pair { 33 | const auto modidx = formid >> 24; 34 | if (modidx == 0xFE) { 35 | const auto file = RE::TESDataHandler::GetSingleton()->LookupLoadedLightModByIndex(static_cast((formid >> 12) & 0xFFF)); 36 | return { file ? file->fileName : ""s, (0xFFF & formid) }; 37 | } else { 38 | const auto file = RE::TESDataHandler::GetSingleton()->LookupLoadedModByIndex(static_cast(modidx)); 39 | return { file ? file->fileName : ""s, (0xFFFFFF & formid) }; 40 | } 41 | }(); 42 | if (entry.first.empty()) { 43 | logger::error("Unable to find mod for formid {}", formid); 44 | continue; 45 | } 46 | _root[entry.first][entry.second] = static_cast(strip); 47 | } 48 | std::ofstream{ STRIP_PATH } << _root; 49 | } catch (const std::exception& e) { 50 | logger::error("Unable to save StripConfig. Error: {}", e.what()); 51 | } 52 | logger::info("Saved custom strip settings for {} mods", _root.size()); 53 | } 54 | 55 | Strip StripData::CheckStrip(RE::TESForm* a_form) 56 | { 57 | const auto it = strips.find(a_form->formID); 58 | if (it == strips.end() || it->second == Strip::None) { 59 | return CheckKeywords(a_form); 60 | } 61 | return it->second; 62 | } 63 | 64 | Strip StripData::CheckKeywords(RE::TESForm* a_form) 65 | { 66 | const auto& kwd = a_form->As(); 67 | if (kwd) { 68 | if (kwd->ContainsKeywordString("NoStrip")) 69 | return Strip::NoStrip; 70 | if (kwd->ContainsKeywordString("AlwaysStrip")) 71 | return Strip::Always; 72 | } 73 | return Strip::None; 74 | } 75 | } // namespace UserData 76 | -------------------------------------------------------------------------------- /src/UserData/StripData.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace UserData 4 | { 5 | enum class Strip 6 | { 7 | NoStrip = -1, 8 | Always = 1, 9 | 10 | None = 0 11 | }; 12 | 13 | class StripData : 14 | public Singleton 15 | { 16 | public: 17 | Strip CheckStrip(RE::TESForm* a_form); 18 | Strip CheckKeywords(RE::TESForm* a_form); 19 | 20 | public: 21 | void Load(); 22 | void Save(); 23 | 24 | void AddArmor(RE::TESForm* a_form, Strip a_type) { strips[a_form->formID] = a_type; } 25 | void RemoveArmor(RE::TESForm* a_form) { strips.erase(a_form->formID); } 26 | void RemoveArmorAll() { strips.clear(); } 27 | 28 | private: 29 | std::map strips{}; 30 | YAML::Node _root; 31 | }; 32 | } // namespace UserData 33 | -------------------------------------------------------------------------------- /src/UserData/config.def: -------------------------------------------------------------------------------- 1 | // #define INI_SETTING(STR, DEFAULT, CAT) ... 2 | 3 | INI_SETTING(fFurniturePreference, 0.75f, "Animation") 4 | INI_SETTING(fFurnitureScanRadius, 750.0f, "Animation") 5 | INI_SETTING(fMinScale, 0.88f, "Animation") 6 | INI_SETTING(bAllowDead, false, "Animation") 7 | INI_SETTING(fMinSetupTime, 0.7f, "Animation") 8 | INI_SETTING(fAdjustStepSizeIncrement, 0.1f, "Animation") 9 | INI_SETTING(bAdjustNodes, true, "Animation") 10 | INI_SETTING(fGhostModeAlpha, 0.6f, "Animation") 11 | INI_SETTING(fFurnitureSquare, 32.0f, "Animation") 12 | INI_SETTING(fFurnitureSquareHeight, 128.0f, "Animation") 13 | INI_SETTING(fFurnitureSquareFloorSkip, 16.0f, "Animation") 14 | INI_SETTING(fFurnitureSquareStepSize, 8.0f, "Animation") 15 | INI_SETTING(fFurnitureTiltTolerance, 10.0f, "Animation") 16 | 17 | INI_SETTING(iScoreAcceptThreshold, 1, "Filter") 18 | INI_SETTING(iWeightSexStrict, 100, "Filter") 19 | INI_SETTING(iWeightSexLight, 50, "Filter") 20 | INI_SETTING(iWeightSexMismatch, -15, "Filter") 21 | INI_SETTING(iWeightVampire, 10, "Filter") 22 | INI_SETTING(iWeightSubmissive, 20, "Filter") 23 | INI_SETTING(iWeightUnconscious, 10, "Filter") 24 | INI_SETTING(fScaleTolerance, 0.1f, "Filter") 25 | INI_SETTING(iWeightScale, 10, "Filter") 26 | 27 | INI_SETTING(bAshHopper, true, "Race") 28 | INI_SETTING(bBear, true, "Race") 29 | INI_SETTING(bBoar, true, "Race") 30 | INI_SETTING(bBoarMounted, true, "Race") 31 | INI_SETTING(bBoarSingle, true, "Race") 32 | INI_SETTING(bCanine, true, "Race") 33 | INI_SETTING(bChaurus, true, "Race") 34 | INI_SETTING(bChaurusHunter, true, "Race") 35 | INI_SETTING(bChaurusReaper, true, "Race") 36 | INI_SETTING(bChicken, true, "Race") 37 | INI_SETTING(bCow, true, "Race") 38 | INI_SETTING(bDeer, true, "Race") 39 | INI_SETTING(bDog, true, "Race") 40 | INI_SETTING(bDragon, true, "Race") 41 | INI_SETTING(bDragonPriest, true, "Race") 42 | INI_SETTING(bDraugr, true, "Race") 43 | INI_SETTING(bDwarvenBallista, true, "Race") 44 | INI_SETTING(bDwarvenCenturion, true, "Race") 45 | INI_SETTING(bDwarvenSphere, true, "Race") 46 | INI_SETTING(bDwarvenSpider, true, "Race") 47 | INI_SETTING(bFalmer, true, "Race") 48 | INI_SETTING(bFlameAtronach, true, "Race") 49 | INI_SETTING(bFox, true, "Race") 50 | INI_SETTING(bFrostAtronach, true, "Race") 51 | INI_SETTING(bGargoyle, true, "Race") 52 | INI_SETTING(bGiant, true, "Race") 53 | INI_SETTING(bGiantSpider, true, "Race") 54 | INI_SETTING(bGoat, true, "Race") 55 | INI_SETTING(bHagraven, true, "Race") 56 | INI_SETTING(bHare, true, "Race") 57 | INI_SETTING(bHorker, true, "Race") 58 | INI_SETTING(bHorse, true, "Race") 59 | INI_SETTING(bIceWraith, true, "Race") 60 | INI_SETTING(bLargeSpider, true, "Race") 61 | INI_SETTING(bLurker, true, "Race") 62 | INI_SETTING(bMammoth, true, "Race") 63 | INI_SETTING(bMudcrab, true, "Race") 64 | INI_SETTING(bNetch, true, "Race") 65 | INI_SETTING(bRiekling, true, "Race") 66 | INI_SETTING(bSabrecat, true, "Race") 67 | INI_SETTING(bSeeker, true, "Race") 68 | INI_SETTING(bSkeever, true, "Race") 69 | INI_SETTING(bSlaughterfish, true, "Race") 70 | INI_SETTING(bSpider, true, "Race") 71 | INI_SETTING(bSpriggan, true, "Race") 72 | INI_SETTING(bStormAtronach, true, "Race") 73 | INI_SETTING(bTroll, true, "Race") 74 | INI_SETTING(bVampireLord, true, "Race") 75 | INI_SETTING(bWerewolf, true, "Race") 76 | INI_SETTING(bWisp, true, "Race") 77 | INI_SETTING(bWispmother, true, "Race") 78 | INI_SETTING(bWolf, true, "Race") 79 | 80 | INI_SETTING(fPercentageHetero, 80.0f, "Statistics") 81 | INI_SETTING(fPercentageHomo, 9.0f, "Statistics") 82 | 83 | INI_SETTING(fAnimObjDist, 23.0f, "Distance") 84 | INI_SETTING(fAngleCunnilingus, 130.0f, "Distance") 85 | INI_SETTING(fDistanceFoot, 13.3f, "Distance") 86 | INI_SETTING(fDistanceFootMouth, 4.3f, "Distance") 87 | INI_SETTING(fDistanceHand, 8.3f, "Distance") 88 | INI_SETTING(fDistanceMouth, 5.4f, "Distance") 89 | INI_SETTING(fDistanceCrotch, 8.0f, "Distance") 90 | INI_SETTING(fAngleToHeadTolerance, 20.0f, "Distance") 91 | INI_SETTING(fAngleToHeadSidewaysTolerance, 50.0f, "Distance") 92 | INI_SETTING(fAngleToHeadFrontalTolerance, 45.0f, "Distance") 93 | INI_SETTING(fCloseToHeadRatio, 1.5f, "Distance") 94 | INI_SETTING(fThroatToleranceRadius, 0.25f, "Distance") 95 | INI_SETTING(fAngleKissing, 70.0f, "Distance") 96 | INI_SETTING(fAngleGrinding, 70.0f, "Distance") 97 | INI_SETTING(fAngleGrindingFF, 130.0f, "Distance") 98 | INI_SETTING(fAnglePenetration, 90.0f, "Distance") 99 | INI_SETTING(fPenetrationVaginalTolerance, 1.0f, "Distance") 100 | INI_SETTING(fPenetrationVaginalToleranceRepeat, 3.5f, "Distance") 101 | INI_SETTING(fPenetrationAnalToleranceRepeat, 3.0f, "Distance") 102 | INI_SETTING(fAdjustHeadLimit, 33.0f, "Distance") 103 | INI_SETTING(fAdjustSchlongLimit, 90.0f, "Distance") 104 | INI_SETTING(fAdjustSchlongVaginalLimit, 55.0f, "Distance") 105 | 106 | INI_SETTING(fEnjGrinding, 0.075f, "Enjoyment") 107 | INI_SETTING(fEnjHandActive, 0.375f, "Enjoyment") 108 | INI_SETTING(fEnjHandPassive, 0.475f, "Enjoyment") 109 | INI_SETTING(fEnjFootActive, 0.175f, "Enjoyment") 110 | INI_SETTING(fEnjFootPassive, 0.3f, "Enjoyment") 111 | INI_SETTING(fEnjOralActive, 0.525f, "Enjoyment") 112 | INI_SETTING(fEnjOralPassive, 0.575f, "Enjoyment") 113 | INI_SETTING(fEnjVaginalActive, 0.725f, "Enjoyment") 114 | INI_SETTING(fEnjVaginalPassive, 0.825f, "Enjoyment") 115 | INI_SETTING(fEnjAnalActive, 0.925f, "Enjoyment") 116 | INI_SETTING(fEnjAnalPassive, 1.025f, "Enjoyment") 117 | INI_SETTING(fFactorNonInterEnjRaise, 0.6f, "Enjoyment") 118 | INI_SETTING(fFactorInterEnjRaise, 1.2f, "Enjoyment") 119 | INI_SETTING(fTimeMax, 30.0f, "Enjoyment") 120 | INI_SETTING(fRequiredXP, 50.0f, "Enjoyment") 121 | INI_SETTING(fBoostTime, 30.0f, "Enjoyment") 122 | INI_SETTING(fPenaltyTime, 80.0f, "Enjoyment") 123 | INI_SETTING(iMaxNoPainOrgasmsM, 1, "Enjoyment") 124 | INI_SETTING(iMaxNoPainOrgasmsF, 2, "Enjoyment") 125 | -------------------------------------------------------------------------------- /src/UserData/mcm.def: -------------------------------------------------------------------------------- 1 | // #define MCM_SETTING(STR, DEFAULT) ... 2 | 3 | MCM_SETTING(bDebugMode, false) 4 | MCM_SETTING(bCreatureGender, false) 5 | MCM_SETTING(bAllowCreatures, false) 6 | MCM_SETTING(bRedressVictim, true) 7 | MCM_SETTING(bUseLipSync, false) 8 | MCM_SETTING(bUseExpressions, false) 9 | MCM_SETTING(bDisablePlayer, false) 10 | MCM_SETTING(bAutoTFC, false) 11 | MCM_SETTING(bAutoAdvance, true) 12 | MCM_SETTING(bOrgasmEffects, false) 13 | MCM_SETTING(bShowInMap, false) 14 | MCM_SETTING(bSetAnimSpeedByEnjoyment, false) 15 | MCM_SETTING(bDisableTeleport, true) 16 | MCM_SETTING(bDisableScale, false) 17 | MCM_SETTING(bUndressAnimation, false) 18 | MCM_SETTING(bSubmissivePlayer, false) 19 | MCM_SETTING(bSubmissiveTarget, false) 20 | MCM_SETTING(bMatchMakerActive, false) 21 | MCM_SETTING(bLipsFixedValue, true) 22 | 23 | MCM_SETTING(iAskBed, 1) 24 | MCM_SETTING(iNPCBed, 1) 25 | MCM_SETTING(iUseFade, 1) 26 | MCM_SETTING(iClimaxType, 0) 27 | MCM_SETTING(iFilterStrictness, 2) 28 | MCM_SETTING(iLipsSoundTime, 1) 29 | 30 | MCM_SETTING(iLovenseStrength, 10) 31 | MCM_SETTING(iLovenseStrengthOrgasm, 20) 32 | MCM_SETTING(fLovenseDurationOrgasm, 8) 33 | 34 | // KeyBinds/Scene Control Related 35 | MCM_SETTING(iKeyUp, 0x11) // W 36 | MCM_SETTING(iKeyDown, 0x1F) // S 37 | MCM_SETTING(iKeyLeft, 0x1E) // A 38 | MCM_SETTING(iKeyRight, 0x20) // D 39 | MCM_SETTING(iKeyAdvance, 0x39) // Space 40 | MCM_SETTING(iKeyEnd, 0xCF) // End 41 | MCM_SETTING(iKeyExtra2, 0x12) // E 42 | MCM_SETTING(iKeyMod, 0x2A) // Shift 43 | MCM_SETTING(iKeyReset, 0x1D) // Ctrl 44 | 45 | MCM_SETTING(iToggleFreeCamera, 81) 46 | MCM_SETTING(iTargetActor, 49) 47 | 48 | // TODO: Add these to MCM 49 | MCM_SETTING(bAdjustTargetStage, false) 50 | MCM_SETTING(fAdjustStepSize, 0.5f) 51 | MCM_SETTING(bAdjustStage, true) 52 | MCM_SETTING(bHideHUD, false) 53 | 54 | // MCM_SETTING(iAdjustStage, 157) 55 | // MCM_SETTING(iBackwards, 54) 56 | // MCM_SETTING(iChangeAnimation, 24) 57 | // MCM_SETTING(iChangePositions, 13) 58 | // MCM_SETTING(iAdjustChange, 37) 59 | // MCM_SETTING(iAdjustForward, 38) 60 | // MCM_SETTING(iAdjustSideways, 40) 61 | // MCM_SETTING(iAdjustUpward, 39) 62 | // MCM_SETTING(iRealignActors, 26) 63 | // MCM_SETTING(iMoveScene, 27) 64 | // MCM_SETTING(iRestoreOffsets, 12) 65 | // MCM_SETTING(iRotateScene, 22) 66 | // MCM_SETTING(iAdjustSchlong, 46) 67 | 68 | MCM_SETTING(fShakeStrength, 0.7f) 69 | MCM_SETTING(fAutoSUCSM, 5.0f) 70 | MCM_SETTING(fMaleVoiceDelay, 5.0f) 71 | MCM_SETTING(fFemaleVoiceDelay, 4.0f) 72 | MCM_SETTING(fVoiceVolume, 1.0f) 73 | MCM_SETTING(fSFXDelay, 3.0f) 74 | MCM_SETTING(fSFXVolume, 1.0f) 75 | 76 | MCM_SETTING(bUseCum, true) 77 | MCM_SETTING(fCumTimer, 180.0f) 78 | MCM_SETTING(fCumAlpha, 1.0f) 79 | MCM_SETTING(bSwimmingCleans, true) 80 | 81 | MCM_SETTING(sRequiredTags, ""s) 82 | MCM_SETTING(sExcludedTags, ""s) 83 | MCM_SETTING(sOptionalTags, ""s) 84 | 85 | MCM_SETTING(iStripForms, std::vector({ 1032555423, 1, 1032555423, 1, 806420, 0, 352928413, 1 })) 86 | MCM_SETTING(fTimers, std::vector({ 10.0f, 15.0f, 25.0f, 7.0f })) -------------------------------------------------------------------------------- /src/Util/Combinatorics.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Combinatorics 4 | { 5 | enum class CResult 6 | { 7 | Next, 8 | Stop, 9 | }; 10 | 11 | template 12 | void ForEachCombination(const std::vector>& a_iterative, std::function::const_iterator>&)> a_iterator) 13 | { 14 | std::vector::const_iterator> it; 15 | for (auto& subvec : a_iterative) 16 | it.push_back(subvec.begin()); 17 | 18 | assert(it.size() == a_iterative.size()); 19 | const auto K = it.size() - 1; 20 | while (it[0] != a_iterative[0].end()) { 21 | if (a_iterator(it) == CResult::Stop) 22 | return; 23 | // Next 24 | ++it[K]; 25 | for (auto i = K; i > 0 && it[i] == a_iterative[i].end(); i--) { 26 | it[i] = a_iterative[i].begin(); 27 | ++it[i - 1]; 28 | } 29 | } 30 | } 31 | 32 | } // namespace Combinatorics 33 | -------------------------------------------------------------------------------- /src/Util/FormLookup.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Util 4 | { 5 | static inline RE::FormID FormFromString(const std::string_view& a_string, int base) 6 | { 7 | const auto split = a_string.find('|'); 8 | const auto formIdStr = a_string.substr(0, split); 9 | RE::FormID formid; 10 | const auto offset = a_string.starts_with("0x") ? 2 : 0; 11 | const auto [ptr, res] = std::from_chars( 12 | formIdStr.data() + offset, 13 | formIdStr.data() + formIdStr.size(), 14 | formid, base); 15 | if (res != std::errc()) { 16 | // logger::error("Invalid form ID: {} for base: {}", formIdStr, base); 17 | return 0; 18 | } else if (split == std::string_view::npos) { 19 | return formid; 20 | } else { 21 | return RE::TESDataHandler::GetSingleton()->LookupFormID(formid, a_string.substr(split + 1)); 22 | } 23 | } 24 | 25 | static inline RE::FormID FormFromString(const std::string_view& a_string) 26 | { 27 | const auto base = a_string.starts_with("0x") ? 16 : 10; 28 | return FormFromString(a_string, base); 29 | } 30 | 31 | template >> 32 | static inline T* FormFromString(const std::string_view& a_string, int base) 33 | { 34 | const auto id = FormFromString(a_string, base); 35 | return id == 0 ? nullptr : RE::TESForm::LookupByID(id); 36 | } 37 | 38 | template >> 39 | static inline T* FormFromString(const std::string_view& a_string) 40 | { 41 | const auto base = a_string.starts_with("0x") ? 16 : 10; 42 | return FormFromString(a_string, base); 43 | } 44 | 45 | template ::value>> 46 | static inline T FormFromString(const std::string_view& a_string, int base) 47 | { 48 | using U = std::remove_pointer::type; 49 | return FormFromString(a_string, base); 50 | } 51 | 52 | template ::value>> 53 | static inline T FormFromString(const std::string_view& a_string) 54 | { 55 | using U = std::remove_pointer::type; 56 | return FormFromString(a_string); 57 | } 58 | 59 | static inline std::string FormToString(RE::TESForm* a_form) 60 | { 61 | auto file = a_form->GetFile(0); 62 | if (!file) { 63 | return std::format("0x{:X}", a_form->GetFormID()); 64 | } else { 65 | return std::format("0x{:X}|{}", a_form->GetLocalFormID(), file->GetFilename()); 66 | } 67 | } 68 | 69 | } // namespace Util 70 | -------------------------------------------------------------------------------- /src/Util/Misc.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Util 4 | { 5 | inline void PrintConsole(const std::string& a_str) 6 | { 7 | const auto console = RE::ConsoleLog::GetSingleton(); 8 | if (a_str.empty()) 9 | return; 10 | else if (a_str.size() < 1000) 11 | console->Print(a_str.data()); 12 | else { // Large strings printed to console crash the game - truncate it 13 | size_t i = 0; 14 | do { 15 | constexpr auto maxchar = 950; 16 | auto print = a_str.substr(i, i + maxchar); 17 | print += '\n'; 18 | i += maxchar; 19 | console->Print(print.data()); 20 | } while (i < a_str.size()); 21 | } 22 | } 23 | 24 | RE::TESActorBase* GetLeveledActorBase(RE::Actor* a_actor) 25 | { 26 | const auto base = a_actor->GetTemplateActorBase(); 27 | return base ? base : a_actor->GetActorBase(); 28 | } 29 | 30 | // using _GetFormEditorID = const char* (*)(std::uint32_t); 31 | // inline std::string GetEditorID(RE::TESForm* a_form) 32 | // { 33 | // static auto tweaks = GetModuleHandle("po3_Tweaks"); 34 | // static auto func = reinterpret_cast<_GetFormEditorID>(GetProcAddress(tweaks, "GetFormEditorID")); 35 | // if (func) { 36 | // return func(a_form->formID); 37 | // } 38 | // return {}; 39 | // } 40 | } -------------------------------------------------------------------------------- /src/Util/Random.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | #include 4 | 5 | template 6 | concept Container = requires(T a) { 7 | typename T::value_type; 8 | { a.begin() } -> std::input_iterator; 9 | { a.end() } -> std::input_iterator; 10 | { a.size() } -> std::convertible_to; 11 | }; 12 | 13 | struct Random 14 | { 15 | Random() = delete; 16 | 17 | static inline std::mt19937 eng{ std::random_device{}() }; 18 | 19 | template 20 | static inline T draw(T a_min, T a_max) 21 | { 22 | if constexpr (std::is_integral_v) 23 | return std::uniform_int_distribution{ a_min, a_max }(eng); 24 | else 25 | return std::uniform_real_distribution{ a_min, a_max }(eng); 26 | } 27 | 28 | template 29 | static inline typename T::value_type draw(const T& a_container) 30 | { 31 | if (a_container.empty()) { 32 | throw std::out_of_range("Cannot draw from an empty container."); 33 | } 34 | std::uniform_int_distribution dist{ 0, a_container.size() - 1 }; 35 | return *(a_container.begin() + dist(eng)); 36 | } 37 | 38 | template 39 | static inline void shuffle(V& a_container) 40 | { 41 | std::ranges::shuffle(a_container, eng); 42 | } 43 | 44 | static inline std::string generateUUID() 45 | { 46 | constexpr std::string_view v = "0123456789abcdef"; 47 | constexpr std::string_view templateStr{ "xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" }; 48 | std::uniform_int_distribution dist{ 0, v.size() }; 49 | 50 | std::string ret{ templateStr }; 51 | for (int i = 0; i < ret.size(); i++) { 52 | if (ret[i] == 'x') { 53 | ret[i] = v[dist(eng)]; 54 | } 55 | } 56 | return ret; 57 | } 58 | }; 59 | -------------------------------------------------------------------------------- /src/Util/Script.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Script 4 | { 5 | using VM = RE::BSScript::Internal::VirtualMachine; 6 | using ObjectPtr = RE::BSTSmartPointer; 7 | using ArrayPtr = RE::BSTSmartPointer; 8 | using TypePtr = RE::BSTSmartPointer; 9 | using CallbackPtr = RE::BSTSmartPointer; 10 | using Args = RE::BSScript::IFunctionArguments; 11 | using RawType = RE::BSScript::TypeInfo::RawType; 12 | 13 | inline RE::VMHandle GetHandle(const RE::TESForm* a_form) 14 | { 15 | auto vm = VM::GetSingleton(); 16 | auto policy = vm->GetObjectHandlePolicy(); 17 | return policy->GetHandleForObject(a_form->GetFormType(), a_form); 18 | } 19 | 20 | inline ObjectPtr GetScriptObject(const RE::TESForm* a_form, const char* a_class, bool a_create = false) 21 | { 22 | auto vm = VM::GetSingleton(); 23 | auto handle = GetHandle(a_form); 24 | 25 | ObjectPtr object = nullptr; 26 | bool found = vm->FindBoundObject(handle, a_class, object); 27 | if (!found && a_create) { 28 | vm->CreateObject2(a_class, object); 29 | vm->BindObject(object, handle, false); 30 | } 31 | 32 | return object; 33 | } 34 | 35 | template 36 | inline T GetProperty(ObjectPtr a_obj, const RE::BSFixedString& a_prop) 37 | { 38 | auto var = a_obj->GetProperty(a_prop); 39 | assert(var); 40 | return RE::BSScript::UnpackValue(var); 41 | } 42 | 43 | template || std::is_floating_point_v>> 44 | inline T GetTrivialProperty(ObjectPtr a_obj, const RE::BSFixedString& a_prop) 45 | { 46 | const auto var = a_obj->GetProperty(a_prop); 47 | const auto type = var->GetType().GetRawType(); 48 | switch (type) { 49 | case RawType::kBool: 50 | return static_cast(RE::BSScript::UnpackValue(var)); 51 | case RawType::kInt: 52 | return static_cast(RE::BSScript::UnpackValue(var)); 53 | case RawType::kFloat: 54 | return static_cast(RE::BSScript::UnpackValue(var)); 55 | default: 56 | logger::error("Not a trivial type: {}", std::to_underlying(type)); 57 | break; 58 | } 59 | return 0; 60 | } 61 | 62 | template 63 | inline void SetProperty(ObjectPtr a_obj, const RE::BSFixedString& a_prop, T a_val) 64 | { 65 | auto var = a_obj->GetProperty(a_prop); 66 | assert(var); 67 | RE::BSScript::PackValue(var, a_val); 68 | } 69 | 70 | inline bool DispatchMethodCall(ObjectPtr a_obj, const RE::BSFixedString& a_fnName, CallbackPtr a_callback, Args* a_args) 71 | { 72 | auto vm = VM::GetSingleton(); 73 | return vm->DispatchMethodCall(a_obj, a_fnName, a_args, a_callback); 74 | } 75 | 76 | template 77 | inline bool DispatchMethodCall(ObjectPtr a_obj, const RE::BSFixedString& a_fnName, CallbackPtr a_callback, Args&&... a_args) 78 | { 79 | auto args = RE::MakeFunctionArguments(std::forward(a_args)...); 80 | return DispatchMethodCall(a_obj, a_fnName, a_callback, args); 81 | } 82 | 83 | inline bool DispatchStaticCall(const RE::BSFixedString& a_class, const RE::BSFixedString& a_fnName, CallbackPtr a_callback, Args* a_args) 84 | { 85 | auto vm = VM::GetSingleton(); 86 | return vm->DispatchStaticCall(a_class, a_fnName, a_args, a_callback); 87 | } 88 | 89 | template 90 | inline bool DispatchStaticCall(const RE::BSFixedString& a_class, const RE::BSFixedString& a_fnName, CallbackPtr a_callback, Args&&... a_args) 91 | { 92 | auto args = RE::MakeFunctionArguments(std::forward(a_args)...); 93 | return DispatchStaticCall(a_class, a_fnName, a_callback, args); 94 | } 95 | } 96 | -------------------------------------------------------------------------------- /src/Util/Singleton.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | template 4 | class Singleton 5 | { 6 | protected: 7 | Singleton() = default; 8 | ~Singleton() = default; 9 | 10 | public: 11 | static T* GetSingleton() 12 | { 13 | static T singleton; 14 | return &singleton; 15 | } 16 | 17 | public: 18 | Singleton(const Singleton&) = delete; 19 | Singleton(Singleton&&) = delete; 20 | 21 | Singleton& operator=(const Singleton&) = delete; 22 | Singleton& operator=(Singleton&&) = delete; 23 | }; -------------------------------------------------------------------------------- /src/Util/StringUtil.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Util 4 | { 5 | #pragma warning(push) 6 | #pragma warning(disable : 4244) 7 | #define STR_TRANSFORM(f) std::transform(str.cbegin(), str.cend(), str.begin(), [](int c) { return f(c); }); 8 | 9 | template 10 | constexpr void ToLower(T& str) 11 | { 12 | STR_TRANSFORM(std::tolower); 13 | } 14 | template 15 | constexpr void ToUpper(T& str) 16 | { 17 | STR_TRANSFORM(std::toupper); 18 | } 19 | constexpr std::string CastLower(std::string str) 20 | { 21 | ToLower(str); 22 | return str; 23 | } 24 | constexpr std::string CastUpper(std::string str) 25 | { 26 | ToUpper(str); 27 | return str; 28 | } 29 | 30 | #undef STR_TRANSFORM 31 | #pragma warning(pop) 32 | 33 | inline std::vector StringSplit(const std::string_view& a_view, const std::string_view& a_delim) 34 | { 35 | namespace views = std::ranges::views; 36 | return a_view | views::split(a_delim) | views::transform([](auto&& subrange) { 37 | auto word = std::string_view(&*subrange.begin(), std::ranges::distance(subrange)); 38 | while (!word.empty() && std::isspace(word.front())) 39 | word.remove_prefix(1); 40 | while (!word.empty() && std::isspace(word.back())) 41 | word.remove_suffix(1); 42 | return word; 43 | }) | views::filter([](auto&& word) { return !word.empty(); }) | 44 | std::ranges::to(); 45 | } 46 | 47 | inline std::vector StringSplitToOwned(const std::string_view& a_view, const std::string_view& a_delim) 48 | { 49 | const auto ret = StringSplit(a_view, a_delim); 50 | return std::vector(ret.cbegin(), ret.cend()); 51 | } 52 | 53 | template 54 | static inline std::string StringJoin(const std::vector& a_vec, std::string_view a_delimiter) 55 | { 56 | std::string ret; 57 | if (a_vec.empty()) 58 | return ret; 59 | ret.reserve(a_vec.size() * 2); // reserve twice the size to avoid reallocations 60 | for (const auto& str : a_vec) { 61 | ret += str; 62 | ret += a_delimiter; 63 | } 64 | ret.resize(ret.size() - a_delimiter.length()); // remove last delimiter 65 | return ret; 66 | } 67 | 68 | static inline bool IsNumericString(const std::string& a_str) 69 | { 70 | static const std::regex pattern{ R"(^[+-]?(?:(0x)?[0-9A-Fa-f]+|\d+|\d*\.\d+)$)" }; 71 | return std::regex_match(a_str, pattern); 72 | } 73 | 74 | static inline std::vector FilterByPrefix(std::vector a_strs, const std::string& a_prefix) 75 | { 76 | std::erase_if(a_strs, [&a_prefix](std::string& a_str) { return !a_str.starts_with(a_prefix); }); 77 | return a_strs; 78 | } 79 | 80 | static inline std::string Replace(std::string str, const std::string& substr1, const std::string& substr2) 81 | { 82 | for (size_t index = str.find(substr1, 0); index != std::string::npos && substr1.length(); index = str.find(substr1, index + substr2.length())) 83 | str.replace(index, substr1.length(), substr2); 84 | return str; 85 | } 86 | 87 | } // namespace String 88 | -------------------------------------------------------------------------------- /src/Util/World.h: -------------------------------------------------------------------------------- 1 | #pragma once 2 | 3 | namespace Util 4 | { 5 | inline void ForEachObjectInRange(RE::TESObjectREFR* a_center, float a_radius, std::function a_forEachFunc) 6 | { 7 | const auto TES = RE::TES::GetSingleton(); 8 | const auto center_coords = a_center->GetPosition(); 9 | if (const auto interior = TES->interiorCell; interior) { 10 | interior->ForEachReferenceInRange(center_coords, a_radius, a_forEachFunc); 11 | } else if (const auto grids = TES->gridCells; grids) { 12 | auto gridLength = grids->length; 13 | if (gridLength > 0) { 14 | float yPlus = center_coords.y + a_radius; 15 | float yMinus = center_coords.y - a_radius; 16 | float xPlus = center_coords.x + a_radius; 17 | float xMinus = center_coords.x - a_radius; 18 | for (uint32_t x = 0, y = 0; (x < gridLength && y < gridLength); x++, y++) { 19 | const auto gridcell = grids->GetCell(x, y); 20 | if (gridcell && gridcell->IsAttached()) { 21 | auto cellCoords = gridcell->GetCoordinates(); 22 | if (!cellCoords) 23 | continue; 24 | float worldX = cellCoords->worldX; 25 | float worldY = cellCoords->worldY; 26 | if (worldX < xPlus && (worldX + 4096.0) > xMinus && worldY < yPlus && (worldY + 4096.0) > yMinus) { 27 | gridcell->ForEachReferenceInRange(center_coords, a_radius, a_forEachFunc); 28 | } 29 | } 30 | } 31 | } 32 | } 33 | } 34 | } // namespace CellCrawler 35 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | #include "Papyrus/Papyrus.h" 2 | #include "Registry/CumFx.h" 3 | #include "Registry/Library.h" 4 | #include "Registry/Stats.h" 5 | #include "Serialization.h" 6 | #include "Thread/Interface/SceneMenu.h" 7 | #include "Thread/Interface/SelectionMenu.h" 8 | #include "Thread/NiNode/NiUpdate.h" 9 | #include "UserData/StripData.h" 10 | 11 | // class EventHandler : 12 | // public Singleton, 13 | // public RE::BSTEventSink 14 | // { 15 | // public: 16 | // using EventResult = RE::BSEventNotifyControl; 17 | 18 | // void Register() 19 | // { 20 | // RE::PlayerCharacter::GetSingleton()->AddAnimationGraphEventSink(this); 21 | // } 22 | 23 | // public: 24 | // EventResult ProcessEvent(const RE::BSAnimationGraphEvent* a_event, RE::BSTEventSource*) override 25 | // { 26 | // if (!a_event || a_event->holder->IsNot(RE::FormType::ActorCharacter)) 27 | // return EventResult::kContinue; 28 | 29 | // auto source = const_cast(a_event->holder->As()); 30 | // if (source->IsWeaponDrawn()) 31 | // logger::info("Tag = {} | Payload = {}", a_event->tag, a_event->payload); 32 | // return EventResult::kContinue; 33 | // } 34 | // }; 35 | 36 | static void SKSEMessageHandler(SKSE::MessagingInterface::Message* message) 37 | { 38 | switch (message->type) { 39 | case SKSE::MessagingInterface::kPostLoad: 40 | Settings::Initialize(); 41 | Registry::CumFx::GetSingleton()->Initialize(); 42 | break; 43 | case SKSE::MessagingInterface::kDataLoaded: 44 | if (!GameForms::LoadData()) { 45 | logger::critical("Unable to load esp objects"); 46 | const auto err = 47 | "Some game objects could not be loaded. This is usually due to a required game plugin not being loaded in your game." 48 | "See the SexLabUtil.log for more information about which form failed to load." 49 | "\n\nExit Game now? (Recommended yes)"; 50 | if (REX::W32::MessageBoxA(nullptr, err, "SexLab p+ Load Data", 0x00000004) == 6) 51 | std::_Exit(EXIT_FAILURE); 52 | return; 53 | } 54 | Registry::Library::GetSingleton()->Initialize(); 55 | UserData::StripData::GetSingleton()->Load(); 56 | Settings::InitializeData(); 57 | break; 58 | case SKSE::MessagingInterface::kSaveGame: 59 | std::thread([]() { 60 | Settings::Save(); 61 | Registry::Library::GetSingleton()->Save(); 62 | UserData::StripData::GetSingleton()->Save(); 63 | }).detach(); 64 | break; 65 | case SKSE::MessagingInterface::kPostLoadGame: 66 | // EventHandler::GetSingleton()->Register(); 67 | break; 68 | } 69 | } 70 | 71 | extern "C" DLLEXPORT bool SKSEAPI SKSEPlugin_Load(const SKSE::LoadInterface* a_skse) 72 | { 73 | constexpr auto PLUGIN_NAME = "SexLabUtil"sv; 74 | const auto InitLogger = []() -> bool { 75 | #ifndef NDEBUG 76 | auto sink = std::make_shared(); 77 | #else 78 | auto path = logger::log_directory(); 79 | if (!path) 80 | return false; 81 | *path /= std::format("{}.log", PLUGIN_NAME); 82 | auto sink = std::make_shared(path->string(), true); 83 | #endif 84 | auto log = std::make_shared("global log"s, std::move(sink)); 85 | log->set_level(spdlog::level::info); 86 | log->flush_on(spdlog::level::info); 87 | spdlog::set_default_logger(std::move(log)); 88 | #ifndef NDEBUG 89 | spdlog::set_pattern("%s(%#): [%T] [%^%l%$] %v"s); 90 | #else 91 | spdlog::set_pattern("[%T] [%^%l%$] %v"s); 92 | #endif 93 | return true; 94 | }; 95 | 96 | if (a_skse->IsEditor()) { 97 | logger::critical("Loaded in editor, marking as incompatible"); 98 | return false; 99 | } else if (!InitLogger()) { 100 | logger::critical("Failed to initialize logger"); 101 | return false; 102 | } 103 | 104 | SKSE::Init(a_skse); 105 | logger::info("{} loaded", PLUGIN_NAME); 106 | 107 | const auto msging = SKSE::GetMessagingInterface(); 108 | if (!msging->RegisterListener("SKSE", SKSEMessageHandler)) { 109 | logger::critical("Failed to register Listener"); 110 | return false; 111 | } 112 | 113 | if (!Papyrus::Register()) { 114 | logger::critical("Failed to register papyrus functions"); 115 | return false; 116 | } 117 | 118 | Thread::Interface::SceneMenu::Register(); 119 | Thread::Interface::SelectionMenu::Register(); 120 | Thread::NiNode::NiUpdate::Install(); 121 | 122 | const auto serialization = SKSE::GetSerializationInterface(); 123 | serialization->SetUniqueID('slpp'); 124 | serialization->SetSaveCallback(Serialization::Serialize::SaveCallback); 125 | serialization->SetLoadCallback(Serialization::Serialize::LoadCallback); 126 | serialization->SetRevertCallback(Serialization::Serialize::RevertCallback); 127 | serialization->SetFormDeleteCallback(Serialization::Serialize::FormDeleteCallback); 128 | 129 | Registry::Statistics::StatisticsData::GetSingleton()->Register(); 130 | 131 | logger::info("Initialization complete"); 132 | 133 | return true; 134 | } 135 | -------------------------------------------------------------------------------- /xmake-requires.lock: -------------------------------------------------------------------------------- 1 | { 2 | __meta__ = { 3 | version = "1.0" 4 | }, 5 | ["windows|x64"] = { 6 | ["cmake#31fecfc4"] = { 7 | repo = { 8 | branch = "master", 9 | commit = "d690827620fac7d455dac05ed2e60e576f5cdcf8", 10 | url = "https://github.com/xmake-io/xmake-repo.git" 11 | }, 12 | version = "3.31.6" 13 | }, 14 | ["eigen#31fecfc4"] = { 15 | repo = { 16 | branch = "master", 17 | commit = "d690827620fac7d455dac05ed2e60e576f5cdcf8", 18 | url = "https://github.com/xmake-io/xmake-repo.git" 19 | }, 20 | version = "3.4.0" 21 | }, 22 | ["glm#31fecfc4"] = { 23 | repo = { 24 | branch = "master", 25 | commit = "d690827620fac7d455dac05ed2e60e576f5cdcf8", 26 | url = "https://github.com/xmake-io/xmake-repo.git" 27 | }, 28 | version = "1.0.1" 29 | }, 30 | ["magic_enum#31fecfc4"] = { 31 | repo = { 32 | branch = "master", 33 | commit = "c54cc6e7bc4c571586864d58fdcf119d2ac32250", 34 | url = "https://github.com/xmake-io/xmake-repo.git" 35 | }, 36 | version = "v0.9.7" 37 | }, 38 | ["nlohmann_json#31fecfc4"] = { 39 | repo = { 40 | branch = "master", 41 | commit = "c54cc6e7bc4c571586864d58fdcf119d2ac32250", 42 | url = "https://github.com/xmake-io/xmake-repo.git" 43 | }, 44 | version = "v3.11.3" 45 | }, 46 | ["rsm-binary-io#31fecfc4"] = { 47 | repo = { 48 | branch = "master", 49 | commit = "c54cc6e7bc4c571586864d58fdcf119d2ac32250", 50 | url = "https://github.com/xmake-io/xmake-repo.git" 51 | }, 52 | version = "2.0.6" 53 | }, 54 | ["simpleini#31fecfc4"] = { 55 | repo = { 56 | branch = "master", 57 | commit = "c54cc6e7bc4c571586864d58fdcf119d2ac32250", 58 | url = "https://github.com/xmake-io/xmake-repo.git" 59 | }, 60 | version = "v4.22" 61 | }, 62 | ["spdlog#9c36f0a9"] = { 63 | repo = { 64 | branch = "master", 65 | commit = "d690827620fac7d455dac05ed2e60e576f5cdcf8", 66 | url = "https://github.com/xmake-io/xmake-repo.git" 67 | }, 68 | version = "v1.15.1" 69 | }, 70 | ["yaml-cpp#31fecfc4"] = { 71 | repo = { 72 | branch = "master", 73 | commit = "c54cc6e7bc4c571586864d58fdcf119d2ac32250", 74 | url = "https://github.com/xmake-io/xmake-repo.git" 75 | }, 76 | version = "0.8.0" 77 | } 78 | } 79 | } -------------------------------------------------------------------------------- /xmake.lua: -------------------------------------------------------------------------------- 1 | set_xmakever("2.9.5") 2 | 3 | -- Globals 4 | PROJECT_NAME = "SexLabUtil" 5 | 6 | -- Project 7 | set_project(PROJECT_NAME) 8 | set_version("2.15.7") 9 | set_languages("cxx23") 10 | set_license("apache-2.0") 11 | set_warnings("allextra", "error") 12 | 13 | -- Options 14 | option("copy_to_papyrus") 15 | set_default(true) 16 | set_description("Copy finished build to Papyrus SKSE folder") 17 | option_end() 18 | 19 | option("skyrim_se") 20 | set_default(false) 21 | set_description("Enable support for Skyrim 1.5") 22 | option_end() 23 | 24 | option("skyrim_vr") 25 | set_default(false) 26 | set_description("Enable support for Skyrim VR") 27 | add_defines("SKYRIM_SUPPORT_VR=1") 28 | option_end() 29 | 30 | -- Dependencies & Includes 31 | -- https://github.com/xmake-io/xmake-repo/tree/dev 32 | add_requires("yaml-cpp", "magic_enum", "nlohmann_json", "simpleini", "glm", "eigen") 33 | 34 | if get_config("skyrim_vr") then 35 | includes("lib/commonlibvr") 36 | else 37 | includes("lib/commonlibsse") 38 | if get_config("skyrim_se") then 39 | set_config("skyrim_ae", false) 40 | else 41 | set_config("skyrim_ae", true) 42 | end 43 | end 44 | 45 | -- policies 46 | set_policy("package.requires_lock", true) 47 | 48 | -- rules 49 | add_rules("mode.debug", "mode.release") 50 | 51 | if is_mode("debug") then 52 | add_defines("DEBUG") 53 | set_optimize("none") 54 | elseif is_mode("release") then 55 | add_defines("NDEBUG") 56 | set_optimize("fastest") 57 | set_symbols("debug") 58 | end 59 | 60 | -- Target 61 | target(PROJECT_NAME) 62 | -- Dependencies 63 | add_packages("yaml-cpp", "magic_enum", "nlohmann_json", "simpleini", "glm", "eigen") 64 | 65 | -- CommonLibSSE 66 | add_deps("commonlibsse") 67 | add_rules("commonlibsse.plugin", { 68 | name = PROJECT_NAME, 69 | author = "Scrab", 70 | description = "Backend for skyrims adult animation framework 'SexLab'." 71 | }) 72 | 73 | -- Source files 74 | set_pcxxheader("src/PCH.h") 75 | add_files("src/**.cpp") 76 | add_headerfiles("src/**.h") 77 | add_includedirs("src") 78 | 79 | -- flags 80 | add_cxxflags( 81 | "cl::/cgthreads4", 82 | "cl::/diagnostics:caret", 83 | "cl::/external:W0", 84 | "cl::/fp:contract", 85 | "cl::/fp:except-", 86 | "cl::/guard:cf-", 87 | "cl::/Zc:enumTypes", 88 | "cl::/Zc:preprocessor", 89 | "cl::/Zc:templateScope", 90 | "cl::/utf-8" 91 | ) 92 | -- flags (cl: warnings -> errors) 93 | add_cxxflags("cl::/we4715") -- `function` : not all control paths return a value 94 | -- flags (cl: disable warnings) 95 | add_cxxflags( 96 | "cl::/wd4068", -- unknown pragma 'clang' 97 | "cl::/wd4201", -- nonstandard extension used : nameless struct/union 98 | "cl::/wd4265" -- 'type': class has virtual functions, but its non-trivial destructor is not virtual; instances of this class may not be destructed correctly 99 | ) 100 | -- Conditional flags 101 | if is_mode("debug") then 102 | add_cxxflags("cl::/bigobj") 103 | elseif is_mode("release") then 104 | add_cxxflags("cl::/Zc:inline", "cl::/JMC-", "cl::/Ob3") 105 | end 106 | -- Post Build 107 | after_build(function (target) 108 | os.exec("python scripts/generate_config.py") 109 | 110 | local mod_folder = os.getenv("XSE_TES5_MODS_PATH") 111 | if not has_config("copy_to_papyrus") then 112 | print("Notice: copy_to_papyrus not defined. Skipping post-build copy.") 113 | elseif mod_folder then 114 | local plugin_path = path.join(mod_folder, "SL-Dev", "SKSE/Plugins") 115 | if not os.isdir(plugin_path) then 116 | os.mkdir(plugin_path) 117 | end 118 | os.cp(target:targetfile(), plugin_path) 119 | os.cp(target:symbolfile(), plugin_path) 120 | os.cp("scripts/out/*", plugin_path) 121 | else 122 | print("Warning: SkyrimPath not defined. Skipping post-build copy.") 123 | end 124 | print("Build finished. Skyrim V" .. (get_config("skyrim_vr") and "VR" or (get_config("skyrim_ae") and "1.6" or "1.5"))) 125 | end) 126 | target_end() 127 | --------------------------------------------------------------------------------