From 1e5e2d76523a3c5d940f905cc74b08e4b9f1008b Mon Sep 17 00:00:00 2001 From: mwilsnd Date: Sat, 10 Oct 2020 07:00:32 -0500 Subject: [PATCH] Beta 1 --- CHANGES.md | 7 ++++ MCM/SmoothCamMCM.pex | Bin 80962 -> 82307 bytes MCM/mcm.psc | 51 +++++++++++++++++++++-- MCM/preproc/preproc_constexpr_struct.lua | 32 +++++++------- README.md | 12 +++++- SmoothCam/include/camera.h | 5 +++ SmoothCam/include/config.h | 14 ++++--- SmoothCam/include/pch.h | 1 + SmoothCam/source/camera.cpp | 31 ++++++++++++-- SmoothCam/source/camera_state.cpp | 16 ++++--- SmoothCam/source/config.cpp | 4 ++ SmoothCam/source/detours.cpp | 5 +++ SmoothCam/source/main.cpp | 2 +- SmoothCam/source/papyrus.cpp | 38 +++++++++++++++++ 14 files changed, 181 insertions(+), 37 deletions(-) diff --git a/CHANGES.md b/CHANGES.md index 419c18d..ddfd95c 100644 --- a/CHANGES.md +++ b/CHANGES.md @@ -1,3 +1,10 @@ +## Beta 1 +* Bumped module and MCM version number to 9 +* Promoted to beta. +* Added shoulder swapping via hotkey. +* Added separable option for swapping the distance clamping X axis when swapping shoulders. +* Fixed issue with `preproc_constexpr_struct.lua` incorrectly assuming all `a.b` syntax was a struct macro to be replaced. + ## Alpha 1.7 * Bumped module and MCM version number to 8 * Added a "group edit" tab in the MCM, allowing users to edit all offset groups at once. diff --git a/MCM/SmoothCamMCM.pex b/MCM/SmoothCamMCM.pex index 904b31175e31fc3debbff9a9589c23b0fcfedd4a..a3543d359e2857a4794dcdf37e9a30b91428a8b6 100644 GIT binary patch literal 82307 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 = AddKeyMapOption(this->displayName, SmoothCam_GetIntConfig(this->settingName)) + } + + MACRO implSelectHandler = { + if (StringUtil.GetLength(a_conflictControl) == 0) + SmoothCam_SetIntConfig(this->settingName, a_keyCode) + SetKeyMapOptionValue(this->ref, a_keyCode) + else + if (ShowMessage(a_conflictControl + " conflicts with another control assigned to " + a_conflictName + ".\nAre you sure you want to assign this control?")) + SmoothCam_SetIntConfig(this->settingName, a_keyCode) + SetKeyMapOptionValue(this->ref, a_keyCode) + endIf + endIf + } + + MACRO implDesc = { + SetInfoText(this->desc) + } +} + ScriptMeta scriptMetaInfo -> { - version: 8 + version: 9 } ; Presets @@ -294,6 +323,11 @@ ToggleSetting cameraDistanceClampZEnable -> { displayName: "Enable Z Distance Clamp" desc: "Clamp the maximum distance the camera may move away from the target position along the Z (up) axis." } +ToggleSetting swapDistanceClampXAxis -> { + settingName: "ShoulderSwapXClamping" + displayName: "Also Swap X Axis Clamping" + desc: "When shoulder swapping, will also swap the distance clamping X axis range." +} ListSetting interpMethod -> { settingName: "InterpolationMethod" @@ -490,6 +524,12 @@ SliderSetting sepLocalSpaceInterpRate -> { displayFormat: "{2}" } +KeyBindSetting shoulderSwapKey -> { + settingName: "ShoulderSwapKeyCode" + displayName: "Shoulder Swap Key" + desc: "Inverts the current X offset of the camera" +} + ; Crosshair ToggleSetting crosshair3DBowEnabled -> { settingName: "Enable3DBowCrosshair" @@ -1366,7 +1406,7 @@ event OnPageReset(string a_page) AddHeaderOption("Misc") IMPL_STRUCT_MACRO_INVOKE_GROUP(implControl, { - zoomMul, disableDeltaTime + shoulderSwapKey, swapDistanceClampXAxis, zoomMul, disableDeltaTime }) elseIf (a_page == " Crosshair") AddHeaderOption("3D Crosshair Settings") @@ -1645,12 +1685,17 @@ event OnOptionInputAccept(int a_option, string a_input) IMPL_IFCHAIN_MACRO_INVOKE(a_option, ref, implAcceptHandler, {IMPL_ALL_IMPLS_OF_STRUCT(SavePresetSetting)}) endEvent +event OnOptionKeyMapChange(int a_option, int a_keyCode, string a_conflictControl, string a_conflictName) + IMPL_IFCHAIN_MACRO_INVOKE(a_option, ref, implSelectHandler, {IMPL_ALL_IMPLS_OF_STRUCT(KeyBindSetting)}) +endEvent + event OnOptionHighlight(int a_option) IMPL_IFCHAIN_MACRO_INVOKE(a_option, ref, implDesc, { IMPL_ALL_IMPLS_OF_STRUCT(SliderSetting), IMPL_ALL_IMPLS_OF_STRUCT(ToggleSetting), IMPL_ALL_IMPLS_OF_STRUCT(ListSetting), IMPL_ALL_IMPLS_OF_STRUCT(SavePresetSetting), - IMPL_ALL_IMPLS_OF_STRUCT(LoadPresetSetting) + IMPL_ALL_IMPLS_OF_STRUCT(LoadPresetSetting), + IMPL_ALL_IMPLS_OF_STRUCT(KeyBindSetting) }) endEvent \ No newline at end of file diff --git a/MCM/preproc/preproc_constexpr_struct.lua b/MCM/preproc/preproc_constexpr_struct.lua index 6125d56..8ba183c 100644 --- a/MCM/preproc/preproc_constexpr_struct.lua +++ b/MCM/preproc/preproc_constexpr_struct.lua @@ -168,25 +168,27 @@ do class "ConstexprStructToVars" : namespace "papyrus.preproc" { gen_member = gen_member:trim() if not generated[member.."."..gen_decl.."."..gen_member] then - local typeInfo = self.m_tblStructTypes[self.m_tblStructImpls[gen_decl].type] - - local usage = self.m_tblStructMemberUsage[gen_decl.."."..gen_member] - if usage then - code = code:replace( gen_decl.."."..gen_member, usage.name ) - else - if typeInfo.members[gen_member].isreal then - code = code:replace( gen_decl.."."..gen_member, gen_decl.. "_".. gen_member ) + if self.m_tblStructImpls[gen_decl] and self.m_tblStructTypes[self.m_tblStructImpls[gen_decl].type] then + local typeInfo = self.m_tblStructTypes[self.m_tblStructImpls[gen_decl].type] + + local usage = self.m_tblStructMemberUsage[gen_decl.."."..gen_member] + if usage then + code = code:replace( gen_decl.."."..gen_member, usage.name ) else - -- Value literal - local value = self.m_tblStructImpls[gen_decl].members[gen_member] - if not value then - value = typeInfo.members[gen_member].value + if typeInfo.members[gen_member].isreal then + code = code:replace( gen_decl.."."..gen_member, gen_decl.. "_".. gen_member ) + else + -- Value literal + local value = self.m_tblStructImpls[gen_decl].members[gen_member] + if not value then + value = typeInfo.members[gen_member].value + end + code = code:replace( gen_decl.."."..gen_member, value ) end - code = code:replace( gen_decl.."."..gen_member, value ) end - end - generated[member.."."..gen_decl.."."..gen_member] = true + generated[member.."."..gen_decl.."."..gen_member] = true + end end end diff --git a/README.md b/README.md index 606bb9c..c3c0224 100644 --- a/README.md +++ b/README.md @@ -18,7 +18,7 @@ Depending on the age of your processor (~2011 and earlier), you might need to us ## Installing If using one of the pre-compiled releases, just copy the `Data` folder in the archive into your skyrim folder or install via a mod manager. -If installing after a build, copy `SmoothCam.dll` to `Data/SKSE/Plugins`, `hudmenu.swf` to `Data/interface`, `SmoothCam.esp` to `Data/` and `SmoothCamMCM.pex` to `Data/Scripts`. Enable the esp file if you wish to use the MCM, otherwise the module will generate a json file in the plugins folder you can edit to manually configure the camera. +If installing after a build, copy `SmoothCam.dll` to `Data/SKSE/Plugins` (along with the address library database file), `SmoothCam.esp` to `Data/` and `SmoothCamMCM.pex` to `Data/Scripts`. Enable the esp file if you wish to use the MCM, otherwise the module will generate a json file in the plugins folder you can edit to manually configure the camera. ## Building To build the project, clone the repo (remember to include `--recurse-submodules`), then run `make_vs2019.bat` or `make_vs2017.bat`. @@ -26,7 +26,7 @@ The build scripts require `premake5` and `7-Zip` to be installed and available o Once premake has finished, open the generated Visual Studio solution, select `Debug` or `Release` and select `Build Solution` from the build dropdown. -Built files are placed in `SmoothCam/bin//SmoothCam`. Be sure to also copy `hudmenu.swf` from the UI folder into `Data/interface` for the 3D crosshair to work. Copy the esp to `Data/` and the pex file to `Data/Scripts`. +Built files are placed in `SmoothCam/bin//SmoothCam`. To build the papyrus script, you'll need `lua` on the system path. To run the code generation just run `MCM/run_preprocess.bat` which will generate `SmoothCamMCM.psc`. From there just compile the generated code like any normal papyrus script. @@ -46,6 +46,7 @@ use3DBowAimCrosshair | Enables the raycasted crosshair while aiming with a bow use3DMagicCrosshair | Enables the raycasted crosshair when magic is equiped hideNonCombatCrosshair | Hides the crosshair when no weapon is drawn hideCrosshairMeleeCombat | Hides the crosshair when melee weapons are drawn +enableCrosshairSizeManip | Enable size manipulation of the crosshair crosshairNPCHitGrowSize | When the 3D crosshair is over an NPC, grow the size of the crosshair by this amount crosshairMinDistSize | Sets the size of the 3D crosshair when the player's aim ray is at the maximum distance crosshairMaxDistSize | Sets the size of the 3D crosshair when the player's aim ray is at the minimum distance @@ -54,6 +55,8 @@ disableDuringDialog | Disables SmoothCam during character-NPC conversations comaptIC_FirstPersonHorse | Enables compat code for dealing with issue when running the Improved Camera mod comaptIC_FirstPersonDragon | Enables compat code for dealing with issue when running the Improved Camera mod compatIC_FirstPersonSitting | Enables compat code for dealing with issue when running the Improved Camera mod +shoulderSwapKey | The key code used for swapping the X axis when pressed +swapXClamping | When swapping the shoulder/X axis, also flips the distance clamping X values currentScalar | A value from 0 - 21: The scalar function to use for interpolation minCameraFollowDistance | The distance the camera follows the player from when at the lowest zoom level minCameraFollowRate | The amount of camera latency when the camera is close to the player (lower = more latency) @@ -100,14 +103,19 @@ OffsetSetting | Description --- | --- sideOffset | The amount to move the camera to the right upOffset | The amount to move the camera up +zoomOffset | The amount to zoom the camera by combatRangedSideOffset | The amount to move the camera to the right during ranged combat combatRangedUpOffset | The amount to move the camera up during ranged combat +combatRangedZoomOffset | The amount to zoom the camera by combatMagicSideOffset | The amount to move the camera to the right during magic combat combatMagicUpOffset | The amount to move the camera up during magic combat +combatMagicZoomOffset | The amount to zoom the camera by combatMeleeSideOffset | The amount to move the camera to the right during melee combat combatMeleeUpOffset | The amount to move the camera up during melee combat +combatMeleeZoomOffset | The amount to zoom the camera by horseSideOffset | The amount to move the camera to the right when on horseback horseUpOffset | The amount to move the camera up when on horseback +horseZoomOffset | The amount to zoom the camera by interp | Enable smoothing during this state interpRangedCombat | Enable smoothing during this state when in ranged combat interpMagicCombat | Enable smoothing during this state when in magic combat diff --git a/SmoothCam/include/camera.h b/SmoothCam/include/camera.h index 93acf86..198ef8a 100644 --- a/SmoothCam/include/camera.h +++ b/SmoothCam/include/camera.h @@ -54,6 +54,8 @@ namespace Camera { void UpdateCamera(PlayerCharacter* player, CorrectedPlayerCamera* camera); // Called when the player toggles the POV void OnTogglePOV(const ButtonEvent* ev) noexcept; + // Called when any other key is pressed + void OnKeyPress(const ButtonEvent* ev) noexcept; // Called when the dialog menu is shown or hidden void OnDialogMenuChanged(const MenuOpenCloseEvent* const ev) noexcept; // Returns our most recent camera position @@ -108,6 +110,8 @@ namespace Camera { glm::vec3 GetCurrentCameraOffset(PlayerCharacter* player, const CorrectedPlayerCamera* camera) const noexcept; // Returns the current smoothing scalar to use for the given distance to the player float GetCurrentSmoothingScalar(const float distance, ScalarSelector method = ScalarSelector::Normal) const; + // Returns the user defined distance clamping vector pair + std::tuple GetDistanceClamping() const noexcept; // Returns true if interpolation is allowed in the current state bool IsInterpAllowed(PlayerCharacter* player) const noexcept; // Constructs the view matrix for the camera @@ -230,6 +234,7 @@ namespace Camera { bool povIsThird = false; bool povWasPressed = false; bool dialogMenuOpen = false; + int shoulderSwap = 1; friend class State::BaseCameraState; }; diff --git a/SmoothCam/include/config.h b/SmoothCam/include/config.h index 5c69b0c..92d4eff 100644 --- a/SmoothCam/include/config.h +++ b/SmoothCam/include/config.h @@ -122,6 +122,8 @@ namespace Config { // Misc bool disableDeltaTime = false; + int shoulderSwapKey = -1; + bool swapXClamping = true; // Comapt bool disableDuringDialog = false; @@ -133,21 +135,21 @@ namespace Config { bool enableInterp = true; ScalarMethods currentScalar = ScalarMethods::SINE_IN; float minCameraFollowDistance = 64.0f; - float minCameraFollowRate = 0.2f; - float maxCameraFollowRate = 0.5f; + float minCameraFollowRate = 0.15f; + float maxCameraFollowRate = 0.4f; float zoomMul = 500.0f; float zoomMaxSmoothingDistance = 650.0f; // Separate local space interpolation - bool separateLocalInterp = false; + bool separateLocalInterp = true; ScalarMethods separateLocalScalar = ScalarMethods::CIRC_IN; - float localScalarRate = 1.0f; + float localScalarRate = 0.75f; // Separate Z - bool separateZInterp = false; + bool separateZInterp = true; ScalarMethods separateZScalar = ScalarMethods::SINE_IN; float separateZMaxSmoothingDistance = 60.0f; - float separateZMinFollowRate = 0.25f; + float separateZMinFollowRate = 0.15f; float separateZMaxFollowRate = 0.4f; // Offset interpolation diff --git a/SmoothCam/include/pch.h b/SmoothCam/include/pch.h index 39b9dee..26c180a 100644 --- a/SmoothCam/include/pch.h +++ b/SmoothCam/include/pch.h @@ -11,6 +11,7 @@ #include #include #include +#include #include #pragma warning( push ) diff --git a/SmoothCam/source/camera.cpp b/SmoothCam/source/camera.cpp index 45e59e2..9e42678 100644 --- a/SmoothCam/source/camera.cpp +++ b/SmoothCam/source/camera.cpp @@ -22,6 +22,12 @@ void Camera::SmoothCamera::OnTogglePOV(const ButtonEvent* ev) noexcept { povWasPressed = true; } +void Camera::SmoothCamera::OnKeyPress(const ButtonEvent* ev) noexcept { + if (config->shoulderSwapKey >= 0 && config->shoulderSwapKey == ev->keyMask && ev->timer <= 0.000001f) { + shoulderSwap = shoulderSwap == 1 ? -1 : 1; + } +} + void Camera::SmoothCamera::OnDialogMenuChanged(const MenuOpenCloseEvent* const ev) noexcept { dialogMenuOpen = ev->opening; } @@ -397,9 +403,9 @@ float Camera::SmoothCamera::GetCurrentCameraSideOffset(PlayerCharacter* player, switch (currentState) { case GameState::CameraState::Horseback: { if (GameState::IsBowDrawn(player)) { - return config->bowAim.horseSideOffset; + return config->bowAim.horseSideOffset * shoulderSwap; } else { - return GetActiveWeaponStateSideOffset(player, &config->horseback); + return GetActiveWeaponStateSideOffset(player, &config->horseback) * shoulderSwap; } } default: @@ -412,14 +418,14 @@ float Camera::SmoothCamera::GetCurrentCameraSideOffset(PlayerCharacter* player, case CameraActionState::Sitting: case CameraActionState::Aiming: case CameraActionState::Swimming: { - return offsetState.currentGroup->sideOffset; + return offsetState.currentGroup->sideOffset * shoulderSwap; } case CameraActionState::Sneaking: case CameraActionState::Sprinting: case CameraActionState::Walking: case CameraActionState::Running: case CameraActionState::Standing: { - return GetActiveWeaponStateSideOffset(player, offsetState.currentGroup); + return GetActiveWeaponStateSideOffset(player, offsetState.currentGroup) * shoulderSwap; } default: { break; @@ -557,6 +563,23 @@ float Camera::SmoothCamera::GetCurrentSmoothingScalar(const float distance, Scal return mmath::RunScalarFunction(scalarMethod, interpValue); } +// Returns the user defined distance clamping vector pair +std::tuple Camera::SmoothCamera::GetDistanceClamping() const noexcept { + float minsX = config->cameraDistanceClampXMin; + float maxsX = config->cameraDistanceClampXMax; + + if (config->swapXClamping && shoulderSwap < 1) { + std::swap(minsX, maxsX); + maxsX *= -1.0f; + minsX *= -1.0f; + } + + return std::tie( + glm::vec3{ minsX, config->cameraDistanceClampYMin, config->cameraDistanceClampZMin }, + glm::vec3{ maxsX, config->cameraDistanceClampYMax, config->cameraDistanceClampZMax } + ); +} + // Returns true if interpolation is allowed in the current state bool Camera::SmoothCamera::IsInterpAllowed(PlayerCharacter* player) const noexcept { auto ofs = offsetState.currentGroup; diff --git a/SmoothCam/source/camera_state.cpp b/SmoothCam/source/camera_state.cpp index f4f1f62..2f695fc 100644 --- a/SmoothCam/source/camera_state.cpp +++ b/SmoothCam/source/camera_state.cpp @@ -57,13 +57,15 @@ glm::vec3 Camera::State::BaseCameraState::ComputeOffsetClamping(PlayerCharacter* forward, right, up, coef ); + auto [mins, maxs] = camera->GetDistanceClamping(); + // Now we can do whatever we want to the interp vector in axis aligned space if (camera->config->cameraDistanceClampXEnable) - coef.x = glm::clamp(coef.x, camera->config->cameraDistanceClampXMin, camera->config->cameraDistanceClampXMax); + coef.x = glm::clamp(coef.x, mins.x, maxs.x); if (camera->config->cameraDistanceClampYEnable) - coef.y = glm::clamp(coef.y, camera->config->cameraDistanceClampYMin, camera->config->cameraDistanceClampYMax); + coef.y = glm::clamp(coef.y, mins.y, maxs.y); if (camera->config->cameraDistanceClampZEnable) - coef.z = glm::clamp(coef.z, camera->config->cameraDistanceClampZMin, camera->config->cameraDistanceClampZMax); + coef.z = glm::clamp(coef.z, mins.z, maxs.z); // Now recompose with the new coefficients and add back the world position return (forward * coef.x) + (right * coef.y) + (up * coef.z) + expectedPosition; @@ -80,13 +82,15 @@ glm::vec3 Camera::State::BaseCameraState::ComputeOffsetClamping(PlayerCharacter* forward, right, up, coef ); + auto [mins, maxs] = camera->GetDistanceClamping(); + // Now we can do whatever we want to the interp vector in axis aligned space if (camera->config->cameraDistanceClampXEnable) - coef.x = glm::clamp(coef.x, camera->config->cameraDistanceClampXMin, camera->config->cameraDistanceClampXMax); + coef.x = glm::clamp(coef.x, mins.x, maxs.x); if (camera->config->cameraDistanceClampYEnable) - coef.y = glm::clamp(coef.y, camera->config->cameraDistanceClampYMin, camera->config->cameraDistanceClampYMax); + coef.y = glm::clamp(coef.y, mins.y, maxs.y); if (camera->config->cameraDistanceClampZEnable) - coef.z = glm::clamp(coef.z, camera->config->cameraDistanceClampZMin, camera->config->cameraDistanceClampZMax); + coef.z = glm::clamp(coef.z, mins.z, maxs.z); // Now recompose with the new coefficients and add back the world position return (forward * coef.x) + (right * coef.y) + (up * coef.z) + cameraWorldTarget/* + expectedPosition*/; diff --git a/SmoothCam/source/config.cpp b/SmoothCam/source/config.cpp index 47eb52f..5952320 100644 --- a/SmoothCam/source/config.cpp +++ b/SmoothCam/source/config.cpp @@ -68,6 +68,8 @@ void Config::to_json(json& j, const UserConfig& obj) { CREATE_JSON_VALUE(obj, crosshairMinDistSize), CREATE_JSON_VALUE(obj, crosshairMaxDistSize), CREATE_JSON_VALUE(obj, disableDeltaTime), + CREATE_JSON_VALUE(obj, shoulderSwapKey), + CREATE_JSON_VALUE(obj, swapXClamping), CREATE_JSON_VALUE(obj, disableDuringDialog), CREATE_JSON_VALUE(obj, currentScalar), CREATE_JSON_VALUE(obj, comaptIC_FirstPersonHorse), @@ -125,6 +127,8 @@ void Config::from_json(const json& j, UserConfig& obj) { VALUE_FROM_JSON(obj, crosshairMinDistSize) VALUE_FROM_JSON(obj, crosshairMaxDistSize) VALUE_FROM_JSON(obj, disableDeltaTime) + VALUE_FROM_JSON(obj, shoulderSwapKey) + VALUE_FROM_JSON(obj, swapXClamping) VALUE_FROM_JSON(obj, disableDuringDialog) VALUE_FROM_JSON(obj, currentScalar) VALUE_FROM_JSON(obj, comaptIC_FirstPersonHorse) diff --git a/SmoothCam/source/detours.cpp b/SmoothCam/source/detours.cpp index 9af7f7f..f51d604 100644 --- a/SmoothCam/source/detours.cpp +++ b/SmoothCam/source/detours.cpp @@ -77,6 +77,11 @@ uintptr_t __fastcall mOnInput(PlayerInputHandler* pThis, InputEvent* input) { if (!g_theCamera.expired() && (lockedPtr = g_theCamera.lock(), lockedPtr != nullptr)) { lockedPtr->OnTogglePOV(ev); } + } else { + std::shared_ptr lockedPtr; + if (!g_theCamera.expired() && (lockedPtr = g_theCamera.lock(), lockedPtr != nullptr)) { + lockedPtr->OnKeyPress(ev); + } } break; diff --git a/SmoothCam/source/main.cpp b/SmoothCam/source/main.cpp index 16ad7e8..1c4069b 100644 --- a/SmoothCam/source/main.cpp +++ b/SmoothCam/source/main.cpp @@ -57,7 +57,7 @@ extern "C" { info->infoVersion = PluginInfo::kInfoVersion; info->name = "SmoothCam"; - info->version = 8; + info->version = 9; g_pluginHandle = skse->GetPluginHandle(); diff --git a/SmoothCam/source/papyrus.cpp b/SmoothCam/source/papyrus.cpp index 8b9f30c..9818ce2 100644 --- a/SmoothCam/source/papyrus.cpp +++ b/SmoothCam/source/papyrus.cpp @@ -76,6 +76,7 @@ const std::unordered_map> boolGetter IMPL_GETTER("CameraDistanceClampXEnable", cameraDistanceClampXEnable) IMPL_GETTER("CameraDistanceClampYEnable", cameraDistanceClampYEnable) IMPL_GETTER("CameraDistanceClampZEnable", cameraDistanceClampZEnable) + IMPL_GETTER("ShoulderSwapXClamping", swapXClamping) IMPL_GETTER("InterpStanding", standing.interp) IMPL_GETTER("InterpStandingRangedCombat", standing.interpRangedCombat) @@ -230,6 +231,10 @@ const std::unordered_map> floatGett IMPL_GETTER("Dragon:UpOffset", dragon.upOffset) }; +const std::unordered_map> intGetters = { + IMPL_GETTER("ShoulderSwapKeyCode", shoulderSwapKey) +}; + const std::unordered_map> stringSetters = { IMPL_SCALAR_METHOD_SETTER("InterpolationMethod", currentScalar) IMPL_SCALAR_METHOD_SETTER("SeparateZInterpMethod", separateZScalar) @@ -258,6 +263,7 @@ const std::unordered_map> boolSetter IMPL_SETTER("CameraDistanceClampXEnable", cameraDistanceClampXEnable, bool) IMPL_SETTER("CameraDistanceClampYEnable", cameraDistanceClampYEnable, bool) IMPL_SETTER("CameraDistanceClampZEnable", cameraDistanceClampZEnable, bool) + IMPL_SETTER("ShoulderSwapXClamping", swapXClamping, bool) IMPL_SETTER("InterpStanding", standing.interp, bool) IMPL_SETTER("InterpStandingRangedCombat", standing.interpRangedCombat, bool) @@ -426,6 +432,10 @@ const std::unordered_map> floatSett IMPL_GROUP_SETTER("Group:Melee:ZoomOffset", combatMeleeZoomOffset, float) }; +const std::unordered_map> intSetters = { + IMPL_SETTER("ShoulderSwapKeyCode", shoulderSwapKey, int) +}; + void PapyrusBindings::Bind(VMClassRegistry* registry) { registry->RegisterFunction( new NativeFunction2( @@ -466,6 +476,19 @@ void PapyrusBindings::Bind(VMClassRegistry* registry) { ) ); + registry->RegisterFunction( + new NativeFunction2( + "SmoothCam_SetIntConfig", + ScriptClassName, + [](StaticFunctionTag* thisInput, BSFixedString var, SInt32 value) { + const auto it = intSetters.find(var.c_str()); + if (it != intSetters.end()) + it->second(static_cast(value)); + }, + registry + ) + ); + registry->RegisterFunction( new NativeFunction1( "SmoothCam_GetStringConfig", @@ -511,6 +534,21 @@ void PapyrusBindings::Bind(VMClassRegistry* registry) { ) ); + registry->RegisterFunction( + new NativeFunction1( + "SmoothCam_GetIntConfig", + ScriptClassName, + [](StaticFunctionTag* thisInput, BSFixedString var) { + const auto it = intGetters.find(var.c_str()); + if (it != intGetters.end()) + return static_cast(it->second()); + else + return static_cast(-1); + }, + registry + ) + ); + registry->RegisterFunction( new NativeFunction2( "SmoothCam_SaveAsPreset",