├── .gitattributes
├── .github
└── FUNDING.yml
├── .gitignore
├── BSIndexHapticFix.sln
├── BSIndexHapticFix
├── BSIndexHapticFix.csproj
├── Components
│ └── HapticEmulator.cs
├── Directory.Build.props
├── Directory.Build.targets
├── HarmonyPatches
│ └── POpenVRHelper_TriggerHapticPulse.cs
├── Plugin.cs
├── Properties
│ └── AssemblyInfo.cs
└── manifest.json
├── LICENSE
└── README.md
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.github/FUNDING.yml:
--------------------------------------------------------------------------------
1 | # These are supported funding model platforms
2 |
3 | github: # Replace with up to 4 GitHub Sponsors-enabled usernames e.g., [user1, user2]
4 | patreon: BeatSaberPlus
5 | open_collective: # Replace with a single Open Collective username
6 | ko_fi: # Replace with a single Ko-fi username
7 | tidelift: # Replace with a single Tidelift platform-name/package-name e.g., npm/babel
8 | community_bridge: # Replace with a single Community Bridge project-name e.g., cloud-foundry
9 | liberapay: # Replace with a single Liberapay username
10 | issuehunt: # Replace with a single IssueHunt username
11 | otechie: # Replace with a single Otechie username
12 | custom: # Replace with up to 4 custom sponsorship URLs e.g., ['link1', 'link2']
13 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | [Ll]ibrary/
2 | [Tt]emp/
3 | [Oo]bj/
4 | [Bb]uild/
5 | [Bb]in/
6 | [Bb]uilds/
7 | Assets/AssetStoreTools*
8 |
9 | # Visual Studio cache directory
10 | .vs/
11 |
12 | # Autogenerated VS/MD/Consulo solution and project files
13 | ExportedObj/
14 | .consulo/
15 | *.unityproj
16 | *.suo
17 | *.tmp
18 | *.user
19 | *.userprefs
20 | *.pidb
21 | *.booproj
22 | *.svd
23 | *.pdb
24 | *.opendb
25 | *.VC.db
26 |
27 | # Unity3D generated meta files
28 | *.pidb.meta
29 | *.pdb.meta
30 |
31 | # Unity3D Generated File On Crash Reports
32 | sysinfo.txt
33 |
34 | # Builds
35 | *.apk
36 | *.unitypackage
37 |
--------------------------------------------------------------------------------
/BSIndexHapticFix.sln:
--------------------------------------------------------------------------------
1 |
2 | Microsoft Visual Studio Solution File, Format Version 12.00
3 | # Visual Studio Version 16
4 | VisualStudioVersion = 16.0.31729.503
5 | MinimumVisualStudioVersion = 10.0.40219.1
6 | Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BSIndexHapticFix", "BSIndexHapticFix\BSIndexHapticFix.csproj", "{71A6A586-1683-4545-9295-A1AF472480DA}"
7 | EndProject
8 | Global
9 | GlobalSection(SolutionConfigurationPlatforms) = preSolution
10 | Debug|Any CPU = Debug|Any CPU
11 | Release|Any CPU = Release|Any CPU
12 | EndGlobalSection
13 | GlobalSection(ProjectConfigurationPlatforms) = postSolution
14 | {71A6A586-1683-4545-9295-A1AF472480DA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
15 | {71A6A586-1683-4545-9295-A1AF472480DA}.Debug|Any CPU.Build.0 = Debug|Any CPU
16 | {71A6A586-1683-4545-9295-A1AF472480DA}.Release|Any CPU.ActiveCfg = Release|Any CPU
17 | {71A6A586-1683-4545-9295-A1AF472480DA}.Release|Any CPU.Build.0 = Release|Any CPU
18 | EndGlobalSection
19 | GlobalSection(SolutionProperties) = preSolution
20 | HideSolutionNode = FALSE
21 | EndGlobalSection
22 | GlobalSection(ExtensibilityGlobals) = postSolution
23 | SolutionGuid = {0D12CF74-A946-4E2A-9103-6E1D8CC943B6}
24 | EndGlobalSection
25 | EndGlobal
26 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/BSIndexHapticFix.csproj:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 | Debug
5 | AnyCPU
6 | 8.0.30703
7 | 2.0
8 | {71A6A586-1683-4545-9295-A1AF472480DA}
9 | Library
10 | Properties
11 | BSIndexHapticFix
12 | BSIndexHapticFix
13 | v4.7.2
14 | 512
15 | true
16 | portable
17 | ..\Refs
18 | $(LocalRefsDir)
19 | $(MSBuildProjectDirectory)\
20 |
21 | prompt
22 | 4
23 |
24 |
25 | false
26 | bin\Debug\
27 | DEBUG;TRACE
28 |
29 |
30 | true
31 | bin\Release\
32 | prompt
33 | 4
34 |
35 |
36 | True
37 |
38 |
39 | True
40 | True
41 |
42 |
43 |
44 | $(BeatSaberDir)\Libs\0Harmony.dll
45 | False
46 | False
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 | $(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll
56 | False
57 |
58 |
59 | $(BeatSaberDir)\Beat Saber_Data\Managed\HMLib.dll
60 | False
61 |
62 |
63 | $(BeatSaberDir)\Beat Saber_Data\Managed\HMUI.dll
64 | False
65 |
66 |
67 | $(BeatSaberDir)\Beat Saber_Data\Managed\IPA.Loader.dll
68 | False
69 |
70 |
71 | $(BeatSaberDir)\Beat Saber_Data\Managed\Unity.TextMeshPro.dll
72 | False
73 |
74 |
75 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.dll
76 | False
77 |
78 |
79 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.CoreModule.dll
80 | False
81 |
82 |
83 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UI.dll
84 | False
85 |
86 |
87 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIElementsModule.dll
88 | False
89 |
90 |
91 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.UIModule.dll
92 | False
93 |
94 |
95 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.VRModule.dll
96 | False
97 |
98 |
99 | $(BeatSaberDir)\Beat Saber_Data\Managed\UnityEngine.XRModule.dll
100 | False
101 | False
102 |
103 |
104 |
105 |
106 |
107 |
108 |
109 |
110 |
111 |
112 |
113 |
114 |
115 |
116 |
117 |
118 |
119 |
120 | 1.2.3
121 | runtime; build; native; contentfiles; analyzers; buildtransitive
122 | all
123 |
124 |
125 |
126 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/Components/HapticEmulator.cs:
--------------------------------------------------------------------------------
1 | using System.Collections;
2 | using System.Collections.Generic;
3 | using UnityEngine;
4 |
5 | namespace BSIndexHapticFix.Components
6 | {
7 | ///
8 | /// Haptic emulator for a single controller
9 | ///
10 | public class HapticEmulator : MonoBehaviour
11 | {
12 | ///
13 | /// Target XR Node
14 | ///
15 | public UnityEngine.XR.XRNode Node { get; private set; }
16 | ///
17 | /// Is it an Index knuckle?
18 | ///
19 | public bool IsIndexKnuckle { get; private set; } = false;
20 | ///
21 | /// Is left hand
22 | ///
23 | public bool IsLeftHand => Node == UnityEngine.XR.XRNode.LeftHand;
24 | ///
25 | /// Is right hand
26 | ///
27 | public bool IsRightHand => Node == UnityEngine.XR.XRNode.RightHand;
28 |
29 | ////////////////////////////////////////////////////////////////////////////
30 | ////////////////////////////////////////////////////////////////////////////
31 |
32 | ///
33 | /// Matching XR input device
34 | ///
35 | private UnityEngine.XR.InputDevice? m_Device = null;
36 | ///
37 | /// Remaining haptic time
38 | ///
39 | private float m_OpenVREmulatedHapticRemainingTime = 0f;
40 | ///
41 | /// Remaining haptic strength
42 | ///
43 | private float m_OpenVREmulatedHapticAmplitude = 0f;
44 |
45 | ////////////////////////////////////////////////////////////////////////////
46 | ////////////////////////////////////////////////////////////////////////////
47 |
48 | ///
49 | /// Start is called before the first frame update
50 | ///
51 | public void Init(UnityEngine.XR.XRNode p_Node)
52 | {
53 | Node = p_Node;
54 |
55 | /// Bind callbacks
56 | UnityEngine.XR.InputDevices.deviceConnected += InputDevices_deviceConnected;
57 | UnityEngine.XR.InputDevices.deviceDisconnected += InputDevices_deviceDisconnected;
58 |
59 | /// In case of late init, search device manually
60 | var l_AvailableDevicesAtNode = new List();
61 | UnityEngine.XR.InputDevices.GetDevicesAtXRNode(Node, l_AvailableDevicesAtNode);
62 | l_AvailableDevicesAtNode.ForEach(x => InputDevices_deviceConnected(x));
63 |
64 | GameObject.DontDestroyOnLoad(gameObject);
65 | }
66 | ///
67 | /// On component destroy
68 | ///
69 | private void OnDestroy()
70 | {
71 | /// Remove callbacks
72 | UnityEngine.XR.InputDevices.deviceDisconnected -= InputDevices_deviceDisconnected;
73 | UnityEngine.XR.InputDevices.deviceConnected -= InputDevices_deviceConnected;
74 | }
75 |
76 | ////////////////////////////////////////////////////////////////////////////
77 | ////////////////////////////////////////////////////////////////////////////
78 |
79 | public void SetHaptics(float p_Amplitude, float p_Duration)
80 | {
81 | float l_VibrationStrength = 2f;
82 |
83 | p_Duration *= l_VibrationStrength;
84 | p_Amplitude *= l_VibrationStrength;
85 |
86 | if (p_Amplitude <= 0.01f)
87 | return;
88 |
89 | m_OpenVREmulatedHapticRemainingTime = p_Duration;
90 | m_OpenVREmulatedHapticAmplitude = Mathf.Clamp01(p_Amplitude);
91 | }
92 |
93 | ////////////////////////////////////////////////////////////////////////////
94 | ////////////////////////////////////////////////////////////////////////////
95 |
96 | ///
97 | /// On XR device connected
98 | ///
99 | /// New device
100 | private void InputDevices_deviceConnected(UnityEngine.XR.InputDevice p_Device)
101 | {
102 | var l_RequiredCharacteristics = UnityEngine.XR.InputDeviceCharacteristics.Controller
103 | | UnityEngine.XR.InputDeviceCharacteristics.TrackedDevice
104 | | UnityEngine.XR.InputDeviceCharacteristics.HeldInHand
105 | | (IsLeftHand ? UnityEngine.XR.InputDeviceCharacteristics.Left : UnityEngine.XR.InputDeviceCharacteristics.Right);
106 |
107 | /// Check for matching role
108 | if (!p_Device.isValid || (p_Device.characteristics & l_RequiredCharacteristics) != l_RequiredCharacteristics)
109 | return;
110 |
111 | m_Device = p_Device;
112 | IsIndexKnuckle = m_Device.Value.name.ToLower().Contains("knuckles");
113 |
114 | if (IsIndexKnuckle)
115 | StartCoroutine(Coroutine_OpenVRHaptics());
116 |
117 | Plugin.Logger.Debug($"Device found \"{p_Device.manufacturer}\"-\"{p_Device.name}\" with role \"{p_Device.characteristics}\" serial {p_Device.serialNumber} is index knuckle? {IsIndexKnuckle}");
118 | }
119 | ///
120 | /// On XR device disconnected
121 | ///
122 | /// Disconnected device
123 | private void InputDevices_deviceDisconnected(UnityEngine.XR.InputDevice p_Device)
124 | {
125 | /// Check for matching role
126 | if (!m_Device.HasValue || m_Device != p_Device)
127 | return;
128 |
129 | m_Device = null;
130 | StopAllCoroutines();
131 |
132 | Plugin.Logger.Debug($"[OPVR.VRController] Device lost \"{p_Device.manufacturer}\"-\"{p_Device.name}\" with role \"{p_Device.characteristics}\"");
133 | }
134 |
135 | ////////////////////////////////////////////////////////////////////////////
136 | ////////////////////////////////////////////////////////////////////////////
137 |
138 | ///
139 | /// Handles the haptic process every 1/80 second.
140 | ///
141 | ///
142 | private IEnumerator Coroutine_OpenVRHaptics()
143 | {
144 | const float s_Rate = 1 / 80f;
145 |
146 | var l_Waiter = new WaitForSecondsRealtime(s_Rate);
147 | while (true)
148 | {
149 | m_OpenVREmulatedHapticRemainingTime -= s_Rate;
150 |
151 | if (m_OpenVREmulatedHapticRemainingTime > 0f)
152 | m_Device?.SendHapticImpulse(0, m_OpenVREmulatedHapticAmplitude);
153 |
154 | yield return l_Waiter;
155 | }
156 | }
157 | }
158 | }
159 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/Directory.Build.props:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | true
6 | true
7 | true
8 |
9 |
10 | false
11 | true
12 | true
13 |
14 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/Directory.Build.targets:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | 2.0
7 |
8 | false
9 |
10 | $(OutputPath)$(AssemblyName)
11 |
12 | $(OutputPath)Final
13 | True
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
25 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
33 |
34 |
35 | $(AssemblyName)
36 | $(ArtifactName)-$(PluginVersion)
37 | $(ArtifactName)-bs$(GameVersion)
38 | $(ArtifactName)-$(CommitHash)
39 |
40 |
41 |
42 |
43 |
44 |
45 | $(AssemblyName)
46 |
47 |
48 |
49 |
50 |
51 |
52 |
53 |
54 |
55 |
56 |
57 |
58 |
59 | $(AssemblyName)
60 | $(OutDir)zip\
61 |
62 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
72 |
73 | $(BeatSaberDir)\Plugins
74 | True
75 | Unable to copy assembly to game folder, did you set 'BeatSaberDir' correctly in your 'csproj.user' file? Plugins folder doesn't exist: '$(PluginDir)'.
76 |
77 | Unable to copy to Plugins folder, '$(BeatSaberDir)' does not appear to be a Beat Saber game install.
78 |
79 | Unable to copy to Plugins folder, 'BeatSaberDir' has not been set in your 'csproj.user' file.
80 | False
81 |
82 |
83 |
84 |
85 |
86 |
87 |
88 | $(BeatSaberDir)\IPA\Pending\Plugins
89 |
90 |
91 |
92 |
93 |
94 |
95 |
96 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/HarmonyPatches/POpenVRHelper_TriggerHapticPulse.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using UnityEngine.XR;
3 |
4 | namespace BSIndexHapticFix.HarmonyPatches
5 | {
6 | [HarmonyPatch(typeof(OpenVRHelper))]
7 | [HarmonyPatch(nameof(OpenVRHelper.TriggerHapticPulse))]
8 | internal class POpenVRHelper_TriggerHapticPulse
9 | {
10 | static bool Prefix(XRNode node, float duration, float strength, float frequency)
11 | {
12 | if (node == XRNode.LeftHand && Plugin.LeftHapticEmulator && Plugin.LeftHapticEmulator.IsIndexKnuckle)
13 | {
14 | Plugin.LeftHapticEmulator.SetHaptics(strength, duration);
15 |
16 | /// Skip original method
17 | return false;
18 | }
19 |
20 | if (node == XRNode.RightHand && Plugin.RightHapticEmulator && Plugin.RightHapticEmulator.IsIndexKnuckle)
21 | {
22 | Plugin.RightHapticEmulator.SetHaptics(strength, duration);
23 |
24 | /// Skip original method
25 | return false;
26 | }
27 |
28 | /// Forward to base method
29 | return true;
30 | }
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/Plugin.cs:
--------------------------------------------------------------------------------
1 | using HarmonyLib;
2 | using IPA;
3 | using System.Reflection;
4 | using UnityEngine;
5 |
6 | namespace BSIndexHapticFix
7 | {
8 | ///
9 | /// Plugin main class
10 | ///
11 | [Plugin(RuntimeOptions.SingleStartInit)]
12 | public class Plugin
13 | {
14 | ///
15 | /// Logger instance
16 | ///
17 | internal static IPA.Logging.Logger Logger { get; private set; }
18 | ///
19 | /// Harmony ID for patches
20 | ///
21 | internal const string HarmonyID = "com.github.hardcpp.bsindexhapticfix";
22 | ///
23 | /// Left controller haptic emulator
24 | ///
25 | internal static Components.HapticEmulator LeftHapticEmulator;
26 | ///
27 | /// Right controller haptic emulator
28 | ///
29 | internal static Components.HapticEmulator RightHapticEmulator;
30 |
31 | ////////////////////////////////////////////////////////////////////////////
32 | ////////////////////////////////////////////////////////////////////////////
33 |
34 | ///
35 | /// Harmony patch holder
36 | ///
37 | private static Harmony m_Harmony;
38 |
39 | ////////////////////////////////////////////////////////////////////////////
40 | ////////////////////////////////////////////////////////////////////////////
41 |
42 | ///
43 | /// Called when the plugin is first loaded by IPA (either when the game starts or when the plugin is enabled if it starts disabled).
44 | /// [Init] methods that use a Constructor or called before regular methods like InitWithConfig.
45 | /// Only use [Init] with one Constructor.
46 | ///
47 | [Init]
48 | public void Init(IPA.Logging.Logger p_Logger)
49 | {
50 | Logger = p_Logger;
51 |
52 | Logger.Info("BSIndexHapticFix initialized.");
53 | }
54 |
55 | ////////////////////////////////////////////////////////////////////////////
56 | ////////////////////////////////////////////////////////////////////////////
57 |
58 | ///
59 | /// On application start
60 | ///
61 | [OnStart]
62 | public void OnApplicationStart()
63 | {
64 | try
65 | {
66 | Logger.Debug("OnApplicationStart");
67 |
68 | m_Harmony = new Harmony(HarmonyID);
69 | m_Harmony.PatchAll(Assembly.GetExecutingAssembly());
70 |
71 | LeftHapticEmulator = new GameObject("BSIndexHapticFix_Left").AddComponent();
72 | LeftHapticEmulator.Init(UnityEngine.XR.XRNode.LeftHand);
73 |
74 | RightHapticEmulator = new GameObject("BSIndexHapticFix_Right").AddComponent();
75 | RightHapticEmulator.Init(UnityEngine.XR.XRNode.RightHand);
76 | }
77 | catch (System.Exception p_Exception)
78 | {
79 | Logger.Critical(p_Exception);
80 | }
81 | }
82 | ///
83 | /// On application quit
84 | ///
85 | [OnExit]
86 | public void OnApplicationQuit()
87 | {
88 | try
89 | {
90 | Logger.Debug("OnApplicationQuit");
91 |
92 | m_Harmony.UnpatchAll(HarmonyID);
93 |
94 | GameObject.Destroy(LeftHapticEmulator);
95 | GameObject.Destroy(RightHapticEmulator);
96 | }
97 | catch (System.Exception p_Exception)
98 | {
99 | Logger.Critical(p_Exception);
100 | }
101 | }
102 | }
103 | }
104 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/Properties/AssemblyInfo.cs:
--------------------------------------------------------------------------------
1 | using System.Reflection;
2 | using System.Runtime.CompilerServices;
3 | using System.Runtime.InteropServices;
4 |
5 | // General Information about an assembly is controlled through the following
6 | // set of attributes. Change these attribute values to modify the information
7 | // associated with an assembly.
8 | [assembly: AssemblyTitle("BSIndexHapticFix")]
9 | [assembly: AssemblyDescription("")]
10 | [assembly: AssemblyConfiguration("")]
11 | [assembly: AssemblyCompany("")]
12 | [assembly: AssemblyProduct("BSIndexHapticFix")]
13 | [assembly: AssemblyCopyright("Copyright © 2021")]
14 | [assembly: AssemblyTrademark("")]
15 | [assembly: AssemblyCulture("")]
16 |
17 | // Setting ComVisible to false makes the types in this assembly not visible
18 | // to COM components. If you need to access a type in this assembly from
19 | // COM, set the ComVisible attribute to true on that type.
20 | [assembly: ComVisible(false)]
21 |
22 | // The following GUID is for the ID of the typelib if this project is exposed to COM
23 | [assembly: Guid("6bf8f645-643e-4239-8eda-38337bb12868")]
24 |
25 | // Version information for an assembly consists of the following four values:
26 | //
27 | // Major Version
28 | // Minor Version
29 | // Build Number
30 | // Revision
31 | //
32 | // You can specify all the values or you can default the Build and Revision Numbers
33 | // by using the '*' as shown below:
34 | // [assembly: AssemblyVersion("1.0.*")]
35 | [assembly: AssemblyVersion("0.0.2")]
36 | [assembly: AssemblyFileVersion("0.0.2")]
37 |
--------------------------------------------------------------------------------
/BSIndexHapticFix/manifest.json:
--------------------------------------------------------------------------------
1 | {
2 | "$schema": "https://raw.githubusercontent.com/bsmg/BSIPA-MetadataFileSchema/master/Schema.json",
3 | "id": "BSIndexHapticFix",
4 | "name": "BSIndexHapticFix",
5 | "author": "HardCPP#1985",
6 | "version": "0.0.2",
7 | "description": "A simple mod that fix index users haptics on knuckles",
8 | "gameVersion": "1.18.0",
9 | "dependsOn": {
10 | "BSIPA": "^4.0.5"
11 | },
12 | "features": []
13 | }
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2021 HardCPP
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # BSIndexHapticFix
2 | Fix haptics on index knucles
3 |
--------------------------------------------------------------------------------