Skip to content

Commit

Permalink
Initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
hardcpp committed Dec 11, 2021
0 parents commit 1a7bc06
Show file tree
Hide file tree
Showing 37 changed files with 514 additions and 0 deletions.
2 changes: 2 additions & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
# Auto detect text files and perform LF normalization
* text=auto
37 changes: 37 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
[Ll]ibrary/
[Tt]emp/
[Oo]bj/
[Bb]uild/
[Bb]uilds/
Assets/AssetStoreTools*

# Visual Studio cache directory
.vs/

# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.opendb
*.VC.db

# Unity3D generated meta files
*.pidb.meta
*.pdb.meta

# Unity3D Generated File On Crash Reports
sysinfo.txt

# Builds
*.apk
*.unitypackage
158 changes: 158 additions & 0 deletions BSIndexHapticFix/Components/HapticEmulator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,158 @@
using System.Collections;
using System.Collections.Generic;
using UnityEngine;

namespace BSIndexHapticFix.Components
{
/// <summary>
/// Haptic emulator for a single controller
/// </summary>
public class HapticEmulator : MonoBehaviour
{
/// <summary>
/// Target XR Node
/// </summary>
public UnityEngine.XR.XRNode Node { get; private set; }
/// <summary>
/// Is it an Index knuckle?
/// </summary>
public bool IsIndexKnuckle { get; private set; } = false;
/// <summary>
/// Is left hand
/// </summary>
public bool IsLeftHand => Node == UnityEngine.XR.XRNode.LeftHand;
/// <summary>
/// Is right hand
/// </summary>
public bool IsRightHand => Node == UnityEngine.XR.XRNode.RightHand;

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

/// <summary>
/// Matching XR input device
/// </summary>
private UnityEngine.XR.InputDevice? m_Device = null;
/// <summary>
/// Remaining haptic time
/// </summary>
private float m_OpenVREmulatedHapticRemainingTime = 0f;
/// <summary>
/// Remaining haptic strength
/// </summary>
private float m_OpenVREmulatedHapticAmplitude = 0f;

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

/// <summary>
/// Start is called before the first frame update
/// </summary>
public void Init(UnityEngine.XR.XRNode p_Node)
{
Node = p_Node;

/// Bind callbacks
UnityEngine.XR.InputDevices.deviceConnected += InputDevices_deviceConnected;
UnityEngine.XR.InputDevices.deviceDisconnected += InputDevices_deviceDisconnected;

/// In case of late init, search device manually
var l_AvailableDevicesAtNode = new List<UnityEngine.XR.InputDevice>();
UnityEngine.XR.InputDevices.GetDevicesAtXRNode(Node, l_AvailableDevicesAtNode);
l_AvailableDevicesAtNode.ForEach(x => InputDevices_deviceConnected(x));

GameObject.DontDestroyOnLoad(gameObject);
}
/// <summary>
/// On component destroy
/// </summary>
private void OnDestroy()
{
/// Remove callbacks
UnityEngine.XR.InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected;
UnityEngine.XR.InputDevices.deviceConnected -= InputDevices_deviceConnected;
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

public void SetHaptics(float p_Amplitude, float p_Duration)
{
float l_VibrationStrength = 2f;

p_Duration *= l_VibrationStrength;
p_Amplitude *= l_VibrationStrength;

if (p_Amplitude <= 0.01f)
return;

m_OpenVREmulatedHapticRemainingTime = p_Duration;
m_OpenVREmulatedHapticAmplitude = Mathf.Clamp01(p_Amplitude);
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

/// <summary>
/// On XR device connected
/// </summary>
/// <param name="p_Device">New device</param>
private void InputDevices_deviceConnected(UnityEngine.XR.InputDevice p_Device)
{
var l_RequiredCharacteristics = UnityEngine.XR.InputDeviceCharacteristics.Controller
| UnityEngine.XR.InputDeviceCharacteristics.TrackedDevice
| UnityEngine.XR.InputDeviceCharacteristics.HeldInHand
| (IsLeftHand ? UnityEngine.XR.InputDeviceCharacteristics.Left : UnityEngine.XR.InputDeviceCharacteristics.Right);

/// Check for matching role
if (!p_Device.isValid || (p_Device.characteristics & l_RequiredCharacteristics) != l_RequiredCharacteristics)
return;

m_Device = p_Device;
IsIndexKnuckle = m_Device.Value.name.ToLower().Contains("knuckles");

if (IsIndexKnuckle)
StartCoroutine(Coroutine_OpenVRHaptics());

Plugin.Logger.Debug($"Device found \"{p_Device.manufacturer}\"-\"{p_Device.name}\" with role \"{p_Device.characteristics}\" serial {p_Device.serialNumber} is index knuckle? {IsIndexKnuckle}");
}
/// <summary>
/// On XR device disconnected
/// </summary>
/// <param name="p_Device">Disconnected device</param>
private void InputDevices_deviceDisconnected(UnityEngine.XR.InputDevice p_Device)
{
/// Check for matching role
if (!m_Device.HasValue || m_Device != p_Device)
return;

m_Device = null;
StopAllCoroutines();

Plugin.Logger.Debug($"[OPVR.VRController] Device lost \"{p_Device.manufacturer}\"-\"{p_Device.name}\" with role \"{p_Device.characteristics}\"");
}

////////////////////////////////////////////////////////////////////////////
////////////////////////////////////////////////////////////////////////////

/// <summary>
/// Handles the haptic process every 1/80 second.
/// </summary>
/// <returns></returns>
private IEnumerator Coroutine_OpenVRHaptics()
{
const float s_Rate = 1 / 80f;

var l_Waiter = new WaitForSecondsRealtime(s_Rate);
while (true)
{
m_OpenVREmulatedHapticRemainingTime -= s_Rate;

if (m_OpenVREmulatedHapticRemainingTime > 0f)
m_Device?.SendHapticImpulse(0, m_OpenVREmulatedHapticAmplitude);

yield return l_Waiter;
}
}
}
}
14 changes: 14 additions & 0 deletions BSIndexHapticFix/Directory.Build.props
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file contains project properties used by the build. -->
<Project>
<PropertyGroup Condition="'$(GITHUB_ACTIONS)' == 'true'">
<ContinuousIntegrationBuild>true</ContinuousIntegrationBuild>
<DisableCopyToPlugins>true</DisableCopyToPlugins>
<DisableZipRelease>true</DisableZipRelease>
</PropertyGroup>
<PropertyGroup Condition="'$(NCrunch)' == '1'">
<ContinuousIntegrationBuild>false</ContinuousIntegrationBuild>
<DisableCopyToPlugins>true</DisableCopyToPlugins>
<DisableZipRelease>true</DisableZipRelease>
</PropertyGroup>
</Project>
96 changes: 96 additions & 0 deletions BSIndexHapticFix/Directory.Build.targets
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- This file contains the build tasks and targets for verifying the manifest, zipping Release builds,
and copying the plugin to to your Beat Saber folder. Only edit this if you know what you are doing. -->
<Project>
<PropertyGroup>
<BuildTargetsVersion>2.0</BuildTargetsVersion>
<!--Set this to true if you edit this file to prevent automatic updates-->
<BuildTargetsModified>false</BuildTargetsModified>
<!--Output assembly path without extension-->
<OutputAssemblyName>$(OutputPath)$(AssemblyName)</OutputAssemblyName>
<!--Path to folder to be zipped. Needs to be relative to the project directory to work without changes to the 'BuildForCI' target.-->
<ArtifactDestination>$(OutputPath)Final</ArtifactDestination>
<ErrorOnMismatchedVersions Condition="'$(Configuration)' == 'Release'">True</ErrorOnMismatchedVersions>
</PropertyGroup>
<!--Build Targets-->
<!--Displays a warning if BeatSaberModdingTools.Tasks is not installed.-->
<Target Name="CheckBSMTInstalled" AfterTargets="BeforeBuild" Condition="'$(BSMTTaskAssembly)' == ''">
<Warning Text="The BeatSaberModdingTools.Tasks nuget package doesn't seem to be installed, advanced build targets will not work." />
</Target>
<!--Runs a build task to get info about the project used by later targets.-->
<Target Name="GetProjectInfo" AfterTargets="CheckBSMTInstalled" DependsOnTargets="CheckBSMTInstalled" Condition="'$(BSMTTaskAssembly)' != ''">
<Message Text="Using AssemblyVersion defined in project instead of 'Properties\AssemblyInfo.cs'" Importance="high" Condition="'$(AssemblyVersion)' != ''" />
<GetManifestInfo KnownAssemblyVersion="$(AssemblyVersion)" ErrorOnMismatch="$(ErrorOnMismatchedVersions)">
<Output TaskParameter="PluginVersion" PropertyName="PluginVersion" />
<Output TaskParameter="GameVersion" PropertyName="GameVersion" />
<Output TaskParameter="AssemblyVersion" PropertyName="AssemblyVersion" />
</GetManifestInfo>
<GetCommitInfo ProjectDir="$(ProjectDir)">
<Output TaskParameter="CommitHash" PropertyName="CommitHash" />
<Output TaskParameter="Branch" PropertyName="Branch" />
<Output TaskParameter="Modified" PropertyName="GitModified" />
</GetCommitInfo>
<PropertyGroup>
<!--Build name for artifact/zip file-->
<ArtifactName>$(AssemblyName)</ArtifactName>
<ArtifactName Condition="'$(PluginVersion)' != ''">$(ArtifactName)-$(PluginVersion)</ArtifactName>
<ArtifactName Condition="'$(GameVersion)' != ''">$(ArtifactName)-bs$(GameVersion)</ArtifactName>
<ArtifactName Condition="'$(CommitHash)' != '' AND '$(CommitHash)' != 'local'">$(ArtifactName)-$(CommitHash)</ArtifactName>
</PropertyGroup>
</Target>
<!--Build target for Continuous Integration builds. Set up for GitHub Actions.-->
<Target Name="BuildForCI" AfterTargets="Build" DependsOnTargets="GetProjectInfo" Condition="'$(ContinuousIntegrationBuild)' == 'True' AND '$(BSMTTaskAssembly)' != ''">
<PropertyGroup>
<!--Set 'ArtifactName' if it failed before.-->
<ArtifactName Condition="'$(ArtifactName)' == ''">$(AssemblyName)</ArtifactName>
</PropertyGroup>
<Message Text="Building for CI" Importance="high" />
<Message Text="PluginVersion: $(PluginVersion), AssemblyVersion: $(AssemblyVersion), GameVersion: $(GameVersion)" Importance="high" />
<Message Text="::set-output name=filename::$(ArtifactName)" Importance="high" />
<Message Text="::set-output name=assemblyname::$(AssemblyName)" Importance="high" />
<Message Text="::set-output name=artifactpath::$(ProjectDir)$(ArtifactDestination)" Importance="high" />
<Message Text="Copying '$(OutputAssemblyName).dll' to '$(ProjectDir)$(ArtifactDestination)\Plugins\$(AssemblyName).dll'" Importance="high" />
<Copy SourceFiles="$(OutputAssemblyName).dll" DestinationFiles="$(ProjectDir)$(ArtifactDestination)\Plugins\$(AssemblyName).dll" />
</Target>
<!--Creates a BeatMods compliant zip file with the release.-->
<Target Name="ZipRelease" AfterTargets="Build" Condition="'$(DisableZipRelease)' != 'True' AND '$(Configuration)' == 'Release' AND '$(BSMTTaskAssembly)' != ''">
<PropertyGroup>
<!--Set 'ArtifactName' if it failed before.-->
<ArtifactName Condition="'$(ArtifactName)' == ''">$(AssemblyName)</ArtifactName>
<DestinationDirectory>$(OutDir)zip\</DestinationDirectory>
</PropertyGroup>
<ItemGroup>
<OldZips Include="$(DestinationDirectory)$(AssemblyName)*.zip"/>
</ItemGroup>
<Copy SourceFiles="$(OutputAssemblyName).dll" DestinationFiles="$(ArtifactDestination)\Plugins\$(AssemblyName).dll" />
<Message Text="PluginVersion: $(PluginVersion), AssemblyVersion: $(AssemblyVersion), GameVersion: $(GameVersion)" Importance="high" />
<Delete Files="@(OldZips)" TreatErrorsAsWarnings="true" ContinueOnError="true" />
<ZipDir SourceDirectory="$(ArtifactDestination)" DestinationFile="$(DestinationDirectory)$(ArtifactName).zip" />
</Target>
<!--Copies the assembly and pdb to the Beat Saber folder.-->
<Target Name="CopyToPlugins" AfterTargets="Build" Condition="'$(DisableCopyToPlugins)' != 'True' AND '$(ContinuousIntegrationBuild)' != 'True'">
<PropertyGroup>
<PluginDir>$(BeatSaberDir)\Plugins</PluginDir>
<CanCopyToPlugins>True</CanCopyToPlugins>
<CopyToPluginsError Condition="!Exists('$(PluginDir)')">Unable to copy assembly to game folder, did you set 'BeatSaberDir' correctly in your 'csproj.user' file? Plugins folder doesn't exist: '$(PluginDir)'.</CopyToPluginsError>
<!--Error if 'BeatSaberDir' does not have 'Beat Saber.exe'-->
<CopyToPluginsError Condition="!Exists('$(BeatSaberDir)\Beat Saber.exe')">Unable to copy to Plugins folder, '$(BeatSaberDir)' does not appear to be a Beat Saber game install.</CopyToPluginsError>
<!--Error if 'BeatSaberDir' is the same as 'LocalRefsDir'-->
<CopyToPluginsError Condition="'$(BeatSaberDir)' == '$(LocalRefsDir)' OR '$(BeatSaberDir)' == ''">Unable to copy to Plugins folder, 'BeatSaberDir' has not been set in your 'csproj.user' file.</CopyToPluginsError>
<CanCopyToPlugins Condition="'$(CopyToPluginsError)' != ''">False</CanCopyToPlugins>
</PropertyGroup>
<!--Check if Beat Saber is running-->
<IsProcessRunning ProcessName="Beat Saber" Condition="'$(BSMTTaskAssembly)' != ''">
<Output TaskParameter="IsRunning" PropertyName="IsRunning" />
</IsProcessRunning>
<PropertyGroup>
<!--If Beat Saber is running, output to the Pending folder-->
<PluginDir Condition="'$(IsRunning)' == 'True'">$(BeatSaberDir)\IPA\Pending\Plugins</PluginDir>
</PropertyGroup>
<Warning Text="$(CopyToPluginsError)" Condition="'$(CopyToPluginsError)' != ''" />
<Message Text="Copying '$(OutputAssemblyName).dll' to '$(PluginDir)'." Importance="high" Condition="$(CanCopyToPlugins)" />
<Copy SourceFiles="$(OutputAssemblyName).dll" DestinationFiles="$(PluginDir)\$(AssemblyName).dll" Condition="$(CanCopyToPlugins)" />
<Copy SourceFiles="$(OutputAssemblyName).pdb" DestinationFiles="$(PluginDir)\$(AssemblyName).pdb" Condition="'$(CanCopyToPlugins)' == 'True' AND Exists('$(OutputAssemblyName).pdb')" />
<Warning Text="Beat Saber is running, restart the game to use the latest build." Condition="'$(IsRunning)' == 'True'" />
</Target>
</Project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
using HarmonyLib;
using UnityEngine.XR;

namespace BSIndexHapticFix.HarmonyPatches
{
[HarmonyPatch(typeof(OpenVRHelper))]
[HarmonyPatch(nameof(OpenVRHelper.TriggerHapticPulse))]
internal class POpenVRHelper_TriggerHapticPulse
{
static bool Prefix(XRNode node, float duration, float strength, float frequency)
{
if (node == XRNode.LeftHand && Plugin.LeftHapticEmulator && Plugin.LeftHapticEmulator.IsIndexKnuckle)
{
Plugin.LeftHapticEmulator.SetHaptics(strength, duration);

/// Skip original method
return false;
}

if (node == XRNode.RightHand && Plugin.RightHapticEmulator && Plugin.RightHapticEmulator.IsIndexKnuckle)
{
Plugin.RightHapticEmulator.SetHaptics(strength, duration);

/// Skip original method
return false;
}

/// Forward to base method
return true;
}
}
}
Loading

0 comments on commit 1a7bc06

Please sign in to comment.