diff --git a/.gitmodules b/.gitmodules index c8d1f40..719b751 100644 --- a/.gitmodules +++ b/.gitmodules @@ -1,3 +1,6 @@ [submodule "VRGIN_OpenXR"] path = VRGIN_OpenXR - url = https://github.com/ManlyMarco/VRGIN_OpenXR \ No newline at end of file + url = https://github.com/ManlyMarco/VRGIN_OpenXR +[submodule "VRGIN"] + path = VRGIN + url = https://github.com/lotsofbearsKoikatsu/VRGIN diff --git a/CharaStudioVR/CharaStudioVR.csproj b/CharaStudioVR/CharaStudioVR.csproj index f0837b1..0b8142a 100644 --- a/CharaStudioVR/CharaStudioVR.csproj +++ b/CharaStudioVR/CharaStudioVR.csproj @@ -24,7 +24,7 @@ prompt 4 true - 8 + Preview embedded @@ -36,6 +36,7 @@ true 8 true + Preview @@ -66,6 +67,12 @@ ..\packages\ExtensibleSaveFormat.KoikatsuSunshine.16.8.1\lib\net46\KKS_ExtensibleSaveFormat.dll False + + ..\..\KK_SensibleH\Libs\KK_FinalIK.dll + + + ..\Libs\Obi.dll + ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.CoreModule.2019.4.9\lib\net46\Sirenix.Serialization.dll False @@ -184,6 +191,11 @@ + + + + + @@ -204,6 +216,8 @@ + + diff --git a/Shared/Controls/BetterMenuTool.cs b/CharaStudioVR/Controls/BetterMenuTool.cs similarity index 86% rename from Shared/Controls/BetterMenuTool.cs rename to CharaStudioVR/Controls/BetterMenuTool.cs index 21047bb..107859b 100644 --- a/Shared/Controls/BetterMenuTool.cs +++ b/CharaStudioVR/Controls/BetterMenuTool.cs @@ -9,23 +9,24 @@ using VRGIN.Visuals; using Object = UnityEngine.Object; -namespace KKS_VR.Controls + +namespace KK_VR.Controls { /// /// MenuTool that supports right clicks by pressing the trackpad /// internal class BetterMenuTool : MenuTool { - public override List GetHelpTexts() - { - return new List(new[] - { - ToolUtil.HelpTrackpadCenter(Owner, "Press to Right Click"), - ToolUtil.HelpTrackpadUp(Owner, "Slide to Move Cursor"), - ToolUtil.HelpTrigger(Owner, "Left Click"), - ToolUtil.HelpGrip(Owner, "Take/release screen") - }.Where(x => x != null)); - } + //public override List GetHelpTexts() + //{ + // return new List(new[] + // { + // ToolUtil.HelpTrackpadCenter(Owner, "Press to Right Click"), + // ToolUtil.HelpTrackpadUp(Owner, "Slide to Move Cursor"), + // ToolUtil.HelpTrigger(Owner, "Left Click"), + // ToolUtil.HelpGrip(Owner, "Take/release screen") + // }.Where(x => x != null)); + //} #region Override base functionality to add right clicks diff --git a/Shared/Controls/BetterWarpTool.cs b/CharaStudioVR/Controls/BetterWarpTool.cs similarity index 74% rename from Shared/Controls/BetterWarpTool.cs rename to CharaStudioVR/Controls/BetterWarpTool.cs index 9dc8f2e..93e6680 100644 --- a/Shared/Controls/BetterWarpTool.cs +++ b/CharaStudioVR/Controls/BetterWarpTool.cs @@ -5,7 +5,9 @@ using VRGIN.Controls; using VRGIN.Controls.Tools; -namespace KKS_VR.Controls + +// MainGame doesn't have warp tool no more. +namespace KK_VR.Controls { /// /// WarpTool version that cancels teleporting when user tries grab moving @@ -32,13 +34,13 @@ protected override void OnUpdate() base.OnUpdate(); } - public override List GetHelpTexts() - { - return new List(new[] - { - ToolUtil.HelpTrackpadCenter(Owner, "Press to teleport"), - ToolUtil.HelpGrip(Owner, "Hold to move"), - }.Where(x => x != null)); - } + //public override List GetHelpTexts() + //{ + // return new List(new[] + // { + // ToolUtil.HelpTrackpadCenter(Owner, "Press to teleport"), + // ToolUtil.HelpGrip(Owner, "Hold to move"), + // }.Where(x => x != null)); + //} } } diff --git a/CharaStudioVR/Controls/GripMenuHandler.cs b/CharaStudioVR/Controls/GripMenuHandler.cs index 190e0b0..03be0d3 100644 --- a/CharaStudioVR/Controls/GripMenuHandler.cs +++ b/CharaStudioVR/Controls/GripMenuHandler.cs @@ -1,5 +1,5 @@ using System.Linq; -using KKS_VR.Settings; +using KK_VR.Settings; using UnityEngine; using Valve.VR; using VRGIN.Controls; @@ -7,7 +7,7 @@ using VRGIN.Native; using VRGIN.Visuals; -namespace KKS_VR.Controls +namespace KK_VR.Controls { public class GripMenuHandler : ProtectedBehaviour { diff --git a/CharaStudioVR/Controls/GripMoveStudioNEOV2Tool.cs b/CharaStudioVR/Controls/GripMoveStudioNEOV2Tool.cs index 5f54aaa..582c6da 100644 --- a/CharaStudioVR/Controls/GripMoveStudioNEOV2Tool.cs +++ b/CharaStudioVR/Controls/GripMoveStudioNEOV2Tool.cs @@ -1,8 +1,8 @@ using System; using System.Collections; using System.Collections.Generic; -using KKS_VR.Settings; -using KKS_VR.Util; +using KK_VR.Settings; +using KK_VR.Util; using Studio; using UnityEngine; using UnityEngine.Rendering; @@ -15,7 +15,7 @@ using VRGIN.Helpers; using VRGIN.Visuals; -namespace KKS_VR.Controls +namespace KK_VR.Controls { internal class GripMoveStudioNEOV2Tool : Tool { @@ -89,7 +89,7 @@ private void CreatePointer() } } - protected override void OnDestroy() + private void OnDestroy() { if (marker != null) Destroy(marker); if (mirror1 != null) Destroy(mirror1); diff --git a/CharaStudioVR/Controls/IKTool.cs b/CharaStudioVR/Controls/IKTool.cs index 25ffbf3..ad69e0d 100644 --- a/CharaStudioVR/Controls/IKTool.cs +++ b/CharaStudioVR/Controls/IKTool.cs @@ -1,12 +1,12 @@ using System; using System.Collections; -using KKS_VR.Util; +using KK_VR.Util; using Studio; using UnityEngine; using UnityEngine.Rendering; using UnityEngine.SceneManagement; -namespace KKS_VR.Controls +namespace KK_VR.Controls { public class IKTool : MonoBehaviour { diff --git a/CharaStudioVR/Controls/ObjMoveHelper.cs b/CharaStudioVR/Controls/ObjMoveHelper.cs index 0e1a4ae..fac9529 100644 --- a/CharaStudioVR/Controls/ObjMoveHelper.cs +++ b/CharaStudioVR/Controls/ObjMoveHelper.cs @@ -2,7 +2,7 @@ using Studio; using UnityEngine; -namespace KKS_VR.Controls +namespace KK_VR.Controls { internal class ObjMoveHelper { diff --git a/CharaStudioVR/Controls/VRCameraMoveHelper.cs b/CharaStudioVR/Controls/VRCameraMoveHelper.cs index 23097dd..0fe9d1c 100644 --- a/CharaStudioVR/Controls/VRCameraMoveHelper.cs +++ b/CharaStudioVR/Controls/VRCameraMoveHelper.cs @@ -5,7 +5,7 @@ using UnityEngine.UI; using VRGIN.Core; -namespace KKS_VR.Controls +namespace KK_VR.Controls { public class VRCameraMoveHelper : MonoBehaviour { diff --git a/CharaStudioVR/Controls/VRItemObjMoveHelper.cs b/CharaStudioVR/Controls/VRItemObjMoveHelper.cs index 95b73d7..e139b8c 100644 --- a/CharaStudioVR/Controls/VRItemObjMoveHelper.cs +++ b/CharaStudioVR/Controls/VRItemObjMoveHelper.cs @@ -8,7 +8,7 @@ using VRGIN.Core; using VRGIN.Helpers; -namespace KKS_VR.Controls +namespace KK_VR.Controls { public class VRItemObjMoveHelper : MonoBehaviour { diff --git a/CharaStudioVR/Features/VRBoop.cs b/CharaStudioVR/Features/VRBoop.cs new file mode 100644 index 0000000..904b64f --- /dev/null +++ b/CharaStudioVR/Features/VRBoop.cs @@ -0,0 +1,131 @@ +using System; +using System.Collections.Generic; +using HarmonyLib; +using UnityEngine; +using UnityEngine.XR; +using VRGIN.Controls; + +// MainGame uses a bit different implementation. +namespace KK_VR.Features +{ + /// + /// Adds colliders to the controllers so you can boop things + /// Based on a feature in KK_VREnhancement by thojmr + /// https://github.com/thojmr/KK_VREnhancement/blob/5e46bc9a89bf2517c5482bc9df097c7f0274730f/KK_VREnhancement/VRController.Collider.cs + /// + public static class VRBoopStudio + { + internal const string LeftColliderName = "Left_Boop_Collider"; + internal const string RightColliderName = "Right_Boop_Collider"; + + private static DynamicBoneCollider _leftCollider; + private static DynamicBoneCollider _rightCollider; + + public static void Initialize(Controller controller, EyeSide controllerSide) + { + // Hooks in here don't get patched by the whole assembly PatchAll since the class has no HarmonyPatch attribute + Harmony.CreateAndPatchAll(typeof(VRBoop), typeof(VRBoop).FullName); + + switch (controllerSide) + { + case EyeSide.Left: + _leftCollider = GetOrAttachCollider(controller.gameObject, LeftColliderName); + break; + case EyeSide.Right: + _rightCollider = GetOrAttachCollider(controller.gameObject, RightColliderName); + break; + default: + throw new ArgumentOutOfRangeException(nameof(controllerSide), controllerSide, null); + } + } + + [HarmonyPostfix] + [HarmonyWrapSafe] + [HarmonyPatch(typeof(DynamicBone), nameof(DynamicBone.SetupParticles))] + [HarmonyPatch(typeof(DynamicBone_Ver01), nameof(DynamicBone_Ver01.SetupParticles))] + [HarmonyPatch(typeof(DynamicBone_Ver02), nameof(DynamicBone_Ver02.SetupParticles))] + private static void OnDynamicBoneInit(MonoBehaviour __instance) + { + AttachControllerColliders(__instance); + } + + [HarmonyPrefix] + [HarmonyWrapSafe] + [HarmonyPatch(typeof(ChaControl), nameof(ChaControl.LoadCharaFbxDataAsync))] + // [HarmonyPatch(typeof(ChaControl), nameof(ChaControl.LoadCharaFbxDataNoAsync))] // unnecessary, the collider array is reset before the SetupParticles hook + private static void OnClothesChanged(ref Action actObj) + { + // This action is called with the loaded object after the colliders on it are set up + // This needs to be done despite the SetupParticles hook because LoadCharaFbxData resets the collider list + actObj += newObj => + { + if (newObj == null) return; + foreach (var newBone in newObj.GetComponentsInChildren()) + { + var colliders = newBone.m_Colliders; + if (colliders != null) + AttachControllerColliders(colliders); + } + }; + } + + private static void AttachControllerColliders(MonoBehaviour dynamicBone) + { + var colliderList = GetColliderList(dynamicBone); + if (colliderList == null) return; + AttachControllerColliders(colliderList); + } + + private static void AttachControllerColliders(List colliderList) + { + if (colliderList == null) throw new ArgumentNullException(nameof(colliderList)); + + if (_leftCollider && !colliderList.Contains(_leftCollider)) + colliderList.Add(_leftCollider); + if (_rightCollider && !colliderList.Contains(_rightCollider)) + colliderList.Add(_rightCollider); + } + + private static List GetColliderList(MonoBehaviour dynamicBone) + { + return dynamicBone switch + { + DynamicBone d => d.m_Colliders, + DynamicBone_Ver01 d => d.m_Colliders, + DynamicBone_Ver02 d => d.Colliders, + null => throw new ArgumentNullException(nameof(dynamicBone)), + _ => throw new ArgumentException(@"Not a DynamicBone - " + dynamicBone.GetType(), nameof(dynamicBone)), + }; + } + + private static DynamicBoneCollider GetOrAttachCollider(GameObject controllerGameObject, string colliderName) + { + if (controllerGameObject == null) throw new ArgumentNullException(nameof(controllerGameObject)); + if (colliderName == null) throw new ArgumentNullException(nameof(colliderName)); + + //Check for existing DB collider that may have been attached earlier + var existingCollider = controllerGameObject.GetComponentInChildren(); + if (existingCollider == null) + { + //Add a DB collider to the controller + return AddDbCollider(controllerGameObject, colliderName); + } + + return existingCollider; + } + + private static DynamicBoneCollider AddDbCollider(GameObject controllerGameObject, string colliderName, + float colliderRadius = 0.05f, float collierHeight = 0f, Vector3 colliderCenter = new Vector3(), DynamicBoneCollider.Direction colliderDirection = default) + { + //Build the dynamic bone collider + var colliderObject = new GameObject(colliderName); + var collider = colliderObject.AddComponent(); + collider.m_Radius = colliderRadius; + collider.m_Height = collierHeight; + collider.m_Center = colliderCenter; + collider.m_Direction = colliderDirection; + colliderObject.transform.SetParent(controllerGameObject.transform, false); + return collider; + } + } +} diff --git a/CharaStudioVR/Fixes/LoadFixHook.cs b/CharaStudioVR/Fixes/LoadFixHook.cs index a92aeb4..0cf2fe1 100644 --- a/CharaStudioVR/Fixes/LoadFixHook.cs +++ b/CharaStudioVR/Fixes/LoadFixHook.cs @@ -1,11 +1,11 @@ using System; using BepInEx.Logging; using HarmonyLib; -using KKS_VR.Interpreters; +using KK_VR.Interpreters; using Studio; using VRGIN.Core; -namespace KKS_VR.Fixes +namespace KK_VR.Fixes { public static class LoadFixHook { diff --git a/Shared/Fixes/OpenVRHelperTempfixHook.cs b/CharaStudioVR/Fixes/OpenVRHelperTempfixHook.cs similarity index 90% rename from Shared/Fixes/OpenVRHelperTempfixHook.cs rename to CharaStudioVR/Fixes/OpenVRHelperTempfixHook.cs index 772824a..3b3eb3b 100644 --- a/Shared/Fixes/OpenVRHelperTempfixHook.cs +++ b/CharaStudioVR/Fixes/OpenVRHelperTempfixHook.cs @@ -2,7 +2,8 @@ using HarmonyLib; using Unity.XR.OpenVR; -namespace KKS_VR.Fixes +// Will break haptic feedback in KKS if enabled. +namespace KK_VR.Fixes { /// /// No idea what exactly it does but it doesn't seem to hurt anything. Originally a part of KKS_CharaStudioVR diff --git a/CharaStudioVR/Fixes/SaveLoadSceneHook.cs b/CharaStudioVR/Fixes/SaveLoadSceneHook.cs index 89b02c6..e547c5d 100644 --- a/CharaStudioVR/Fixes/SaveLoadSceneHook.cs +++ b/CharaStudioVR/Fixes/SaveLoadSceneHook.cs @@ -2,17 +2,17 @@ using System.Reflection; using BepInEx.Logging; using HarmonyLib; -using KKS_VR.Controls; +using KK_VR.Controls; using Manager; using Studio; using UnityEngine; using VRGIN.Core; -namespace KKS_VR.Fixes +namespace KK_VR.Fixes { public static class SaveLoadSceneHook { - private static Camera[] backupRenderCam; + private static UnityEngine.Camera[] backupRenderCam; private static Sprite sceneLoadScene_spriteLoad; @@ -24,17 +24,17 @@ public static void InstallHook() [HarmonyPrefix] [HarmonyPatch(typeof(Studio.Studio), "SaveScene", new Type[] { })] - public static bool SaveScenePreHook(Studio.Studio __instance, ref Camera[] __state) + public static bool SaveScenePreHook(Studio.Studio __instance, ref UnityEngine.Camera[] __state) { VRPlugin.Logger.Log(LogLevel.Debug, "Update Camera position and rotation for Scene Capture and last Camera data."); try { VRCameraMoveHelper.Instance.CurrentToCameraCtrl(); var field = typeof(Studio.GameScreenShot).GetField("renderCam", BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - var obj = field.GetValue(Singleton.Instance.gameScreenShot) as Camera[]; + var obj = field.GetValue(Singleton.Instance.gameScreenShot) as UnityEngine.Camera[]; VRPlugin.Logger.Log(LogLevel.Debug, "Backup Screenshot render cam."); backupRenderCam = obj; - var value = new Camera[1] { VR.Camera.SteamCam.camera }; + var value = new UnityEngine.Camera[1] { VR.Camera.SteamCam.camera }; __state = backupRenderCam; field.SetValue(Singleton.Instance.gameScreenShot, value); } @@ -49,7 +49,7 @@ public static bool SaveScenePreHook(Studio.Studio __instance, ref Camera[] __sta [HarmonyPostfix] [HarmonyPatch(typeof(Studio.Studio), "SaveScene", new Type[] { })] - public static void SaveScenePostHook(Studio.Studio __instance, Camera[] __state) + public static void SaveScenePostHook(Studio.Studio __instance, UnityEngine.Camera[] __state) { VRPlugin.Logger.Log(LogLevel.Debug, "Restore backup render cam."); try diff --git a/Shared/Fixes/TopmostToolIcons.cs b/CharaStudioVR/Fixes/TopmostToolIcons.cs similarity index 96% rename from Shared/Fixes/TopmostToolIcons.cs rename to CharaStudioVR/Fixes/TopmostToolIcons.cs index 4992548..5829107 100644 --- a/Shared/Fixes/TopmostToolIcons.cs +++ b/CharaStudioVR/Fixes/TopmostToolIcons.cs @@ -5,7 +5,9 @@ using VRGIN.Controls; using VRGIN.Core; -namespace KKS_VR.Fixes + +// Only single tool left, no icons needed. +namespace KK_VR.Fixes { /// /// Fix custom tool icons not being on top of the black circle diff --git a/Shared/Fixes/topmostgui.shader b/CharaStudioVR/Fixes/topmostgui.shader similarity index 100% rename from Shared/Fixes/topmostgui.shader rename to CharaStudioVR/Fixes/topmostgui.shader diff --git a/Shared/Fixes/topmostguishader b/CharaStudioVR/Fixes/topmostguishader similarity index 100% rename from Shared/Fixes/topmostguishader rename to CharaStudioVR/Fixes/topmostguishader diff --git a/CharaStudioVR/Interpreters/KKSCharaStudioActor.cs b/CharaStudioVR/Interpreters/KKSCharaStudioActor.cs index 20c2133..a9068dd 100644 --- a/CharaStudioVR/Interpreters/KKSCharaStudioActor.cs +++ b/CharaStudioVR/Interpreters/KKSCharaStudioActor.cs @@ -1,11 +1,11 @@ using System; using System.Configuration; -using KKS_VR.Settings; +using KK_VR.Settings; using UnityEngine; using VRGIN.Core; using VRGIN.Helpers; -namespace KKS_VR.Interpreters +namespace KK_VR.Interpreters { public class KKSCharaStudioActor : DefaultActorBehaviour { @@ -38,7 +38,7 @@ protected override void OnLateUpdate() base.OnLateUpdate(); var eyeLookCtrl = Actor.eyeLookCtrl; var neckLookCtrl = Actor.neckLookCtrl; - var transform = Camera.main.transform; + var transform = UnityEngine.Camera.main.transform; if ((bool)transform) { if ((bool)eyeLookCtrl && eyeLookCtrl.target == transform) eyeLookCtrl.target = _TargetController.Target; @@ -73,7 +73,7 @@ internal void OnVRModeChanged(bool newMode) var eyeLookCtrl = Actor.eyeLookCtrl; var neckLookCtrl = Actor.neckLookCtrl; - var transform = Camera.main.transform; + var transform = UnityEngine.Camera.main.transform; if ((bool)transform) { if ((bool)eyeLookCtrl && eyeLookCtrl.target == _TargetController.Target) eyeLookCtrl.target = transform; diff --git a/CharaStudioVR/Interpreters/KKSCharaStudioInterpreter.cs b/CharaStudioVR/Interpreters/KKSCharaStudioInterpreter.cs index 803f5bb..53953e6 100644 --- a/CharaStudioVR/Interpreters/KKSCharaStudioInterpreter.cs +++ b/CharaStudioVR/Interpreters/KKSCharaStudioInterpreter.cs @@ -9,7 +9,7 @@ using UnityEngine.SceneManagement; using VRGIN.Core; -namespace KKS_VR.Interpreters +namespace KK_VR.Interpreters { internal class KKSCharaStudioInterpreter : GameInterpreter { @@ -35,7 +35,7 @@ protected override void OnStart() FixMenuCanvasLayers(); } - public override Camera FindCamera() + public override UnityEngine.Camera FindCamera() { return null; } @@ -82,7 +82,7 @@ private void SyncVRCameraSkybox() private void UpdateMainCameraCullingMask() { - var component = VR.Camera.SteamCam.GetComponent(); + var component = VR.Camera.SteamCam.GetComponent(); var list = new List(); var obj = new List { "Studio/Col", "Studio/Select" }; diff --git a/CharaStudioVR/Interpreters/TransientHead.cs b/CharaStudioVR/Interpreters/TransientHead.cs index 512d6a1..20846f6 100644 --- a/CharaStudioVR/Interpreters/TransientHead.cs +++ b/CharaStudioVR/Interpreters/TransientHead.cs @@ -5,7 +5,7 @@ using VRGIN.Core; using VRGIN.Helpers; -namespace KKS_VR.Interpreters +namespace KK_VR.Interpreters { public class TransientHead : ProtectedBehaviour { diff --git a/CharaStudioVR/Settings/CharaStudioContext.cs b/CharaStudioVR/Settings/CharaStudioContext.cs index cd60c6c..4040c12 100644 --- a/CharaStudioVR/Settings/CharaStudioContext.cs +++ b/CharaStudioVR/Settings/CharaStudioContext.cs @@ -4,7 +4,7 @@ using VRGIN.Core; using VRGIN.Visuals; -namespace KKS_VR.Settings +namespace KK_VR.Settings { [XmlRoot("Context")] public class CharaStudioContext : IVRManagerContext diff --git a/CharaStudioVR/Settings/StudioSettings.cs b/CharaStudioVR/Settings/StudioSettings.cs index 090eb0b..a9e1c02 100644 --- a/CharaStudioVR/Settings/StudioSettings.cs +++ b/CharaStudioVR/Settings/StudioSettings.cs @@ -3,7 +3,7 @@ using KKAPI.Utilities; using VRGIN.Core; -namespace KKS_VR.Settings +namespace KK_VR.Settings { public static class StudioSettings { diff --git a/CharaStudioVR/StudioStandingMode.cs b/CharaStudioVR/StudioStandingMode.cs index e02edcf..1edc6a3 100644 --- a/CharaStudioVR/StudioStandingMode.cs +++ b/CharaStudioVR/StudioStandingMode.cs @@ -1,13 +1,13 @@ using System; using System.Collections.Generic; -using KKS_VR.Controls; -using KKS_VR.Features; -using KKS_VR.Settings; +using KK_VR.Controls; +using KK_VR.Features; +using KK_VR.Settings; using UnityEngine.XR; using VRGIN.Controls; using VRGIN.Modes; -namespace KKS_VR +namespace KK_VR { internal class StudioStandingMode : StandingMode { @@ -35,7 +35,7 @@ protected override Controller CreateRightController() private static void AddComponents(Controller controller, EyeSide controllerSide) { if (StudioSettings.EnableBoop.Value) - VRBoop.Initialize(controller, controllerSide); + VRBoopStudio.Initialize(controller, controllerSide); } } } diff --git a/CharaStudioVR/Util/MaterialHelper.cs b/CharaStudioVR/Util/MaterialHelper.cs index 10bcfae..8620c2e 100644 --- a/CharaStudioVR/Util/MaterialHelper.cs +++ b/CharaStudioVR/Util/MaterialHelper.cs @@ -2,7 +2,7 @@ using KKAPI.Utilities; using UnityEngine; -namespace KKS_VR.Util +namespace KK_VR.Util { internal class MaterialHelper { diff --git a/CharaStudioVR/Util/MoveableGUIObject.cs b/CharaStudioVR/Util/MoveableGUIObject.cs index c9c4526..9dec632 100644 --- a/CharaStudioVR/Util/MoveableGUIObject.cs +++ b/CharaStudioVR/Util/MoveableGUIObject.cs @@ -4,7 +4,7 @@ using UnityEngine; using VRGIN.Core; -namespace KKS_VR.Util +namespace KK_VR.Util { public class MoveableGUIObject : MonoBehaviour { diff --git a/CharaStudioVR/VRPlugin.cs b/CharaStudioVR/VRPlugin.cs index 555bba7..60e5557 100644 --- a/CharaStudioVR/VRPlugin.cs +++ b/CharaStudioVR/VRPlugin.cs @@ -3,18 +3,19 @@ using System.Runtime.InteropServices; using BepInEx; using BepInEx.Logging; +using KK_VR; using KKAPI; -using KKS_VR.Controls; -using KKS_VR.Fixes; -using KKS_VR.Interpreters; -using KKS_VR.Settings; +using KK_VR.Controls; +using KK_VR.Fixes; +using KK_VR.Interpreters; +using KK_VR.Settings; using Unity.XR.OpenVR; using UnityEngine; using UnityEngine.SceneManagement; using Valve.VR; using VRGIN.Core; -namespace KKS_VR +namespace KK_VR { /// /// Studio code was forked from KKS_CharaStudioVR at https://vr-erogamer.com/archives/1065 @@ -136,7 +137,7 @@ private IEnumerator LoadDevice(VRSettings settings) private void UpdateNearClipPlane() { - VR.Camera.GetComponent().nearClipPlane = StudioSettings.NearClipPlane.Value; + VR.Camera.GetComponent().nearClipPlane = StudioSettings.NearClipPlane.Value; } private static class NativeMethods diff --git a/KKS_VR.sln b/KKS_VR.sln index 192b54d..c60b1c3 100644 --- a/KKS_VR.sln +++ b/KKS_VR.sln @@ -3,67 +3,134 @@ Microsoft Visual Studio Solution File, Format Version 12.00 # Visual Studio Version 17 VisualStudioVersion = 17.5.33424.131 MinimumVisualStudioVersion = 10.0.40219.1 -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "MainGameVR", "MainGameVR\MainGameVR.csproj", "{1F634B93-F4C6-45E9-894D-2F3556A74D67}" - ProjectSection(ProjectDependencies) = postProject - {4B6E4305-C95A-4274-94A6-B04A1B01989D} = {4B6E4305-C95A-4274-94A6-B04A1B01989D} - EndProjectSection -EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "CharaStudioVR", "CharaStudioVR\CharaStudioVR.csproj", "{07ECD6A8-61D0-4409-A009-C138E76DFA1F}" ProjectSection(ProjectDependencies) = postProject {4B6E4305-C95A-4274-94A6-B04A1B01989D} = {4B6E4305-C95A-4274-94A6-B04A1B01989D} EndProjectSection EndProject -Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Shared", "Shared\Shared.shproj", "{C21C1A1B-5045-4BBE-818B-8574756D1F34}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Submodules", "Submodules", "{CAEA7F44-149D-486A-8791-77C3C44ED139}" EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRGIN_OpenXR", "VRGIN_OpenXR\VRGIN_OpenXR.csproj", "{4B6E4305-C95A-4274-94A6-B04A1B01989D}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KK_VR", "KK_VR\KK_VR.csproj", "{ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "KKS_VR", "KKS_VR\KKS_VR.csproj", "{1F36AB35-19D3-4553-A166-AF8DFCA39D50}" +EndProject +Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "Shared", "Shared\Shared.shproj", "{447CA481-4806-48D1-8E95-D545D8294620}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "VRGIN", "VRGIN\VRGIN\VRGIN.csproj", "{22788A2C-7695-4A6A-A9F0-115AB9A05FD4}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PatcherLoader", "PatcherLoader\PatcherLoader.csproj", "{7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}" +EndProject +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "native_patcher", "native_patcher\native_patcher.vcxproj", "{30177590-0ADF-4817-9D9B-804C7C4C749A}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU Debug|x64 = Debug|x64 + Debug|x86 = Debug|x86 Release|Any CPU = Release|Any CPU Release|x64 = Release|x64 + Release|x86 = Release|x86 EndGlobalSection GlobalSection(ProjectConfigurationPlatforms) = postSolution - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Debug|Any CPU.Build.0 = Debug|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Debug|x64.ActiveCfg = Debug|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Debug|x64.Build.0 = Debug|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Release|Any CPU.ActiveCfg = Release|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Release|Any CPU.Build.0 = Release|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Release|x64.ActiveCfg = Release|Any CPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67}.Release|x64.Build.0 = Release|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Debug|Any CPU.Build.0 = Debug|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Debug|x64.ActiveCfg = Debug|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Debug|x64.Build.0 = Debug|Any CPU + {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Debug|x86.ActiveCfg = Debug|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Release|Any CPU.ActiveCfg = Release|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Release|Any CPU.Build.0 = Release|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Release|x64.ActiveCfg = Release|Any CPU {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Release|x64.Build.0 = Release|Any CPU + {07ECD6A8-61D0-4409-A009-C138E76DFA1F}.Release|x86.ActiveCfg = Release|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Debug|Any CPU.Build.0 = Debug|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Debug|x64.ActiveCfg = Debug|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Debug|x64.Build.0 = Debug|Any CPU + {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Debug|x86.ActiveCfg = Debug|Any CPU + {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Debug|x86.Build.0 = Debug|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Release|Any CPU.ActiveCfg = Release|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Release|Any CPU.Build.0 = Release|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Release|x64.ActiveCfg = Release|Any CPU {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Release|x64.Build.0 = Release|Any CPU + {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Release|x86.ActiveCfg = Release|Any CPU + {4B6E4305-C95A-4274-94A6-B04A1B01989D}.Release|x86.Build.0 = Release|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Debug|Any CPU.Build.0 = Debug|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Debug|x64.ActiveCfg = Debug|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Debug|x64.Build.0 = Debug|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Debug|x86.ActiveCfg = Debug|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Debug|x86.Build.0 = Debug|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Release|Any CPU.ActiveCfg = Release|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Release|Any CPU.Build.0 = Release|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Release|x64.ActiveCfg = Release|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Release|x64.Build.0 = Release|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Release|x86.ActiveCfg = Release|Any CPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE}.Release|x86.Build.0 = Release|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Debug|Any CPU.Build.0 = Debug|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Debug|x64.ActiveCfg = Debug|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Debug|x64.Build.0 = Debug|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Debug|x86.ActiveCfg = Debug|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Debug|x86.Build.0 = Debug|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Release|Any CPU.ActiveCfg = Release|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Release|Any CPU.Build.0 = Release|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Release|x64.ActiveCfg = Release|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Release|x64.Build.0 = Release|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Release|x86.ActiveCfg = Release|Any CPU + {1F36AB35-19D3-4553-A166-AF8DFCA39D50}.Release|x86.Build.0 = Release|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Debug|x64.ActiveCfg = Debug|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Debug|x64.Build.0 = Debug|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Debug|x86.ActiveCfg = Debug|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Debug|x86.Build.0 = Debug|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Release|Any CPU.Build.0 = Release|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Release|x64.ActiveCfg = Release|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Release|x64.Build.0 = Release|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Release|x86.ActiveCfg = Release|Any CPU + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4}.Release|x86.Build.0 = Release|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Debug|x64.ActiveCfg = Debug|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Debug|x64.Build.0 = Debug|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Debug|x86.ActiveCfg = Debug|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Debug|x86.Build.0 = Debug|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Release|Any CPU.Build.0 = Release|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Release|x64.ActiveCfg = Release|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Release|x64.Build.0 = Release|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Release|x86.ActiveCfg = Release|Any CPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D}.Release|x86.Build.0 = Release|Any CPU + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Debug|Any CPU.ActiveCfg = Debug|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Debug|Any CPU.Build.0 = Debug|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Debug|x64.ActiveCfg = Debug|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Debug|x64.Build.0 = Debug|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Debug|x86.ActiveCfg = Debug|Win32 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Debug|x86.Build.0 = Debug|Win32 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Release|Any CPU.ActiveCfg = Release|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Release|Any CPU.Build.0 = Release|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Release|x64.ActiveCfg = Release|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Release|x64.Build.0 = Release|x64 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Release|x86.ActiveCfg = Release|Win32 + {30177590-0ADF-4817-9D9B-804C7C4C749A}.Release|x86.Build.0 = Release|Win32 EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE EndGlobalSection GlobalSection(NestedProjects) = preSolution {4B6E4305-C95A-4274-94A6-B04A1B01989D} = {CAEA7F44-149D-486A-8791-77C3C44ED139} + {22788A2C-7695-4A6A-A9F0-115AB9A05FD4} = {CAEA7F44-149D-486A-8791-77C3C44ED139} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {6A44D8C4-D4F0-4951-A1F2-B64FFDD9A290} EndGlobalSection GlobalSection(SharedMSBuildProjectFiles) = preSolution Shared\Shared.projitems*{07ecd6a8-61d0-4409-a009-c138e76dfa1f}*SharedItemsImports = 4 - Shared\Shared.projitems*{1f634b93-f4c6-45e9-894d-2f3556a74d67}*SharedItemsImports = 4 - Shared\Shared.projitems*{c21c1a1b-5045-4bbe-818b-8574756d1f34}*SharedItemsImports = 13 + Shared\Shared.projitems*{1f36ab35-19d3-4553-a166-af8dfca39d50}*SharedItemsImports = 4 + Shared\Shared.projitems*{447ca481-4806-48d1-8e95-d545d8294620}*SharedItemsImports = 13 + Shared\Shared.projitems*{abe9b34c-6644-4e93-a8a1-5cad5fa638ee}*SharedItemsImports = 4 EndGlobalSection EndGlobal diff --git a/MainGameVR/MainGameVR.csproj b/KKS_VR/KKS_VR.csproj similarity index 63% rename from MainGameVR/MainGameVR.csproj rename to KKS_VR/KKS_VR.csproj index ec28640..48957c2 100644 --- a/MainGameVR/MainGameVR.csproj +++ b/KKS_VR/KKS_VR.csproj @@ -1,46 +1,41 @@  - + Debug AnyCPU - {1F634B93-F4C6-45E9-894D-2F3556A74D67} + {1F36AB35-19D3-4553-A166-AF8DFCA39D50} Library Properties KKS_VR KKS_MainGameVR - v4.6.2 + v4.7.2 512 + true true - embedded + full false - bin\ - TRACE;DEBUG;KKS MainGame + ..\bin\KKS\ + TRACE;DEBUG;KKS prompt 4 + Preview true - false - 8 - embedded + pdbonly true - bin\Release\ - TRACE;KKS MainGame + ..\bin\KKS\ + TRACE;KKS prompt 4 + Preview true - false - 8 - true - - - false @@ -55,20 +50,20 @@ ..\packages\IllusionLibs.KoikatsuSunshine.Assembly-CSharp-firstpass.2021.9.17\lib\net46\Assembly-CSharp-firstpass.dll False - - ..\packages\IllusionLibs.BepInEx.5.4.20\lib\net35\BepInEx.dll + + ..\packages\IllusionLibs.BepInEx.5.4.22\lib\net35\BepInEx.dll False - - ..\packages\IllusionLibs.KoikatsuSunshine.Cinemachine.2019.4.9\lib\net46\Cinemachine.dll + + ..\packages\IllusionModdingAPI.KKSAPI.1.41.0\lib\net46\KKSAPI.dll False - - ..\packages\IllusionModdingAPI.KKSAPI.1.35.0\lib\net46\KKSAPI.dll - False + + False + ..\Libs\KK_FinalIK.dll - - ..\packages\ExtensibleSaveFormat.KoikatsuSunshine.16.8.1\lib\net46\KKS_ExtensibleSaveFormat.dll + + ..\Libs\Obi.dll False @@ -79,7 +74,9 @@ + + ..\packages\IllusionLibs.KoikatsuSunshine.UniRx.2021.9.17\lib\net46\UniRx.dll @@ -106,16 +103,13 @@ False - ..\VRGIN_OpenXR\Libs\Unity.Subsystem.Registration.dll - True + ..\Libs\Unity.Subsystem.Registration.dll - ..\VRGIN_OpenXR\Libs\Unity.XR.Management.dll - True + ..\Libs\Unity.XR.Management.dll - ..\VRGIN_OpenXR\Libs\Unity.XR.OpenVR.dll - True + ..\Libs\Unity.XR.OpenVR.dll ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.CoreModule.2019.4.9\lib\net46\UnityEngine.dll @@ -125,10 +119,6 @@ ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.AnimationModule.2019.4.9\lib\net46\UnityEngine.AnimationModule.dll False - - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.AssetBundleModule.2019.4.9\lib\net46\UnityEngine.AssetBundleModule.dll - False - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.AudioModule.2019.4.9\lib\net46\UnityEngine.AudioModule.dll False @@ -137,10 +127,6 @@ ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.CoreModule.2019.4.9\lib\net46\UnityEngine.CoreModule.dll False - - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.ImageConversionModule.2019.4.9\lib\net46\UnityEngine.ImageConversionModule.dll - False - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.IMGUIModule.2019.4.9\lib\net46\UnityEngine.IMGUIModule.dll False @@ -154,12 +140,7 @@ False - ..\VRGIN_OpenXR\Libs\UnityEngine.SpatialTracking.dll - True - - - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.TextRenderingModule.2019.4.9\lib\net46\UnityEngine.TextRenderingModule.dll - False + ..\Libs\UnityEngine.SpatialTracking.dll ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.UI.2019.4.9\lib\net46\UnityEngine.UI.dll @@ -169,94 +150,123 @@ ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.UIModule.2019.4.9\lib\net46\UnityEngine.UIModule.dll False - - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.VRModule.2019.4.9\lib\net46\UnityEngine.VRModule.dll - False + + ..\Libs\UnityEngine.UnityWebRequestAudioModule.dll - - ..\VRGIN_OpenXR\Libs\UnityEngine.XR.LegacyInputHelpers.dll - True + + ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.UnityWebRequestModule.2019.4.9\lib\net46\UnityEngine.UnityWebRequestModule.dll + False - - ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.XRModule.2019.4.9\lib\net46\UnityEngine.XRModule.dll + + ..\packages\IllusionLibs.KoikatsuSunshine.UnityEngine.UnityWebRequestWWWModule.2019.4.9\lib\net46\UnityEngine.UnityWebRequestWWWModule.dll False - - ..\VRGIN_OpenXR\Libs\Valve.Newtonsoft.Json.dll - True + + ..\Libs\UnityEngine.XR.LegacyInputHelpers.dll - - ..\VRGIN_OpenXR\bin\VRGIN_OpenXR.dll + + ..\Libs\Valve.Newtonsoft.Json.dll - ..\VRGIN_OpenXR\Libs\WindowsInput.dll - True + ..\Libs\WindowsInput.dll - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + LICENSE + PreserveNewest + + + README.md + PreserveNewest + - + + + IF EXIST "$(SolutionDir)PostBuild.bat" CALL "$(SolutionDir)PostBuild.bat" "$(TargetDir)\KKS_MainGameVR.dll" KKS + - + + + + + {4b6e4305-c95a-4274-94a6-b04a1b01989d} + VRGIN_OpenXR + + + + + Images\credits.txt + PreserveNewest + + + Images\cursor.png + PreserveNewest + + + Images\icon_call.png + PreserveNewest + + + Images\icon_call_xz.png + PreserveNewest + + + Images\icon_gripmove.png + PreserveNewest + + + Images\icon_hand.png + PreserveNewest + + + Images\icon_hand_1.png + PreserveNewest + + + Images\icon_hand_2.png + PreserveNewest + + + Images\icon_play.png + PreserveNewest + + + Images\icon_school.png + PreserveNewest + + + Images\icon_school_1.png + PreserveNewest + + + Images\icon_school_2.png + PreserveNewest + + + Images\icon_settings.png + PreserveNewest + + + Images\icon_warp.png + PreserveNewest + - - - + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + @@ -266,16 +276,11 @@ - - - - - - - - + + + @@ -286,21 +291,9 @@ - - - - - - - - - - - - - IF EXIST "$(SolutionDir)PostBuild.bat" CALL "$(SolutionDir)PostBuild.bat" "$(TargetDir)\KKS_MainGameVR.dll" KKS - - + + + \ No newline at end of file diff --git a/MainGameVR/VRPlugin.cs b/KKS_VR/VRPlugin.cs similarity index 75% rename from MainGameVR/VRPlugin.cs rename to KKS_VR/VRPlugin.cs index 4c3ce42..87d67d6 100644 --- a/MainGameVR/VRPlugin.cs +++ b/KKS_VR/VRPlugin.cs @@ -5,25 +5,27 @@ using BepInEx.Logging; using HarmonyLib; using KKAPI; -using KKAPI.Utilities; -using KKS_VR.Features; -using KKS_VR.Fixes; -using KKS_VR.Settings; +using KKAPI.MainGame; +using KK_VR.Features; +using KK_VR.Fixes; +using KK_VR.Interpreters; +using KK_VR.Settings; using Unity.XR.OpenVR; using UnityEngine; using Valve.VR; using VRGIN.Core; -using WindowsInput; +using VRGIN.Helpers; -namespace KKS_VR +namespace KK_VR { [BepInPlugin(GUID, Name, Version)] [BepInProcess(KoikatuAPI.GameProcessName)] [BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)] + [BepInDependency(KK.PluginFinalIK.GUID, KK.PluginFinalIK.Version)] [BepInIncompatibility("bero.crossfadervr")] public class VRPlugin : BaseUnityPlugin { - public const string GUID = "KKS_MainGameVR"; + public const string GUID = "kks.vr.game"; public const string Name = "KKS Main Game VR"; public const string Version = Constants.Version; @@ -33,23 +35,30 @@ private void Awake() { Logger = base.Logger; - var vrActivated = Environment.CommandLine.Contains("--vr"); - if (vrActivated) + //var vrDeactivated = Environment.CommandLine.Contains("--novr"); + + var enabled = Environment.CommandLine.Contains("--vr") || SteamVRDetector.IsRunning; + // The presence of settings even outside of vr mode for comfy editing. + var settings = SettingsManager.Create(Config); + if (enabled) { BepInExVrLogBackend.ApplyYourself(); - OpenVRHelperTempfixHook.Patch(); - var settings = SettingsManager.Create(Config); + + // This patch fixes crash(null refs?) on game exit and disables Haptic Feedback (atleast). + // No clue how to fix it otherwise, as most likely possible fix resides in unmanaged code. + //OpenVRHelperTempfixHook.Patch(); + StartCoroutine(LoadDevice(settings)); } - AnimationCrossFader.Initialize(Config, vrActivated); + CrossFader.Initialize(Config, enabled); } private IEnumerator LoadDevice(KoikatuSettings settings) { - yield return new WaitUntil(() => Manager.Scene.initialized && Manager.Scene.LoadSceneName == "Title"); - + yield return new WaitUntil(() => Manager.Scene.initialized); + //yield return new WaitUntil(() => Manager.Scene.initialized && Manager.Scene.LoadSceneName == "Title"); Logger.LogInfo("Loading OpenVR..."); var ovrsettings = OpenVRSettings.GetSettings(true); @@ -107,61 +116,46 @@ private IEnumerator LoadDevice(KoikatuSettings settings) Logger.LogInfo("Initializing the plugin..."); new Harmony(GUID).PatchAll(typeof(VRPlugin).Assembly); - TopmostToolIcons.Patch(); + //TopmostToolIcons.Patch(); VRManager.Create(new KoikatuContext(settings)); // VRGIN doesn't update the near clip plane until a first "main" camera is created, so we set it here. UpdateNearClipPlane(settings); + UpdateIPD(settings); settings.AddListener("NearClipPlane", (_, _1) => UpdateNearClipPlane(settings)); - - SetInputSimulator(settings); - settings.AddListener("UseLegacyInputSimulator", (_, _1) => SetInputSimulator(settings)); - + settings.AddListener("IPDScale", (_, _1) => UpdateIPD(settings)); VR.Manager.SetMode(); + VRFade.Create(); PrivacyScreen.Initialize(); GraphicRaycasterPatches.Initialize(); - + // It's been reported in #28 that the game window defocues when // the game is under heavy load. We disable window ghosting in // an attempt to counter this. NativeMethods.DisableProcessWindowsGhosting(); - - TweakShadowSettings(); - + DontDestroyOnLoad(VRCamera.Instance.gameObject); // Probably unnecessary, but just to be safe VR.Mode.MoveToPosition(Vector3.zero, Quaternion.Euler(Vector3.zero), true); Logger.LogInfo("Finished loading into VR mode!"); + + if (SettingsManager.EnableBoop.Value) + VRBoop.Initialize(); + GameAPI.RegisterExtraBehaviour(GUID); } private void UpdateNearClipPlane(KoikatuSettings settings) { VR.Camera.gameObject.GetComponent().nearClipPlane = settings.NearClipPlane; } - - private void SetInputSimulator(KoikatuSettings settings) - { - if (settings.UseLegacyInputSimulator) - { - VR.Manager.Input = new InputSimulator(); - } - else - { - VR.Manager.Input = new RobustInputSimulator(); - } - } - - private static void TweakShadowSettings() + private void UpdateIPD(KoikatuSettings settings) { - // Default shadows look too wobbly in VR. - QualitySettings.shadowProjection = ShadowProjection.StableFit; - QualitySettings.shadowCascades = 4; - QualitySettings.shadowCascade4Split = new Vector4(0.05f, 0.1f, 0.2f); + VRCamera.Instance.SteamCam.origin.localScale = Vector3.one * settings.IPDScale; } private static class NativeMethods diff --git a/KKS_VR/packages.config b/KKS_VR/packages.config new file mode 100644 index 0000000..af262af --- /dev/null +++ b/KKS_VR/packages.config @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/KK_VR/KK_VR.csproj b/KK_VR/KK_VR.csproj new file mode 100644 index 0000000..fe59c73 --- /dev/null +++ b/KK_VR/KK_VR.csproj @@ -0,0 +1,187 @@ + + + + + Debug + AnyCPU + {ABE9B34C-6644-4E93-A8A1-5CAD5FA638EE} + Library + Properties + KK_VR + KoikatuVR + v3.5 + 512 + true + + + + + true + full + false + ..\bin\KK\ + TRACE;DEBUG;KK + prompt + 4 + true + Preview + + + pdbonly + true + ..\bin\KK\ + TRACE;KK + prompt + 4 + true + Preview + + + IF EXIST "$(SolutionDir)PostBuild.bat" CALL "$(SolutionDir)PostBuild.bat" "$(TargetDir)\KKS_MainGameVR.dll" KKS + + + + ..\packages\IllusionLibs.BepInEx.Harmony.2.9.0\lib\net35\0Harmony.dll + False + + + ..\packages\IllusionLibs.Koikatu.Assembly-CSharp.2019.4.27.4\lib\net35\Assembly-CSharp.dll + False + + + ..\packages\IllusionLibs.Koikatu.Assembly-CSharp-firstpass.2019.4.27.4\lib\net35\Assembly-CSharp-firstpass.dll + False + + + ..\packages\IllusionLibs.BepInEx.5.4.22\lib\net35\BepInEx.dll + False + + + ..\packages\IllusionModdingAPI.KKAPI.1.41.0\lib\net35\KKAPI.dll + False + + + ..\Libs\KK_FinalIK.dll + + + + + + + + + ..\packages\IllusionLibs.Koikatu.UnityEngine.5.6.2.4\lib\net35\UnityEngine.dll + False + + + ..\packages\IllusionLibs.Koikatu.UnityEngine.UI.5.6.2.4\lib\net35\UnityEngine.UI.dll + False + + + ..\Libs\WindowsInput.dll + + + + + + + + LICENSE + PreserveNewest + + + README.md + PreserveNewest + + + + + + + + + + + + + + Images\credits.txt + PreserveNewest + + + Images\cursor.png + PreserveNewest + + + Images\icon_call.png + PreserveNewest + + + Images\icon_call_xz.png + PreserveNewest + + + Images\icon_gripmove.png + PreserveNewest + + + Images\icon_hand.png + PreserveNewest + + + Images\icon_hand_1.png + PreserveNewest + + + Images\icon_hand_2.png + PreserveNewest + + + Images\icon_play.png + PreserveNewest + + + Images\icon_school.png + PreserveNewest + + + Images\icon_school_1.png + PreserveNewest + + + Images\icon_school_2.png + PreserveNewest + + + Images\icon_settings.png + PreserveNewest + + + Images\icon_warp.png + PreserveNewest + + + + + {22788a2c-7695-4a6a-a9f0-115ab9a05fd4} + VRGIN + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + + + + + + + + + \ No newline at end of file diff --git a/KK_VR/VRPlugin.cs b/KK_VR/VRPlugin.cs new file mode 100644 index 0000000..10b7ba1 --- /dev/null +++ b/KK_VR/VRPlugin.cs @@ -0,0 +1,127 @@ +using System; +using System.Collections; +using System.Runtime.InteropServices; +using BepInEx; +using BepInEx.Logging; +using HarmonyLib; +using KKAPI; +using KKAPI.MainGame; +using KK_VR.Features; +using KK_VR.Fixes; +using KK_VR.Interpreters; +using KK_VR.Settings; +using UnityEngine; +using VRGIN.Core; +using VRGIN.Helpers; +using VRGIN.Controls.Handlers; + +namespace KK_VR +{ + [BepInPlugin(GUID, Name, Version)] + [BepInProcess(KoikatuAPI.GameProcessName)] + [BepInDependency(KoikatuAPI.GUID, KoikatuAPI.VersionConst)] + [BepInDependency(KK.PluginFinalIK.GUID, KK.PluginFinalIK.Version)] + [BepInIncompatibility("bero.crossfadervr")] + public class VRPlugin : BaseUnityPlugin + { + public const string GUID = "kk.vr.game"; + public const string Name = "MainGameVR"; + public const string Version = Constants.Version; + + internal static new ManualLogSource Logger; + + private void Awake() + { + Logger = base.Logger; + + var settings = SettingsManager.Create(Config); + + if (Environment.CommandLine.Contains("--vr") || SteamVRDetector.IsRunning) + { + BepInExVrLogBackend.ApplyYourself(); + StartCoroutine(LoadDevice(settings)); + } + CrossFader.Initialize(Config, enabled); + } + + private const string DeviceOpenVR = "OpenVR"; + private IEnumerator LoadDevice(KoikatuSettings settings) + { + //yield return new WaitUntil(() => Manager.Scene. initialized); + //yield return new WaitUntil(() => Manager.Scene.initialized && Manager.Scene.LoadSceneName == "Title"); + + if (UnityEngine.VR.VRSettings.loadedDeviceName != DeviceOpenVR) + { + UnityEngine.VR.VRSettings.LoadDeviceByName(DeviceOpenVR); + yield return null; + } + UnityEngine.VR.VRSettings.enabled = true; + while (UnityEngine.VR.VRSettings.loadedDeviceName != DeviceOpenVR) + { + yield return null; + } + while (true) + { + var rect = VRGIN.Native.WindowManager.GetClientRect(); + if (rect.Right - rect.Left > 0) + { + break; + } + //VRLog.Info("waiting for the window rect to be non-empty"); + yield return null; + } + + new Harmony(GUID).PatchAll(typeof(VRPlugin).Assembly); + VRManager.Create(new KoikatuContext(settings)); + + // VRGIN doesn't update the near clip plane until a first "main" camera is created, so we set it here. + UpdateNearClipPlane(settings); + UpdateIPD(settings); + settings.AddListener("NearClipPlane", (_, _1) => UpdateNearClipPlane(settings)); + settings.AddListener("IPDScale", (_, _1) => UpdateIPD(settings)); + + VR.Manager.SetMode(); + + VRFade.Create(); + PrivacyScreen.Initialize(); + GraphicRaycasterPatches.Initialize(); + + // It's been reported in #28 that the game window defocues when + // the game is under heavy load. We disable window ghosting in + // an attempt to counter this. + NativeMethods.DisableProcessWindowsGhosting(); + + //DontDestroyOnLoad(VRCamera.Instance.gameObject); + + // Probably unnecessary, but just to be safe + //VR.Mode.MoveToPosition(Vector3.zero, Quaternion.Euler(Vector3.zero), true); + + + if (SettingsManager.EnableBoop.Value) + { + VRBoop.Initialize(); + } + GameAPI.RegisterExtraBehaviour(GUID); + + // In KK they refuse to assume proper position on init. + + Logger.LogInfo("Finished loading into VR mode!"); + } + + private void UpdateNearClipPlane(KoikatuSettings settings) + { + VR.Camera.gameObject.GetComponent().nearClipPlane = settings.NearClipPlane; + } + private void UpdateIPD(KoikatuSettings settings) + { + VRCamera.Instance.SteamCam.origin.localScale = Vector3.one * settings.IPDScale; + } + + + private static class NativeMethods + { + [DllImport("user32.dll")] + public static extern void DisableProcessWindowsGhosting(); + } + } +} diff --git a/KK_VR/packages.config b/KK_VR/packages.config new file mode 100644 index 0000000..60af75e --- /dev/null +++ b/KK_VR/packages.config @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/Libs/KK_FinalIK.dll b/Libs/KK_FinalIK.dll new file mode 100644 index 0000000..05c7629 Binary files /dev/null and b/Libs/KK_FinalIK.dll differ diff --git a/Libs/Obi.dll b/Libs/Obi.dll new file mode 100644 index 0000000..4ee1efa Binary files /dev/null and b/Libs/Obi.dll differ diff --git a/Libs/Unity.Subsystem.Registration.dll b/Libs/Unity.Subsystem.Registration.dll new file mode 100644 index 0000000..92f6ff3 Binary files /dev/null and b/Libs/Unity.Subsystem.Registration.dll differ diff --git a/Libs/Unity.XR.Management.dll b/Libs/Unity.XR.Management.dll new file mode 100644 index 0000000..2e6e626 Binary files /dev/null and b/Libs/Unity.XR.Management.dll differ diff --git a/Libs/Unity.XR.OpenVR.dll b/Libs/Unity.XR.OpenVR.dll new file mode 100644 index 0000000..227eb0f Binary files /dev/null and b/Libs/Unity.XR.OpenVR.dll differ diff --git a/Libs/UnityEngine.SpatialTracking.dll b/Libs/UnityEngine.SpatialTracking.dll new file mode 100644 index 0000000..f546558 Binary files /dev/null and b/Libs/UnityEngine.SpatialTracking.dll differ diff --git a/Libs/UnityEngine.UnityWebRequestAudioModule.dll b/Libs/UnityEngine.UnityWebRequestAudioModule.dll new file mode 100644 index 0000000..f910ae4 Binary files /dev/null and b/Libs/UnityEngine.UnityWebRequestAudioModule.dll differ diff --git a/Libs/UnityEngine.XR.LegacyInputHelpers.dll b/Libs/UnityEngine.XR.LegacyInputHelpers.dll new file mode 100644 index 0000000..4cf809d Binary files /dev/null and b/Libs/UnityEngine.XR.LegacyInputHelpers.dll differ diff --git a/Libs/Valve.Newtonsoft.Json.dll b/Libs/Valve.Newtonsoft.Json.dll new file mode 100644 index 0000000..2674c18 Binary files /dev/null and b/Libs/Valve.Newtonsoft.Json.dll differ diff --git a/Libs/WindowsInput.dll b/Libs/WindowsInput.dll new file mode 100644 index 0000000..c54c03a Binary files /dev/null and b/Libs/WindowsInput.dll differ diff --git a/Libs/_Data/Plugins/x86_64/XRSDKOpenVR.dll b/Libs/_Data/Plugins/x86_64/XRSDKOpenVR.dll new file mode 100644 index 0000000..1bda158 Binary files /dev/null and b/Libs/_Data/Plugins/x86_64/XRSDKOpenVR.dll differ diff --git a/Libs/_Data/Plugins/x86_64/openvr_api.dll b/Libs/_Data/Plugins/x86_64/openvr_api.dll new file mode 100644 index 0000000..ef336cb Binary files /dev/null and b/Libs/_Data/Plugins/x86_64/openvr_api.dll differ diff --git a/Libs/_Data/StreamingAssets/SteamVR/OpenVRSettings.asset b/Libs/_Data/StreamingAssets/SteamVR/OpenVRSettings.asset new file mode 100644 index 0000000..a7f8f05 --- /dev/null +++ b/Libs/_Data/StreamingAssets/SteamVR/OpenVRSettings.asset @@ -0,0 +1,23 @@ +%YAML 1.1 +%TAG !u! tag:unity3d.com,2011: +--- !u!114 &11400000 +MonoBehaviour: + m_ObjectHideFlags: 0 + m_CorrespondingSourceObject: {fileID: 0} + m_PrefabInstance: {fileID: 0} + m_PrefabAsset: {fileID: 0} + m_GameObject: {fileID: 0} + m_Enabled: 1 + m_EditorHideFlags: 0 + m_Script: {fileID: 11500000, guid: 7a32bfe54d957ec4581fa4a630f1647a, type: 3} + m_Name: Open VR Settings + m_EditorClassIdentifier: + PromptToUpgradePackage: 1 + PromptToUpgradePreviewPackages: 1 + SkipPromptForVersion: + StereoRenderingMode: 0 + InitializationType: 1 + EditorAppKey: application.generated.unity.unity2019_4_openxr.exe + ActionManifestFileRelativeFilePath: StreamingAssets\SteamVR\actions.json + MirrorView: 2 + HasCopiedDefaults: 0 diff --git a/Libs/_Data/StreamingAssets/SteamVR/actions.json b/Libs/_Data/StreamingAssets/SteamVR/actions.json new file mode 100644 index 0000000..9779c7d --- /dev/null +++ b/Libs/_Data/StreamingAssets/SteamVR/actions.json @@ -0,0 +1,212 @@ +{ + "actions": [ + { + "name": "/actions/legacy_emulate/in/Grip_Press", + "type": "boolean" + }, + { + "name": "/actions/legacy_emulate/in/Grip_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Grip_2D", + "type": "vector2", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/ApplicationMenu_Press", + "type": "boolean" + }, + { + "name": "/actions/legacy_emulate/in/ApplicationMenu_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/A_Press", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/A_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis0_Press", + "type": "boolean" + }, + { + "name": "/actions/legacy_emulate/in/Axis0_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis0_2D", + "type": "vector2" + }, + { + "name": "/actions/legacy_emulate/in/Axis1_Press", + "type": "boolean" + }, + { + "name": "/actions/legacy_emulate/in/Axis1_Touch", + "type": "boolean" + }, + { + "name": "/actions/legacy_emulate/in/Axis1_2D", + "type": "vector2" + }, + { + "name": "/actions/legacy_emulate/in/Axis2_Press", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis2_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis2_2D", + "type": "vector2", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis3_Press", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis3_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis3_2D", + "type": "vector2", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis4_Press", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis4_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis4_2D", + "type": "vector2", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/System_Press", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/System_Touch", + "type": "boolean", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Axis1_1D", + "type": "vector1", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Grip_1D", + "type": "vector1", + "requirement": "optional" + }, + { + "name": "/actions/legacy_emulate/in/Pose", + "type": "pose" + }, + { + "name": "/actions/legacy_emulate/out/Huptic", + "type": "vibration" + } + ], + "action_sets": [ + { + "name": "/actions/legacy_emulate", + "usage": "single" + } + ], + "default_bindings": [ + { + "controller_type": "vive_controller", + "binding_url": "bindings_vive_controller.json" + }, + { + "controller_type": "oculus_touch", + "binding_url": "bindings_oculus_touch.json" + }, + { + "controller_type": "knuckles", + "binding_url": "bindings_knuckles.json" + }, + { + "controller_type": "holographic_controller", + "binding_url": "bindings_holographic_controller.json" + }, + { + "controller_type": "vive_cosmos_controller", + "binding_url": "bindings_vive_cosmos_controller.json" + }, + { + "controller_type": "logitech_stylus", + "binding_url": "bindings_logitech_stylus.json" + }, + { + "controller_type": "vive_cosmos", + "binding_url": "binding_vive_cosmos.json" + }, + { + "controller_type": "vive", + "binding_url": "binding_vive.json" + }, + { + "controller_type": "indexhmd", + "binding_url": "binding_index_hmd.json" + }, + { + "controller_type": "vive_pro", + "binding_url": "binding_vive_pro.json" + }, + { + "controller_type": "rift", + "binding_url": "binding_rift.json" + }, + { + "controller_type": "holographic_hmd", + "binding_url": "binding_holographic_hmd.json" + }, + { + "controller_type": "vive_tracker_camera", + "binding_url": "binding_vive_tracker_camera.json" + } + ], + "localization": [ + { + "language_tag": "en_US", + "/actions/legacy_emulate/in/Axis0_Press": "Touchpad (Press)", + "/actions/legacy_emulate/in/Axis0_Touch": "Touchpad (Touch)", + "/actions/legacy_emulate/in/Axis0_2D": "Touchpad (Location)", + "/actions/legacy_emulate/in/Axis1_Press": "Trigger (Press)", + "/actions/legacy_emulate/in/Axis1_Touch": "Trigger (Touch)", + "/actions/legacy_emulate/in/Axis1_2D": "Trigger (Analog)", + "/actions/legacy_emulate/in/Grip_Press": "Vive Grip (Press) | Touch Hand Trigger (Press)", + "/actions/legacy_emulate/in/Grip_Touch": "Vive Grip (Touch) | Touch Hand Trigger (Touch)", + "/actions/legacy_emulate/in/Grip_2D": "Vive Grip (Analog) | Touch Hand Trigger (Analog)", + "/actions/legacy_emulate/in/Axis1_1D": "Trigger (Analog)", + "/actions/legacy_emulate/in/Grip_1D": "Grip (Analog)", + "/actions/legacy_emulate/in/Pose": "Pose" + } + ] +} \ No newline at end of file diff --git a/Libs/_Data/StreamingAssets/SteamVR/bindings_knuckles.json b/Libs/_Data/StreamingAssets/SteamVR/bindings_knuckles.json new file mode 100644 index 0000000..734560b --- /dev/null +++ b/Libs/_Data/StreamingAssets/SteamVR/bindings_knuckles.json @@ -0,0 +1,187 @@ +{ + "action_manifest_version" : 0, + "alias_info" : {}, + "app_key" : "kkd.charastudio.exe", + "bindings" : { + "/actions/legacy_emulate" : { + "haptics" : [ + { + "output" : "/actions/legacy_emulate/out/huptic", + "path" : "/user/hand/left/output/haptic" + }, + { + "output" : "/actions/legacy_emulate/out/huptic", + "path" : "/user/hand/right/output/haptic" + } + ], + "poses" : [ + { + "output" : "/actions/legacy_emulate/in/pose", + "path" : "/user/hand/left/pose/raw" + }, + { + "output" : "/actions/legacy_emulate/in/pose", + "path" : "/user/hand/right/pose/raw" + } + ], + "sources" : [ + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/system_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/system_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/system" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/system_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/system_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/system" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis1_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/axis1_1d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis1_touch" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/trigger" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis1_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/axis1_1d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis1_touch" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/trigger" + }, + { + "inputs" : { + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "trackpad", + "path" : "/user/hand/left/input/trackpad" + }, + { + "inputs" : { + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "trackpad", + "path" : "/user/hand/right/input/trackpad" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + }, + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "joystick", + "path" : "/user/hand/left/input/thumbstick" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + }, + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "joystick", + "path" : "/user/hand/right/input/thumbstick" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/b" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/b" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/grip_press" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/a" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/grip_press" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/a" + } + ] + } + }, + "category" : "steamvr_input", + "controller_type" : "knuckles", + "description" : "", + "name" : "knuckles_default", + "options" : {}, + "simulated_actions" : [] +} diff --git a/Libs/_Data/StreamingAssets/SteamVR/bindings_oculus_touch.json b/Libs/_Data/StreamingAssets/SteamVR/bindings_oculus_touch.json new file mode 100644 index 0000000..2212245 --- /dev/null +++ b/Libs/_Data/StreamingAssets/SteamVR/bindings_oculus_touch.json @@ -0,0 +1,204 @@ +{ + "action_manifest_version" : 0, + "alias_info" : {}, + "app_key" : "application.generated.kks_charastudio.exe", + "bindings" : { + "/actions/legacy_emulate" : { + "poses" : [ + { + "output" : "/actions/legacy_emulate/in/pose", + "path" : "/user/hand/left/pose/raw" + }, + { + "output" : "/actions/legacy_emulate/in/pose", + "path" : "/user/hand/right/pose/raw" + } + ], + "sources" : [ + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/grip_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/grip_1d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/grip_touch" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/grip" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/grip_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/grip_1d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/grip_touch" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/grip" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/x" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/x" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + }, + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "joystick", + "path" : "/user/hand/left/input/joystick" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + }, + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "joystick", + "path" : "/user/hand/right/input/joystick" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/system_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/system_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/system" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/system_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/system_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/system" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis1_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/axis1_1d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis1_touch" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/trigger" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis1_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/axis1_1d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis1_touch" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/trigger" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/y" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/y" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_touch" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/a" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/b" + } + ] + } + }, + "category" : "steamvr_input", + "controller_type" : "oculus_touch", + "description" : "", + "name" : "oculus_touch", + "options" : {}, + "simulated_actions" : [] +} diff --git a/Libs/_Data/StreamingAssets/SteamVR/bindings_vive_controller.json b/Libs/_Data/StreamingAssets/SteamVR/bindings_vive_controller.json new file mode 100644 index 0000000..e6f9eab --- /dev/null +++ b/Libs/_Data/StreamingAssets/SteamVR/bindings_vive_controller.json @@ -0,0 +1,145 @@ +{ + "action_manifest_version" : 0, + "alias_info" : {}, + "app_key" : "kks.charastudio.exe", + "bindings" : { + "/actions/legacy_emulate" : { + "haptics" : [ + { + "output" : "/actions/legacy_emulate/out/huptic", + "path" : "/user/hand/left/output/haptic" + }, + { + "output" : "/actions/legacy_emulate/out/huptic", + "path" : "/user/hand/right/output/haptic" + } + ], + "poses" : [ + { + "output" : "/actions/legacy_emulate/in/pose", + "path" : "/user/hand/left/pose/raw" + }, + { + "output" : "/actions/legacy_emulate/in/pose", + "path" : "/user/hand/right/pose/raw" + } + ], + "sources" : [ + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/grip_press" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/grip" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/grip_press" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/grip" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/application_menu" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/applicationmenu_press" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/application_menu" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis1_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/axis1_1d" + } + }, + "mode" : "trigger", + "path" : "/user/hand/left/input/trigger" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis1_press" + }, + "pull" : { + "output" : "/actions/legacy_emulate/in/axis1_1d" + } + }, + "mode" : "trigger", + "path" : "/user/hand/right/input/trigger" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + }, + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "trackpad", + "path" : "/user/hand/left/input/trackpad" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/axis0_press" + }, + "position" : { + "output" : "/actions/legacy_emulate/in/axis0_2d" + }, + "touch" : { + "output" : "/actions/legacy_emulate/in/axis0_touch" + } + }, + "mode" : "trackpad", + "path" : "/user/hand/right/input/trackpad" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/system_press" + } + }, + "mode" : "button", + "path" : "/user/hand/left/input/system" + }, + { + "inputs" : { + "click" : { + "output" : "/actions/legacy_emulate/in/system_press" + } + }, + "mode" : "button", + "path" : "/user/hand/right/input/system" + } + ] + } + }, + "category" : "steamvr_input", + "controller_type" : "vive_controller", + "description" : "", + "name" : "vive defaults", + "options" : {}, + "simulated_actions" : [] +} diff --git a/Libs/_Data/UnitySubsystems/XRSDKOpenVR/UnitySubsystemsManifest.json b/Libs/_Data/UnitySubsystems/XRSDKOpenVR/UnitySubsystemsManifest.json new file mode 100644 index 0000000..22b4ae6 --- /dev/null +++ b/Libs/_Data/UnitySubsystems/XRSDKOpenVR/UnitySubsystemsManifest.json @@ -0,0 +1,18 @@ +{ + "name": "XRSDKOpenVR", + "version": "1.0.0-preview.1", + "libraryName": "XRSDKOpenVR", + + "displays": [ + { + "id": "OpenVR Display" + } + ], + "inputs": [ + { + "id": "OpenVR Input", + "disablesLegacyInput": true, + "disablesLegacyVr" : true + } + ] +} diff --git a/MainGameVR/Camera/CameraMoveHooks.cs b/MainGameVR/Camera/CameraMoveHooks.cs deleted file mode 100644 index e3813dd..0000000 --- a/MainGameVR/Camera/CameraMoveHooks.cs +++ /dev/null @@ -1,148 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using System.Reflection; -using HarmonyLib; -using UnityEngine; -using VRGIN.Core; - -// This file is a collection of hooks to move the VR camera at appropriate -// points of the game. - -namespace KKS_VR.Camera -{ - [HarmonyPatch(typeof(ADV.TextScenario))] - internal class TextScenarioPatches1 - { - [HarmonyPatch("ADVCameraSetting")] - [HarmonyPostfix] - private static void PostADVCameraSetting(ADV.TextScenario __instance) - { - if (Manager.Scene.initialized && Manager.Scene.NowSceneNames[0] == "Talk") - // Talk scenes are handled separately. - return; - - VRLog.Debug("PostADVCameraSetting"); - var backTrans = __instance.BackCamera?.transform; - if (backTrans == null) - // backTrans can be null in Roaming. We don't want to move the - // camera anyway in that case. - return; - VRCameraMover.Instance.MaybeMoveADV(__instance, backTrans.position, backTrans.rotation, false); - } - } - - [HarmonyPatch] - internal class RequestNextLinePatches - { - private static IEnumerable TargetMethods() - { - // In some versions of the base game, MainScenario._RequestNextLine - // duplicates the logic found in TextScenario._RequestNextLine. - // In other versions, the former simply calls the latter. - // We want to patch both methods or the latter alone depending on - // the version. - yield return AccessTools.Method(typeof(ADV.TextScenario), "_RequestNextLine"); - if (AccessTools.Field(typeof(ADV.MainScenario), "textHash") == null) yield return AccessTools.Method(typeof(ADV.MainScenario), "_RequestNextLine"); - } - - private static void Postfix(ADV.TextScenario __instance, ref IEnumerator __result) - { - if (Manager.Scene.initialized && Manager.Scene.NowSceneNames[0] == "Talk") - // Talk scenes are handled separately. - return; - - if (__instance.advScene == null) - // Outside ADV scene (probably roaming), ignore. - return; - - __result = new[] { __result, Postfix() }.GetEnumerator(); - - IEnumerator Postfix() - { - VRCameraMover.Instance.HandleTextScenarioProgress(__instance); - yield break; - } - } - } - - [HarmonyPatch(typeof(ADV.Program))] - internal class ProgramPatches1 - { - [HarmonyPatch(nameof(ADV.Program.SetNull))] - [HarmonyPostfix] - private static void PostSetNull(Transform transform) - { - VRLog.Debug("PostSetNull"); - VRCameraMover.Instance.MaybeMoveTo(transform.position, transform.rotation, false); - } - } - - [HarmonyPatch(typeof(HSceneProc))] - internal class HSceneProcPatches - { - [HarmonyPatch("ChangeAnimator")] - [HarmonyPostfix] - public static void PostChangeAnimator(HSceneProc __instance, bool _isForceCameraReset, List ___lstFemale) - { - if (_isForceCameraReset) UpdateVRCamera(__instance, ___lstFemale, null); - } - - [HarmonyPatch("ChangeCategory")] - [HarmonyPrefix] - public static void PreChangeCategory(List ___lstFemale, out float __state) - { - __state = ___lstFemale[0].objTop.transform.position.y; - } - - [HarmonyPatch("ChangeCategory")] - [HarmonyPostfix] - public static void PostChangeCategory(HSceneProc __instance, List ___lstFemale, float __state) - { - UpdateVRCamera(__instance, ___lstFemale, __state); - } - - - /// - /// Update the transform of the VR camera. - /// - /// - /// - private static void UpdateVRCamera(HSceneProc instance, List lstFemale, float? previousFemaleY) - { - var baseTransform = lstFemale[0].objTop.transform; - var camDat = new Traverse(instance.flags.ctrlCamera).Field("CamDat").Value; - var cameraRotation = baseTransform.rotation * Quaternion.Euler(camDat.Rot); - Vector3 dir; - switch (instance.flags.mode) - { - case HFlag.EMode.masturbation: - case HFlag.EMode.peeping: - case HFlag.EMode.lesbian: - // Use the default distance for 3rd-person scenes. - dir = camDat.Dir; - break; - default: - // Start closer otherwise. - dir = Vector3.back * 0.8f; - break; - } - - var cameraPosition = cameraRotation * dir + baseTransform.TransformPoint(camDat.Pos); - if (previousFemaleY is float prevY) - { - // Keep the relative Y coordinate from the female. - var cameraHeight = VR.Camera.transform.position.y + baseTransform.position.y - prevY; - var destination = new Vector3(cameraPosition.x, cameraHeight, cameraPosition.z); - VRCameraMover.Instance.MaybeMoveTo(destination, cameraRotation, false); - } - else - { - // We are starting from scratch. - // TODO: the height calculation below assumes standing mode. - var cameraHeight = lstFemale[0].transform.position.y + VR.Camera.transform.localPosition.y; - var destination = new Vector3(cameraPosition.x, cameraHeight, cameraPosition.z); - VRCameraMover.Instance.MoveTo(destination, cameraRotation, false); - } - } - } -} diff --git a/MainGameVR/Camera/POV.cs b/MainGameVR/Camera/POV.cs deleted file mode 100644 index d212d62..0000000 --- a/MainGameVR/Camera/POV.cs +++ /dev/null @@ -1,293 +0,0 @@ -using System.Linq; -using UnityEngine; -using VRGIN.Controls; -using VRGIN.Core; -using VRGIN.Helpers; -using KKS_VR.Settings; - - -public enum POV_MODE -{ - // Press Key (default: Y) to change POV Mode, configurable in BepInEX Plugin Settings (F1) - EYE = 0, // Mode1: Tracking Eye Position & Rotation - HEAD = 1, // Mode2: Only Tracking Eye Position (Default) - TELEPORT = 2, // Mode3: Teleport(Jump) to next character when trigger controller - TOTAL = 3 // Total num of Enum -} - -namespace KKS_VR.Camera -{ - internal class POV : ProtectedBehaviour - { - private Controller _leftController, _rightController; - - private ChaControl _currentChara; - - private POV_MODE _povMode; - - private bool _active; - - private KoikatuSettings _settings; - - private Quaternion _tmp_head_y; // for POV_MODE.EYE - - public void Initialize(Controller left, Controller right) - { - _leftController = left; - _rightController = right; - } - - public bool IsActive() - { - return _active; - } - - protected override void OnAwake() - { - base.OnAwake(); - // HScene Init - _currentChara = null; - - _povMode = POV_MODE.HEAD; - - _active = false; - - _settings = VR.Context.Settings as KoikatuSettings; - - _tmp_head_y = Quaternion.identity; - } - - private void SetCameraToCharEye(ChaControl target) - { - var position = GetEyes(target).position; - var rotation = GetEyes(target).rotation; - - // Set Camera to Character Eye - VRManager.Instance.Mode.MoveToPosition(position, rotation, false); - } - - // from VRGIN ControlMode.cs - private void MoveToPositionEx(Vector3 targetPosition, Quaternion rotation = default(Quaternion)) - { - // Camera rotates with character eye - VR.Camera.SteamCam.origin.rotation = rotation * _tmp_head_y; - - float targetY = targetPosition.y; - float myY = VR.Camera.SteamCam.head.position.y; - targetPosition = new Vector3(targetPosition.x, targetY, targetPosition.z); - var myPosition = new Vector3(VR.Camera.SteamCam.head.position.x, myY, VR.Camera.SteamCam.head.position.z); - VR.Camera.SteamCam.origin.position += (targetPosition - myPosition); - } - - // from VRGIN ControlMode.cs - private Quaternion MakeUpright(Quaternion rotation) - { - return Quaternion.Euler(0, rotation.eulerAngles.y, 0); - } - private void resetRotationXZ() - { - VR.Camera.SteamCam.origin.rotation = MakeUpright(VR.Camera.SteamCam.origin.rotation); - _tmp_head_y = Quaternion.identity; - } - - private Transform GetHead(ChaControl human) - { - return human.objHead.GetComponentsInParent().First((Transform t) => t.name.StartsWith("c") && t.name.ToLower().Contains("j_head")); - } - private Transform GetEyes(ChaControl human) - { - Transform transform = human.objHeadBone.transform.Descendants().FirstOrDefault((Transform t) => t.name.StartsWith("c") && t.name.ToLower().EndsWith("j_faceup_tz")); - if (!transform) - { - VRLog.Info("Creating eyes, {0}", human.name); - transform = new GameObject("cf_j_faceup_tz").transform; - transform.SetParent(GetHead(human), false); - transform.transform.localPosition = new Vector3(0f, 0.07f, 0.05f); - } - else - { - VRLog.Debug("Found eyes, {0}", human.name); - } - return transform; - } - private int getCurrChaIdx(ChaControl[] targets) - { - if (_currentChara) - { - for (int i = 0; i < targets.Length; i++) - { - if (ChaControl.ReferenceEquals(targets[i], _currentChara) && - ((_currentChara.sex == (int)POVConfig.targetGender.Value) || (POVConfig.targetGender.Value == POVConfig.Gender.All))) - { - return i; - } - } - } - // cannot find last pov character (initialize / deleted) - return targets.Length - 1; - } - - // set _currentChara - private void nextChar(ChaControl[] targets, int currentTargetIndex, bool keep_char = false) - { - var cnt = 0; - - while (cnt < targets.Length) - { - if ((keep_char && cnt == 0) == false) - { - currentTargetIndex = (currentTargetIndex + 1) % targets.Length; - } - _currentChara = targets[currentTargetIndex]; - - if (_currentChara) - { - if ((POVConfig.targetGender.Value == POVConfig.Gender.All) || (_currentChara.sex == (int)POVConfig.targetGender.Value)) // 1 is female - { - if (_povMode == POV_MODE.EYE || _povMode == POV_MODE.HEAD || _povMode == POV_MODE.TELEPORT) - { - SetCameraToCharEye(_currentChara); - - if (_povMode == POV_MODE.EYE) - { - // _tmp_head_y : the initial y when jump to character - _tmp_head_y = MakeUpright(VR.Camera.SteamCam.origin.rotation) * - Quaternion.Inverse(MakeUpright(VR.Camera.SteamCam.head.rotation)); - } - } - return; - } - } - cnt++; - } - // target character not found - _currentChara = null; - } - - public void SwitchToNextChar() - { - // Change to next target character - if (!_active) return; - - ChaControl[] targets = GameObject.FindObjectsOfType(); - var currentTargetIndex = getCurrChaIdx(targets); - nextChar(targets, currentTargetIndex, false); - } - - private void initPOVTarget() - { - // Inherit last target character if still alive, otherwise find a new target character - ChaControl[] targets = GameObject.FindObjectsOfType(); - var currentTargetIndex = getCurrChaIdx(targets); - nextChar(targets, currentTargetIndex, true); - } - - private void SetPOV() - { - if (!_active) return; - - if (_currentChara) - { - switch (_povMode) - { - case POV_MODE.EYE: - MoveToPositionEx(GetEyes(_currentChara).position, GetEyes(_currentChara).rotation); - break; - - case POV_MODE.HEAD: - VRManager.Instance.Mode.MoveToPosition(GetEyes(_currentChara).position, false); - break; - - case POV_MODE.TELEPORT: - // Already Teleport in SwitchToNextChar - break; - - default: - break; - } - } - } - - protected override void OnUpdate() - { - if (_settings.EnablePOV == false) return; - // Press Key (default: Y) to change POV Mode, configurable in BepInEX Plugin Settings (F1) - if (POVConfig.switchPOVModeKey.Value == POVConfig.POVKeyList.VR_TRIGGER || POVConfig.switchPOVModeKey.Value == POVConfig.POVKeyList.VR_BUTTON2) // Use VR button - { - var button_id = (POVConfig.switchPOVModeKey.Value == POVConfig.POVKeyList.VR_TRIGGER) ? Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger : Valve.VR.EVRButtonId.k_EButton_A; - if (_leftController.Input.GetPressDown(button_id) || _rightController.Input.GetPressDown(button_id)) - { - if (_povMode == POV_MODE.EYE) { resetRotationXZ(); } - _povMode = (POV_MODE)(((int)(_povMode + 1)) % (int)(POV_MODE.TOTAL)); - } - } - else if (Input.GetKeyDown((KeyCode)POVConfig.switchPOVModeKey.Value)) // Use Keyboard Key - { - if (_povMode == POV_MODE.EYE) { resetRotationXZ(); } - _povMode = (POV_MODE)(((int)(_povMode + 1)) % (int)(POV_MODE.TOTAL)); - } - - if (!_active) // Activate POV under Hand Tool if user press VR Trigger button / Key - { - if (POVConfig.POVKey.Value == POVConfig.POVKeyList.VR_TRIGGER || POVConfig.POVKey.Value == POVConfig.POVKeyList.VR_BUTTON2) // Use VR button - { - var button_id = (POVConfig.POVKey.Value == POVConfig.POVKeyList.VR_TRIGGER) ? Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger : Valve.VR.EVRButtonId.k_EButton_A; - // When left hand controller is Hand Tool and visible, Press VR button to set _active - if (VR.Mode.Left.ToolIndex == 2 && VR.Mode.Left.ActiveTool.isActiveAndEnabled && _leftController.Input.GetPressDown(button_id)) - { - _active = true; - initPOVTarget(); - return; - } - // When right hand controller is Hand Tool and visible, Press VR button to set _active - else if (VR.Mode.Right.ToolIndex == 2 && VR.Mode.Right.ActiveTool.isActiveAndEnabled && _rightController.Input.GetPressDown(button_id)) - { - _active = true; - initPOVTarget(); - return; - } - } - else // Use Keyboard Key - { - if ((VR.Mode.Left.ToolIndex == 2 || VR.Mode.Right.ToolIndex == 2) && Input.GetKeyDown((KeyCode)POVConfig.POVKey.Value)) - { - _active = true; - initPOVTarget(); - return; - } - } - } - // When there is no Hand Tool, deactive - else if (_active && VR.Mode.Left.ToolIndex != 2 && VR.Mode.Right.ToolIndex != 2) - { - _active = false; - if (_povMode == POV_MODE.EYE) { resetRotationXZ(); } - return; - } - - if (_active) - { - if (POVConfig.POVKey.Value == POVConfig.POVKeyList.VR_TRIGGER || POVConfig.POVKey.Value == POVConfig.POVKeyList.VR_BUTTON2) // Use VR button - { - var button_id = (POVConfig.POVKey.Value == POVConfig.POVKeyList.VR_TRIGGER) ? Valve.VR.EVRButtonId.k_EButton_SteamVR_Trigger : Valve.VR.EVRButtonId.k_EButton_A; - // Press VR button to change target POV character - if (VR.Mode.Left.ToolIndex == 2 && VR.Mode.Left.ActiveTool.isActiveAndEnabled && _leftController.Input.GetPressDown(button_id)) - { - SwitchToNextChar(); - } - else if (VR.Mode.Right.ToolIndex == 2 && VR.Mode.Right.ActiveTool.isActiveAndEnabled && _rightController.Input.GetPressDown(button_id)) - { - SwitchToNextChar(); - } - } - else // Use Keyboard Key - { - if (Input.GetKeyDown((KeyCode)POVConfig.POVKey.Value)) { SwitchToNextChar(); } - } - } - - - SetPOV(); - } - } -} diff --git a/MainGameVR/Caress/AibuColliderTracker.cs b/MainGameVR/Caress/AibuColliderTracker.cs deleted file mode 100644 index 36f0bc0..0000000 --- a/MainGameVR/Caress/AibuColliderTracker.cs +++ /dev/null @@ -1,195 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using KKS_VR.Interpreters; -using UnityEngine; -using VRGIN.Core; -using Object = UnityEngine.Object; - -namespace KKS_VR.Caress -{ - /// - /// An object that tracks the set of aibu colliders that we are - /// currently intersecting. - /// Each instance only concerns one H scene. A fresh instance should be - /// created for each H scene. - /// - internal class AibuColliderTracker - { - private static readonly IDictionary aibuTagTable - = new Dictionary - { - { "mouth", new[] { HandCtrl.AibuColliderKind.mouth, HandCtrl.AibuColliderKind.reac_head } }, - { "muneL", new[] { HandCtrl.AibuColliderKind.muneL, HandCtrl.AibuColliderKind.reac_bodyup } }, - { "muneR", new[] { HandCtrl.AibuColliderKind.muneR, HandCtrl.AibuColliderKind.reac_bodyup } }, - { "kokan", new[] { HandCtrl.AibuColliderKind.kokan, HandCtrl.AibuColliderKind.reac_bodydown } }, - { "anal", new[] { HandCtrl.AibuColliderKind.anal, HandCtrl.AibuColliderKind.reac_bodydown } }, - { "siriL", new[] { HandCtrl.AibuColliderKind.siriL, HandCtrl.AibuColliderKind.reac_bodydown } }, - { "siriR", new[] { HandCtrl.AibuColliderKind.siriR, HandCtrl.AibuColliderKind.reac_bodydown } }, - { "Reaction/head", new[] { HandCtrl.AibuColliderKind.reac_head } }, - { "Reaction/bodyup", new[] { HandCtrl.AibuColliderKind.reac_bodyup } }, - { "Reaction/bodydown", new[] { HandCtrl.AibuColliderKind.reac_bodydown } }, - { "Reaction/armL", new[] { HandCtrl.AibuColliderKind.reac_armL } }, - { "Reaction/armR", new[] { HandCtrl.AibuColliderKind.reac_armR } }, - { "Reaction/legL", new[] { HandCtrl.AibuColliderKind.reac_legL } }, - { "Reaction/legR", new[] { HandCtrl.AibuColliderKind.reac_legR } } - }; - - private readonly IDictionary> _currentlyIntersecting - = new Dictionary>(); - - private readonly IDictionary> _knownColliders - = new Dictionary>(); - - private readonly Transform _referencePoint; - - public AibuColliderTracker(HSceneProc proc, Transform referencePoint) - { - Proc = proc; - _referencePoint = referencePoint; - - // Populate _knwonColliders - var lstFemale = new Traverse(proc).Field("lstFemale").GetValue>(); - for (var i = 0; i < lstFemale.Count; i++) - { - var colliders = lstFemale[i].GetComponentsInChildren(true); - foreach (var collider in colliders) - { - var aibuHit = Extensions.StripPrefix("H/Aibu/Hit/", collider.tag); - if (aibuHit == null) continue; - - if (aibuTagTable.TryGetValue(aibuHit, out var kinds)) _knownColliders[collider] = ValueTuple.Create(i, kinds); - } - } - } - - public HSceneProc Proc { get; } - - /// - /// If the given collider is an aibu collider, start tracking it. - /// - /// - /// Whether the current collider kind may have changed. - public bool AddIfRelevant(Collider other) - { - if (!_knownColliders.TryGetValue(other, out var idx_kinds)) return false; - - var idx = idx_kinds.Item1; - var kinds = idx_kinds.Item2; - var hand = idx == 0 ? Proc.hand : Proc.hand1; - var kind = kinds.Where(k => AibuKindAllowed(hand, k)).FirstOrDefault(); - if (kind != HandCtrl.AibuColliderKind.none) - { - _currentlyIntersecting[other] = ValueTuple.Create(idx, kind); - return true; - } - - return false; - } - - /// - /// If the given collider is currently being tracked, stop tracking it. - /// - /// - /// Whether the current collider kind may have changed. - public bool RemoveIfRelevant(Collider other) - { - return _currentlyIntersecting.Remove(other); - } - - /// - /// Get The kind of the collider we should be interacting with. - /// Also outputs the female who is the owner of the collider. - /// - public HandCtrl.AibuColliderKind GetCurrentColliderKind(out int femaleIndex) - { - if (_currentlyIntersecting.Count == 0) - { - femaleIndex = 0; - return HandCtrl.AibuColliderKind.none; - } - - // Only consider the colliders with the highest priority. - var priority = _currentlyIntersecting.Values.Select(idx_kind => ColliderPriority(idx_kind.Item2)).Max(); - var refPosition = _referencePoint.position; - var best = _currentlyIntersecting.Where(kv => ColliderPriority(kv.Value.Item2) == priority) - .OrderBy(kv => (kv.Key.transform.position - refPosition).sqrMagnitude) - .Select(kv => kv.Value).FirstOrDefault(); - femaleIndex = best.Item1; - return best.Item2; - } - - /// - /// Return whether there is any collider we should be interacting with. - /// This is equivalent to this.GetCurrentColliderKind() != none, but is more efficient. - /// - /// - public bool IsIntersecting() - { - return _currentlyIntersecting.Count > 0; - } - - private static int ColliderPriority(HandCtrl.AibuColliderKind kind) - { - if (kind == HandCtrl.AibuColliderKind.none) - return 0; - if (HandCtrl.AibuColliderKind.mouth <= kind && kind < HandCtrl.AibuColliderKind.reac_head) - return 2; - return 1; - } - - /// - /// Check whether a particular body interaction is allowed. - /// - /// - /// - /// - private bool AibuKindAllowed(HandCtrl hand, HandCtrl.AibuColliderKind kind) - { - // It's ok to use lstHeroine[0] here because this variable is not used - // when there are more than one heroine. - var heroine = hand.flags.lstHeroine[0]; - var dicNowReaction = new Traverse(hand).Field("dicNowReaction").GetValue>(); - HandCtrl.ReactionInfo rinfo; - switch (kind) - { - case HandCtrl.AibuColliderKind.none: - return true; - case HandCtrl.AibuColliderKind.mouth: - return hand.nowMES.isTouchAreas[0] && - (hand.flags.mode == HFlag.EMode.aibu || heroine.isGirlfriend || heroine.isKiss || heroine.denial.kiss); - case HandCtrl.AibuColliderKind.muneL: - return hand.nowMES.isTouchAreas[1]; - case HandCtrl.AibuColliderKind.muneR: - return hand.nowMES.isTouchAreas[2]; - case HandCtrl.AibuColliderKind.kokan: - return hand.nowMES.isTouchAreas[3]; - case HandCtrl.AibuColliderKind.anal: - return hand.nowMES.isTouchAreas[4] && - (hand.flags.mode == HFlag.EMode.aibu || heroine.hAreaExps[3] > 0f || heroine.denial.anal); - case HandCtrl.AibuColliderKind.siriL: - return hand.nowMES.isTouchAreas[5]; - case HandCtrl.AibuColliderKind.siriR: - return hand.nowMES.isTouchAreas[6]; - case HandCtrl.AibuColliderKind.reac_head: - return dicNowReaction.TryGetValue(0, out rinfo) && rinfo.isPlay; - case HandCtrl.AibuColliderKind.reac_bodyup: - return dicNowReaction.TryGetValue(1, out rinfo) && rinfo.isPlay; - case HandCtrl.AibuColliderKind.reac_bodydown: - return dicNowReaction.TryGetValue(2, out rinfo) && rinfo.isPlay; - case HandCtrl.AibuColliderKind.reac_armL: - return dicNowReaction.TryGetValue(3, out rinfo) && rinfo.isPlay; - case HandCtrl.AibuColliderKind.reac_armR: - return dicNowReaction.TryGetValue(4, out rinfo) && rinfo.isPlay; - case HandCtrl.AibuColliderKind.reac_legL: - return dicNowReaction.TryGetValue(5, out rinfo) && rinfo.isPlay; - case HandCtrl.AibuColliderKind.reac_legR: - return dicNowReaction.TryGetValue(6, out rinfo) && rinfo.isPlay; - } - - VRLog.Warn("AibuKindAllowed: undefined kind: {0}", kind); - return false; - } - } -} diff --git a/MainGameVR/Caress/CaressController.cs b/MainGameVR/Caress/CaressController.cs deleted file mode 100644 index 41ea8bf..0000000 --- a/MainGameVR/Caress/CaressController.cs +++ /dev/null @@ -1,204 +0,0 @@ -using System; -using System.Collections.Generic; -using HarmonyLib; -using KKS_VR.Settings; -using Manager; -using UnityEngine; -using Valve.VR; -using VRGIN.Controls; -using VRGIN.Core; -using VRGIN.Helpers; - -namespace KKS_VR.Caress -{ - /// - /// An extra component to be attached to each controller, providing the caress - /// functionality in H scenes. - /// This component is designed to exist only for the duration of an H scene. - /// - internal class CaressController : ProtectedBehaviour - { - // Basic plan: - // - // * Keep track of the potential caress points - // near this controller. _aibuTracker is responsible for this. - // * While there is at least one such point, lock the controller - // to steal any trigger events. - // * When the trigger is pulled, initiate caress. - // * Delay releasing of the lock until the trigger is released. - - KoikatuSettings _settings; - Controller _controller; - AibuColliderTracker _aibuTracker; - Undresser _undresser; - Controller.Lock _lock; // may be null but never invalid - bool _triggerPressed; // Whether the trigger is currently pressed. false if _lock is null. - ValueTuple? _undressing; - - public Controller getController() - { - return _controller; - } - - protected override void OnAwake() - { - base.OnAwake(); - _settings = VR.Context.Settings as KoikatuSettings; - _controller = GetComponent(); - var proc = FindObjectOfType(); - if (proc == null) - { - VRLog.Warn("HSceneProc not found"); - return; - } - _aibuTracker = new AibuColliderTracker(proc, referencePoint: transform); - _undresser = new Undresser(proc); - } - - private void OnDestroy() - { - if (_lock != null) ReleaseLock(); - } - - protected override void OnUpdate() - { - if (_lock != null) - { - HandleTrigger(); - HandleToolChange(); - HandleUndress(); - } - UpdateLock(); - } - - protected void OnTriggerEnter(Collider other) - { - try - { - if (Scene.NowSceneNames[0] == "HPointMove") return; - var wasIntersecting = _aibuTracker.IsIntersecting(); - if (_aibuTracker.AddIfRelevant(other)) - { - if (!wasIntersecting && _aibuTracker.IsIntersecting()) - { - _controller.StartRumble(new RumbleImpulse(1000)); - if (_settings.AutomaticTouching) - { - var colliderKind = _aibuTracker.GetCurrentColliderKind(out int femaleIndex); - if (HandCtrl.AibuColliderKind.reac_head <= colliderKind && - !CaressUtil.IsSpeaking(_aibuTracker.Proc, femaleIndex)) - { - CaressUtil.SetSelectKindTouch(_aibuTracker.Proc, femaleIndex, colliderKind); - StartCoroutine(CaressUtil.ClickCo()); - } - } - } - } - - _undresser.Enter(other); - UpdateLock(); - } - catch (Exception e) - { - VRLog.Error(e); - } - } - - protected void OnTriggerExit(Collider other) - { - try - { - _aibuTracker.RemoveIfRelevant(other); - _undresser.Exit(other); - UpdateLock(); - } - catch (Exception e) - { - VRLog.Error(e); - } - } - - private void UpdateLock() - { - bool shouldHaveLock = (_aibuTracker.IsIntersecting() || _undressing != null) && - Manager.Scene.NowSceneNames[0] != "HPointMove"; - if (shouldHaveLock && _lock == null) - _controller.TryAcquireFocus(out _lock); - else if (!shouldHaveLock && _lock != null && !_triggerPressed) ReleaseLock(); - } - - private void HandleTrigger() - { - var device = _controller.Input; //SteamVR_Controller.Input((int)_controller.Tracking.index); - if (!_triggerPressed && device.GetPressDown(EVRButtonId.k_EButton_SteamVR_Trigger)) - { - UpdateSelectKindTouch(); - HandCtrlHooks.InjectMouseButtonDown(0); - _controller.StartRumble(new RumbleImpulse(1000)); - _triggerPressed = true; - } - else if (_triggerPressed && device.GetPressUp(EVRButtonId.k_EButton_SteamVR_Trigger)) - { - HandCtrlHooks.InjectMouseButtonUp(0); - _triggerPressed = false; - UpdateLock(); - } - } - - private void HandleToolChange() - { - var device = _controller.Input; //SteamVR_Controller.Input((int)_controller.Tracking.index); - if (device.GetPressUp(EVRButtonId.k_EButton_ApplicationMenu)) - { - UpdateSelectKindTouch(); - HandCtrlHooks.InjectMouseScroll(1f); - } - - } - - private void HandleUndress() - { - var device = _controller.Input; - var proc = _aibuTracker.Proc; - if (_undressing == null && device.GetPressDown(EVRButtonId.k_EButton_SteamVR_Touchpad)) - { - var females = new Traverse(proc).Field>("lstFemale").Value; - var toUndress = _undresser.ComputeUndressTarget(females, out int femaleIndex); - if (toUndress is ChaFileDefine.ClothesKind kind) - { - _undressing = ValueTuple.Create(females[femaleIndex], kind, transform.position); - } - } - if (_undressing is ValueTuple undressing - && device.GetPressUp(EVRButtonId.k_EButton_SteamVR_Touchpad)) - { - if (0.3f * 0.3f < (transform.position - undressing.Item3).sqrMagnitude) - { - undressing.Item1.SetClothesState((int)undressing.Item2, 3); - } - else - { - undressing.Item1.SetClothesStateNext((int)undressing.Item2); - } - _undressing = null; - } - } - - private void ReleaseLock() - { - CaressUtil.SetSelectKindTouch(_aibuTracker.Proc, 0, HandCtrl.AibuColliderKind.none); - if (_triggerPressed) - HandCtrlHooks.InjectMouseButtonUp(0); - _triggerPressed = false; - _undressing = null; - _lock.Release(); - _lock = null; - } - - private void UpdateSelectKindTouch() - { - var colliderKind = _aibuTracker.GetCurrentColliderKind(out var femaleIndex); - CaressUtil.SetSelectKindTouch(_aibuTracker.Proc, femaleIndex, colliderKind); - } - } -} diff --git a/MainGameVR/Caress/CaressUtil.cs b/MainGameVR/Caress/CaressUtil.cs deleted file mode 100644 index 94de988..0000000 --- a/MainGameVR/Caress/CaressUtil.cs +++ /dev/null @@ -1,51 +0,0 @@ -using System.Collections; -using System.Collections.Generic; -using HarmonyLib; - -namespace KKS_VR.Caress -{ - public class CaressUtil - { - /// - /// Modify the internal state of the hand controls so that subsequent mouse button - /// presses are interpreted to point to the specified (female, point) pair. - /// - public static void SetSelectKindTouch(HSceneProc proc, int femaleIndex, HandCtrl.AibuColliderKind colliderKind) - { - var hands = GetHands(proc); - for (var i = 0; i < hands.Count; i++) - { - var kind = i == femaleIndex ? colliderKind : HandCtrl.AibuColliderKind.none; - new Traverse(hands[i]).Field("selectKindTouch").SetValue(kind); - } - } - - public static List GetHands(HSceneProc proc) - { - var ret = new List(); - for (var i = 0; i < proc.flags.lstHeroine.Count; i++) ret.Add(i == 0 ? proc.hand : proc.hand1); - return ret; - } - - /// - /// Send a synthetic click event to the hand controls. - /// - /// - public static IEnumerator ClickCo() - { - var consumed = false; - HandCtrlHooks.InjectMouseButtonDown(0, () => consumed = true); - while (!consumed) yield return null; - HandCtrlHooks.InjectMouseButtonUp(0); - } - - /// - /// Is the specified female speaking? Moans are ignored. - /// - public static bool IsSpeaking(HSceneProc proc, int femaleIndex) - { - return proc.voice.nowVoices[femaleIndex].state == HVoiceCtrl.VoiceKind.voice && - Manager.Voice.IsPlay(proc.flags.transVoiceMouth[femaleIndex], true); - } - } -} diff --git a/MainGameVR/Caress/LongDistanceKissMachine.cs b/MainGameVR/Caress/LongDistanceKissMachine.cs deleted file mode 100644 index 191da13..0000000 --- a/MainGameVR/Caress/LongDistanceKissMachine.cs +++ /dev/null @@ -1,61 +0,0 @@ -using UnityEngine; - -namespace KKS_VR.Caress -{ - /// - /// A state machine for starting and finishing a kiss in the caress mode. - /// - /// This requires a special treatment because the female leans forward after - /// a kiss is started. This means a kiss should start with some distance. - /// - public class LongDistanceKissMachine - { - private float? _startTime; // null iff not kissing - private bool _prevEntryConditionMet = true; - - public bool Step( - float currentTime, - Vector3 femaleFromHmd, - Vector3 hmdFromFemale, - float femaleFaceAngleY) - { - var entryConditionMet = EntryScore(femaleFromHmd, hmdFromFemale, femaleFaceAngleY) < 0; - bool result; - if (_startTime is float startTime) - { - var duration = currentTime - startTime; - var maxDistance = Mathf.Max(0.10f, 0.55f - 0.4f * duration); - result = hmdFromFemale.sqrMagnitude < maxDistance * maxDistance; - } - else - { - result = entryConditionMet && !_prevEntryConditionMet; - } - - _prevEntryConditionMet = entryConditionMet; - - if (result) - _startTime = _startTime ?? currentTime; - else if (_startTime != null) _startTime = null; - return result; - } - - public void Reset() - { - _startTime = null; - _prevEntryConditionMet = true; - } - - private static float EntryScore(Vector3 femaleFromHmd, Vector3 hmdFromFemale, float femaleFaceAngle) - { - var total = OneSidedScore(femaleFromHmd) + OneSidedScore(hmdFromFemale) + 0.1f * Mathf.Abs(femaleFaceAngle); - return total - 2.0f; - } - - private static float OneSidedScore(Vector3 rel) - { - rel.z = 0.4f * (rel.z - 0.1f); - return 500f * rel.sqrMagnitude; - } - } -} diff --git a/MainGameVR/Caress/Undresser.cs b/MainGameVR/Caress/Undresser.cs deleted file mode 100644 index 74f7cc6..0000000 --- a/MainGameVR/Caress/Undresser.cs +++ /dev/null @@ -1,181 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using UnityEngine; -using VRGIN.Core; -using HarmonyLib; - -using static ChaFileDefine; - -namespace KKS_VR.Caress -{ - /// - /// An object responsible for determining which clothing item to remove, - /// based on the controller position. - /// - class Undresser - { - private readonly Dictionary> _knownColliders = - new Dictionary>(); - private readonly HashSet _currentlyIntersecting = new HashSet(); - - public Undresser(HSceneProc proc) - { - // Populate _knownColliders. - var lstFemale = new Traverse(proc).Field("lstFemale").GetValue>(); - for (int i = 0; i < lstFemale.Count; i++) - { - var colliders = lstFemale[i].GetComponentsInChildren(includeInactive: true); - foreach (var collider in colliders) - { - if (ColliderBodyPart(collider) is InteractionBodyPart part) - { - _knownColliders.Add(collider, ValueTuple.Create(i, part)); - } - } - } - } - - public void Enter(Collider collider) - { - if (_knownColliders.ContainsKey(collider)) - { - _currentlyIntersecting.Add(collider); - } - } - - public void Exit(Collider collider) - { - _currentlyIntersecting.Remove(collider); - } - - public ClothesKind? ComputeUndressTarget(List females, out int femaleIndex) - { - femaleIndex = 0; - if (_currentlyIntersecting.Count == 0) - { - return null; - } - var part = (InteractionBodyPart)9999; - foreach (var collider in _currentlyIntersecting) - { - var item = _knownColliders[collider]; - if (item.Item2 < part) - { - femaleIndex = item.Item1; - part = item.Item2; - break; - } - } - - var female = females[femaleIndex]; - var targets = _itemsForPart[(int)part]; - if (part == InteractionBodyPart.Crotch && IsWearingSkirt(female)) - { - // Special case: if the character is wearing a skirt, allow - // directly removing the underwear. - targets = _skirtCrotchTargets; - } - foreach (var target in targets) - { - if (!female.IsClothesStateKind((int)target.kind)) - { - continue; - } - var state = female.fileStatus.clothesState[(int)target.kind]; - if (target.min_state <= state && state <= target.max_state) - { - VRLog.Info($"toUndress: {target.kind}"); - return target.kind; - } - } - return null; - } - - private static bool IsWearingSkirt(ChaControl female) - { - var objBot = female.objClothes[1]; - return objBot != null && objBot.GetComponent() != null; - } - - /// - /// A body part the user can interact with. A more specific part gets - /// a lower number. - /// - private enum InteractionBodyPart - { - Crotch, - Groin, - Breast, - LegL, - LegR, - Forearm, - UpperArm, - Thigh, - Torso, - } - - private static readonly UndressTarget[][] _itemsForPart = new[] - { - new[] { Target(ClothesKind.bot), Target(ClothesKind.panst), Target(ClothesKind.shorts) }, - new[] { Target(ClothesKind.bot, 0), Target(ClothesKind.panst), Target(ClothesKind.shorts) }, - new[] { Target(ClothesKind.top, 0), Target(ClothesKind.bra) }, - new[] { Target(ClothesKind.socks), Target(ClothesKind.shorts, 2, 2) }, - new[] { Target(ClothesKind.socks) }, - new UndressTarget[] { }, - new[] { Target(ClothesKind.top) }, - new[] { Target(ClothesKind.panst), Target(ClothesKind.bot), Target(ClothesKind.socks) }, - new[] { Target(ClothesKind.top) }, - }; - - private static readonly UndressTarget[] _skirtCrotchTargets = - new[] { Target(ClothesKind.panst), Target(ClothesKind.shorts), Target(ClothesKind.bot) }; - - private static UndressTarget Target(ClothesKind kind, int max_state = 2, int min_state = 0) - { - return new UndressTarget(kind, max_state, min_state); - } - - private static InteractionBodyPart? ColliderBodyPart(Collider collider) - { - var name = collider.name; - if (name == "aibu_hit_kokan" || - name == "aibu_hit_ana") - return InteractionBodyPart.Crotch; - if (name.StartsWith("aibu_hit_siri") || - name.StartsWith("aibu_reaction_waist")) - return InteractionBodyPart.Groin; - if (name.StartsWith("cf_hit_bust")) - return InteractionBodyPart.Breast; - if (name == "aibu_reaction_legL") - return InteractionBodyPart.LegL; - if (name == "aibu_reaction_legR") - return InteractionBodyPart.LegR; - if (name.StartsWith("cf_hit_wrist")) - return InteractionBodyPart.Forearm; - if (name.StartsWith("cf_hit_arm")) - return InteractionBodyPart.UpperArm; - if (name.StartsWith("aibu_reaction_thigh")) - return InteractionBodyPart.Thigh; - if (name == "cf_hit_spine01" || - name == "cf_hit_spine03" || - name == "cf_hit_berry") - return InteractionBodyPart.Torso; - return null; - } - - private struct UndressTarget - { - public UndressTarget(ClothesKind k, int m, int mm) - { - kind = k; - max_state = m; - min_state = mm; - } - public ClothesKind kind; - public int max_state; - public int min_state; - } - } -} diff --git a/MainGameVR/Caress/VRMouth.cs b/MainGameVR/Caress/VRMouth.cs deleted file mode 100644 index 4214b07..0000000 --- a/MainGameVR/Caress/VRMouth.cs +++ /dev/null @@ -1,343 +0,0 @@ -using System; -using System.Collections; -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using KKS_VR.Settings; -using UnityEngine; -using VRGIN.Core; - -namespace KKS_VR.Caress -{ - /// - /// A component to be attached to the VR camera during an H scene. - /// It allows the user to kiss in H scenes by moving their head. - /// - public class VRMouth : ProtectedBehaviour - { - private KoikatuSettings _settings; - private AibuColliderTracker _aibuTracker; - private Transform _firstFemale; - private Transform _firstFemaleMouth; - private VRMouthColliderObject _small, _large; - private bool _inCaressMode = true; - private readonly LongDistanceKissMachine _machine = new LongDistanceKissMachine(); - - /// - /// Indicates whether the currently running KissCo should end. - /// null if KissCo is not running. - /// - private bool? _kissCoShouldEnd; - - /// - /// Indicates whether the currently running KissCo should end. - /// null if LickCo is not running. - /// - private bool? _lickCoShouldEnd; - - protected override void OnAwake() - { - base.OnAwake(); - _settings = VR.Context.Settings as KoikatuSettings; - - // Create 2 colliders, a small one for entering and a large one for exiting. - _small = VRMouthColliderObject - .Create("VRMouthSmall", new Vector3(0, 0, 0), new Vector3(0.05f, 0.05f, 0.07f)); - _small.TriggerEnter += HandleTriggerEnter; - _large = VRMouthColliderObject - .Create("VRMouthLarge", new Vector3(0, 0, 0.05f), new Vector3(0.1f, 0.1f, 0.15f)); - _large.TriggerExit += HandleTriggerExit; - - var hProc = FindObjectOfType(); - - if (hProc == null) - { - VRLog.Error("hProc is null"); - return; - } - - _aibuTracker = new AibuColliderTracker(hProc, transform); - var lstFemale = new Traverse(hProc).Field("lstFemale").GetValue>(); - _firstFemale = lstFemale[0].objTop.transform; - _firstFemaleMouth = lstFemale[0].objHeadBone.transform.Find( - "cf_J_N_FaceRoot/cf_J_FaceRoot/cf_J_FaceBase/cf_J_FaceLow_tz/a_n_mouth"); - } - - private void OnDestroy() - { - Destroy(_small.gameObject); - Destroy(_large.gameObject); - } - - protected override void OnUpdate() - { - HandleScoreBasedKissing(); - } - - private void HandleScoreBasedKissing() - { - var inCaressMode = _aibuTracker.Proc.flags.mode == HFlag.EMode.aibu; - if (inCaressMode) - { - var decision = _settings.AutomaticKissing && - _machine.Step( - Time.time, - _small.transform.InverseTransformPoint(_firstFemaleMouth.position), - _firstFemaleMouth.InverseTransformPoint(_small.transform.position), - Mathf.DeltaAngle(_firstFemale.eulerAngles.y, _firstFemaleMouth.transform.eulerAngles.y)); - if (decision) - StartKiss(); - else - FinishKiss(); - } - - if (_inCaressMode & !inCaressMode) - { - FinishKiss(); - _machine.Reset(); - } - - _inCaressMode = inCaressMode; - } - - private void HandleTriggerEnter(Collider other) - { - if (_aibuTracker.AddIfRelevant(other)) - { - var colliderKind = _aibuTracker.GetCurrentColliderKind(out var femaleIndex); - UpdateKissLick(colliderKind); - - if (_kissCoShouldEnd == null && - HandCtrl.AibuColliderKind.reac_head <= colliderKind && - _settings.AutomaticTouchingByHmd && - !CaressUtil.IsSpeaking(_aibuTracker.Proc, femaleIndex)) - StartCoroutine(TriggerReactionCo(femaleIndex, colliderKind)); - } - } - - private IEnumerator TriggerReactionCo(int femaleIndex, HandCtrl.AibuColliderKind colliderKind) - { - var kindFields = CaressUtil.GetHands(_aibuTracker.Proc) - .Select(h => new Traverse(h).Field("selectKindTouch")) - .ToList(); - var oldKinds = kindFields.Select(f => f.Value).ToList(); - CaressUtil.SetSelectKindTouch(_aibuTracker.Proc, femaleIndex, colliderKind); - yield return CaressUtil.ClickCo(); - for (var i = 0; i < kindFields.Count(); i++) kindFields[i].Value = oldKinds[i]; - } - - private void HandleTriggerExit(Collider other) - { - if (_aibuTracker.RemoveIfRelevant(other)) - { - var colliderKind = _aibuTracker.GetCurrentColliderKind(out var _); - UpdateKissLick(colliderKind); - } - } - - private void UpdateKissLick(HandCtrl.AibuColliderKind colliderKind) - { - if (!_inCaressMode && colliderKind == HandCtrl.AibuColliderKind.mouth && _settings.AutomaticKissing) - { - StartKiss(); - } - else if (_settings.AutomaticLicking && IsLickingOk(colliderKind, out var layerNum)) - { - StartLicking(colliderKind, layerNum); - } - else - { - if (!_inCaressMode) FinishKiss(); - FinishLicking(); - } - } - - private bool IsLickingOk(HandCtrl.AibuColliderKind colliderKind, out int layerNum) - { - layerNum = 0; - if (colliderKind <= HandCtrl.AibuColliderKind.mouth || - HandCtrl.AibuColliderKind.reac_head <= colliderKind) - return false; - - var bodyPartId = colliderKind - HandCtrl.AibuColliderKind.muneL; - var hand = _aibuTracker.Proc.hand; - var handTrav = new Traverse(hand); - var layerInfos = handTrav.Field[]>("dicAreaLayerInfos").Value[bodyPartId]; - var clothState = handTrav.Method("GetClothState", new[] { typeof(HandCtrl.AibuColliderKind) }).GetValue(colliderKind); - var layerKv = layerInfos.Where(kv => kv.Value.useArray == 2).FirstOrDefault(); - var layerInfo = layerKv.Value; - layerNum = layerKv.Key; - if (layerInfo == null) - { - VRLog.Warn("Licking not ok: no layer found"); - return false; - } - - if (layerInfo.plays[clothState] == -1) return false; - var heroine = _aibuTracker.Proc.flags.lstHeroine[0]; - if (_aibuTracker.Proc.flags.mode != HFlag.EMode.aibu && - colliderKind == HandCtrl.AibuColliderKind.anal && - !heroine.denial.anal && - heroine.hAreaExps[3] == 0f) - return false; - - return true; - } - - /// - /// Attempt to start a kiss. - /// - private void StartKiss() - { - if (_kissCoShouldEnd != null || new Traverse(_aibuTracker.Proc.hand).Field("isKiss").Value) - // Already kissing. - return; - - _kissCoShouldEnd = false; - StartCoroutine(KissCo()); - } - - private IEnumerator KissCo() - { - StopAllLicking(); - - var hand = _aibuTracker.Proc.hand; - var handTrav = new Traverse(hand); - var selectKindTouchTrav = handTrav.Field("selectKindTouch"); - - var prevKindTouch = selectKindTouchTrav.Value; - selectKindTouchTrav.Value = HandCtrl.AibuColliderKind.mouth; - var messageDelivered = false; - HandCtrlHooks.InjectMouseButtonDown(0, () => messageDelivered = true); - while (!messageDelivered) yield return null; - yield return new WaitForEndOfFrame(); - - // Try to restore the old value of selectKindTouch. - if (selectKindTouchTrav.Value == HandCtrl.AibuColliderKind.mouth) selectKindTouchTrav.Value = prevKindTouch; - - var isKissTrav = handTrav.Field("isKiss"); - while (_kissCoShouldEnd == false && isKissTrav.Value) yield return null; - - HandCtrlHooks.InjectMouseButtonUp(0); - _kissCoShouldEnd = null; - } - - private void FinishKiss() - { - if (_kissCoShouldEnd == false) _kissCoShouldEnd = true; - } - - private void StartLicking(HandCtrl.AibuColliderKind colliderKind, int layerNum) - { - if (_lickCoShouldEnd != null) - // Already licking. - return; - - var hand = _aibuTracker.Proc.hand; - var handTrav = new Traverse(hand); - var bodyPartId = colliderKind - HandCtrl.AibuColliderKind.muneL; - var usedItem = handTrav.Field("useAreaItems").Value[bodyPartId]; - - // If another item is being used on the target body part, detach it. - if (usedItem != null && usedItem.idUse != 2) hand.DetachItemByUseItem(usedItem.idUse); - - StartCoroutine(LickCo(colliderKind, layerNum)); - } - - private IEnumerator LickCo(HandCtrl.AibuColliderKind colliderKind, int layerNum) - { - _lickCoShouldEnd = false; - - var hand = _aibuTracker.Proc.hand; - var handTrav = new Traverse(hand); - var areaItem = handTrav.Field("areaItem").Value; - var bodyPartId = colliderKind - HandCtrl.AibuColliderKind.muneL; - var selectKindTouchTrav = handTrav.Field("selectKindTouch"); - - - var oldLayerNum = areaItem[bodyPartId]; - areaItem[bodyPartId] = layerNum; - - while (_lickCoShouldEnd == false && areaItem[bodyPartId] == layerNum) - { - var oldKindTouch = selectKindTouchTrav.Value; - selectKindTouchTrav.Value = colliderKind; - yield return CaressUtil.ClickCo(); - selectKindTouchTrav.Value = oldKindTouch; - yield return new WaitForSeconds(0.2f); - } - - hand.DetachItemByUseItem(2); - if (areaItem[bodyPartId] == layerNum) areaItem[bodyPartId] = oldLayerNum; - - _lickCoShouldEnd = null; - } - - private void FinishLicking() - { - if (_lickCoShouldEnd == false) _lickCoShouldEnd = true; - } - - private void StopAllLicking() - { - FinishLicking(); - _aibuTracker.Proc.hand.DetachItemByUseItem(2); - } - - private class VRMouthColliderObject : ProtectedBehaviour - { - public delegate void TriggerHandler(Collider other); - - public event TriggerHandler TriggerEnter; - public event TriggerHandler TriggerExit; - - public static VRMouthColliderObject Create(string name, Vector3 center, Vector3 size) - { - var gameObj = new GameObject(name); - gameObj.transform.localPosition = new Vector3(0, -0.07f, 0.02f); - gameObj.transform.SetParent(VR.Camera.transform, false); - - var collider = gameObj.AddComponent(); - collider.size = size; - collider.center = center; - collider.isTrigger = true; - - gameObj.AddComponent().isKinematic = true; - return gameObj.AddComponent(); - } - - protected void OnTriggerEnter(Collider other) - { - try - { - TriggerEnter?.Invoke(other); - } - catch (Exception e) - { - VRLog.Error(e); - } - } - - protected void OnTriggerExit(Collider other) - { - try - { - TriggerExit?.Invoke(other); - } - catch (Exception e) - { - VRLog.Error(e); - } - } - } - } -} - -// Notes on some fields in HandCtrl: -// -// areaItem[p] : The layer num of the item to be used on the body part p -// useAreaItems[p] : The item currently being used on the body part p -// useItems[s] : The item currently in the slot s -// dicAreaLayerInfos[p][l].useArray : The slot to be used when the layer l is used on the body part p. -// 3 means either slot 0 (left hand) or 1 (right hand). -// item.idUse: The slot to be used for the item. diff --git a/MainGameVR/Controls/ButtonsSubtool.cs b/MainGameVR/Controls/ButtonsSubtool.cs deleted file mode 100644 index 887f059..0000000 --- a/MainGameVR/Controls/ButtonsSubtool.cs +++ /dev/null @@ -1,200 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using KKS_VR.Camera; -using KKS_VR.Interpreters; -using KKS_VR.Settings; -using UnityEngine; -using VRGIN.Core; -using WindowsInput.Native; - -namespace KKS_VR.Controls -{ - /// - /// A subtool that handles an arbitrary number of simple actions that only - /// requires a single button. - /// - internal class ButtonsSubtool - { - private readonly KoikatuInterpreter _Interpreter; - - /// - /// The set of keys for which we've sent a down message but not a - /// corresponding up message. - /// - private readonly HashSet _SentUnmatchedDown - = new HashSet(); - - private readonly KoikatuSettings _Settings; - private int _ScrollRepeatAmount; - - private float _ScrollRepeatTime; - - public ButtonsSubtool(KoikatuInterpreter interpreter, KoikatuSettings settings) - { - _Interpreter = interpreter; - _Settings = settings; - } - - /// - /// A method to be called in Update(). - /// - public void Update() - { - if (_SentUnmatchedDown.Contains(AssignableFunction.PL2CAM)) IfActionScene(interpreter => interpreter.MovePlayerToCamera()); - if (_ScrollRepeatAmount != 0 && _ScrollRepeatTime < Time.unscaledTime) - { - _ScrollRepeatTime += 0.1f; - VR.Input.Mouse.VerticalScroll(_ScrollRepeatAmount); - } - } - - /// - /// Whether it's desirable to lock the controller. - /// - /// - public bool WantLock() - { - return _SentUnmatchedDown.Count > 0; - } - - /// - /// A method to be called when this subtool is destroyed. - /// - public void Destroy() - { - // Make a copy because the loop below will modify the HashSet. - var todo = _SentUnmatchedDown.ToList(); - foreach (var key in todo) ButtonUp(key); - } - - /// - /// Process a ButtonDown message. - /// - public void ButtonDown(AssignableFunction fun) - { - switch (fun) - { - case AssignableFunction.NONE: - break; - case AssignableFunction.WALK: - IfActionScene(interpreter => interpreter.StartWalking()); - break; - case AssignableFunction.DASH: - IfActionScene(interpreter => interpreter.StartWalking(true)); - break; - case AssignableFunction.PL2CAM: - break; - case AssignableFunction.LBUTTON: - VR.Input.Mouse.LeftButtonDown(); - break; - case AssignableFunction.RBUTTON: - VR.Input.Mouse.RightButtonDown(); - break; - case AssignableFunction.MBUTTON: - VR.Input.Mouse.MiddleButtonDown(); - break; - case AssignableFunction.LROTATION: - Rotate(-_Settings.RotationAngle); - break; - case AssignableFunction.RROTATION: - Rotate(_Settings.RotationAngle); - break; - case AssignableFunction.SCROLLUP: - StartScroll(1); - break; - case AssignableFunction.SCROLLDOWN: - StartScroll(-1); - break; - case AssignableFunction.CROUCH: - IfActionScene(interpreter => interpreter.Crouch()); - break; - case AssignableFunction.NEXT: - throw new NotSupportedException(); - case AssignableFunction.KEYBOARD_PAGE_DOWN: - VR.Input.Keyboard.KeyDown(VirtualKeyCode.NEXT); - break; - default: - VR.Input.Keyboard.KeyDown((VirtualKeyCode)Enum.Parse(typeof(VirtualKeyCode), fun.ToString())); - break; - } - - _SentUnmatchedDown.Add(fun); - } - - /// - /// Process a ButtonUp message. - /// - public void ButtonUp(AssignableFunction fun) - { - switch (fun) - { - case AssignableFunction.NONE: - break; - case AssignableFunction.WALK: - IfActionScene(interpreter => interpreter.StopWalking()); - break; - case AssignableFunction.DASH: - IfActionScene(interpreter => interpreter.StopWalking()); - break; - case AssignableFunction.PL2CAM: - break; - case AssignableFunction.LBUTTON: - VR.Input.Mouse.LeftButtonUp(); - break; - case AssignableFunction.RBUTTON: - VR.Input.Mouse.RightButtonUp(); - break; - case AssignableFunction.MBUTTON: - VR.Input.Mouse.MiddleButtonUp(); - break; - case AssignableFunction.LROTATION: - case AssignableFunction.RROTATION: - break; - case AssignableFunction.SCROLLUP: - case AssignableFunction.SCROLLDOWN: - _ScrollRepeatAmount = 0; - break; - case AssignableFunction.CROUCH: - IfActionScene(interpreter => interpreter.StandUp()); - break; - case AssignableFunction.NEXT: - throw new NotSupportedException(); - case AssignableFunction.KEYBOARD_PAGE_DOWN: - VR.Input.Keyboard.KeyUp(VirtualKeyCode.NEXT); - break; - default: - VR.Input.Keyboard.KeyUp((VirtualKeyCode)Enum.Parse(typeof(VirtualKeyCode), fun.ToString())); - break; - } - - _SentUnmatchedDown.Remove(fun); - } - - private void StartScroll(int amount) - { - VR.Input.Mouse.VerticalScroll(amount); - _ScrollRepeatTime = Time.unscaledTime + 0.5f; - _ScrollRepeatAmount = amount; - } - - /// - /// Rotate the camera. If we are in Roaming, rotate the protagonist as well. - /// - private void Rotate(float degrees) - { - VRLog.Debug("Rotating {0} degrees", degrees); - var actInterpreter = _Interpreter.SceneInterpreter as ActionSceneInterpreter; - if (actInterpreter != null) actInterpreter.MoveCameraToPlayer(true); - var camera = VR.Camera.transform; - var newRotation = Quaternion.AngleAxis(degrees, Vector3.up) * camera.rotation; - VRCameraMover.Instance.MoveTo(camera.position, newRotation, false); - if (actInterpreter != null) actInterpreter.MovePlayerToCamera(); - } - - private void IfActionScene(Action a) - { - if (_Interpreter.SceneInterpreter is ActionSceneInterpreter actInterpreter) a(actInterpreter); - } - } -} diff --git a/MainGameVR/Controls/GameplayTool.cs b/MainGameVR/Controls/GameplayTool.cs deleted file mode 100644 index 9de9614..0000000 --- a/MainGameVR/Controls/GameplayTool.cs +++ /dev/null @@ -1,335 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using System.Linq; -using KKS_VR.Interpreters; -using KKS_VR.Settings; -using UnityEngine; -using UnityEngine.EventSystems; -using Valve.VR; -using VRGIN.Controls; -using VRGIN.Controls.Tools; -using VRGIN.Core; -using VRGIN.Helpers; - -namespace KKS_VR.Controls -{ - public class GameplayTool : Tool - { - private readonly Texture2D _hand1Texture = UnityHelper.LoadImage("icon_hand_1.png"); - private readonly Texture2D _hand2Texture = UnityHelper.LoadImage("icon_hand_2.png"); - private readonly Texture2D _handTexture = UnityHelper.LoadImage("icon_hand.png"); - private readonly Texture2D _image = new Texture2D(512, 512); - private readonly Texture2D _school1Texture = UnityHelper.LoadImage("icon_school_1.png"); - private readonly Texture2D _school2Texture = UnityHelper.LoadImage("icon_school_2.png"); - private readonly Texture2D _schoolTexture = UnityHelper.LoadImage("icon_school.png"); - - private MoveDirection? _touchDirection; - private MoveDirection? _lastPressDirection; - - // When eneabled, exactly one of the below is non-null. - private ButtonsSubtool _buttonsSubtool; - private bool _InHScene; - private KoikatuInterpreter _Interpreter; - private KeySet _KeySet; - private int _KeySetIndex; - //private Controller.Lock _lock = VRGIN.Controls.Controller.Lock.Invalid; - private KoikatuSettings _Settings; - - public override Texture2D Image => _image; - private GrabAction _grab; - - private void ChangeKeySet() - { - var keySets = KeySets(); - - _KeySetIndex = (_KeySetIndex + 1) % keySets.Count; - _KeySet = keySets[_KeySetIndex]; - UpdateIcon(); - } - - private List KeySets() - { - return _InHScene ? _Settings.HKeySets : _Settings.KeySets; - } - - private void ResetKeys() - { - SetScene(_InHScene); - } - - private void SetScene(bool inHScene) - { - if (_buttonsSubtool != null) - { - _buttonsSubtool.Destroy(); - _buttonsSubtool = new ButtonsSubtool(_Interpreter, _Settings); - } - - _InHScene = inHScene; - var keySets = KeySets(); - _KeySetIndex = 0; - _KeySet = keySets[0]; - UpdateIcon(); - } - - private void UpdateIcon() - { - var icon = - _InHScene - ? _Settings.HKeySets.Count > 1 - ? _KeySetIndex == 0 - ? _hand1Texture - : _hand2Texture - : _handTexture - : _Settings.KeySets.Count > 1 - ? _KeySetIndex == 0 - ? _school1Texture - : _school2Texture - : _schoolTexture; - Graphics.CopyTexture(icon, _image); - } - - protected override void OnStart() - { - base.OnStart(); - - _Settings = VR.Context.Settings as KoikatuSettings; - SetScene(false); - _Settings.AddListener("KeySets", (_, _1) => ResetKeys()); - _Settings.AddListener("HKeySets", (_, _1) => ResetKeys()); - } - - protected override void OnDestroy() - { - // nothing to do. - } - - protected override void OnDisable() - { - _buttonsSubtool?.Destroy(); - _buttonsSubtool = null; - _grab?.Destroy(); - _grab = null; - //if (_lock.IsValid) _lock.Release(); - _touchDirection = null; - _lastPressDirection = null; - base.OnDisable(); - } - - protected override void OnEnable() - { - base.OnEnable(); - _Interpreter = VR.Interpreter as KoikatuInterpreter; - _buttonsSubtool = new ButtonsSubtool(_Interpreter, _Settings); - } - - protected override void OnUpdate() - { - base.OnUpdate(); - - //UpdateLock(); - - var inHScene = _Interpreter.CurrentScene == KoikatuInterpreter.SceneType.HScene; - if (inHScene != _InHScene) SetScene(inHScene); - - if (_grab != null) - { - if (!_grab.HandleGrabbing()) - { - _grab.Destroy(); - _grab = null; - _buttonsSubtool = new ButtonsSubtool(_Interpreter, _Settings); - } - } - - if (_buttonsSubtool != null) HandleButtons(); - } - - //private void UpdateLock() - //{ - // var wantLock = /*_grab != null ||*/ _buttonsSubtool?.WantLock() == true; - // if (wantLock && !_lock.IsValid) - // _lock = Owner.AcquireFocus( /*keepTool: true*/); - // else if (!wantLock && _lock.IsValid) _lock.Release(); - //} - - private void HandleButtons() - { - var device = Controller; - - if (device.GetPressDown(EVRButtonId.k_EButton_SteamVR_Trigger)) InputDown(_KeySet.Trigger, EVRButtonId.k_EButton_SteamVR_Trigger); - - if (device.GetPressUp(EVRButtonId.k_EButton_SteamVR_Trigger)) InputUp(_KeySet.Trigger); - - if (device.GetPressDown(EVRButtonId.k_EButton_Grip)) InputDown(_KeySet.Grip, EVRButtonId.k_EButton_Grip); - - if (device.GetPressUp(EVRButtonId.k_EButton_Grip)) InputUp(_KeySet.Grip); - - if (device.GetPressDown(EVRButtonId.k_EButton_SteamVR_Touchpad)) - { - var axis = Controller.GetAxis(); - var dir = GetTrackpadDirection(axis); - var fun = GetTrackpadFunction(dir); - if (RequiresPress(fun)) - { - _lastPressDirection = dir; - InputDown(fun, EVRButtonId.k_EButton_SteamVR_Touchpad); - } - } - - if (_buttonsSubtool == null) return; - - // 上げたときの位置によらず、押したボタンを離す - // Release the pressed button regardless of the position when it is raised - if (device.GetPressUp(EVRButtonId.k_EButton_SteamVR_Touchpad) && _lastPressDirection.HasValue) - { - InputUp(GetTrackpadFunction(_lastPressDirection.Value)); - _lastPressDirection = null; - } - - // Handle touchpad actions that don't require a press, only touch - var newTouchDirection = device.GetTouch(EVRButtonId.k_EButton_SteamVR_Touchpad) - ? GetTrackpadDirection(Controller.GetAxis()) - : (MoveDirection?)null; - if (_touchDirection != newTouchDirection) - { - //Console.WriteLine("changed to " + newTouchDirection); - if (_touchDirection.HasValue) - { - var oldFun = GetTrackpadFunction(_touchDirection.Value); - if (!RequiresPress(oldFun)) - { - //Console.WriteLine("up " + oldFun); - InputUp(oldFun); - } - } - - if (newTouchDirection.HasValue) - { - var newFun = GetTrackpadFunction(newTouchDirection.Value); - if (!RequiresPress(newFun)) - { - //Console.WriteLine("down " + newFun); - InputDown(newFun, EVRButtonId.k_EButton_SteamVR_Touchpad); - } - } - - _touchDirection = newTouchDirection; - } - - _buttonsSubtool.Update(); - } - - private AssignableFunction GetTrackpadFunction(MoveDirection dir) - { - switch (dir) - { - case MoveDirection.Left: return _KeySet.Left; - case MoveDirection.Up: return _KeySet.Up; - case MoveDirection.Right: return _KeySet.Right; - case MoveDirection.Down: return _KeySet.Down; - case MoveDirection.None: return _KeySet.Center; - default: throw new ArgumentOutOfRangeException(); - } - } - - private static MoveDirection GetTrackpadDirection(Vector2 dir) - { - const float deadzone = 0.5f; - var x = dir.x; - var y = dir.y; - if (Mathf.Abs(x) < deadzone && y > deadzone) return MoveDirection.Up; - if (Mathf.Abs(x) < deadzone && y < -deadzone) return MoveDirection.Down; - if (x < -deadzone && Mathf.Abs(y) < deadzone) return MoveDirection.Left; - if (x > deadzone && Mathf.Abs(y) < deadzone) return MoveDirection.Right; - return MoveDirection.None; - } - - /// - /// When this function is assigned to trackpad, does it require a press - /// or does a touch suffice? - /// - private static bool RequiresPress(AssignableFunction fun) - { - switch (fun) - { - case AssignableFunction.SCROLLDOWN: - case AssignableFunction.SCROLLUP: - case AssignableFunction.LROTATION: - case AssignableFunction.RROTATION: - return false; - default: - return true; - } - } - - private void InputDown(AssignableFunction fun, EVRButtonId buttonMask) - { - - switch (fun) - { - case AssignableFunction.NEXT: - break; - case AssignableFunction.GRAB: - _buttonsSubtool.Destroy(); - _buttonsSubtool = null; - _grab = new GrabAction(Owner, buttonMask); - break; - - case AssignableFunction.SCROLLUP: - case AssignableFunction.SCROLLDOWN: - case AssignableFunction.LBUTTON: - case AssignableFunction.MBUTTON: - case AssignableFunction.RBUTTON: - // Move the cursor to the bottom right corner so buttons/scrolling affect the H speed control - // Extremely fiddly but what can you do - if (_InHScene) VR.Input.Mouse.MoveMouseBy(Screen.width - 10, Screen.height - 10); - - // Force focus the window here so the cursor doesn't go off into the desktop or click the window that's currently on top of the game window - WindowTools.BringWindowToFront(); - goto default; - - default: - _buttonsSubtool.ButtonDown(fun); - break; - } - } - - private void InputUp(AssignableFunction fun) - { - switch (fun) - { - case AssignableFunction.NEXT: - ChangeKeySet(); - break; - case AssignableFunction.GRAB: - break; - default: - _buttonsSubtool.ButtonUp(fun); - break; - } - } - - public override List GetHelpTexts() - { - return new List(new[] - { - ToolUtil.HelpTrigger(Owner, DescriptionFor(_KeySet.Trigger)), - ToolUtil.HelpGrip(Owner, DescriptionFor(_KeySet.Grip)), - ToolUtil.HelpTrackpadCenter(Owner, DescriptionFor(_KeySet.Center)), - ToolUtil.HelpTrackpadLeft(Owner, DescriptionFor(_KeySet.Left)), - ToolUtil.HelpTrackpadRight(Owner, DescriptionFor(_KeySet.Right)), - ToolUtil.HelpTrackpadUp(Owner, DescriptionFor(_KeySet.Up)), - ToolUtil.HelpTrackpadDown(Owner, DescriptionFor(_KeySet.Down)) - }.Where(x => x != null)); - } - - private static string DescriptionFor(AssignableFunction fun) - { - var member = typeof(AssignableFunction).GetMember(fun.ToString()).FirstOrDefault(); - var descr = member?.GetCustomAttributes(typeof(DescriptionAttribute), false).Cast().FirstOrDefault()?.Description; - return descr ?? fun.ToString(); - } - } -} diff --git a/MainGameVR/Controls/GrabAction.cs b/MainGameVR/Controls/GrabAction.cs deleted file mode 100644 index 94f13cb..0000000 --- a/MainGameVR/Controls/GrabAction.cs +++ /dev/null @@ -1,96 +0,0 @@ -using UnityEngine; -using Valve.VR; -using VRGIN.Controls; -using VRGIN.Core; -using VRGIN.Helpers; - -namespace KKS_VR.Controls -{ - internal class GrabAction - { - private readonly Controller _controller; - private readonly EVRButtonId _button; - - public GrabAction(Controller controller, EVRButtonId button) - { - _controller = controller; - _button = button; - - _PrevControllerPos = controller.transform.position; - _GripStartTime = Time.unscaledTime; - _PrevControllerPos = controller.transform.position; - _PrevControllerRot = controller.transform.rotation; - - _TravelRumble = new TravelDistanceRumble(500, 0.1f, controller.transform); - _TravelRumble.UseLocalPosition = true; - _TravelRumble.Reset(); - //controller.StartRumble(_TravelRumble); - } - - public void Destroy() - { - _TravelRumble.Close(); - } - - //private PlayArea _ProspectedPlayArea = new PlayArea(); - private TravelDistanceRumble _TravelRumble; - private float? _GripStartTime; - private Vector3 _PrevControllerPos; - private Quaternion _PrevControllerRot; - - public bool HandleGrabbing() - { - float? gripStartTime; - float? nullable; - var isPressed = _controller.Input.GetPress(_button); - if (isPressed) - { - var vector3 = _controller.transform.position - _PrevControllerPos; - var quaternion = Quaternion.Inverse(_PrevControllerRot * Quaternion.Inverse(_controller.transform.rotation)) * - (_controller.transform.rotation * Quaternion.Inverse(_controller.transform.rotation)); - var unscaledTime = Time.unscaledTime; - gripStartTime = _GripStartTime; - nullable = unscaledTime - gripStartTime; - if (nullable.GetValueOrDefault() > 0.1f & nullable.HasValue || Calculator.Distance(vector3.magnitude) > 0.01f) - { - var num2 = Calculator.Angle(Vector3.forward, quaternion * Vector3.forward) * VR.Settings.RotationMultiplier; - VR.Camera.SteamCam.origin.transform.position -= vector3; - //_ProspectedPlayArea.Height -= vector3.y; - //if (!VR.Settings.GrabRotationImmediateMode && _controller.Input.GetPress(12884901888UL)) - //{ - // VR.Camera.SteamCam.origin.transform.RotateAround(VR.Camera.Head.position, Vector3.up, -num2); - // //_ProspectedPlayArea.Rotation -= num2; - //} - - _GripStartTime = 0.0f; - } - } - if (_controller.Input.GetPressUp(_button)) - { - //this.EnterState(WarpTool.WarpState.None); - var unscaledTime = Time.unscaledTime; - gripStartTime = _GripStartTime; - nullable = unscaledTime - gripStartTime; - var num = 0.1f; - if (nullable.GetValueOrDefault() < num & nullable.HasValue) - { - _controller.StartRumble(new RumbleImpulse(800)); - //_ProspectedPlayArea.Height = 0.0f; - //_ProspectedPlayArea.Scale = _IPDOnStart; - } - }/* - if (VRGIN.Core.VR.Settings.GrabRotationImmediateMode && _controller.Input.GetPressUp(12884901888UL)) - { - float angle = Calculator.Angle(Vector3.ProjectOnPlane(_controller.transform.position - VRGIN.Core.VR.Camera.Head.position, Vector3.up).normalized, Vector3.ProjectOnPlane(VRGIN.Core.VR.Camera.Head.forward, Vector3.up).normalized); - VRGIN.Core.VR.Camera.SteamCam.origin.transform.RotateAround(VRGIN.Core.VR.Camera.Head.position, Vector3.up, angle); - this._ProspectedPlayArea.Rotation = angle; - }*/ - _PrevControllerPos = _controller.transform.position; - _PrevControllerRot = _controller.transform.rotation; - //this.CheckRotationalPress(); - - return isPressed; - } - - } -} diff --git a/MainGameVR/Controls/KoikatuWarpTool.cs b/MainGameVR/Controls/KoikatuWarpTool.cs deleted file mode 100644 index f75139b..0000000 --- a/MainGameVR/Controls/KoikatuWarpTool.cs +++ /dev/null @@ -1,100 +0,0 @@ -using System.Collections; -using ActionGame.Chara; -using KKS_VR.Camera; -using KKS_VR.Interpreters; -using KKS_VR.Settings; -using UnityEngine; -using VRGIN.Core; - -namespace KKS_VR.Controls -{ - internal class KoikatuWarpTool : BetterWarpTool - { - private KoikatuInterpreter _interpreter; - private GameObject _protagonistToFollow; - private KoikatuSettings _settings; - - protected override void OnStart() - { - base.OnStart(); - _interpreter = VR.Interpreter as KoikatuInterpreter; - _settings = VR.Settings as KoikatuSettings; - } - - protected override void OnEnable() - { - base.OnEnable(); - VRCameraMover.Instance.OnMove += OnCameraMove; - } - - protected override void OnDisable() - { - base.OnDisable(); - VRCameraMover.Instance.OnMove -= OnCameraMove; - } - - protected override void OnUpdate() - { - var origin = VR.Camera.Origin; - var oldOriginPosition = origin.position; - var oldOriginRotation = origin.rotation; - - base.OnUpdate(); - - // Detect teleporting in Roam mode. - if (_settings.TeleportWithProtagonist && - (origin.position - oldOriginPosition).sqrMagnitude > 0.04f && - _interpreter.SceneInterpreter is ActionSceneInterpreter act && - GameObject.Find("ActionScene/Player") is GameObject player && - player.activeInHierarchy) - { - // It looks like we either just teleported or become upright. - var diffRotation = origin.rotation * Quaternion.Inverse(oldOriginRotation); - var nonHorizontal = Vector3.Angle(Vector3.up, diffRotation * Vector3.up); - if (nonHorizontal < 0.1f) - { - // Looks like we teleported. - act.MovePlayerToCamera(); - // We undo the camera movement because we want the actual - // teleportation to happen after the game has a chance - // to correct the protagonist's position. - origin.SetPositionAndRotation(oldOriginPosition, oldOriginRotation); - _protagonistToFollow = player; - } - } - } - - protected override void OnLateUpdate() - { - base.OnLateUpdate(); - if (_protagonistToFollow != null) - { - var player = _protagonistToFollow.GetComponent(); - StartCoroutine(FollowDelayedCo(player)); - _protagonistToFollow = null; - } - } - - - private IEnumerator FollowDelayedCo(Player player) - { - // Temporarily hide the protagonist. - var oldActive = player.chaCtrl.objTop.activeSelf; - player.chaCtrl.objTop.SetActive(false); - // Wait for the game to correct the protagonist's position. - yield return null; - if (_interpreter.SceneInterpreter is ActionSceneInterpreter act) - { - VRLog.Debug("Following player"); - act.MoveCameraToPlayer(); - } - - player.chaCtrl.objTop.SetActive(oldActive); - } - - private void OnCameraMove() - { - OnPlayAreaUpdated(); - } - } -} diff --git a/MainGameVR/Controls/TalkSceneHandler.cs b/MainGameVR/Controls/TalkSceneHandler.cs deleted file mode 100644 index 449efab..0000000 --- a/MainGameVR/Controls/TalkSceneHandler.cs +++ /dev/null @@ -1,93 +0,0 @@ -using System.Collections.Generic; -using System.Linq; -using HarmonyLib; -using UnityEngine; -using Valve.VR; -using VRGIN.Controls; -using VRGIN.Core; -using VRGIN.Helpers; - -namespace KKS_VR.Controls -{ - /// - /// A handler component to be attached to a controller, providing touch/look - /// functionalities in talk scenes. - /// This component is meant to remain disabled outside talk scenes. - /// - internal class TalkSceneHandler : ProtectedBehaviour - { - private Controller _controller; - - private readonly HashSet _currentlyIntersecting - = new HashSet(); - - private Controller.Lock _lock; // null or valid - private TalkScene _talkScene; - - protected override void OnStart() - { - base.OnStart(); - - _controller = GetComponent(); - _talkScene = FindObjectOfType(); - if (_talkScene == null) VRLog.Warn("TalkSceneHandler: TalkScene not found"); - } - - protected void OnDisable() - { - _currentlyIntersecting.Clear(); - UpdateLock(); - } - - protected override void OnUpdate() - { - if (_lock != null) HandleTrigger(); - } - - private void HandleTrigger() - { - var device = _controller.Input; //SteamVR_Controller.Input((int)_controller.Tracking.index); - if (device.GetPressUp(EVRButtonId.k_EButton_SteamVR_Trigger)) PerformAction(); - } - - private void PerformAction() - { - // Find the nearest intersecting collider. - var nearest = _currentlyIntersecting - .OrderBy(_col => (_col.transform.position - transform.position).sqrMagnitude) - .FirstOrDefault(); - if (nearest == null) return; - var kind = Extensions.StripPrefix("Com/Hit/", nearest.tag); - if (kind != null) new Traverse(_talkScene).Method("TouchFunc", new[] { typeof(string), typeof(Vector3) }).GetValue(kind, Vector3.zero); - } - - protected void OnTriggerEnter(Collider other) - { - var wasIntersecting = _currentlyIntersecting.Count > 0; - if (other.tag.StartsWith("Com/Hit/")) - { - _currentlyIntersecting.Add(other); - if (!wasIntersecting) _controller.StartRumble(new RumbleImpulse(1000)); - UpdateLock(); - } - } - - protected void OnTriggerExit(Collider other) - { - if (_currentlyIntersecting.Remove(other)) UpdateLock(); - } - - private void UpdateLock() - { - if (_currentlyIntersecting.Count > 0 && _lock == null) - { - _controller.TryAcquireFocus(out _lock); - } - else if (_currentlyIntersecting.Count == 0 && _lock != null) - { - _lock.Release(); - _lock = null; - } - } - } -} diff --git a/MainGameVR/Features/VRFade.cs b/MainGameVR/Features/VRFade.cs deleted file mode 100644 index 7f4e385..0000000 --- a/MainGameVR/Features/VRFade.cs +++ /dev/null @@ -1,147 +0,0 @@ -using System; -using System.Collections; -using ActionGame; -using UnityEngine; -using Valve.VR; -using VRGIN.Core; - -namespace KKS_VR.Features -{ - /// - /// A VR fader that replaces the fader of the base game. - /// - internal class VRFade : ProtectedBehaviour - { - /// - /// Reference to the image used by the vanilla SceneFade object. - /// - private CanvasGroup _vanillaFade; - - private readonly float _gridFadeTime = 1; - private readonly float _fadeAlphaThresholdHigh = 0.9999f; - private readonly float _fadeAlphaThresholdLow = 0.0001f; - - private bool _isFading; - - public static void Create() - { - VR.Camera.gameObject.AddComponent(); - } - - protected override void OnAwake() - { - _vanillaFade = Manager.Scene.sceneFadeCanvas?.canvasGroup ?? throw new ArgumentNullException(nameof(_vanillaFade), "sceneFadeCanvas or canvasGroup is null"); - } - - protected override void OnUpdate() - { - if (!_isFading && _vanillaFade && _vanillaFade.alpha > _fadeAlphaThresholdLow) - { - StartCoroutine(DeepFadeCo()); - } - } - - /// - /// A coroutine for entering "deep fade", where we cut to the compositor's grid and display some overlay. - /// Based on https://github.com/mosirnik/KK_MainGameVR/commit/12e435f1e9a70c7d7b5dd56de416d300a2836091 - /// - private IEnumerator DeepFadeCo() - { - if (OpenVR.Overlay == null || _isFading) - yield break; - - _isFading = true; - - // Make the world outside of the game the same color as the loading screen instead of the headset default skybox - SetCompositorSkyboxOverride(GetFadeColor()); - - var compositor = OpenVR.Compositor; - if (compositor != null) - { - // Fade the game out so the ouside world is now seen instead of the laggy loading screen - compositor.FadeGrid(_gridFadeTime, true); - - // It looks like we need to pause rendering here, otherwise the - // compositor will automatically put us back from the grid. - SteamVR_Render.pauseRendering = true; - } - - // Wait for the game to fully fade in - while (_vanillaFade.alpha <= _fadeAlphaThresholdHigh) - { - if (!_vanillaFade || _vanillaFade.alpha < _fadeAlphaThresholdLow) - goto endEarly; - - yield return null; - } - - // Wait for the game to start fading out - while (_vanillaFade.alpha > _fadeAlphaThresholdHigh) - { - yield return null; - } - - // Wait for things to settle down - yield return null; - yield return null; - - endEarly: - - // Let the game be rendered again and fade into it - SteamVR_Render.pauseRendering = false; - if (compositor != null) - { - compositor.FadeGrid(_gridFadeTime, false); - yield return new WaitForSeconds(_gridFadeTime); - } - - // Wait for the game to finish fading to make sure we are synchronized - while (_vanillaFade && _vanillaFade.alpha > _fadeAlphaThresholdLow) - { - yield return null; - } - - SteamVR_Skybox.ClearOverride(); - - _isFading = false; - } - - private static Color GetFadeColor() - { - try - { - var cycle = FindObjectOfType(); - switch (cycle?.nowType) - { - default: - case Cycle.Type.WakeUp: - case Cycle.Type.Morning: - case Cycle.Type.Daytime: - return new Color(0.44f, 0.78f, 1f); - case Cycle.Type.Evening: - return new Color(0.85f, 0.50f, 0.37f); - case Cycle.Type.Night: - case Cycle.Type.GotoMyHouse: - case Cycle.Type.MyHouse: - return new Color(0.12f, 0.2f, 0.5f); - } - } - catch (Exception e) - { - Console.WriteLine(e); - return Color.white; - } - } - - private static void SetCompositorSkyboxOverride(Color fadeColor) - { - var tex = new Texture2D(1, 1); - var color = fadeColor; - color.a = 1f; - tex.SetPixel(0, 0, color); - tex.Apply(); - SteamVR_Skybox.SetOverride(tex, tex, tex, tex, tex, tex); - Destroy(tex); - } - } -} diff --git a/MainGameVR/Fixes/GameFixes.cs b/MainGameVR/Fixes/GameFixes.cs deleted file mode 100644 index 9a09f17..0000000 --- a/MainGameVR/Fixes/GameFixes.cs +++ /dev/null @@ -1,365 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Diagnostics; -using System.Reflection; -using System.Reflection.Emit; -using ActionGame; -using ADV; -using Cysharp.Threading.Tasks; -using HarmonyLib; -using KKAPI.Utilities; -using KKS_VR.Interpreters; -using KKS_VR.Settings; -using Sirenix.Serialization.Utilities; -using StrayTech; -using UnityEngine; -using UnityStandardAssets.ImageEffects; -using VRGIN.Core; -using Object = UnityEngine.Object; - -/* - * Fixes for issues that are in the base game but are only relevant in VR. - */ -namespace KKS_VR.Fixes -{ - /// - /// Suppress character update for invisible characters in some sub-scenes of Roaming. - /// - [HarmonyPatch(typeof(ChaControl))] - public class ChaControlPatches1 - { - [HarmonyPrefix] - [HarmonyPatch(nameof(ChaControl.LateUpdateForce))] - private static bool PreLateUpdateForce(ChaControl __instance) - { - return !SafeToSkipUpdate(__instance); - } - - [HarmonyPrefix] - [HarmonyPatch(nameof(ChaControl.UpdateForce))] - private static bool PreUpdateForce(ChaControl __instance) - { - return !SafeToSkipUpdate(__instance); - } - - public static bool SafeToSkipUpdate(ChaControl control) - { - return - VR.Settings is KoikatuSettings settings && - settings.OptimizeHInsideRoaming && - control.objTop?.activeSelf == false && - VR.Interpreter is KoikatuInterpreter interpreter && - (interpreter.CurrentScene == KoikatuInterpreter.SceneType.HScene || - interpreter.CurrentScene == KoikatuInterpreter.SceneType.TalkScene); - } - } - - /// - /// Fix game crash during map load - /// todo hack, handle properly? - /// - [HarmonyPatch(typeof(SunLightInfo))] - public class FogHack1 - { - [HarmonyFinalizer] - [HarmonyPatch(nameof(SunLightInfo.Set))] - private static Exception PreLateUpdateForce(Exception __exception) - { - if (__exception != null) VRPlugin.Logger.LogDebug("Caught expected crash: " + __exception); - return null; - } - } - - /// - /// Fix game crash during map load - /// todo hack, handle properly? - /// - [HarmonyPatch(typeof(ActionMap))] - public class FogHack2 - { - // todo hack, handle properly - [HarmonyFinalizer] - [HarmonyPatch(nameof(ActionMap.UpdateCameraFog))] - private static Exception PreLateUpdateForce(Exception __exception) - { - if (__exception != null) VRPlugin.Logger.LogDebug("Caught expected crash: " + __exception); - return null; - } - } - - /// - /// Fix game crash during ADV scene load - /// - [HarmonyPatch(typeof(Manager.Game))] - public class ADVSceneFix1 - { - [HarmonyPostfix] - [HarmonyPatch(nameof(Manager.Game.cameraEffector), MethodType.Getter)] - private static void FixMissingCameraEffector(Manager.Game __instance, ref CameraEffector __result) - { - if (__result == null && __instance.isCameraChanged) - // vr camera doesn't have this component on it, which crashes game code with nullref. Use the component on original advcamera instead - __instance._cameraEffector = __result = Object.FindObjectOfType(); - } - } - - /// - /// Fix being unable to do some actions in roaming mode - /// - [HarmonyPatch(typeof(CameraSystem))] - public class ADVSceneFix2 - { - [HarmonyPrefix] - [HarmonyPatch(nameof(CameraSystem.SystemStatus), MethodType.Getter)] - private static bool FixNeverEndingTransition(ref CameraSystem.CameraSystemStatus __result) - { - __result = CameraSystem.CameraSystemStatus.Inactive; - return false; - } - } - - /// - /// Fix ADV scenes messing with the VR camera by moving it or setting flags on it. Feed it the default 2D camera instead so it's happy. - /// - [HarmonyPatch] - public class ADVSceneFix3 - { - private static IEnumerable TargetMethods() - { - yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(TalkScene), nameof(TalkScene.Setup))); - } - - private static UnityEngine.Camera GetOriginalMainCamera() - { - // vr camera doesn't have this component on it - var originalMainCamera = (Manager.Game.instance.cameraEffector ?? Object.FindObjectOfType()).GetComponent(); - VRPlugin.Logger.LogDebug($"GetOriginalMainCamera called, cam found: {originalMainCamera?.GetFullPath()}\n{new StackTrace()}"); - return originalMainCamera; - } - - private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) - { - var targert = AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)); - var replacement = AccessTools.Method(typeof(ADVSceneFix3), nameof(GetOriginalMainCamera)); - return insts.Manipulator( - instr => instr.opcode == OpCodes.Call && (MethodInfo)instr.operand == targert, - instr => - { - instr.operand = replacement; - VRPlugin.Logger.LogDebug("Patched Camera.main in " + __originalMethod.GetNiceName()); - }); - } - } - - /// - /// Fix ADV scenes messing with the VR camera by moving it or setting flags on it. Feed it the default 2D camera instead so it's happy. - /// - [HarmonyPatch] - public class ADVSceneFix4 - { - private static IEnumerable TargetMethods() - { - yield return AccessTools.Method(typeof(ADVScene), nameof(ADVScene.Init)); - } - - private static UnityEngine.Camera GetOriginalMainCamera() - { - // vr camera doesn't have this component on it - var originalMainCamera = (Manager.Game.instance.cameraEffector ?? Object.FindObjectOfType()).GetComponent(); - VRPlugin.Logger.LogDebug($"GetOriginalMainCamera called, cam found: {originalMainCamera?.GetFullPath()}\n{new StackTrace()}"); - return originalMainCamera; - } - - private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) - { - var targert = AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)); - var replacement = AccessTools.Method(typeof(ADVSceneFix4), nameof(GetOriginalMainCamera)); - return insts.Manipulator( - instr => instr.opcode == OpCodes.Call && (MethodInfo)instr.operand == targert, - instr => - { - instr.operand = replacement; - VRPlugin.Logger.LogDebug("Patched Camera.main in " + __originalMethod.GetNiceName()); - }); - } - - private static void Postfix(ADVScene __instance) - { - Manager.Sound.Listener = UnityEngine.Camera.main.transform; - } - } - - /// - /// Fix vending machines and some other action points softlocking the game - /// - [HarmonyPatch(typeof(Manager.PlayerAction))] - public class VendingMachineFix - { - [HarmonyTranspiler] - [HarmonyPatch(nameof(Manager.PlayerAction.Action))] - private static IEnumerable Transpiler(IEnumerable insts) - { - // Multiple methods get this crossFade field and try to fade on it. Problem is, it doesn't exist. - // Instead of patching everything, create a dummy crossFade when it's being set - var target = AccessTools.Field(typeof(Manager.PlayerAction), nameof(Manager.PlayerAction.crossFade)); - if (target == null) throw new ArgumentNullException(nameof(target)); - return new CodeMatcher(insts).MatchForward(false, new CodeMatch(OpCodes.Stfld, target)) - .ThrowIfInvalid("crossFade not found") - .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(VendingMachineFix), nameof(VendingMachineFix.GiveDummyCrossFade)))) - .Instructions(); - } - - private static CrossFade _dummyCrossFade; - private static CrossFade GiveDummyCrossFade(CrossFade existing) - { - if (existing) return existing; - - // To disable the fade texBase has to be null. texBase is set in Start so it's delayed from creation. - if (!_dummyCrossFade) - _dummyCrossFade = new GameObject("DummyCrossFade").AddComponent(); - else - _dummyCrossFade.texBase = null; - - return _dummyCrossFade; - } - } - - /// - /// Fix wrong position being sometimes set in TalkScene after introduction finishes - /// - [HarmonyPatch] - public class TalkScenePostAdvFix - { - [HarmonyPostfix] - [HarmonyPatch(typeof(TalkScene), nameof(TalkScene.Introduction), MethodType.Normal)] - private static void IntroductionPostfix(TalkScene __instance, UniTask __result) - { - __instance.StartCoroutine(__result.WaitForFinishCo().AppendCo(() => TalkSceneInterpreter.AdjustPosition(__instance))); - } - } - - /// - /// Fix crash when playing ADV scenes - /// - [HarmonyPatch] - public class CycleCrossFadeFix1 - { - private static IEnumerable TargetMethods() - { - yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(Cycle), nameof(Cycle.WakeUp))); - } - - private static bool IsProcessWithNullcheck(CrossFade instance) - { - return instance != null && instance.isProcess; - } - - private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) - { - var targert = AccessTools.PropertyGetter(typeof(CrossFade), nameof(CrossFade.isProcess)); - var replacement = AccessTools.Method(typeof(CycleCrossFadeFix1), nameof(IsProcessWithNullcheck)); - return insts.Manipulator( - instr => instr.opcode == OpCodes.Callvirt && (MethodInfo)instr.operand == targert, - instr => - { - instr.opcode = OpCodes.Call; - instr.operand = replacement; - VRPlugin.Logger.LogDebug("Patched CrossFade.isProcess in " + __originalMethod.GetFullName()); - }); - } - } - - /// - /// Fix hscene killing the camera at end - /// - [HarmonyPatch] - public class HSceneFix1 - { - private static IEnumerable TargetMethods() - { - yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(HScene), nameof(HScene.Start))); - yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(HScene), nameof(HScene.ResultTalk))); - } - - private static UnityEngine.Camera GetOriginalMainCamera() - { - // vr camera doesn't have this component on it - var originalMainCamera = (Manager.Game.instance.cameraEffector ?? Object.FindObjectOfType()).GetComponent(); - VRPlugin.Logger.LogDebug($"GetOriginalMainCamera called, cam found: {originalMainCamera?.GetFullPath()}\n{new StackTrace()}"); - return originalMainCamera; - } - - private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) - { - var targert = AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)); - - VRPlugin.Logger.LogDebug("Patching Camera.main -> null in " + __originalMethod.GetNiceName()); - - // Change Camera.main property get to return null instead to skip code that messes with player camera. - // Only last instance needs to be patched or HScene.ResultTalk will break. - return new CodeMatcher(insts).End() - .MatchBack(false, new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)))) - .ThrowIfInvalid("Camera.main not found in " + __originalMethod.GetNiceName()) - .Set(OpCodes.Ldnull, null) - .Instructions(); - } - } - /// - /// Fix hscene killing the camera at end - /// - [HarmonyPatch(typeof(HScene))] - public class HSceneFix2 - { - [HarmonyPrefix] - [HarmonyPatch(nameof(HScene.HResultADVCameraSetting), MethodType.Normal)] - private static bool SkipCameraSetup() - { - VRPlugin.Logger.LogDebug("Skipping HScene.HResultADVCameraSetting"); - return false; - } - } - - /// - /// Fix mainly for character maker, the mask doesn't work properly in VR and goes all over the place. - /// This removes the mask and applies the amplify effect to the whole camera, with downside of darkening the UI. - /// This component also exists in some places in main game, but it seems like this patch has no ill effects related to that. - /// - [HarmonyPatch(typeof(CameraEffectorColorMask))] - public class CameraEffectorColorMaskFix - { - [HarmonyPrefix] - [HarmonyPatch(nameof(CameraEffectorColorMask.Awake), MethodType.Normal)] - private static bool SkipCameraSetup(CameraEffectorColorMask __instance) - { - VRPlugin.Logger.LogDebug("Skipping CameraEffectorColorMask.Awake and destroying the component"); - GameObject.Destroy(__instance); - return false; - } - } - - /// - /// The game includes an old version of GlobalFog, which assumes that the - /// viewing frustum is always centered at the camera. This assumption is - /// invalid in VR, so we fix it up here. - /// - [HarmonyPatch(typeof(GlobalFog))] - public class GlobalFogPatches - { - [HarmonyPatch(nameof(GlobalFog.CustomGraphicsBlit))] - [HarmonyPrefix] - private static void PreCustomGraphicsBlit(Material fxMaterial) - { - UnityEngine.Camera camera = UnityEngine.Camera.current; - camera.CalculateFrustumCorners( - new Rect(0, 0, 1, 1), camera.farClipPlane, camera.stereoActiveEye, _frustumBuffer); - Matrix4x4 corners = Matrix4x4.zero; - corners.SetRow(0, camera.transform.TransformDirection(_frustumBuffer[1])); - corners.SetRow(1, camera.transform.TransformDirection(_frustumBuffer[2])); - corners.SetRow(2, camera.transform.TransformDirection(_frustumBuffer[3])); - corners.SetRow(3, camera.transform.TransformDirection(_frustumBuffer[0])); - fxMaterial.SetMatrix("_FrustumCornersWS", corners); - } - - static readonly Vector3[] _frustumBuffer = new Vector3[4]; - } -} diff --git a/MainGameVR/Fixes/Mirror/mirror-shader b/MainGameVR/Fixes/Mirror/mirror-shader deleted file mode 100644 index 05ef3d1..0000000 Binary files a/MainGameVR/Fixes/Mirror/mirror-shader and /dev/null differ diff --git a/MainGameVR/Fixes/ReduceAssetUnloads.cs b/MainGameVR/Fixes/ReduceAssetUnloads.cs deleted file mode 100644 index 466dd90..0000000 --- a/MainGameVR/Fixes/ReduceAssetUnloads.cs +++ /dev/null @@ -1,54 +0,0 @@ -using System.Collections.Generic; -using System.Reflection; -using System.Reflection.Emit; -using HarmonyLib; -using KKAPI.Utilities; -using Sirenix.Serialization.Utilities; -using UnityEngine; -using VRGIN.Core; - -namespace KKS_VR.Fixes -{ - /// - /// Avoid triggering resource unload when loading UI-only scenes. - /// todo move into illusionfixes? - /// - [HarmonyPatch] - public class ReduceAssetUnloads - { - private static IEnumerable TargetMethods() - { - yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(Manager.Scene), nameof(Manager.Scene.LoadStart))); - } - - private static AsyncOperation MaybeUnloadUnusedAssets() - { - var shouldUnload = Manager.Scene.IsFadeNow; - if (shouldUnload) - { - return Resources.UnloadUnusedAssets(); - } - else - { - VRLog.Info("Skipping unload"); - return null; - } - } - - private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) - { - foreach (var inst in insts) - if (inst.opcode == OpCodes.Call && - inst.operand is MethodInfo method && - method.Name == "UnloadUnusedAssets") - { - yield return CodeInstruction.Call(() => MaybeUnloadUnusedAssets()); - VRPlugin.Logger.LogDebug("Patched UnloadUnusedAssets in " + __originalMethod.GetFullName()); - } - else - { - yield return inst; - } - } - } -} diff --git a/MainGameVR/Interpreters/ActionSceneInterpreter.cs b/MainGameVR/Interpreters/ActionSceneInterpreter.cs deleted file mode 100644 index 1317aa5..0000000 --- a/MainGameVR/Interpreters/ActionSceneInterpreter.cs +++ /dev/null @@ -1,224 +0,0 @@ -using KKS_VR.Camera; -using KKS_VR.Features; -using KKS_VR.Settings; -using StrayTech; -using UnityEngine; -using VRGIN.Core; -using WindowsInput.Native; - -namespace KKS_VR.Interpreters -{ - internal class ActionSceneInterpreter : SceneInterpreter - { - private KoikatuSettings _Settings; - private ActionScene _ActionScene; - - private GameObject _Map; - private GameObject _CameraSystem; - private bool _NeedsResetCamera; - private bool _IsStanding = true; - private bool _Walking = false; - private bool _Dashing = false; // ダッシュ時は_Walkingと両方trueになる - - public override void OnStart() - { - VRLog.Info("ActionScene OnStart"); - - _Settings = VR.Context.Settings as KoikatuSettings; - _ActionScene = Object.FindObjectOfType(); - - ResetState(); - HoldCamera(); - } - - public override void OnDisable() - { - VRLog.Info("ActionScene OnDisable"); - - ResetState(); - ReleaseCamera(); - } - - private void ResetState() - { - VRLog.Info("ActionScene ResetState"); - - StandUp(); - StopWalking(); - _NeedsResetCamera = false; - } - - private void ResetCamera() - { - var pl = _ActionScene.Player?.chaCtrl.objTop; - - if (pl != null && pl.activeSelf) - { - _CameraSystem = MonoBehaviourSingleton.Instance.gameObject; - - // トイレなどでFPS視点になっている場合にTPS視点に戻す - _CameraSystem.GetComponent().ModeChangeForce((ActionGame.CameraMode?)ActionGame.CameraMode.TPS, true); - //scene.GetComponent().isCursorLock = false; - - // カメラをプレイヤーの位置に移動 - MoveCameraToPlayer(); - - _NeedsResetCamera = false; - VRLog.Info("ResetCamera succeeded"); - } - } - - private void HoldCamera() - { - VRLog.Info("ActionScene HoldCamera"); - - _CameraSystem = MonoBehaviourSingleton.Instance.gameObject; - - if (_CameraSystem != null) - { - _CameraSystem.SetActive(false); - - VRLog.Info("succeeded"); - } - } - - private void ReleaseCamera() - { - VRLog.Info("ActionScene ReleaseCamera"); - - if (_CameraSystem != null) - { - _CameraSystem.SetActive(true); - - VRLog.Info("succeeded"); - } - } - - public override void OnUpdate() - { - var map = _ActionScene.Map.mapRoot?.gameObject; - - if (map != _Map) - { - VRLog.Info("! map changed."); - - ResetState(); - _Map = map; - _NeedsResetCamera = true; - } - - if (_Walking) MoveCameraToPlayer(true, true); - - if (_NeedsResetCamera) ResetCamera(); - - UpdateCrouch(); - } - - private void UpdateCrouch() - { - var pl = _ActionScene.Player?.chaCtrl.objTop; - - if (_Settings.CrouchByHMDPos && pl?.activeInHierarchy == true) - { - var cam = VR.Camera.transform; - var delta_y = cam.position.y - pl.transform.position.y; - - if (_IsStanding && delta_y < _Settings.CrouchThreshold) - Crouch(); - else if (!_IsStanding && delta_y > _Settings.StandUpThreshold) StandUp(); - } - } - - public void MoveCameraToPlayer(bool onlyPosition = false, bool quiet = false) - { - var player = _ActionScene.Player; - - var playerHead = player.chaCtrl.objHead.transform; - var headCam = VR.Camera.transform; - - var cf = Vector3.Scale(player.transform.forward, new Vector3(1, 0, 1)).normalized; - - Vector3 pos; - if (_Settings.UsingHeadPos) - { - pos = playerHead.position; - } - else - { - pos = player.position; - pos.y += _IsStanding ? _Settings.StandingCameraPos : _Settings.CrouchingCameraPos; - } - - VRCameraMover.Instance.MoveTo( - pos + cf * 0.23f, // 首が見えるとうざいのでほんの少し前目にする - onlyPosition ? headCam.rotation : player.rotation, - false, - quiet); - } - - public void MovePlayerToCamera(bool onlyRotation = false) - { - var player = _ActionScene.Player; - var playerHead = player.chaCtrl.objHead.transform; - var headCam = VR.Camera.transform; - - var pos = headCam.position; - pos.y += player.position.y - playerHead.position.y; - - var delta_y = headCam.rotation.eulerAngles.y - player.rotation.eulerAngles.y; - player.transform.Rotate(Vector3.up * delta_y); - var cf = Vector3.Scale(player.transform.forward, new Vector3(1, 0, 1)).normalized; - - if (!onlyRotation) player.position = pos - cf * 0.1f; - } - - public void Crouch() - { - if (_IsStanding) - { - _IsStanding = false; - VR.Input.Keyboard.KeyDown(VirtualKeyCode.VK_Z); - } - } - - public void StandUp() - { - if (!_IsStanding) - { - _IsStanding = true; - VR.Input.Keyboard.KeyUp(VirtualKeyCode.VK_Z); - } - } - - public void StartWalking(bool dash = false) - { - MovePlayerToCamera(true); - - if (!dash) - { - VR.Input.Keyboard.KeyDown(VirtualKeyCode.LSHIFT); - _Dashing = true; - } - - VR.Input.Mouse.LeftButtonDown(); - _Walking = true; - // Force hide the protagonist's head while walking, so that it - // remains hidden when the game lags. - HideMaleHead.ForceHideHead = true; - } - - public void StopWalking() - { - VR.Input.Mouse.LeftButtonUp(); - - if (_Dashing) - { - VR.Input.Keyboard.KeyUp(VirtualKeyCode.LSHIFT); - _Dashing = false; - } - - _Walking = false; - HideMaleHead.ForceHideHead = false; - } - } -} diff --git a/MainGameVR/Interpreters/CustomSceneInterpreter.cs b/MainGameVR/Interpreters/CustomSceneInterpreter.cs deleted file mode 100644 index 243b9bd..0000000 --- a/MainGameVR/Interpreters/CustomSceneInterpreter.cs +++ /dev/null @@ -1,18 +0,0 @@ -namespace KKS_VR.Interpreters -{ - internal class CustomSceneInterpreter : SceneInterpreter - { - public override void OnStart() - { - } - - public override void OnDisable() - { - // nothing to do. - } - - public override void OnUpdate() - { - } - } -} diff --git a/MainGameVR/Interpreters/HSceneInterpreter.cs b/MainGameVR/Interpreters/HSceneInterpreter.cs deleted file mode 100644 index 223f982..0000000 --- a/MainGameVR/Interpreters/HSceneInterpreter.cs +++ /dev/null @@ -1,87 +0,0 @@ -using UnityEngine; -using VRGIN.Core; - -namespace KKS_VR.Interpreters -{ - internal class HSceneInterpreter : SceneInterpreter - { - private bool _active; - private HSceneProc _proc; - private Caress.VRMouth _vrMouth; - private Caress.CaressController _leftController; - private Caress.CaressController _rightController; - private Camera.POV _pov; - - private Color _currentBackgroundColor; - private bool _currentShowMap; - - public override void OnStart() - { - _leftController = VR.Mode.Left.gameObject.AddComponent(); - _rightController = VR.Mode.Right.gameObject.AddComponent(); - _pov = VR.Camera.gameObject.AddComponent(); - _pov.Initialize(_leftController.getController(), _rightController.getController()); - _currentBackgroundColor = Manager.Config.HData.BackColor; - _currentShowMap = Manager.Config.HData.Map; - UpdateCameraState(); - } - - public override void OnDisable() - { - Deactivate(); - Object.Destroy(_pov); - Object.Destroy(_leftController); - Object.Destroy(_rightController); - } - - public override void OnUpdate() - { - if (_currentShowMap != Manager.Config.HData.Map || _currentBackgroundColor != Manager.Config.HData.BackColor) - { - if (!_active || !_pov.IsActive()) - UpdateCameraState(); - } - - if (_active && (!_proc || !_proc.enabled)) - { - // The HProc scene is over, but there may be one more coming. - Deactivate(); - } - - if (!_active && - Manager.Scene.GetRootComponent("HProc") is HSceneProc proc && - proc.enabled) - { - _vrMouth = VR.Camera.gameObject.AddComponent(); - _proc = proc; - _active = true; - } - } - - private void Deactivate() - { - if (_active) - { - VR.Camera.SteamCam.camera.clearFlags = CameraClearFlags.Skybox; - Object.Destroy(_vrMouth); - _proc = null; - _active = false; - } - } - - private void UpdateCameraState() - { - if (!Manager.Config.HData.Map) - { - VR.Camera.SteamCam.camera.backgroundColor = Manager.Config.HData.BackColor; - VR.Camera.SteamCam.camera.clearFlags = CameraClearFlags.SolidColor; - } - else - { - VR.Camera.SteamCam.camera.clearFlags = CameraClearFlags.Skybox; - } - _currentBackgroundColor = Manager.Config.HData.BackColor; - _currentShowMap = Manager.Config.HData.Map; - } - } -} diff --git a/MainGameVR/Interpreters/KoikatuGameInterpreter.cs b/MainGameVR/Interpreters/KoikatuGameInterpreter.cs deleted file mode 100644 index 2e5c0d5..0000000 --- a/MainGameVR/Interpreters/KoikatuGameInterpreter.cs +++ /dev/null @@ -1,302 +0,0 @@ -using System; -using System.Collections; -using Funly.SkyStudio; -using KKAPI.MainGame; -using KKAPI.Maker; -using KKS_VR.Camera; -using KKS_VR.Features; -using UnityEngine; -using UnityEngine.SceneManagement; -using VRGIN.Core; - -namespace KKS_VR.Interpreters -{ - internal class KoikatuInterpreter : GameInterpreter - { - public enum SceneType - { - OtherScene, - ActionScene, - TalkScene, - HScene, - NightMenuScene, - CustomScene - } - - public SceneType CurrentScene { get; private set; } - public SceneInterpreter SceneInterpreter; - - private Fixes.Mirror.Manager _mirrorManager; - private int _kkapiCanvasHackWait; - private Canvas _kkSubtitlesCaption; - private GameObject _sceneObjCache; - - protected override void OnAwake() - { - base.OnAwake(); - - CurrentScene = SceneType.OtherScene; - SceneInterpreter = new OtherSceneInterpreter(); - SceneManager.sceneLoaded += OnSceneLoaded; - _mirrorManager = new Fixes.Mirror.Manager(); - VR.Camera.gameObject.AddComponent(); - } - - protected override void OnUpdate() - { - base.OnUpdate(); - - UpdateScene(); - SceneInterpreter.OnUpdate(); - } - - protected override void OnLateUpdate() - { - base.OnLateUpdate(); - if (_kkSubtitlesCaption != null) FixupKkSubtitles(); - } - - private void OnSceneLoaded(Scene scene, LoadSceneMode mode) - { - foreach (var reflection in FindObjectsOfType()) _mirrorManager.Fix(reflection); - - if (scene.name == "Title" || scene.name == "FreeH" || scene.name == "Uploader" || scene.name == "Downloader") - LoadTitleSkybox(); - } - - private static void LoadTitleSkybox() - { - if (GameObject.FindObjectOfType()) return; - - try - { - var stockSkyProfiles = new string[] - { - "_morning_stu", - "_daytime_stu", - "_evening_stu", - "_night_stu", - }; - // Use either day or night skybox depending on real world time to reduce eye abuse at night - // morning and evening skyboxes are not worth using for title screen - var timeNow = DateTime.Now; - var isNight = timeNow.Hour < 6 || timeNow.Hour > 20; - var skyType = isNight ? stockSkyProfiles[3] : stockSkyProfiles[1]; - // var skyType = stockSkyProfiles[UnityEngine.Random.RandomRangeInt(0, stockSkyProfiles.Length)]; - - VRLog.Info($"Loading skybox {skyType}..."); - - var skyProfile = CommonLib.LoadAsset(@"studio\sky\01.unity3d", "SkyProfile" + skyType, true, null, true); - var skyMaterial = CommonLib.LoadAsset(@"studio\sky\01.unity3d", "SkyboxMaterial" + skyType, true, null, true); - if (skyProfile != null && skyMaterial != null) - { - VRLog.Info($"SkyProfile: {skyProfile} SkyboxMaterial: {skyMaterial}"); - - var instanceGameObject = new GameObject("KKSVR_Skybox"); - var skyController = instanceGameObject.AddComponent(); - - // Need to add dummy sun and mun objects or the controller will refuse to work - var sun = new GameObject("Sun"); - sun.transform.parent = instanceGameObject.transform; - new GameObject("Position", typeof(RotateBody)).transform.parent = sun.transform; - skyController.sunOrbit = sun.AddComponent(); - - var mun = new GameObject("Moon"); - mun.transform.parent = instanceGameObject.transform; - new GameObject("Position", typeof(RotateBody)).transform.parent = mun.transform; - skyController.moonOrbit = mun.AddComponent(); - - // For some reason the profile doesn't come with the material, which is required - skyProfile.skyboxMaterial = skyMaterial; - // This applies the skybox right away - skyController.skyProfile = skyProfile; - } - else - { - VRLog.Warn("Skybox not found! Missing CharaStudio assets?"); - } - } - catch (Exception e) - { - VRLog.Error("Failed to load Skybox! Error: " + e); - } - } - - /// - /// Fix up scaling of subtitles added by KK_Subtitles. See - /// https://github.com/IllusionMods/KK_Plugins/pull/91 for details. - /// - private void FixupKkSubtitles() - { - foreach (Transform child in _kkSubtitlesCaption.transform) - if (child.localScale != Vector3.one) - { - VRLog.Info($"Fixing up scale for {child}"); - child.localScale = Vector3.one; - } - } - - public override bool IsIgnoredCanvas(Canvas canvas) - { - if (PrivacyScreen.IsOwnedCanvas(canvas)) - { - return true; - } - else if (canvas.name == "Canvas_BackGround") - { - BackgroundDisplayer.Instance.TakeCanvas(canvas); - return true; - } - else if (canvas.name == "CvsMenuTree") - { - // Here, we attempt to avoid some unfortunate conflict with - // KKAPI. - // - // In order to support plugin-defined subcategories in Maker, - // KKAPI clones some UI elements out of CvsMenuTree when the - // canvas is created, then uses them as templates for custom - // UI items. - // - // At the same time, VRGIN attempts to claim the canvas by - // setting its mode to ScreenSpaceCamera, which changes - // localScale of the canvas by a factor of 100 or so. If this - // happens between KKAPI's cloning out and cloning in, the - // resulting UI items will have the wrong scale, 72x the correct - // size to be precise. - // - // So our solution here is to hide the canvas from VRGIN for a - // couple of frames. Crude but works. - - if (_kkapiCanvasHackWait == 0) - { - _kkapiCanvasHackWait = 3; - return true; - } - else - { - _kkapiCanvasHackWait -= 1; - return 0 < _kkapiCanvasHackWait; - } - } - else if (canvas.name == "KK_Subtitles_Caption") - { - _kkSubtitlesCaption = canvas; - } - - return false; - } - - // 前回とSceneが変わっていれば切り替え処理をする - private void UpdateScene() - { - var nextSceneType = DetectScene(); - - if (nextSceneType != CurrentScene) - { - VRLog.Info($"Load interpreter for new scene type: {nextSceneType}"); - SceneInterpreter.OnDisable(); - - CurrentScene = nextSceneType; - SceneInterpreter = CreateSceneInterpreter(nextSceneType); - SceneInterpreter.OnStart(); - } - } - - private SceneType DetectScene() - { - if (GameAPI.InsideHScene) return SceneType.HScene; - if (MakerAPI.InsideMaker) return SceneType.CustomScene; - if (TalkScene.isPaly) return SceneType.TalkScene; - - var stack = Manager.Scene.NowSceneNames; - foreach (var name in stack) - { - //if (name == "H" && SceneObjPresent("HScene")) - // return SceneType.HScene; - if (ActionScene.initialized && name == "Action") - return SceneType.ActionScene; - //if (name == "Talk" && SceneObjPresent("TalkScene")) - // return SceneType.TalkScene; - if (name == "NightMenu" && SceneObjPresent("NightMenuScene")) - return SceneType.NightMenuScene; - //if (name == "CustomScene" && SceneObjPresent("CustomScene")) - // return SceneType.CustomScene; - } - - return SceneType.OtherScene; - } - - private bool SceneObjPresent(string name) - { - if (_sceneObjCache != null && _sceneObjCache.name == name) return true; - var obj = GameObject.Find(name); - if (obj != null) - { - _sceneObjCache = obj; - return true; - } - - return false; - } - - private static SceneInterpreter CreateSceneInterpreter(SceneType ty) - { - switch (ty) - { - case SceneType.OtherScene: - return new OtherSceneInterpreter(); - case SceneType.ActionScene: - return new ActionSceneInterpreter(); - case SceneType.CustomScene: - return new CustomSceneInterpreter(); - case SceneType.NightMenuScene: - return new NightMenuSceneInterpreter(); - case SceneType.HScene: - return new HSceneInterpreter(); - case SceneType.TalkScene: - return new TalkSceneInterpreter(); - default: - VRLog.Warn($"Unknown scene type: {ty}"); - return new OtherSceneInterpreter(); - } - } - - protected override CameraJudgement JudgeCameraInternal(UnityEngine.Camera camera) - { - if (camera.CompareTag("MainCamera")) StartCoroutine(HandleMainCameraCo(camera)); - return base.JudgeCameraInternal(camera); - } - - /// - /// A coroutine to be called when a new main camera is detected. - /// - /// - /// - private IEnumerator HandleMainCameraCo(UnityEngine.Camera camera) - { - // Unity might have messed with the camera transform for this frame, - // so we wait for the next frame to get clean data. - yield return null; - - if (camera.name == "ActionCamera" || camera.name == "FrontCamera") - { - VRLog.Info("Adding ActionCameraControl"); - camera.gameObject.AddComponent(); - } - else if (camera.GetComponent() != null) - { - VRLog.Info("New main camera detected: moving to {0} {1}", camera.transform.position, camera.transform.eulerAngles); - VRCameraMover.Instance.MoveTo(camera.transform.position, camera.transform.rotation, false); - VRLog.Info("moved to {0} {1}", VR.Camera.Head.position, VR.Camera.Head.eulerAngles); - VRLog.Info("Adding CameraControlControl"); - camera.gameObject.AddComponent(); - } - else - { - VRLog.Warn($"Unknown kind of main camera was added: {camera.name}"); - } - } - - //public override bool ApplicationIsQuitting => Manager.Scene.isGameEnd; - } -} diff --git a/MainGameVR/Interpreters/NightMenuSceneInterpreter.cs b/MainGameVR/Interpreters/NightMenuSceneInterpreter.cs deleted file mode 100644 index 0737671..0000000 --- a/MainGameVR/Interpreters/NightMenuSceneInterpreter.cs +++ /dev/null @@ -1,19 +0,0 @@ -namespace KKS_VR.Interpreters -{ - internal class NightMenuSceneInterpreter : SceneInterpreter - { - public override void OnStart() - { - } - - public override void OnDisable() - { - // nothing to do. - } - - public override void OnUpdate() - { - // nothing to do. - } - } -} diff --git a/MainGameVR/Interpreters/OtherSceneInterpreter.cs b/MainGameVR/Interpreters/OtherSceneInterpreter.cs deleted file mode 100644 index fcdbad1..0000000 --- a/MainGameVR/Interpreters/OtherSceneInterpreter.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace KKS_VR.Interpreters -{ - internal class OtherSceneInterpreter : SceneInterpreter - { - public override void OnStart() - { - // nothing to do. - } - - public override void OnDisable() - { - // nothing to do. - } - - public override void OnUpdate() - { - // nothing to do. - } - } -} diff --git a/MainGameVR/Interpreters/SceneInterpreter.cs b/MainGameVR/Interpreters/SceneInterpreter.cs deleted file mode 100644 index fdcced2..0000000 --- a/MainGameVR/Interpreters/SceneInterpreter.cs +++ /dev/null @@ -1,28 +0,0 @@ -using UnityEngine; -using VRGIN.Core; - -namespace KKS_VR.Interpreters -{ - internal abstract class SceneInterpreter - { - public abstract void OnStart(); - public abstract void OnDisable(); - public abstract void OnUpdate(); - - protected void AddControllerComponent() - where T : Component - { - VR.Mode.Left.gameObject.AddComponent(); - VR.Mode.Right.gameObject.AddComponent(); - } - - protected void DestroyControllerComponent() - where T : Component - { - var left = VR.Mode.Left.GetComponent(); - if (left != null) Object.Destroy(left); - var right = VR.Mode.Right.GetComponent(); - if (right != null) Object.Destroy(right); - } - } -} diff --git a/MainGameVR/Interpreters/TalkSceneInterpreter.cs b/MainGameVR/Interpreters/TalkSceneInterpreter.cs deleted file mode 100644 index 302a92d..0000000 --- a/MainGameVR/Interpreters/TalkSceneInterpreter.cs +++ /dev/null @@ -1,69 +0,0 @@ -using Illusion.Extensions; -using KKS_VR.Camera; -using UnityEngine; -using VRGIN.Core; - -namespace KKS_VR.Interpreters -{ - internal class TalkSceneInterpreter : SceneInterpreter - { - private Canvas _canvasBack; - public static float TalkDistance = 0.65f; - - public override void OnDisable() - { - DestroyControllerComponent(); - if (_canvasBack != null) _canvasBack.enabled = true; - } - - public override void OnStart() - { - AddControllerComponent(); - - if (!TalkScene.initialized) - { - VRLog.Warn("TalkScene object not found"); - return; - } - - VRLog.Warn("TalkScene init"); - - var talkScene = TalkScene.instance; - - talkScene.otherInitialize += () => - { - VRLog.Warn("talkScene.otherInitialize"); - - AdjustPosition(talkScene); - - // talkscene messes with camera settings - UnityEngine.Camera.main.clearFlags = CameraClearFlags.Skybox; - - talkScene.backGround.visible = false; - talkScene.canvasBack.gameObject.SetActiveIfDifferent(false); - }; - - _canvasBack = talkScene.canvasBack; - } - - public static void AdjustPosition(TalkScene talkScene) - { - if (talkScene == null) return; - - // The default camera location is a bit too far for a friendly - // conversation. - var heroine = talkScene.targetHeroine.transform; - VRCameraMover.Instance.MoveTo( - heroine.TransformPoint(new Vector3(0, ActionCameraControl.GetPlayerHeight(), TalkDistance)), - heroine.rotation * Quaternion.Euler(0, 180f, 0), - false); - } - - public override void OnUpdate() - { - // We don't need the background image because we directly see - // background objects. - if (_canvasBack != null) _canvasBack.enabled = false; - } - } -} diff --git a/MainGameVR/MainGameVR.csproj.DotSettings b/MainGameVR/MainGameVR.csproj.DotSettings deleted file mode 100644 index 3439569..0000000 --- a/MainGameVR/MainGameVR.csproj.DotSettings +++ /dev/null @@ -1,2 +0,0 @@ - - True \ No newline at end of file diff --git a/MainGameVR/RobustInputSimulator.cs b/MainGameVR/RobustInputSimulator.cs deleted file mode 100644 index 94fe0db..0000000 --- a/MainGameVR/RobustInputSimulator.cs +++ /dev/null @@ -1,459 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using WindowsInput; -using WindowsInput.Native; -using VRGIN.Core; -using VRGIN.Native; -using HarmonyLib; -using UnityEngine; -using System.Threading; -using System.Runtime.InteropServices; - -namespace KKS_VR -{ - /// - /// A version of InputSimulator that tries to avoid the issue of - /// requiring focus & accidentally clicking outside the application window. - /// - public class RobustInputSimulator : IInputSimulator - { - public IKeyboardSimulator Keyboard { get; private set; } - public IMouseSimulator Mouse { get; private set; } - public IInputDeviceStateAdaptor InputDeviceState { get; private set; } - - public RobustInputSimulator() - { - Keyboard = new RobustKeyboardSimulator(this); - Mouse = new RobustMouseSimulator(this); - InputDeviceState = new WindowsInputDeviceStateAdaptor(); - } - - /// - /// Make Unity think we have focus, if it doesn't already. This is necessary to prevent - /// messages from being ignored. - /// - internal static void FakeFocus() - { - if (!Application.isFocused) - { - NativeMethods.PostMessage( - WindowManager.Handle, - 0x6, // WM_ACTIVATE - new IntPtr(0x2), // Activated by a mouse click - IntPtr.Zero); // Handle to the previously active window - } - } - - internal class NativeMethods - { - [DllImport("user32.dll", SetLastError = true)] - internal static extern bool GetKeyboardState([Out] byte[] lpKeyState); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern bool SetKeyboardState([In] byte[] lpKeyState); - - [DllImport("user32.dll", SetLastError = true)] - internal static extern bool PostMessage(IntPtr hWnd, uint msg, IntPtr wParam, IntPtr lParam); - } - } - - /// - /// This class is similar to MouseSimulator, but avoids generating - /// button down messages using SendInput. Instead it uses PostMessage - /// to send a fake WndProc message to the game window. This means - /// the plugin continues to work fine if the game window loses focus. - /// It also avoids the risk of accidentally clicking a random window. - /// The overall strategy is: - /// - /// * Down and Up events: Use a WndProc message. - /// * Wheel scroll: Use WM_MOUSEWHEEL, but this only affects IMGui for - /// some reason. So we additionally patch UnityEngine.Input - /// to inject synthetic wheel scrolls for UI and game code. - /// * Cursor movement: Use SendInput to make sure that the actual cursor - /// moves. This is necessary because game code often queries for the - /// cursor position. However this is insufficient for IMGUI to recognize - /// a drag. For this reason, an additional WM_MOUSEMOVE message is - /// generated when dragging. - /// - public class RobustMouseSimulator : IMouseSimulator - { - private readonly MouseSimulator _systemSimulator; - private Buttons _pressedMask = 0; - private WindowsInterop.POINT? _dragCurrent; - - // Bitmask that can be passed to WM_MOUSEMOVE - enum Buttons - { - Left = 0x1, - Right = 0x2, - Middle = 0x10, - } - - public IKeyboardSimulator Keyboard => _inputSimulator.Keyboard; - - public RobustMouseSimulator(RobustInputSimulator inputSimulator) - { - _inputSimulator = inputSimulator; - _systemSimulator = new MouseSimulator(inputSimulator); - } - - public IMouseSimulator MoveMouseBy(int pixelDeltaX, int pixelDeltaY) - { - RobustInputSimulator.FakeFocus(); - _systemSimulator.MoveMouseBy(pixelDeltaX, pixelDeltaY); - if (_pressedMask != 0) - { - var startingPoint = _dragCurrent ?? MouseOperations.GetCursorPosition(); - DragToScreenCoordinate(startingPoint.X + pixelDeltaX, startingPoint.Y + pixelDeltaY); - } - return this; - } - - public IMouseSimulator MoveMouseTo(double absoluteX, double absoluteY) - { - RobustInputSimulator.FakeFocus(); - _systemSimulator.MoveMouseTo(absoluteX, absoluteY); - if (_pressedMask != 0) - { - int width = WindowsInterop.GetSystemMetrics(WindowsInterop.SystemMetric.SM_CXSCREEN); - int height = WindowsInterop.GetSystemMetrics(WindowsInterop.SystemMetric.SM_CYSCREEN); - DragToScreenCoordinate( - (int)Math.Round(absoluteX / 65535 * width), - (int)Math.Round(absoluteY / 65535 * height)); - } - return this; - } - - public IMouseSimulator MoveMouseToPositionOnVirtualDesktop(double absoluteX, double absoluteY) - { - RobustInputSimulator.FakeFocus(); - _systemSimulator.MoveMouseToPositionOnVirtualDesktop(absoluteX, absoluteY); - if (_pressedMask != 0) - { - var vRect = WindowManager.GetVirtualScreenRect(); - DragToScreenCoordinate( - (int)Math.Round(absoluteX / 65535 * (vRect.Right - vRect.Left)) + vRect.Left, - (int)Math.Round(absoluteY / 65535 * (vRect.Bottom - vRect.Top)) + vRect.Top); - - } - return this; - } - - public IMouseSimulator LeftButtonDown() - { - RobustInputSimulator.FakeFocus(); - _pressedMask |= Buttons.Left; - return PostButtonMessage(0x201); // WM_LBUTTONDOWN - } - - public IMouseSimulator LeftButtonUp() - { - ClearPressed(Buttons.Left); - return PostButtonMessage(0x202); // WM_LBUTTONUP - } - - public IMouseSimulator LeftButtonClick() - { - throw new NotImplementedException(); - } - - public IMouseSimulator LeftButtonDoubleClick() - { - throw new NotImplementedException(); - } - - public IMouseSimulator MiddleButtonDown() - { - RobustInputSimulator.FakeFocus(); - _pressedMask |= Buttons.Middle; - return PostButtonMessage(0x207); // WM_MBUTTONDOWN - } - - public IMouseSimulator MiddleButtonUp() - { - ClearPressed(Buttons.Middle); - return PostButtonMessage(0x208); // WM_MBUTTONUP - } - - public IMouseSimulator MiddleButtonClick() - { - throw new NotImplementedException(); - } - - public IMouseSimulator MiddleButtonDoubleClick() - { - throw new NotImplementedException(); - } - - public IMouseSimulator RightButtonDown() - { - RobustInputSimulator.FakeFocus(); - _pressedMask |= Buttons.Right; - return PostButtonMessage(0x204); // WM_RBUTTONDOWN - } - - public IMouseSimulator RightButtonUp() - { - ClearPressed(Buttons.Right); - return PostButtonMessage(0x205); // WM_RBUTTONUP - } - - public IMouseSimulator RightButtonClick() - { - throw new NotImplementedException(); - } - - public IMouseSimulator RightButtonDoubleClick() - { - throw new NotImplementedException(); - } - - public IMouseSimulator XButtonDown(int buttonId) - { - throw new NotImplementedException(); - } - - public IMouseSimulator XButtonUp(int buttonId) - { - throw new NotImplementedException(); - } - - public IMouseSimulator XButtonClick(int buttonId) - { - throw new NotImplementedException(); - } - - public IMouseSimulator XButtonDoubleClick(int buttonId) - { - throw new NotImplementedException(); - } - - public IMouseSimulator VerticalScroll(int scrollAmountInClicks) - { - RobustInputSimulator.FakeFocus(); - InputPatches.RequestScroll(scrollAmountInClicks); - return PostScrollMessage(0x20a, scrollAmountInClicks * 120); // WM_MOUSEWHEEL - } - - public IMouseSimulator VerticalScrollAbsolute(int scrollAmount) - { - RobustInputSimulator.FakeFocus(); - InputPatches.RequestScroll(scrollAmount); - return PostScrollMessage(0x20a, scrollAmount * 120); // WM_MOUSEWHEEL - } - - public IMouseSimulator HorizontalScroll(int scrollAmountInClicks) - { - throw new NotImplementedException(); - } - - public IMouseSimulator HorizontalScrollAbsolute(int scrollAmount) - { - throw new NotImplementedException(); - } - - public IMouseSimulator Sleep(int millsecondsTimeout) - { - _systemSimulator.Sleep(millsecondsTimeout); - return this; - } - - public IMouseSimulator Sleep(TimeSpan timeout) - { - _systemSimulator.Sleep(timeout); - return this; - } - - private IMouseSimulator PostButtonMessage(uint msg) - { - // wParam represents the modifier key state, - // but Unity doesn't seem to care. - return PostMouseMessage(msg, IntPtr.Zero); - } - - private IMouseSimulator PostScrollMessage(uint msg, int amount) - { - return PostMouseMessage(msg, PackWords(amount, 0)); - } - - private IMouseSimulator PostMouseMessage(uint msg, IntPtr wParam) - { - WindowsInterop.GetCursorPos(out var mousePosition); - var clientRect = WindowManager.GetClientRect(); - var x = mousePosition.X - clientRect.Left; - var y = mousePosition.Y - clientRect.Top; - RobustInputSimulator.NativeMethods.PostMessage( - WindowManager.Handle, - msg, - wParam, - PackWords(y, x)); - return this; - } - - private void DragToScreenCoordinate(int x, int y) - { - var clientRect = WindowManager.GetClientRect(); - RobustInputSimulator.NativeMethods.PostMessage( - WindowManager.Handle, - 0x200, // WM_MOUSEMOVE - new IntPtr((int)_pressedMask), - PackWords(y - clientRect.Top, x - clientRect.Left)); - _dragCurrent = new WindowsInterop.POINT(x, y); - } - - private void ClearPressed(Buttons button) - { - _pressedMask &= ~button; - if (_pressedMask == 0) - { - _dragCurrent = null; - } - } - private static IntPtr PackWords(int hi, int lo) - { - return new IntPtr((hi << 16) | (lo & 0xffff)); - } - - private readonly RobustInputSimulator _inputSimulator; - } - - /// - /// A keyboard simulator that uses PostMessage and SetKeyboardState to - /// simulate keypresses. - /// - public class RobustKeyboardSimulator : IKeyboardSimulator - { - public IMouseSimulator Mouse => _inputSimulator.Mouse; - - public RobustKeyboardSimulator(IInputSimulator inputSimulator) - { - _inputSimulator = inputSimulator; - } - - public IKeyboardSimulator KeyDown(VirtualKeyCode code) - { - RobustInputSimulator.FakeFocus(); - RobustInputSimulator.NativeMethods.PostMessage( - WindowManager.Handle, - 0x100, // WM_KEYDOWN - (IntPtr)code, - IntPtr.Zero); - ModifyInputState(code, true); - return this; - } - public IKeyboardSimulator KeyUp(VirtualKeyCode code) - { - RobustInputSimulator.NativeMethods.PostMessage( - WindowManager.Handle, - 0x101, // WM_KEYUP - (IntPtr)code, - new IntPtr(0xc000_0001u)); // repeat=1, context=0, previous=1, transition=1 - ModifyInputState(code, false); - return this; - } - public IKeyboardSimulator KeyPress(VirtualKeyCode code) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator KeyPress(params VirtualKeyCode[] code) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator ModifiedKeyStroke(IEnumerable mods, IEnumerable codes) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator ModifiedKeyStroke(IEnumerable mods, VirtualKeyCode code) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator ModifiedKeyStroke(VirtualKeyCode mod, IEnumerable codes) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator ModifiedKeyStroke(VirtualKeyCode mod, VirtualKeyCode code) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator TextEntry(string str) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator TextEntry(char c) - { - throw new NotImplementedException(); - } - public IKeyboardSimulator Sleep(int millisecondsTimeout) - { - Thread.Sleep(millisecondsTimeout); - return this; - } - public IKeyboardSimulator Sleep(TimeSpan timeout) - { - Thread.Sleep(timeout); - return this; - } - - private void ModifyInputState(VirtualKeyCode code, bool value) - { - var state = new byte[256]; - if (!RobustInputSimulator.NativeMethods.GetKeyboardState(state)) - { - VRLog.Error($"GetKeyboardState failed"); - return; - } - state[(int)code] = (value ? (byte)0x80 : (byte)0); - if (!RobustInputSimulator.NativeMethods.SetKeyboardState(state)) - { - VRLog.Error($"SetKeyboardState failed"); - return; - } - } - - private readonly IInputSimulator _inputSimulator; - } - - [HarmonyPatch(typeof(Input))] - class InputPatches - { - [HarmonyPatch(nameof(Input.mouseScrollDelta), MethodType.Getter)] - [HarmonyPostfix] - private static void PostGetMouseScrollDelta(ref Vector2 __result) - { - UpdateForFrame(); - __result.y += _scrollCurrent; - } - - [HarmonyPatch(nameof(Input.GetAxis))] - [HarmonyPostfix] - private static void PostGetAxis(string axisName, ref float __result) - { - if (axisName == "Mouse ScrollWheel") - { - UpdateForFrame(); - __result += _scrollCurrent; - } - } - - private static void UpdateForFrame() - { - if (Time.frameCount != _lastUpdate) - { - _lastUpdate = Time.frameCount; - _scrollCurrent = _scrollRequest; - _scrollRequest = 0; - } - } - - internal static void RequestScroll(int amount) - { - _scrollRequest += amount; - } - - private static int _lastUpdate = -1; - private static int _scrollRequest = 0; - private static int _scrollCurrent = 0; - } -} diff --git a/MainGameVR/Settings/KoikatuSettings.cs b/MainGameVR/Settings/KoikatuSettings.cs deleted file mode 100644 index 2eb1c04..0000000 --- a/MainGameVR/Settings/KoikatuSettings.cs +++ /dev/null @@ -1,290 +0,0 @@ -using System.Collections.Generic; -using System.ComponentModel; -using VRGIN.Core; - -namespace KKS_VR.Settings -{ - /// - /// User settings. SettingsManager is responsible for updating this. - /// - public class KoikatuSettings : VRSettings - { - public List KeySets - { - get => _KeySets; - set - { - _KeySets = value; - TriggerPropertyChanged("KeySets"); - } - } - - private List _KeySets = null; - - public List HKeySets - { - get => _HKeySets; - set - { - _HKeySets = value; - TriggerPropertyChanged("HKeySets"); - } - } - - private List _HKeySets = null; - - public bool UsingHeadPos - { - get => _UsingHeadPos; - set => _UsingHeadPos = value; - } - - private bool _UsingHeadPos = false; - - public float StandingCameraPos - { - get => _StandingCameraPos; - set => _StandingCameraPos = value; - } - - private float _StandingCameraPos = 1.5f; - - public float CrouchingCameraPos - { - get => _CrouchingCameraPos; - set => _CrouchingCameraPos = value; - } - - private float _CrouchingCameraPos = 0.7f; - - public bool CrouchByHMDPos - { - get => _CrouchByHMDPos; - set => _CrouchByHMDPos = value; - } - - private bool _CrouchByHMDPos = true; - - public float CrouchThreshold { get; set; } - - public float StandUpThreshold { get; set; } - - public float RotationAngle - { - get => _RotationAngle; - set => _RotationAngle = value; - } - - private float _RotationAngle = 45f; - - public bool AutomaticTouching - { - get => _AutomaticTouching; - set => _AutomaticTouching = value; - } - - private bool _AutomaticTouching = false; - - public bool AutomaticTouchingByHmd { get; set; } = false; - - public bool AutomaticKissing - { - get => _AutomaticKissing; - set => _AutomaticKissing = value; - } - - private bool _AutomaticKissing = false; - - public bool AutomaticLicking { get; set; } - - public bool FirstPersonADV { get; set; } - - public bool TeleportWithProtagonist { get; set; } - - public bool PrivacyScreen - { - get => _PrivacyScreen; - set - { - _PrivacyScreen = value; - TriggerPropertyChanged("PrivacyScreen"); - } - } - - private bool _PrivacyScreen = false; - - public bool OptimizeHInsideRoaming { get; set; } - - public float NearClipPlane - { - get => _NearClipPlane; - set - { - _NearClipPlane = value; - TriggerPropertyChanged("NearClipPlane"); - } - } - - private float _NearClipPlane; - - public bool UseLegacyInputSimulator - { - get => _UseLegacyInputSimulator; - set - { - _UseLegacyInputSimulator = value; - TriggerPropertyChanged("UseLegacyInputSimulator"); - } - } - - private bool _UseLegacyInputSimulator; - - public bool EnablePOV - { - get => _EnablePOV; - set - { - _EnablePOV = value; - TriggerPropertyChanged("EnablePOV"); - } - } - - private bool _EnablePOV = true; - } - - public class KeySet - { - public KeySet( - AssignableFunction trigger, - AssignableFunction grip, - AssignableFunction Up, - AssignableFunction Down, - AssignableFunction Right, - AssignableFunction Left, - AssignableFunction Center) - { - Trigger = trigger; - Grip = grip; - this.Up = Up; - this.Down = Down; - this.Right = Right; - this.Left = Left; - this.Center = Center; - } - - public AssignableFunction Trigger { get; set; } - - public AssignableFunction Grip { get; set; } - - public AssignableFunction Up { get; set; } - - public AssignableFunction Down { get; set; } - - public AssignableFunction Right { get; set; } - - public AssignableFunction Left { get; set; } - - public AssignableFunction Center { get; set; } - } - - public enum AssignableFunction - { - [Description("None")] NONE, - [Description("Walk (Roam mode)")] WALK, - [Description("Dash (Roam mode)")] DASH, - - [Description("Move protagonist to camera (Roam mode)")] - PL2CAM, - [Description("Crouch (Roam mode)")] CROUCH, - [Description("Turn left")] LROTATION, - [Description("Turn right")] RROTATION, - [Description("Left mouse button")] LBUTTON, - [Description("Right mouse button")] RBUTTON, - [Description("Middle mouse button")] MBUTTON, - [Description("Mouse wheel scroll up")] SCROLLUP, - - [Description("Mouse wheel scroll down")] - SCROLLDOWN, - - [Description("Switch button assignments")] - NEXT, - [Description("Grab space to move")] GRAB, - [Description("Keyboard Tab")] TAB, - [Description("Keyboard Enter")] RETURN, - [Description("Keyboard Esc")] ESCAPE, - [Description("Keyboard Space")] SPACE, - [Description("Keyboard Home")] HOME, - [Description("Keyboard End")] END, - [Description("Keyboard arrow left")] LEFT, - [Description("Keyboard arrow up")] UP, - [Description("Keyboard arrow right")] RIGHT, - [Description("Keyboard arrow down")] DOWN, - [Description("Keyboard Ins")] INSERT, - [Description("Keyboard Del")] DELETE, - [Description("Keyboard Page Up")] PRIOR, - [Description("Keyboard Page Down")] KEYBOARD_PAGE_DOWN, - [Description("Keyboard Backspace")] BACK, - [Description("Keyboard Shift")] SHIFT, - [Description("Keyboard Ctrl")] CONTROL, - [Description("Keyboard Alt")] MENU, - [Description("Keyboard Pause")] PAUSE, - [Description("Keyboard F1")] F1, - [Description("Keyboard F2")] F2, - [Description("Keyboard F3")] F3, - [Description("Keyboard F4")] F4, - [Description("Keyboard F5")] F5, - [Description("Keyboard F6")] F6, - [Description("Keyboard F7")] F7, - [Description("Keyboard F8")] F8, - [Description("Keyboard F9")] F9, - [Description("Keyboard F10")] F10, - [Description("Keyboard F11")] F11, - [Description("Keyboard F12")] F12, - [Description("Keyboard A")] VK_A, - [Description("Keyboard B")] VK_B, - [Description("Keyboard C")] VK_C, - [Description("Keyboard D")] VK_D, - [Description("Keyboard E")] VK_E, - [Description("Keyboard F")] VK_F, - [Description("Keyboard G")] VK_G, - [Description("Keyboard H")] VK_H, - [Description("Keyboard I")] VK_I, - [Description("Keyboard J")] VK_J, - [Description("Keyboard K")] VK_K, - [Description("Keyboard L")] VK_L, - [Description("Keyboard M")] VK_M, - [Description("Keyboard N")] VK_N, - [Description("Keyboard O")] VK_O, - [Description("Keyboard P")] VK_P, - [Description("Keyboard Q")] VK_Q, - [Description("Keyboard R")] VK_R, - [Description("Keyboard S")] VK_S, - [Description("Keyboard T")] VK_T, - [Description("Keyboard U")] VK_U, - [Description("Keyboard V")] VK_V, - [Description("Keyboard W")] VK_W, - [Description("Keyboard X")] VK_X, - [Description("Keyboard Y")] VK_Y, - [Description("Keyboard Z")] VK_Z, - [Description("Keyboard 0")] VK_0, - [Description("Keyboard 1")] VK_1, - [Description("Keyboard 2")] VK_2, - [Description("Keyboard 3")] VK_3, - [Description("Keyboard 4")] VK_4, - [Description("Keyboard 5")] VK_5, - [Description("Keyboard 6")] VK_6, - [Description("Keyboard 7")] VK_7, - [Description("Keyboard 8")] VK_8, - [Description("Keyboard 9")] VK_9, - [Description("Keyboard Numpad 0")] NUMPAD0, - [Description("Keyboard Numpad 1")] NUMPAD1, - [Description("Keyboard Numpad 2")] NUMPAD2, - [Description("Keyboard Numpad 3")] NUMPAD3, - [Description("Keyboard Numpad 4")] NUMPAD4, - [Description("Keyboard Numpad 5")] NUMPAD5, - [Description("Keyboard Numpad 6")] NUMPAD6, - [Description("Keyboard Numpad 7")] NUMPAD7, - [Description("Keyboard Numpad 8")] NUMPAD8, - [Description("Keyboard Numpad 9")] NUMPAD9 - } -} diff --git a/MainGameVR/Settings/SettingsManager.cs b/MainGameVR/Settings/SettingsManager.cs deleted file mode 100644 index 10ad00c..0000000 --- a/MainGameVR/Settings/SettingsManager.cs +++ /dev/null @@ -1,517 +0,0 @@ -using System; -using System.Collections.Generic; -using System.ComponentModel; -using BepInEx.Configuration; -using KKAPI.Utilities; -using VRGIN.Core; -using UnityEngine; - -namespace KKS_VR.Settings -{ - /// - /// Manages configuration and keeps it up to date. - /// - /// BepInEx wants us to store the config in a bunch of ConfigEntry objects, - /// but VRGIN wants it stored inside a class inheriting VRSettings. So - /// our plan is: - /// - /// * We have both ConfigEntry objects and KoikatuSettings around. - /// * The ConfigEntry objects are the master copy and the KoikatuSettings - /// object is a mirror. - /// * SettingsManager is responsible for keeping KoikatuSettings up to date. - /// * No other parts of code should modify KoikatuSettings. In fact, there - /// are code paths where VRGIN tries to modify it. We simply attempt - /// to avoid executing those code paths. - /// - public class SettingsManager - { - public const string SectionGeneral = "0. General"; - public const string SectionRoaming = "1. Roaming"; - public const string SectionCaress = "1. Caress"; - public const string SectionEventScenes = "1. Event scenes"; - - public static ConfigEntry EnableBoop { get; private set; } - - /// - /// Create config entries under the given ConfigFile. Also create a fresh - /// KoikatuSettings object and arrange that it be synced with the config - /// entries. - /// - /// The new KoikatuSettings object. - public static KoikatuSettings Create(ConfigFile config) - { - var settings = new KoikatuSettings(); - - var ipdScale = config.Bind(SectionGeneral, "IPD Scale", 1f, - new ConfigDescription( - "Scale of the camera. The higher, the more gigantic the player is.", - new AcceptableValueRange(0.25f, 4f))); - Tie(ipdScale, v => settings.IPDScale = v); - - var rumble = config.Bind(SectionGeneral, "Rumble", true, - "Whether or not rumble is activated."); - Tie(rumble, v => settings.Rumble = v); - - var rotationMultiplier = config.Bind(SectionGeneral, "Rotation multiplier", 1f, - new ConfigDescription( - "How quickly the the view should rotate when doing so with the controllers.", - new AcceptableValueRange(-4f, 4f), - new ConfigurationManagerAttributes { Order = -1 })); - Tie(rotationMultiplier, v => settings.RotationMultiplier = v); - - //todo missing - //var touchpadThreshold = config.Bind(sectionGeneral, "Touchpad direction threshold", 0.8f, - // new ConfigDescription( - // "Touchpad presses within this radius are considered center clicks rather than directional ones.", - // new AcceptableValueRange(0f, 1f))); - //Tie(touchpadThreshold, v => settings.TouchpadThreshold = v); - - var logLevel = config.Bind(SectionGeneral, "Log level", VRLog.LogMode.Info, - new ConfigDescription( - "The minimum severity for a message to be logged.", - null, - new ConfigurationManagerAttributes { IsAdvanced = true })); - Tie(logLevel, v => VRLog.Level = v); - - var rotationAngle = config.Bind(SectionGeneral, "Rotation angle", 45f, - new ConfigDescription( - "Angle of rotation, in degrees", - new AcceptableValueRange(0f, 180f))); - Tie(rotationAngle, v => settings.RotationAngle = v); - - var privacyScreen = config.Bind(SectionGeneral, "Privacy screen", false, - "Attempt to hide everything in the desktop mirror window"); - Tie(privacyScreen, v => settings.PrivacyScreen = v); - - var nearClipPlane = config.Bind(SectionGeneral, "Near clip plane", 0.002f, - new ConfigDescription( - "Minimum distance from camera for an object to be shown (causes visual glitches on some maps when set too small)", - new AcceptableValueRange(0.001f, 0.2f))); - Tie(nearClipPlane, v => settings.NearClipPlane = v); - - var useLegacyInputSimulator = config.Bind(SectionGeneral, "Use legacy input simulator", false, - new ConfigDescription( - "Simulate mouse and keyboard input by generating system-wide fake events", - null, - new ConfigurationManagerAttributes { IsAdvanced = true })); - Tie(useLegacyInputSimulator, v => settings.UseLegacyInputSimulator = v); - - var usingHeadPos = config.Bind(SectionRoaming, "Use head position", false, - new ConfigDescription( - "Place the camera exactly at the protagonist's head (may cause motion sickness). If disabled, use a fixed height from the floor.", - null, - new ConfigurationManagerAttributes { Order = -1 })); - Tie(usingHeadPos, v => settings.UsingHeadPos = v); - - var standingCameraPos = config.Bind(SectionRoaming, "Camera height", 1.5f, - new ConfigDescription( - "Default camera height for when not using the head position.", - new AcceptableValueRange(0.2f, 3f), - new ConfigurationManagerAttributes { Order = -2 })); - Tie(standingCameraPos, v => settings.StandingCameraPos = v); - - var crouchingCameraPos = config.Bind(SectionRoaming, "Crouching camera height", 0.7f, - new ConfigDescription( - "Crouching camera height for when not using the head position", - new AcceptableValueRange(0.2f, 3f), - new ConfigurationManagerAttributes { Order = -2 })); - Tie(crouchingCameraPos, v => settings.CrouchingCameraPos = v); - - var crouchByHMDPos = config.Bind(SectionRoaming, "Crouch by HMD position", true, - new ConfigDescription( - "Crouch when the HMD position is below some threshold.", - null, - new ConfigurationManagerAttributes { Order = -3 })); - Tie(crouchByHMDPos, v => settings.CrouchByHMDPos = v); - - var crouchThreshold = config.Bind(SectionRoaming, "Crouch height", 0.9f, - new ConfigDescription( - "Trigger crouching when the camera is below this height", - new AcceptableValueRange(0.05f, 3f), - new ConfigurationManagerAttributes { Order = -4 })); - Tie(crouchThreshold, v => settings.CrouchThreshold = v); - - var standUpThreshold = config.Bind(SectionRoaming, "Stand up height", 1f, - new ConfigDescription( - "End crouching when the camera is above this height", - new AcceptableValueRange(0.05f, 3f), - new ConfigurationManagerAttributes { Order = -4 })); - Tie(standUpThreshold, v => settings.StandUpThreshold = v); - - var teleportWithProtagonist = config.Bind(SectionRoaming, "Teleport with protagonist", true, - "When teleporting, the protagonist also teleports"); - Tie(teleportWithProtagonist, v => settings.TeleportWithProtagonist = v); - - var optimizeHInsideRoaming = config.Bind(SectionRoaming, "Aggressive performance optimizations", true, - "Improve framerate and reduce stutter in H and Talk scenes inside Roaming. May cause visual glitches."); - Tie(optimizeHInsideRoaming, v => settings.OptimizeHInsideRoaming = v); - - var automaticTouching = config.Bind(SectionCaress, "Automatic touching", false, - "Touching the female's body with controllers triggers reaction"); - Tie(automaticTouching, v => settings.AutomaticTouching = v); - - var automaticKissing = config.Bind(SectionCaress, "Automatic kissing", true, - "Initiate kissing by moving your head"); - Tie(automaticKissing, v => settings.AutomaticKissing = v); - - var automaticLicking = config.Bind(SectionCaress, "Automatic licking", true, - "Initiate licking by moving your head"); - Tie(automaticLicking, v => settings.AutomaticLicking = v); - - var automaticTouchingByHmd = config.Bind(SectionCaress, "Kiss body", true, - "Touch the female's body by moving your head"); - Tie(automaticTouchingByHmd, v => settings.AutomaticTouchingByHmd = v); - - var firstPersonADV = config.Bind(SectionEventScenes, "First person", true, - "Prefer first person view in event scenes"); - Tie(firstPersonADV, v => settings.FirstPersonADV = v); - - EnableBoop = config.Bind(SectionGeneral, "Enable Boop", true, - "Adds colliders to the controllers so you can boop things.\nGame restart required for change to take effect."); - - KeySetsConfig keySetsConfig = null; - - void updateKeySets() - { - keySetsConfig.CurrentKeySets(out var keySets, out var hKeySets); - settings.KeySets = keySets; - settings.HKeySets = hKeySets; - } - - keySetsConfig = new KeySetsConfig(config, updateKeySets); - updateKeySets(); - - POVConfig pOVConfig = new POVConfig(config, settings); - - // Fixed settings - settings.ApplyEffects = false; // We manage effects ourselves. - - return settings; - } - - private static void Tie(ConfigEntry entry, Action set) - { - set(entry.Value); - entry.SettingChanged += (_, _1) => set(entry.Value); - } - } - - internal class KeySetsConfig - { - private readonly KeySetConfig _main; - private readonly KeySetConfig _main1; - private readonly KeySetConfig _h; - private readonly KeySetConfig _h1; - - private readonly ConfigEntry _useMain1; - private readonly ConfigEntry _useH1; - - public KeySetsConfig(ConfigFile config, Action onUpdate) - { - const string sectionP = "2. Non-H button assignments (primary)"; - const string sectionS = "2. Non-H button assignments (secondary)"; - const string sectionHP = "3. H button assignments (primary)"; - const string sectionHS = "3. H button assignments (secondary)"; - - _main = new KeySetConfig(config, onUpdate, sectionP, false, false); - _main1 = new KeySetConfig(config, onUpdate, sectionS, false, true); - _h = new KeySetConfig(config, onUpdate, sectionHP, true, false); - _h1 = new KeySetConfig(config, onUpdate, sectionHS, true, true); - - _useMain1 = config.Bind(sectionS, "Use secondary assignments", false, - new ConfigDescription("", null, new ConfigurationManagerAttributes { IsAdvanced = true })); - _useMain1.SettingChanged += (_, _1) => onUpdate(); - _useH1 = config.Bind(sectionHS, "Use secondary assignments", false, - new ConfigDescription("", null, new ConfigurationManagerAttributes { IsAdvanced = true })); - _useH1.SettingChanged += (_, _1) => onUpdate(); - } - - public void CurrentKeySets(out List keySets, out List hKeySets) - { - keySets = new List(); - keySets.Add(_main.CurrentKeySet()); - if (_useMain1.Value) keySets.Add(_main1.CurrentKeySet()); - - hKeySets = new List(); - hKeySets.Add(_h.CurrentKeySet()); - if (_useH1.Value) hKeySets.Add(_h1.CurrentKeySet()); - } - } - - internal class KeySetConfig - { - private readonly ConfigEntry _trigger; - private readonly ConfigEntry _grip; - private readonly ConfigEntry _up; - private readonly ConfigEntry _down; - private readonly ConfigEntry _right; - private readonly ConfigEntry _left; - private readonly ConfigEntry _center; - - public KeySetConfig(ConfigFile config, Action onUpdate, string section, bool isH, bool advanced) - { - var order = -1; - - ConfigEntry create(string name, AssignableFunction def) - { - var entry = config.Bind(section, name, def, new ConfigDescription("", null, - new ConfigurationManagerAttributes { Order = order, IsAdvanced = advanced })); - entry.SettingChanged += (_, _1) => onUpdate(); - order -= 1; - return entry; - } - - if (isH) - { - _trigger = create("Trigger", AssignableFunction.LBUTTON); - _grip = create("Grip", AssignableFunction.GRAB); - _up = create("Up", AssignableFunction.SCROLLUP); - _down = create("Down", AssignableFunction.SCROLLDOWN); - _left = create("Left", AssignableFunction.NONE); - _right = create("Right", AssignableFunction.RBUTTON); - _center = create("Center", AssignableFunction.MBUTTON); - } - else - { - _trigger = create("Trigger", AssignableFunction.WALK); - _grip = create("Grip", AssignableFunction.GRAB); - _up = create("Up", AssignableFunction.F3); - _down = create("Down", AssignableFunction.F1); - _left = create("Left", AssignableFunction.LROTATION); - _right = create("Right", AssignableFunction.RROTATION); - _center = create("Center", AssignableFunction.RBUTTON); - } - } - - public KeySet CurrentKeySet() - { - return new KeySet( - _trigger.Value, - _grip.Value, - _up.Value, - _down.Value, - _right.Value, - _left.Value, - _center.Value); - } - } - - public class POVConfig - { - private const string sectionPOV = "4. POV between characters (Hand Tool in free H)"; - public static ConfigEntry switchPOVModeKey { get; private set; } - public static ConfigEntry POVKey { get; private set; } - public static ConfigEntry targetGender { get; private set; } - - public enum POVKeyList - { - [Description("VR Trigger Button")] // k_EButton_SteamVR_Trigger - VR_TRIGGER = KeyCode.None, - [Description("VR Button2(A/X).Depend on device")] // k_EButton_A - VR_BUTTON2 = 1, // Any number, not in KeyCode - [Description("Left mouse(=VR Trigger Button)")] - LBUTTON = KeyCode.Mouse0, - [Description("Right mouse(=VR Right Touchpad Click)")] - RBUTTON = KeyCode.Mouse1, - [Description("Middle mouse(=VR Touchpad Click)")] - MBUTTON = KeyCode.Mouse2, - [Description("Keyboard A")] - A = KeyCode.A, - [Description("Keyboard B")] - B = KeyCode.B, - [Description("Keyboard C")] - C = KeyCode.C, - [Description("Keyboard D")] - D = KeyCode.D, - [Description("Keyboard E")] - E = KeyCode.E, - [Description("Keyboard F")] - F = KeyCode.F, - [Description("Keyboard G")] - G = KeyCode.G, - [Description("Keyboard H")] - H = KeyCode.H, - [Description("Keyboard I")] - I = KeyCode.I, - [Description("Keyboard J")] - J = KeyCode.J, - [Description("Keyboard K")] - K = KeyCode.K, - [Description("Keyboard L")] - L = KeyCode.L, - [Description("Keyboard M")] - M = KeyCode.M, - [Description("Keyboard N")] - N = KeyCode.N, - [Description("Keyboard O")] - O = KeyCode.O, - [Description("Keyboard P")] - P = KeyCode.P, - [Description("Keyboard Q")] - Q = KeyCode.Q, - [Description("Keyboard R")] - R = KeyCode.R, - [Description("Keyboard S")] - S = KeyCode.S, - [Description("Keyboard T")] - T = KeyCode.T, - [Description("Keyboard U")] - U = KeyCode.U, - [Description("Keyboard V")] - V = KeyCode.V, - [Description("Keyboard W")] - W = KeyCode.W, - [Description("Keyboard X")] - X = KeyCode.X, - [Description("Keyboard Y")] - Y = KeyCode.Y, - [Description("Keyboard Z")] - Z = KeyCode.Z, - [Description("Keyboard 0")] - ALPHA0 = KeyCode.Alpha0, - [Description("Keyboard 1")] - ALPHA1 = KeyCode.Alpha1, - [Description("Keyboard 2")] - ALPHA2 = KeyCode.Alpha2, - [Description("Keyboard 3")] - ALPHA3 = KeyCode.Alpha3, - [Description("Keyboard 4")] - ALPHA4 = KeyCode.Alpha4, - [Description("Keyboard 5")] - ALPHA5 = KeyCode.Alpha5, - [Description("Keyboard 6")] - ALPHA6 = KeyCode.Alpha6, - [Description("Keyboard 7")] - ALPHA7 = KeyCode.Alpha7, - [Description("Keyboard 8")] - ALPHA8 = KeyCode.Alpha8, - [Description("Keyboard 9")] - ALPHA9 = KeyCode.Alpha9, - [Description("Keyboard Numpad 0")] - NUMPAD0 = KeyCode.Keypad0, - [Description("Keyboard Numpad 1")] - NUMPAD1 = KeyCode.Keypad1, - [Description("Keyboard Numpad 2")] - NUMPAD2 = KeyCode.Keypad2, - [Description("Keyboard Numpad 3")] - NUMPAD3 = KeyCode.Keypad3, - [Description("Keyboard Numpad 4")] - NUMPAD4 = KeyCode.Keypad4, - [Description("Keyboard Numpad 5")] - NUMPAD5 = KeyCode.Keypad5, - [Description("Keyboard Numpad 6")] - NUMPAD6 = KeyCode.Keypad6, - [Description("Keyboard Numpad 7")] - NUMPAD7 = KeyCode.Keypad7, - [Description("Keyboard Numpad 8")] - NUMPAD8 = KeyCode.Keypad8, - [Description("Keyboard Numpad 9")] - NUMPAD9 = KeyCode.Keypad9, - [Description("Keyboard F1")] - F1 = KeyCode.F1, - [Description("Keyboard F2")] - F2 = KeyCode.F2, - [Description("Keyboard F3")] - F3 = KeyCode.F3, - [Description("Keyboard F4")] - F4 = KeyCode.F4, - [Description("Keyboard F5")] - F5 = KeyCode.F5, - [Description("Keyboard F6")] - F6 = KeyCode.F6, - [Description("Keyboard F7")] - F7 = KeyCode.F7, - [Description("Keyboard F8")] - F8 = KeyCode.F8, - [Description("Keyboard F9")] - F9 = KeyCode.F9, - [Description("Keyboard F10")] - F10 = KeyCode.F10, - [Description("Keyboard F11")] - F11 = KeyCode.F11, - [Description("Keyboard F12")] - F12 = KeyCode.F12, - [Description("Keyboard Tab")] - TAB = KeyCode.Tab, - [Description("Keyboard Enter")] - RETURN = KeyCode.Return, - [Description("Keyboard Esc")] - ESC = KeyCode.Escape, - [Description("Keyboard Space")] - SPACE = KeyCode.Space, - [Description("Keyboard Home")] - HOME = KeyCode.Home, - [Description("Keyboard End")] - END = KeyCode.End, - [Description("Keyboard arrow left")] - LEFT = KeyCode.LeftArrow, - [Description("Keyboard arrow up")] - UP = KeyCode.UpArrow, - [Description("Keyboard arrow right")] - RIGHT = KeyCode.RightArrow, - [Description("Keyboard arrow down")] - DOWN = KeyCode.DownArrow, - [Description("Keyboard Ins")] - INSERT = KeyCode.Insert, - [Description("Keyboard Del")] - DELETE = KeyCode.Delete, - [Description("Keyboard Page Up")] - PAGEUP = KeyCode.PageUp, - [Description("Keyboard Page Down")] - PAGEDOWN = KeyCode.PageDown, - [Description("Keyboard Backspace")] - BACK = KeyCode.Backspace, - [Description("Keyboard Left Shift")] - LEFTSHIFT = KeyCode.LeftShift, - [Description("Keyboard Right Shift")] - RIGHTSHIFT = KeyCode.RightShift, - [Description("Keyboard Left Ctrl")] - LEFTCTRL = KeyCode.LeftControl, - [Description("Keyboard Right Ctrl")] - RIGHTCTRL = KeyCode.RightControl, - [Description("Keyboard Left Alt")] - LEFTALT = KeyCode.LeftAlt, - [Description("Keyboard Right Alt")] - RIGHTALT = KeyCode.RightAlt - } - public enum Gender - { - Male = 0, // must same as Koikatsu Male ChaControl.sex = 0 - Female = 1, // must same as Koikatsu Female ChaControl.sex = 1 - All - } - public POVConfig(ConfigFile config, KoikatuSettings settings) - { - var enablePOV = config.Bind(sectionPOV, "POV", true, - "Switch POV between characters in free H scenes (only works in Hand Tool)"); - Tie(enablePOV, v => settings.EnablePOV = v); - - switchPOVModeKey = config.Bind(sectionPOV, "Switch POV Mode (1~3)", POVKeyList.Y, - new ConfigDescription( - "Use VR Button/Key to switch POV between characters. " + - "Three Modes:\r\n" + - "1: Camera is fixed at character's eye.\r\n" + - "2: Camera moves when character's head moves. (Default)\r\n" + - "3: Camera doesn't move when character's head moves (jump to character).", - null, - new ConfigurationManagerAttributes { Order = -1 })); - - POVKey = config.Bind(sectionPOV, "Switch POV Camera", POVKeyList.VR_TRIGGER, - new ConfigDescription( - "Use this hotkey to switch the POV camera between target characters.", - null, - new ConfigurationManagerAttributes { Order = -2 })); - - targetGender = config.Bind(sectionPOV, "Target", Gender.All, - new ConfigDescription( - "The Gender of the POV targets.", - null, - new ConfigurationManagerAttributes { Order = -3 })); - } - private static void Tie(ConfigEntry entry, Action set) - { - set(entry.Value); - entry.SettingChanged += (_, _1) => set(entry.Value); - } - } -} diff --git a/MainGameVR/VRAssetBundles/.gitignore b/MainGameVR/VRAssetBundles/.gitignore deleted file mode 100644 index 16aeaaf..0000000 --- a/MainGameVR/VRAssetBundles/.gitignore +++ /dev/null @@ -1,74 +0,0 @@ -# This .gitignore file should be placed at the root of your Unity project directory -# -# Get latest from https://github.com/github/gitignore/blob/main/Unity.gitignore -# -/[Ll]ibrary/ -/[Tt]emp/ -/[Oo]bj/ -/[Bb]uild/ -/[Bb]uilds/ -/[Ll]ogs/ -/[Uu]ser[Ss]ettings/ - -# MemoryCaptures can get excessive in size. -# They also could contain extremely sensitive data -/[Mm]emoryCaptures/ - -# Recordings can get excessive in size -/[Rr]ecordings/ - -# Uncomment this line if you wish to ignore the asset store tools plugin -# /[Aa]ssets/AssetStoreTools* - -# Autogenerated Jetbrains Rider plugin -/[Aa]ssets/Plugins/Editor/JetBrains* - -# Visual Studio cache directory -.vs/ - -# Gradle cache directory -.gradle/ - -# Autogenerated VS/MD/Consulo solution and project files -ExportedObj/ -.consulo/ -*.csproj -*.unityproj -*.sln -*.suo -*.tmp -*.user -*.userprefs -*.pidb -*.booproj -*.svd -*.pdb -*.mdb -*.opendb -*.VC.db - -# Unity3D generated meta files -*.pidb.meta -*.pdb.meta -*.mdb.meta - -# Unity3D generated file on crash reports -sysinfo.txt - -# Builds -*.apk -*.aab -*.unitypackage -*.app - -# Crashlytics generated file -crashlytics-build.properties - -# Packed Addressables -/[Aa]ssets/[Aa]ddressable[Aa]ssets[Dd]ata/*/*.bin* - -# Temporary auto-generated Android Assets -/[Aa]ssets/[Ss]treamingAssets/aa.meta -/[Aa]ssets/[Ss]treamingAssets/aa/* - -/Assets/AssetBundles diff --git a/MainGameVR/VRAssetBundles/Assets/AssetBundles.meta b/MainGameVR/VRAssetBundles/Assets/AssetBundles.meta deleted file mode 100644 index e4bbe6c..0000000 --- a/MainGameVR/VRAssetBundles/Assets/AssetBundles.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: a4bba4eb05e596647b49ba9213988c37 -folderAsset: yes -timeCreated: 1694326413 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/MainGameVR/VRAssetBundles/Assets/Editor.meta b/MainGameVR/VRAssetBundles/Assets/Editor.meta deleted file mode 100644 index aa3003c..0000000 --- a/MainGameVR/VRAssetBundles/Assets/Editor.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 93e6af1384dad6146bd3234cb70373cf -folderAsset: yes -timeCreated: 1694324809 -licenseType: Free -DefaultImporter: - userData: - assetBundleName: - assetBundleVariant: diff --git a/MainGameVR/VRAssetBundles/Assets/Editor/BuildAssetBundles.cs b/MainGameVR/VRAssetBundles/Assets/Editor/BuildAssetBundles.cs deleted file mode 100644 index 49aa4f5..0000000 --- a/MainGameVR/VRAssetBundles/Assets/Editor/BuildAssetBundles.cs +++ /dev/null @@ -1,18 +0,0 @@ -using UnityEditor; -using System.IO; - -public class CreateAssetBundles -{ - [MenuItem("Assets/Build AssetBundles")] - static void BuildAllAssetBundles() - { - string assetBundleDirectory = "Assets/AssetBundles"; - if (!Directory.Exists(assetBundleDirectory)) - { - Directory.CreateDirectory(assetBundleDirectory); - } - BuildPipeline.BuildAssetBundles(assetBundleDirectory, - BuildAssetBundleOptions.None, - BuildTarget.StandaloneWindows); - } -} \ No newline at end of file diff --git a/MainGameVR/VRAssetBundles/Assets/Editor/BuildAssetBundles.cs.meta b/MainGameVR/VRAssetBundles/Assets/Editor/BuildAssetBundles.cs.meta deleted file mode 100644 index e9f98a8..0000000 --- a/MainGameVR/VRAssetBundles/Assets/Editor/BuildAssetBundles.cs.meta +++ /dev/null @@ -1,12 +0,0 @@ -fileFormatVersion: 2 -guid: 299b82424bb52b749bbd1df7ab5a8bd2 -timeCreated: 1694324825 -licenseType: Free -MonoImporter: - serializedVersion: 2 - defaultReferences: [] - executionOrder: 0 - icon: {instanceID: 0} - userData: - assetBundleName: - assetBundleVariant: diff --git a/MainGameVR/VRAssetBundles/Assets/MirrorReflection.shader b/MainGameVR/VRAssetBundles/Assets/MirrorReflection.shader deleted file mode 100644 index dcc99d5..0000000 --- a/MainGameVR/VRAssetBundles/Assets/MirrorReflection.shader +++ /dev/null @@ -1,95 +0,0 @@ -// Upgrade NOTE: replaced 'UNITY_INSTANCE_ID' with 'UNITY_VERTEX_INPUT_INSTANCE_ID' -// Upgrade NOTE: replaced 'mul(UNITY_MATRIX_MVP,*)' with 'UnityObjectToClipPos(*)' - -Shader "FX/MirrorReflection" -{ - Properties - { - _MainTex("Base (RGB)", 2D) = "white" {} - [HideInInspector] _LeftReflectionTex("", 2D) = "white" {} - [HideInInspector] _RightReflectionTex("", 2D) = "white" {} - } - SubShader - { - Tags{ "RenderType" = "Opaque" } - LOD 100 - - Pass{ - CGPROGRAM -#pragma vertex vert -#pragma fragment frag -#include "UnityCG.cginc" - - float4 _MainTex_ST; - sampler2D _MainTex; - sampler2D _LeftReflectionTex; - sampler2D _RightReflectionTex; - - struct VertexInput - { - float4 pos : POSITION; - float2 uv: TEXCOORD0; - UNITY_VERTEX_INPUT_INSTANCE_ID - }; - - struct v2f - { - float2 uv : TEXCOORD0; - float4 refl : TEXCOORD1; - float4 pos : SV_POSITION; - UNITY_VERTEX_INPUT_INSTANCE_ID - UNITY_VERTEX_OUTPUT_STEREO - }; - - // Same as standard ComputeScreenPos() except that it doesn't call TransformStereoScreenSpaceTex() - // when stereo instance rendering is enabled. This is important because we need to be able to sample - // from the entire reflection texture, and not just the left/right half, which is what the normal - // ComputeScreenPos() would get us. - inline float4 ComputeScreenPosIgnoreStereo(float4 pos) { - float4 o = pos * 0.5f; -#if defined(UNITY_HALF_TEXEL_OFFSET) - o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w * _ScreenParams.zw; -#else - o.xy = float2(o.x, o.y*_ProjectionParams.x) + o.w; -#endif - o.zw = pos.zw; - return o; - } - - v2f vert(VertexInput v) - { - UNITY_SETUP_INSTANCE_ID(v); - v2f o; - UNITY_INITIALIZE_OUTPUT(v2f, o); - UNITY_TRANSFER_INSTANCE_ID(v, o); - UNITY_INITIALIZE_VERTEX_OUTPUT_STEREO(o); - - o.pos = UnityObjectToClipPos(v.pos); - o.uv = TRANSFORM_TEX(v.uv, _MainTex); - o.refl = ComputeScreenPosIgnoreStereo(o.pos); - return o; - } - - fixed4 frag(v2f i) : SV_Target - { - UNITY_SETUP_INSTANCE_ID(i) - fixed4 tex = tex2D(_MainTex, i.uv); - fixed4 refl; - - bool leftEye = unity_StereoEyeIndex == 0; - if (leftEye) - { - refl = tex2Dproj(_LeftReflectionTex, UNITY_PROJ_COORD(i.refl)); - } - else - { - refl = tex2Dproj(_RightReflectionTex, UNITY_PROJ_COORD(i.refl)); - } - return tex * refl; - } - ENDCG - } - } - - Fallback "VertexLit" // For the shadow caster -} \ No newline at end of file diff --git a/MainGameVR/VRAssetBundles/Assets/MirrorReflection.shader.meta b/MainGameVR/VRAssetBundles/Assets/MirrorReflection.shader.meta deleted file mode 100644 index 0783a11..0000000 --- a/MainGameVR/VRAssetBundles/Assets/MirrorReflection.shader.meta +++ /dev/null @@ -1,9 +0,0 @@ -fileFormatVersion: 2 -guid: 6ae78fa632a56c54ea116450a0709670 -timeCreated: 1694324919 -licenseType: Free -ShaderImporter: - defaultTextures: [] - userData: - assetBundleName: mirror-shader - assetBundleVariant: diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/AudioManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/AudioManager.asset deleted file mode 100644 index 2c4f5a1..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/AudioManager.asset +++ /dev/null @@ -1,16 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!11 &1 -AudioManager: - m_ObjectHideFlags: 0 - m_Volume: 1 - Rolloff Scale: 1 - Doppler Factor: 1 - Default Speaker Mode: 2 - m_SampleRate: 0 - m_DSPBufferSize: 0 - m_VirtualVoiceCount: 512 - m_RealVoiceCount: 32 - m_SpatializerPlugin: - m_DisableAudio: 0 - m_VirtualizeEffects: 1 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/ClusterInputManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/ClusterInputManager.asset deleted file mode 100644 index e7886b2..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/ClusterInputManager.asset +++ /dev/null @@ -1,6 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!236 &1 -ClusterInputManager: - m_ObjectHideFlags: 0 - m_Inputs: [] diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/DynamicsManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/DynamicsManager.asset deleted file mode 100644 index 6be6910..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/DynamicsManager.asset +++ /dev/null @@ -1,18 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!55 &1 -PhysicsManager: - m_ObjectHideFlags: 0 - serializedVersion: 3 - m_Gravity: {x: 0, y: -9.81, z: 0} - m_DefaultMaterial: {fileID: 0} - m_BounceThreshold: 2 - m_SleepThreshold: 0.005 - m_DefaultContactOffset: 0.01 - m_DefaultSolverIterations: 6 - m_DefaultSolverVelocityIterations: 1 - m_QueriesHitBackfaces: 0 - m_QueriesHitTriggers: 1 - m_EnableAdaptiveForce: 0 - m_EnablePCM: 1 - m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/EditorBuildSettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/EditorBuildSettings.asset deleted file mode 100644 index 6dc24f7..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/EditorBuildSettings.asset +++ /dev/null @@ -1,7 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!1045 &1 -EditorBuildSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Scenes: [] diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/EditorSettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/EditorSettings.asset deleted file mode 100644 index 7f72f1b..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/EditorSettings.asset +++ /dev/null @@ -1,14 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!159 &1 -EditorSettings: - m_ObjectHideFlags: 0 - serializedVersion: 3 - m_ExternalVersionControlSupport: Visible Meta Files - m_SerializationMode: 2 - m_DefaultBehaviorMode: 0 - m_SpritePackerMode: 0 - m_SpritePackerPaddingPower: 1 - m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd - m_ProjectGenerationRootNamespace: - m_UserGeneratedProjectSuffix: diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/GraphicsSettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/GraphicsSettings.asset deleted file mode 100644 index d74737e..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/GraphicsSettings.asset +++ /dev/null @@ -1,62 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!30 &1 -GraphicsSettings: - m_ObjectHideFlags: 0 - serializedVersion: 12 - m_Deferred: - m_Mode: 1 - m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} - m_DeferredReflections: - m_Mode: 1 - m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} - m_ScreenSpaceShadows: - m_Mode: 1 - m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} - m_LegacyDeferred: - m_Mode: 1 - m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} - m_DepthNormals: - m_Mode: 1 - m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} - m_MotionVectors: - m_Mode: 1 - m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} - m_LightHalo: - m_Mode: 1 - m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} - m_LensFlare: - m_Mode: 1 - m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} - m_AlwaysIncludedShaders: - - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} - - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} - - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} - - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} - - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} - - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} - - {fileID: 16000, guid: 0000000000000000f000000000000000, type: 0} - m_PreloadedShaders: [] - m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, - type: 0} - m_CustomRenderPipeline: {fileID: 0} - m_TransparencySortMode: 0 - m_TransparencySortAxis: {x: 0, y: 0, z: 1} - m_DefaultRenderingPath: 1 - m_DefaultMobileRenderingPath: 1 - m_TierSettings: [] - m_LightmapStripping: 0 - m_FogStripping: 0 - m_InstancingStripping: 0 - m_LightmapKeepPlain: 1 - m_LightmapKeepDirCombined: 1 - m_LightmapKeepDynamicPlain: 1 - m_LightmapKeepDynamicDirCombined: 1 - m_LightmapKeepShadowMask: 1 - m_LightmapKeepSubtractive: 1 - m_FogKeepLinear: 1 - m_FogKeepExp: 1 - m_FogKeepExp2: 1 - m_AlbedoSwatchInfos: [] - m_LightsUseLinearIntensity: 0 - m_LightsUseColorTemperature: 0 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/InputManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/InputManager.asset deleted file mode 100644 index 17c8f53..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/InputManager.asset +++ /dev/null @@ -1,295 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!13 &1 -InputManager: - m_ObjectHideFlags: 0 - serializedVersion: 2 - m_Axes: - - serializedVersion: 3 - m_Name: Horizontal - descriptiveName: - descriptiveNegativeName: - negativeButton: left - positiveButton: right - altNegativeButton: a - altPositiveButton: d - gravity: 3 - dead: 0.001 - sensitivity: 3 - snap: 1 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Vertical - descriptiveName: - descriptiveNegativeName: - negativeButton: down - positiveButton: up - altNegativeButton: s - altPositiveButton: w - gravity: 3 - dead: 0.001 - sensitivity: 3 - snap: 1 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Fire1 - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: left ctrl - altNegativeButton: - altPositiveButton: mouse 0 - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Fire2 - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: left alt - altNegativeButton: - altPositiveButton: mouse 1 - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Fire3 - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: left shift - altNegativeButton: - altPositiveButton: mouse 2 - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Jump - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: space - altNegativeButton: - altPositiveButton: - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Mouse X - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: - altNegativeButton: - altPositiveButton: - gravity: 0 - dead: 0 - sensitivity: 0.1 - snap: 0 - invert: 0 - type: 1 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Mouse Y - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: - altNegativeButton: - altPositiveButton: - gravity: 0 - dead: 0 - sensitivity: 0.1 - snap: 0 - invert: 0 - type: 1 - axis: 1 - joyNum: 0 - - serializedVersion: 3 - m_Name: Mouse ScrollWheel - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: - altNegativeButton: - altPositiveButton: - gravity: 0 - dead: 0 - sensitivity: 0.1 - snap: 0 - invert: 0 - type: 1 - axis: 2 - joyNum: 0 - - serializedVersion: 3 - m_Name: Horizontal - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: - altNegativeButton: - altPositiveButton: - gravity: 0 - dead: 0.19 - sensitivity: 1 - snap: 0 - invert: 0 - type: 2 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Vertical - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: - altNegativeButton: - altPositiveButton: - gravity: 0 - dead: 0.19 - sensitivity: 1 - snap: 0 - invert: 1 - type: 2 - axis: 1 - joyNum: 0 - - serializedVersion: 3 - m_Name: Fire1 - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: joystick button 0 - altNegativeButton: - altPositiveButton: - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Fire2 - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: joystick button 1 - altNegativeButton: - altPositiveButton: - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Fire3 - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: joystick button 2 - altNegativeButton: - altPositiveButton: - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Jump - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: joystick button 3 - altNegativeButton: - altPositiveButton: - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Submit - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: return - altNegativeButton: - altPositiveButton: joystick button 0 - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Submit - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: enter - altNegativeButton: - altPositiveButton: space - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 - - serializedVersion: 3 - m_Name: Cancel - descriptiveName: - descriptiveNegativeName: - negativeButton: - positiveButton: escape - altNegativeButton: - altPositiveButton: joystick button 1 - gravity: 1000 - dead: 0.001 - sensitivity: 1000 - snap: 0 - invert: 0 - type: 0 - axis: 0 - joyNum: 0 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/NavMeshAreas.asset b/MainGameVR/VRAssetBundles/ProjectSettings/NavMeshAreas.asset deleted file mode 100644 index 6dd520f..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/NavMeshAreas.asset +++ /dev/null @@ -1,89 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!126 &1 -NavMeshProjectSettings: - m_ObjectHideFlags: 0 - serializedVersion: 2 - areas: - - name: Walkable - cost: 1 - - name: Not Walkable - cost: 1 - - name: Jump - cost: 2 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - - name: - cost: 1 - m_LastAgentTypeID: -887442657 - m_Settings: - - serializedVersion: 2 - agentTypeID: 0 - agentRadius: 0.5 - agentHeight: 2 - agentSlope: 45 - agentClimb: 0.75 - ledgeDropHeight: 0 - maxJumpAcrossDistance: 0 - minRegionArea: 2 - manualCellSize: 0 - cellSize: 0.16666667 - manualTileSize: 0 - tileSize: 256 - accuratePlacement: 0 - m_SettingNames: - - Humanoid diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/NetworkManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/NetworkManager.asset deleted file mode 100644 index 5dc6a83..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/NetworkManager.asset +++ /dev/null @@ -1,8 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!149 &1 -NetworkManager: - m_ObjectHideFlags: 0 - m_DebugLevel: 0 - m_Sendrate: 15 - m_AssetToPrefab: {} diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/Physics2DSettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/Physics2DSettings.asset deleted file mode 100644 index 5f6ffab..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/Physics2DSettings.asset +++ /dev/null @@ -1,35 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!19 &1 -Physics2DSettings: - m_ObjectHideFlags: 0 - serializedVersion: 3 - m_Gravity: {x: 0, y: -9.81} - m_DefaultMaterial: {fileID: 0} - m_VelocityIterations: 8 - m_PositionIterations: 3 - m_VelocityThreshold: 1 - m_MaxLinearCorrection: 0.2 - m_MaxAngularCorrection: 8 - m_MaxTranslationSpeed: 100 - m_MaxRotationSpeed: 360 - m_BaumgarteScale: 0.2 - m_BaumgarteTimeOfImpactScale: 0.75 - m_TimeToSleep: 0.5 - m_LinearSleepTolerance: 0.01 - m_AngularSleepTolerance: 2 - m_DefaultContactOffset: 0.01 - m_QueriesHitTriggers: 1 - m_QueriesStartInColliders: 1 - m_ChangeStopsCallbacks: 0 - m_CallbacksOnDisable: 1 - m_AlwaysShowColliders: 0 - m_ShowColliderSleep: 1 - m_ShowColliderContacts: 0 - m_ShowColliderAABB: 0 - m_ContactArrowScale: 0.2 - m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} - m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} - m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} - m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} - m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/ProjectSettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/ProjectSettings.asset deleted file mode 100644 index ca84efd..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/ProjectSettings.asset +++ /dev/null @@ -1,589 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!129 &1 -PlayerSettings: - m_ObjectHideFlags: 0 - serializedVersion: 11 - productGUID: eed6064fe0a601a4e99e68470b5c3824 - AndroidProfiler: 0 - defaultScreenOrientation: 4 - targetDevice: 2 - useOnDemandResources: 0 - accelerometerFrequency: 60 - companyName: DefaultCompany - productName: VRAssetBundles - defaultCursor: {fileID: 0} - cursorHotspot: {x: 0, y: 0} - m_SplashScreenBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21176471, a: 1} - m_ShowUnitySplashScreen: 1 - m_ShowUnitySplashLogo: 1 - m_SplashScreenOverlayOpacity: 1 - m_SplashScreenAnimation: 1 - m_SplashScreenLogoStyle: 1 - m_SplashScreenDrawMode: 0 - m_SplashScreenBackgroundAnimationZoom: 1 - m_SplashScreenLogoAnimationZoom: 1 - m_SplashScreenBackgroundLandscapeAspect: 1 - m_SplashScreenBackgroundPortraitAspect: 1 - m_SplashScreenBackgroundLandscapeUvs: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - m_SplashScreenBackgroundPortraitUvs: - serializedVersion: 2 - x: 0 - y: 0 - width: 1 - height: 1 - m_SplashScreenLogos: [] - m_SplashScreenBackgroundLandscape: {fileID: 0} - m_SplashScreenBackgroundPortrait: {fileID: 0} - m_VirtualRealitySplashScreen: {fileID: 0} - m_HolographicTrackingLossScreen: {fileID: 0} - defaultScreenWidth: 1024 - defaultScreenHeight: 768 - defaultScreenWidthWeb: 960 - defaultScreenHeightWeb: 600 - m_StereoRenderingPath: 0 - m_ActiveColorSpace: 0 - m_MTRendering: 1 - m_MobileMTRendering: 0 - m_StackTraceTypes: 010000000100000001000000010000000100000001000000 - iosShowActivityIndicatorOnLoading: -1 - androidShowActivityIndicatorOnLoading: -1 - tizenShowActivityIndicatorOnLoading: -1 - iosAppInBackgroundBehavior: 0 - displayResolutionDialog: 1 - iosAllowHTTPDownload: 1 - allowedAutorotateToPortrait: 1 - allowedAutorotateToPortraitUpsideDown: 1 - allowedAutorotateToLandscapeRight: 1 - allowedAutorotateToLandscapeLeft: 1 - useOSAutorotation: 1 - use32BitDisplayBuffer: 1 - disableDepthAndStencilBuffers: 0 - defaultIsFullScreen: 1 - defaultIsNativeResolution: 1 - runInBackground: 0 - captureSingleScreen: 0 - muteOtherAudioSources: 0 - Prepare IOS For Recording: 0 - submitAnalytics: 1 - usePlayerLog: 1 - bakeCollisionMeshes: 0 - forceSingleInstance: 0 - resizableWindow: 0 - useMacAppStoreValidation: 0 - macAppStoreCategory: public.app-category.games - gpuSkinning: 0 - graphicsJobs: 0 - xboxPIXTextureCapture: 0 - xboxEnableAvatar: 0 - xboxEnableKinect: 0 - xboxEnableKinectAutoTracking: 0 - xboxEnableFitness: 0 - visibleInBackground: 0 - allowFullscreenSwitch: 1 - graphicsJobMode: 0 - macFullscreenMode: 2 - d3d9FullscreenMode: 1 - d3d11FullscreenMode: 1 - xboxSpeechDB: 0 - xboxEnableHeadOrientation: 0 - xboxEnableGuest: 0 - xboxEnablePIXSampling: 0 - n3dsDisableStereoscopicView: 0 - n3dsEnableSharedListOpt: 1 - n3dsEnableVSync: 0 - ignoreAlphaClear: 0 - xboxOneResolution: 0 - xboxOneMonoLoggingLevel: 0 - xboxOneLoggingLevel: 1 - videoMemoryForVertexBuffers: 0 - psp2PowerMode: 0 - psp2AcquireBGM: 1 - wiiUTVResolution: 0 - wiiUGamePadMSAA: 1 - wiiUSupportsNunchuk: 0 - wiiUSupportsClassicController: 0 - wiiUSupportsBalanceBoard: 0 - wiiUSupportsMotionPlus: 0 - wiiUSupportsProController: 0 - wiiUAllowScreenCapture: 1 - wiiUControllerCount: 0 - m_SupportedAspectRatios: - 4:3: 1 - 5:4: 1 - 16:10: 1 - 16:9: 1 - Others: 1 - bundleVersion: 1.0 - preloadedAssets: [] - metroInputSource: 0 - m_HolographicPauseOnTrackingLoss: 1 - xboxOneDisableKinectGpuReservation: 0 - xboxOneEnable7thCore: 0 - vrSettings: - cardboard: - depthFormat: 0 - enableTransitionView: 0 - daydream: - depthFormat: 0 - useSustainedPerformanceMode: 0 - hololens: - depthFormat: 1 - protectGraphicsMemory: 0 - useHDRDisplay: 0 - applicationIdentifier: {} - buildNumber: {} - AndroidBundleVersionCode: 1 - AndroidMinSdkVersion: 16 - AndroidTargetSdkVersion: 0 - AndroidPreferredInstallLocation: 1 - aotOptions: - stripEngineCode: 1 - iPhoneStrippingLevel: 0 - iPhoneScriptCallOptimization: 0 - ForceInternetPermission: 0 - ForceSDCardPermission: 0 - CreateWallpaper: 0 - APKExpansionFiles: 0 - keepLoadedShadersAlive: 0 - StripUnusedMeshComponents: 0 - VertexChannelCompressionMask: - serializedVersion: 2 - m_Bits: 238 - iPhoneSdkVersion: 988 - iOSTargetOSVersionString: - tvOSSdkVersion: 0 - tvOSRequireExtendedGameController: 0 - tvOSTargetOSVersionString: - uIPrerenderedIcon: 0 - uIRequiresPersistentWiFi: 0 - uIRequiresFullScreen: 1 - uIStatusBarHidden: 1 - uIExitOnSuspend: 0 - uIStatusBarStyle: 0 - iPhoneSplashScreen: {fileID: 0} - iPhoneHighResSplashScreen: {fileID: 0} - iPhoneTallHighResSplashScreen: {fileID: 0} - iPhone47inSplashScreen: {fileID: 0} - iPhone55inPortraitSplashScreen: {fileID: 0} - iPhone55inLandscapeSplashScreen: {fileID: 0} - iPadPortraitSplashScreen: {fileID: 0} - iPadHighResPortraitSplashScreen: {fileID: 0} - iPadLandscapeSplashScreen: {fileID: 0} - iPadHighResLandscapeSplashScreen: {fileID: 0} - appleTVSplashScreen: {fileID: 0} - tvOSSmallIconLayers: [] - tvOSLargeIconLayers: [] - tvOSTopShelfImageLayers: [] - tvOSTopShelfImageWideLayers: [] - iOSLaunchScreenType: 0 - iOSLaunchScreenPortrait: {fileID: 0} - iOSLaunchScreenLandscape: {fileID: 0} - iOSLaunchScreenBackgroundColor: - serializedVersion: 2 - rgba: 0 - iOSLaunchScreenFillPct: 100 - iOSLaunchScreenSize: 100 - iOSLaunchScreenCustomXibPath: - iOSLaunchScreeniPadType: 0 - iOSLaunchScreeniPadImage: {fileID: 0} - iOSLaunchScreeniPadBackgroundColor: - serializedVersion: 2 - rgba: 0 - iOSLaunchScreeniPadFillPct: 100 - iOSLaunchScreeniPadSize: 100 - iOSLaunchScreeniPadCustomXibPath: - iOSDeviceRequirements: [] - iOSURLSchemes: [] - iOSBackgroundModes: 0 - iOSMetalForceHardShadows: 0 - metalEditorSupport: 1 - metalAPIValidation: 1 - iOSRenderExtraFrameOnPause: 0 - appleDeveloperTeamID: - iOSManualSigningProvisioningProfileID: - tvOSManualSigningProvisioningProfileID: - appleEnableAutomaticSigning: 0 - AndroidTargetDevice: 0 - AndroidSplashScreenScale: 0 - androidSplashScreen: {fileID: 0} - AndroidKeystoreName: - AndroidKeyaliasName: - AndroidTVCompatibility: 1 - AndroidIsGame: 1 - androidEnableBanner: 1 - m_AndroidBanners: - - width: 320 - height: 180 - banner: {fileID: 0} - androidGamepadSupportLevel: 0 - resolutionDialogBanner: {fileID: 0} - m_BuildTargetIcons: [] - m_BuildTargetBatching: [] - m_BuildTargetGraphicsAPIs: [] - m_BuildTargetVRSettings: [] - openGLRequireES31: 0 - openGLRequireES31AEP: 0 - webPlayerTemplate: APPLICATION:Default - m_TemplateCustomTags: {} - wiiUTitleID: 0005000011000000 - wiiUGroupID: 00010000 - wiiUCommonSaveSize: 4096 - wiiUAccountSaveSize: 2048 - wiiUOlvAccessKey: 0 - wiiUTinCode: 0 - wiiUJoinGameId: 0 - wiiUJoinGameModeMask: 0000000000000000 - wiiUCommonBossSize: 0 - wiiUAccountBossSize: 0 - wiiUAddOnUniqueIDs: [] - wiiUMainThreadStackSize: 3072 - wiiULoaderThreadStackSize: 1024 - wiiUSystemHeapSize: 128 - wiiUTVStartupScreen: {fileID: 0} - wiiUGamePadStartupScreen: {fileID: 0} - wiiUDrcBufferDisabled: 0 - wiiUProfilerLibPath: - playModeTestRunnerEnabled: 0 - actionOnDotNetUnhandledException: 1 - enableInternalProfiler: 0 - logObjCUncaughtExceptions: 1 - enableCrashReportAPI: 0 - cameraUsageDescription: - locationUsageDescription: - microphoneUsageDescription: - switchNetLibKey: - switchSocketMemoryPoolSize: 6144 - switchSocketAllocatorPoolSize: 128 - switchSocketConcurrencyLimit: 14 - switchScreenResolutionBehavior: 2 - switchUseCPUProfiler: 0 - switchApplicationID: 0x01004b9000490000 - switchNSODependencies: - switchTitleNames_0: - switchTitleNames_1: - switchTitleNames_2: - switchTitleNames_3: - switchTitleNames_4: - switchTitleNames_5: - switchTitleNames_6: - switchTitleNames_7: - switchTitleNames_8: - switchTitleNames_9: - switchTitleNames_10: - switchTitleNames_11: - switchPublisherNames_0: - switchPublisherNames_1: - switchPublisherNames_2: - switchPublisherNames_3: - switchPublisherNames_4: - switchPublisherNames_5: - switchPublisherNames_6: - switchPublisherNames_7: - switchPublisherNames_8: - switchPublisherNames_9: - switchPublisherNames_10: - switchPublisherNames_11: - switchIcons_0: {fileID: 0} - switchIcons_1: {fileID: 0} - switchIcons_2: {fileID: 0} - switchIcons_3: {fileID: 0} - switchIcons_4: {fileID: 0} - switchIcons_5: {fileID: 0} - switchIcons_6: {fileID: 0} - switchIcons_7: {fileID: 0} - switchIcons_8: {fileID: 0} - switchIcons_9: {fileID: 0} - switchIcons_10: {fileID: 0} - switchIcons_11: {fileID: 0} - switchSmallIcons_0: {fileID: 0} - switchSmallIcons_1: {fileID: 0} - switchSmallIcons_2: {fileID: 0} - switchSmallIcons_3: {fileID: 0} - switchSmallIcons_4: {fileID: 0} - switchSmallIcons_5: {fileID: 0} - switchSmallIcons_6: {fileID: 0} - switchSmallIcons_7: {fileID: 0} - switchSmallIcons_8: {fileID: 0} - switchSmallIcons_9: {fileID: 0} - switchSmallIcons_10: {fileID: 0} - switchSmallIcons_11: {fileID: 0} - switchManualHTML: - switchAccessibleURLs: - switchLegalInformation: - switchMainThreadStackSize: 1048576 - switchPresenceGroupId: - switchLogoHandling: 0 - switchReleaseVersion: 0 - switchDisplayVersion: 1.0.0 - switchStartupUserAccount: 0 - switchTouchScreenUsage: 0 - switchSupportedLanguagesMask: 0 - switchLogoType: 0 - switchApplicationErrorCodeCategory: - switchUserAccountSaveDataSize: 0 - switchUserAccountSaveDataJournalSize: 0 - switchApplicationAttribute: 0 - switchCardSpecSize: -1 - switchCardSpecClock: -1 - switchRatingsMask: 0 - switchRatingsInt_0: 0 - switchRatingsInt_1: 0 - switchRatingsInt_2: 0 - switchRatingsInt_3: 0 - switchRatingsInt_4: 0 - switchRatingsInt_5: 0 - switchRatingsInt_6: 0 - switchRatingsInt_7: 0 - switchRatingsInt_8: 0 - switchRatingsInt_9: 0 - switchRatingsInt_10: 0 - switchRatingsInt_11: 0 - switchLocalCommunicationIds_0: - switchLocalCommunicationIds_1: - switchLocalCommunicationIds_2: - switchLocalCommunicationIds_3: - switchLocalCommunicationIds_4: - switchLocalCommunicationIds_5: - switchLocalCommunicationIds_6: - switchLocalCommunicationIds_7: - switchParentalControl: 0 - switchAllowsScreenshot: 1 - switchDataLossConfirmation: 0 - switchSupportedNpadStyles: 3 - switchSocketConfigEnabled: 0 - switchTcpInitialSendBufferSize: 32 - switchTcpInitialReceiveBufferSize: 64 - switchTcpAutoSendBufferSizeMax: 256 - switchTcpAutoReceiveBufferSizeMax: 256 - switchUdpSendBufferSize: 9 - switchUdpReceiveBufferSize: 42 - switchSocketBufferEfficiency: 4 - ps4NPAgeRating: 12 - ps4NPTitleSecret: - ps4NPTrophyPackPath: - ps4ParentalLevel: 11 - ps4ContentID: ED1633-NPXX51362_00-0000000000000000 - ps4Category: 0 - ps4MasterVersion: 01.00 - ps4AppVersion: 01.00 - ps4AppType: 0 - ps4ParamSfxPath: - ps4VideoOutPixelFormat: 0 - ps4VideoOutInitialWidth: 1920 - ps4VideoOutBaseModeInitialWidth: 1920 - ps4VideoOutReprojectionRate: 120 - ps4PronunciationXMLPath: - ps4PronunciationSIGPath: - ps4BackgroundImagePath: - ps4StartupImagePath: - ps4SaveDataImagePath: - ps4SdkOverride: - ps4BGMPath: - ps4ShareFilePath: - ps4ShareOverlayImagePath: - ps4PrivacyGuardImagePath: - ps4NPtitleDatPath: - ps4RemotePlayKeyAssignment: -1 - ps4RemotePlayKeyMappingDir: - ps4PlayTogetherPlayerCount: 0 - ps4EnterButtonAssignment: 1 - ps4ApplicationParam1: 0 - ps4ApplicationParam2: 0 - ps4ApplicationParam3: 0 - ps4ApplicationParam4: 0 - ps4DownloadDataSize: 0 - ps4GarlicHeapSize: 2048 - ps4ProGarlicHeapSize: 2560 - ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ - ps4UseDebugIl2cppLibs: 0 - ps4pnSessions: 1 - ps4pnPresence: 1 - ps4pnFriends: 1 - ps4pnGameCustomData: 1 - playerPrefsSupport: 0 - restrictedAudioUsageRights: 0 - ps4UseResolutionFallback: 0 - ps4ReprojectionSupport: 0 - ps4UseAudio3dBackend: 0 - ps4SocialScreenEnabled: 0 - ps4ScriptOptimizationLevel: 3 - ps4Audio3dVirtualSpeakerCount: 14 - ps4attribCpuUsage: 0 - ps4PatchPkgPath: - ps4PatchLatestPkgPath: - ps4PatchChangeinfoPath: - ps4PatchDayOne: 0 - ps4attribUserManagement: 0 - ps4attribMoveSupport: 0 - ps4attrib3DSupport: 0 - ps4attribShareSupport: 0 - ps4attribExclusiveVR: 0 - ps4disableAutoHideSplash: 0 - ps4videoRecordingFeaturesUsed: 0 - ps4contentSearchFeaturesUsed: 0 - ps4attribEyeToEyeDistanceSettingVR: 0 - ps4IncludedModules: [] - monoEnv: - psp2Splashimage: {fileID: 0} - psp2NPTrophyPackPath: - psp2NPSupportGBMorGJP: 0 - psp2NPAgeRating: 12 - psp2NPTitleDatPath: - psp2NPCommsID: - psp2NPCommunicationsID: - psp2NPCommsPassphrase: - psp2NPCommsSig: - psp2ParamSfxPath: - psp2ManualPath: - psp2LiveAreaGatePath: - psp2LiveAreaBackroundPath: - psp2LiveAreaPath: - psp2LiveAreaTrialPath: - psp2PatchChangeInfoPath: - psp2PatchOriginalPackage: - psp2PackagePassword: F69AzBlax3CF3EDNhm3soLBPh71Yexui - psp2KeystoneFile: - psp2MemoryExpansionMode: 0 - psp2DRMType: 0 - psp2StorageType: 0 - psp2MediaCapacity: 0 - psp2DLCConfigPath: - psp2ThumbnailPath: - psp2BackgroundPath: - psp2SoundPath: - psp2TrophyCommId: - psp2TrophyPackagePath: - psp2PackagedResourcesPath: - psp2SaveDataQuota: 10240 - psp2ParentalLevel: 1 - psp2ShortTitle: Not Set - psp2ContentID: IV0000-ABCD12345_00-0123456789ABCDEF - psp2Category: 0 - psp2MasterVersion: 01.00 - psp2AppVersion: 01.00 - psp2TVBootMode: 0 - psp2EnterButtonAssignment: 2 - psp2TVDisableEmu: 0 - psp2AllowTwitterDialog: 1 - psp2Upgradable: 0 - psp2HealthWarning: 0 - psp2UseLibLocation: 0 - psp2InfoBarOnStartup: 0 - psp2InfoBarColor: 0 - psp2UseDebugIl2cppLibs: 0 - psmSplashimage: {fileID: 0} - splashScreenBackgroundSourceLandscape: {fileID: 0} - splashScreenBackgroundSourcePortrait: {fileID: 0} - spritePackerPolicy: - webGLMemorySize: 256 - webGLExceptionSupport: 1 - webGLNameFilesAsHashes: 0 - webGLDataCaching: 0 - webGLDebugSymbols: 0 - webGLEmscriptenArgs: - webGLModulesDirectory: - webGLTemplate: APPLICATION:Default - webGLAnalyzeBuildSize: 0 - webGLUseEmbeddedResources: 0 - webGLUseWasm: 0 - webGLCompressionFormat: 1 - scriptingDefineSymbols: {} - platformArchitecture: {} - scriptingBackend: {} - incrementalIl2cppBuild: {} - additionalIl2CppArgs: - apiCompatibilityLevelPerPlatform: {} - m_RenderingPath: 1 - m_MobileRenderingPath: 1 - metroPackageName: VRAssetBundles - metroPackageVersion: - metroCertificatePath: - metroCertificatePassword: - metroCertificateSubject: - metroCertificateIssuer: - metroCertificateNotAfter: 0000000000000000 - metroApplicationDescription: VRAssetBundles - wsaImages: {} - metroTileShortName: - metroCommandLineArgsFile: - metroTileShowName: 0 - metroMediumTileShowName: 0 - metroLargeTileShowName: 0 - metroWideTileShowName: 0 - metroDefaultTileSize: 1 - metroTileForegroundText: 2 - metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} - metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, - a: 1} - metroSplashScreenUseBackgroundColor: 0 - platformCapabilities: {} - metroFTAName: - metroFTAFileTypes: [] - metroProtocolName: - metroCompilationOverrides: 1 - tizenProductDescription: - tizenProductURL: - tizenSigningProfileName: - tizenGPSPermissions: 0 - tizenMicrophonePermissions: 0 - tizenDeploymentTarget: - tizenDeploymentTargetType: -1 - tizenMinOSVersion: 1 - n3dsUseExtSaveData: 0 - n3dsCompressStaticMem: 1 - n3dsExtSaveDataNumber: 0x12345 - n3dsStackSize: 131072 - n3dsTargetPlatform: 2 - n3dsRegion: 7 - n3dsMediaSize: 0 - n3dsLogoStyle: 3 - n3dsTitle: GameName - n3dsProductCode: - n3dsApplicationId: 0xFF3FF - stvDeviceAddress: - stvProductDescription: - stvProductAuthor: - stvProductAuthorEmail: - stvProductLink: - stvProductCategory: 0 - XboxOneProductId: - XboxOneUpdateKey: - XboxOneSandboxId: - XboxOneContentId: - XboxOneTitleId: - XboxOneSCId: - XboxOneGameOsOverridePath: - XboxOnePackagingOverridePath: - XboxOneAppManifestOverridePath: - XboxOnePackageEncryption: 0 - XboxOnePackageUpdateGranularity: 2 - XboxOneDescription: - XboxOneLanguage: - - enus - XboxOneCapability: [] - XboxOneGameRating: {} - XboxOneIsContentPackage: 0 - XboxOneEnableGPUVariability: 0 - XboxOneSockets: {} - XboxOneSplashScreen: {fileID: 0} - XboxOneAllowedProductIds: [] - XboxOnePersistentLocalStorageSize: 0 - xboxOneScriptCompiler: 0 - vrEditorSettings: - daydream: - daydreamIconForeground: {fileID: 0} - daydreamIconBackground: {fileID: 0} - cloudServicesEnabled: {} - facebookSdkVersion: 7.9.1 - apiCompatibilityLevel: 2 - cloudProjectId: - projectName: - organizationId: - cloudEnabled: 0 - enableNewInputSystem: 0 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/ProjectVersion.txt b/MainGameVR/VRAssetBundles/ProjectSettings/ProjectVersion.txt deleted file mode 100644 index 735bd8a..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/ProjectVersion.txt +++ /dev/null @@ -1 +0,0 @@ -m_EditorVersion: 5.6.2f1 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/QualitySettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/QualitySettings.asset deleted file mode 100644 index 3b1bea2..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/QualitySettings.asset +++ /dev/null @@ -1,181 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!47 &1 -QualitySettings: - m_ObjectHideFlags: 0 - serializedVersion: 5 - m_CurrentQuality: 5 - m_QualitySettings: - - serializedVersion: 2 - name: Fastest - pixelLightCount: 0 - shadows: 0 - shadowResolution: 0 - shadowProjection: 1 - shadowCascades: 1 - shadowDistance: 15 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - blendWeights: 1 - textureQuality: 1 - anisotropicTextures: 0 - antiAliasing: 0 - softParticles: 0 - softVegetation: 0 - realtimeReflectionProbes: 0 - billboardsFaceCameraPosition: 0 - vSyncCount: 0 - lodBias: 0.3 - maximumLODLevel: 0 - particleRaycastBudget: 4 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 4 - excludedTargetPlatforms: [] - - serializedVersion: 2 - name: Fast - pixelLightCount: 0 - shadows: 0 - shadowResolution: 0 - shadowProjection: 1 - shadowCascades: 1 - shadowDistance: 20 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - blendWeights: 2 - textureQuality: 0 - anisotropicTextures: 0 - antiAliasing: 0 - softParticles: 0 - softVegetation: 0 - realtimeReflectionProbes: 0 - billboardsFaceCameraPosition: 0 - vSyncCount: 0 - lodBias: 0.4 - maximumLODLevel: 0 - particleRaycastBudget: 16 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 4 - excludedTargetPlatforms: [] - - serializedVersion: 2 - name: Simple - pixelLightCount: 1 - shadows: 1 - shadowResolution: 0 - shadowProjection: 1 - shadowCascades: 1 - shadowDistance: 20 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - blendWeights: 2 - textureQuality: 0 - anisotropicTextures: 1 - antiAliasing: 0 - softParticles: 0 - softVegetation: 0 - realtimeReflectionProbes: 0 - billboardsFaceCameraPosition: 0 - vSyncCount: 1 - lodBias: 0.7 - maximumLODLevel: 0 - particleRaycastBudget: 64 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 4 - excludedTargetPlatforms: [] - - serializedVersion: 2 - name: Good - pixelLightCount: 2 - shadows: 2 - shadowResolution: 1 - shadowProjection: 1 - shadowCascades: 2 - shadowDistance: 40 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - blendWeights: 2 - textureQuality: 0 - anisotropicTextures: 1 - antiAliasing: 0 - softParticles: 0 - softVegetation: 1 - realtimeReflectionProbes: 1 - billboardsFaceCameraPosition: 1 - vSyncCount: 1 - lodBias: 1 - maximumLODLevel: 0 - particleRaycastBudget: 256 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 4 - excludedTargetPlatforms: [] - - serializedVersion: 2 - name: Beautiful - pixelLightCount: 3 - shadows: 2 - shadowResolution: 2 - shadowProjection: 1 - shadowCascades: 2 - shadowDistance: 70 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - blendWeights: 4 - textureQuality: 0 - anisotropicTextures: 2 - antiAliasing: 2 - softParticles: 1 - softVegetation: 1 - realtimeReflectionProbes: 1 - billboardsFaceCameraPosition: 1 - vSyncCount: 1 - lodBias: 1.5 - maximumLODLevel: 0 - particleRaycastBudget: 1024 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 4 - excludedTargetPlatforms: [] - - serializedVersion: 2 - name: Fantastic - pixelLightCount: 4 - shadows: 2 - shadowResolution: 2 - shadowProjection: 1 - shadowCascades: 4 - shadowDistance: 150 - shadowNearPlaneOffset: 3 - shadowCascade2Split: 0.33333334 - shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} - blendWeights: 4 - textureQuality: 0 - anisotropicTextures: 2 - antiAliasing: 2 - softParticles: 1 - softVegetation: 1 - realtimeReflectionProbes: 1 - billboardsFaceCameraPosition: 1 - vSyncCount: 1 - lodBias: 2 - maximumLODLevel: 0 - particleRaycastBudget: 4096 - asyncUploadTimeSlice: 2 - asyncUploadBufferSize: 4 - excludedTargetPlatforms: [] - m_PerPlatformDefaultQuality: - Android: 2 - Nintendo 3DS: 5 - PS4: 5 - PSM: 5 - PSP2: 2 - Samsung TV: 2 - Standalone: 5 - Switch: 5 - Tizen: 2 - Web: 5 - WebGL: 3 - WiiU: 5 - Windows Store Apps: 5 - XboxOne: 5 - iPhone: 2 - tvOS: 2 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/TagManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/TagManager.asset deleted file mode 100644 index 1c92a78..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/TagManager.asset +++ /dev/null @@ -1,43 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!78 &1 -TagManager: - serializedVersion: 2 - tags: [] - layers: - - Default - - TransparentFX - - Ignore Raycast - - - - Water - - UI - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - m_SortingLayers: - - name: Default - uniqueID: 0 - locked: 0 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/TimeManager.asset b/MainGameVR/VRAssetBundles/ProjectSettings/TimeManager.asset deleted file mode 100644 index 558a017..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/TimeManager.asset +++ /dev/null @@ -1,9 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!5 &1 -TimeManager: - m_ObjectHideFlags: 0 - Fixed Timestep: 0.02 - Maximum Allowed Timestep: 0.33333334 - m_TimeScale: 1 - Maximum Particle Timestep: 0.03 diff --git a/MainGameVR/VRAssetBundles/ProjectSettings/UnityConnectSettings.asset b/MainGameVR/VRAssetBundles/ProjectSettings/UnityConnectSettings.asset deleted file mode 100644 index ec1ab29..0000000 --- a/MainGameVR/VRAssetBundles/ProjectSettings/UnityConnectSettings.asset +++ /dev/null @@ -1,32 +0,0 @@ -%YAML 1.1 -%TAG !u! tag:unity3d.com,2011: ---- !u!310 &1 -UnityConnectSettings: - m_ObjectHideFlags: 0 - m_Enabled: 0 - m_TestMode: 0 - m_TestEventUrl: - m_TestConfigUrl: - m_TestInitMode: 0 - CrashReportingSettings: - m_EventUrl: https://perf-events.cloud.unity3d.com/api/events/crashes - m_Enabled: 0 - m_CaptureEditorExceptions: 1 - UnityPurchasingSettings: - m_Enabled: 0 - m_TestMode: 0 - UnityAnalyticsSettings: - m_Enabled: 0 - m_InitializeOnStartup: 1 - m_TestMode: 0 - m_TestEventUrl: - m_TestConfigUrl: - UnityAdsSettings: - m_Enabled: 0 - m_InitializeOnStartup: 1 - m_TestMode: 0 - m_EnabledPlatforms: 4294967295 - m_IosGameId: - m_AndroidGameId: - PerformanceReportingSettings: - m_Enabled: 0 diff --git a/MainGameVR/packages.config b/MainGameVR/packages.config deleted file mode 100644 index 3a806ad..0000000 --- a/MainGameVR/packages.config +++ /dev/null @@ -1,25 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/PatcherLoader/Loader.cs b/PatcherLoader/Loader.cs new file mode 100644 index 0000000..8a12bbf --- /dev/null +++ b/PatcherLoader/Loader.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; +using System.Collections.Generic; +using System.Runtime.InteropServices; +using System.Reflection; +using Mono.Cecil; +using BepInEx; +using BepInEx.Logging; +using System.Diagnostics; +using System.Linq; + +namespace PatcherLoader +{ + /// + /// This "patcher" doesn't patch any .NET assemblies. It just loads and + /// executes a native DLL, which applies some patches to in-memory native + /// code. + /// + /// This needs to be implemented as a preloading-time patcher rather than + /// a regular plugin because the latter doesn't run early enough in the + /// startup sequence of the game. + /// + public static class Loader + { + public static IEnumerable TargetDLLs => GetDLLs(); + + public static void Patch(AssemblyDefinition assembly) + { + } + + private readonly static ManualLogSource logger + = Logger.CreateLogSource("KK_MainGameVR_Patcher"); + + private delegate void SetupAll(); + + private static IEnumerable GetDLLs() + { + var processName = Paths.ProcessName; + if ((processName != "Koikatu" && processName != "Koikatsu Party") + || (!Environment.CommandLine.Contains("--vr") + && !Process.GetProcesses().Where(p => p != null && p.ProcessName == "vrcompositor").Any())) + { + yield break; + } + + var patcherPath = Path.Combine( + Path.GetDirectoryName(Assembly.GetExecutingAssembly().Location), + "native_patcher.dll"); + + logger.LogInfo($"Loading {patcherPath}"); + + var hModule = NativeMethods.LoadLibraryW(patcherPath); + if (hModule == IntPtr.Zero) + { + logger.LogError($"Failed to load native DLL. Error code: {Marshal.GetLastWin32Error()}"); + yield break; + } + var funPtr = NativeMethods.GetProcAddress(hModule, "setup_all"); + if (funPtr == IntPtr.Zero) + { + logger.LogError($"GetProcAddress failed: {Marshal.GetLastWin32Error()}"); + yield break; + } + var setupAll = (SetupAll)Marshal.GetDelegateForFunctionPointer(funPtr, typeof(SetupAll)); + setupAll(); + + yield break; + } + } + + internal static class NativeMethods + { + [DllImport("kernel32", CharSet = CharSet.Unicode, SetLastError = true)] + public static extern IntPtr LoadLibraryW(string name); + + [DllImport("kernel32", CharSet = CharSet.Ansi, SetLastError = true)] + public static extern IntPtr GetProcAddress(IntPtr hModule, string lpProcName); + } +} diff --git a/PatcherLoader/PatcherLoader.csproj b/PatcherLoader/PatcherLoader.csproj new file mode 100644 index 0000000..0de68b4 --- /dev/null +++ b/PatcherLoader/PatcherLoader.csproj @@ -0,0 +1,72 @@ + + + + + Debug + AnyCPU + {7B4A46E3-629C-46CC-ADF4-8FE8FCED992D} + Library + Properties + PatcherLoader + PatcherLoader + v3.5 + 512 + true + + + + + true + full + false + bin\Debug\ + DEBUG;TRACE + prompt + 4 + true + + + pdbonly + true + bin\Release\ + TRACE + prompt + 4 + true + + + + ..\packages\IllusionLibs.BepInEx.Harmony.2.2.0.1\lib\net35\0Harmony.dll + False + + + ..\packages\IllusionLibs.BepInEx.5.4.4\lib\net35\BepInEx.dll + False + + + ..\packages\IllusionLibs.BepInEx.Harmony.2.2.0.1\lib\net35\BepInEx.Harmony.dll + False + + + ..\packages\Mono.Cecil.0.10.4\lib\net35\Mono.Cecil.dll + False + + + + + + + + + + + + + + + + This project references NuGet package(s) that are missing on this computer. Use NuGet Package Restore to download them. For more information, see http://go.microsoft.com/fwlink/?LinkID=322105. The missing file is {0}. + + + + \ No newline at end of file diff --git a/PatcherLoader/Properties/AssemblyInfo.cs b/PatcherLoader/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..9da7747 --- /dev/null +++ b/PatcherLoader/Properties/AssemblyInfo.cs @@ -0,0 +1,36 @@ +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("PatcherLoader")] +[assembly: AssemblyDescription("")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("PatcherLoader")] +[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("7b4a46e3-629c-46cc-adf4-8fe8fced992d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/PatcherLoader/packages.config b/PatcherLoader/packages.config new file mode 100644 index 0000000..930e5ba --- /dev/null +++ b/PatcherLoader/packages.config @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/README.md b/README.md index 754acd9..6a48d88 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,5 @@ -# KKS_VR - VR Plugin for Koikatsu Sunshine -A BepInEx plugin for Koikatsu Sunshine (KKS) that allows you to play both the main game and studio in VR. +# KKS_VR - VR Plugin for Koikatsu and Koikatsu Sunshine +A BepInEx plugin for Koikatsu (KK) and Koikatsu Sunshine (KKS) that allows you to play the main game and studio (Sunshine only) in VR. The difference from the official VR modules is that you have access to the full game/studio, while the official modules have limited features and spotty mod support. Currently only the standing (aka room-scale) mode is fully supported. @@ -10,22 +10,150 @@ The studio part is a fork of the [KKS_CharaStudioVR](https://vr-erogamer.com/arc ## Prerequisites -* Koikatsu Sunshine +* Koikatsu or Koikatsu Sunshine * Latest version of BepInEx 5.x and KKSAPI/ModdingAPI * SteamVR * A VR headset supported by SteamVR * VR controllers + ## Installation 1. Make sure BepInEx, KKSAPI and all their dependencies have been installed. -2. Download the latest [release](https://github.com/IllusionMods/KKS_VR/releases). +2. Download the latest [release](https://github.com/IllusionMods/KKS_VR/releases) for the corresponding game. 3. Extract the zip into the game folder (where the abdata and BepInEx folders are). -4. Create a shortcut to KoikatsuSunshine.exe and/or CharaStudio.exe, and add `--vr` to the command line. - -## Control - -**Warning: This section was written for KK_MainGameVR and might not be accurate, especially in Studio.** +4. Create a shortcut to Koikatu.exe and/or KoikatsuSunshine.exe and/or CharaStudio.exe, and add `--vr` to the command line. + +The game (not the studio) also can be launched without any added arguments if SteamVR is running. + +## Tips + * Be advised to set InterPupillary Distance (IPD) in the settings to change the scale of the world according to own taste and used hardware. + * If VR mode doesn't launch, make sure that neither of controllers is asleep during game launch.* + +## Controls Game +### Overview + +There are two controllers for each hand with identical functional without any tools or modes. +There is no input customization or helping texts. Designed to be able to do any action with a single hand. +The only means of movement are native in-game functions and *GripMove*, no *Warp*. +No double clicks, only *Short* or *LongPress* for buttons and *DirectionalInput* for *Touchpad*. The sole function of *Menu* button is to toggle the floating menu's visibility. + +The plugin assumes that VR controller has: +* **Grip** used as a **Grab** button. Grabs things to move them around. +* **Trigger** used as an **Action** button. Performs actions where applicable or completes their wait period if one is already queued but not yet determined whether it's a *Short* or *LongPress*. +* **Touchpad** aka *Thumbstick* aka *Joystick* used as a **Generic** button. Never requires a click in non-centered positions. + +### Modules and their inputs: + +### GripMove +Grab the world and move around oneself. +Available in **Any Scene** as the last priority action i.e. when no better actions are available. +* **Grip** to start *GripMove*. +* **Trigger** while in *GripMove* to manipulate **Yaw** while using controller as an axis. +* **Touchpad** while in *GripMove* with pressed *Trigger* to manipulate **Rotation** of the camera. +* **Touchpad** while in *GripMove* without pressed *Trigger* to become **Upright**. Registers after *LongPress*. + +Has settings for stabilization. Depending on the context may behave differently. + +### Impersonation aka PoV +Assume orientation of a character head and follow it loosely. +Available in **H Scene** outside of character interactions. +* **Touchpad** to start, stop, change or reset *Impersonation*. Registers after *LongPress*. +* **Touchpad** while in *Impersonation* and in *GripMove* with pressed *Trigger* to set custom offset. + +Has settings for gender preferences and automatization. + +### Assisted kiss/lick +Attach the camera to a partner's PoI to follow it. +Available in **H Scene** when the camera is in direct proximity to the said PoI. Outside of the caress positions requires *GripMove*. +* **Grip** while *Assisted* to start altered version of *GripMove* to acquire precise offsets on the fly. The long gap between the camera and the PoI will cause disengagement. +* **Trigger** while *Assisted* and not in *GripMove* to stop the action and disengage. + +Has plenty of settings for customization. + +### Controller representation +Native in-game items serving as the controller representation. +Available in **Any Scene** as the last priority action i.e. when no better actions are available. +They won't go inside of things easily, preferring instead to stick to the surface. +* **Touchpad** with pressed *Trigger* to cycle available items. +* **DirectionHorizontal** with pressed *Trigger* to cycle through item animations. + +### IK Manipulator aka Grasp +Alter currently playing animation on the fly. +Available in **H, Talk and Text Scenes** when interacting with a character i.e. controller is in close proximity to it. +* **Grip** to start *Grasp* i.e. hold relevant bodyParts and reposition them with the controller movements. +* **Trigger** while in *Grasp* and the visual cue of the held bodyPart is green to attach it. + Currently only to self/different character or controller. ~~Hand holding.~~ +* **Trigger** while in *Grasp* to extend the amount of held bodyParts, up to the whole character. Registers after *ShortPress*. +* **Trigger** while in *Grasp* to extend the amount of held bodyParts temporarily. Registers after *LongPress*. +* **Touchpad** while in *Grasp* to reset currently held bodyParts to default offsets. +* **Touchpad** while not in *Grasp* to reset relevant bodyPart to the default offset. Registers after *LongPress*. +* **Touchpad** while not in *Grasp* but in *Impersonation* to start or stop the synchronization of a relevant bodyPart with the controller. Registers after *LongPress*. +* **DirectionHorizontal** while in *Grasp* and the main held bodyPart is the hand to scroll through it's animations. Goes full circle then resets to the animation's default. +* **DirectionHorizontal** while in *Grasp* and holding the whole character to change *Yaw*. +* **DirectionVertical** while in *Grasp* and holding the whole character to move in direction of the camera. +* **DirectionVertical** while in *Grasp* to Show/Hide guide objects of held bodyParts. Temporarily overrides setting. + +Setting *Maintain limb orientation* changes drastically behavior of arms. + +### Menu interaction +Available in **Any Scene** when aiming controller at the floating in-game *Menu*. +* **Grip** to grab *Menu*. +* **Touchpad** while holding *Menu* with pressed *Trigger* to abandon it in the world. +* **DirectionHorizontal** while holding *Menu* to change it's size. +* **DirectionVertical** while holding *Menu* to move it in controller direction. + +### H Interpreter +Available in **H Scene**, relies heavily on [SensibleH](https://github.com/lotsofbears/KK_SensibleH), without it many functions will be unavailable. +Described horizonal directions assume the right controller, for the left controller the directions will be mirrored. + +#### Generic +* **DirectionLeft** to choose random position from the current category. Registers after *LongPress*. Add *Trigger* for any available position. +* **DirectionRight** to enter *PointMove*. Registers after *LongPress*. +* **DirectionVertical** on partner's bodyPart to (un)dress it. + +#### PointMove +* **DirectionLeft** to exit *PointMove*. +* **DirectionRight** to choose one at random and exit *PointMove*. Registers after *LongPress*. +* **DirectionVertical** to scroll through available categories. + +#### Caress +*AutoCaress* can be overtaken in any way by *Assisted kiss/lick*. +* **Grip** on attached caress item while in *AutoCaress* to take the manual control. +* **Trigger** on attached caress item to start *AutoCaress*. Registers after *LongPress*. +* **Trigger** on attached caress item while in *AutoCaress* to stop it. +* **Trigger** while in manual control of a caress item to squeeze. Might not always work if *AutoCaress* still runs some other item. +* **DirectionDown** on attached caress item while not in *AutoCaress* to detach it. +* **DirectionDown** while not in *AutoCaress* to prompt the partner to initiate the kiss. Limited to the caress positions. Registers after *LongPress*. +* **DirectionHorizontal** on attached caress item to toggle it's visibility. +* **DirectionHorizontal** while an attached caress item is present to scroll through animations. Limited to caress positions. + +#### Service, Intercourse +* **DirectionUp** to insert, start, finish, change speed. Registers after *LongPress*. +* **DirectionUp** with pressed *Trigger* to opt for an options without voice . Registers after *LongPress*. +* **DirectionUp** with pressed *Touchpad* to opt for anal if applicable. Can be used with pressed *Trigger*. Registers after *LongPress*. +* **DirectionDown** to set condom, pullout, stop, change to outside during climax, change speed. Registers after *LongPress*. +* **DirectionHorizontal** to scroll through animations. + +### Talk/Text Interpreter +Available in **Talk and Text Scenes**. +* **Trigger** on partner's bodyPart to provoke a reaction. +* **DirectionVertical** on partner's bodyPart to (un)dress it. +* **DirectionVertical** to scroll buttons on the left side of the screen or choices from the text scenario. +* **DirectionHorizontal** to select/deselect current button/category. +* **DirectionHorizontal** to click on previous action. Registers after *LongPress*. +* **DirectionHorizontal** while text is visible to advance the text scenario. +* **DirectionHorizontal** while text is visible to toggle *Auto*. Registers after *LongPress*. + +### Roaming Interpreter +Available in **Roaming Scene**. +* **Trigger** to start locomotion. +* **DirectionUp** to interact or speed up. +* **DirectionDown** to crouch or stand up. +* **Horizontal direction** to turn. + +## Controls Studio +**Warning: This section was written for [KK_MainGameVR](https://github.com/mosirnik/KK_MainGameVR) and serves as a loose, vague reference for an actual functional.** This plugin assumes that your VR controller has the following buttons/controls: @@ -109,69 +237,6 @@ Just touching the touchpad or tilting the thumbstick won't be recognized. Exceptions to this rule are mouse wheel scroll actions and rotate actions, which only require touching. -## Situation-specific controls - -**Warning: This section was written for KK_MainGameVR and might not be accurate, especially in Studio.** - -The school tool can be used when you need more complex interactions than simple -mouse clicks. - -There are also a few types of context-specific controls, where you can interact -directly with 3D objects using the controllers. This type of interaction does -not require any specific tool to be selected. The tool icon disappears when -such an interaction is available. - -Below is a list of situations that offer special controls. - -### Roaming - -In the Roaming mode, you can move around by using the school tool to walk -(default: Trigger), and turn left and right (default: Touchpad left and right). -You can also use the warp tool to teleport. - -You can use the school tool to simulate ordinary mouse and keyboard -inputs, e.g. right click (default: touchpad center) for interacting with an -object. - -Use the laser pointer (touchpad center) to open the middle-button menu. - -You can crouch by lowering your viewpoint relative to the floor. To do this, -you can either physically move your head or use grab action to bring the -floor closer. This behavior can be disabled in the config. - -### Talk scene - -When talking to a character, most interactions are done through the menu. -In addition, you can touch or look at the character by putting one of the -controllers at the position you want to touch/look at, then pulling the -Trigger. - -### H scene - -Caressing can be done in the same way as touching in talk scenes. Additionally, -you can switch to a different mode of caressing by pressing the Application -menu button with the controller in place. - -Optionally, automatic touching can be enabled, so that you don't even need to -pull the Trigger. - -You can also kiss or lick the female by moving your head to the right place. -This can be turned off in the config. - -You can undress a female character by putting a controller on one of her -clothing items, then pressing the touchpad. A single touchpad click will -perform one level of undressing. If you press and hold the touchpad, -move the controller away and then release the touchpad, it will completely -remove that part of clothing. - -Unfortunately this direct undressing operation doesn't work as expected for -all clothes because the plugin doesn't exactly know what parts of -body each clothing item covers. You can always fall back to -using the menu for clothing state changes. - -When changing location, you can use the green laser to point to a new location -icon and pull the Trigger to confirm. - ## Configuration **Warning: This section was written for KK_MainGameVR and might not be accurate, especially in Studio.** @@ -180,9 +245,6 @@ This plugin has a lot of configuration options. It is recommended that you use [ConfigurationManager](https://github.com/BepInEx/BepInEx.ConfigurationManager), which allows you to change settings of this plugin from within the game. -Alternatively you can manually edit `BepInEx\config\mosirnik.kk-main-game-vr.cfg` -with a text editor. - ## Controller Support At the moment, most VR controllers seem to work out of the box with this plugin. diff --git a/Shared/AssemblyInfo.cs b/Shared/AssemblyInfo.cs index db7544d..c37dc8a 100644 --- a/Shared/AssemblyInfo.cs +++ b/Shared/AssemblyInfo.cs @@ -1,8 +1,8 @@ using System.Reflection; -using KKS_VR; +using KK_VR; [assembly: AssemblyTitle(VRPlugin.Name)] -[assembly: AssemblyDescription("Adds VR support. Launch the game with a --vr switch to enable the plugin.")] +[assembly: AssemblyDescription("Adds VR support. Launch the game with running SteamVR.")] [assembly: AssemblyCompany("https://github.com/IllusionMods/KKS_VR")] [assembly: AssemblyProduct(VRPlugin.Name)] [assembly: AssemblyCopyright("Copyright © 2020, 2021")] diff --git a/MainGameVR/Camera/ActionCameraControl.cs b/Shared/Camera/ActionCameraControl.cs similarity index 67% rename from MainGameVR/Camera/ActionCameraControl.cs rename to Shared/Camera/ActionCameraControl.cs index 8423e55..007ef53 100644 --- a/MainGameVR/Camera/ActionCameraControl.cs +++ b/Shared/Camera/ActionCameraControl.cs @@ -3,11 +3,11 @@ using System.Reflection; using System.Reflection.Emit; using HarmonyLib; -using KKS_VR.Interpreters; +using KK_VR.Interpreters; using UnityEngine; using VRGIN.Core; -namespace KKS_VR.Camera +namespace KK_VR.Camera { /// /// This component takes over control of an Action camera. @@ -83,51 +83,64 @@ public static Transform GetIdealTransformFor(Component c) return GetIdealTransformForObject(c.gameObject); } - public static Transform GetIdealTransformForObject(GameObject o) + public static Transform GetIdealTransformForObject(GameObject gameObject) { // TODO: cache? - var control = o.GetComponent(); +#if KK + var control = gameObject.GetComponent(); if (control != null) +#elif KKS + if (gameObject.TryGetComponent(out var control)) +#endif + { return control.VRIdealCamera; + } else - return o.transform; + { + return gameObject.transform; + } } public static void SetIdealPositionAndRotation(Transform t, Vector3 position, Quaternion rotation) { - if (position.Equals(Vector3.zero)) + // No clue how it all works, but this way atleast it works. + if (!position.Equals(Vector3.zero)) { - VRLog.Warn("position=0,0,0 in " + new StackTrace()); - return; + GetIdealTransformFor(t).SetPositionAndRotation(position, rotation); } + } + //public static void SetIdealPositionAndRotation(Transform t, Vector3 position, Quaternion rotation) + //{ + // if (position.Equals(Vector3.zero)) + // { + // VRLog.Warn("position=0,0,0 in " + new StackTrace()); + // return; + // } - if (TalkScene.isPaly) - { - // todo keep old height? - var heroine = TalkScene.instance.targetHeroine.transform; - var newPosition = heroine.TransformPoint(new Vector3(0, GetPlayerHeight(), TalkSceneInterpreter.TalkDistance)); - if (HeadIsAwayFromPosition(newPosition)) - GetIdealTransformFor(t).SetPositionAndRotation(newPosition, heroine.rotation * Quaternion.Euler(0, 180f, 0)); - } - else - { - var add = rotation.eulerAngles.normalized * 1f; - add.y = 0; - var added = position + add; + // if (TalkScene.isPaly) + // { + // VRLog.Debug($"SetIdealPositionAndRotation[isPaly]"); + // // todo keep old height? + // var heroine = TalkScene.instance.targetHeroine.transform; + // var newPosition = heroine.TransformPoint(new Vector3(0, GetPlayerHeight(), TalkSceneInterpreter.TalkDistance)); + // if (HeadIsAwayFromPosition(newPosition)) + // GetIdealTransformFor(t).SetPositionAndRotation(newPosition, rotation);// , heroine.rotation * Quaternion.Euler(0, 180f, 0)); + // } + // else + // { + // var add = rotation.eulerAngles.normalized * 1f; + // add.y = 0; + // var added = position + add; - // breaks position at talk scene start - //if (HeadIsAwayFromPosition(added)) - GetIdealTransformFor(t).SetPositionAndRotation(added, rotation); - } - } + // // breaks position at talk scene start + // //if (HeadIsAwayFromPosition(added)) + // GetIdealTransformFor(t).SetPositionAndRotation(added, rotation); + // } + //} public static float GetPlayerHeight() { - //todo setting? - //var playerHeight = VR.Camera.Head.position.y - VR.Camera.Origin.position.y; - //Console.WriteLine("height: " + playerHeight); - //return playerHeight; - return 1.4f; + return TalkSceneInterpreter.height != 0f ? TalkSceneInterpreter.height : 1.4f; } public static bool HeadIsAwayFromPosition(Vector3 targetPosition) @@ -137,8 +150,8 @@ public static bool HeadIsAwayFromPosition(Vector3 targetPosition) public static float GetDistanceFromCurrentHeadPos(Vector3 targetPosition) { - var dist = Vector3.Distance(targetPosition, VR.Camera.SteamCam.head.position); - VRLog.Debug("Distance of head from pos={0} is {1}", targetPosition, dist); + var dist = Vector3.Distance(targetPosition, VR.Camera.Head.position); + //VRLog.Debug("Distance of head from pos={0} is {1}", targetPosition, dist); return dist; } } @@ -217,58 +230,58 @@ private static void PreSetNull(ref Transform transform) } } - // todo different lambda in kks - //[HarmonyPatch] - //class TalkScenePatches - //{ - // static IEnumerable TargetMethods() - // { - // // Our target is a particular lambda defined in TalkScene.Start. - // // The code below is ugly and fragile, but there doesn't seem to be - // // any good alternative. - // var nested1 = typeof(TalkScene).GetNestedType("c__Iterator0", BindingFlags.NonPublic); - // if (nested1 == null) - // { - // VRLog.Error("nested1 is null!"); - // yield break; - // } - // - // var nested2 = nested1.GetNestedType("c__AnonStorey8", BindingFlags.NonPublic); - // if (nested2 == null) - // { - // VRLog.Error("nested2 is null"); - // yield break; - // } - // - // yield return AccessTools.Method(nested2, "<>m__3"); - // } - // - // static IEnumerable Transpiler(IEnumerable code) - // { - // // Replace the second (!) call to Transform.SetPositionAndRotation. - // // This is laughably fragile, but it doesn't seem worthwhile to - // // make it more robust until an actual conflict is reported... - // int found = 0; - // foreach (var inst in code) - // { - // if ((inst.opcode == OpCodes.Call || inst.opcode == OpCodes.Callvirt) && - // inst.operand is MethodInfo method && - // method.ReflectedType == typeof(Transform) && - // method.Name == "SetPositionAndRotation" && - // found++ == 1) - // { - // yield return new CodeInstruction( - // OpCodes.Call, - // AccessTools.Method(typeof(ActionCameraControl), nameof(ActionCameraControl.SetIdealPositionAndRotation))); - // } - // else - // { - // yield return inst; - // } - // } - // } - // - //} +#if KK + [HarmonyPatch] + class TalkScenePatches + { + static IEnumerable TargetMethods() + { + // Our target is a particular lambda defined in TalkScene.Start. + // The code below is ugly and fragile, but there doesn't seem to be + // any good alternative. + var nested1 = typeof(TalkScene).GetNestedType("c__Iterator0", BindingFlags.NonPublic); + if (nested1 == null) + { + VRLog.Error("nested1 is null!"); + yield break; + } + + var nested2 = nested1.GetNestedType("c__AnonStorey8", BindingFlags.NonPublic); + if (nested2 == null) + { + VRLog.Error("nested2 is null"); + yield break; + } + + yield return AccessTools.Method(nested2, "<>m__3"); + } + + static IEnumerable Transpiler(IEnumerable code) + { + // Replace the second (!) call to Transform.SetPositionAndRotation. + // This is laughably fragile, but it doesn't seem worthwhile to + // make it more robust until an actual conflict is reported... + int found = 0; + foreach (var inst in code) + { + if ((inst.opcode == OpCodes.Call || inst.opcode == OpCodes.Callvirt) && + inst.operand is MethodInfo method && + method.ReflectedType == typeof(Transform) && + method.Name == "SetPositionAndRotation" && + found++ == 1) + { + yield return new CodeInstruction( + OpCodes.Call, + AccessTools.Method(typeof(ActionCameraControl), nameof(ActionCameraControl.SetIdealPositionAndRotation))); + } + else + { + yield return inst; + } + } + } + } +#endif [HarmonyPatch(typeof(ADV.EventCG.Data))] internal class EventCGDataPatches diff --git a/MainGameVR/Camera/BackgroundDisplayer.cs b/Shared/Camera/BackgroundDisplayer.cs similarity index 95% rename from MainGameVR/Camera/BackgroundDisplayer.cs rename to Shared/Camera/BackgroundDisplayer.cs index 1031ddb..26d577d 100644 --- a/MainGameVR/Camera/BackgroundDisplayer.cs +++ b/Shared/Camera/BackgroundDisplayer.cs @@ -3,7 +3,7 @@ using UnityEngine.UI; using VRGIN.Core; -namespace KKS_VR.Camera +namespace KK_VR.Camera { /// /// A singleton object that is responsible for displaying the @@ -39,7 +39,10 @@ public void OnCameraMove() public void TakeCanvas(Canvas canvas) { VRLog.Info("Taking canvas: {0}", canvas.name); - if (_bgCanvas != null) VRLog.Warn($"Taking a second canvas? {_bgCanvas}"); + if (_bgCanvas != null) + { + VRLog.Warn($"Taking a second canvas? {_bgCanvas}"); + } canvas.renderMode = RenderMode.WorldSpace; canvas.worldCamera = null; canvas.gameObject.layer = 0; @@ -53,7 +56,10 @@ public void TakeCanvas(Canvas canvas) private void UpdateCanvasPlacement() { - if (_bgCanvas == null) return; + if (_bgCanvas == null) + { + return; + } var level = Mathf.Clamp(EyeLevel, 0.25f, 0.75f); var y = (0.5f - level) * Height; diff --git a/MainGameVR/Camera/CameraControlControl.cs b/Shared/Camera/CameraControlControl.cs similarity index 84% rename from MainGameVR/Camera/CameraControlControl.cs rename to Shared/Camera/CameraControlControl.cs index ba5c96b..9fd883e 100644 --- a/MainGameVR/Camera/CameraControlControl.cs +++ b/Shared/Camera/CameraControlControl.cs @@ -1,6 +1,6 @@ using VRGIN.Core; -namespace KKS_VR.Camera +namespace KK_VR.Camera { /// /// CameraControlControl disables a CameraControl_Ver2 object and @@ -31,7 +31,10 @@ internal class CameraControlControl : ProtectedBehaviour protected override void OnStart() { _control = GetComponent(); - if (_control == null) VRLog.Error("CameraControlControl: CameraControl_Ver2 was not found"); + if (_control == null) + { + VRLog.Error("CameraControlControl: CameraControl_Ver2 was not found"); + } if (_control.enabled) _control.enabled = false; @@ -45,12 +48,18 @@ protected override void OnUpdate() transform.SetPositionAndRotation(head.position, head.rotation); // One of the default macros from GameObjectList enables the camera // control. We make sure that it remains desabled. - if (_control != null) _control.enabled = false; + if (_control != null) + { + _control.enabled = false; + } } protected override void OnLateUpdate() { - if (_control.isCursorLock && Singleton.IsInstance()) Singleton.Instance.SetCursorLock(false); + if (_control.isCursorLock && Singleton.IsInstance()) + { + Singleton.Instance.SetCursorLock(false); + } } } } diff --git a/Shared/Camera/CameraMoveHooks.cs b/Shared/Camera/CameraMoveHooks.cs new file mode 100644 index 0000000..e51f379 --- /dev/null +++ b/Shared/Camera/CameraMoveHooks.cs @@ -0,0 +1,306 @@ +using System.Collections; +using System.Collections.Generic; +using System.Reflection; +using HarmonyLib; +using IllusionUtility.SetUtility; +using KK_VR.Caress; +using KK_VR.Features; +using KK_VR.Grasp; +using KK_VR.Interactors; +using KK_VR.Interpreters; +using UnityEngine; +using VRGIN.Core; + +// This file is a collection of hooks to move the VR camera at appropriate +// points of the game. + +namespace KK_VR.Camera +{ + [HarmonyPatch] + internal class TextScenarioPatches1 + { + [HarmonyPostfix] + [HarmonyPatch(typeof(ADV.TextScenario), nameof(ADV.TextScenario.ADVCameraSetting))] + private static void PostADVCameraSetting(ADV.TextScenario __instance) + { +#if KK + + if (Manager.Scene.IsInstance() && Manager.Scene.Instance.NowSceneNames[0] == "Talk") +#elif KKS + if (Manager.Scene.initialized && Manager.Scene.NowSceneNames[0] == "Talk") +#endif + { + // Talk scenes are handled separately. + return; + } + + VRLog.Debug("PostADVCameraSetting"); + var backTrans = __instance.BackCamera?.transform; + if (backTrans == null) + { + // backTrans can be null in Roaming. We don't want to move the + // camera anyway in that case. + return; + } + VRCameraMover.Instance.MaybeMoveADV(__instance, backTrans.position, backTrans.rotation); + } + } + + [HarmonyPatch] + internal class RequestNextLinePatches + { + private static IEnumerable TargetMethods() + { + // In some versions of the base game, MainScenario._RequestNextLine + // duplicates the logic found in TextScenario._RequestNextLine. + // In other versions, the former simply calls the latter. + // We want to patch both methods or the latter alone depending on + // the version. + yield return AccessTools.Method(typeof(ADV.TextScenario), "_RequestNextLine"); + if (AccessTools.Field(typeof(ADV.MainScenario), "textHash") == null) + { + yield return AccessTools.Method(typeof(ADV.MainScenario), "_RequestNextLine"); + } + } + + private static void Postfix(ADV.TextScenario __instance, ref IEnumerator __result) + { +#if KK + + if (Manager.Scene.IsInstance() && Manager.Scene.Instance.NowSceneNames[0] == "Talk") +#elif KKS + if (Manager.Scene.initialized && Manager.Scene.NowSceneNames[0] == "Talk") +#endif + { + // Talk scenes are handled separately. + return; + } + if (__instance.advScene == null) + { + // Outside ADV scene (probably roaming), ignore. + return; + } + __result = new[] { __result, Postfix() }.GetEnumerator(); + + IEnumerator Postfix() + { + VRCameraMover.Instance.HandleTextScenarioProgress(__instance); + yield break; + } + } + } + + [HarmonyPatch] + internal class ProgramPatches1 + { + [HarmonyPostfix] + [HarmonyPatch(typeof(ADV.Program), nameof(ADV.Program.SetNull))] + private static void PostSetNull(Transform transform) + { + VRLog.Debug("PostSetNull"); + VRCameraMover.Instance.MaybeMoveTo(transform.position, transform.rotation); + } + +#if KKS + [HarmonyPrefix] + [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.ChangeAnimator))] + public static void ChangeAnimatorPrefix(HSceneProc __instance) + { + __instance.ctrlObi.solver.gameObject.SetActive(true); + } +#endif + [HarmonyPostfix] + [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.ChangeAnimator))] + public static void PostChangeAnimator(HSceneProc.AnimationListInfo _nextAinmInfo, bool _isForceCameraReset, HSceneProc __instance, List ___lstFemale) + { + HSceneInterpreter.OnPoseChange(_nextAinmInfo); + UpdateVRCamera(__instance, ___lstFemale); +#if KKS + Fixes.ObiCtrlFix.SetFluidsState(false); +#endif + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.ChangeCategory))] + public static void PostChangeCategory(HSceneProc __instance, List ___lstFemale)//, float __state) + { + HSceneInterpreter.OnSpotChange(); + UpdateVRCamera(__instance, ___lstFemale); + +#if KKS + Fixes.ObiCtrlFix.SetFluidsState(false); +#endif + } + + [HarmonyPostfix] + [HarmonyPatch(typeof(HSceneProc), nameof(HSceneProc.GotoPointMoveScene))] + public static void GotoPointMoveScenePostfix() + { + if (SmoothMover.Instance != null) + { + SmoothMover.Instance.MakeUpright(); + } + } + + private static void UpdateVRCamera(HSceneProc instance, List lstFemale) + { + var spotChange = Vector3.Distance(VR.Camera.transform.position, lstFemale[0].transform.position) > 2f; + //VRPlugin.Logger.LogDebug($"UpdateVRCamera:spotChange = {spotChange}"); + var baseTransform = lstFemale[0].objTop.transform; + var camDat = instance.flags.ctrlCamera.CamDat;// new Traverse(instance.flags.ctrlCamera).Field("CamDat").Value; + var cameraRotation = baseTransform.rotation * Quaternion.Euler(camDat.Rot); + Vector3 dir; + switch (instance.flags.mode) + { + case HFlag.EMode.masturbation: + case HFlag.EMode.peeping: + case HFlag.EMode.lesbian: + // Use the default distance for 3rd-person scenes. + dir = camDat.Dir; + break; + default: + // Start closer otherwise. + dir = Vector3.back * 0.8f; + break; + } + + var cameraPosition = cameraRotation * dir + baseTransform.TransformPoint(camDat.Pos); + //if (previousFemaleY is float prevY) + //{ + // // Keep the relative Y coordinate from the female. + // var cameraHeight = VR.Camera.transform.position.y + baseTransform.position.y - prevY; + // var destination = new Vector3(cameraPosition.x, cameraHeight, cameraPosition.z); + + // if (VRMoverH.Instance != null && VRMoverH.Instance._settings.FlyInH) + // { + // VRMoverH.Instance.MoveToInH(position: destination); + // } + // else + // { + // VRCameraMover.Instance.MaybeMoveTo(destination, cameraRotation, false); + // } + //} + //else + { + // We are starting from scratch. + // TODO: the height calculation below assumes standing mode. + + if (KoikatuInterpreter.Settings.SmoothTransition && SmoothMover.Instance != null && !VRFade.IsFade) + { + SmoothMover.Instance.MoveToInH(cameraPosition, cameraRotation, spotChange); + } + else + { + var cameraHeight = lstFemale[0].transform.position.y + VR.Camera.transform.localPosition.y; + var destination = new Vector3(cameraPosition.x, cameraHeight, cameraPosition.z); + VRCameraMover.Instance.MoveTo(destination, cameraRotation); + } + + + } + } + } + + //[HarmonyPatch(typeof(HSceneProc))] + //internal class HSceneProcPatches + //{ + + + // //[HarmonyPatch("ChangeAnimator")] + // //[HarmonyPostfix] + // //public static void PostChangeAnimator(HSceneProc.AnimationListInfo _nextAinmInfo, bool _isForceCameraReset, HSceneProc __instance, List ___lstFemale) + // //{ + // // UpdateVRCamera(__instance, ___lstFemale, null); + // // HSceneInterpreter.OnPoseChange(_nextAinmInfo); + + // // Fixes.ObiCtrlFix.SetFluidsState(false); + // //} + + + // //[HarmonyPatch("ChangeCategory")] + // //[HarmonyPrefix] + // //public static void PreChangeCategory() + // //{ + // // if (GraspHelper.Instance != null) GraspHelper.Instance.OnSpotChangePre(); + // //} + + // //[HarmonyPatch("ChangeCategory")] + // //[HarmonyPostfix] + // //public static void PostChangeCategory(HSceneProc __instance, List ___lstFemale)//, float __state) + // //{ + // // if (KoikatuInterpreter.SceneInterpreter is HSceneInterpreter hScene) + // // hScene.OnSpotChangePost(); + // // UpdateVRCamera(__instance, ___lstFemale, null);// __state); + + // // Fixes.ObiCtrlFix.SetFluidsState(false); + // //} + // //[HarmonyPatch(nameof(HSceneProc.GotoPointMoveScene))] + // //[HarmonyPostfix] + // //public static void GotoPointMoveScenePostfix() + // //{ + // // if (VRMoverH.Instance != null) + // // { + // // VRMoverH.Instance.MakeUpright(); + // // } + // //} + + // /// + // /// Update the transform of the VR camera. + // /// + // //private static void UpdateVRCamera(HSceneProc instance, List lstFemale, float? previousFemaleY) + // //{ + // // var baseTransform = lstFemale[0].objTop.transform; + // // var camDat = instance.flags.ctrlCamera.CamDat;// new Traverse(instance.flags.ctrlCamera).Field("CamDat").Value; + // // var cameraRotation = baseTransform.rotation * Quaternion.Euler(camDat.Rot); + // // Vector3 dir; + // // switch (instance.flags.mode) + // // { + // // case HFlag.EMode.masturbation: + // // case HFlag.EMode.peeping: + // // case HFlag.EMode.lesbian: + // // // Use the default distance for 3rd-person scenes. + // // dir = camDat.Dir; + // // break; + // // default: + // // // Start closer otherwise. + // // dir = Vector3.back * 0.8f; + // // break; + // // } + + // // var cameraPosition = cameraRotation * dir + baseTransform.TransformPoint(camDat.Pos); + // // //if (previousFemaleY is float prevY) + // // //{ + // // // // Keep the relative Y coordinate from the female. + // // // var cameraHeight = VR.Camera.transform.position.y + baseTransform.position.y - prevY; + // // // var destination = new Vector3(cameraPosition.x, cameraHeight, cameraPosition.z); + + // // // if (VRMoverH.Instance != null && VRMoverH.Instance._settings.FlyInH) + // // // { + // // // VRMoverH.Instance.MoveToInH(position: destination); + // // // } + // // // else + // // // { + // // // VRCameraMover.Instance.MaybeMoveTo(destination, cameraRotation, false); + // // // } + // // //} + // // //else + // // { + // // // We are starting from scratch. + // // // TODO: the height calculation below assumes standing mode. + + // // if (VRMoverH.Instance != null && KoikatuInterpreter.settings.FlyInH) + // // { + // // VRMoverH.Instance.MoveToInH(cameraPosition, cameraRotation, previousFemaleY == null, instance.flags.mode); + // // } + // // else + // // { + // // var cameraHeight = lstFemale[0].transform.position.y + VR.Camera.transform.localPosition.y; + // // var destination = new Vector3(cameraPosition.x, cameraHeight, cameraPosition.z); + // // VRCameraMover.Instance.MoveTo(destination, cameraRotation); + // // } + + + // // } + // //} + //} +} diff --git a/Shared/Camera/MoveToPoi.cs b/Shared/Camera/MoveToPoi.cs new file mode 100644 index 0000000..6dbd185 --- /dev/null +++ b/Shared/Camera/MoveToPoi.cs @@ -0,0 +1,344 @@ +using KK_VR.Interpreters; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using VRGIN.Core; +using Random = UnityEngine.Random; + +namespace KK_VR.Camera +{ + /// + /// Provides movement to a predefined poi of the provided character. + /// + internal class MoveToPoi + { + private struct PoIPatternInfo + { + internal string teleportTo; + internal List lookAt; + internal float forwardMin; + internal float forwardMax; + internal float upMin; + internal float upMax; + internal float rightMin; + internal float rightMax; + } + internal MoveToPoi(ChaControl chara, Action onFinish) + { + var dicValue = _poiDic.ElementAt(Random.Range(0, _poiDic.Count)).Value; + + var transforms = chara.transform.GetComponentsInChildren(includeInactive: true); + _teleportTo = transforms + .Where(t => t.name.Equals(dicValue.teleportTo)) + .FirstOrDefault(); + + if (_teleportTo == null) + { + throw new NullReferenceException($"{GetType().Name}:Bad dic, can't find the target."); + } + + var lookAtName = dicValue.lookAt[Random.Range(0, dicValue.lookAt.Count)]; + _lookAt = transforms + .Where(t => t.name.Equals(lookAtName)) + .FirstOrDefault(); + + _offset = new Vector3( + Random.Range(dicValue.rightMin, dicValue.rightMax), + Random.Range(dicValue.upMin, dicValue.upMax), + Random.Range(dicValue.forwardMin, dicValue.forwardMax)); + + _startRotation = VR.Camera.Origin.rotation; + _startPosition = VR.Camera.Head.position; + var offsetPos = _teleportTo.TransformPoint(_offset); + _targetRotation = Quaternion.LookRotation(_lookAt.position - offsetPos); + + _lerpMultiplier = Mathf.Min( + KoikatuInterpreter.Settings.FlightSpeed / Vector3.Distance(offsetPos, _startPosition), + KoikatuInterpreter.Settings.FlightSpeed * 60f / Quaternion.Angle(_startRotation, _targetRotation)); + + } + private readonly Quaternion _startRotation; + private readonly Quaternion _targetRotation; + private readonly Vector3 _startPosition; + + private readonly Transform _teleportTo; + private readonly Transform _lookAt; + private readonly Vector3 _offset; + private readonly float _lerpMultiplier; + + private readonly Action _onFinish; + + private float _lerp; + + internal void Move() + { + var step = Mathf.SmoothStep(0f, 1f, _lerp += Time.deltaTime * _lerpMultiplier); + var offsetPos = _teleportTo.TransformPoint(_offset); + var pos = Vector3.Slerp(_startPosition, offsetPos, step); + VR.Camera.Origin.rotation = Quaternion.Slerp(_startRotation, _targetRotation, step); + VR.Camera.Origin.position += pos - VR.Camera.Head.position; + if (step >= 1f) + { + _onFinish?.Invoke(); + } + } + + //private readonly Dictionary poiDicDev = new() + //{ + + // { + // "NavelUpFront", // Upfront + // new PoIPatternInfo { + // teleportTo = "cf_j_spine01", + // lookAt = [ + // "cf_j_spine03", + // "cf_j_spine01", + // "cf_j_spine02" + // ], + // forwardMin = 0.05f, + // forwardMax = 0.15f, + // upMin = -0.1f, + // upMax = 0.1f, + // rightMin = -0.1f, + // rightMax = 0.1f + // } + // }, + // { + // "NavelLeftSide", + // new PoIPatternInfo { + // teleportTo = "cf_j_spine01", + // lookAt = [ + // "cf_j_spine03", + // "cf_j_spine01", + // "cf_j_spine02" + // ], + // forwardMin = -0.1f, + // forwardMax = 0.1f, + // upMin = -0.1f, + // upMax = 0.1f, + // rightMin = -0.15f, + // rightMax = -0.25f + // } + // }, + // { + // "NavelRightSide", + // new PoIPatternInfo { + // teleportTo = "cf_j_spine01", + // lookAt = [ + // "cf_j_spine03", + // "cf_j_spine01", + // "cf_j_spine02" + // ], + // forwardMin = -0.1f, + // forwardMax = 0.1f, + // upMin = -0.1f, + // upMax = 0.1f, + // rightMin = 0.15f, + // rightMax = 0.25f + // } + // } + //}; + private readonly Dictionary _poiDic = new() + { + { + "FaceUpFront", // Upfront + new PoIPatternInfo { + teleportTo = "cf_J_FaceUp_tz", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_neck" + }, + forwardMin = 0.15f, + forwardMax = 0.3f, + upMin = -0.05f, + upMax = 0.05f, + rightMin = -0.2f, + rightMax = 0.2f + } + }, + { + "FaceLeftSide", + new PoIPatternInfo { + teleportTo = "cf_J_FaceUp_tz", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_neck" + }, + forwardMin = 0.1f, + forwardMax = 0.2f, + upMin = -0.05f, + upMax = 0.05f, + rightMin = -0.15f, + rightMax = -0.3f + } + }, + { + "FaceRightSide", // Right + new PoIPatternInfo { + teleportTo = "cf_J_FaceUp_tz", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_neck" + }, + forwardMin = 0.1f, + forwardMax = 0.2f, + upMin = -0.05f, + upMax = 0.05f, + rightMin = 0.15f, + rightMax = 0.3f + } + }, + { + "NeckUpFront", // Upfront + new PoIPatternInfo { + teleportTo = "cf_j_neck", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_spine03" + }, + forwardMin = 0.2f, + forwardMax = 0.3f, + upMin = -0.05f, + upMax = 0.05f, + rightMin = -0.2f, + rightMax = 0.2f + } + }, + { + "NeckLeftSide", + new PoIPatternInfo { + teleportTo = "cf_j_neck", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_spine03" + }, + forwardMin = 0.1f, + forwardMax = 0.2f, + upMin = -0.05f, + upMax = 0.05f, + rightMin = -0.15f, + rightMax = -0.25f + } + }, + { + "NeckRightSide", + new PoIPatternInfo { + teleportTo = "cf_j_neck", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_spine03" + }, + forwardMin = 0.1f, + forwardMax = 0.2f, + upMin = -0.05f, + upMax = 0.05f, + rightMin = 0.15f, + rightMax = 0.25f + } + }, + { + "BreastUpFront", // Upfront + new PoIPatternInfo { + teleportTo = "cf_j_spine03", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_neck", + "cf_j_spine03" + }, + forwardMin = 0.05f, + forwardMax = 0.15f, + upMin = -0.1f, + upMax = 0.1f, + rightMin = -0.1f, + rightMax = 0.1f + } + }, + { + "BreastLeftSide", + new PoIPatternInfo { + teleportTo = "cf_j_spine03", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_neck", + "cf_j_spine03" + }, + forwardMin = -0.1f, + forwardMax = 0.1f, + upMin = -0.1f, + upMax = 0.1f, + rightMin = -0.15f, + rightMax = -0.25f + } + }, + { + "BreastRightSide", + new PoIPatternInfo { + teleportTo = "cf_j_spine03", + lookAt = new List { + "cf_J_FaceUp_tz", + "cf_j_neck", + "cf_j_spine03" + }, + forwardMin = -0.1f, + forwardMax = 0.1f, + upMin = -0.1f, + upMax = 0.1f, + rightMin = 0.15f, + rightMax = 0.25f + } + }, + { + "NavelUpFront", // Upfront + new PoIPatternInfo { + teleportTo = "cf_j_spine01", + lookAt = new List { + "cf_j_spine03", + "cf_j_spine01", + "cf_j_spine02" + }, + forwardMin = 0.05f, + forwardMax = 0.15f, + upMin = -0.1f, + upMax = 0.1f, + rightMin = -0.1f, + rightMax = 0.1f + } + }, + { + "NavelLeftSide", + new PoIPatternInfo { + teleportTo = "cf_j_spine01", + lookAt = new List { + "cf_j_spine03", + "cf_j_spine01", + "cf_j_spine02" + }, + forwardMin = -0.1f, + forwardMax = 0.1f, + upMin = -0.1f, + upMax = 0.1f, + rightMin = -0.15f, + rightMax = -0.25f + } + }, + { + "NavelRightSide", + new PoIPatternInfo { + teleportTo = "cf_j_spine01", + lookAt = new List { + "cf_j_spine03", + "cf_j_spine01", + "cf_j_spine02" + }, + forwardMin = -0.1f, + forwardMax = 0.1f, + upMin = -0.1f, + upMax = 0.1f, + rightMin = 0.15f, + rightMax = 0.25f + } + } + }; + } +} diff --git a/Shared/Camera/SmoothMover.cs b/Shared/Camera/SmoothMover.cs new file mode 100644 index 0000000..47ebb09 --- /dev/null +++ b/Shared/Camera/SmoothMover.cs @@ -0,0 +1,217 @@ +using KK_VR.Features; +using System; +using System.Collections; +using UnityEngine; +using VRGIN.Core; +using KK_VR.Interpreters; +using KK_VR.Handlers; +using KKAPI.Utilities; + +namespace KK_VR.Camera +{ + /// + /// We fly towards adjusted positions. By flying rather then teleporting the sense of actual scene is created. No avoidance system (yet). + /// + internal class SmoothMover : MonoBehaviour + { + internal static SmoothMover Instance => _instance; + private static SmoothMover _instance; + //private Transform _chara; + //private Transform _eyes; + //private Transform _torso; + //private Transform _kokan; + //private List _activeCoroutines = new List(); + private void Awake() + { + _instance = this; + } + internal void MoveToPoV() + { + var mode = HSceneInterpreter.mode; + if (PoV.Active || (KoikatuInterpreter.Settings.AutoEnterPov && (mode == HFlag.EMode.houshi || mode == HFlag.EMode.sonyu))) + { + PoV.Instance.TryDisable(moveTo: false); + StartCoroutine(WaitForLag(PoV.Instance.StartPov, null)); + } + } + internal void MoveToInH(Vector3 position, Quaternion rotation, bool spotChange) + { + //VRPlugin.Logger.LogDebug("VRMoverH:MoveToInH"); + StopAllCoroutines(); + var mode = HSceneInterpreter.mode; + if (PoV.Active || (KoikatuInterpreter.Settings.AutoEnterPov && (mode == HFlag.EMode.houshi || mode == HFlag.EMode.sonyu))) + { + PoV.Instance.TryDisable(moveTo: false); + if (spotChange) + { + StartCoroutine(WaitForLag(PoV.Instance.OnSpotChange, null)); + } + else + { + StartCoroutine(WaitForLag(PoV.Instance.StartPov, null)); + } + } + else + { + StartCoroutine(FlyToPosition(position, rotation, spotChange)); + } + } + + internal void MakeUpright(Action method = null, params object[] args) + { + StartCoroutine(RotateToUpright(method, args)); + } + + private IEnumerator RotateToUpright(Action method = null, params object[] args) + { + // Wait for lag. + yield return null; + yield return new WaitUntil(() => Time.deltaTime < 0.05f); + + // Wait for IK to put transforms back. + yield return CoroutineUtils.WaitForEndOfFrame; + var origin = VR.Camera.Origin; + if (origin.eulerAngles.x != 0f || origin.eulerAngles.z != 0f) + { + var head = VR.Camera.Head; + var uprightRot = Quaternion.Euler(0f, origin.eulerAngles.y, 0f); + //var uprightRot = Quaternion.Euler(0f, head.eulerAngles.y, 0f); + var lerpMultiplier = KoikatuInterpreter.Settings.FlightSpeed * 90f / Quaternion.Angle(head.rotation, uprightRot); + var lerp = 0f; + var startRot = origin.rotation; + Vector3 oldPos; + while (lerp < 1f) + { + var step = Mathf.SmoothStep(0f, 1f, lerp += Time.deltaTime * lerpMultiplier); + oldPos = head.position; + origin.rotation = Quaternion.Lerp(startRot, uprightRot, step); //Quaternion.RotateTowards(origin.rotation, uprightRot, Time.deltaTime * 120f); + origin.position += oldPos - head.position; + yield return CoroutineUtils.WaitForEndOfFrame; + } + //oldPos = head.position; + //origin.rotation = uprightRot; + //origin.position += oldPos - head.position; + } + method?.DynamicInvoke(args); + //VRPlugin.Logger.LogDebug($"VRMoverH:MakeUpright:Done"); + } + private IEnumerator WaitForLag(Action action, params object[] args) + { + //VRPlugin.Logger.LogDebug($"VRMoverH:WaitForLag"); + // We wait for the lag of position change. + yield return null; + yield return new WaitUntil(() => Time.deltaTime < 0.05f); + //if (actionChange) + //{ + // yield return CoroutineUtils.WaitForEndOfFrame; + // var destination = _pov.GetDestination(); + // if (destination != Vector3.zero) + // { + // var head = VR.Camera.Head; + // var origin = VR.Camera.Origin; + // var distance = Vector3.Distance(destination, head.position); + // var target = destination + _pov.GetRotation() * (Vector3.forward * 0.4f); + // var rotation = Quaternion.LookRotation(target - head.position); + // if (distance > 2f && Quaternion.Angle(origin.rotation, rotation) > 30f) + // { + // //VRPlugin.Logger.LogDebug($"VRMoverH:FlyToPov:MovementOverride"); + // var moveSpeed = 0.5f + distance * 0.5f * _settings.FlightSpeed; + // var halfDist = distance * 0.5f; + // while (true) + // { + // var angleDelta = Mathf.Clamp(Quaternion.Angle(origin.rotation, rotation) - 30f, 0f, 180f); + // if (angleDelta == 0f) + // { + // break; + // } + // distance = Vector3.Distance(destination, head.position) - halfDist; + // var step = Time.deltaTime * moveSpeed; + // var moveTowards = Vector3.MoveTowards(head.position, destination, step); + // var rotSpeed = angleDelta / (distance / step); + // origin.rotation = Quaternion.RotateTowards(origin.rotation, rotation, 1f * rotSpeed); + // origin.position += moveTowards - head.position; + // yield return CoroutineUtils.WaitForEndOfFrame; + // } + // while (true) + // { + // distance = Vector3.Distance(destination, head.position); + // var step = Time.deltaTime * moveSpeed; + // var angleDelta = Quaternion.Angle(origin.rotation, rotation); + // var moveTowards = Vector3.MoveTowards(head.position, destination, step); + // var rotSpeed = angleDelta / (distance / step); + // origin.rotation = Quaternion.RotateTowards(origin.rotation, rotation, 1f * rotSpeed); + // origin.position += moveTowards - head.position; + // yield return CoroutineUtils.WaitForEndOfFrame; + // if (distance < step) + // { + // break; + // } + // } + // } + // } + //} + //VRPlugin.Logger.LogDebug($"VRMoverH:FlyToPov:Done"); + action?.DynamicInvoke(args); + } + private IEnumerator FlyToPosition(Vector3 position, Quaternion rotation, bool spotChange) + { + yield return null; + yield return new WaitUntil(() => Time.deltaTime < 0.05f); + yield return CoroutineUtils.WaitForEndOfFrame; + var origin = VR.Camera.Origin; + var head = VR.Camera.Head; + MouthGuide.Instance.PauseInteractions = true; + + var chara = HSceneInterpreter.lstFemale[0]; + var eyes = chara.objHeadBone.transform.Find("cf_J_N_FaceRoot/cf_J_FaceRoot/cf_J_FaceBase/cf_J_FaceUp_ty/cf_J_FaceUp_tz/cf_J_Eye_tz"); + //_torso = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03"); + var kokan = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_kokan/cf_j_kokan"); + + var eyeLevel = eyes.transform.position.y; + + if (eyeLevel - chara.transform.position.y > 1f) + { + //VRLog.Debug($"VRMoverH:FlyToPosition[height is high, resetting rotation]"); + // Prob upright position, some of them have weird rotations. + rotation = Quaternion.Euler(0f, rotation.eulerAngles.y, 0f); + if (position.y < eyeLevel) + { + //VRLog.Debug($"VRMoverH:FlyToPosition[height is low, meeting eye level]"); + position.y = eyeLevel; + } + else + { + position.y += 0.2f; + //VRLog.Debug($"VRMoverH:FlyToPosition[height is low, increasing a bit]"); + } + + } + + // distKokan = Vector3.Distance(position, kokan.position); + //var distTorso = Vector3.Distance(position, _torso.position); + //var distEyes = Vector3.Distance(position, eyes.position); + var proximity = Mathf.Min(Vector3.Distance(position, eyes.position), Vector3.Distance(position, kokan.position)); + if (proximity > 0.4f) + { + // We are moving.. somewhere, maybe we'll get closer. Changing rotation dulls it more often then not. + //VRLog.Debug($"VRMoverH:FlyToPosition[not close enough, moving forward for {proximity - 0.4f}]"); + position += rotation * Vector3.forward * (proximity - 0.4f); + + } + var lerp = 0f; + var lerpModifier = KoikatuInterpreter.Settings.FlightSpeed * (spotChange ? 3f : 1f) / Vector3.Distance(head.position, position); + var startPos = head.position; + var startRot = origin.rotation; + while (lerp < 1f) + { + var step = Mathf.SmoothStep(0f, 1f, lerp += Time.deltaTime * lerpModifier); + + var pos = Vector3.Lerp(startPos, position, step); + origin.rotation = Quaternion.Slerp(startRot, rotation, step); + origin.position += pos - head.position; + yield return CoroutineUtils.WaitForEndOfFrame; + } + MouthGuide.Instance.PauseInteractions = false; + } + } +} diff --git a/MainGameVR/Camera/VRCameraMover.cs b/Shared/Camera/VRCameraMover.cs similarity index 50% rename from MainGameVR/Camera/VRCameraMover.cs rename to Shared/Camera/VRCameraMover.cs index a314498..000f6ba 100644 --- a/MainGameVR/Camera/VRCameraMover.cs +++ b/Shared/Camera/VRCameraMover.cs @@ -3,13 +3,15 @@ using System.Diagnostics; using System.Linq; using ADV; +using ADV.Commands.Object; using HarmonyLib; -using KKS_VR.Interpreters; -using KKS_VR.Settings; +using KK_VR.Interpreters; +using KK_VR.Settings; +using Manager; using UnityEngine; using VRGIN.Core; -namespace KKS_VR.Camera +namespace KK_VR.Camera { /// /// A class responsible for moving the VR camera. @@ -17,13 +19,11 @@ namespace KKS_VR.Camera /// public class VRCameraMover { - public static VRCameraMover Instance => _instance ?? (_instance = new VRCameraMover()); - + public static VRCameraMover Instance => _instance ??= new VRCameraMover(); private static VRCameraMover _instance; private Vector3 _lastPosition; private Quaternion _lastRotation; - private readonly KoikatuSettings _settings; public delegate void OnMoveAction(); @@ -33,36 +33,33 @@ public VRCameraMover() { _lastPosition = Vector3.zero; _lastRotation = Quaternion.identity; - _settings = VR.Settings as KoikatuSettings; } /// /// Move the camera to the specified pose. /// - public void MoveTo(Vector3 position, Quaternion rotation, bool keepHeight, bool quiet = false) + public void MoveTo(Vector3 position, Quaternion rotation) { if (position.Equals(Vector3.zero)) { - VRLog.Warn($"Prevented something from moving camera to pos={position} rot={rotation.eulerAngles} Trace:\n{new StackTrace(1)}"); - Console.WriteLine(); return; } - if (!quiet) - { -#if DEBUG - VRLog.Debug("Moving camera to pos={0} rot={1} Trace:\n{2}", position, rotation.eulerAngles, new StackTrace(1)); -#else - VRLog.Debug("Moving camera to pos={0} rot={1}", position, rotation.eulerAngles); -#endif - } - - +//#if DEBUG +// VRPlugin.Logger.LogDebug($"{GetType().Name}:MoveTo\n{new StackTrace(0)}"); +//#endif _lastPosition = position; _lastRotation = rotation; - // Trim out X (pitch) and Z (roll) to prevent player from being upside down and such - var trimmedRotation = Quaternion.Euler(0, rotation.eulerAngles.y, 0); - VR.Mode.MoveToPosition(position, trimmedRotation, keepHeight); + // We don't want to respect head rotations when we deal with adv text. + VR.Camera.Origin.rotation = Quaternion.Euler(0f, rotation.eulerAngles.y, 0f); + VR.Camera.Origin.position += position - VR.Camera.Head.position; + + + //// Trim out X (pitch) and Z (roll) to prevent player from being upside down and such + //var trimmedRotation = Quaternion.Euler(0, rotation.eulerAngles.y, 0); + //VR.Mode.MoveToPosition(position, trimmedRotation, keepHeight); + + //VR.Mode.MoveToPosition(position, rotation, ignoreHeight: keepHeight); OnMove?.Invoke(); } @@ -72,39 +69,40 @@ public void MoveTo(Vector3 position, Quaternion rotation, bool keepHeight, bool /// The position and rotation arguments should represent the pose /// the camera would take in the 2D version of the game. /// - /// - /// - /// - public void MaybeMoveTo(Vector3 position, Quaternion rotation, bool keepHeight) + public void MaybeMoveTo(Vector3 position, Quaternion rotation) { - MoveWithHeuristics(position, rotation, keepHeight, false); + MoveWithHeuristics(position, rotation, false); } /// /// Similar to MaybeMoveTo, but also considers the ADV fade state. /// - public void MaybeMoveADV(ADV.TextScenario textScenario, Vector3 position, Quaternion rotation, bool keepHeight) + public void MaybeMoveADV(ADV.TextScenario textScenario, Vector3 position, Quaternion rotation) { - var advFade = new Traverse(textScenario).Field("advFade").Value; + var advFade = textScenario.advFade;// new Traverse(textScenario).Field("advFade").Value; var closerPosition = AdjustAdvPosition(textScenario, position, rotation); - +#if KKS AdjustBasedOnMap(ref closerPosition, ref rotation); - - MoveWithHeuristics(closerPosition, rotation, keepHeight, !advFade.IsEnd); +#endif + MoveWithHeuristics(closerPosition, rotation, !advFade.IsEnd); } - private static Vector3 AdjustAdvPosition(TextScenario textScenario, Vector3 position, Quaternion rotation) + private Vector3 AdjustAdvPosition(TextScenario textScenario, Vector3 position, Quaternion rotation) { // Needed for zero checks later if (position.Equals(Vector3.zero)) return Vector3.zero; - var characterTransforms = textScenario.commandController?.Characters.Where(x => x.Value?.transform != null).Select(x => x.Value.transform.position).ToArray(); - if (characterTransforms != null && characterTransforms.Length > 0) + var characterTransforms = textScenario.commandController?.Characters + .Where(x => x.Value?.transform != null) + .Select(x => x.Value.transform.position); + + if (characterTransforms != null && characterTransforms.Count() > 0) { //var closerPosition = position + (rotation * Vector3.forward) * 1f; - var averageV = new Vector3(characterTransforms.Sum(x => x.x), characterTransforms.Sum(x => x.y), characterTransforms.Sum(x => x.z)); + var averageV = new Vector3(characterTransforms.Sum(x => x.x), characterTransforms.Sum(x => x.y), characterTransforms.Sum(x => x.z)) / characterTransforms.Count(); + var positionNoY = position; positionNoY.y = 0; @@ -113,11 +111,11 @@ private static Vector3 AdjustAdvPosition(TextScenario textScenario, Vector3 posi //if (Vector3.Angle(positionNoY, averageNoY) < 90) { - var closerPosition = Vector3.MoveTowards(positionNoY, averageNoY, Vector3.Distance(positionNoY, averageNoY) - TalkSceneInterpreter.TalkDistance); + var closerPosition = Vector3.MoveTowards(positionNoY, averageNoY, Vector3.Distance(positionNoY, averageNoY) - TalkSceneInterpreter.talkDistance); closerPosition.y = averageV.y + ActionCameraControl.GetPlayerHeight(); - VRLog.Warn("Adjusting position {0} -> {1} for rotation {2}", position, closerPosition, rotation.eulerAngles); + //VRLog.Warn("Adjusting position {0} -> {1} for rotation {2}", position, closerPosition, rotation.eulerAngles); return closerPosition; } @@ -134,26 +132,35 @@ public void HandleTextScenarioProgress(ADV.TextScenario textScenario) { var isFadingOut = IsFadingOut(textScenario.advFade); - VRLog.Debug("HandleTextScenarioProgress isFadingOut={0}", isFadingOut); - - if (_settings.FirstPersonADV && - FindMaleToImpersonate(out var male) && - male.objHead != null) + //VRLog.Debug("HandleTextScenarioProgress isFadingOut={0}", isFadingOut); + + // We catch it in TalkSceneInterpreter as we have visible male all the time. + //if (_settings.FirstPersonADV && + // FindMaleToImpersonate(out var male) && + // male.objHead != null) + //{ + // VRLog.Debug("Maybe impersonating male"); + // male.StartCoroutine(ImpersonateCo(isFadingOut, male.objHead.transform)); + //} + if (ShouldApproachCharacter(textScenario, out var character)) { - VRLog.Debug("Maybe impersonating male"); - male.StartCoroutine(ImpersonateCo(isFadingOut, male.objHead.transform)); - } - else if (ShouldApproachCharacter(textScenario, out var character)) - { - var distance = InCafe() ? 0.75f : TalkSceneInterpreter.TalkDistance; +#if KK + var distance = InCafe() ? 0.75f : TalkSceneInterpreter.talkDistance; +#elif KKS + var distance = TalkSceneInterpreter.talkDistance; +#endif float height; Quaternion rotation; +#if KK + if (Manager.Scene.Instance.NowSceneNames[0] == "H") +#elif KKS if (Manager.Scene.NowSceneNames[0] == "H") +#endif { VRLog.Debug("Approaching character (H)"); // TODO: find a way to get a proper height. height = character.transform.position.y + 1.3f; - rotation = character.transform.rotation * Quaternion.AngleAxis(180f, Vector3.up); + rotation = character.transform.rotation * Quaternion.Euler(0f, 180f, 0f); } else { @@ -167,7 +174,6 @@ public void HandleTextScenarioProgress(ADV.TextScenario textScenario) MoveWithHeuristics( new Vector3(cameraXZ.x, height, cameraXZ.z), rotation, - false, isFadingOut); } else @@ -177,15 +183,15 @@ public void HandleTextScenarioProgress(ADV.TextScenario textScenario) var targetRotation = target.rotation; targetPosition = AdjustAdvPosition(textScenario, targetPosition, target.rotation); - +#if KKS AdjustBasedOnMap(ref targetPosition, ref targetRotation); - +#endif if (ActionCameraControl.HeadIsAwayFromPosition(targetPosition)) - MoveWithHeuristics(targetPosition, targetRotation, false, isFadingOut); + MoveWithHeuristics(targetPosition, targetRotation, isFadingOut); } } - - private static void AdjustBasedOnMap(ref Vector3 targetPosition, ref Quaternion targetRotation) +#if KKS + private void AdjustBasedOnMap(ref Vector3 targetPosition, ref Quaternion targetRotation) { var insideMyRoom = UnityEngine.SceneManagement.SceneManager.GetActiveScene().name == "GasyukuMyroom"; if (insideMyRoom) @@ -200,77 +206,125 @@ private static void AdjustBasedOnMap(ref Vector3 targetPosition, ref Quaternion } } } +#endif + - private static bool IsFadingOut(ADVFade fade) + private bool IsFadingOut(ADVFade fade) { - bool IsFadingOutSub(ADVFade.Fade f) => f.initColor.a > 0.5f && !f.IsEnd; +#if KK + static bool IsFadingOutSub(ADVFade.Fade f) => f.initColor.a > 0.5f && !f.IsEnd; return IsFadingOutSub(fade.front) || IsFadingOutSub(fade.back); +#else + // KKS doesn't have working advFade. + return Scene.sceneFadeCanvas.isFading; +#endif + } - private IEnumerator ImpersonateCo(bool isFadingOut, Transform head) + //private IEnumerator ImpersonateCo(bool isFadingOut, Transform head) + //{ + // // For reasons I don't understand, the male may not have a correct pose + // // until later in the update loop. + // yield return CoroutineUtils.WaitForEndOfFrame; + // MoveWithHeuristics( + // head.TransformPoint(0, 0.15f, 0.15f), + // head.rotation, + // false, + // isFadingOut); + //} + + public void Impersonate(ChaControl chara) { - // For reasons I don't understand, the male may not have a correct pose - // until later in the update loop. - yield return new WaitForEndOfFrame(); - MoveWithHeuristics( - head.TransformPoint(0, 0.15f, 0.15f), - head.rotation, - false, - isFadingOut); + if (chara != null && chara.objTop.activeSelf) + { + // Some inconsistent interference happens. + chara.visibleAll = true; + chara.fileStatus.visibleBodyAlways = true; + + var eyes = chara.objHeadBone.transform + .Find("cf_J_N_FaceRoot/cf_J_FaceRoot/cf_J_FaceBase/cf_J_FaceUp_ty/cf_J_FaceUp_tz/cf_J_Eye_tz"); + var position = eyes.TransformPoint(0f, KoikatuInterpreter.Settings.PositionOffsetY, KoikatuInterpreter.Settings.PositionOffsetZ); + MoveTo(position, eyes.rotation); + } } - - private void MoveWithHeuristics(Vector3 position, Quaternion rotation, bool keepHeight, bool pretendFading) + private void MoveWithHeuristics(Vector3 position, Quaternion rotation, bool pretendFading) { - var fade = Manager.Scene.sceneFadeCanvas; - var fadeOk = fade.isEnd; //(fade._Fade == SimpleFade.Fade.Out) ^ fade.IsEnd; +#if KK + var fade = Manager.Scene.Instance.sceneFade; + bool fadeOk = (fade._Fade == SimpleFade.Fade.Out) ^ fade.IsEnd; if (pretendFading || fadeOk || IsDestinationFar(position, rotation)) - MoveTo(position, rotation, keepHeight); +#elif KKS + //var fade = Manager.Scene.sceneFadeCanvas; + if (Features.VRFade.IsFade || IsDestinationFar(position, rotation)) //(pretendFading || IsDestinationFar(position, rotation)) + + // No clue what this condition should be about, in KKS it doesn't work (always true). + // KK has no problem with it('s alternative). + //var fadeOk = fade.isEnd; //(fade._Fade == SimpleFade.Fade.Out) ^ fade.IsEnd; +#endif + { +#if DEBUG + VRPlugin.Logger.LogDebug($"MoveWithHeuristics:Move:Pos = {position}"); +#endif + MoveTo(position, rotation); + } +#if DEBUG else - VRLog.Debug("Not moving because heuristic conditions are not met"); + { + VRPlugin.Logger.LogDebug($"MoveWithHeuristics:Stay:Pos = {position}"); + } +#endif } private bool IsDestinationFar(Vector3 position, Quaternion rotation) { var distance = (position - _lastPosition).magnitude; - var angleDistance = Mathf.DeltaAngle(rotation.eulerAngles.y, _lastRotation.eulerAngles.y); - return 1f < distance / 2f + angleDistance / 90f; - } - - private static bool FindMaleToImpersonate(out ChaControl male) - { - male = null; - - if (!Manager.Character.IsInstance()) return false; - - var males = Manager.Character.dictEntryChara.Values - .Where(ch => ch.isActiveAndEnabled && ch.sex == 0 && ch.objTop?.activeSelf == true && ch.visibleAll) - .ToArray(); - if (males.Length == 1) - { - male = males[0]; - return true; - } - - return false; + var deltaAngle = Mathf.Abs(Mathf.DeltaAngle(rotation.eulerAngles.y, _lastRotation.eulerAngles.y)); + return 1f < distance * 0.5f + deltaAngle / 90f; } - private static bool ShouldApproachCharacter(ADV.TextScenario textScenario, out ChaControl control) +// private static bool FindMaleToImpersonate(out ChaControl male) +// { +// male = null; + +// if (!Manager.Character.IsInstance()) return false; + +//#if KK +// var males = Manager.Character.Instance.dictEntryChara.Values +//#elif KKS +// var males = Manager.Character.dictEntryChara.Values +//#endif +// .Where(ch => ch.isActiveAndEnabled && ch.sex == 0 && ch.objTop?.activeSelf == true && ch.visibleAll) +// .ToArray(); +// if (males.Length == 1) +// { +// male = males[0]; +// return true; +// } +// return false; +// } + + private bool ShouldApproachCharacter(ADV.TextScenario textScenario, out ChaControl control) { - if ((Manager.Scene.NowSceneNames[0] == "H" || textScenario.BGParam.visible) && - textScenario.currentChara != null) +#if KK + if ((Manager.Scene.Instance.NowSceneNames[0] == "H" || textScenario.BGParam.visible) +#elif KKS + if ((Manager.Scene.NowSceneNames[0] == "H" || textScenario.BGParam.visible) +#endif + && textScenario.currentChara != null) { control = textScenario.currentChara.chaCtrl; return true; } - control = null; return false; } - private static bool InCafe() +#if KK + private bool InCafe() { - return ActionScene.initialized && - ActionScene.instance.transform.Find("cafeChair"); //todo taken from kk, needs a test, probably not working + return Manager.Game.IsInstance() + && Manager.Game.Instance.actScene.transform.Find("cafeChair"); } +#endif } } diff --git a/MainGameVR/Camera/VREffector.cs b/Shared/Camera/VREffector.cs similarity index 90% rename from MainGameVR/Camera/VREffector.cs rename to Shared/Camera/VREffector.cs index 96f7c9a..5339f58 100644 --- a/MainGameVR/Camera/VREffector.cs +++ b/Shared/Camera/VREffector.cs @@ -3,7 +3,7 @@ using UnityStandardAssets.ImageEffects; using VRGIN.Core; -namespace KKS_VR.Camera +namespace KK_VR.Camera { /// /// A component to be attached to the VR camera. Ensures that it has the @@ -13,8 +13,6 @@ class VREffector : ProtectedBehaviour { protected override void OnUpdate() { - base.OnUpdate(); - var blueprint = VR.Camera.Blueprint; if (blueprint && _source != blueprint) { @@ -25,8 +23,6 @@ protected override void OnUpdate() protected override void OnLateUpdate() { - base.OnLateUpdate(); - _fog.UpdateEnabled(); _amplifyColor.UpdateEnabled(); if (_amplifyColor.mirror && _amplifyColor.source) @@ -41,6 +37,7 @@ protected override void OnLateUpdate() { _sunShafts.mirror.sunColor = _sunShafts.source.sunColor; _sunShafts.mirror.sunTransform = _sunShafts.source.sunTransform; + TweakSunShaftSettings(_sunShafts.mirror); } _vignette.UpdateEnabled(); @@ -113,7 +110,20 @@ private static void TweakSunShaftSettings(SunShafts sunShafts) sunShafts.sunShaftIntensity = 0.6f; } - struct Mirrored + /// + /// Keep up with constant VFX updates + /// + internal static void Refresh() + { + var effector = VR.Camera.GetComponent(); + if (effector != null && VR.Camera.Blueprint != null) + { + effector.HandleNewGameCamera(VR.Camera.Blueprint); + } + } + + + struct Mirrored where T : Behaviour { public T mirror; diff --git a/Shared/Caress/CaressUtil.cs b/Shared/Caress/CaressUtil.cs new file mode 100644 index 0000000..ebce327 --- /dev/null +++ b/Shared/Caress/CaressUtil.cs @@ -0,0 +1,20 @@ +using System.Collections; +using System.Collections.Generic; +using HarmonyLib; + +namespace KK_VR.Caress +{ + public class CaressUtil + { + /// + /// Send a synthetic click event to the hand controls. + /// + public static IEnumerator ClickCo() + { + var consumed = false; + HandCtrlHooks.InjectMouseButtonDown(0, () => consumed = true); + while (!consumed) yield return null; + HandCtrlHooks.InjectMouseButtonUp(0); + } + } +} diff --git a/MainGameVR/Caress/HandCtrlHooks.cs b/Shared/Caress/HandCtrlHooks.cs similarity index 86% rename from MainGameVR/Caress/HandCtrlHooks.cs rename to Shared/Caress/HandCtrlHooks.cs index a900a49..14306bb 100644 --- a/MainGameVR/Caress/HandCtrlHooks.cs +++ b/Shared/Caress/HandCtrlHooks.cs @@ -4,16 +4,17 @@ using System.Reflection; using System.Reflection.Emit; using HarmonyLib; +using KK_VR.Handlers; using UnityEngine; -namespace KKS_VR.Caress +namespace KK_VR.Caress { /// /// Allows injecting simulated user inputs to HandCtrl. This is similar to /// faking mouse clicks using VR.Input, but is safer because it doesn't /// accidentally interact with the game UI. /// - internal class HandCtrlHooks + public class HandCtrlHooks { private static HandCtrlHooks _instance; @@ -81,13 +82,16 @@ public static float GetAxis(string name) private static HandCtrlHooks GetInstance() { - if (_instance == null) _instance = new HandCtrlHooks(); + _instance ??= new HandCtrlHooks(); return _instance; } private ButtonHandler GetButtonHandler(int button) { - if (!_buttonHandlers.ContainsKey(button)) _buttonHandlers.Add(button, new ButtonHandler(button)); + if (!_buttonHandlers.ContainsKey(button)) + { + _buttonHandlers.Add(button, new ButtonHandler(button)); + } return _buttonHandlers[button]; } @@ -217,5 +221,30 @@ inst.operand is MethodInfo method && yield return inst; } } + + [HarmonyPatch] + internal class HandCtrlHelperHook + { + // Should be safe kill switch. + // Triggered by overlap menus too (does so beforehand). + [HarmonyPostfix, HarmonyPatch(typeof(HandCtrl), nameof(HandCtrl.ForceFinish))] + public static void ForceFinishPostfix() + { + if (MouthGuide.Instance != null) + MouthGuide.Instance.Halt(disengage: false); + } + + //[HarmonyPostfix, HarmonyPatch(typeof(HAibu), nameof(HAibu.GotoDislikes))] + //public static void GotoDislikesPostfix() + //{ + // var helper = CaressHelper.Instance; + // if (helper != null && !helper.IsEndKissCo) + // { + // helper.Halt(disengage: true); + // } + //} + + } } + } diff --git a/Shared/Constants.cs b/Shared/Constants.cs index 10d6d7e..983f540 100644 --- a/Shared/Constants.cs +++ b/Shared/Constants.cs @@ -1,4 +1,4 @@ -namespace KKS_VR +namespace KK_VR { internal static class Constants { diff --git a/Shared/Controls/GameplayTool.cs b/Shared/Controls/GameplayTool.cs new file mode 100644 index 0000000..f913140 --- /dev/null +++ b/Shared/Controls/GameplayTool.cs @@ -0,0 +1,227 @@ +using UnityEngine; +using VRGIN.Controls; +using VRGIN.Controls.Tools; +using VRGIN.Core; +using KK_VR.Interpreters; +using Valve.VR; +using KK_VR.Holders; +//using EVRButtonId = Unity.XR.OpenVR.EVRButtonId; + +namespace KK_VR.Controls +{ + public class GameplayTool : Tool + { + private int _index; + + private KoikatuMenuTool _menu; + + private KoikatuMenuHandler _menuHandler; + + private Controller.TrackpadDirection _lastDirection; + + private GripMove _grip; + + internal bool IsGrip => _grip != null; + + internal bool IsInit => _init; + + private bool _init; + public override Texture2D Image + { + get; + } + + protected override void OnDisable() + { + DestroyGripMove(); + base.OnDisable(); + } + protected override void OnDestroy() + { + + } + protected override void OnEnable() + { + if (!_init + && Neighbor != null + && Neighbor.Tools[0] is GameplayTool tool + && tool.IsInit) + { + // Ancient bug, can happen if controller was asleep at the VRGIN's init phase. + OnRenderModelLoaded(); + } + base.OnEnable(); + } + protected override void OnUpdate() + { + if (_init) + { + HandleInput(); + _grip?.HandleGrabbing(); + } + } + internal void OnRenderModelLoaded() + { + _init = true; + _index = Owner == VR.Mode.Left ? 0 : 1; + _menu = new KoikatuMenuTool(_index); + _menuHandler = new KoikatuMenuHandler(Owner); + } + internal void DestroyGripMove() + { + _grip = null; + KoikatuInterpreter.SceneInput.OnGripMove(_index, active: false); + } + internal void LazyGripMove(int avgFrame) + { + // In all honesty tho, the proper name would be retarded, not lazy as it does way more in this mode and lags behind. + _grip?.StartLag(avgFrame); + } + internal void AttachGripMove(Transform attachPoint) + { + _grip?.AttachGripMove(attachPoint); + } + internal void UnlazyGripMove() + { + _grip?.StopLag(); + } + internal void HideLaser() + { + _menuHandler?.SetLaserVisibility(false); + } + + private void HandleInput() + { + var direction = Owner.GetTrackpadDirection(); + var menuInteractable = !_menu.IsAttached && _menuHandler.CheckMenu(); + + if (menuInteractable && !_menuHandler.LaserVisible) + { + // Don't show laser if something of interest is going on. + var handler = HandHolder.GetHand(_index).Handler; + if (KoikatuInterpreter.SceneInput.IsBusy || (handler != null && handler.IsBusy)) + { + menuInteractable = false; + } + else + { + _menuHandler.SetLaserVisibility(true); + } + } + + if (Controller.GetPressDown(EVRButtonId.k_EButton_ApplicationMenu)) + { + if (!KoikatuInterpreter.SceneInput.OnButtonDown(_index, EVRButtonId.k_EButton_ApplicationMenu, direction)) + { + KoikatuMenuTool.ToggleState(); + } + } + + if (Controller.GetPressDown(EVRButtonId.k_EButton_SteamVR_Trigger)) + { + if (menuInteractable) + { + _menuHandler.OnTrigger(true); + } + else if (!KoikatuInterpreter.SceneInput.OnButtonDown(_index, EVRButtonId.k_EButton_SteamVR_Trigger, direction)) + { + _grip?.OnTrigger(true); + } + + } + else if (Controller.GetPressUp(EVRButtonId.k_EButton_SteamVR_Trigger)) + { + if (menuInteractable) + { + _menuHandler.OnTrigger(false); + } + else + { + _grip?.OnTrigger(false); + KoikatuInterpreter.SceneInput.OnButtonUp(_index, EVRButtonId.k_EButton_SteamVR_Trigger, direction); + } + } + + if (Controller.GetPressDown(EVRButtonId.k_EButton_Grip)) + { + + if (_menu.IsAttached) + { + _menu.AbandonGUI(); + } + else if (menuInteractable) + { + _menuHandler.OnGrip(true); + } + // If particular interpreter doesn't want grip move right now, it will be blocked. + else if (!KoikatuInterpreter.SceneInput.OnButtonDown(_index, EVRButtonId.k_EButton_Grip, direction)) + { + _grip = new GripMove(HandHolder.GetHand(_index), HandHolder.GetHand(_index == 0 ? 1 : 0)); + // Grab initial Trigger/Touchpad modifiers, if they were already pressed. + if (Controller.GetPress(EVRButtonId.k_EButton_SteamVR_Trigger)) _grip.OnTrigger(true); + if (Controller.GetPress(EVRButtonId.k_EButton_SteamVR_Touchpad)) _grip.OnTouchpad(true); + KoikatuInterpreter.SceneInput.OnGripMove(_index, active: true); + } + } + else if (Controller.GetPressUp(EVRButtonId.k_EButton_Grip)) + { + if (menuInteractable) + { + _menuHandler.OnGrip(false); + } + else + { + KoikatuInterpreter.SceneInput.OnButtonUp(_index, EVRButtonId.k_EButton_Grip, direction); + if (_grip != null) + { + DestroyGripMove(); + } + } + } + + if (Controller.GetPressDown(EVRButtonId.k_EButton_SteamVR_Touchpad)) + { + if (menuInteractable) + { + _menuHandler.OnTouchpad(true); + } + else if (!KoikatuInterpreter.SceneInput.OnButtonDown(_index, EVRButtonId.k_EButton_SteamVR_Touchpad, direction)) + { + _grip?.OnTouchpad(true); + } + } + else if (Controller.GetPressUp(EVRButtonId.k_EButton_SteamVR_Touchpad)) + { + if (menuInteractable) + { + _menuHandler.OnTouchpad(false); + } + else + { + _grip?.OnTouchpad(false); + KoikatuInterpreter.SceneInput.OnButtonUp(_index, EVRButtonId.k_EButton_SteamVR_Touchpad, direction); + } + } + + if (_lastDirection != direction) + { + if (menuInteractable) + { + _menuHandler.SetLastDirection(direction); + } + else + { + if (_lastDirection != VRGIN.Controls.Controller.TrackpadDirection.Center) + { + KoikatuInterpreter.SceneInput.OnDirectionUp(_index, _lastDirection); + } + if (direction != VRGIN.Controls.Controller.TrackpadDirection.Center) + { + KoikatuInterpreter.SceneInput.OnDirectionDown(_index, direction); + } + } + _lastDirection = direction; + } + } + } +} diff --git a/Shared/Controls/GripMove.cs b/Shared/Controls/GripMove.cs new file mode 100644 index 0000000..c5da60f --- /dev/null +++ b/Shared/Controls/GripMove.cs @@ -0,0 +1,264 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Valve.VR; +using VRGIN.Controls; +using VRGIN.Core; +using VRGIN.Helpers; +using UnityEngine; +using KK_VR.Handlers; +using KK_VR.Holders; +using KK_VR.Interpreters; + +namespace KK_VR.Controls +{ + // Placed directly in KK part for comfy use, and easy new features. + internal class GripMove + { + private readonly GameplayTool _other; + private readonly Transform _controller; + + //private readonly TravelDistanceRumble _travelRumble; + + + /// + /// If present, orbiting it instead of changing Yaw around controller. + /// + private Transform _attachPoint; + private Vector3 _prevAttachVec; + private Vector3 _prevAttachPos; + //private Quaternion _prevAttachRot; + + private GripMoveLag _moveLag; + + // Is current instance active or superseded by neighbor. + private bool _main; + + private bool _otherGrip; + private bool _alterYaw; + private bool _alterRotation; + + private Vector3 _prevPos; + private Quaternion _prevRot; + private readonly bool _rotInPlace = KoikatuInterpreter.Settings.GripMoveLimitRotation; + + internal GripMove(HandHolder hand, HandHolder otherHand) + { + _main = true; + _controller = hand.Controller.transform; + _other = otherHand.Tool; + _otherGrip = _other.IsGrip; + // Feels too much with those forced vibrations on trigger/grip - press/release. + //_travelRumble = new TravelDistanceRumble(500, 0.1f, _owner.transform) + //{ + // UseLocalPosition = true + //}; + + // _travelRumble.Reset(); + //_owner.StartRumble(_travelRumble); + + _prevPos = _controller.position; + _prevRot = _controller.rotation; + } + + /// + /// Used as means to acquire highly precise offsets. Adds extra source to evaluate movements and provides the point we start to "orbit". + /// + internal void AttachGripMove(Transform attachPoint) + { + // All calculations are done through deltas due to saturated input. + _attachPoint = attachPoint; + _prevAttachPos = _attachPoint.position; + _moveLag.ResetPositions(Vector3.zero); + + // Necessary is we started with trigger already. + _prevAttachVec = VR.Camera.Head.TransformPoint(new Vector3(0f, 0.05f, 0f)) - attachPoint.position; + // With full trigger + touchpad. + //_prevAttachRot = _attachPoint.rotation; + } + + public void HandleGrabbing() + { + // We check if other controller wants to joint us, or override control if other has ended gripMove. + // Then we use deltas of orientation to setup origin orientation directly, or through evaluation of multiple frames and "averaging it out" if current action requests it. + if (_main) + { + if (_otherGrip && !_other.IsGrip) + { + _otherGrip = false; + } + if (!_otherGrip && _other.IsGrip) + { + _main = false; + } + else + { + var origin = VR.Camera.SteamCam.origin; + if (_alterYaw) + { + var deltaRot = _prevRot * Quaternion.Inverse(_controller.rotation); + //var invRot = Quaternion.Inverse(_prevRot) * _owner.transform.rotation; + if (_moveLag == null) + { + if (_alterRotation) + { + origin.rotation = deltaRot * origin.rotation; + } + else + { + origin.RotateAround(_controller.position, Vector3.up, deltaRot.eulerAngles.y); + } + origin.position += _prevPos - _controller.position; + } + else + { + if (_alterRotation) + { + if (_attachPoint == null) + { + if (_rotInPlace) + { + var head = VR.Camera.SteamCam.head; + var preRotPos = head.position; + _moveLag.SetDeltaRotation(deltaRot); + origin.position += preRotPos - head.position; + } + else + { + _moveLag.SetDeltaRotation(deltaRot); + origin.position += (_prevPos - _controller.position); + } + //_moveLag.SetPositionAndRotation(deltaRot); + } + else + { + _moveLag.SetDeltaPositionAndRotation( + (_attachPoint.position - _prevAttachPos), + deltaRot + ); + _prevAttachPos = _attachPoint.position; + //_prevAttachRot = _attachPoint.rotation; + } + } + else + { + if (_attachPoint == null) + { + _moveLag.SetDeltaRotation(Quaternion.Euler(0f, deltaRot.eulerAngles.y, 0f)); + origin.position += _prevPos - _controller.position; + + //_moveLag.SetPositionAndRotation( + // //origin.position + + // _controller.transform.position + + // deltaRotY * vec, + // //deltaRotY * (origin.position - new Vector3(_controller.position.x, origin.position.y, _controller.position.z)), + // deltaRotY); + } + else + { + var newAttachVec = deltaRot * _prevAttachVec; + _moveLag.SetDeltaPositionAndRotation( + (newAttachVec - _prevAttachVec) + (_prevPos - _controller.position) + (_attachPoint.position - _prevAttachPos), + deltaRot + ); + _prevAttachVec = newAttachVec; + _prevAttachPos = _attachPoint.position; + } + } + + } + } + else + { + if (_moveLag == null) + { + origin.position += _prevPos - _controller.position; + } + else + { + if (_attachPoint == null) + { + _moveLag.SetPosition(); + } + else + { + _moveLag.SetDeltaPosition(_attachPoint.position - _prevAttachPos + (_prevPos - _controller.position)); + _prevAttachPos = _attachPoint.position; + } + } + } + } + _prevPos = _controller.position; + _prevRot = _controller.rotation; + } + else + { + if (!_other.IsGrip) + { + _main = true; + _otherGrip = false; + _prevPos = _controller.position; + _prevRot = _controller.rotation; + } + } + } + + internal void StartLag(int avgFrame) + { + _moveLag = new GripMoveLag(_controller, avgFrame); + } + internal void StopLag() + { + _moveLag = null; + _attachPoint = null; + } + internal void OnTrigger(bool press) + { + _alterYaw = press; + if (press) + { + UpdateAttachVec(); + if (_moveLag == null && KoikatuInterpreter.Settings.GripMoveStabilize == Settings.KoikatuSettings.GripMoveStabilization.YawAndRotation) + { + _moveLag = new GripMoveLag(_controller, KoikatuInterpreter.ScaleWithFps(KoikatuInterpreter.Settings.GripMoveStabilizationAmount)); + } + } + } + + internal void OnTouchpad(bool press) + { + if (KoikatuInterpreter.Settings.GripMoveEnableRotation) + { + _alterRotation = press; + if (press) + { + if (_moveLag == null + && (KoikatuInterpreter.Settings.GripMoveStabilize == Settings.KoikatuSettings.GripMoveStabilization.YawAndRotation + || KoikatuInterpreter.Settings.GripMoveStabilize == Settings.KoikatuSettings.GripMoveStabilization.OnlyRotation)) + { + _moveLag = new GripMoveLag(_controller, KoikatuInterpreter.ScaleWithFps(KoikatuInterpreter.Settings.GripMoveStabilizationAmount)); + } + } + else + { + UpdateAttachVec(); + if (_attachPoint == null && _moveLag != null && KoikatuInterpreter.Settings.GripMoveStabilize == Settings.KoikatuSettings.GripMoveStabilization.OnlyRotation) + { + _moveLag = null; + } + } + } + + } + private void UpdateAttachVec() + { + // Due to vec being utilized only by Trigger-mode, other modes don't update it, so we do it on button input. + + if (_moveLag != null && _attachPoint != null) + { + _prevAttachVec = VR.Camera.SteamCam.head.TransformPoint(new Vector3(0f, 0.05f, 0f)) - _attachPoint.position; + } + } + } +} diff --git a/Shared/Controls/GripMoveLag.cs b/Shared/Controls/GripMoveLag.cs new file mode 100644 index 0000000..bc4a14d --- /dev/null +++ b/Shared/Controls/GripMoveLag.cs @@ -0,0 +1,274 @@ +using ADV.Commands.Object; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using Valve.VR; +using VRGIN.Core; +using static UnityEngine.UI.GridLayoutGroup; + +namespace KK_VR.Handlers +{ + /// + /// Evaluates orientation from previous frames and sets as needed. + /// + internal class GripMoveLag + { + private readonly Transform _origin = VR.Camera.SteamCam.origin; + private readonly Transform _head = VR.Camera.SteamCam.head; + private readonly Transform _controller; + private readonly int _frameAmount; + private readonly float _frameAmountCoef; + private int _frameIndex; + private readonly float[] _frameCoefs; + private readonly Quaternion[] _prevRotations; + private readonly Vector3[] _prevPositions; + private Vector3 _lastAvgPos; + private bool _resetRequired; + internal GripMoveLag(Transform controller, int frameAvg, Vector3 inheritedPosition = default) + { + _controller = controller; + _frameAmount = frameAvg; + _frameAmountCoef = 1f / frameAvg; + _prevRotations = new Quaternion[frameAvg]; + _prevPositions = new Vector3[frameAvg]; + _frameCoefs = new float[frameAvg]; + _lastAvgPos = _controller.position; + + // Coefficients can be customized to change rotation follow type, even non-linear should look good. + for (var i = 0; i < frameAvg; i++) + { + _frameCoefs[i] = 1f / (i + 2f); + } + var pos = inheritedPosition == Vector3.zero ? _controller.position : inheritedPosition; + var rot = Quaternion.identity; + for (var i = 0; i < frameAvg; i++) + { + _prevRotations[i] = rot; + _prevPositions[i] = pos; + } + } + /// + /// Fills previous positions with supplied whatever. + /// + internal void ResetPositions(Vector3 position) + { + for (var i = 0; i < _frameAmount; i++) + { + _prevPositions[i] = position; + } + _lastAvgPos = position; + _resetRequired = false; + } + /// + /// Sets Origin to a "catching up" rotation. Same as other methods, but rotation only. + /// + internal void SetDeltaRotation(Quaternion rotation) + { + _resetRequired = true; + _prevRotations[_frameIndex] = rotation; + _frameIndex++; + if (_frameIndex == _frameAmount) _frameIndex = 0; + UpdateRotation(); + } + private void UpdateRotation() + { + // Not average at all, is a biased catch-up. All averages I've seen suck tremendously. + // The most stale frame is grabbed on init part. + var count = _frameAmount - 1; + + // The most stale rotation. Doesn't get touched in the loop. + var avgRot = _prevRotations[_frameIndex]; + var j = _frameIndex; + for (var i = 0; i < count; i++) + { + if (++j == _frameAmount) j = 0; + avgRot = Quaternion.Lerp(avgRot, _prevRotations[j], _frameCoefs[i]); + } + _origin.rotation = avgRot * _origin.rotation; + } + /// + /// Sets Origin to an "average" of supplied orientation. + /// + internal void SetPositionAndRotation(Vector3 position, Quaternion rotation) + { + _prevRotations[_frameIndex] = rotation; + if (_resetRequired) + { + ResetPositions(position); + } + else + { + _prevPositions[_frameIndex] = position; + } + _frameIndex++; + if (_frameIndex == _frameAmount) _frameIndex = 0; + UpdatePositionAndRotation(); + } + internal void SetDeltaPositionAndRotation(Vector3 deltaPosition, Quaternion deltaRotation) + { + _prevRotations[_frameIndex] = deltaRotation; + _prevPositions[_frameIndex] = deltaPosition; + _frameIndex++; + if (_frameIndex == _frameAmount) _frameIndex = 0; + UpdateDeltaPositionAndRotation(); + } + private void UpdateDeltaPositionAndRotation() + { + // The most stale frame is grabbed on init part. + var count = _frameAmount - 1; + + // The most stale rotation. Doesn't get touched in the loop. + var avgRot = _prevRotations[_frameIndex]; + // Position that won't get touched in the loop. Don't care about the order, we use average. + var avgPos = _prevPositions[count]; + + var j = _frameIndex; + for (var i = 0; i < count; i++) + { + if (++j == _frameAmount) j = 0; + avgRot = Quaternion.Lerp(avgRot, _prevRotations[j], _frameCoefs[i]); + avgPos += _prevPositions[i]; + } + + avgPos *= _frameAmountCoef; + var preRotPos = _head.position; + _origin.rotation = avgRot * _origin.rotation; + _origin.position += (preRotPos - _head.position) + avgPos; + } + + /// + /// Sets Origin to an "average" of supplied rotation and Controller position. + /// + internal void SetPositionAndRotation(Quaternion rotation) + { + // Can also reset rotations when switching away from this method, + // but hey, those movement look very human-esque, + // well, as long as they weren't something crazy, like 180 deg/frame. + _prevRotations[_frameIndex] = rotation; + if (_resetRequired) + { + ResetPositions(_controller.position); + } + else + { + _prevPositions[_frameIndex] = _controller.position; + } + _frameIndex++; + if (_frameIndex == _frameAmount) _frameIndex = 0; + UpdatePositionAndRotation(); + } + private void UpdatePositionAndRotation() + { + // The most stale frame is grabbed on init part. + var count = _frameAmount - 1; + + // The most stale rotation. Doesn't get touched in the loop. + var avgRot = _prevRotations[_frameIndex]; + // Position that won't get touched in the loop. Don't care about the order, we use average. + var avgPos = _prevPositions[count]; + + var j = _frameIndex; + for (var i = 0; i < count; i++) + { + if (++j == _frameAmount) j = 0; + avgRot = Quaternion.Lerp(avgRot, _prevRotations[j], _frameCoefs[i]); + avgPos += _prevPositions[i]; + } + + avgPos *= _frameAmountCoef; + var preRotPos = _head.position; + _origin.rotation = avgRot * _origin.rotation; + //_origin.position += (preRotPos - _origin.position) + (_lastAvgPos - avgPos); + _origin.position += (preRotPos - _head.position) + (_lastAvgPos - avgPos); + _lastAvgPos = avgPos; + } + /// + /// Sets Origin to an average of Controller position. + /// + internal void SetPosition() + { + if (_resetRequired) + { + ResetPositions(_controller.position); + } + else + { + _prevPositions[_frameIndex] = _controller.position; + } + _frameIndex++; + if (_frameIndex == _frameAmount) _frameIndex = 0; + UpdatePosition(inverse: true); + } + /// + /// Sets Origin to an average of supplied delta of positions. + /// + internal void SetDeltaPosition(Vector3 deltaPosition) + { + //if (_resetRequired) + //{ + // ResetPositions(deltaPosition); + //} + //else + //{ + _prevPositions[_frameIndex] = deltaPosition; + //} + _frameIndex++; + if (_frameIndex == _frameAmount) _frameIndex = 0; + UpdatePositionDelta(); + } + private void UpdatePositionDelta() + { + var avgPos = Vector3.zero; + for (var i = 0; i < _frameAmount; i++) + { + avgPos += _prevPositions[i]; + } + avgPos *= _frameAmountCoef; + _origin.position += avgPos; + } + + private void UpdatePosition(bool inverse) + { + var avgPos = Vector3.zero; + for (var i = 0; i < _frameAmount; i++) + { + avgPos += _prevPositions[i]; + } + avgPos *= _frameAmountCoef; + if (inverse) + { + _origin.position += _lastAvgPos - avgPos; + } + else + { + _origin.position += avgPos - _lastAvgPos; + } + _lastAvgPos = avgPos; + } + + + // Way easier to create a new one instead. + //internal void ChangeLagAmount(bool increase) + //{ + // if (increase) + // { + // if (_frameCurAmount != _frameCeiling) + // { + // _frameCurAmount++; + // _frameCurAmountCoef = 1f / _frameCurAmount; + // } + // } + // else + // { + // if (_frameCurAmount != _frameFloor) + // { + // _frameCurAmount--; + // _frameCurAmountCoef = 1f / _frameCurAmount; + // } + // } + //} + + } +} diff --git a/Shared/Controls/KoikatuMenuHandler.cs b/Shared/Controls/KoikatuMenuHandler.cs new file mode 100644 index 0000000..1411d47 --- /dev/null +++ b/Shared/Controls/KoikatuMenuHandler.cs @@ -0,0 +1,434 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using Valve.VR; +using VRGIN.Core; +using VRGIN.Controls; +using VRGIN.Visuals; +using System.Linq; +using VRGIN.Native; +using static VRGIN.Native.WindowsInterop; +using KK_VR.Holders; + +namespace KK_VR.Controls +{ + internal class KoikatuMenuHandler + { /// + /// Handler that is in charge of the menu interaction with controllers + /// + private readonly Transform _controller; + private const int MOUSE_STABILIZER_THRESHOLD = 30; // pixels + //private Controller.Lock _LaserLock = Controller.Lock.Invalid; + private LineRenderer Laser; + private Vector2? _mouseDownPosition; + private GUIQuad _quad; + private Vector3 _scaleVector; + private Buttons _pressedButtons; + private Controller.TrackpadDirection _lastDirection; + private float? _NextScrollTime; + internal bool LaserVisible + { + get + { + return Laser.gameObject.activeSelf; + } + set + { + // Toggle laser + Laser.gameObject.SetActive(value); + //VRPlugin.Logger.LogDebug($"LaserVisible = {value}"); + // Initialize start position + if (value) + { + Laser.SetPosition(0, Laser.transform.position); + Laser.SetPosition(1, Laser.transform.position); + } + else + { + _mouseDownPosition = null; + } + } + } + + internal void SetLastDirection(Controller.TrackpadDirection direction) => _lastDirection = direction; + + internal KoikatuMenuHandler(Controller controller) + { + //_controller = controller; + _scaleVector = new Vector2((float)VRGUI.Width / Screen.width, (float)VRGUI.Height / Screen.height); + _controller = controller.transform; + var attachPosition = controller.FindAttachPosition("tip"); + + if (!attachPosition) + { + VRLog.Error("Attach position not found for laser!"); + attachPosition = _controller; + } + Laser = new GameObject("Laser").AddComponent(); + Laser.transform.SetParent(_controller, worldPositionStays: false); // (attachPosition, false); + //Laser.transform.SetParent(controller.transform, false); // (attachPosition, false); + Laser.transform.SetPositionAndRotation(attachPosition.position, attachPosition.rotation); + Laser.material = new Material(Shader.Find("Sprites/Default")); + Laser.material.renderQueue += 5000; + Laser.startColor = new Color(0f, 1f, 1f, 0f); + Laser.endColor = Color.cyan; + + if (SteamVR.instance.hmd_TrackingSystemName == "lighthouse") + { + Laser.transform.localRotation = Quaternion.Euler(60, 0, 0); + Laser.transform.position += Laser.transform.forward * 0.06f; + } + else + { +#if KK + Laser.transform.localRotation *= Quaternion.Euler(-10f, 0, 0); +#else + Laser.transform.localRotation *= Quaternion.Euler(25f, 0, 0); +#endif + } + Laser.SetVertexCount(2); + Laser.useWorldSpace = true; + Laser.SetWidth(0.002f, 0.002f); + LaserVisible = false; + } + + enum Buttons + { + Left = 1, + Right = 2, + Middle = 4, + } + + internal bool CheckMenu() + { + if (LaserVisible) + { + CheckInput(); + return UpdateLaser(); + } + else + { + return CheckForNearMenu(); + } + } + + internal void SetLaserVisibility(bool show) + { + LaserVisible = show; + } + + internal void OnTrigger(bool press) + { + if (press) + { + VR.Input.Mouse.LeftButtonDown(); + _pressedButtons |= Buttons.Left; + _mouseDownPosition = Vector2.Scale(new Vector2(Input.mousePosition.x, Screen.height - Input.mousePosition.y), _scaleVector); + } + else + { + VR.Input.Mouse.LeftButtonUp(); + _pressedButtons &= ~Buttons.Left; + _mouseDownPosition = null; + } + } + internal void OnGrip(bool press) + { + if (press && !_quad.IsOwned) + { + _quad.transform.SetParent(_controller, worldPositionStays: true); + _quad.IsOwned = true; + } + else + { + AbandonGUI(); + } + } + internal void OnTouchpad(bool press) + { + if (press) + { + if (_quad.IsOwned && (_pressedButtons & Buttons.Left) != 0) + { + _quad.transform.SetParent(VR.Manager.transform, worldPositionStays: true); + _quad.IsOwned = false; + } + else + { + + VR.Input.Mouse.MiddleButtonDown(); + _pressedButtons |= Buttons.Middle; + } + } + else if ((_pressedButtons & Buttons.Middle) != 0) + { + VR.Input.Mouse.MiddleButtonUp(); + _pressedButtons &= ~Buttons.Middle; + } + } + private void CheckInput() + { + switch (_lastDirection) + { + case Controller.TrackpadDirection.Up: + if (_quad.IsOwned) + { + if ((_pressedButtons & Buttons.Left) != 0) + { + MoveGui(Time.deltaTime); + } + else + { + ChangeGuiSize(Time.deltaTime); + } + } + else + { + Scroll(1); + } + break; + case Controller.TrackpadDirection.Down: + if (_quad.IsOwned) + { + if ((_pressedButtons & Buttons.Left) != 0) + { + MoveGui(-Time.deltaTime); + } + else + { + ChangeGuiSize(-Time.deltaTime); + } + } + else + { + Scroll(-1); + } + break; + default: + _NextScrollTime = null; + break; + } + } + + private void ChangeGuiSize(float number) + { + _quad.transform.localScale *= 1 + number; + } + + private void MoveGui(float number) + { + _quad.transform.position += number * 0.6f * Laser.transform.forward; + } + + private void Scroll(int amount) + { + if (_NextScrollTime == null) + { + _NextScrollTime = Time.unscaledTime + 0.5f; + } + else if (_NextScrollTime < Time.unscaledTime) + { + _NextScrollTime += 0.1f; + } + else + { + return; + } + VR.Input.Mouse.VerticalScroll(amount); + } + + private bool CheckForNearMenu() + { + _quad = GUIQuadRegistry.Quads.FirstOrDefault(IsLaserable); + if (_quad != null) + { + //VRPlugin.Logger.LogDebug($"CheckForNearMenu:Hit"); + return true; + } + //VRPlugin.Logger.LogDebug($"CheckForNearMenu:Miss"); + return false; + } + + private bool IsLaserable(GUIQuad quad) + { + //return IsWithinRange(quad) && Raycast(quad, out _); + return Raycast(quad, out _); + } + + private float GetRange(GUIQuad quad) + { + return quad.transform.localScale.z; + } + private bool IsWithinRange(GUIQuad quad) + { + return (quad.transform.position - Laser.transform.position).magnitude < GetRange(quad); + } + + private bool Raycast(GUIQuad quad, out RaycastHit hit) + { + return quad.GetComponent().Raycast(new Ray(Laser.transform.position, Laser.transform.forward), out hit, GetRange(quad)); + } + + private bool UpdateLaser() + { + if (_quad && _quad.gameObject.activeInHierarchy + && Raycast(_quad, out var hit)) + { + Laser.SetPosition(0, Laser.transform.position); + Laser.SetPosition(1, hit.point); + + var newPos = new Vector2(hit.textureCoord.x * VRGUI.Width, (1 - hit.textureCoord.y) * VRGUI.Height); + //VRLog.Info("New Pos: {0}, textureCoord: {1}", newPos, hit.textureCoord); + if (!_mouseDownPosition.HasValue || Vector2.Distance(_mouseDownPosition.Value, newPos) > MOUSE_STABILIZER_THRESHOLD) + { + SetMousePosition(newPos); + _mouseDownPosition = null; + } + //VRPlugin.Logger.LogDebug($"UpdateLaser:On"); + //VRPlugin.Logger.LogDebug($"MenuHandler:UpdateLaser:Success"); + return true; + } + else + { + // May day, may day -- window is gone! + + //VRPlugin.Logger.LogDebug($"UpdateLaser:Off"); + LaserVisible = false; + ClearPresses(); + return false; + } + } + + private void ClearPresses() + { + AbandonGUI(); + if ((_pressedButtons & Buttons.Left) != 0) + { + VR.Input.Mouse.LeftButtonUp(); + } + if ((_pressedButtons & Buttons.Right) != 0) + { + VR.Input.Mouse.RightButtonUp(); + } + if ((_pressedButtons & Buttons.Middle) != 0) + { + VR.Input.Mouse.MiddleButtonUp(); + } + _pressedButtons = 0; + _NextScrollTime = null; + } + + private void AbandonGUI() + { + if (_quad && _quad.transform.parent == _controller.transform) + { + _quad.transform.SetParent(VR.Camera.Origin, true); + _quad.IsOwned = false; + } + } + + + + + private static void SetMousePosition(Vector2 newPos) + { + int x = (int)Mathf.Round(newPos.x); + int y = (int)Mathf.Round(newPos.y); + var clientRect = WindowManager.GetClientRect(); + var virtualScreenRect = WindowManager.GetVirtualScreenRect(); + VR.Input.Mouse.MoveMouseToPositionOnVirtualDesktop( + (clientRect.Left + x - virtualScreenRect.Left) * 65535.0 / (virtualScreenRect.Right - virtualScreenRect.Left), + (clientRect.Top + y - virtualScreenRect.Top) * 65535.0 / (virtualScreenRect.Bottom - virtualScreenRect.Top)); + } + + //class ResizeHandler : ProtectedBehaviour + //{ + // GUIQuad _Gui; + // Vector3? _StartLeft; + // Vector3? _StartRight; + // Vector3? _StartScale; + // Quaternion? _StartRotation; + // Vector3? _StartPosition; + // Quaternion _StartRotationController; + // Vector3? _OffsetFromCenter; + + // public bool IsDragging { get; private set; } + // protected override void OnStart() + // { + // base.OnStart(); + // _Gui = GetComponent(); + // } + + // protected override void OnUpdate() + // { + // base.OnUpdate(); + // IsDragging = GetDevice(VR.Mode.Left).GetPress(EVRButtonId.k_EButton_Grip) && + // GetDevice(VR.Mode.Right).GetPress(EVRButtonId.k_EButton_Grip); + + // if (IsDragging) + // { + // if (_StartScale == null) + // { + // Initialize(); + // } + // var newLeft = VR.Mode.Left.transform.position; + // var newRight = VR.Mode.Right.transform.position; + + // var distance = Vector3.Distance(newLeft, newRight); + // var originalDistance = Vector3.Distance(_StartLeft.Value, _StartRight.Value); + // var newDirection = newRight - newLeft; + // var newCenter = newLeft + newDirection * 0.5f; + + // // It would probably be easier than that but Quaternions have never been a strength of mine... + // var inverseOriginRot = Quaternion.Inverse(VR.Camera.SteamCam.origin.rotation); + // var avgRot = GetAverageRotation(); + // var rotation = (inverseOriginRot * avgRot) * Quaternion.Inverse(inverseOriginRot * _StartRotationController); + + // _Gui.transform.localScale = (distance / originalDistance) * _StartScale.Value; + // _Gui.transform.localRotation = rotation * _StartRotation.Value; + // _Gui.transform.position = newCenter + (avgRot * Quaternion.Inverse(_StartRotationController)) * _OffsetFromCenter.Value; + + // } + // else + // { + // _StartScale = null; + // } + // } + + // private Quaternion GetAverageRotation() + // { + // var leftPos = VR.Mode.Left.transform.position; + // var rightPos = VR.Mode.Right.transform.position; + + // var right = (rightPos - leftPos).normalized; + // var up = Vector3.Lerp(VR.Mode.Left.transform.forward, VR.Mode.Right.transform.forward, 0.5f); + // var forward = Vector3.Cross(right, up).normalized; + + // return Quaternion.LookRotation(forward, up); + // } + // private void Initialize() + // { + // _StartLeft = VR.Mode.Left.transform.position; + // _StartRight = VR.Mode.Right.transform.position; + // _StartScale = _Gui.transform.localScale; + // _StartRotation = _Gui.transform.localRotation; + // _StartPosition = _Gui.transform.position; + // _StartRotationController = GetAverageRotation(); + + // var originalDistance = Vector3.Distance(_StartLeft.Value, _StartRight.Value); + // var originalDirection = _StartRight.Value - _StartLeft.Value; + // var originalCenter = _StartLeft.Value + originalDirection * 0.5f; + // _OffsetFromCenter = transform.position - originalCenter; + // } + + + // private SteamVR_Controller.Device GetDevice(Controller controller) + // { + // return SteamVR_Controller.Input((int)controller.Tracking.index); + // } + //} + } +} + + diff --git a/Shared/Controls/KoikatuMenuTool.cs b/Shared/Controls/KoikatuMenuTool.cs new file mode 100644 index 0000000..3ac12b1 --- /dev/null +++ b/Shared/Controls/KoikatuMenuTool.cs @@ -0,0 +1,80 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using VRGIN.Controls; +using UnityEngine; +using static Illusion.Utils; +using VRGIN.Visuals; +using VRGIN.Core; + +namespace KK_VR.Controls +{ + internal class KoikatuMenuTool + { + internal bool IsAttached => _attached; + private bool _attached; + private static GUIQuad _gui; + internal KoikatuMenuTool(int index) + { + if (!_gui && index == 1) + { + _gui = GUIQuad.Create(); + _gui.transform.parent = VR.Mode.Right.transform; + _gui.transform.localScale = Vector3.one * 0.3f; + _gui.transform.localPosition = new Vector3(0, 0.05f, -0.06f); + _gui.transform.localRotation = Quaternion.Euler(90, 0, 0); + _gui.IsOwned = true; + _gui.gameObject.SetActive(true); + _attached = true; + } + } + internal static void ToggleState() + { + _gui.gameObject.SetActive(!_gui.gameObject.activeSelf); + } + //internal void TakeGUI(GUIQuad quad) + //{ + // if (quad && !Gui && !quad.IsOwned) + // { + // Gui = quad; + // //Gui.transform.parent = transform; + // Gui.transform.SetParent(transform, worldPositionStays: true); + + // quad.IsOwned = true; + // } + // VRLog.Debug($"TakeGui:{Gui}:{quad.IsOwned}"); + //} + + internal static void TakeGui() + { + if (_gui != null && !_gui.transform.parent.name.Contains("Controller") && _gui.transform.parent != VR.Camera.Origin) + { + var head = VR.Camera.Head; + var origin = VR.Camera.Origin; + + _gui.transform.SetParent(origin, worldPositionStays: true); + + // If no menu in proximity after the scene load. (was abandoned beforehand) + if (Vector3.Distance(_gui.transform.position, head.position) > 3f) + { + _gui.transform.SetPositionAndRotation( + head.position + (origin.rotation * Quaternion.Euler(0f, 60f, 0f)) * head.forward, + head.rotation * Quaternion.Euler(0f, 90f, 0f) + ); + } + } + } + + internal void AbandonGUI() + { + if (_attached) + { + //timeAbandoned = Time.unscaledTime; + _gui.IsOwned = false; + _gui.transform.SetParent(VR.Camera.Origin, true); + _attached = false; + } + } + } +} diff --git a/MainGameVR/Controls/LocationPicker.cs b/Shared/Controls/LocationPicker.cs similarity index 62% rename from MainGameVR/Controls/LocationPicker.cs rename to Shared/Controls/LocationPicker.cs index d59c4ea..9510b02 100644 --- a/MainGameVR/Controls/LocationPicker.cs +++ b/Shared/Controls/LocationPicker.cs @@ -11,12 +11,12 @@ using VRGIN.Core; using Utils = Illusion.Game.Utils; -namespace KKS_VR.Controls +namespace KK_VR.Controls { /// /// A component to add to the controllers the ability to pick a new location in H scenes. /// - internal class LocationPicker : ProtectedBehaviour + internal class LocationPicker : MonoBehaviour { private Controller _controller; private LineRenderer _laser; @@ -25,20 +25,47 @@ internal class LocationPicker : ProtectedBehaviour private HPointData _selection; // may be null private Animator _selectionAnim; // may be null. Also, null if _selection is null. - - protected override void OnAwake() + internal static void AddComponents() { - base.OnAwake(); + VR.Mode.Left.gameObject.AddComponent(); + VR.Mode.Right.gameObject.AddComponent(); + } + internal static void DestroyComponents() + { +#if KK + var leftComponent = VR.Mode.Left.gameObject.GetComponent(); + if (leftComponent != null) +#elif KKS + if (VR.Mode.Left.gameObject.TryGetComponent(out var leftComponent)) +#endif + { + UnityEngine.Object.Destroy(leftComponent); + } +#if KK + var rightComponent = VR.Mode.Right.gameObject.GetComponent(); + if (rightComponent != null) +#elif KKS + if (VR.Mode.Right.gameObject.TryGetComponent(out var rightComponent)) +#endif + { + UnityEngine.Object.Destroy(rightComponent); + } + } + private void Awake() + { _controller = GetComponent(); + AddLaser(); } - protected override void OnUpdate() + private void Update() { - base.OnUpdate(); - // TODO: somehow arrange that this component is only enabled during location selection? +#if KK + if (Manager.Scene.Instance.NowSceneNames[0].Equals("HPointMove") +#elif KKS if (Scene.NowSceneNames[0] == "HPointMove" +#endif && (_lock != null || _controller.CanAcquireFocus())) { if (!_laserEnabled) @@ -67,13 +94,15 @@ private void UpdateSelection() { var ray = new Ray(_laser.transform.position, _laser.transform.TransformDirection(Vector3.forward)); var hit = Physics.RaycastAll(ray) - .Where(h => h.collider.tag == "H/HPoint") + .Where(h => h.collider.CompareTag("H/HPoint")) .OrderBy(h => h.distance) .FirstOrDefault(); - if (hit.collider?.transform.parent.GetComponent() is HPointData point) + if (hit.collider != null) { - if (point != _selection) Select(point); + var hPointData = hit.collider.transform.parent.GetComponent(); + if (hPointData != _selection) + Select(hPointData); } else { @@ -107,7 +136,11 @@ private static IEnumerator ChangeLocation(Action action) { yield return null; action(); +#if KK + Manager.Scene.Instance.UnLoad(); +#else Scene.Unload(); +#endif } private void Unselect() @@ -115,11 +148,14 @@ private void Unselect() if (_selectionAnim != null) { if (_selectionAnim.GetCurrentAnimatorStateInfo(0).IsName("upidle")) + { _selectionAnim.SetTrigger("down"); + } else + { _selectionAnim.Play("idle"); + } } - CleanupSelection(); } @@ -146,35 +182,29 @@ private void Select(HPointData point) _controller.TryAcquireFocus(out _lock); } - // This method is called by VRGIN via SendMessage. - private void OnRenderModelLoaded() + private void AddLaser() { - try + var attachPosition = _controller.transform.Find("Laser"); + if (!attachPosition) { - var attachPosition = _controller.FindAttachPosition("tip"); + VRPlugin.Logger.LogWarning("Attach position not found for laser!"); + attachPosition = transform; + } - if (!attachPosition) - { - VRLog.Warn("LocationPicker: Attach position not found for laser!"); - attachPosition = transform; - } + _laser = new GameObject("LocationPicker").AddComponent(); - _laser = new GameObject("LocationPicker laser").AddComponent(); - _laser.transform.SetParent(attachPosition, false); - _laser.material = new Material(Shader.Find("Sprites/Default")); - _laser.startColor = _laser.endColor = new Color(0.21f, 0.96f, 1.00f); + _laser.transform.parent = _controller.transform; + _laser.transform.SetPositionAndRotation(attachPosition.position, attachPosition.rotation); - _laser.positionCount = 2; - _laser.useWorldSpace = false; - _laser.startWidth = _laser.endWidth = 0.002f; - _laser.SetPosition(0, Vector3.zero); - _laser.SetPosition(1, Vector3.forward * 20); - _laser.gameObject.SetActive(false); - } - catch (Exception e) - { - VRLog.Error(e); - } + _laser.material = new Material(Shader.Find("Sprites/Default")); + _laser.startColor = Color.cyan; + _laser.endColor = new Color(1f, 1f, 1f, 0f); + _laser.positionCount = 2; + _laser.useWorldSpace = false; + _laser.startWidth = _laser.endWidth = 0.002f; + _laser.SetPosition(0, Vector3.zero); + _laser.SetPosition(1, Vector3.forward * 5); + _laser.gameObject.SetActive(false); } } } diff --git a/Shared/Controls/ToolUtil.cs b/Shared/Controls/ToolUtil.cs deleted file mode 100644 index b05fff9..0000000 --- a/Shared/Controls/ToolUtil.cs +++ /dev/null @@ -1,66 +0,0 @@ -using System.Linq; -using UnityEngine; -using VRGIN.Controls; -using VRGIN.Core; - -namespace KKS_VR.Controls -{ - internal class ToolUtil - { - public static HelpText HelpTrigger(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(0.06f, -0.04f, -0.05f), Vector3.zero, "trigger"); - } - - public static HelpText HelpGrip(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(-0.06f, 0, -0.05f), Vector3.zero, "lgrip", "handgrip"); - } - - public static HelpText HelpTrackpadCenter(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(0, 0.06f, 0.02f), Vector3.zero, "trackpad", "thumbstick"); - } - - public static HelpText HelpTrackpadLeft(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(-0.05f, 0.04f, 0), new Vector3(-0.01f, 0, 0), "trackpad", "thumbstick"); - } - - public static HelpText HelpTrackpadRight(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(0.05f, 0.04f, 0), new Vector3(0.01f, 0, 0), "trackpad", "thumbstick"); - } - - public static HelpText HelpTrackpadUp(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(0, 0.04f, 0.07f), new Vector3(0, 0, 0.01f), "trackpad", "thumbstick"); - } - - public static HelpText HelpTrackpadDown(Controller controller, string description) - { - return MakeHelpText(controller, description, new Vector3(0, 0.04f, -0.05f), new Vector3(0, 0, -0.01f), "trackpad", "thumbstick"); - } - - - public static HelpText MakeHelpText( - Controller controller, - string description, - Vector3 textOffset, - Vector3 lineOffset, - params string[] attachNames) - { - var attach = attachNames - .Select(name => controller.FindAttachPosition(name)) - .Where(x => x != null) - .FirstOrDefault(); - if (attach == null) - { - VRLog.Warn($"HelpText: attach point not found for {attachNames}"); - return null; - } - - return HelpText.Create(description, attach, textOffset, lineOffset); - } - } -} diff --git a/MainGameVR/AnimationCrossFader.cs b/Shared/Features/CrossFader.cs similarity index 76% rename from MainGameVR/AnimationCrossFader.cs rename to Shared/Features/CrossFader.cs index c56ed33..6cc7638 100644 --- a/MainGameVR/AnimationCrossFader.cs +++ b/Shared/Features/CrossFader.cs @@ -1,26 +1,34 @@ using System; using System.Collections.Generic; using System.Diagnostics; +using System.Linq; +using System.Text.RegularExpressions; using ADV; using ADV.Commands.Base; using BepInEx.Bootstrap; using BepInEx.Configuration; using HarmonyLib; +using KKAPI.MainGame; using KKAPI.Utilities; -using KKS_VR.Settings; +using KK_VR.Interpreters; +using KK_VR.Settings; +using Manager; +using Unity.Linq; using UnityEngine; using UnityEngine.SceneManagement; using Motion = Illusion.Game.Elements.EasyLoader.Motion; using Object = UnityEngine.Object; using Random = UnityEngine.Random; -namespace KKS_VR +namespace KK_VR.Features { /// /// Based on KKS_CrossFader by Sabakan /// - public static class AnimationCrossFader + public static class CrossFader { + public static bool InTransition => _inTransition; + private static bool _inTransition; public enum CrossFaderMode { Disabled, @@ -37,7 +45,6 @@ public static void Initialize(ConfigFile config, bool vrActivated) VRPlugin.Logger.LogWarning("Disabling the AnimationCrossFader feature because KKS_CrossFader is installed"); return; } - var enabled = config.Bind(SettingsManager.SectionGeneral, "Cross-fade character animations", CrossFaderMode.OnlyInVr, "Interpolate between animations/poses to make transitions look less jarring.\nChanges take effect after a scene change."); @@ -70,7 +77,7 @@ private static void ApplyHooks(bool enable) { if (enable && _hi == null) { - _hi = new Harmony(typeof(AnimationCrossFader).FullName); + _hi = new Harmony(typeof(CrossFader).FullName); _hi.PatchAll(typeof(AdvHooks)); _hi.PatchAll(typeof(HSceneHooks)); } @@ -92,20 +99,31 @@ private static void ApplyHooks(bool enable) } // CrossFade animations in ADV and TalkScene - private static class AdvHooks + internal static class AdvHooks { + internal static bool Reaction { get; private set; } [HarmonyPrefix] - [HarmonyWrapSafe] + //[HarmonyWrapSafe] [HarmonyPatch(typeof(Motion), nameof(Motion.Play))] public static void AdvMotionAddCrossfadeHook(Motion __instance, Animator animator) { // Make the animation cross fade from the current one, uses stock game code __instance.isCrossFade = true; - __instance.transitionDuration = Random.Range(0.1f, 0.3f); - } + if (KoikatuInterpreter.CurrentScene == KoikatuInterpreter.SceneType.TalkScene) + { + // Speed up considerably crossFade after Talk/AdvScene TouchReaction function. + __instance.transitionDuration = Reaction ? Random.Range(0.1f, 0.2f) : Random.Range(0.5f, 1f); + Reaction = false; + if (__instance.state.StartsWith("f_reaction_", StringComparison.Ordinal)) + { + Reaction = true; + } + } + else + __instance.transitionDuration = Random.Range(0.3f, 0.6f); + } #region Disable screen fade effect when ADV is changing character animations - [HarmonyPrefix] [HarmonyWrapSafe] [HarmonyPatch(typeof(TalkScene), nameof(TalkScene.AnimePlay))] @@ -122,7 +140,6 @@ public static void AdvMotionPlayRemoveFadeHook(ADV.Commands.Base.Motion.Data mot { if (isCrossFade) { - VRPlugin.Logger.LogDebug("Disabling isCrossFade in MotionPlay"); isCrossFade = false; } } @@ -164,14 +181,14 @@ public static void AdvMotionDoFadeOverridePost(ADV.Commands.Chara.Motion __insta } #if DEBUG - [HarmonyPrefix] - [HarmonyWrapSafe] - [HarmonyPatch(typeof(CrossFade), nameof(CrossFade.FadeStart))] - public static void DebugCrossFadeStartHook(CrossFade __instance, float time) - { - if (__instance.texBase != null) - VRPlugin.Logger.LogWarning($"CrossFade.FadeStart called (obj={__instance.GetFullPath()} time={time}) from:\n{new StackTrace(2)}"); - } + //[HarmonyPrefix] + //[HarmonyWrapSafe] + //[HarmonyPatch(typeof(CrossFade), nameof(CrossFade.FadeStart))] + //public static void DebugCrossFadeStartHook(CrossFade __instance, float time) + //{ + // if (__instance.texBase != null) + // VRPlugin.Logger.LogWarning($"CrossFade.FadeStart called (obj={__instance.GetFullPath()} time={time}) from:\n{new StackTrace(2)}"); + //} #endif #endregion @@ -196,7 +213,7 @@ public static bool LoadAnimatorOverridePre(Motion __instance, Animator animator, var newHash = __instance.bundle + "|" + __instance.asset; if (newHash == hash) { - VRPlugin.Logger.LogDebug($"Skipping loading already loaded animator controller from [{newHash}] on [{animator.GetFullPath()}]"); + //VRPlugin.Logger.LogDebug($"Skipping loading already loaded animator controller from [{newHash}] on [{animator.GetFullPath()}]"); return false; } else _AnimationControllerLookup.Remove(animatorController); @@ -223,31 +240,42 @@ public static void LoadAnimatorOverridePost(Motion __instance, Animator animator } // CrossFade animations in HScenes, same as the KKS_CrossFader plugin but more compact - private static class HSceneHooks + internal static class HSceneHooks { + // Because first crossFade matters. + internal static void SetFlag(HFlag flag) => _hflag = flag; private static HFlag _hflag; [HarmonyPrefix] - [HarmonyPatch(typeof(CrossFade), nameof(CrossFade.FadeStart), new[] { typeof(float) }, null)] + [HarmonyPatch(typeof(CrossFade), nameof(CrossFade.FadeStart), [typeof(float)], null)] public static bool HSceneFadeStartOverrideHook() { return _hflag == null; } [HarmonyPrefix] - [HarmonyPatch(typeof(ChaControl), nameof(ChaControl.syncPlay), new[] { typeof(string), typeof(int), typeof(float) }, null)] +#if KK + [HarmonyPatch(typeof(ChaControl), nameof(ChaControl.setPlay), [typeof(string), typeof(int)], null)] + public static bool HSceneSetPlayHook(string _strAnmName, int _nLayer, ChaControl __instance, ref bool __result) +#else + [HarmonyPatch(typeof(ChaControl), nameof(ChaControl.syncPlay), [typeof(string), typeof(int), typeof(float)], null)] public static bool HSceneSyncPlayHook(ChaControl __instance, string _strameHash, int _nLayer, float _fnormalizedTime, ref bool __result) +#endif { if (!KKAPI.MainGame.GameAPI.InsideHScene) return true; if (_hflag == null) _hflag = Object.FindObjectOfType(); if (_hflag == null) return true; - - //VRPlugin.Logger.LogDebug($"syncPlay hflag={_hflag} namehash={_strameHash} nlayer={_nLayer} normalizedtime={_fnormalizedTime} chara={__instance}"); +#if KK + var str = _strAnmName; +#else + var str = _strameHash; +#endif + //VRPlugin.Logger.LogDebug($"ChaControl:syncPlay hflag={_hflag} namehash={_strameHash} nlayer={_nLayer} normalizedtime={_fnormalizedTime} chara={__instance}"); switch (_hflag.mode) { case HFlag.EMode.peeping: - __instance.animBody.CrossFadeInFixedTime(_strameHash, 0f, _nLayer); + __instance.animBody.CrossFadeInFixedTime(str, 0f, _nLayer); __result = true; return false; @@ -255,9 +283,9 @@ public static bool HSceneSyncPlayHook(ChaControl __instance, string _strameHash, case HFlag.EMode.houshi3P: case HFlag.EMode.houshi3PMMF: { - if (_strameHash == "Oral_Idle_IN" || _strameHash == "M_OUT_Start") + if (str == "Oral_Idle_IN" || str == "M_OUT_Start") { - __instance.animBody.CrossFadeInFixedTime(_strameHash, 0.2f, _nLayer); + __instance.animBody.CrossFadeInFixedTime(str, 0.2f, _nLayer); __result = true; return false; } @@ -265,12 +293,12 @@ public static bool HSceneSyncPlayHook(ChaControl __instance, string _strameHash, } } - if ((_strameHash == "M_Idle" && __instance.animBody.GetCurrentAnimatorStateInfo(0).IsName("M_Touch")) - || (_strameHash == "A_Idle" && __instance.animBody.GetCurrentAnimatorStateInfo(0).IsName("A_Touch")) - || (_strameHash == "S_Idle" && __instance.animBody.GetCurrentAnimatorStateInfo(0).IsName("S_Touch"))) + if ((str == "M_Idle" && __instance.animBody.GetCurrentAnimatorStateInfo(0).IsName("M_Touch")) + || (str == "A_Idle" && __instance.animBody.GetCurrentAnimatorStateInfo(0).IsName("A_Touch")) + || (str == "S_Idle" && __instance.animBody.GetCurrentAnimatorStateInfo(0).IsName("S_Touch"))) return true; - __instance.animBody.CrossFadeInFixedTime(_strameHash, Random.Range(0.5f, 1f), _nLayer); + __instance.animBody.CrossFadeInFixedTime(str, Random.Range(0.5f, 1f), _nLayer); __result = true; return false; } @@ -288,8 +316,8 @@ public static bool HSceneSyncPlayHook(ChaControl __instance, string _strameHash, [HarmonyPatch(typeof(H3PDarkSonyu), nameof(H3PDarkSonyu.Proc))] public static bool HSceneProcOverrideHook(HActionBase __instance) { - var inTransition = !__instance.female.animBody.GetCurrentAnimatorStateInfo(0).IsName(__instance.flags.nowAnimStateName); - return !inTransition; + _inTransition = !__instance.female.animBody.GetCurrentAnimatorStateInfo(0).IsName(__instance.flags.nowAnimStateName); + return !_inTransition; } } } diff --git a/MainGameVR/Features/HideMaleHead.cs b/Shared/Features/HideMaleHead.cs similarity index 77% rename from MainGameVR/Features/HideMaleHead.cs rename to Shared/Features/HideMaleHead.cs index af496d3..b4a464e 100644 --- a/MainGameVR/Features/HideMaleHead.cs +++ b/Shared/Features/HideMaleHead.cs @@ -1,8 +1,7 @@ using HarmonyLib; using VRGIN.Core; -using KKS_VR.Settings; -namespace KKS_VR.Features +namespace KK_VR.Features { /// /// A component to be attached to every male character. @@ -10,6 +9,7 @@ namespace KKS_VR.Features internal class HideMaleHead : ProtectedBehaviour { public static bool ForceHideHead { get; set; } + public static bool ForceShowHead { get; set; } private ChaControl _control; @@ -23,6 +23,11 @@ protected override void OnLateUpdate() // Hide the head if the VR camera is inside it. // This also essentially negates the effect of scenairo-controlled // head hiding, which is found in some ADV scenes. + if (ForceShowHead) + { + _control.fileStatus.visibleHeadAlways = true; + return; + } var head = _control.objHead?.transform; if (_control.objTop?.activeSelf == true && head != null) { @@ -38,7 +43,10 @@ protected override void OnLateUpdate() // this case, it's important that the head disappears with // 0 frame delay, so we proactively deactive it here. _control.objHead.SetActive(false); - foreach (var hair in _control.objHair) hair.SetActive(false); + foreach (var hair in _control.objHair) + { + hair.SetActive(false); + } } } else @@ -53,12 +61,11 @@ internal class HideMaleHeadPatches { [HarmonyPatch(nameof(ChaControl.Initialize))] [HarmonyPostfix] - private static void PostInitialize(ChaControl __instance) + static void PostInitialize(ChaControl __instance) { - if ((__instance.sex == 0 && POVConfig.targetGender.Value == POVConfig.Gender.Male) || - (__instance.sex == 1 && POVConfig.targetGender.Value == POVConfig.Gender.Female) || - (POVConfig.targetGender.Value == POVConfig.Gender.All)) + if (__instance.sex == 0 && !__instance.transform.parent.name.Equals("HScene")) { + // In H we do this more lazily/appropriately through POV. __instance.GetOrAddComponent(); } } diff --git a/Shared/Features/LoadVoice.cs b/Shared/Features/LoadVoice.cs new file mode 100644 index 0000000..c4d9e79 --- /dev/null +++ b/Shared/Features/LoadVoice.cs @@ -0,0 +1,214 @@ +using BepInEx.Configuration; +using HarmonyLib; +using Illusion.Game; +using KK_VR.Features.Extras; +using KK_VR.Interpreters; +using Manager; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.SqlTypes; +using System.IO; +using System.Linq; +using System.Reflection; +using System.Text; +using System.Text.RegularExpressions; +using UnityEngine; +using UnityEngine.Networking; +using Random = UnityEngine.Random; + +namespace KK_VR.Features +{ + /// + /// Plays native voice lines for any chara at any moment + /// + public static class LoadVoice + { + public enum VoiceType + { + Laugh, + Short + } + private static Func _maleBreathPersonality; + private const string _path = "sound/data/pcm/c**/"; + private static string GetBundleH(int personalityId) + { +#if KK + return personalityId switch + { + 30 => "14", + 31 => "15", + 32 => "16", + 33 => "17", + 34 or 35 or 36 or 37 => "20", + 38 => "50", + _ => "00" + }; +#else + return personalityId switch + { + 40 or 41 or 42 or 43 => "71", + _ => "01" + }; +#endif + } + private static string GetBundleNonH(int personalityId) + { + return personalityId switch + { + 40 or 41 or 42 or 43 => "70", + _ => "00" + }; + } + public static void Init() + { + var type = AccessTools.TypeByName("KK_MaleBreath.MaleBreath"); + if (type != null) + { + _maleBreathPersonality = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("GetPlayerPersonality"))); + } + } + private static void Play(VoiceType type, ChaControl chara)//, bool setCooldown) + { + // Copy MaleBreath method here, prettier. + //VRPlugin.Logger.LogDebug($"Voice:Play:{type}:{chara}"); + + var voiceList = GetVoiceList(type); + if (voiceList == null) + { + return; + } +#if KK + var hExp = Game.Instance.HeroineList +#else + var hExp = Game.HeroineList +#endif + .Where(h => h.chaCtrl == chara) + .Select(h => h.HExperience) + .FirstOrDefault(); + + var personalityId = chara.fileParam.personality; + if (chara.sex == 0 && _maleBreathPersonality != null) + { + personalityId = _maleBreathPersonality(); + } + + if (hExp == SaveData.Heroine.HExperienceKind.不慣れ) + { + // They often use the same asset. + // Hook for this? + hExp = SaveData.Heroine.HExperienceKind.初めて; + } + var bundle = _path + voiceList[Random.Range(0, voiceList.Count)]; + + // Replace personality id. + bundle = bundle.Replace("**", (personalityId < 10 ? "0" : "") + personalityId.ToString()); + + // Replace hExp if there is any. + bundle = bundle.Replace("^", ((int)hExp).ToString()); + var index = bundle.LastIndexOf('/'); + + // Extract Asset from the string at the end. + var asset = bundle.Substring(index + 1); + + // Remove it from the string. + bundle = bundle.Remove(index + 1); + + var h = bundle.EndsWith("h/", StringComparison.OrdinalIgnoreCase); + bundle += GetBundle(personalityId, hVoice: h); + + //VRPlugin.Logger.LogDebug($"{bundle} + {asset}"); + var setting = new Utils.Voice.Setting + { + no = personalityId, + assetBundleName = bundle, + assetName = asset, + pitch = chara.fileParam.voicePitch, + voiceTrans = chara.dictRefObj[ChaReference.RefObjKey.a_n_mouth].transform, + + }; + //chara.ChangeMouthPtn(0, true); +#if KK + chara.SetVoiceTransform(Utils.Voice.OnecePlayChara(setting)); +#else + chara.SetLipSync(Utils.Voice.OncePlayChara(setting)); +#endif + + // We respect hScene voices. + if (KoikatuInterpreter.CurrentScene == KoikatuInterpreter.SceneType.HScene) + { + for (var i = 0; i < HSceneInterpreter.lstFemale.Count; i++) + { + if (HSceneInterpreter.lstFemale[i] == chara) + { + // Something of this is probably unnecessary, but figuring it out is a huge pain, given 'HVoiceCtrl' structure. + var voice = HSceneInterpreter.hVoice.nowVoices[i]; + voice.state = HVoiceCtrl.VoiceKind.breathShort; + voice.notOverWrite = true; + voice.shortInfo.isPlay = true; + voice.link = new HVoiceCtrl.LinkInfo(); + voice.shortInfo.pathAsset = bundle; + voice.shortInfo.nameFile = asset; + HSceneInterpreter.hVoice.linkUseBreathPtn[i] = null; + HSceneInterpreter.hVoice.linkUseVoicePtn[i] = null; + break; + } + } + } + } + public static void PlayVoice(VoiceType voiceType, ChaControl chara, bool voiceWait = true) + { + if (!voiceWait || chara.asVoice == null || !IsVoiceActive(chara)) + { + Play(voiceType, chara); + } + } + private static bool IsVoiceActive(ChaControl chara) + { + if (KoikatuInterpreter.CurrentScene == KoikatuInterpreter.SceneType.HScene) + { + for (var i = 0; i < HSceneInterpreter.lstFemale.Count; i++) + { + if (HSceneInterpreter.lstFemale[i] == chara) + { + return HSceneInterpreter.hVoice.nowVoices[i].state != HVoiceCtrl.VoiceKind.breath || HSceneInterpreter.IsKissAnim; + } + } + } + return chara.asVoice != null + && !(chara.asVoice.name.StartsWith("h_ko", StringComparison.Ordinal) + // Match "0**_0*" at the end for 'Short'. e.g. in "h_ko_27_00_006_04" - [006_04] = Match! + || (Regex.IsMatch(chara.asVoice.name, @"0..\S0.$", RegexOptions.CultureInvariant) + && _kissBreaths.Any(s => chara.asVoice.name.EndsWith(s, StringComparison.Ordinal)))); + } + private static readonly List _kissBreaths = + [ + "013", + "014", + "015", + "016", + "017", + "018", + "019", + "020" + ]; + private static string GetBundle(int id, bool hVoice) + { +#if KK + return GetBundleH(id) + (hVoice ? "_00.unity3d" : ".unity3d"); +#else + return (hVoice ? GetBundleH(id) : GetBundleNonH(id)) + ".unity3d"; +#endif + } + private static List GetVoiceList(VoiceType type) + { + return type switch + { + VoiceType.Laugh => VoiceBundles.Laughs, + VoiceType.Short => VoiceBundles.Shorts, + _ => null + }; + + } + } +} diff --git a/Shared/Features/OldVRFade.cs b/Shared/Features/OldVRFade.cs new file mode 100644 index 0000000..36c698c --- /dev/null +++ b/Shared/Features/OldVRFade.cs @@ -0,0 +1,147 @@ +//using System; +//using System.Collections; +//using ActionGame; +//using UnityEngine; +//using Valve.VR; +//using VRGIN.Core; + +//namespace KK_VR.Features +//{ +// /// +// /// A VR fader that replaces the fader of the base game. +// /// +// internal class OldVRFade : ProtectedBehaviour +// { +// /// +// /// Reference to the image used by the vanilla SceneFade object. +// /// +// private CanvasGroup _vanillaFade; + +// private readonly float _gridFadeTime = 1; +// private readonly float _fadeAlphaThresholdHigh = 0.9999f; +// private readonly float _fadeAlphaThresholdLow = 0.0001f; + +// private bool _isFading; + +// public static void Create() +// { +// VR.Camera.gameObject.AddComponent(); +// } + +// protected override void OnAwake() +// { +// _vanillaFade = Manager.Scene.sceneFadeCanvas?.canvasGroup ?? throw new ArgumentNullException(nameof(_vanillaFade), "sceneFadeCanvas or canvasGroup is null"); +// } + +// protected override void OnUpdate() +// { +// if (!_isFading && _vanillaFade && _vanillaFade.alpha > _fadeAlphaThresholdLow) +// { +// StartCoroutine(DeepFadeCo()); +// } +// } + +// /// +// /// A coroutine for entering "deep fade", where we cut to the compositor's grid and display some overlay. +// /// Based on https://github.com/mosirnik/KK_MainGameVR/commit/12e435f1e9a70c7d7b5dd56de416d300a2836091 +// /// +// private IEnumerator DeepFadeCo() +// { +// if (OpenVR.Overlay == null || _isFading) +// yield break; + +// _isFading = true; + +// // Make the world outside of the game the same color as the loading screen instead of the headset default skybox +// SetCompositorSkyboxOverride(GetFadeColor()); + +// var compositor = OpenVR.Compositor; +// if (compositor != null) +// { +// // Fade the game out so the ouside world is now seen instead of the laggy loading screen +// compositor.FadeGrid(_gridFadeTime, true); + +// // It looks like we need to pause rendering here, otherwise the +// // compositor will automatically put us back from the grid. +// SteamVR_Render.pauseRendering = true; +// } + +// // Wait for the game to fully fade in +// while (_vanillaFade.alpha <= _fadeAlphaThresholdHigh) +// { +// if (!_vanillaFade || _vanillaFade.alpha < _fadeAlphaThresholdLow) +// goto endEarly; + +// yield return null; +// } + +// // Wait for the game to start fading out +// while (_vanillaFade.alpha > _fadeAlphaThresholdHigh) +// { +// yield return null; +// } + +// // Wait for things to settle down +// yield return null; +// yield return null; + +// endEarly: + +// // Let the game be rendered again and fade into it +// SteamVR_Render.pauseRendering = false; +// if (compositor != null) +// { +// compositor.FadeGrid(_gridFadeTime, false); +// yield return new WaitForSeconds(_gridFadeTime); +// } + +// // Wait for the game to finish fading to make sure we are synchronized +// while (_vanillaFade && _vanillaFade.alpha > _fadeAlphaThresholdLow) +// { +// yield return null; +// } + +// SteamVR_Skybox.ClearOverride(); + +// _isFading = false; +// } + +// private static Color GetFadeColor() +// { +// try +// { +// var cycle = FindObjectOfType(); +// switch (cycle?.nowType) +// { +// default: +// case Cycle.Type.WakeUp: +// case Cycle.Type.Morning: +// case Cycle.Type.Daytime: +// return new Color(0.44f, 0.78f, 1f); +// case Cycle.Type.Evening: +// return new Color(0.85f, 0.50f, 0.37f); +// case Cycle.Type.Night: +// case Cycle.Type.GotoMyHouse: +// case Cycle.Type.MyHouse: +// return new Color(0.12f, 0.2f, 0.5f); +// } +// } +// catch (Exception e) +// { +// Console.WriteLine(e); +// return Color.white; +// } +// } + +// private static void SetCompositorSkyboxOverride(Color fadeColor) +// { +// var tex = new Texture2D(1, 1); +// var color = fadeColor; +// color.a = 1f; +// tex.SetPixel(0, 0, color); +// tex.Apply(); +// SteamVR_Skybox.SetOverride(tex, tex, tex, tex, tex, tex); +// Destroy(tex); +// } +// } +//} diff --git a/Shared/Features/Pov.cs b/Shared/Features/Pov.cs new file mode 100644 index 0000000..19c1880 --- /dev/null +++ b/Shared/Features/Pov.cs @@ -0,0 +1,560 @@ +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using VRGIN.Core; +using UniRx; +using Manager; +using KK_VR.Settings; +using KK_VR.Interpreters; +using KK_VR.Handlers; +using KK_VR.Camera; + +namespace KK_VR.Features +{ + public class PoV : MonoBehaviour + { + private class OneWayTrip + { + internal OneWayTrip(float lerpMultiplier, Quaternion targetRotation) + { + _lerpMultiplier = lerpMultiplier; + _startPosition = VR.Camera.Head.position; + _startRotation = VR.Camera.Origin.rotation; + _targetRotation = targetRotation; + } + private float _lerp; + private readonly float _lerpMultiplier; + private readonly Quaternion _startRotation; + private readonly Vector3 _startPosition; + + // Quaternion (S)Lerp will go awry with constantly changing end point. + private readonly Quaternion _targetRotation; + + internal float Move(Vector3 position) + { + var smoothStep = Mathf.SmoothStep(0f, 1f, _lerp += Time.deltaTime * _lerpMultiplier); + position = Vector3.Lerp(_startPosition, position, smoothStep); + + VR.Camera.Origin.rotation = Quaternion.Slerp(_startRotation, _targetRotation, smoothStep); + VR.Camera.Origin.position += position - VR.Camera.Head.position; + return smoothStep; + } + } + + public static PoV Instance; + /// + /// girlPOV is NOT set proactively, use "active" to monitor state. + /// + public static bool GirlPoV; + public static bool Active => Instance != null && Instance._active; + public static ChaControl Target => _target; + + enum Mode + { + Disable, + Move, + Follow + } + + + private bool _active; + private static ChaControl _target; + private ChaControl _prevTarget; + + private Transform _targetEyes; + private Mode _mode; + private bool _newAttachPoint; + private Vector3 _offsetVecNewAttach; + private bool _rotationRequired; + private int _rotDeviationThreshold; + private int _rotDeviationHalf; + private Vector3 _offsetVecEyes; + private OneWayTrip _trip; + private MoveToPoi _moveTo; + private SmoothDamp _smoothDamp; + private float _degPerSec; + private bool _sync; + private float _syncTimestamp; + private Vector3 _prevFramePos; + private bool _forceHideHead; + + private Vector3 GetEyesPosition() => _targetEyes.TransformPoint(_offsetVecEyes); + private bool IsClimax => HSceneInterpreter.hFlag.nowAnimStateName.EndsWith("_Loop", System.StringComparison.Ordinal); + + internal static PoV Create() + { + var component = VR.Camera.gameObject.GetComponent(); + if (component != null) + { + return component; + } + return VR.Camera.gameObject.AddComponent(); + } + private void Awake() + { + Instance = this; + } + + private void UpdateSettings() + { + _sync = false; + _syncTimestamp = 0f; + _smoothDamp = new SmoothDamp(); + _degPerSec = 30f * KoikatuInterpreter.Settings.RotationMultiplier; + _rotDeviationThreshold = KoikatuInterpreter.Settings.RotationDeviationThreshold; + _rotDeviationHalf = (int)(_rotDeviationThreshold * 0.4f); + _offsetVecEyes = new Vector3(0f, KoikatuInterpreter.Settings.PositionOffsetY, KoikatuInterpreter.Settings.PositionOffsetZ); + } + private void SetVisibility(ChaControl chara) + { + if (chara != null) chara.fileStatus.visibleHeadAlways = true; + } + private void MoveToPos() + { + var origin = VR.Camera.Origin; + if (_newAttachPoint) + { + if (!IsClimax) + { + //origin.rotation = _offsetRotNewAttach; + origin.position += _targetEyes.position + _offsetVecNewAttach - VR.Camera.Head.position; + } + } + else + { + if (IsClimax) + { + if (_rotationRequired) + { + _rotationRequired = false; + _smoothDamp = null; + //_synced = false; + } + } + else + { + var angle = Quaternion.Angle(origin.rotation, _targetEyes.rotation); + if (!_rotationRequired) + { + if (angle > _rotDeviationThreshold) + { + _sync = false; + _syncTimestamp = 0f; + _rotationRequired = true; + _smoothDamp = new SmoothDamp(); + } + } + else + { + float sDamp; + if (angle < _rotDeviationHalf) + { + sDamp = _smoothDamp.Current; + if (angle < 1f) // && sDamp < 0.01f) + { + if (_syncTimestamp == 0f) + { + if (Quaternion.Angle(VR.Camera.Head.rotation, _targetEyes.rotation) < 30f) + { + _syncTimestamp = Time.time + 2f; + } + } + else + { + if (_syncTimestamp < Time.time) + { + if (Quaternion.Angle(VR.Camera.Head.rotation, _targetEyes.rotation) < 30f) + { + _sync = true; + _syncTimestamp = 0f; + _smoothDamp = null; + _rotationRequired = false; + _prevFramePos = VR.Camera.Head.position; + } + else + { + _syncTimestamp = 0f; + } + } + } + } + } + else + { + sDamp = _smoothDamp.Increase(); + } + var moveTowards = Vector3.MoveTowards(VR.Camera.Head.position, GetEyesPosition(), 0.05f); + origin.rotation = Quaternion.RotateTowards(origin.rotation, _targetEyes.rotation, Time.deltaTime * _degPerSec * sDamp); + origin.position += moveTowards - VR.Camera.Head.position; + return; + } + if (_sync) + { + var pos = GetEyesPosition(); + origin.position += (pos - _prevFramePos); // + (Vector3.MoveTowards(VR.Camera.Head.position, pos, 0.01f) - VR.Camera.Head.position); + _prevFramePos = pos; + } + else + { + // We don't branch here anymore? + origin.position += GetEyesPosition() - VR.Camera.Head.position; + } + } + } + } + public void StartPov() + { + _active = true; + NextChara(keepChara: true); + } + + public void OnSpotChange() + { + StartPov(); + CameraIsFar(3f); + } + public void CameraIsFar(float speed = 1f) + { + _mode = Mode.Move; + if (speed != 1f) + { + StartMoveToHead(speed); + } + } + public void CameraIsFarAndBusy() + { + CameraIsFar(); + if (MouthGuide.Instance != null) + { + MouthGuide.Instance.PauseInteractions = true; + } + } + public void CameraIsNear() + { + _mode = Mode.Follow; + _sync = false; + _syncTimestamp = 0f; + _rotationRequired = true; + _smoothDamp = new SmoothDamp(); + SetVisibility(_prevTarget); + _prevTarget = null; + if (_target.sex == 1) + { + GirlPoV = true; + if (MouthGuide.Instance != null) + { + MouthGuide.Instance.PauseInteractions = true; + } + } + else + { + GirlPoV = false; + if (MouthGuide.Instance != null) + { + MouthGuide.Instance.PauseInteractions = false; + } + } + } + + private void StartMoveToHead(float speed = 1f) + { + if (KoikatuInterpreter.Settings.FlyInPov == KoikatuSettings.MovementTypeH.Disabled) + { + _newAttachPoint = false; + CameraIsNear(); + } + else + { + // Only one mode is currently operational. + _trip = new OneWayTrip(Mathf.Min( + KoikatuInterpreter.Settings.FlightSpeed * speed / Vector3.Distance(VR.Camera.Head.position, GetEyesPosition()), + KoikatuInterpreter.Settings.FlightSpeed * 60f / Quaternion.Angle(VR.Camera.Origin.rotation, _targetEyes.rotation)), + _targetEyes.rotation); + } + } + private void MoveToHeadEx() + { + if (_trip == null) + { + StartMoveToHead(); + } + else if (_trip.Move(GetEyesPosition()) >= 1f) + { + CameraIsNear(); + _newAttachPoint = false; + _trip = null; + } + } + + internal void OnGraspEnd() + { + if (_active && !_newAttachPoint) + { + CameraIsFar(0.25f); + } + } + + + private int GetCurrentCharaIndex(List _chaControls) + { + if (_target != null) + { + for (int i = 0; i < _chaControls.Count; i++) + { + if (_chaControls[i] == _target) + { + return i; + } + } + } + return 0; + } + + private void NextChara(bool keepChara = false) + { + // As some may add extra characters with kPlug, we look them all up. + var charas = FindObjectsOfType() + .Where(c => c.objTop.activeSelf && c.visibleAll + && c.sex != (KoikatuInterpreter.Settings.PoV == KoikatuSettings.Impersonation.Girls ? 0 : KoikatuInterpreter.Settings.PoV == KoikatuSettings.Impersonation.Boys ? 1 : 2)) + .ToList(); + + if (charas.Count == 0) + { + Sleep(); + VRPlugin.Logger.LogWarning("Can't impersonate, no appropriate targets. To extend allowed genders change setting."); + return; + } + var currentCharaIndex = GetCurrentCharaIndex(charas); + + if (keepChara) + { + _target = charas[currentCharaIndex]; + } + else if (currentCharaIndex == charas.Count - 1) + { + // No point in switching with only one active character, disable instead. + + _prevTarget = _target; + _target = charas[0]; + + _mode = Mode.Disable; + return; + } + else + { + _prevTarget = _target; + _target = charas[currentCharaIndex + 1]; + } + if (MouthGuide.Instance != null) + { + MouthGuide.Instance.OnImpersonation(_target); + } + _targetEyes = _target.objHeadBone.transform.Find("cf_J_N_FaceRoot/cf_J_FaceRoot/cf_J_FaceBase/cf_J_FaceUp_ty/cf_J_FaceUp_tz"); + CameraIsFarAndBusy(); + UpdateSettings(); + } + + private void NewPosition() + { + // Most likely a bad idea to kiss/lick when detached from the head but still inheriting all movements. + CameraIsNear(); + _offsetVecNewAttach = VR.Camera.Head.position - _targetEyes.position; + } + + internal void OnGripMove(bool press) + { + //_gripMove = press; + if (_active) + { + if (press) + { + CameraIsFar(); + } + else if (_newAttachPoint) + { + NewPosition(); + } + } + } + + internal bool OnTouchpad(bool press) + { + // We call it only in gripMove state. + if (press) + { + if (_active && !_newAttachPoint) + { + _newAttachPoint = true; + return true; + } + } + return false; + } + + private void Sleep() + { + _active = false; + SetVisibility(_target); + _mode = Mode.Disable; + _newAttachPoint = false; + _forceHideHead = false; + _moveTo = null; + + if (MouthGuide.Instance != null) + { + MouthGuide.Instance.PauseInteractions = false; + MouthGuide.Instance.OnUnImpersonation(); + } + } + + private void Disable(bool moveTo) + { + if (_moveTo == null) + { + if (!moveTo || _target == null) + { + Sleep(); + } + else + { + var target = _target.sex == 1 ? _target : FindObjectsOfType() + .Where(c => c.sex == 1 && c.objTop.activeSelf && c.visibleAll) + .FirstOrDefault(); + _moveTo = new MoveToPoi(target != null ? target : _target, Sleep ); + } + } + else + { + _moveTo.Move(); + } + } + + private void HandleDisable(bool moveTo = true) + { + if (_newAttachPoint) + { + _newAttachPoint = false; + CameraIsFarAndBusy(); + } + else + { + Disable(moveTo); + } + } + + internal bool TryDisable(bool moveTo) + { + if (_active) + { + if (!moveTo) + { + Sleep(); + } + else + { + Disable(moveTo); + } + return true; + } + return false; + } + + private void Update() + { + if (_active) + { + if (KoikatuInterpreter.SceneInput.IsBusy// || _mouth.IsActive +#if KK + || !Scene.Instance.AddSceneName.Equals("HProc")) +#else + || !Scene.AddSceneName.Equals("HProc")) +#endif + // !Scene.AddSceneName.Equals("HProc")) // SceneApi.GetIsOverlap()) KKS option KK has it broken. + { + // We don't want pov while kissing/licking or if config/pointmove scene pops up. + CameraIsFar(); + } + else + { + switch (_mode) + { + case Mode.Disable: + HandleDisable(); + break; + case Mode.Follow: + MoveToPos(); + break; + case Mode.Move: + MoveToHeadEx(); + break; + } + } + } + } + + private void LateUpdate() + { + if (_active && KoikatuInterpreter.Settings.HideHeadInPOV && _target != null) + { + HideHeadEx(_target); + if (_prevTarget != null) + { + HideHead(_prevTarget); + } + } + } + + private void HideHeadEx(ChaControl chara) + { + if (_forceHideHead) + { + chara.fileStatus.visibleHeadAlways = false; + } + else + { + HideHead(chara); + } + } + + private void HideHead(ChaControl chara) + { + var head = chara.objHead.transform; + var wasVisible = chara.fileStatus.visibleHeadAlways; + var headCenter = head.TransformPoint(0, 0.12f, -0.04f); + var sqrDistance = (VR.Camera.transform.position - headCenter).sqrMagnitude; + var visible = 0.0361f < sqrDistance; // 19 centimeters + chara.fileStatus.visibleHeadAlways = visible; + if (wasVisible && !visible) + { + chara.objHead.SetActive(false); + + foreach (var hair in chara.objHair) + { + hair.SetActive(false); + } + } + } + + internal void TryEnable() + { + if (KoikatuInterpreter.Settings.PoV != KoikatuSettings.Impersonation.Disabled) + { + if (_newAttachPoint) + { + CameraIsFarAndBusy(); + _newAttachPoint = false; + } + else if (_active) + NextChara(); + else + StartPov(); + } + } + + internal void OnLimbSync(bool start) + { + _forceHideHead = start; + } + } +} + diff --git a/MainGameVR/Features/PrivacyScreen.cs b/Shared/Features/PrivacyScreen.cs similarity index 97% rename from MainGameVR/Features/PrivacyScreen.cs rename to Shared/Features/PrivacyScreen.cs index df64303..c681d33 100644 --- a/MainGameVR/Features/PrivacyScreen.cs +++ b/Shared/Features/PrivacyScreen.cs @@ -1,10 +1,10 @@ -using KKS_VR.Settings; +using KK_VR.Settings; using UnityEngine; using UnityEngine.UI; using VRGIN.Core; using Object = UnityEngine.Object; -namespace KKS_VR.Features +namespace KK_VR.Features { /// /// Class that allows the user to hide contents of the desktop mirror screen. diff --git a/Shared/Features/Undresser.cs b/Shared/Features/Undresser.cs new file mode 100644 index 0000000..d30efcb --- /dev/null +++ b/Shared/Features/Undresser.cs @@ -0,0 +1,342 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Core; +using HarmonyLib; + +using static ChaFileDefine; +using KK_VR.Fixes; +using static KK_VR.Features.LoadVoice; +using KK_VR.Controls; +using KK_VR.Features; +using static KK_VR.Trackers.Tracker; + +namespace KK_VR.Interactors +{ + /// + /// Simplified/expanded version from https://github.com/mosirnik/KK_MainGameVR + /// + static class Undresser + { + public static bool IsBodyPartClothed(ChaControl chara, Body part) + { + var array = ConvertToSlot(part); + if (array == null) return false; + foreach (var item in array) + { + if (chara.IsClothes(item) && chara.fileStatus.clothesState[item] == 0) return true; + } + return false; + } + private static int[] ConvertToSlot(Body part) + { + return part switch + { + Body.MuneL or Body.MuneR => [0, 2], + Body.UpperBody => [0], + Body.LowerBody => [1, 5], + Body.ArmL or Body.ArmR => [0, 4], + Body.Groin or Body.Asoko => [1, 3, 5], + Body.ThighL or Body.ThighR or Body.LegL or Body.LegR or Body.FootL or Body.FootR => [5, 6], + _ => null, + }; + } + private static Body ConvertToUndress(Body body) + { + return body switch + { + Body.Head => Body.None, + Body.HandR => Body.HandL, + Body.ArmR => Body.ArmL, + Body.MuneR => Body.MuneL, + Body.Groin => Body.Asoko, + Body.ThighR => Body.ThighL, + Body.ForearmL => Body.ArmL, + Body.ForearmR => Body.ArmL, + Body.FootR => Body.FootL, + _ => body + }; + } + public static bool Undress(Body part, ChaControl chara, bool decrease) + { + part = ConvertToUndress(part); + //if (part == InteractionBodyPart.Crotch && IsWearingSkirt(female)) + //{ + // //VRLog.Debug($"WearingSkirt"); + // // Special case: if the character is wearing a skirt, allow + // // directly removing the underwear. + // targets = _skirtCrotchTargets; + //} + + var targets = decrease ? UndressDic[part] : RedressDic[part]; + + foreach (var target in targets) + { + var slot = target.slot; + if (!chara.IsClothes(slot) + || (decrease && chara.fileStatus.clothesState[slot] > target.state) + || (!decrease && chara.fileStatus.clothesState[slot] <= target.state)) + { + //VRPlugin.Logger.LogDebug($"Undress:Skip[{part}]"); + continue; + } + else + { + //VRPlugin.Logger.LogDebug($"Undress:Valid:Part[{part}]:Slot[{slot}]:State[{target.state}]"); + } + //if (slot > 6) + //{ + // // Target proper shoe slot. + // slot = chara.fileStatus.shoesType == 0 ? 7 : 8; + //} + if (slot == 3 || slot == 5 || slot == 6) + { + if (decrease) + { + // Check for pants. If present override pantyhose/socks/panties with them. + if (chara.fileStatus.clothesState[1] < (slot == 3 ? 1 : 3) && chara.objClothes[1].GetComponent() == null) + { + chara.SetClothesStateNext(1); + return true; + } + } + else + { + if (slot != 3) + { + if (chara.fileStatus.clothesState[3] == 2) + { + // Is we decided to redress pantyhose/socks with panties hanging on the leg, remove them instead. + chara.SetClothesState(3, 3, false); + //chara.fileStatus.clothesState[3] = 3; + } + else if (slot == 5 && chara.fileStatus.clothesState[3] == 1) + { + // Or put them back on if only shifted and we redress pantyhose. + chara.SetClothesState(3, 0, false); + //chara.fileStatus.clothesState[3] = 0; + } + } + else + { + // Put panties on in one go. + chara.SetClothesState(3, 0, false); + return true; + } + } + } + if (decrease) + { + chara.SetClothesStateNext(slot); + PlayVoice((VoiceType)UnityEngine.Random.Range(0, 2), chara); + } + else + { + chara.SetClothesStatePrev(slot); + } + return true; + } + return false; + } + + /* + * KKS Slots + * 0 - Top + * 1 - Bottom + * 2 - Bra + * 3 - Panties + * 4 - gloves + * 5 - pantyhose + * 6 - Stockings + * + * 8 - Shoes + */ + + struct SlotState + { + public int slot; + public int state; + } + private static readonly Dictionary> UndressDic = new() + { + // Pairs of clothing slots and their states + // We check each, if state is less or equal, jump to the next one, otherwise change state. + { + Body.Asoko, new List + { + new() { slot = 1, state = 0 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 3, state = 0 }, + //new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 3, state = 1 } + } + }, + { + Body.LowerBody, new List + { + new SlotState { slot = 1, state = 0 }, + new SlotState { slot = 1, state = 1 } + } + }, + { + Body.UpperBody, new List + { + new SlotState { slot = 0, state = 0 }, + new SlotState { slot = 0, state = 1 } + } + }, + { + Body.ThighL, new List + { + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 1, state = 0 } + } + }, + { + Body.LegL, new List + { + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 8, state = 0 }, + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 3, state = 2 } + } + }, + { + Body.LegR, new List + { + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 8, state = 0 }, + new SlotState { slot = 5, state = 1 }, + } + }, + { + Body.MuneL, new List + { + new SlotState { slot = 0, state = 0 }, + new SlotState { slot = 2, state = 0 }, + new SlotState { slot = 0, state = 1 }, + new SlotState { slot = 2, state = 1 }, + } + }, + { + Body.ArmL, new List + { + new SlotState { slot = 0, state = 0 }, + new SlotState { slot = 0, state = 1 }, + new SlotState { slot = 4, state = 0 } + } + }, + { + Body.HandL, new List + { + new SlotState { slot = 4, state = 0 } + } + }, + { + Body.FootL, + [ + new SlotState { slot = 8, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 5, state = 1 } + ] + } + + }; + private static readonly Dictionary> RedressDic = new() + { + // Pairs of clothing slots and their states + // We check each, if state is less, jump to the next one, otherwise change state. + { + Body.Asoko, new List + { + new SlotState { slot = 3, state = 0 }, + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 1, state = 0 } + } + }, + { + Body.LowerBody, new List + { + //new SlotState { slot = 3, state = 0 }, + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 1, state = 0 } + } + }, + { + Body.UpperBody, new List + { + new SlotState { slot = 2, state = 1 }, + new SlotState { slot = 2, state = 0 }, + new SlotState { slot = 0, state = 1 }, + new SlotState { slot = 0, state = 0 }, + } + }, + { + Body.ThighL, new List + { + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 } + } + }, + { + Body.LegL, new List + { + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 8, state = 0 } + } + }, + { + Body.LegR, new List + { + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 8, state = 0 } + } + }, + { + Body.MuneL, new List + { + new SlotState { slot = 2, state = 1 }, + new SlotState { slot = 2, state = 0 }, + new SlotState { slot = 0, state = 1 }, + new SlotState { slot = 0, state = 0 } + } + }, + { + Body.ArmL, new List + { + new SlotState { slot = 4, state = 0 }, + new SlotState { slot = 0, state = 1 }, + new SlotState { slot = 0, state = 0 } + } + }, + { + Body.HandL, new List + { + new SlotState { slot = 4, state = 0 } + } + }, + { + Body.FootL, + [ + new SlotState { slot = 5, state = 1 }, + new SlotState { slot = 5, state = 0 }, + new SlotState { slot = 6, state = 0 }, + new SlotState { slot = 8, state = 0 }, + ] + } + }; + } +} diff --git a/Shared/Features/VRBoop.cs b/Shared/Features/VRBoop.cs index 9eaac5b..d0b6d2e 100644 --- a/Shared/Features/VRBoop.cs +++ b/Shared/Features/VRBoop.cs @@ -2,10 +2,12 @@ using System.Collections.Generic; using HarmonyLib; using UnityEngine; -using UnityEngine.XR; +using VRGIN.Core; using VRGIN.Controls; +using System.Linq; +using System.Collections; -namespace KKS_VR.Features +namespace KK_VR.Features { /// /// Adds colliders to the controllers so you can boop things @@ -14,30 +16,38 @@ namespace KKS_VR.Features /// public static class VRBoop { - internal const string LeftColliderName = "Left_Boop_Collider"; - internal const string RightColliderName = "Right_Boop_Collider"; - - private static DynamicBoneCollider _leftCollider; - private static DynamicBoneCollider _rightCollider; - - public static void Initialize(Controller controller, EyeSide controllerSide) + private readonly static List _activeDBC = []; + public static void Initialize() { // Hooks in here don't get patched by the whole assembly PatchAll since the class has no HarmonyPatch attribute Harmony.CreateAndPatchAll(typeof(VRBoop), typeof(VRBoop).FullName); - - switch (controllerSide) + } + public static void AddDB(DynamicBoneCollider DBCollider) + { + _activeDBC.Add(DBCollider); + } + public static void RefreshDynamicBones(IEnumerable charas) + { + // Hooks don't give us BetterPenetration dynamic bones. + foreach (var chara in charas) { - case EyeSide.Left: - _leftCollider = GetOrAttachCollider(controller.gameObject, LeftColliderName); - break; - case EyeSide.Right: - _rightCollider = GetOrAttachCollider(controller.gameObject, RightColliderName); - break; - default: - throw new ArgumentOutOfRangeException(nameof(controllerSide), controllerSide, null); + var dbList = chara.GetComponentsInChildren(); + foreach (var db in dbList) + { + AttachControllerColliders(db); + } + var dbList01 = chara.GetComponentsInChildren(); + foreach (var db in dbList01) + { + AttachControllerColliders(db); + } + var dbList02 = chara.GetComponentsInChildren(); + foreach (var db in dbList02) + { + AttachControllerColliders(db); + } } } - [HarmonyPostfix] [HarmonyWrapSafe] [HarmonyPatch(typeof(DynamicBone), nameof(DynamicBone.SetupParticles))] @@ -63,7 +73,7 @@ private static void OnClothesChanged(ref Action actObj) { var colliders = newBone.m_Colliders; if (colliders != null) - AttachControllerColliders(colliders); + AddColliders(colliders); } }; } @@ -71,18 +81,20 @@ private static void OnClothesChanged(ref Action actObj) private static void AttachControllerColliders(MonoBehaviour dynamicBone) { var colliderList = GetColliderList(dynamicBone); - if (colliderList == null) return; - AttachControllerColliders(colliderList); + if (colliderList != null) + { + AddColliders(colliderList); + } } - - private static void AttachControllerColliders(List colliderList) + private static void AddColliders(List colliderList) { - if (colliderList == null) throw new ArgumentNullException(nameof(colliderList)); - - if (_leftCollider && !colliderList.Contains(_leftCollider)) - colliderList.Add(_leftCollider); - if (_rightCollider && !colliderList.Contains(_rightCollider)) - colliderList.Add(_rightCollider); + foreach (var dbc in _activeDBC) + { + if (!colliderList.Contains(dbc)) + { + colliderList.Add(dbc); + } + } } private static List GetColliderList(MonoBehaviour dynamicBone) @@ -96,35 +108,5 @@ private static List GetColliderList(MonoBehaviour dynamicBo _ => throw new ArgumentException(@"Not a DynamicBone - " + dynamicBone.GetType(), nameof(dynamicBone)), }; } - - private static DynamicBoneCollider GetOrAttachCollider(GameObject controllerGameObject, string colliderName) - { - if (controllerGameObject == null) throw new ArgumentNullException(nameof(controllerGameObject)); - if (colliderName == null) throw new ArgumentNullException(nameof(colliderName)); - - //Check for existing DB collider that may have been attached earlier - var existingCollider = controllerGameObject.GetComponentInChildren(); - if (existingCollider == null) - { - //Add a DB collider to the controller - return AddDbCollider(controllerGameObject, colliderName); - } - - return existingCollider; - } - - private static DynamicBoneCollider AddDbCollider(GameObject controllerGameObject, string colliderName, - float colliderRadius = 0.05f, float collierHeight = 0f, Vector3 colliderCenter = new Vector3(), DynamicBoneCollider.Direction colliderDirection = default) - { - //Build the dynamic bone collider - var colliderObject = new GameObject(colliderName); - var collider = colliderObject.AddComponent(); - collider.m_Radius = colliderRadius; - collider.m_Height = collierHeight; - collider.m_Center = colliderCenter; - collider.m_Direction = colliderDirection; - colliderObject.transform.SetParent(controllerGameObject.transform, false); - return collider; - } } } diff --git a/Shared/Features/VRFade.cs b/Shared/Features/VRFade.cs new file mode 100644 index 0000000..aa7b27f --- /dev/null +++ b/Shared/Features/VRFade.cs @@ -0,0 +1,398 @@ +using UnityEngine.UI; +using UnityEngine; +using VRGIN.Helpers; +using System.Collections; +using Valve.VR; +using VRGIN.Core; +using ActionGame; +using Manager; +using KK_VR.Interpreters; + +namespace KK_VR.Features +{ + /// + /// A VR fader that replaces the fader of the base game. + /// + internal class VRFade : MonoBehaviour + { + /// + /// Reference to the image used by the vanilla SceneFade object. + /// + //Graphic _vanillaGraphic; + private static VRFade _instance; + //internal static bool IsFade => _instance._alpha != 0f; + internal static bool IsFade => _instance._alpha == 1f; +#if KK + private Image _vanillaImage; + private Slider _vanillaProgressBar; +#else + // KKS doesn't have fade out by default, + // and the fade is hidden so well couldn't even find it, + // so we go with the fade delegates and custom fade. + + private LoadingIconJob _loadingIconJob; + private SceneFadeCanvas _sceneFadeCanvas; + private bool _isFade; + private float _fadeTimestamp; +#endif + private Material _fadeMaterial; + private int _fadeMaterialColorID; + private float _alpha; + private bool _inDeepFade; + private Color _fadeColor; + + + + const float DeepFadeAlphaThreshold = 0.9999f; + + public static void Create() + { + VR.Camera.gameObject.AddComponent(); + } + private void Awake() + { + _instance = this; +#if KK + _vanillaImage = Manager.Scene.Instance.sceneFade.image; + _vanillaProgressBar = Manager.Scene.Instance.progressSlider; + _vanillaImage.enabled = false; +#else + _sceneFadeCanvas = Manager.Scene.sceneFadeCanvas; + _loadingIconJob = Manager.Scene.loadingIconJob; + Manager.Scene.sceneFadeCanvas.onStart += OnFadeIn; + Manager.Scene.sceneFadeCanvas.onComplete += OnFadeOut; +#endif + //_vanillaGraphic = _sceneFadeCanvas.fadeImage; + _fadeMaterial = new Material(UnityHelper.GetShader("Custom/SteamVR_Fade")); + _fadeMaterialColorID = Shader.PropertyToID("fadeColor"); + } +#if KKS + private void OnFadeIn(FadeCanvas.Fade fade) + { + if (!_isFade) + { + _isFade = true; + _fadeColor = GetFadeColor(); + + _fadeTimestamp = Time.unscaledTime + 2f; + } + } + private void OnFadeOut(FadeCanvas.Fade fade) + { + if (!_sceneFadeCanvas.isFading) + { + if (_inDeepFade) + { + _isFade = false; + _alpha = 0f; + } + if (TalkSceneInterpreter.advScene != null && KoikatuInterpreter.SceneInterpreter is TalkSceneInterpreter talkInterpreter) + { + talkInterpreter.AdjustAdvScene(); + } + } + } + + private void OnPostRender() + { + if (_isFade) + { + if (!_inDeepFade) + { + if (Scene.IsFadeNow) + { + if (_alpha != 1f) + { + _alpha = Mathf.Clamp01(_alpha + Mathf.Min(Time.deltaTime, 0.05f)); + } + else if (_fadeTimestamp < Time.unscaledTime) + { + StartCoroutine(DeepFadeCo()); + } + } + else + { + _alpha = Mathf.Clamp01(_alpha - Mathf.Min(Time.deltaTime, 0.05f)); + if (_alpha == 0f) + { + _isFade = false; + } + } + _fadeColor.a = Mathf.Clamp01(_alpha); + DrawQuad(); + } + else + { + // KKS doesn't have fade out by default it seems. We'll keep it that way on non-vr render. + if (Scene.IsFadeNow) + { + DrawQuad(); + } + } + } + } +#else + private void OnPostRender() + { + if (_vanillaImage != null) + { + _fadeColor = _vanillaImage.color; + _alpha = Mathf.Max(_alpha - 0.05f, _fadeColor.a); // Use at least 20 frames to fade out. + _fadeColor.a = _alpha; + if (_alpha > 0.0001f) + { + DrawQuad(); + } + + if (DeepFadeAlphaThreshold < _alpha && + _vanillaProgressBar.isActiveAndEnabled && + !_inDeepFade) + { + StartCoroutine(DeepFadeCo()); + } + } + } +#endif + + private void DrawQuad() + { + _fadeMaterial.SetColor(_fadeMaterialColorID, _fadeColor); + _fadeMaterial.SetPass(0); + GL.Begin(GL.QUADS); + GL.Vertex3(-1, -1, 0); + GL.Vertex3(1, -1, 0); + GL.Vertex3(1, 1, 0); + GL.Vertex3(-1, 1, 0); + GL.End(); + } + + + /// + /// A coroutine for entering "deep fade", where we cut to the compositor's + /// grid and display some overlay. + /// + private IEnumerator DeepFadeCo() + { + var overlay = OpenVR.Overlay; + if (overlay == null) + { + yield break; + } + _inDeepFade = true; + + var gridFadeTime = 1f; + var compositor = OpenVR.Compositor; +#if KK + SetCompositorSkyboxOverride(_vanillaImage.color); +#else + SetCompositorSkyboxOverride(GetFadeColor()); +#endif + if (compositor != null) + { + compositor.FadeGrid(gridFadeTime, true); + // It looks like we need to pause rendering here, otherwise the + // compositor will automatically put us back from the grid. + SteamVR_Render.pauseRendering = true; + } + // Adding loading icon seems to be way harder then i'd like. + +#if KK + var loadingOverlay = new LoadingOverlay(overlay); + while (DeepFadeAlphaThreshold < _vanillaImage.color.a) + { + loadingOverlay.Update(); + yield return null; + } + loadingOverlay.Destroy(); +#else + while (_alpha == 1f) + { + //loadingOverlay.Update(); + yield return null; + } +#endif + + // Wait for things to settle down + yield return null; + yield return null; + + SteamVR_Render.pauseRendering = false; + if (compositor != null) + { + compositor.FadeGrid(gridFadeTime, false); + yield return new WaitForSeconds(gridFadeTime); + } + + SteamVR_Skybox.ClearOverride(); + _inDeepFade = false; + } + private static void SetCompositorSkyboxOverride(Color fadeColor) + { + var tex = new Texture2D(1, 1); + var color = fadeColor; + color.a = 1f; + tex.SetPixel(0, 0, color); + tex.Apply(); + SteamVR_Skybox.SetOverride(tex, tex, tex, tex, tex, tex); + Destroy(tex); + } +#if KKS + private static Color GetFadeColor() + { + var cycle = FindObjectOfType(); + if (cycle == null) + { + //return Color.white; + return new Color(0.44f, 0.78f, 1f); + } + return (cycle.nowType) switch + { + Cycle.Type.Evening => new Color(0.85f, 0.50f, 0.37f), + // Find better color for the night. + Cycle.Type.Night or Cycle.Type.GotoMyHouse or Cycle.Type.MyHouse => new Color(0.12f, 0.2f, 0.5f), + _ => new Color(0.44f, 0.78f, 1f), + }; + } +#endif +#if KK + + /// An object that manages an OpenVR overlay that shows a "Now Loading..." image + /// and a progress bar. This needs to be an overlay rather than a GameObject so + /// that its rendering continues while the game's framerate drops massively. + class LoadingOverlay + { + readonly CVROverlay _overlay; + readonly ulong _handle; // handle to our overlay + readonly RenderTexture _texture; // texture to be displayed in the overlay + readonly UnityEngine.Camera _camera; // camera for rendering to the texture + readonly Canvas _canvas; // canvas to hold UI elements + readonly Image _baseGameLoadingImage; // base game's "Now Loading" image + readonly Image _loadingImage; // our "Now Loading" image + readonly Slider _baseGameProgressBar; // base game's progress bar + readonly Slider _progressBar; // our progress bar + + internal LoadingOverlay(CVROverlay overlay) + { + _overlay = overlay; + + _handle = OpenVR.k_ulOverlayHandleInvalid; + var error = overlay.CreateOverlay( + VRPlugin.GUID + ".now_loading", + "Now Loading", + ref _handle); + if (error != EVROverlayError.None) + { + VRLog.Error("Cannot create overlay: {0}", + overlay.GetOverlayErrorNameFromEnum(error)); + return; + } + + _texture = new RenderTexture(272, 56, 24); + + _camera = new GameObject("VRLoadingOverlayCamera") + .AddComponent(); + DontDestroyOnLoad(_camera); + _camera.targetTexture = _texture; + _camera.cullingMask = VR.Context.UILayerMask; + _camera.depth = 1; + _camera.nearClipPlane = VR.Context.GuiNearClipPlane; + _camera.farClipPlane = VR.Context.GuiFarClipPlane; + _camera.backgroundColor = Color.clear; + _camera.clearFlags = CameraClearFlags.SolidColor; + _camera.orthographic = true; + _camera.useOcclusionCulling = false; + + var baseGameCanvas = Manager.Scene.Instance.sceneFade + .image.transform.parent.GetComponent(); + _canvas = GameObject.Instantiate(baseGameCanvas); + DontDestroyOnLoad(_canvas); + _canvas.name = "VRLoadingOverlayCanvas"; + _canvas.renderMode = RenderMode.ScreenSpaceCamera; + _canvas.worldCamera = _camera; + var scaler = _canvas.GetComponent(); + scaler.uiScaleMode = CanvasScaler.ScaleMode.ConstantPixelSize; + scaler.scaleFactor = 1; + _canvas.gameObject.SetActive(true); + + _baseGameLoadingImage = baseGameCanvas.transform.Find("NowLoading").GetComponent(); + _loadingImage = _canvas.transform.Find("NowLoading").GetComponent(); + var imageTrans = _loadingImage.GetComponent(); + imageTrans.anchorMin = imageTrans.anchorMax = Vector2.zero; + imageTrans.offsetMin = new Vector2(24, 24); + imageTrans.offsetMax = new Vector2(232, 56); + + _baseGameProgressBar = baseGameCanvas.transform.Find("Progress").GetComponent(); + _progressBar = _canvas.transform.Find("Progress").GetComponent(); + var barTrans = _progressBar.GetComponent(); + barTrans.anchorMin = barTrans.anchorMax = Vector2.zero; + barTrans.offsetMin = Vector2.zero; + barTrans.offsetMax = new Vector2(272, 28); + + InitializeOverlay(); + } + + private void InitializeOverlay() + { + Check("SetWidth", _overlay.SetOverlayWidthInMeters(_handle, 0.3f)); + var vrcam = VR.Camera; + var rot = Quaternion.Euler(0f, vrcam.transform.localRotation.eulerAngles.y, 0f); + var pos = vrcam.transform.localPosition + rot * Vector3.forward * 3f; + var offset = new SteamVR_Utils.RigidTransform(pos, rot); + var t = offset.ToHmdMatrix34(); + Check("SetTransform", + _overlay.SetOverlayTransformAbsolute( + _handle, + SteamVR_Render.instance.trackingSpace, + ref t)); + + var textureBounds1 = new VRTextureBounds_t + { + uMin = 0f, + uMax = 1f, + // The image will be vertically flipped unless we set vMax < vMin. + // I don't know why. + vMin = 1f, + vMax = 0f, + }; + Check("SetBounds", _overlay.SetOverlayTextureBounds(_handle, ref textureBounds1)); + + _overlay.ShowOverlay(_handle); + } + + internal void Update() + { + _loadingImage.gameObject.SetActive(_baseGameLoadingImage.gameObject.activeSelf); + _loadingImage.color = _baseGameLoadingImage.color; + _progressBar.gameObject.SetActive(_baseGameProgressBar.gameObject.activeSelf); + _progressBar.value = _baseGameProgressBar.value; + RedrawOverlay(); + } + + private void RedrawOverlay() + { + var tex = new Texture_t(); + tex.handle = _texture.GetNativeTexturePtr(); + tex.eType = SteamVR.instance.textureType; + tex.eColorSpace = EColorSpace.Auto; + Check("SetTexture", _overlay.SetOverlayTexture(_handle, ref tex)); + } + + internal void Destroy() + { + Check("Destroy", _overlay.DestroyOverlay(_handle)); + GameObject.Destroy(_camera.gameObject); + GameObject.Destroy(_canvas.gameObject); + GameObject.Destroy(_texture); + } + + private void Check(string label, EVROverlayError error) + { + if (error != EVROverlayError.None) + { + VRLog.Error($"Overlay {label}: {_overlay.GetOverlayErrorNameFromEnum(error)}"); + } + } + } +#endif + } +} diff --git a/Shared/Features/VoiceBundles.cs b/Shared/Features/VoiceBundles.cs new file mode 100644 index 0000000..b6efafb --- /dev/null +++ b/Shared/Features/VoiceBundles.cs @@ -0,0 +1,50 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace KK_VR.Features.Extras +{ + public static class VoiceBundles + { + + // Look up maleBreath, i do it way more eloquently there. + + // ** is a Stand-in for personality Id. + // ^ is a Stand-in for experience level. + public static List Shorts = + [ + "h/h_ko_**_0^_005_00", + "h/h_ko_**_0^_005_01", + "h/h_ko_**_0^_005_02", + "h/h_ko_**_0^_005_03", + "h/h_ko_**_0^_005_04", + "h/h_ko_**_0^_005_05", + "h/h_ko_**_0^_005_06", + "h/h_ko_**_0^_005_07", + "h/h_ko_**_0^_005_08", + "h/h_ko_**_0^_005_09", + "h/h_ko_**_0^_006_00", + "h/h_ko_**_0^_006_01", + "h/h_ko_**_0^_006_02", + "h/h_ko_**_0^_006_03", + "h/h_ko_**_0^_006_04", + "h/h_ko_**_0^_006_05", + "h/h_ko_**_0^_006_06", + "h/h_ko_**_0^_006_07", + "h/h_ko_**_0^_006_08", + "h/h_ko_**_0^_006_09", + ]; + + public static List Laughs = + [ + "adv/com_ev_**_464_00", + "adv/com_ev_**_464_01", + //"adv/com_ev_**_464_02", + "adv/com_ev_**_465_00", + "adv/com_ev_**_465_01", + "adv/com_ev_**_465_02", + //"adm/adm_**_tanon_02" + ]; + } +} diff --git a/Shared/Fixes/BepInExVrLogBackend.cs b/Shared/Fixes/BepInExVrLogBackend.cs index 55b72a3..acbcf0f 100644 --- a/Shared/Fixes/BepInExVrLogBackend.cs +++ b/Shared/Fixes/BepInExVrLogBackend.cs @@ -2,7 +2,7 @@ using BepInEx.Logging; using VRGIN.Core; -namespace KKS_VR.Fixes +namespace KK_VR.Fixes { public class BepInExVrLogBackend : ILoggerBackend { diff --git a/Shared/Fixes/GameFixes.cs b/Shared/Fixes/GameFixes.cs new file mode 100644 index 0000000..4ec3d0d --- /dev/null +++ b/Shared/Fixes/GameFixes.cs @@ -0,0 +1,436 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Reflection; +using System.Reflection.Emit; +using ActionGame; +using ADV; +using HarmonyLib; +using KKAPI.Utilities; +using KK_VR.Interpreters; +using KK_VR.Settings; +using StrayTech; +using UnityEngine; +using UnityStandardAssets.ImageEffects; +using VRGIN.Core; +using Object = UnityEngine.Object; +using KK_VR.Camera; + +/* + * Fixes for issues that are in the base game but are only relevant in VR. + */ +namespace KK_VR.Fixes +{ + /// + /// Suppress character update for invisible characters in some sub-scenes of Roaming. + /// + [HarmonyPatch(typeof(ChaControl))] + public class ChaControlPatches1 + { + public static KoikatuSettings _setting = VR.Settings as KoikatuSettings; + [HarmonyPrefix] + [HarmonyPatch(nameof(ChaControl.LateUpdateForce))] + private static bool PreLateUpdateForce(ChaControl __instance) + { + return !SafeToSkipUpdate(__instance); + } + + [HarmonyPrefix] + [HarmonyPatch(nameof(ChaControl.UpdateForce))] + private static bool PreUpdateForce(ChaControl __instance) + { + return !SafeToSkipUpdate(__instance); + } + + public static bool SafeToSkipUpdate(ChaControl chara) + { + return _setting.OptimizeHInsideRoaming + && KoikatuInterpreter.CurrentScene > KoikatuInterpreter.SceneType.ActionScene + && chara.objTop != null + && !chara.objTop.activeSelf; + } + } +#if KKS + /// + /// Fix being unable to do some actions in roaming mode + /// + [HarmonyPatch(typeof(CameraSystem))] + public class ADVSceneFix2 + { + [HarmonyPrefix] + [HarmonyPatch(nameof(CameraSystem.SystemStatus), MethodType.Getter)] + private static bool FixNeverEndingTransition(ref CameraSystem.CameraSystemStatus __result) + { + __result = CameraSystem.CameraSystemStatus.Inactive; + return false; + } + } +#endif + /// + /// Fix mainly for character maker, the mask doesn't work properly in VR and goes all over the place. + /// This removes the mask and applies the amplify effect to the whole camera, with downside of darkening the UI. + /// This component also exists in some places in main game, but it seems like this patch has no ill effects related to that. + /// + [HarmonyPatch(typeof(CameraEffectorColorMask))] + public class CameraEffectorColorMaskFix + { + [HarmonyPrefix] + [HarmonyPatch(nameof(CameraEffectorColorMask.Awake), MethodType.Normal)] + private static bool SkipCameraSetup(CameraEffectorColorMask __instance) + { + GameObject.Destroy(__instance); + return false; + } + } + + + /// + /// The game includes an old version of GlobalFog, which assumes that the + /// viewing frustum is always centered at the camera. This assumption is + /// invalid in VR, so we fix it up here. + /// + [HarmonyPatch(typeof(GlobalFog))] + public class GlobalFogPatches + { + [HarmonyPatch(nameof(GlobalFog.CustomGraphicsBlit))] + [HarmonyPrefix] + private static void PreCustomGraphicsBlit(Material fxMaterial) + { +#if KK + + UnityEngine.Camera camera = UnityEngine.Camera.current; + camera.CalculateFrustumCorners( + new Rect(0, 0, 1, 1), camera.farClipPlane, camera.stereoActiveEye, _frustumBuffer); + // We need to transform these frustum corners to world space, but + // this transformation is different for each eye. Fortunately, here + // (inside OnRenderImage) Unity seems to have set up cameraToWorldMatrix + // correctly for the currently active eye. We just need to + // adjust it to cancel the Z-flipping. + var cam2world = camera.cameraToWorldMatrix * Matrix4x4.Scale(new Vector3(1f, 1f, -1f)); + Matrix4x4 corners = Matrix4x4.zero; + corners.SetRow(0, cam2world.MultiplyVector(_frustumBuffer[1])); + corners.SetRow(1, cam2world.MultiplyVector(_frustumBuffer[2])); + corners.SetRow(2, cam2world.MultiplyVector(_frustumBuffer[3])); + corners.SetRow(3, cam2world.MultiplyVector(_frustumBuffer[0])); + fxMaterial.SetMatrix("_FrustumCornersWS", corners); +#else + + UnityEngine.Camera camera = UnityEngine.Camera.current; + camera.CalculateFrustumCorners( + new Rect(0, 0, 1, 1), camera.farClipPlane, camera.stereoActiveEye, _frustumBuffer); + Matrix4x4 corners = Matrix4x4.zero; + corners.SetRow(0, camera.transform.TransformDirection(_frustumBuffer[1])); + corners.SetRow(1, camera.transform.TransformDirection(_frustumBuffer[2])); + corners.SetRow(2, camera.transform.TransformDirection(_frustumBuffer[3])); + corners.SetRow(3, camera.transform.TransformDirection(_frustumBuffer[0])); + fxMaterial.SetMatrix("_FrustumCornersWS", corners); +#endif + } + + static readonly Vector3[] _frustumBuffer = new Vector3[4]; + } +#if KKS + + // When the game disables the map in HScene (Alpha6 shortcut), it changes camera's config, we want to syncronize it with VR camera to get propper pass-through in VR. + [HarmonyPatch] + public class CameraClearFlagsHook + { + public static void UpdateVRCamera() + { + KoikatuInterpreter.RunAfterUpdate(VRGIN.Core.VRCamera.Instance.UpdateCameraBlueprint); + } + static MethodBase TargetMethod() + { + // Our target is nested type in HSceneProc.SetShortcutKey. + var nested = typeof(HSceneProc).GetNestedType("<>c", BindingFlags.NonPublic); + if (nested == null) + { + return null; + } + return AccessTools.Method(nested, "b__191_7"); + } + public static IEnumerable Transpiler(IEnumerable instructions) + { + var method = AccessTools.Method(typeof(CameraClearFlagsHook), nameof(CameraClearFlagsHook.UpdateVRCamera)); + foreach (var code in instructions) + { + if (code.opcode == OpCodes.Ret) + { + yield return new CodeInstruction(OpCodes.Call, method); + } + yield return code; + } + } + } +#endif + + + // Not tagging VRCamera as MainCamera fixes all those issues. + + // /// + // /// Fix game crash during map load + // /// todo hack, handle properly? + // /// + //[HarmonyPatch(typeof(SunLightInfo))] + // public class FogHack1 + // { + // [HarmonyPrefix] + // [HarmonyPatch(typeof(SunLightInfo), nameof(SunLightInfo.Set))] + // private static void SunLightInfoSet(SunLightInfo.Info.Type? type, ref UnityEngine.Camera cam) + // { + // //VRPlugin.Logger.LogDebug($"SunLightInfo.Set:{(int)type}:{cam.name}"); + // if (cam == VR.Camera.MainCamera && KoikatuInterpreter.mainCamera != null) + // { + // cam = KoikatuInterpreter.mainCamera; + // //VRPlugin.Logger.LogDebug($"Camera substitute:{cam.name}"); + // } + // } + // //[HarmonyFinalizer] + // //[HarmonyPatch(nameof(SunLightInfo.Set))] + // //private static Exception PreLateUpdateForce(Exception __exception) + // //{ + // // if (__exception != null)//VRPlugin.Logger.LogDebug("SunLightInfo.Set:Caught expected crash: " + __exception); + // // return null; + // //} + // } + + ///// + ///// Fix game crash during map load + ///// todo hack, handle properly? + ///// + //[HarmonyPatch(typeof(ActionMap))] + //public class FogHack2 + //{ + // // todo hack, handle properly + // [HarmonyFinalizer] + // [HarmonyPatch(nameof(ActionMap.UpdateCameraFog))] + // private static Exception PreLateUpdateForce(Exception __exception) + // { + // if (__exception != null)//VRPlugin.Logger.LogDebug("ActionMap.UpdateCameraFog:Caught expected crash: " + __exception); + // return null; + // } + // //[HarmonyPrefix] + // //[HarmonyPatch(typeof(ActionMap), nameof(ActionMap.UpdateCameraFog))] + + //} + + ///// + ///// Fix game crash during ADV scene load + ///// + //[HarmonyPatch(typeof(Manager.Game))] + //public class ADVSceneFix1 + //{ + // [HarmonyPostfix] + // [HarmonyPatch(nameof(Manager.Game.cameraEffector), MethodType.Getter)] + // private static void FixMissingCameraEffector(Manager.Game __instance, ref CameraEffector __result) + // { + // if (__result == null && __instance.isCameraChanged) + // // vr camera doesn't have this component on it, which crashes game code with nullref. Use the component on original advcamera instead + // __instance._cameraEffector = __result = Object.FindObjectOfType(); + // } + //} + + + ///// + ///// Fix ADV scenes messing with the VR camera by moving it or setting flags on it. Feed it the default 2D camera instead so it's happy. + ///// + //[HarmonyPatch] + //public class ADVSceneFix3 + //{ + // private static IEnumerable TargetMethods() + // { + // yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(TalkScene), nameof(TalkScene.Setup))); + // } + + // private static UnityEngine.Camera GetOriginalMainCamera() + // { + // // vr camera doesn't have this component on it + // var originalMainCamera = (Manager.Game.instance.cameraEffector ?? Object.FindObjectOfType()).GetComponent(); + // //VRPlugin.Logger.LogDebug($"GetOriginalMainCamera called, cam found: {originalMainCamera?.GetFullPath()}\n{new StackTrace()}"); + // return originalMainCamera; + // } + + // private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) + // { + // var targert = AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)); + // var replacement = AccessTools.Method(typeof(ADVSceneFix3), nameof(GetOriginalMainCamera)); + // return insts.Manipulator( + // instr => instr.opcode == OpCodes.Call && (MethodInfo)instr.operand == targert, + // instr => + // { + // instr.operand = replacement; + // //VRPlugin.Logger.LogDebug("Patched Camera.main in " + __originalMethod.GetNiceName()); + // }); + // } + //} + + ///// + ///// Fix ADV scenes messing with the VR camera by moving it or setting flags on it. Feed it the default 2D camera instead so it's happy. + ///// + //[HarmonyPatch] + //public class ADVSceneFix4 + //{ + // private static IEnumerable TargetMethods() + // { + // yield return AccessTools.Method(typeof(ADVScene), nameof(ADVScene.Init)); + // } + + // private static UnityEngine.Camera GetOriginalMainCamera() + // { + // // vr camera doesn't have this component on it + // var originalMainCamera = (Manager.Game.instance.cameraEffector ?? Object.FindObjectOfType()).GetComponent(); + // //VRPlugin.Logger.LogDebug($"GetOriginalMainCamera called, cam found: {originalMainCamera?.GetFullPath()}\n{new StackTrace()}"); + // return originalMainCamera; + // } + + // private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) + // { + // var targert = AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)); + // var replacement = AccessTools.Method(typeof(ADVSceneFix4), nameof(GetOriginalMainCamera)); + // return insts.Manipulator( + // instr => instr.opcode == OpCodes.Call && (MethodInfo)instr.operand == targert, + // instr => + // { + // instr.operand = replacement; + // //VRPlugin.Logger.LogDebug("Patched Camera.main in " + __originalMethod.GetNiceName()); + // }); + // } + + // private static void Postfix(ADVScene __instance) + // { + // Manager.Sound.Listener = UnityEngine.Camera.main.transform; + // } + //} + + ///// + ///// Fix vending machines and some other action points softlocking the game + ///// + //[HarmonyPatch(typeof(Manager.PlayerAction))] + //public class VendingMachineFix + //{ + // [HarmonyTranspiler] + // [HarmonyPatch(nameof(Manager.PlayerAction.Action))] + // private static IEnumerable Transpiler(IEnumerable insts) + // { + // // Multiple methods get this crossFade field and try to fade on it. Problem is, it doesn't exist. + // // Instead of patching everything, create a dummy crossFade when it's being set + // var target = AccessTools.Field(typeof(Manager.PlayerAction), nameof(Manager.PlayerAction.crossFade)); + // if (target == null) throw new ArgumentNullException(nameof(target)); + // return new CodeMatcher(insts).MatchForward(false, new CodeMatch(OpCodes.Stfld, target)) + // .ThrowIfInvalid("crossFade not found") + // .Insert(new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(VendingMachineFix), nameof(VendingMachineFix.GiveDummyCrossFade)))) + // .Instructions(); + // } + + // private static CrossFade _dummyCrossFade; + // private static CrossFade GiveDummyCrossFade(CrossFade existing) + // { + // if (existing) return existing; + + // // To disable the fade texBase has to be null. texBase is set in Start so it's delayed from creation. + // if (!_dummyCrossFade) + // _dummyCrossFade = new GameObject("DummyCrossFade").AddComponent(); + // else + // _dummyCrossFade.texBase = null; + + // return _dummyCrossFade; + // } + //} + + ///// + ///// Fix wrong position being sometimes set in TalkScene after introduction finishes + ///// + //[HarmonyPatch] + //public class TalkScenePostAdvFix + //{ + // [HarmonyPostfix] + // [HarmonyPatch(typeof(TalkScene), nameof(TalkScene.Introduction), MethodType.Normal)] + // private static void IntroductionPostfix(TalkScene __instance, UniTask __result) + // { + // __instance.StartCoroutine(__result.WaitForFinishCo().AppendCo(() => TalkSceneInterpreter.AdjustPosition(__instance))); + // } + //} + + ///// + ///// Fix crash when playing ADV scenes + ///// + //[HarmonyPatch] + //public class CycleCrossFadeFix1 + //{ + // private static IEnumerable TargetMethods() + // { + // yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(Cycle), nameof(Cycle.WakeUp))); + // } + + // private static bool IsProcessWithNullcheck(CrossFade instance) + // { + // return instance != null && instance.isProcess; + // } + + // private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) + // { + // var targert = AccessTools.PropertyGetter(typeof(CrossFade), nameof(CrossFade.isProcess)); + // var replacement = AccessTools.Method(typeof(CycleCrossFadeFix1), nameof(IsProcessWithNullcheck)); + // return insts.Manipulator( + // instr => instr.opcode == OpCodes.Callvirt && (MethodInfo)instr.operand == targert, + // instr => + // { + // instr.opcode = OpCodes.Call; + // instr.operand = replacement; + // //VRPlugin.Logger.LogDebug("Patched CrossFade.isProcess in " + __originalMethod.GetFullName()); + // }); + // } + //} + + ///// + ///// Fix hscene killing the camera at end + ///// + //[HarmonyPatch] + //public class HSceneFix1 + //{ + // private static IEnumerable TargetMethods() + // { + // yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(HScene), nameof(HScene.Start))); + // yield return CoroutineUtils.GetMoveNext(AccessTools.Method(typeof(HScene), nameof(HScene.ResultTalk))); + // } + + // private static UnityEngine.Camera GetOriginalMainCamera() + // { + // // vr camera doesn't have this component on it + // var originalMainCamera = (Manager.Game.instance.cameraEffector ?? Object.FindObjectOfType()).GetComponent(); + // //VRPlugin.Logger.LogDebug($"GetOriginalMainCamera called, cam found: {originalMainCamera?.GetFullPath()}\n{new StackTrace()}"); + // return originalMainCamera; + // } + // private static IEnumerable Transpiler(IEnumerable insts, MethodBase __originalMethod) + // { + // var targert = AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)); + + // //VRPlugin.Logger.LogDebug("Patching Camera.main -> null in " + __originalMethod.GetNiceName()); + + // // Change Camera.main property get to return null instead to skip code that messes with player camera. + // // Only last instance needs to be patched or HScene.ResultTalk will break. + // return new CodeMatcher(insts).End() + // .MatchBack(false, new CodeMatch(OpCodes.Call, AccessTools.PropertyGetter(typeof(UnityEngine.Camera), nameof(UnityEngine.Camera.main)))) + // .ThrowIfInvalid("Camera.main not found in " + __originalMethod.GetNiceName()) + // .Set(OpCodes.Ldnull, null) + // .Instructions(); + // } + //} + ///// + ///// Fix hscene killing the camera at end + ///// + //[HarmonyPatch(typeof(HScene))] + //public class HSceneFix2 + //{ + // [HarmonyPrefix] + // [HarmonyPatch(nameof(HScene.HResultADVCameraSetting), MethodType.Normal)] + // private static bool SkipCameraSetup() + // { + // //VRPlugin.Logger.LogDebug("Skipping HScene.HResultADVCameraSetting"); + // return false; + // } + //} + + +} diff --git a/MainGameVR/Fixes/Mirror/Manager.cs b/Shared/Fixes/Mirror/Manager.cs similarity index 97% rename from MainGameVR/Fixes/Mirror/Manager.cs rename to Shared/Fixes/Mirror/Manager.cs index 77b5739..e60e5cf 100644 --- a/MainGameVR/Fixes/Mirror/Manager.cs +++ b/Shared/Fixes/Mirror/Manager.cs @@ -3,7 +3,7 @@ using VRGIN.Core; using Object = UnityEngine.Object; -namespace KKS_VR.Fixes.Mirror +namespace KK_VR.Fixes.Mirror { /// /// Mirrors in the base game look very weird in VR. This object diff --git a/MainGameVR/Fixes/Mirror/VRReflection.cs b/Shared/Fixes/Mirror/VRReflection.cs similarity index 89% rename from MainGameVR/Fixes/Mirror/VRReflection.cs rename to Shared/Fixes/Mirror/VRReflection.cs index 3bfc150..a88b6ee 100644 --- a/MainGameVR/Fixes/Mirror/VRReflection.cs +++ b/Shared/Fixes/Mirror/VRReflection.cs @@ -5,7 +5,7 @@ using UnityEngine; using Valve.VR; -namespace KKS_VR.Fixes.Mirror +namespace KK_VR.Fixes.Mirror { // TODO borked [ExecuteInEditMode] // Make mirror live-update even when not in play mode @@ -77,34 +77,36 @@ public void OnWillRenderObject() GL.invertCulling = !oldInvertCulling; UpdateCameraModes(cam, reflectionData.camera); - + if (cam.stereoEnabled) { if (cam.stereoTargetEye == StereoTargetEyeMask.Both || cam.stereoTargetEye == StereoTargetEyeMask.Left) { - Matrix4x4 projectionMatrix = GetSteamVRProjectionMatrix(cam, Valve.VR.EVREye.Eye_Left); - var viewMatrix = cam.GetStereoViewMatrix(UnityEngine.Camera.StereoscopicEye.Left); - RenderTexture target = m_UseSharedRenderTexture ? m_SharedReflectionTextureLeft : reflectionData.left; + var eyePos = cam.transform.TransformPoint(SteamVR.instance.eyes[0].pos); + var eyeRot = cam.transform.rotation * SteamVR.instance.eyes[0].rot; + var projectionMatrix = GetSteamVRProjectionMatrix(cam, EVREye.Eye_Left); + var target = m_UseSharedRenderTexture ? m_SharedReflectionTextureLeft : reflectionData.left; reflectionData.propertyBlock.SetTexture(s_LeftTexturePropertyID, target); - RenderMirror(reflectionData.camera, target, projectionMatrix, viewMatrix); + RenderMirror(reflectionData.camera, target, eyePos, eyeRot, projectionMatrix); } if (cam.stereoTargetEye == StereoTargetEyeMask.Both || cam.stereoTargetEye == StereoTargetEyeMask.Right) { - Matrix4x4 projectionMatrix = GetSteamVRProjectionMatrix(cam, Valve.VR.EVREye.Eye_Right); - var viewMatrix = cam.GetStereoViewMatrix(UnityEngine.Camera.StereoscopicEye.Right); - RenderTexture target = m_UseSharedRenderTexture ? m_SharedReflectionTextureRight : reflectionData.right; + var eyePos = cam.transform.TransformPoint(SteamVR.instance.eyes[1].pos); + var eyeRot = cam.transform.rotation * SteamVR.instance.eyes[1].rot; + var projectionMatrix = GetSteamVRProjectionMatrix(cam, EVREye.Eye_Right); + var target = m_UseSharedRenderTexture ? m_SharedReflectionTextureRight : reflectionData.right; reflectionData.propertyBlock.SetTexture(s_RightTexturePropertyID, target); - RenderMirror(reflectionData.camera, target, projectionMatrix, viewMatrix); + RenderMirror(reflectionData.camera, target, eyePos, eyeRot, projectionMatrix); } } else { - RenderTexture target = m_UseSharedRenderTexture ? m_SharedReflectionTextureLeft : reflectionData.left; + var target = m_UseSharedRenderTexture ? m_SharedReflectionTextureLeft : reflectionData.left; reflectionData.propertyBlock.SetTexture(s_LeftTexturePropertyID, target); - RenderMirror(reflectionData.camera, target, cam.projectionMatrix, cam.worldToCameraMatrix); + RenderMirror(reflectionData.camera, target, cam.transform.position, cam.transform.rotation, cam.projectionMatrix); } // Apply the property block containing the texture references to the renderer @@ -120,25 +122,28 @@ public void OnWillRenderObject() s_InsideRendering = false; } - void RenderMirror(UnityEngine.Camera reflectionCamera, RenderTexture targetTexture, Matrix4x4 camProjectionMatrix, Matrix4x4 camViewMatrix) + private void RenderMirror(UnityEngine.Camera reflectionCamera, RenderTexture targetTexture, Vector3 camPosition, Quaternion camRotation, Matrix4x4 camProjectionMatrix) { - // Copy camera projection matrix into the reflectionCamera + // Copy camera position/rotation/projection data into the reflectionCamera + reflectionCamera.ResetWorldToCameraMatrix(); + reflectionCamera.transform.position = camPosition; + reflectionCamera.transform.rotation = camRotation; reflectionCamera.projectionMatrix = camProjectionMatrix; - reflectionCamera.targetTexture = targetTexture; + reflectionCamera.cullingMask = ~(1 << 4) & m_ReflectLayers.value; // never render water layer // find out the reflection plane: position and normal in world space - Vector3 pos = transform.position; - Vector3 normal = transform.up; + var pos = transform.position; + var normal = transform.up; // Reflect camera around reflection plane - Vector4 worldSpaceClipPlane = Plane(pos, normal); - reflectionCamera.worldToCameraMatrix = camViewMatrix * CalculateReflectionMatrix(worldSpaceClipPlane); + var worldSpaceClipPlane = Plane(pos, normal); + reflectionCamera.worldToCameraMatrix *= CalculateReflectionMatrix(worldSpaceClipPlane); // Setup oblique projection matrix so that near plane is our reflection // plane. This way we clip everything behind it for free. - Vector4 cameraSpaceClipPlane = CameraSpacePlane(reflectionCamera, pos, normal); + var cameraSpaceClipPlane = CameraSpacePlane(reflectionCamera, pos, normal); reflectionCamera.projectionMatrix = reflectionCamera.CalculateObliqueMatrix(cameraSpaceClipPlane); // Set camera position and rotation (even though it will be ignored by the render pass because we @@ -345,7 +350,7 @@ private static Matrix4x4 CalculateReflectionMatrix(Vector4 plane) return reflectionMat; } - public static Matrix4x4 GetSteamVRProjectionMatrix(UnityEngine.Camera cam, Valve.VR.EVREye eye) + public static Matrix4x4 GetSteamVRProjectionMatrix(UnityEngine.Camera cam, EVREye eye) { var proj = SteamVR.instance.hmd.GetProjectionMatrix(eye, cam.nearClipPlane, cam.farClipPlane); var m = new Matrix4x4(); diff --git a/Shared/Fixes/Mirror/mirror-shader b/Shared/Fixes/Mirror/mirror-shader new file mode 100644 index 0000000..4eebb68 Binary files /dev/null and b/Shared/Fixes/Mirror/mirror-shader differ diff --git a/Shared/Fixes/ObiControlFix.cs b/Shared/Fixes/ObiControlFix.cs new file mode 100644 index 0000000..14c1346 --- /dev/null +++ b/Shared/Fixes/ObiControlFix.cs @@ -0,0 +1,177 @@ +#if KKS +using System.Collections.Generic; +using System.Diagnostics; +using System.Reflection.Emit; +using HarmonyLib; +using Obi; +using UnityEngine; +using VRGIN.Core; + + +namespace KK_VR.Fixes +{ + #region Fix display of bodily fluids in H scene. + [HarmonyPatch] + public class ObiCtrlFix + { + // Due to being quite expensive (about 10 - 15% added gpu load), and vr being extremely gpu hungry, we keep it disabled until we want it. + // In this state it adds about 1 - 2% to load, further suppression isn't worth it due to intricacy. + // It puzzles me why they went for gpu implenetation and not cpu. Wasn't available in older version? + + // Native code resets particles every time animController changes state, changed to more persistent behavior in SensibleH. + + public static bool _activeState; + public static bool _rendererState; + public static bool VRGINCameraSet; + public static ObiFluidRenderer _obiComponent; + public static void OnHSceneEnd() + { + _activeState = false; + VRGINCameraSet = false; + _rendererState = false; + if (_obiComponent != null) + { + Component.Destroy(VR.Camera.GetComponent()); + } + } + + public static void SetFluidsState(bool state) => _activeState = state; + + public static void SetRenderer(bool state) + { + if (_obiComponent == null) + { + _obiComponent = VR.Camera.GetComponent(); + } + _rendererState = state; + _obiComponent.enabled = state; + } + + /// + /// We throttle down activity of the fluid system when there is no need for it. + /// + [HarmonyPrefix, HarmonyPatch(typeof(ObiCtrl), nameof(ObiCtrl.Proc))] + public static bool ObiCtrlProcPrefix(ObiCtrl __instance) + { + if (!_activeState) + { + if (_rendererState) + { + SetRenderer(false); + __instance.solver.gameObject.SetActive(false); + } + return false; + } + else + { + if (!_rendererState) + { + SetRenderer(true); + __instance.solver.gameObject.SetActive(true); + } + return true; + } + } + // Hooks to enable renderer. + // The rest is in CameraMoveHooks. + [HarmonyPostfix] + [HarmonyPatch(typeof(HFlag), nameof(HFlag.AddHoushiInside))] + [HarmonyPatch(typeof(HFlag), nameof(HFlag.AddHoushiOutside))] + [HarmonyPatch(typeof(HFlag), nameof(HFlag.AddSonyuInside))] + [HarmonyPatch(typeof(HFlag), nameof(HFlag.AddSonyuAnalInside))] + [HarmonyPatch(typeof(HFlag), nameof(HFlag.AddSonyuOutside))] + [HarmonyPatch(typeof(HFlag), nameof(HFlag.AddSonyuAnalOutside))] + public static void AddFinishPostfix() + { + SetFluidsState(true); + } + + [HarmonyPrefix, HarmonyPatch(typeof(ObiFluidManager), nameof(ObiFluidManager.Setup))] + public static void ObiFluidManagerSetupPrefix(ObiFluidManager __instance) + { + if (!VRGINCameraSet) + { + //VRPlugin.Logger.LogDebug($"ObiFluidManagerSetup[TransferComponentToVRGIN][Initialized = {__instance.obiFluidRenderer[0] != null}]"); + VRGINCameraSet = true; + _rendererState = true; + var origComponent = __instance.obiFluidRenderer[0]; + VRGIN.Helpers.UnityHelper.CopyComponent(origComponent, VR.Camera.gameObject); + __instance.obiFluidRenderer[0] = VR.Camera.GetComponent(); + Component.Destroy(origComponent); + } + } + + [HarmonyTranspiler, HarmonyPatch(typeof(ObiBaseFluidRenderer), "Awake")] + public static IEnumerable AwakeCameraSubstitute(IEnumerable instructions) + { + var found = false; + var done = false; + foreach (var code in instructions) + { + if (found && !done) + { + if (code.opcode == OpCodes.Ldarg_0) + { + yield return new CodeInstruction(OpCodes.Nop); + continue; + } + else + { + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ObiCtrlFix), nameof(ObiCtrlFix.GetVRCamera))); + done = true; + continue; + } + } + + yield return code; + found = true; + } + } + + [HarmonyTranspiler, HarmonyPatch(typeof(ObiBaseFluidRenderer), "DestroyCommandBuffer")] + public static IEnumerable DestroyCommandBufferCameraSubstitute(IEnumerable instructions) + { + var found = false; + var done = false; + var counter = 0; + foreach (var code in instructions) + { + if (!found && code.opcode == OpCodes.Ldarg_0) + { + counter++; + if (counter == 2) + { + found = true; + } + } + if (found && !done) + { + if (code.opcode == OpCodes.Ldarg_0) + { + yield return new CodeInstruction(OpCodes.Nop); + continue; + } + else + { + yield return new CodeInstruction(OpCodes.Call, AccessTools.Method(typeof(ObiCtrlFix), nameof(ObiCtrlFix.GetVRCamera))); + done = true; + continue; + } + } + yield return code; + } + } + + [HarmonyPostfix, HarmonyPatch(typeof(ObiBaseFluidRenderer), nameof(ObiBaseFluidRenderer.OnEnable))] + public static void ObiFluidRendererOnEnablePostfix() + { + VR.Camera.GetComponent().forceIntoRenderTexture = true; + } + public static UnityEngine.Camera GetVRCamera() + { + return VR.Camera.GetComponent(); + } + } + #endregion +} +#endif \ No newline at end of file diff --git a/MainGameVR/Fixes/UnityPatches.cs b/Shared/Fixes/UnityPatches.cs similarity index 98% rename from MainGameVR/Fixes/UnityPatches.cs rename to Shared/Fixes/UnityPatches.cs index 34611ee..3a8b159 100644 --- a/MainGameVR/Fixes/UnityPatches.cs +++ b/Shared/Fixes/UnityPatches.cs @@ -4,7 +4,7 @@ // Collection of patches to Unity. -namespace KKS_VR.Fixes +namespace KK_VR.Fixes { /// /// GraphicRaycaster.sortOrderPriority and GraphicRaycaster.renderOrderPriority @@ -41,5 +41,7 @@ public static void Initialize() } private static UnityEngine.Camera _vrGuiCamera; + + } } diff --git a/Shared/Fixes/Util.cs b/Shared/Fixes/Util.cs new file mode 100644 index 0000000..cecc01f --- /dev/null +++ b/Shared/Fixes/Util.cs @@ -0,0 +1,110 @@ +using KK_VR.Handlers; +using KK_VR.Holders; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace KK_VR.Fixes +{ + static class Util + { + // Our version of C# doesn't have tuples, wtf. + //public struct ValueTuple + //{ + // public T1 Field1 { get; set; } + // public T2 Field2 { get; set; } + // public ValueTuple(T1 x1, T2 x2) + // { + // Field1 = x1; + // Field2 = x2; + // } + + //} + + //public struct ValueTuple + //{ + // public T1 Field1 { get; set; } + // public T2 Field2 { get; set; } + // public T3 Field3 { get; set; } + // public ValueTuple(T1 x1, T2 x2, T3 x3) + // { + // Field1 = x1; + // Field2 = x2; + // Field3 = x3; + // } + + //} + + //public class ValueTuple + //{ + // public static ValueTuple Create(T1 x1, T2 x2) + // { + // return new ValueTuple(x1, x2); + // } + // public static ValueTuple Create(T1 x1, T2 x2, T3 x3) + // { + // return new ValueTuple(x1, x2, x3); + // } + //} + + /// + /// Remove a prefix from the given string. + /// + /// + /// + /// + public static string StripPrefix(string prefix, string str) + { + if (str.StartsWith(prefix)) + { + return str.Substring(prefix.Length); + } + return null; + } + // Redundant. VRGIN.UnityHelper has it. + //public static Component CopyComponent(Component original, GameObject destination) + //{ + // var type = original.GetType(); + // var copy = destination.AddComponent(type); + // // Copied fields can be restricted with BindingFlags + // var fields = type.GetFields(); + // foreach (var field in fields) + // { + // field.SetValue(copy, field.GetValue(original)); + // } + // return copy; + //} + public static Vector3 Divide(Vector3 a, Vector3 b) => new(a.x / b.x, a.y / b.y, a.z / b.z); + public static GameObject CreatePrimitive(PrimitiveType primitive, Vector3 size, Transform parent, Color color, float alpha, bool removeCollider = true) + { + return CreatePrimitive(primitive, size, parent, new Color(color.r, color.g, color.b, alpha), removeCollider); + } + public static GameObject CreatePrimitive(PrimitiveType primitive, Vector3 size, Transform parent, Color color, bool removeCollider = true) + { + var sphere = GameObject.CreatePrimitive(primitive); + if (removeCollider) + { + GameObject.Destroy(sphere.GetComponent()); + } + var renderer = sphere.GetComponent(); + renderer.material = Holder.Material; + renderer.material.color = color; + if (parent != null) + sphere.transform.SetParent(parent, false); + sphere.transform.localScale = size; + return sphere; + } + public static float SignedAngle(Vector3 from, Vector3 to, Vector3 axis) + { + float unsignedAngle = Vector3.Angle(from, to); + + float cross_x = from.y * to.z - from.z * to.y; + float cross_y = from.z * to.x - from.x * to.z; + float cross_z = from.x * to.y - from.y * to.x; + float sign = Mathf.Sign(axis.x * cross_x + axis.y * cross_y + axis.z * cross_z); + return unsignedAngle * sign; + } + } +} diff --git a/MainGameVR/GameStandingMode.cs b/Shared/GameStandingMode.cs similarity index 63% rename from MainGameVR/GameStandingMode.cs rename to Shared/GameStandingMode.cs index d2b698e..46d18dc 100644 --- a/MainGameVR/GameStandingMode.cs +++ b/Shared/GameStandingMode.cs @@ -1,25 +1,22 @@ using System; using System.Collections.Generic; using System.Linq; -using KKS_VR.Features; -using KKS_VR.Settings; -using UnityEngine.XR; +using KK_VR.Features; +using KK_VR.Settings; using VRGIN.Controls; using VRGIN.Modes; -namespace KKS_VR +namespace KK_VR { /// /// Initialize controllers and custom tools /// internal class GameStandingMode : StandingMode { - public override IEnumerable Tools { get; } = new[] - { - typeof(Controls.BetterMenuTool), - typeof(Controls.KoikatuWarpTool), + public override IEnumerable Tools { get; } = + [ typeof(Controls.GameplayTool) - }; + ]; protected override IEnumerable CreateShortcuts() { @@ -31,25 +28,17 @@ protected override IEnumerable CreateShortcuts() protected override Controller CreateLeftController() { var controller = base.CreateLeftController(); - AddComponents(controller, EyeSide.Left); + controller.ToolIndex = 0; return controller; } protected override Controller CreateRightController() { var controller = base.CreateRightController(); - AddComponents(controller, EyeSide.Right); - controller.ToolIndex = 1; + controller.ToolIndex = 0; return controller; } - private static void AddComponents(Controller controller, EyeSide controllerSide) - { - controller.gameObject.AddComponent(); - if (SettingsManager.EnableBoop.Value) - VRBoop.Initialize(controller, controllerSide); - } - protected override void SyncCameras() { // Do nothing. CameraControlControl and friends take care of this. diff --git a/Shared/Grasp/BaseHold.cs b/Shared/Grasp/BaseHold.cs new file mode 100644 index 0000000..ef434e6 --- /dev/null +++ b/Shared/Grasp/BaseHold.cs @@ -0,0 +1,74 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; +using VRGIN.Core; + +namespace KK_VR.Grasp +{ + /// + /// Holds/manipulates whole character + /// + internal class BaseHold + { + internal BaseHold(BodyPart _bodyPart, Transform _objAnim, Transform _attachPoint) + { + bodyPart = _bodyPart; + objAnim = _objAnim; + attachPoint = _attachPoint; + offsetPos = _attachPoint.InverseTransformDirection(_objAnim.transform.position - _attachPoint.position); + offsetRot = Quaternion.Inverse(_attachPoint.rotation) * _objAnim.transform.rotation; + } + private readonly BodyPart bodyPart; + private readonly Transform objAnim; + private readonly Transform attachPoint; + private Quaternion offsetRot; + private Vector3 offsetPos; + private int scrollDir; + private bool scrollInc; + + private readonly Quaternion _left = Quaternion.Euler(0f, 1f, 0f); + private readonly Quaternion _right = Quaternion.Euler(0f, -1f, 0f); + + internal void Execute() + { + if (scrollDir != 0) + { + if (scrollDir == 1) + { + DoBaseHoldVerticalScroll(scrollInc); + } + else + { + DoBaseHoldHorizontalScroll(scrollInc); + } + } + objAnim.transform.SetPositionAndRotation( + attachPoint.position + attachPoint.TransformDirection(offsetPos), + attachPoint.rotation * offsetRot + ); + } + + internal void StartBaseHoldScroll(int direction, bool increase) + { + scrollDir = direction; + scrollInc = increase; + } + + internal void StopBaseHoldScroll() + { + scrollDir = 0; + } + + private void DoBaseHoldVerticalScroll(bool increase) + { + offsetPos += VR.Camera.Head.forward * (Time.deltaTime * (increase ? 10f : -10f)); + } + + + private void DoBaseHoldHorizontalScroll(bool left) + { + offsetRot *= (left ? _left : _right); + } + } +} diff --git a/Shared/Grasp/BodyPart.cs b/Shared/Grasp/BodyPart.cs new file mode 100644 index 0000000..1ad0e55 --- /dev/null +++ b/Shared/Grasp/BodyPart.cs @@ -0,0 +1,104 @@ +using Illusion.Component.Correct; +using KK_VR.Handlers; +using KK_VR.Interpreters; +using System; +using System.Collections.Generic; +using System.Text; +using static KK_VR.Grasp.GraspController; +using UnityEngine; + +namespace KK_VR.Grasp +{ + /// + /// Convergence of everything we need to manipulate each body part of a character with the IK + /// + internal class BodyPart + { + internal readonly PartName name; + // Personal for each limb. + internal readonly Transform anchor; + // Character bone after IK. + internal readonly Transform afterIK; + // Character bone before IK. (Extra or native gameObject) + internal readonly Transform beforeIK; + // Whatever was in effector.target at the start. We need it to not upset default code when animator changes states (swap done with harmony hooks). Still actual after new IK? + internal readonly Transform origTarget; + // Not applicable to head. + internal readonly KK.RootMotion.FinalIK.IKEffector effector; + internal readonly KK.RootMotion.FinalIK.FBIKChain chain; + // Script to keep effector at offset instead of pinning it. Not applicable to head. + //internal readonly KK_VR.IK.OffsetEffector offsetEffector; + // Default component. We need it to not upset default code when animator changes state. + internal readonly BaseData baseData; + internal State state; + internal List colliders = []; + // Component responsible for moving and collider tracking. + internal readonly PartGuide guide; + // Primitive to show attachment point. + internal readonly VisualObject visual; + internal bool IsLimb() => name > PartName.ThighR && name < PartName.Head; + internal BodyPart( + PartName _name, + Transform _afterIK, + Transform _beforeIK, + KK.RootMotion.FinalIK.IKEffector _effector = null, + KK.RootMotion.FinalIK.FBIKChain _chain = null) + { + name = _name; + afterIK = _afterIK; + beforeIK = _beforeIK; + visual = new VisualObject(this); + anchor = new GameObject("ik_ank_" + GetLowerCaseName()).transform; + + if (_name != PartName.Head) + { + effector = _effector; + effector.positionWeight = 0f; + effector.rotationWeight = 1f; + origTarget = effector.target; + baseData = effector.target.GetComponent(); + effector.target = null; + chain = _chain; + guide = visual.gameObject.AddComponent(); + //offsetEffector = anchor.gameObject.AddComponent(); + if (_name == PartName.HandL || _name == PartName.HandR) + { + effector.maintainRelativePositionWeight = KoikatuInterpreter.Settings.MaintainLimbOrientation ? 1f : 0f; + if (KoikatuInterpreter.Settings.PushParent != 0f) + { + chain.push = 1f; + chain.pushParent = KoikatuInterpreter.Settings.PushParent; + } + else + { + chain.push = 0f; + chain.pushParent = 0f; + } + chain.pushSmoothing = KK.RootMotion.FinalIK.FBIKChain.Smoothing.Cubic; + // To my surprise i couldn't make "chain.reach" to run in the game or editor. + // old one has reach working just fine with seemingly the same config. + chain.reach = 0f; + + // Purely aesthetic offset. The gameObject can be moved freely with no repercussions. + guide.transform.localPosition = new(_name == PartName.HandL ? -0.05f : 0.05f, -0.015f, 0f); + } + + } + else + { + guide = visual.gameObject.AddComponent(); + } + } + internal string GetLowerCaseName() + { + var chars = name.ToString().ToCharArray(); + chars[0] = Char.ToLower(chars[0]); + return new string(chars); + } + + // Limbs/head are always On. The rest are conserving precious ticks. + // They are very cheap.. Limit IK on hidden targets instead, that stuff is VERY expensive. + internal bool GetDefaultState() => name > PartName.ThighR; + internal void Destroy() => GameObject.Destroy(guide.gameObject); + } +} diff --git a/Shared/Grasp/BodyPartHead.cs b/Shared/Grasp/BodyPartHead.cs new file mode 100644 index 0000000..c08617b --- /dev/null +++ b/Shared/Grasp/BodyPartHead.cs @@ -0,0 +1,22 @@ +using System; +using System.Collections.Generic; +using System.Text; +using KK_VR.IK; +using static KK_VR.Grasp.GraspController; +using UnityEngine; + +namespace KK_VR.Grasp +{ + internal class BodyPartHead : BodyPart + { + internal readonly KK.RootMotion.FinalIK.FBBIKHeadEffector headEffector; + internal BodyPartHead( + PartName _name, + ChaControl _chara, + Transform _afterIK, + Transform _beforeIK) : base(_name, _afterIK, _beforeIK) + { + headEffector = FBBIK.CreateHeadEffector(_chara, anchor); + } + } +} diff --git a/Shared/Grasp/GraspController.cs b/Shared/Grasp/GraspController.cs new file mode 100644 index 0000000..fa0a6f8 --- /dev/null +++ b/Shared/Grasp/GraspController.cs @@ -0,0 +1,787 @@ +using ADV.Commands.Base; +using Illusion.Component.Correct; +using KK.RootMotion.FinalIK; +using KK_VR.Handlers; +using KK_VR.Holders; +using KK_VR.IK; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Trackers; +using KK_VR.Grasp; +using RootMotion.FinalIK; +using System; +using System.Collections.Generic; +using System.Linq; +using UniRx; +using Unity.Linq; +using UnityEngine; +using VRGIN.Core; +using BodyPart = KK_VR.Grasp.BodyPart; +using static KK_VR.Grasp.GraspController; +using System.Runtime.CompilerServices; + +namespace KK_VR.Grasp +{ + // Named Grasp so there is less confusion with GripMove. + // Each instance associated with hand controller. + + // Why new FinalIK (FBBIK and likes of it) ? + // Pros. I've better mileage with it, it has HeadEffector that i can actually adapt to normal charas, + // it has VRIK for player, it has animClip baker. Far beyond "enough of a reason" in my book. + // Cons. Can't seem to make 'Reach' of IKEffector to work, not in game nor in editor. Old one has it working just fine. + + /// + /// Manipulates IK of the character + /// + internal class GraspController + { + private readonly HandHolder _hand; + private static GraspHelper _helper; + private static readonly List _instances = []; + private static readonly Dictionary> _bodyPartsDic = []; + + private readonly Dictionary> _blackListDic = []; + private static readonly List> _jointGroupList = + [ + [PartName.ShoulderL, PartName.ShoulderR], + [PartName.ThighL, PartName.ThighR] + ]; + + private ChaControl _heldChara; + private ChaControl _syncedChara; + + // For Grip. + private readonly List _heldBodyParts = []; + // For Trigger conditional long press. + private readonly List _tempHeldBodyParts = []; + // For Touchpad. + private readonly List _syncedBodyParts = []; + + private static readonly List _limbPosOffsets = + [ + new Vector3(-0.005f, 0.015f, -0.04f), + new Vector3(0.005f, 0.015f, -0.04f), + Vector3.zero, + Vector3.zero + ]; + private static readonly List _limbRotOffsets = + [ + Quaternion.Euler(0f, 90f, 0f), + Quaternion.Euler(0f, -90f, 0f), + Quaternion.identity, + Quaternion.identity + ]; + + // Add held items too once implemented. All bodyParts have black list entries, dic is sufficient. + internal bool IsBusy => _blackListDic.Count != 0 || (_helper != null && _helper.baseHold != null); + internal Dictionary> GetBlacklistDic => _blackListDic; + internal List GetFullBodyPartList(ChaControl chara) => _bodyPartsDic[chara]; + internal enum State + { + Default, // Follows animation, no offsets, no rigidBodies. + Translation, // Is being returned to default/??? state. + Active, // Has offset and rigidBody(for Limbs) or specialHandler(for Joints/Head. Not implemented). + Grasped, // Is being held. + Synced, // Follows some weird transform, rigidBody disabled. For now only limbs, later joints/head. + Attached, // + //Grounded // Not implemented. Is attached to floor/some map item collider. + } + + public enum PartName + { + Spine, + ShoulderL, + ShoulderR, + ThighL, + ThighR, + HandL, + HandR, + FootL, + FootR, + Head, + UpperBody, + LowerBody, + Everything + } + internal GraspController(HandHolder hand) + { + _hand = hand; + _instances.Add(this); + } + internal static void Init(IEnumerable charas) + { + _bodyPartsDic.Clear(); + foreach (var inst in _instances) + { + inst.HardReset(); + } + if (_helper == null) + { + _helper = charas.First().gameObject.AddComponent(); + _helper.Init(charas, _bodyPartsDic); +#if DEBUG + VRPlugin.Logger.LogInfo($"Grasp:Init"); +#endif + } + else + { + VRPlugin.Logger.LogWarning($"Grasp:Init - wrong state, Grasp already exists"); + } + } + private void UpdateGrasp(BodyPart bodyPart, ChaControl chara) + { + _heldChara = chara; + _heldBodyParts.Add(bodyPart); + } + private void UpdateGrasp(IEnumerable bodyPart, ChaControl chara) + { + _heldChara = chara; + _heldBodyParts.AddRange(bodyPart); + } + + private void UpdateTempGrasp(BodyPart bodyPart) + { + _tempHeldBodyParts.Add(bodyPart); + } + private void UpdateSync(BodyPart bodyPart, ChaControl chara) + { + _syncedChara = chara; + _syncedBodyParts.Add(bodyPart); + } + private void StopGrasp() + { + _heldBodyParts.Clear(); + if (_heldChara != null) + { + _blackListDic.Remove(_heldChara); + _heldChara = null; + _tempHeldBodyParts.Clear(); + + UpdateBlackList(); + } + _hand.OnGraspRelease(); + } + private void StopTempGrasp() + { + _tempHeldBodyParts.Clear(); + UpdateBlackList(); + } + private void StopSync(bool instant) + { + foreach (var bodyPart in _syncedBodyParts) + { + if (bodyPart.anchor.parent != null && bodyPart.anchor.parent.name.StartsWith("dispos", StringComparison.OrdinalIgnoreCase)) + { + // Check shouldn't be necessary tbh. + GameObject.Destroy(bodyPart.anchor.parent.gameObject); + } + bodyPart.anchor.SetParent(bodyPart.beforeIK, worldPositionStays: true); + if (instant || KoikatuInterpreter.Settings.ReturnBodyPartAfterSync) + { + bodyPart.guide.Sleep(instant); + } + else + { + bodyPart.guide.Stay(); + } + foreach (var collider in bodyPart.colliders) + { + collider.enabled = true; + } + } + _syncedBodyParts.Clear(); + _hand.OnLimbSyncStop(); + if (_syncedChara != null) + { + _syncedChara = null; + UpdateBlackList(); + } + } + private PartName ConvertTrackerToIK(Tracker.Body part) + { + return part switch + { + Tracker.Body.ArmL => PartName.ShoulderL, + Tracker.Body.ArmR => PartName.ShoulderR, + Tracker.Body.MuneL or Tracker.Body.MuneR => PartName.UpperBody, + Tracker.Body.LowerBody => PartName.Spine, + Tracker.Body.LegL or Tracker.Body.FootL => PartName.FootL, + Tracker.Body.LegR or Tracker.Body.FootR => PartName.FootR, + Tracker.Body.ThighL => PartName.ThighL, + Tracker.Body.ThighR => PartName.ThighR, + Tracker.Body.HandL or Tracker.Body.ForearmL => PartName.HandL, + Tracker.Body.HandR or Tracker.Body.ForearmR => PartName.HandR, + Tracker.Body.Groin or Tracker.Body.Asoko => PartName.LowerBody, + Tracker.Body.Head => PartName.Head, + // actual UpperBody + _ => PartName.Spine, + }; + } + + private PartName GetChild(PartName parent) + { + // Shoulders/thighs found separately based on the distance. + return parent switch + { + PartName.ThighL => PartName.FootL, + PartName.ThighR => PartName.FootR, + PartName.ShoulderL => PartName.HandL, + PartName.ShoulderR => PartName.HandR, + _ => parent + }; + } + + private PartName FindJoints(List lstBodyPart, Vector3 pos) + { + // Finds joint pair that was closer to the core and returns it as abnormal index for further processing. + var list = new List(); + foreach (var partNames in _jointGroupList) + { + // Avg distance to both joints + list.Add( + (Vector3.Distance(lstBodyPart[(int)partNames[0]].effector.bone.position, pos) + + Vector3.Distance(lstBodyPart[(int)partNames[1]].effector.bone.position, pos)) + * 0.5f); + } + // 0 - Shoulders, 1 - thighs + return list[0] - 0.1f > list[1] ? PartName.LowerBody : PartName.UpperBody; + } + + private List FindJoint(List lstBodyPart, List partNames, Vector3 pos) + { + // Works with abnormal index, returns closer joint or both based on the distance. + var a = Vector3.Distance(lstBodyPart[(int)partNames[0]].effector.bone.position, pos); + var b = Vector3.Distance(lstBodyPart[(int)partNames[1]].effector.bone.position, pos); + if ((a > b && a * 0.85f < b) + || (a < b && a > b * 0.85f)) + { + // Meaning they are approx equal. + return partNames; + } + else + { + // Nope, they weren't. + return [a < b ? partNames[0] : partNames[1]]; + } + } + + /// + /// Returns 1 .. 3 names that we should start interaction with. + /// + private List GetTargetParts(List lstBodyPart, PartName target, Vector3 pos) + { + // Finds PartName(s) that we should initially target. + + var bodyPartList = new List(); + if (target == PartName.Spine) + { + bodyPartList.Add(lstBodyPart[(int)target]); + target = FindJoints(lstBodyPart, pos); + } + // abnormal index, i.e. pair of joints + if (target > PartName.Head) + { + FindJoint(lstBodyPart, _jointGroupList[target == PartName.UpperBody ? 0 : 1], pos) + .ForEach(name => bodyPartList.Add(lstBodyPart[(int)name])); + } + else + { + bodyPartList.Add(lstBodyPart[(int)target]); + } + return bodyPartList; + } + /// + /// Returns name of corresponding parent. + /// + private PartName GetParent(PartName childName) + { + return childName switch + { + PartName.Spine => PartName.Everything, + PartName.Everything => childName, + PartName.HandL => PartName.ShoulderL, + PartName.HandR => PartName.ShoulderR, + PartName.FootL => PartName.ThighL, + PartName.FootR => PartName.ThighR, + // For shoulders/thighs + _ => PartName.Spine + }; + } + internal bool OnTriggerPress(bool temporarily) + { + //VRPlugin.Logger.LogDebug($"OnTriggerPress"); + + // We look for a BodyPart from which grasp has started (0 index in _heldBodyParts), + // and attach it to the collider's gameObjects. + + if (_heldChara != null) + { + // First we look if it's a limb and it has tracking on something. + // If there is no track, then expand limbs we are holding. + var heldBodyParts = _heldBodyParts.Concat(_tempHeldBodyParts); + var bodyPartsLimbs = heldBodyParts + .Where(b => b.IsLimb() && b.guide.IsBusy); + if (bodyPartsLimbs.Any()) + { + foreach (var bodyPart in bodyPartsLimbs) + { + AttachBodyPart(bodyPart, bodyPart.guide.GetTrackTransform, bodyPart.guide.GetChara); + } + ReleaseBodyParts(heldBodyParts); + StopGrasp(); + } + else + { + return ExtendGrasp(temporarily); + } + } + else if (_syncedChara != null) + { + var bodyParts = _syncedBodyParts + .Where(b => b.guide.IsBusy); + if (bodyParts.Any()) + { + foreach (var bodyPart in bodyParts) + { + AttachBodyPart(bodyPart, bodyPart.guide.GetTrackTransform, bodyPart.guide.GetChara); + } + ReleaseBodyParts(bodyParts); + StopGrasp(); + } + } + else + { + return false; + } + return true; + } + + private bool ExtendGrasp(bool temporarily) + { + // Attempts to grasp BodyPart(s) higher in hierarchy or everything if already top. + var bodyPartList = _bodyPartsDic[_heldChara]; + var closestToCore = _heldBodyParts + .OrderBy(bodyPart => bodyPart.name) + .First().name; + var nearbyPart = GetChild(closestToCore); + if (nearbyPart == closestToCore || bodyPartList[(int)nearbyPart].state > State.Translation) + { + nearbyPart = GetParent(closestToCore); + } + var attachPoint = bodyPartList[(int)closestToCore].anchor; + if (nearbyPart != PartName.Everything) + { + if (temporarily) + UpdateTempGrasp(bodyPartList[(int)nearbyPart]); + else + { + UpdateGrasp(bodyPartList[(int)nearbyPart], _heldChara); + } + UpdateBlackList(); + GraspBodyPart(bodyPartList[(int)nearbyPart], attachPoint); + } + else + { + ReleaseBodyParts(bodyPartList); + HoldChara(); + } + return true; + } + private void HoldChara() + { + _helper.StartBaseHold(_bodyPartsDic[_heldChara][0], _heldChara.objAnim.transform, _hand.Anchor); + } + internal void OnTriggerRelease() + { + if (_tempHeldBodyParts.Count > 0) + { + ReleaseBodyParts(_tempHeldBodyParts); + StopTempGrasp(); + UpdateBlackList(); + } + } + // Reset currently held body parts. + internal bool OnTouchpadResetHeld() + { + if (_helper != null && _heldBodyParts.Count > 0) + { + ResetBodyParts(_heldBodyParts, false); + ResetBodyParts(_tempHeldBodyParts, false); + StopGrasp(); + //_hand.Handler.ClearTracker(); + return true; + } + return false; + } + // Reset tracking by controller body part if not in default state. + internal bool OnTouchpadResetActive(Tracker.Body trackerPart, ChaControl chara) + { + // We attempt to reset orientation if part was active. + if (_helper != null && _bodyPartsDic.ContainsKey(chara)) + { + var baseName = ConvertTrackerToIK(trackerPart); + if (baseName != PartName.Spine) + { + var bodyParts = GetTargetParts(_bodyPartsDic[chara], baseName, _hand.Anchor.position); + var result = false; + foreach (var bodyPart in bodyParts) + { + if (bodyPart.state > State.Translation) + { + bodyPart.guide.Sleep(false); + result = true; + } + } + //if (result) + //{ + // _hand.Handler.ClearTracker(); + //} + return result; + } + else + { + // If torso - reset whole chara. + return OnTouchpadResetEverything(chara, State.Synced); + } + } + return false; + } + internal bool OnTouchpadResetEverything(ChaControl chara, State upToState = State.Synced) + { + if (_helper != null && _bodyPartsDic.ContainsKey(chara)) + { + var result = false; + foreach (var bodyPart in _bodyPartsDic[chara]) + { + if (bodyPart.state > State.Translation && bodyPart.state <= upToState) + { + bodyPart.guide.Sleep(false); + result = true; + } + } + //_hand.Handler.ClearTracker(); + return result; + } + return false; + } + //internal bool OnMenuPress() + //{ + // if (_heldBodyParts.Count != 0) + // { + + // } + // else + // { + // return false; + // } + // return true; + //} + internal void OnGripPress(Tracker.Body trackerPart, ChaControl chara) + { + if (_helper != null && _bodyPartsDic.ContainsKey(chara)) + { + var anchor = _hand.Anchor; + var bodyParts = GetTargetParts(_bodyPartsDic[chara], ConvertTrackerToIK(trackerPart), anchor.position); + + // Update blackList before actual grasp !!! + UpdateGrasp(bodyParts, chara); + UpdateBlackList(); + foreach (var bodyPart in bodyParts) + { + GraspBodyPart(bodyPart, anchor); + } + if (MouthGuide.Instance != null) + { + MouthGuide.Instance.PauseInteractions = true; + } + _hand.OnGraspHold(); + } + } + internal void OnGripRelease() + { + if (_helper != null) + { + if (_helper.baseHold != null) + { + _helper.StopBaseHold(); + StopGrasp(); + } + else if (_heldBodyParts.Count > 0) + { + ReleaseBodyParts(_heldBodyParts); + ReleaseBodyParts(_tempHeldBodyParts); + StopGrasp(); + } + } + } + private bool AttemptToScrollBodyPart(bool increase) + { + // Only bodyParts directly from the tracker live at 0 index, i.e. firstly interacted with. + if (_helper != null && _heldBodyParts.Count > 0 && (_heldBodyParts[0].name == PartName.HandL || _heldBodyParts[0].name == PartName.HandR)) + { + _helper.ScrollHand(_heldBodyParts[0].name, _heldChara, increase); + return true; + } + return false; + + } + + + + internal bool OnBusyHorizontalScroll(bool increase) + { + if (_helper.baseHold != null) + { + _helper.baseHold.StartBaseHoldScroll(2, increase); + } + else if (!AttemptToScrollBodyPart(increase)) + { + return false; + } + return true; + } + internal bool OnFreeHorizontalScroll(Tracker.Body trackerPart, ChaControl chara, bool increase) + { + if (_helper != null && trackerPart == Tracker.Body.HandL || trackerPart == Tracker.Body.HandR) + { + _helper.ScrollHand((PartName)trackerPart, chara, increase); + return true; + } + return false; + } + + internal void OnScrollRelease() + { + if (_helper != null) + { + if (_helper.baseHold != null) + { + _helper.baseHold.StopBaseHoldScroll(); + } + else + { + _helper.StopScroll(); + } + } + } + + internal bool OnVerticalScroll(bool increase) + { + if (_helper != null) + { + if (_helper.baseHold != null) + { + _helper.baseHold.StartBaseHoldScroll(1, increase); + } + else if (_heldBodyParts.Count > 0) + { + foreach (var bodyPart in _heldBodyParts) + { + bodyPart.visual.SetState(increase); + } + } + else + { + return false; + } + return true; + } + return false; + } + + private void ReleaseBodyParts(IEnumerable bodyPartsList) + { + foreach (var bodyPart in bodyPartsList) + { + // Attached bodyParts released one by one if they overstretch (not implemented), or by directly grabbing/resetting one. + if (bodyPart.state != State.Default && bodyPart.state != State.Attached) + { + bodyPart.guide.Stay(); + } + } + } + + private void ResetBodyParts(IEnumerable bodyPartList, bool instant) + { + foreach (var bodyPart in bodyPartList) + { + if (bodyPart.state != State.Default) + { + bodyPart.guide.Sleep(instant); + } + } + } + internal static void OnSpotPoseChange() + { + // If we are initiated. Everything attaches to charas, they gone - whole grasp too. First chara has master components. But all charas have extra weight on them. + if (_helper != null) + { + _helper.OnPoseChange(); + foreach (var inst in _instances) + { + inst.SoftReset(); + } + } + } + private void SoftReset() + { + _hand.Handler.ClearTracker(); + _helper.StopBaseHold(); + _blackListDic.Clear(); + + ResetBodyParts(_heldBodyParts, instant: true); + _heldBodyParts.Clear(); + + ResetBodyParts(_tempHeldBodyParts, instant: true); + _tempHeldBodyParts.Clear(); + + StopSync(instant: true); + _syncedBodyParts.Clear(); + + _heldChara = null; + _syncedChara = null; + } + + private void HardReset() + { + _blackListDic.Clear(); + _heldBodyParts.Clear(); + _tempHeldBodyParts.Clear(); + _syncedBodyParts.Clear(); + _heldChara = null; + _syncedChara = null; + } + + private void SyncBodyPart(BodyPart bodyPart, Transform attachPoint) + { + foreach (var collider in bodyPart.colliders) + { + collider.enabled = false; + } + bodyPart.anchor.SetParent(attachPoint, worldPositionStays: true); + if (bodyPart.guide is BodyPartGuide bodyGuide) + { + bodyGuide.OnSyncStart(); + } + bodyPart.chain.bendConstraint.weight = KoikatuInterpreter.Settings.IKDefaultBendConstraint; + } + + // We attach bodyPart to a static object or to ik driven chara. + // Later has 4 different states during single frame, so we can't parent but follow manually instead. + private void AttachBodyPart(BodyPart bodyPart, Transform attachPoint, ChaControl chara) + { + if (bodyPart.chain != null) + { + bodyPart.chain.bendConstraint.weight = KoikatuInterpreter.Settings.IKDefaultBendConstraint; + } + bodyPart.guide.Attach(attachPoint); + + //_hand.Handler.RemoveGuideObjects(); + } + + private void GraspBodyPart(BodyPart bodyPart, Transform attachPoint) + { + bodyPart.guide.Follow(attachPoint, _hand); + } + private bool IsLimb(PartName partName) => partName > PartName.ThighR && partName < PartName.UpperBody; + internal bool OnTouchpadSyncStart(Tracker.Body trackerPart, ChaControl chara) + { + if (_helper != null) + { + var partName = ConvertTrackerToIK(trackerPart); + if (IsLimb(partName)) + { + VRPlugin.Logger.LogDebug($"Grasp:OnTouchpadSyncStart:{trackerPart} -> {partName}"); + var bodyPart = _bodyPartsDic[chara][(int)partName]; + + var limbIndex = (int)partName - 5; + var disposable = new GameObject("disposeOnSyncEnd").transform; + disposable.SetParent(_hand.Anchor, worldPositionStays: false); + disposable.localPosition = _limbPosOffsets[limbIndex]; + disposable.localRotation = _limbRotOffsets[limbIndex]; + + SyncBodyPart(bodyPart, disposable); + //bodyPart.anchor.transform.localPosition = _limbPosOffsets[limbIndex]; + //bodyPart.anchor.transform.localRotation = _limbRotOffsets[limbIndex]; + //bodyPart.chain.pull = 0f; + UpdateSync(bodyPart, chara); + UpdateBlackList(); + _hand.OnLimbSyncStart(); + _hand.Handler.ClearTracker(); + //_hand.Handler.ClearBlacks(); + return true; + } + } + return false; + } + + internal bool OnTouchpadSyncStop() + { + if (_helper != null && _syncedBodyParts.Count != 0) + { + StopSync(instant: false); + return true; + } + return false; + } + + + private void UpdateBlackList() + { + _blackListDic.Clear(); + SyncBlackList(_syncedBodyParts, _syncedChara); + SyncBlackList(_heldBodyParts, _heldChara); + SyncBlackList(_tempHeldBodyParts, _heldChara); + _hand.Handler.ClearBlacks(); + } + private void SyncBlackList(List bodyPartList, ChaControl chara) + { + if (chara == null || bodyPartList.Count == 0) return; + + if (!_blackListDic.ContainsKey(chara)) + { + _blackListDic.Add(chara, []); + } + foreach (var bodyPart in bodyPartList) + { + foreach (var entry in _blackListEntries[(int)bodyPart.name]) + { + if (!_blackListDic[chara].Contains(entry)) + _blackListDic[chara].Add(entry); + } + } + + } + + + // Parts that we blacklist and don't track (for that chara?). Tracker can flush active blacklisted tracks on demand. + private static readonly List> _blackListEntries = + [ + // 0 + // 'None' stands for complete ignore, chara will be skipped by that tracker. + [Tracker.Body.None], + // 1 + [ Tracker.Body.HandL, Tracker.Body.ForearmL, Tracker.Body.ArmL, + Tracker.Body.UpperBody, Tracker.Body.MuneL, Tracker.Body.MuneR ], + // 2 + [ Tracker.Body.HandR, Tracker.Body.ForearmR, Tracker.Body.ArmR, + Tracker.Body.UpperBody, Tracker.Body.MuneL, Tracker.Body.MuneR ], + // 3 + [ Tracker.Body.LegL, Tracker.Body.ThighL, Tracker.Body.LowerBody, + Tracker.Body.Asoko, Tracker.Body.Groin], + // 4 + [ Tracker.Body.LegR, Tracker.Body.ThighR, Tracker.Body.LowerBody, + Tracker.Body.Asoko, Tracker.Body.Groin], + // 5 + [Tracker.Body.HandL, Tracker.Body.ForearmL, Tracker.Body.ArmL], + // 6 + [Tracker.Body.HandR, Tracker.Body.ForearmR, Tracker.Body.ArmR], + // 7 + [Tracker.Body.LegL, Tracker.Body.FootL], + // 8 + [Tracker.Body.LegR, Tracker.Body.FootR], + // 9 + [Tracker.Body.None], + ]; + } +} diff --git a/Shared/Grasp/GraspHelper.cs b/Shared/Grasp/GraspHelper.cs new file mode 100644 index 0000000..e11f350 --- /dev/null +++ b/Shared/Grasp/GraspHelper.cs @@ -0,0 +1,706 @@ +using Illusion.Component.Correct; +using KK.RootMotion.FinalIK; +using KK_VR.Camera; +using KK_VR.Fixes; +using KK_VR.Handlers; +using KK_VR.IK; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Trackers; +using KK_VR.Grasp; +using RootMotion.FinalIK; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Core; +using static KK_VR.Grasp.GraspController; +using static KK_VR.Grasp.TouchReaction; +using BodyPart = KK_VR.Grasp.BodyPart; +using KK_VR.Holders; +using static KK.RootMotion.FinalIK.IKSolverVR; +using KK_VR.Features; + +namespace KK_VR.Grasp +{ + /// + /// Singleton, helps Grasp with hooks/init/end. + /// Removes everything Grasp related OnDestroy() + /// + internal class GraspHelper : MonoBehaviour + { + internal static GraspHelper Instance => _instance; + private static GraspHelper _instance; + //private bool _transition; + private bool _animChange; + private bool _handChange; + //private readonly List _transitionList = []; + private readonly Dictionary _animChangeDic = []; + private static Dictionary> _bodyPartsDic; + private static Dictionary _auxDic = []; + private readonly List _handScrollList = []; + internal BaseHold baseHold; + + // Switch from chara root to objAnim. + //private static readonly List _origOrientList = []; + + // Obsolete ? + //private class OrigOrient + //{ + // internal OrigOrient(ChaControl chara) + // { + // _chara = chara.transform; + // _position = _chara.position; + // _rotation = _chara.rotation; + // } + // private readonly Transform _chara; + // private readonly Vector3 _position; + // private readonly Quaternion _rotation; + + // internal void Restore() => _chara.SetPositionAndRotation(_position, _rotation); + //} + private class IKStuff + { + internal KK.RootMotion.FinalIK.FullBodyBipedIK newFbik; + internal RootMotion.FinalIK.FullBodyBipedIK oldFbik; + internal LookAtController lookAt; + internal TouchReaction reaction; + } + internal void Init(IEnumerable charas, Dictionary> bodyPartsDic) + { + _instance = this; + _auxDic.Clear(); + _bodyPartsDic = bodyPartsDic; + foreach (var chara in charas) + { + // Dude will have VRIK. Guess we'll need both and hot swap option for all of them. + // hot swap can't be pretty though, but if it happens on the time of pov exit, then it should be fine. + + // Adapting VRIK proves to be the pure pain. Perhaps will stick to the single mode only for each situation. + // One being half animated/half controlled by VRIK, with some boundaries to keep player going nuts in intercourse/service. + // Another one fully controlled by VRIK with custom advanced locomotion animController. + // Hopefully AgiShark has all the proper animations for advanced locomotion, otherwise I've no clue how to retarget animation for our rig. + // He doesn't. Gotta find someone who'd retarget them for us, otherwise working on VRIK is hardly worth it. + + //if (chara.sex == 1) + //{ + AddChara(chara); + //_origOrientList.Add(new(chara)); + //} + } + } + private void AddChara(ChaControl chara) + { + _auxDic.Add(chara, new IKStuff + { + newFbik = FBBIK.UpdateFBIK(chara), + oldFbik = chara.objAnim.GetComponent(), + lookAt = LookAt.SetupLookAtIK(chara), + reaction = chara.objAnim.AddComponent() + }); + //AnimLoaderHelper.FindMissingBones(_auxDic[chara].oldFbik); + var ik = _auxDic[chara].newFbik; + var oldIK = _auxDic[chara].oldFbik; + if (ik == null || oldIK == null) return; + // MotionIK makes adjusts animations based on the body size, doesn't seem to be used outside of H. + var withMotionIK = KoikatuInterpreter.CurrentScene == KoikatuInterpreter.SceneType.HScene; + _bodyPartsDic.Add(chara, + [ + + new ( + _name: PartName.Spine, + _effector: ik.solver.bodyEffector, + _afterIK: ik.solver.bodyEffector.bone, + _beforeIK: BeforeIK.CreateObj("spine", chara, ik.solver.bodyEffector.bone), + _chain: ik.solver.chain[0] + ), + + new ( + _name: PartName.ShoulderL, + _effector: ik.solver.leftShoulderEffector, + _afterIK: ik.solver.leftShoulderEffector.bone, + _beforeIK: BeforeIK.CreateObj("shoulderL", chara, ik.solver.leftShoulderEffector.bone) + ), + + new ( + _name: PartName.ShoulderR, + _effector: ik.solver.rightShoulderEffector, + _afterIK: ik.solver.rightShoulderEffector.bone, + _beforeIK: BeforeIK.CreateObj("shoulderR", chara, ik.solver.rightShoulderEffector.bone) + ), + + new ( + _name: PartName.ThighL, + _effector: ik.solver.leftThighEffector, + _afterIK: ik.solver.leftThighEffector.bone, + _beforeIK: BeforeIK.CreateObj("thighL", chara, ik.solver.leftThighEffector.bone) + ), + + new ( + _name: PartName.ThighR, + _effector: ik.solver.rightThighEffector, + _afterIK: ik.solver.rightThighEffector.bone, + _beforeIK: BeforeIK.CreateObj("thighR", chara, ik.solver.rightThighEffector.bone) + ), + + new ( + _name: PartName.HandL, + _effector: ik.solver.leftHandEffector, + _afterIK: ik.solver.leftHandEffector.bone, + _beforeIK: withMotionIK ? ik.solver.leftHandEffector.target : BeforeIK.CreateObj("handL", chara, ik.solver.leftHandEffector.bone), + _chain: ik.solver.leftArmChain + ), + + new ( + _name: PartName.HandR, + _effector: ik.solver.rightHandEffector, + _afterIK: ik.solver.rightHandEffector.bone, + _beforeIK: withMotionIK ? ik.solver.rightHandEffector.target : BeforeIK.CreateObj("handR", chara, ik.solver.rightHandEffector.bone), + _chain: ik.solver.rightArmChain + ), + + new ( + _name: PartName.FootL, + _effector: ik.solver.leftFootEffector, + _afterIK: ik.solver.leftFootEffector.bone, + _beforeIK: withMotionIK ? ik.solver.leftFootEffector.target : BeforeIK.CreateObj("footL", chara, ik.solver.leftFootEffector.bone), + _chain: ik.solver.leftLegChain + ), + + new ( + _name: PartName.FootR, + _effector: ik.solver.rightFootEffector, + _afterIK: ik.solver.rightFootEffector.bone, + _beforeIK: withMotionIK ? ik.solver.rightFootEffector.target : BeforeIK.CreateObj("footR", chara, ik.solver.rightFootEffector.bone), + _chain: ik.solver.rightLegChain + ), + + new BodyPartHead( + _name: PartName.Head, + _chara: chara, + _afterIK: ik.references.head, + _beforeIK: BeforeIK.CreateObj("head", chara, ik.references.head) + ), + ]); + + AddExtraColliders(chara); + foreach (var bodyPart in _bodyPartsDic[chara]) + { + bodyPart.anchor.SetParent(bodyPart.beforeIK, worldPositionStays: false); + if (bodyPart is BodyPartHead head) + { + head.headEffector.enabled = KoikatuInterpreter.Settings.IKHeadEffector == KoikatuSettings.HeadEffector.Always; + } + bodyPart.guide.Init(bodyPart); + + //if (KoikatuInterpreter.Settings.IKShowDebug) + //{ + // Fixes.Util.CreatePrimitive(PrimitiveType.Sphere, new Vector3(0.06f, 0.06f, 0.06f), bodyPart.anchor, Color.yellow, 0.5f); + // //Util.CreatePrimitive(PrimitiveType.Sphere, new Vector3(0.12f, 0.12f, 0.12f), bodyPart.afterIK, Color.yellow, 0.4f); + //} + if (bodyPart.IsLimb()) + { + FindColliders(bodyPart, chara); + } + } + SetWorkingState(chara); + + // MonoBehavior will get sad if we won't let it get Start(). + StartCoroutine(InitCo(_bodyPartsDic[chara])); + } + + private IEnumerator InitCo(IEnumerable bodyParts) + { + yield return null; + foreach (var bodyPart in bodyParts) + { + bodyPart.anchor.gameObject.SetActive(bodyPart.GetDefaultState()); + } + } + + internal IKCaress StartIKCaress(HandCtrl.AibuColliderKind colliderKind, ChaControl chara, HandHolder hand) + { + var rough = hand.Anchor.gameObject.AddComponent(); + rough.Init(_auxDic[chara].newFbik, colliderKind, _bodyPartsDic[chara], chara, hand.Anchor); + return rough; + } + + private void OnDestroy() + { + if (_bodyPartsDic != null && _bodyPartsDic.Count > 0) + { + foreach (var bodyPartList in _bodyPartsDic.Values) + { + foreach (var bodyPart in bodyPartList) + { + if (bodyPart.anchor.parent != null && bodyPart.anchor.parent.name.StartsWith("ik_b4", StringComparison.OrdinalIgnoreCase)) + { + GameObject.Destroy(bodyPart.anchor.parent.gameObject); + } + else + { + GameObject.Destroy(bodyPart.anchor.gameObject); + } + GameObject.Destroy(bodyPart.guide.gameObject); + } + } + foreach (var ik in _auxDic.Values) + { + if (ik.newFbik != null) + { + Component.Destroy(ik.newFbik); + } + if (ik.lookAt != null) + { + Component.Destroy(ik.lookAt); + } + if (ik.reaction != null) + { + Component.Destroy(ik.reaction); + } + } + _bodyPartsDic.Clear(); + } + } + + + //private readonly Dictionary _poseRelPos = new Dictionary + //{ + // { "kha_f_00", [ 1f, 0f ] }, + // { "kha_f_01", [ 0f, 0f ] }, + // { "kha_f_02", [ 0f, 0f ] }, + // { "kha_f_03", [ 1f, 1f ] }, + // { "kha_f_04", [ 0f, 0f ] }, + // { "kha_f_05", [ 0f, 0f ] }, + // { "kha_f_06", [ 0f, 0f ] }, + // { "kha_f_07", [ 0f, 1f ] }, + + + // { "khs_f_00", [ 1f, 1f ] }, + //}; + + private readonly List _extraColliders = + [ + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_L/cf_j_leg01_L/cf_j_leg03_L/cf_j_foot_L/cf_hit_leg02_L", + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_R/cf_j_leg01_R/cf_j_leg03_R/cf_j_foot_R/cf_hit_leg02_R", + ]; + + private void AddFeetCollider(Transform bone) + { + // StopGap measure until mesh collider. + var collider = bone.gameObject.GetComponent(); + if (collider == null) + { + collider = bone.gameObject.AddComponent(); + collider.radius = 0.1f; + collider.height = 0.5f; + collider.direction = 2; + bone.localPosition = new Vector3(bone.localPosition.x, 0f, 0.06f); + } + } + private void AddExtraColliders(ChaControl chara) + { + foreach (var path in _extraColliders) + { + AddFeetCollider(chara.objBodyBone.transform.Find(path)); + } + } + private void FindColliders(BodyPart bodyPart, ChaControl chara) + { + foreach (var str in _limbColliders[bodyPart.name]) + { + var target = chara.objBodyBone.transform.Find(str); +#if KK + if (target != null) + { + var col = target.GetComponent(); + if (col != null) + { + bodyPart.colliders.Add(col); + } + } +#else + if (target != null && target.TryGetComponent(out var col)) + { + bodyPart.colliders.Add(col); + } +#endif + } + } + + internal static void SetWorkingState(ChaControl chara) + { + // By default only limbs are used, the rest is limited to offset play by hitReaction. + //VRPlugin.Logger.LogDebug($"Helper:Grasp:SetWorkingState:{chara}"); + if (_bodyPartsDic != null && _bodyPartsDic.ContainsKey(chara)) + { + foreach (var bodyPart in _bodyPartsDic[chara]) + { + if (bodyPart.effector != null) + { + bodyPart.effector.target = bodyPart.anchor; + + if (bodyPart.chain != null) + { + bodyPart.chain.bendConstraint.weight = bodyPart.state == State.Default ? 1f : KoikatuInterpreter.Settings.IKDefaultBendConstraint; + } + } + } + _auxDic[chara].oldFbik.enabled = false; + AnimLoaderHelper.FixExtraAnim(chara, _bodyPartsDic[chara]); + } + } + + /// + /// We put IKEffector.target to ~default state. + /// MotionIK.Calc() requires original stuff, without it we won't get body size offsets or effector's supposed targets. + /// + internal static void SetDefaultState(ChaControl chara, string stateName) + { + //VRPlugin.Logger.LogDebug($"Helper:Grasp:SetDefaultState:{chara}"); + if (_bodyPartsDic != null && _bodyPartsDic.ContainsKey(chara)) + { + if (stateName != null && chara.objTop.activeSelf && chara.visibleAll) + { + _instance.StartAnimChange(chara, stateName); + } + foreach (var bodyPart in _bodyPartsDic[chara]) + { + if (bodyPart.effector != null) + { + bodyPart.effector.target = bodyPart.origTarget; + if (bodyPart.chain != null) + { + bodyPart.chain.bendConstraint.weight = 1f; + } + } + } + } + + } + /// + /// We hold anchors of currently modified Hand bodyParts while animation crossfades, and return back afterwards. + /// + private void StartAnimChange(ChaControl chara, string stateName) + { + //VRPlugin.Logger.LogDebug($"Helper:Grasp:StartAnimChange:{chara}"); + for (var i = 5; i < 7; i++) + { + var bodyPart = _bodyPartsDic[chara][i]; + if (bodyPart.state == State.Active) + { + //var parent = GetParent(bodyPart.name); + //VRPlugin.Logger.LogDebug($"AnimChange:Add:{bodyPart.name} -> {parent} -> {_bodyPartsDic[chara][(int)parent].origTarget}"); + if (!_animChangeDic.ContainsKey(chara)) + { + _animChangeDic.Add(chara, stateName); + _animChange = true; + } + bodyPart.guide.Follow(_bodyPartsDic[chara][i - 4].anchor, null); // anchor.parent = _bodyPartsDic[chara][(int)GetParent(bodyPart.name)].anchor; + } + } + } + + internal void ChangeMaintainRelativePosition(bool active) + { + foreach (var bodyPartList in _bodyPartsDic.Values) + { + for (var i = 5; i < 7; i++) + { + bodyPartList[i].effector.maintainRelativePositionWeight = active ? 1f : 0f; + } + } + } + + internal void ChangeParentPush(float number) + { + foreach (var bodyPartList in _bodyPartsDic.Values) + { + for (var i = 5; i < 7; i++) + { + bodyPartList[i].chain.push = number == 0f ? 0f : 1f; + bodyPartList[i].chain.pushParent = number; + } + } + } + + private void DoAnimChange() + { + foreach (var kv in _animChangeDic) + { + //VRPlugin.Logger.LogDebug($"AnimChangeWait:{kv.Key}:{kv.Value}"); + if (kv.Key.animBody.GetCurrentAnimatorStateInfo(0).IsName(kv.Value)) + { + OnAnimChangeEnd(kv.Key); + return; + } + } + } + private void OnAnimChangeEnd(ChaControl chara) + { + //VRPlugin.Logger.LogDebug($"Helper:Grasp:OnAnimChangeEnd"); + for (var i = 5; i < 7; i++) + { + var bodyPart = _bodyPartsDic[chara][i]; + if (bodyPart.state == State.Active) + { + bodyPart.guide.Stay(); + } + } + _animChangeDic.Remove(chara); + _animChange = _animChangeDic.Count != 0; + } + + internal void ScrollHand(PartName partName, ChaControl chara, bool increase) + { + _handChange = true; + _handScrollList.Add(new HandScroll(partName, chara, increase)); + } + + internal void StopScroll() + { + _handChange = false; + _handScrollList.Clear(); + } + + // Those are some shady animations where ik will be very wonky. + private readonly List _animationsNoIK = + [ + "khs_f_61", + ]; + + internal void OnPoseChange() + { + StopAnimChange(); + foreach (var kv in _bodyPartsDic) + { + // Disable IK if animation is sloppy. + if (kv.Key.animBody.runtimeAnimatorController == null + || _animationsNoIK.Contains(kv.Key.animBody.runtimeAnimatorController.name)) + { + _auxDic[kv.Key].newFbik.enabled = false; + continue; + } + var baseDataEmpty = false; + var count = kv.Value.Count; + for (var i = 0; i < count; i++) + { + var bodyPart = kv.Value[i]; + bodyPart.guide.Sleep(instant: true); + if (bodyPart.IsLimb()) + { + if (bodyPart.baseData.bone == null) + { + baseDataEmpty = true; + } + var component = bodyPart.beforeIK.GetComponent(); + if (_auxDic[kv.Key].oldFbik.solver.effectors[i].rotationWeight == 0f) + { + VRPlugin.Logger.LogWarning($"GraspHelper:RetargetEffectors:[{i}]"); + //bodyPart.baseData.pos = kv.Key.objBodyBone.transform.InverseTransformDirection(bodyPart.baseData.transform.position - bodyPart.effector.bone.position); + //bodyPart.baseData.bone = bodyPart.effector.bone; + if (component == null) + { + bodyPart.beforeIK.gameObject.AddComponent().Init(bodyPart.effector.bone); + } + } + else if (component != null) + { + Component.Destroy(component); + } + } + } + if (baseDataEmpty) + { + AnimLoaderHelper.FindMissingBones(kv.Key.objAnim.GetComponent()); + } + } + } + //internal void RetargetEffectors() + //{ + // // If default target doesn't provide rotation. + // foreach (var kv in _bodyPartsDic) + // { + // for (var i = 5; i < 9; i++) + // { + // var bodyPart = kv.Value[i]; + // var component = bodyPart.beforeIK.GetComponent(); + // if (_auxDic[kv.Key].oldFbik.solver.effectors[i].rotationWeight == 0f) + // { + // VRPlugin.Logger.LogWarning($"RetargetEffectors:[{i}]"); + // //bodyPart.baseData.pos = kv.Key.objBodyBone.transform.InverseTransformDirection(bodyPart.baseData.transform.position - bodyPart.effector.bone.position); + // //bodyPart.baseData.bone = bodyPart.effector.bone; + // if (component == null) + // { + // bodyPart.beforeIK.gameObject.AddComponent().Init(bodyPart.effector.bone); + // } + // } + // else if (component != null) + // { + // Component.Destroy(component); + // } + // } + // } + + //} + + internal bool IsGraspActive(ChaControl chara) + { + foreach (var bodyPart in _bodyPartsDic[chara]) + { + if (bodyPart.state != State.Default) + { + // Don't invoke reaction if any of the bodyParts is manipulated. Looks ugly/disruptive. + return true; + } + } + return false; + } + internal void TouchReaction(ChaControl chara, Vector3 handPosition, Tracker.Body body) + { + if (_auxDic.ContainsKey(chara) && !_auxDic[chara].reaction.IsBusy) + { + foreach (var bodyPart in _bodyPartsDic[chara]) + { + if (bodyPart.IsLimb()) + { + ((BodyPartGuide)bodyPart.guide).StartRelativeRotation(); + } + } + var index = ConvertToTouch(body); + var vec = (GetClosestBone(chara, index).position - handPosition); + vec.y = 0f; + _auxDic[chara].reaction.React(index, vec.normalized); + Features.LoadVoice.PlayVoice(Features.LoadVoice.VoiceType.Short, chara); + } + } + private int ConvertToTouch(Tracker.Body part) + { + return part switch + { + Tracker.Body.LowerBody => 0, + Tracker.Body.ArmL or Tracker.Body.MuneL => 1, + Tracker.Body.ArmR or Tracker.Body.MuneR => 2, + Tracker.Body.ThighL => 3, + Tracker.Body.ThighR => 4, + Tracker.Body.HandL or Tracker.Body.ForearmL => 5, + Tracker.Body.HandR or Tracker.Body.ForearmR => 6, + Tracker.Body.FootL => 7, + Tracker.Body.FootR => 8, + Tracker.Body.UpperBody or Tracker.Body.Head => 9, + Tracker.Body.Groin or Tracker.Body.Asoko => 10, + Tracker.Body.LegL => 11, + Tracker.Body.LegR => 12, + _ => 0 + }; + } + + internal void CatchHitReaction(RootMotion.FinalIK.IKSolverFullBodyBiped solver, Vector3 offset, int index) + { + foreach (var value in _auxDic.Values) + { + if (value.oldFbik.solver == solver) + { + value.newFbik.solver.effectors[index].positionOffset += offset; + return; + } + } + } + internal void OnTouchReactionStop(ChaControl chara) + { + foreach (var bodyPart in _bodyPartsDic[chara]) + { + if (bodyPart.IsLimb()) + { + ((BodyPartGuide)bodyPart.guide).StopRelativeRotation(); + } + } + + } + private Transform GetClosestBone(ChaControl chara, int index) + { + // Normalize, not readable otherwise. + return index switch + { + 0 => _auxDic[chara].newFbik.solver.rootNode, // chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01"), + 1 or 2 or 9 => chara.dictRefObj[ChaReference.RefObjKey.BUSTUP_TARGET].transform, // chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03"), + 3 or 4 or > 9 => chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02"), + _ => _auxDic[chara].newFbik.solver.effectors[index].bone.transform + }; + } + private readonly List _partNamesToHold = + [ + PartName.HandL, + PartName.HandR + ]; + private void Update() + { + baseHold?.Execute(); + if (_animChange) DoAnimChange(); + if (_handChange) DoHandChange(); + } + + + internal void StartBaseHold(BodyPart spine, Transform objAnim, Transform attachPoint) + { + baseHold = new BaseHold(spine, objAnim, attachPoint); + } + + internal void StopBaseHold() + { + baseHold = null; + } + + private void DoHandChange() + { + foreach (var scroll in _handScrollList) + { + scroll.Scroll(); + } + } + + private void StopAnimChange() + { + _animChange = false; + _animChangeDic.Clear(); + } + private static readonly Dictionary> _limbColliders = new() + { + { + PartName.HandL, new List() + { + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_L/cf_j_shoulder_L/" + + "cf_j_arm00_L/cf_j_forearm01_L/cf_d_forearm02_L/cf_s_forearm02_L/cf_hit_wrist_L", + + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_L/cf_j_shoulder_L/cf_j_arm00_L/cf_j_forearm01_L/cf_j_hand_L/com_hit_hand_L", + } + }, + { + PartName.HandR, new List() + { + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_R/cf_j_shoulder_R/" + + "cf_j_arm00_R/cf_j_forearm01_R/cf_d_forearm02_R/cf_s_forearm02_R/cf_hit_wrist_R", + + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_R/cf_j_shoulder_R/cf_j_arm00_R/cf_j_forearm01_R/cf_j_hand_R/com_hit_hand_R", + } + }, + { + PartName.FootL, new List() + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_L/cf_j_leg01_L/cf_s_leg01_L/cf_hit_leg01_L/aibu_reaction_legL", + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_L/cf_j_leg01_L/cf_j_leg03_L/cf_j_foot_L/cf_hit_leg02_L", + } + }, + { + PartName.FootR, new List() + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_R/cf_j_leg01_R/cf_s_leg01_R/cf_hit_leg01_R/aibu_reaction_legR", + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_R/cf_j_leg01_R/cf_j_leg03_R/cf_j_foot_R/cf_hit_leg02_R", + } + } + }; + + } +} diff --git a/Shared/Grasp/HandScroll.cs b/Shared/Grasp/HandScroll.cs new file mode 100644 index 0000000..93f5385 --- /dev/null +++ b/Shared/Grasp/HandScroll.cs @@ -0,0 +1,173 @@ +using KK_VR.Trackers; +using KK_VR.Grasp; +using NodeCanvas.Tasks.Conditions; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace KK_VR.Grasp +{ + /// + /// Changes blend values of the character's hand + /// + internal class HandScroll + { + internal HandScroll(GraspController.PartName partName, ChaControl chara, bool increase) + { + _lr = partName == GraspController.PartName.HandL ? 0 : 1; + _chara = chara; + _increase = increase; + _chara.SetEnableShapeHand(_lr, true); + ReTarget(); + } + private ChaControl _chara; + private readonly int _lr; + private readonly bool _increase; + private float _blendValue; + private bool _disable; + + private void ReTarget() + { + + var array = _chara.fileStatus.shapeHandPtn; + //VRPlugin.Logger.LogDebug($"HandScroll:ReTarget:{_chara.fileStatus.shapeHandBlendValue[_lr]}"); + //var newIndex = _increase ? (array[_lr, 1] + 1) % 24 : (array[_lr, 1] - 1) < 0 ? 23 : (array[_lr, 1] - 1); + var newIndex = _increase ? (array[_lr, 1] + 1) % 24 : (array[_lr, 0] - 1) < 0 ? 23 : (array[_lr, 0] - 1); + + + if (_increase) + { + //if (array[_lr, 1] == 0) + //{ + // // Was animated, skip default state and jump to the next. + // //VRPlugin.Logger.LogDebug($"ScrollHand:ExitAnimation:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}"); + // _chara.SetShapeHandValue(_lr, array[_lr, 0], newIndex, 1f); + // _chara.SetEnableShapeHand(_lr, true); + //} + //else + if (_chara.fileStatus.shapeHandBlendValue[_lr] < 1f) + { + _blendValue = _chara.fileStatus.shapeHandBlendValue[_lr]; + //VRPlugin.Logger.LogDebug($"ScrollHand:PrematureBlendValue:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}:{_blendValue}"); + } + else if (_disable) + { + if (_increase) + { + _chara.SetShapeHandValue(_lr, 0, 0, 1f); + } + else + { + _chara.SetShapeHandValue(_lr, 0, 0, 1f); + } + //VRPlugin.Logger.LogDebug($"ScrollHand:Disable:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}"); + _chara.SetEnableShapeHand(_lr, false); + _chara = null; + } + else + { + _chara.SetShapeHandValue(_lr, array[_lr, 1], newIndex, 0f); + _blendValue = 0f; + if (newIndex == 0) _disable = true; + //VRPlugin.Logger.LogDebug($"ScrollHand:{_increase}:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}:blend = {_blendValue}"); + } + } + else + { + //if (array[_lr, 0] == 0) + //{ + // //VRPlugin.Logger.LogDebug($"ScrollHand:ExitAnimation:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}"); + // _chara.SetShapeHandValue(_lr, newIndex, array[_lr, 0], 1f); + // _chara.SetEnableShapeHand(_lr, true); + //} + //else + if (_chara.fileStatus.shapeHandBlendValue[_lr] > 0f) + { + _blendValue = _chara.fileStatus.shapeHandBlendValue[_lr]; + //VRPlugin.Logger.LogDebug($"ScrollHand:PrematureBlendValue:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}:{_blendValue}"); + } + else if (_disable) + { + if (_increase) + { + _chara.SetShapeHandValue(_lr, 0, 0, 1f); + } + else + { + _chara.SetShapeHandValue(_lr, 0, 0, 1f); + } + //VRPlugin.Logger.LogDebug($"ScrollHand:Disable:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}"); + _chara.SetEnableShapeHand(_lr, false); + _chara = null; + } + else + { + _chara.SetShapeHandValue(_lr, newIndex, array[_lr, 0], 1f); + _blendValue = 1f; + if (newIndex == 0) _disable = true; + //VRPlugin.Logger.LogDebug($"ScrollHand:{_increase}:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}:blend = {_blendValue}"); + } + } + + + //if ((_increase && array[_lr, 1] == 0) || (!_increase && array[_lr, 0] == 0)) + //{ + // //VRPlugin.Logger.LogDebug($"ScrollHand:Enable:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}"); + // _chara.SetShapeHandValue(_lr, array[_lr, 0], newIndex, 1f); + // _chara.SetEnableShapeHand(_lr, true); + //} + //else if (newIndex == 0) + //{ + // // Wants to be in default state. Set to animation instead. + // //VRPlugin.Logger.LogDebug($"ScrollHand:Enable:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}"); + // _chara.SetShapeHandValue(_lr, array[_lr, 0], newIndex, 1f); + // _chara.SetEnableShapeHand(_lr, false); + //} + //else + //{ + // //VRPlugin.Logger.LogDebug($"ScrollHand:{_increase}:array - [{array[_lr, 0]}][{array[_lr, 1]}]:new - {newIndex}:blend = {_blendValue}"); + // // If we are in middle of doing stuff. + // if (_chara.fileStatus.shapeHandBlendValue[_lr] <= 0f || _chara.fileStatus.shapeHandBlendValue[_lr] >= 1f) + // { + // _blendValue = _chara.fileStatus.shapeHandBlendValue[_lr]; + // } + // else if (_increase) + // { + // _chara.SetShapeHandValue(_lr, array[_lr, 1], newIndex, 0f); + // _blendValue = 0f; + // } + // else + // { + // _chara.SetShapeHandValue(_lr, newIndex, array[_lr, 0], 1f); + // _blendValue = 1f; + // } + + //} + } + + internal void Scroll() + { + if (_chara != null) + { + if (_increase) + { + _chara.SetShapeHandBlend(_lr, _blendValue += Time.deltaTime * 2f); + if (_blendValue >= 1f) + { + ReTarget(); + } + } + else + { + _chara.SetShapeHandBlend(_lr, _blendValue -= Time.deltaTime * 2f); + if (_blendValue <= 0f) + { + ReTarget(); + } + } + } + } + } +} diff --git a/Shared/Grasp/IKCaress.cs b/Shared/Grasp/IKCaress.cs new file mode 100644 index 0000000..17253ed --- /dev/null +++ b/Shared/Grasp/IKCaress.cs @@ -0,0 +1,148 @@ +using KK_VR.Handlers; +using KK_VR.IK; +using KK_VR.Interpreters; +using System; +using System.Collections.Generic; +using System.Linq; +using UnityEngine; +using static HandCtrl; +using static KK_VR.Grasp.GraspController; + +namespace KK_VR.Grasp +{ + /// + /// Component that applies extra IK offsets during manual caress + /// + internal class IKCaress : OffsetManipulator + { + private bool _end; + private float _startDistance; + private Transform _poi; + private Transform _anchor; + private Vector3 _lastPos; + + private Transform _item; + private int _itemId; + + internal void Init(KK.RootMotion.FinalIK.FullBodyBipedIK ik, AibuColliderKind colliderKind, List bodyPartList, ChaControl chara, Transform anchor) + { + base.OnInit(ik); + _itemId = (int)colliderKind - 2; + _anchor = anchor; + _lastPos = anchor.position; + _item = HSceneInterpreter.handCtrl.useAreaItems[_itemId].obj.transform; + + var slaves = new List(); + foreach (var masterIndex in GetMasterIndex(colliderKind)) + { + Add(bodyPartList[masterIndex], 0.3f); + slaves.AddRange(GetSlaveIndex(masterIndex)); + + } + foreach (var slaveIndex in slaves.Distinct()) + { + var result = false; + foreach (var link in _linkList) + { + if (link.effector == bodyPartList[slaveIndex].effector) + { + result = true; + break; + } + } + if (!result) + { + Add(bodyPartList[slaveIndex], 0.15f); + } + } + //foreach (var test in _linkDic) + //{ + // //VRPlugin.Logger.LogInfo($"RoughCaress:{test.Key.name} - {test.Value.defaultWeight}"); + //} + _poi = chara.objBodyBone.transform.Find(GetPoiName(colliderKind)); + _startDistance = Vector3.SqrMagnitude(_poi.position - anchor.position); + } + + + internal void Move() + { + var vec = (Vector2)_item.InverseTransformVector(_lastPos - _anchor.position); + vec.y = 0f - vec.y; + HSceneInterpreter.hFlag.xy[_itemId] += vec * 10f; + _lastPos = _anchor.position; + } + private float _lerp; + private Vector2 _midVec = new(0.5f, 0.5f); + + + internal void End() + { + _end = true; + } + internal void Update() + { + if (_end) + { + var step = Mathf.SmoothStep(1f, 0f, _lerp += Time.deltaTime); + foreach (var link in _linkList) + { + link.weight = Mathf.Clamp01(link.weight * step); + } + HSceneInterpreter.hFlag.xy[_itemId] = (HSceneInterpreter.hFlag.xy[_itemId] - _midVec) * step + _midVec; + if (step == 0f) + { + Component.Destroy(this); + } + } + else + { + var diff = (Vector3.SqrMagnitude(_poi.position - _anchor.position) - _startDistance) * 10f; + foreach (var link in _linkList) + { + link.weight = Mathf.Clamp01(link.defaultWeight + diff); + } + Move(); + } + } + private int[] GetMasterIndex(AibuColliderKind colliderKind) + { + return colliderKind switch + { + AibuColliderKind.muneL => [1], + AibuColliderKind.muneR => [2], + AibuColliderKind.kokan or AibuColliderKind.anal => [3, 4], + AibuColliderKind.siriL => [3], + AibuColliderKind.siriR => [4], + _ => null + }; + } + private int[] GetSlaveIndex(int masterIndex) + { + return masterIndex switch + { + 1 => [0, 2], + 2 => [0, 1], + 3 => [0, 4], + 4 => [0, 3], + _ => null + }; + } + + private string GetPoiName(AibuColliderKind colliderKind) + { + return colliderKind switch + { + AibuColliderKind.muneL => "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_bust00/cf_s_bust00_L/cf_d_bust01_L" + + "/cf_j_bust01_L/cf_d_bust02_L/cf_j_bust02_L/cf_d_bust03_L/cf_j_bust03_L/cf_s_bust03_L/k_f_mune03L_02", + AibuColliderKind.muneR => "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_bust00/cf_s_bust00_R/cf_d_bust01_R" + + "/cf_j_bust01_R/cf_d_bust02_R/cf_j_bust02_R/cf_d_bust03_R/cf_j_bust03_R/cf_s_bust03_R/k_f_mune03R_02", + AibuColliderKind.kokan => "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_kokan/cf_j_kokan", + AibuColliderKind.anal => "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_ana/cf_j_ana", + AibuColliderKind.siriL => "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_siri_L/cf_d_siri01_L/cf_j_siri_L", + AibuColliderKind.siriR => "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_siri_R/cf_d_siri01_R/cf_j_siri_R", + _ => null + }; + + } + } +} diff --git a/Shared/Grasp/SlapReaction.cs b/Shared/Grasp/SlapReaction.cs new file mode 100644 index 0000000..a8e35ec --- /dev/null +++ b/Shared/Grasp/SlapReaction.cs @@ -0,0 +1,11 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; + +//namespace KK_VR.Grasp +//{ +// internal class SlapReaction +// { +// } +//} diff --git a/Shared/Grasp/TouchReaction.cs b/Shared/Grasp/TouchReaction.cs new file mode 100644 index 0000000..33d21ee --- /dev/null +++ b/Shared/Grasp/TouchReaction.cs @@ -0,0 +1,341 @@ +using KK_VR.IK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using static KK_VR.Grasp.GraspController; +using KK.RootMotion.FinalIK; +using Random = UnityEngine.Random; +using KK_VR.Fixes; + +namespace KK_VR.Grasp +{ + /// + /// Component that implements custom HitReaction, with offsets only. + /// + internal class TouchReaction : MonoBehaviour + { + private class HitPoint + { + internal HitPoint(IKEffector effector, int _index, float _weight, bool useUp = true) + { + id = _index; + _effector = effector; + weight = _weight; + _useUp = useUp; + } + internal readonly int id; + private readonly IKEffector _effector; + internal float weight; + private bool _useUp; + private AnimationCurve _curveForce; + private AnimationCurve _curveUp; + private Vector3 _vecForce; + private Vector3 _vecUp; + private Vector3 _offset; + internal void Start(Vector3 vecForce, Vector3 vecUp, AnimationCurve curveForce, AnimationCurve curveUp) + { + _offset = Vector3.zero; + _vecForce = vecForce; + _vecUp = vecUp; + _curveForce = curveForce; + _curveUp = curveUp; + } + internal void Override(Vector3 vecForce, Vector3 vecUp, AnimationCurve curveForce, AnimationCurve curveUp) + { + curveForce.MoveKey(0, new Keyframe(0f, _offset.magnitude / (vecForce + _offset).magnitude)); + _vecForce = _offset + vecForce; + _vecUp = vecUp; + _curveForce = curveForce; + _curveUp = curveUp; + } + internal void Move(float time) + { + if (weight == 0f) return; + _offset = _curveForce.Evaluate(time) * _vecForce; + if (_useUp) + { + _offset += _curveUp.Evaluate(time) * _vecUp; + } + _effector.positionOffset += _offset * weight; + } + } + private readonly Dictionary, float[]> _currentReactions = []; + private readonly List> _reactionList = []; + private IKEffector[] _effectors; + private ChaControl chara; + + // Consequent reaction doesn't work well/look good, we'll keep to one at a time. + internal bool IsBusy => _currentReactions.Count > 0; + + private void Awake() + { + chara = transform.GetComponentInParent(); + var effectors = gameObject.GetComponent().solver.effectors; + _effectors = effectors; + _reactionList.AddRange( + [ + [// 0 Body + new HitPoint(effectors[0], 0, 0.15f), + new HitPoint(effectors[1], 1, 0.05f), + new HitPoint(effectors[2], 2, 0.05f), + new HitPoint(effectors[3], 3, 0f), + new HitPoint(effectors[4], 4, 0f), + new HitPoint(effectors[5], 5, -0.1f), + new HitPoint(effectors[6], 6, -0.1f), + new HitPoint(effectors[7], 7, 0.5f, false) + ], + [// 1 ShoulderL + new HitPoint(effectors[1], 1, 0.1f), + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[2], 2, -0.05f), + new HitPoint(effectors[3], 3, 0.05f), + new HitPoint(effectors[4], 4, -0.05f), + new HitPoint(effectors[5], 5, 0.05f) + ], + [ // 2 ShoulderR + new HitPoint(effectors[2], 2, 0.1f), + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[1], 1, -0.05f), + new HitPoint(effectors[3], 3, -0.05f), + new HitPoint(effectors[4], 4, 0.05f), + new HitPoint(effectors[6], 6, 0.05f) + ], + [ // 3 ThighL + new HitPoint(effectors[3], 3, 0.1f), + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[1], 1, 0f), + new HitPoint(effectors[2], 2, 0f), + new HitPoint(effectors[4], 4, 0.05f), + new HitPoint(effectors[7], 7, 0.1f) + ], + [ // 4 ThighR + new HitPoint(effectors[0], 0, 0.2f), + new HitPoint(effectors[3], 3, 0.05f), + new HitPoint(effectors[4], 4, 0.15f), + new HitPoint(effectors[8], 8, 0.1f) + ], + [ // 5 ArmL + new HitPoint(effectors[5], 5, 0.1f), + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[1], 1, 0.05f), + new HitPoint(effectors[2], 2, -0.05f), + new HitPoint(effectors[3], 3, 0.05f, false), + new HitPoint(effectors[4], 4, -0.05f, false) + ], + [// 6 ArmR + new HitPoint(effectors[6], 6, 0.1f), + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[1], 1, -0.05f), + new HitPoint(effectors[2], 2, 0.05f), + new HitPoint(effectors[3], 3, -0.05f, false), + new HitPoint(effectors[4], 4, 0.05f, false) + ], + [// 7 FootL + new HitPoint(effectors[1], 1, -0.05f), + new HitPoint(effectors[2], 2, -0.05f), + new HitPoint(effectors[3], 3, 0.05f), + new HitPoint(effectors[4], 4, 0.05f), + new HitPoint(effectors[0], 0, 0.1f), + ], + [// 8 FootR + new HitPoint(effectors[1], 7, -0.05f), + new HitPoint(effectors[2], 8, -0.05f), + new HitPoint(effectors[3], 3, 0.05f), + new HitPoint(effectors[4], 4, 0.05f), + new HitPoint(effectors[0], 0, 0.1f), + ], + [// 9 UpperBody + //new HitPoint(effectors[0], 0, 0.05f), + //new HitPoint(effectors[1], 1, 0.1f), + //new HitPoint(effectors[2], 2, 0.1f), + + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[1], 1, 0.15f), + new HitPoint(effectors[2], 2, 0.15f), + new HitPoint(effectors[3], 3, 0.1f, false), + new HitPoint(effectors[4], 4, 0.1f, false), + new HitPoint(effectors[5], 5, 0.05f ), + new HitPoint(effectors[6], 6, 0f ), + new HitPoint(effectors[7], 7, 0.4f, false), + new HitPoint(effectors[8], 8, 0f, false) + ], + // Delayed reaction via AnimCurve ? + + [// 10 LowerBody // First pass done + new HitPoint(effectors[3], 3, 0.05f, false), + new HitPoint(effectors[4], 4, 0f, false), + new HitPoint(effectors[0], 0, 0.1f, false), + new HitPoint(effectors[1], 1, 0.05f, false), + new HitPoint(effectors[2], 2, -0.05f), + new HitPoint(effectors[5], 5, 0.05f), + new HitPoint(effectors[6], 6, 0.05f), + new HitPoint(effectors[7], 7, 0.05f, false), + new HitPoint(effectors[8], 8, 0f), + ], + [// 11 LegL + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[3], 3, 0.1f), + new HitPoint(effectors[7], 7, 0.2f) + ], + [// 12 LegR + new HitPoint(effectors[0], 0, 0.05f), + new HitPoint(effectors[3], 3, 0.1f), + new HitPoint(effectors[8], 8, 0.2f) + ], + ]); + } + + private Vector3 GetUpVec(int zeroIndexMasterId) + { + return zeroIndexMasterId switch + { + //0 => _effectors[0].bone.up, + //1 or 2 => _effectors[0].bone.transform.rotation * Vector3.down, + //3 or 4 => _effectors[0].bone.up, + 5 or 6 or 7 or 8 => (_effectors[zeroIndexMasterId - 4].bone.position - _effectors[zeroIndexMasterId].bone.position).normalized, + _ => _effectors[0].bone.up + }; + } + private void HelpUpperBody(List hitPoints) + { + + } + //private Vector3 GetRootUpVec(int zeroIndexMasterId) + //{ + // if (zeroIndexMasterId < 3) + // { + // // Shoulders + // return ((_effectors[3].bone.position + _effectors[4].bone.position) * 0.5f) - _effectors[0].bone.position; + // } + // else + // { + // // Thighs + // return ((_effectors[1].bone.position + _effectors[2].bone.position) * 0.5f) - _effectors[0].bone.position; + // } + //} + internal void React(int id, Vector3 direction) + { + // Direction doesn't have Y axis. + //VRPlugin.Logger.LogInfo($"TouchReaction:{id}:{direction}"); + var list = _reactionList[id]; + var duration = 0f; + var upVec = GetUpVec(list[0].id); + direction = HelpVector(list[0].id, direction, upVec); + if (id == 9) + { + // Perhaps there is an easier way to find signed angle in this situation, can't think of it tho. +#if KK + var left = Util.SignedAngle(direction, _effectors[0].bone.forward, _effectors[0].bone.up) > 0f; +#else + var left = Vector3.SignedAngle(direction, _effectors[0].bone.forward, _effectors[0].bone.up) > 0f; +#endif + //var left = Mathf.DeltaAngle(0f, (Quaternion.Inverse(Quaternion.LookRotation(direction) * Quaternion.Euler(0f, 180f, 0f)) * _effectors[0].bone.rotation).eulerAngles.y) < 0f; + //VRPlugin.Logger.LogInfo($"Left = {left}"); + + list[5].weight = left ? 0f : 0.05f; + list[6].weight = left ? 0.05f : 0f; + list[7].weight = left ? 0f : Random.Range(0.3f, 0.5f); + list[8].weight = left ? Random.Range(0.3f, 0.5f) : 0f; + + + } + if (!_currentReactions.ContainsKey(list)) + { + for (int i = 0; i < list.Count; i++) + { + list[i].Start(direction, upVec, GetForceCurve(out var forceDuration), GetUpCurve(forceDuration)); + duration = Mathf.Max(duration, forceDuration); + } + _currentReactions.Add(_reactionList[id], [0f, duration]); + } + else + { + for (int i = 0; i < list.Count; i++) + { + list[i].Override(direction, upVec, GetForceCurve(out var forceDuration), GetUpCurve(forceDuration)); + duration = Mathf.Max(duration, forceDuration); + } + _currentReactions[list][0] = 0f; + _currentReactions[list][1] = duration; + } + } + + // Zero index is a master id from which reaction got triggered. + private Vector3 HelpVector(int index, Vector3 direction, Vector3 upVec) + { + if (index == 5 || index == 6) + { + var hand = _effectors[index].bone; + var shoulder = _effectors[index - 4].bone; + if ((index == 5 && hand.InverseTransformPoint(shoulder.position).y > 0.2f) || (index == 6 && hand.InverseTransformPoint(shoulder.position).y < -0.2)) + { + return Quaternion.Euler(0f, 180f, 0f) * direction * 0.5f + upVec * Random.Range(0.6f, 1f); + } + else + { + var vec = _effectors[0].bone.position - _effectors[index].bone.position; + vec.y = 0f; + if (Vector3.Angle(vec, direction) < 45f) + { + return Quaternion.Euler(0f, Random.Range(45f, 90f) * (Random.value > 0.5f ? 1 : -1), 0f) * direction; + } + } + } + return direction; + } + + private void LateUpdate() + { + if (_currentReactions.Count > 0) + { + foreach (var reaction in _currentReactions) + { + if (reaction.Value[0] > reaction.Value[1]) + { + _currentReactions.Remove(reaction.Key); + if (_currentReactions.Count == 0) + { + GraspHelper.Instance.OnTouchReactionStop(chara); + } + return; + } + else + { + reaction.Value[0] += Time.deltaTime; + foreach (var hitPoint in reaction.Key) + { + hitPoint.Move(reaction.Value[0]); + } + } + } + } + } + private AnimationCurve GetForceCurve(out float duration) + { + //return partName switch + //{ + //PartName.Spine => + return new AnimationCurve( + new Keyframe(0f, 0F), + new Keyframe(Random.Range(0.25f, 0.75f), Random.Range(0.75f, 1f)), + new Keyframe(Random.Range(1.5f, 2f), Random.Range(0.75f, 1f)), + new Keyframe(duration = Random.Range(2.25f, 3f), 0f)); + //}; + } + private AnimationCurve GetUpCurve(float duration) + { + //return partName switch + //{ + //PartName.Spine => + return new AnimationCurve( + new Keyframe(0f, 0f), + new Keyframe(Random.Range(0.25f, 1.5f), Random.Range(0.5f, 1f)), + new Keyframe(Random.Range(1.5f, 2f), Random.Range(0.25f, 0.75f)), + new Keyframe(duration, 0f)); + + //}; + } + } +} diff --git a/Shared/Grasp/VisualObject.cs b/Shared/Grasp/VisualObject.cs new file mode 100644 index 0000000..5a82f67 --- /dev/null +++ b/Shared/Grasp/VisualObject.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static KK_VR.Grasp.GraspController; +using UnityEngine; +using KK_VR.Fixes; +using KK_VR.Settings; +using KK_VR.Interpreters; + +namespace KK_VR.Grasp +{ + /// + /// Visual cue of a character's body part to help during IK manipulation. + /// + internal class VisualObject + { + internal readonly GameObject gameObject; + private readonly Renderer _renderer; + private bool _enable = KoikatuInterpreter.Settings.ShowGuideObjects; + private readonly static List _colors = + [ + new(1f, 0f, 0f, 0.2f), // Red + new(0f, 1f, 0f, 0.2f), // Green + new(0f, 0f, 1f, 0.2f), // Blue + new(1f, 1f, 1f, 0.2f) // Gray + ]; + internal VisualObject(BodyPart bodyPart) + { + gameObject = Util.CreatePrimitive( + PrimitiveType.Sphere, + GetGuideObjectSize(bodyPart.name), + bodyPart.afterIK, + _colors[3], + removeCollider: false); + gameObject.name = "ik_vl_" + bodyPart.GetLowerCaseName(); + _renderer = gameObject.GetComponent(); + _renderer.enabled = false; + } + internal void Show() => _renderer.enabled = _enable && true; + internal void Hide() => _renderer.enabled = false; + internal void SetState(bool state) + { + _enable = state; + if (state) + { + Show(); + } + else + { + Hide(); + } + } + internal void SetColor(bool active) + { + _renderer.material.color = active ? _colors[1] : _colors[3]; + } + private Vector3 GetGuideObjectSize(PartName partName) + { + return partName switch + { + PartName.ShoulderL or PartName.ShoulderR => new Vector3(0.14f, 0.14f, 0.14f), + PartName.HandL or PartName.HandR => new Vector3(0.11f, 0.11f, 0.11f), + _ => new Vector3(0.2f, 0.2f, 0.2f), + }; + } + } +} diff --git a/Shared/Handlers/ForGrasp/BodyPartGuide.cs b/Shared/Handlers/ForGrasp/BodyPartGuide.cs new file mode 100644 index 0000000..c286311 --- /dev/null +++ b/Shared/Handlers/ForGrasp/BodyPartGuide.cs @@ -0,0 +1,247 @@ +using KK.RootMotion.FinalIK; +using KK_VR.Fixes; +using KK_VR.Grasp; +using KK_VR.Handlers; +using KK_VR.Holders; +using KK_VR.IK; +using KK_VR.Interactors; +using KK_VR.Interpreters; +using KK_VR.Trackers; +using RootMotion.FinalIK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Controls; +using VRGIN.Helpers; +using static KK.RootMotion.FinalIK.IKSolverVR; +using static KK_VR.Grasp.GraspController; +using BodyPart = KK_VR.Grasp.BodyPart; + +namespace KK_VR.Handlers +{ + internal class BodyPartGuide : PartGuide + { + private Vector3 _translateExOffset; + private bool _translateEx; + private KK.RootMotion.FinalIK.IKEffector _effector; + private bool _maintainRot; + private BodyPart _bodyPart; + private Quaternion _prevRotOffset; + + + /// + /// Return relative position weight over period of 1 second. + /// + private void TranslateOnFollow() + { + // Way too tricky to do it sneaky. Barely noticeable as it is, not worth it. + _effector.maintainRelativePositionWeight = Mathf.Clamp01(_effector.maintainRelativePositionWeight + Time.deltaTime); + if (_effector.maintainRelativePositionWeight == 1f) + { + _translateEx = false; + } + } + /////////////////////////////////////////// + /// /// + /// ANCHOR = IKObject /// + /// /// + /// THIS.TRANSOFRM = EFFECTOR.BONE /// + /// THIS.TRANSOFRM != ANCHOR /// + /// /// + /////////////////////////////////////////// + + internal override void Init(BodyPart bodyPart) + { + base.Init(bodyPart); + _bodyPart = bodyPart; + _effector = bodyPart.effector; + } + internal override void Follow(Transform target, HandHolder hand) + { + _hand = hand; + _attach = false; + _follow = true; + _target = target; + if (!_anchor.gameObject.activeSelf) + { + //SetBodyPartCollidersToTrigger(true); + _anchor.gameObject.SetActive(true); + //transform.parent = _objAnim; + } + //_bodyPart.effector.rotationWeight = 1f; + //_bodyPart.effector.target = _bodyPart.anchor; + if (_bodyPart.chain != null) + { + _bodyPart.chain.bendConstraint.weight = KoikatuInterpreter.Settings.IKDefaultBendConstraint; + } + if (_bodyPart.effector.maintainRelativePositionWeight != 1f && KoikatuInterpreter.Settings.MaintainLimbOrientation) + { + _translateEx = true; + //_translateOffset = _bodyPart.afterIK.position - transform.position; + } + else + { + // Turning it off just in case. + _translateEx = false; + } + _offsetRot = Quaternion.Inverse(target.rotation) * _anchor.rotation; + _offsetPos = target.InverseTransformPoint(_anchor.position); + + _bodyPart.state = State.Grasped; + + if (hand != null) + { + if (KoikatuInterpreter.Settings.ShowGuideObjects) _bodyPart.visual.Show(); + Tracker.SetBlacklistDic(hand.Grasp.GetBlacklistDic); + ClearBlacks(); + _bodyPart.visual.SetColor(IsBusy); + _wasBusy = false; + } + } + + internal override void Stay() + { + _hand = null; + _follow = false; + _attach = false; + //SetBodyPartCollidersToTrigger(false); + _bodyPart.visual.Hide(); + _bodyPart.state = State.Active; + ClearTracker(); + } + + internal void StartRelativeRotation() + { + if (!_maintainRot) + { + _maintainRot = true; + _prevRotOffset = _offsetRot; + _offsetRot = Quaternion.Inverse(_bodyPart.chain.nodes[1].transform.rotation) * _anchor.rotation; + //_offsetRot = Quaternion.Inverse(Quaternion.LookRotation(_bodyPart.afterIK.position - _bodyPart.chain.nodes[1].transform.position)) * _anchor.rotation; + } + } + + internal void StopRelativeRotation() + { + _maintainRot = false; + _offsetRot = _prevRotOffset; + _anchor.rotation = _bodyPart.beforeIK.rotation * _offsetRot; + } + + private void TranslateOnAttach() + { + // By default we want to have "effector.maintainRelativePositionWeight" in full weight, but on attached we want it at zero, + // but doing so changes calculations of IK Solver quite a bit, thus we compensate over the course of 1 second. + // We look at initial delta vector between OffsetEffector (this gameObject) and actual bone that we see rendered after IK, + // then each frame we adjust our position based on change of this delta. As result there is only a miniscule offset(at least there should be, it's impossible to notice) + // between desired position with full 'maintainRelativePositionWeight' and actual without 'maintainRelativePositionWeight'. + + _effector.maintainRelativePositionWeight = Mathf.Clamp01(_effector.maintainRelativePositionWeight - Time.deltaTime); + _anchor.position = _target.TransformPoint(_offsetPos) - (_translateExOffset - (_anchor.position - _bodyPart.afterIK.position)); + if (_effector.maintainRelativePositionWeight == 0f) + { + _translateEx = false; + _offsetRot = Quaternion.Inverse(_target.rotation) * _anchor.rotation; + _offsetPos = _target.InverseTransformPoint(_anchor.position); + } + } + internal override void Attach(Transform target) + { + if (target.name.StartsWith("hand", StringComparison.Ordinal)) + { + _hand.OnBecomingParent(); + } + _hand = null; + _translateEx = true; + _attach = true; + _target = target; + _bodyPart.visual.Hide(); + _bodyPart.state = State.Attached; + + _translateExOffset = _anchor.position - _bodyPart.afterIK.position; + + _offsetRot = Quaternion.Inverse(_target.rotation) * _anchor.rotation; + _offsetPos = _target.InverseTransformPoint(_anchor.position); + } + + internal void OnSyncStart() + { + _bodyPart.state = State.Synced; + _translate = new Translate(_anchor, () => _effector.maintainRelativePositionWeight -= Time.deltaTime, () => _translate = null); + } + + private void Update() + { + if (_follow) + { + if (_translateEx) + { + if (_follow) + { + if (KoikatuInterpreter.Settings.MaintainLimbOrientation) + { + TranslateOnFollow(); + } + _anchor.SetPositionAndRotation( + _target.TransformPoint(_offsetPos), + _target.rotation * _offsetRot + ); + } + else + { + TranslateOnAttach(); + } + } + else + { + _anchor.SetPositionAndRotation( + _target.TransformPoint(_offsetPos), + _target.rotation * _offsetRot + ); + } + } + else + { + if (_maintainRot) + { + _anchor.rotation = _bodyPart.chain.nodes[1].transform.rotation * _offsetRot; + } + else if (_translate != null) + { + _translate.DoStep(); + } + } + } + private void LateUpdate() + { + _effector.positionOffset += _anchor.position - _effector.bone.position; + } + + //private void FixedUpdate() + //{ + // //if (_unwind) + // //{ + // // _timer = Mathf.Clamp01(_timer - Time.deltaTime); + // // _rigidBody.velocity *= _timer; + // // if (_timer == 0f) + // // { + // // _unwind = false; + // // } + // //} + // if (_follow) + // { + // _rigidBody.MovePosition(_target.TransformPoint(_offsetPos)); + // _rigidBody.MoveRotation(_target.rotation * _offsetRot); + + // if (_translate) + // { + // TranslateOnFollow(); + // } + // } + //} + + } +} + diff --git a/Shared/Handlers/ForGrasp/HeadPartGuide.cs b/Shared/Handlers/ForGrasp/HeadPartGuide.cs new file mode 100644 index 0000000..f51cb28 --- /dev/null +++ b/Shared/Handlers/ForGrasp/HeadPartGuide.cs @@ -0,0 +1,104 @@ +using KK_VR.Handlers; +using KK_VR.Trackers; +using RootMotion.FinalIK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using static KK_VR.Grasp.GraspController; +using KK_VR.Holders; +using KK_VR.Grasp; +using KK_VR.Interpreters; + +namespace KK_VR.Handlers +{ + internal class HeadPartGuide : PartGuide + { + + protected override BodyPart BodyPart + { + get => _bodyPart; + set => _bodyPart = value is BodyPartHead head ? head : null; + } + private BodyPartHead _bodyPart; + internal override void Follow(Transform target, HandHolder hand) + { + if (KoikatuInterpreter.Settings.IKHeadEffector == Settings.KoikatuSettings.HeadEffector.Disabled) + { + return; + } + else if (_bodyPart.headEffector.enabled == false) + { + _bodyPart.headEffector.enabled = true; + } + _hand = hand; + _attach = false; + _follow = true; + _target = target; + + //if (KoikatuInterpreter.Settings.ShowGuideObjects) _bodyPart.visual.Show(); + _bodyPart.state = State.Grasped; + + _offsetRot = Quaternion.Inverse(target.rotation) * _anchor.rotation; + _offsetPos = target.InverseTransformPoint(_anchor.position); + + Tracker.SetBlacklistDic(hand.Grasp.GetBlacklistDic); + ClearBlacks(); + _bodyPart.visual.SetColor(IsBusy); + _wasBusy = false; + } + internal override void Stay() + { + _hand = null; + _follow = false; + _attach = false; + //_anchor.parent = _bodyPart.beforeIK; + //SetBodyPartCollidersToTrigger(false); + } + + //internal override void Sleep(bool instant) + //{ + // _hand = null; + // _follow = false; + // _attach = false; + // //gameObject.SetActive(false); + // //transform.localScale = _origScale; + // SetBodyPartCollidersToTrigger(false); + //} + + internal override void Attach(Transform target) + { + _hand = null; + _attach = true; + _target = target; + + _offsetRot = Quaternion.Inverse(_target.rotation) * _anchor.rotation; + _offsetPos = _target.InverseTransformPoint(_anchor.position); + //transform.parent = _objAnim; + } + protected override void Disable() + { + base.Disable(); + if (KoikatuInterpreter.Settings.IKHeadEffector != Settings.KoikatuSettings.HeadEffector.Always) + { + _bodyPart.headEffector.enabled = false; + } + } + + private void Update() + { + if (_follow) + { + _anchor.SetPositionAndRotation( + _target.TransformPoint(_offsetPos), + _target.rotation * _offsetRot + ); + } + else + { + _translate?.DoStep(); + } + } + } +} diff --git a/Shared/Handlers/ForGrasp/PartGuide.cs b/Shared/Handlers/ForGrasp/PartGuide.cs new file mode 100644 index 0000000..d4fa70d --- /dev/null +++ b/Shared/Handlers/ForGrasp/PartGuide.cs @@ -0,0 +1,200 @@ +using IllusionUtility.GetUtility; +using KK_VR.Holders; +using KK_VR.Trackers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Helpers; +using static ActionGame.VisibleController; +using BodyPart = KK_VR.Grasp.BodyPart; + +namespace KK_VR.Handlers +{ + // Component for the actual character bone (bodyPart.afterIK). Can be repositioned at a whim. + // Controls collider tracker and movement of IK object. + // When IK object is being set and there are appropriate colliders within range of this component, + // IK object may be attached to intersecting collider. Due to nature of IK setup, + // IK object is always somewhere not where you'd expect it to be, + // thus we manage it through this component attached to the bone that would better represent particular IK point visually. + /// + /// Component responsible for orientation of IK driven body part. + /// + internal abstract class PartGuide : Handler + { + protected class Translate + { + internal Translate(Transform anchor, Action onStep, Action onFinish) + { + _anchor = anchor; + _offsetPos = anchor.localPosition; + _offsetRot = anchor.localRotation; + _onStep = onStep; + _onFinish = onFinish; + } + private float _lerp; + private readonly Transform _anchor; + private readonly Quaternion _offsetRot; + private readonly Vector3 _offsetPos; + private readonly Action _onStep; + private readonly Action _onFinish; + + internal void DoStep() + { + _lerp += Time.deltaTime; + var step = Mathf.SmoothStep(0f, 1f, _lerp); + _anchor.localPosition = Vector3.Lerp(_offsetPos, Vector3.zero, step); + _anchor.localRotation = Quaternion.Lerp(_offsetRot, Quaternion.identity, step); + _onStep?.Invoke(); + if (_lerp >= 1f) + { + _onFinish?.Invoke(); + } + } + } + + protected HandHolder _hand; + protected virtual BodyPart BodyPart { get; set; } + + // Transform that guides IK, separate gameObject. + protected Transform _anchor; + + protected Transform _target; + protected Rigidbody _rigidBody; + + protected Vector3 _offsetPos; + protected Quaternion _offsetRot; + + protected Translate _translate; + + protected bool _wasBusy; + + // If are not in default state. + protected bool _follow; + // If we follow particular transform. + protected bool _attach; + + protected virtual void Awake() + { + _rigidBody = gameObject.AddComponent(); + _rigidBody.isKinematic = true; + _rigidBody.freezeRotation = true; + + _rigidBody.useGravity = false; + + // Default primitive's collider-trigger. + var colliderTrigger = gameObject.GetComponent(); + colliderTrigger.isTrigger = true; + + // RigidBody's slave. + //var sphere = gameObject.AddComponent(); + //sphere.isTrigger = false; + //sphere.radius = Mathf.Round(1000f * (colliderTrigger.radius * 0.7f)) * 0.001f; + Tracker = new Tracker(); + } + protected virtual void OnEnable() + { + _wasBusy = false; + } + + internal virtual void Init(BodyPart bodyPart) + { + BodyPart = bodyPart; + _anchor = bodyPart.anchor; + } + /// + /// Sets the transform (controller?) as the parent of the part for further manipulation. + /// + internal abstract void Follow(Transform target, HandHolder hand); + /// + /// Sets part's parent to itself before IK, to follow it with offset. + /// + internal abstract void Stay(); + /// + /// Returns part to the default state. + /// + internal void Sleep(bool instant) + { + BodyPart.state = Grasp.GraspController.State.Active; + _hand = null; + _follow = false; + _attach = false; + if (instant) + { + _anchor.localPosition = Vector3.zero; + _anchor.localRotation = Quaternion.identity; + Disable(); + } + else + { + BodyPart.state = Grasp.GraspController.State.Translation; + _translate = new(_anchor, null, Disable); + } + BodyPart.visual.Hide(); + ClearTracker(); + } + protected virtual void Disable() + { + _translate = null; + if (BodyPart.chain != null) + { + BodyPart.chain.bendConstraint.weight = 1f; + } + BodyPart.state = Grasp.GraspController.State.Default; + _anchor.gameObject.SetActive(BodyPart.GetDefaultState()); + } + + /// + /// Sets the part to follow motion of particular transform. + /// + internal abstract void Attach(Transform target); + + //internal void SetBodyPartCollidersToTrigger(bool active) + //{ + // // To let rigidBody run free. Currently on hold, we use 'isKinematic' for a moment. + // foreach (var kv in _bodyPart.colliders) + // { + // kv.Key.isTrigger = active || kv.Value; + // //VRPlugin.Logger.LogDebug($"{_bodyPart.name} set {kv.Key.name}.Trigger = {kv.Key.isTrigger}[{kv.Value}]"); + // } + //} + + + + protected override void OnTriggerEnter(Collider other) + { + if (Tracker.AddCollider(other)) + { + if (!_wasBusy) + { + _wasBusy = true; + if (_hand != null) + { + _hand.Controller.StartRumble(new RumbleImpulse(500)); + BodyPart.visual.SetColor(true); + } + } + } + } + + protected override void OnTriggerExit(Collider other) + { + if (Tracker.RemoveCollider(other)) + { + if (!IsBusy) + { + _wasBusy = false; + //_unwind = true; + //_timer = 1f; + if (_hand != null) + { + BodyPart.visual.SetColor(false); + } + } + } + } + + } +} + diff --git a/Shared/Handlers/ForScenes/ActionSceneHandler.cs b/Shared/Handlers/ForScenes/ActionSceneHandler.cs new file mode 100644 index 0000000..d64c06a --- /dev/null +++ b/Shared/Handlers/ForScenes/ActionSceneHandler.cs @@ -0,0 +1,26 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using VRGIN.Controls; + +//namespace KK_VR.Handlers +//{ +// // No real use for this one unless we'll get VRIK with custom animController. +// // LF animation retargeting. +// internal class ActionSceneHandler : MonoBehaviour +// { +// private Controller _controller; +// private void Awake() +// { +// _controller = GetComponent(); +// } +// internal float GetStickRotation() +// { +// var xy = _controller.Input.GetAxis(); +// return Mathf.Atan2(xy.y, xy.x) * Mathf.Rad2Deg + 90f; +// } + +// } +//} diff --git a/Shared/Handlers/ForScenes/HSceneHandler.cs b/Shared/Handlers/ForScenes/HSceneHandler.cs new file mode 100644 index 0000000..da4b4d3 --- /dev/null +++ b/Shared/Handlers/ForScenes/HSceneHandler.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using VRGIN.Core; +using VRGIN.Controls; +using VRGIN.Helpers; +using HarmonyLib; +using UnityEngine; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Fixes; +using KK_VR.Features; +using KK_VR.Controls; +using RootMotion.FinalIK; +using static HandCtrl; +using KK_VR.Caress; +using ADV.Commands.Game; +using KK_VR.Trackers; +using ADV.Commands.Base; +using static KKAPI.MainGame.TalkSceneUtils; +using KK_VR.Grasp; +using KK_VR.Handlers.Helpers; + +namespace KK_VR.Handlers +{ + class HSceneHandler : ItemHandler + { + private bool _injectLMB; + private IKCaress _ikCaress; + + protected override void OnDisable() + { + base.OnDisable(); + TriggerRelease(); + } + + internal void StartMovingAibuItem(AibuColliderKind touch) + { + _hand.Shackle(touch == AibuColliderKind.kokan || touch == AibuColliderKind.anal ? 6 : 10); + _ikCaress = GraspHelper.Instance.StartIKCaress(touch, HSceneInterpreter.lstFemale[0], _hand); + } + internal void StopMovingAibuItem() + { + if (_ikCaress != null) + { + _ikCaress.End(); + _hand.Unshackle(); + _hand.SetItemRenderer(true); + } + } + + protected bool AibuKindAllowed(AibuColliderKind kind, ChaControl chara) + { + var heroine = HSceneInterpreter.hFlag.lstHeroine + .Where(h => h.chaCtrl == chara) + .FirstOrDefault(); + return kind switch + { + AibuColliderKind.mouth => heroine.isGirlfriend || heroine.isKiss || heroine.denial.kiss, + AibuColliderKind.anal => heroine.hAreaExps[3] > 0f || heroine.denial.anal, + _ => true + }; + } + + public bool DoUndress(bool decrease) + { + if (decrease && HSceneInterpreter.handCtrl.IsItemTouch() && IsAibuItemPresent(out var touch)) + { + HSceneInterpreter.ShowAibuHand(touch, true); + HSceneInterpreter.handCtrl.DetachItemByUseAreaItem(touch - AibuColliderKind.muneL); + HSceneInterpreter.hFlag.click = HFlag.ClickKind.de_muneL + (int)touch - 2; + } + else if (Interactors.Undresser.Undress(_tracker.colliderInfo.behavior.part, _tracker.colliderInfo.chara, decrease)) + { + //HandNoises.PlaySfx(_index, 1f, HandNoises.Sfx.Undress, HandNoises.Surface.Cloth); + } + else + { + return false; + } + _controller.StartRumble(new RumbleImpulse(1000)); + return true; + } + /// + /// Does tracker has lock on attached aibu item? + /// + /// + /// + internal bool IsAibuItemPresent(out AibuColliderKind touch) + { + touch = _tracker.colliderInfo.behavior.touch; + if (touch > AibuColliderKind.mouth && touch < AibuColliderKind.reac_head) + { + return HSceneInterpreter.handCtrl.useAreaItems[touch - AibuColliderKind.muneL] != null; + } + return false; + } + internal bool TriggerPress() + { + var touch = _tracker.colliderInfo.behavior.touch; + var chara = _tracker.colliderInfo.chara; + //VRPlugin.Logger.LogDebug($"HSceneHandler:[{touch}][{HSceneInterpreter.handCtrl.selectKindTouch}]"); + if (touch > AibuColliderKind.mouth + && touch < AibuColliderKind.reac_head + && chara == HSceneInterpreter.lstFemale[0]) + { + if (IntegrationSensibleH.active && !MouthGuide.Instance.IsActive && HSceneInterpreter.handCtrl.GetUseAreaItemActive() != -1) + { + // If VRMouth isn't active but automatic caress is going. Disable it. + IntegrationSensibleH.OnKissEnd(); + } + else + { + HSceneInterpreter.SetSelectKindTouch(touch); + HandCtrlHooks.InjectMouseButtonDown(0); + _injectLMB = true; + } + } + else + { + HSceneInterpreter.HitReactionPlay(_tracker.colliderInfo.behavior.react, chara, voiceWait: false); + } + return true; + } + internal void TriggerRelease() + { + if (_injectLMB) + { + HSceneInterpreter.SetSelectKindTouch(AibuColliderKind.none); + HandCtrlHooks.InjectMouseButtonUp(0); + _injectLMB = false; + } + } + protected override void DoReaction(float velocity) + { + //VRPlugin.Logger.LogDebug($"DoReaction:{_tracker.colliderInfo.behavior.react}:{_tracker.colliderInfo.behavior.touch}:{_tracker.reactionType}:{velocity}"); + if (_settings.AutomaticTouching > KoikatuSettings.SceneType.TalkScene) + { + if (velocity > 1.5f || (_tracker.reactionType == Tracker.ReactionType.HitReaction && !IsAibuItemPresent(out _))) + { + if (_settings.TouchReaction != 0f + && HSceneInterpreter.mode == HFlag.EMode.aibu + && GraspHelper.Instance != null + && !GraspHelper.Instance.IsGraspActive(_tracker.colliderInfo.chara) + && UnityEngine.Random.value < _settings.TouchReaction) + { + GraspHelper.Instance.TouchReaction(_tracker.colliderInfo.chara, _hand.Anchor.position, _tracker.colliderInfo.behavior.part); + } + else + { + HSceneInterpreter.HitReactionPlay(_tracker.colliderInfo.behavior.react, _tracker.colliderInfo.chara, voiceWait: true); + } + } + else if (_tracker.reactionType == Tracker.ReactionType.Short) + { + Features.LoadVoice.PlayVoice(Features.LoadVoice.VoiceType.Short, _tracker.colliderInfo.chara, voiceWait: true); + } + else //if (_tracker.reactionType == ControllerTracker.ReactionType.Laugh) + { + Features.LoadVoice.PlayVoice(Features.LoadVoice.VoiceType.Laugh, _tracker.colliderInfo.chara, voiceWait: true); + } + _controller.StartRumble(new RumbleImpulse(1000)); + } + } + } +} \ No newline at end of file diff --git a/Shared/Handlers/ForScenes/TalkSceneHandler.cs b/Shared/Handlers/ForScenes/TalkSceneHandler.cs new file mode 100644 index 0000000..1a287e3 --- /dev/null +++ b/Shared/Handlers/ForScenes/TalkSceneHandler.cs @@ -0,0 +1,110 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using VRGIN.Core; +using VRGIN.Controls; +using VRGIN.Helpers; +using UnityEngine; +using HarmonyLib; +using KK_VR.Fixes; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Features; +using KK_VR.Controls; +using static HandCtrl; +using KK_VR.Caress; +using KK_VR.Interactors; +using KK_VR.Trackers; +using static KKAPI.MainGame.TalkSceneUtils; +using KK_VR.Grasp; + +namespace KK_VR.Handlers +{ + class TalkSceneHandler : ItemHandler + { + internal bool DoUndress(bool decrease, out ChaControl chara) + { + if (Undresser.Undress(_tracker.colliderInfo.behavior.part, chara = _tracker.colliderInfo.chara, decrease)) + { + //HandNoises.PlaySfx(_index, 1f, HandNoises.Sfx.Undress, HandNoises.Surface.Cloth); + _controller.StartRumble(new RumbleImpulse(1000)); + return true; + } + return false; + } + + protected override void DoReaction(float velocity) + { + if (_settings.AutomaticTouching == KoikatuSettings.SceneType.Both + || _settings.AutomaticTouching == KoikatuSettings.SceneType.TalkScene) + { + var chara = _tracker.colliderInfo.chara; + var touch = _tracker.colliderInfo.behavior.touch; + if (TalkSceneInterpreter.talkScene != null + && touch != AibuColliderKind.none + && chara == TalkSceneInterpreter.talkScene.targetHeroine.chaCtrl + && !CrossFader.AdvHooks.Reaction + // Add familiarity here too ? prob + && (velocity > 1f || UnityEngine.Random.value < 0.3f) + && (GraspHelper.Instance == null || !GraspHelper.Instance.IsGraspActive(chara))) + { + TalkSceneInterpreter.talkScene.TouchFunc(TouchReaction(touch), Vector3.zero); + } + else if (velocity > 1f || _tracker.reactionType == Tracker.ReactionType.HitReaction) + { + if (GraspHelper.Instance != null && !GraspHelper.Instance.IsGraspActive(chara) && UnityEngine.Random.value < _settings.TouchReaction) + { + GraspHelper.Instance.TouchReaction(chara, _hand.Anchor.position, _tracker.colliderInfo.behavior.part); + } + else + { + TalkSceneInterpreter.HitReactionPlay(_tracker.colliderInfo.behavior.react, chara); + } + } + else if (_tracker.reactionType == Tracker.ReactionType.Short) + { + Features.LoadVoice.PlayVoice(Features.LoadVoice.VoiceType.Short, chara, voiceWait: UnityEngine.Random.value < 0.5f); + } + else if (_tracker.reactionType == Tracker.ReactionType.Laugh) + { + Features.LoadVoice.PlayVoice(Features.LoadVoice.VoiceType.Laugh, chara, voiceWait: UnityEngine.Random.value < 0.5f); + } + _controller.StartRumble(new RumbleImpulse(1000)); + } + } + public bool TriggerPress() + { + var chara = _tracker.colliderInfo.chara; + var touch = _tracker.colliderInfo.behavior.touch; + if (TalkSceneInterpreter.talkScene != null + && touch != AibuColliderKind.none + && chara == TalkSceneInterpreter.talkScene.targetHeroine.chaCtrl + && !CrossFader.AdvHooks.Reaction) + { + TalkSceneInterpreter.talkScene.TouchFunc(TouchReaction(touch), Vector3.zero); + return true; + } + return false; + } + public void TriggerRelease() + { + + } + private string TouchReaction(AibuColliderKind colliderKind) + { + return colliderKind switch + { + AibuColliderKind.mouth => "Cheek", + AibuColliderKind.muneL => "MuneL", + AibuColliderKind.muneR => "MuneR", + AibuColliderKind.reac_head => "Head", + AibuColliderKind.reac_armL => "HandL", + AibuColliderKind.reac_armR => "HandR", + _ => null + + }; + } + } + +} diff --git a/Shared/Handlers/Handler.cs b/Shared/Handlers/Handler.cs new file mode 100644 index 0000000..ddc42ed --- /dev/null +++ b/Shared/Handlers/Handler.cs @@ -0,0 +1,83 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using VRGIN.Core; +using VRGIN.Controls; +using VRGIN.Helpers; +using HarmonyLib; +using UnityEngine; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Fixes; +using KK_VR.Features; +using KK_VR.Controls; +using RootMotion.FinalIK; +using static HandCtrl; +using KK_VR.Caress; +using ADV.Commands.Game; +using KK_VR.Trackers; + +namespace KK_VR.Handlers +{ + /// + /// Component responsible for the management of functions associated with colliders + /// + internal class Handler : MonoBehaviour + { + protected virtual Tracker Tracker { get; set; } + /// + /// True if something is being tracked. Track for recently blacklisted items continues, but new ones don't get added. + /// + internal virtual bool IsBusy => Tracker.IsBusy; + /// + /// Can be true only after 'UpdateNoBlacks()' if every item in track is blacklisted. + /// + internal bool InBlack => Tracker.colliderInfo == null; + internal Transform GetTrackTransform => Tracker.colliderInfo.collider.transform; + internal ChaControl GetChara => Tracker.colliderInfo.chara; + + + protected virtual void OnDisable() + { + Tracker.ClearTracker(); + } + + protected virtual void OnTriggerEnter(Collider other) + { + Tracker.AddCollider(other); + } + + protected virtual void OnTriggerExit(Collider other) + { + Tracker.RemoveCollider(other); + } + internal void ClearBlacks() + { + Tracker.RemoveBlacks(); + } + /// + /// Null-ghost panacea. No clue what they are. + /// + internal void ClearTracker() + { + Tracker.ClearTracker(); + } + internal void UpdateTrackerNoBlacks() + { + Tracker.SetSuggestedInfoNoBlacks(); + } + /// + /// Pick the most interesting bodyPart from the current track. + /// + internal void UpdateTracker(ChaControl tryToAvoid = null) + { + Tracker.SetSuggestedInfo(tryToAvoid); +#if DEBUG + Tracker.DebugShowActive(); +#endif + } + + } +} \ No newline at end of file diff --git a/Shared/Handlers/Helpers/AibuItemMover.cs b/Shared/Handlers/Helpers/AibuItemMover.cs new file mode 100644 index 0000000..3d7b7b5 --- /dev/null +++ b/Shared/Handlers/Helpers/AibuItemMover.cs @@ -0,0 +1,40 @@ +using KK_VR.Grasp; +using KK_VR.Interpreters; +using KK_VR.Trackers; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using static HandCtrl; +using UnityEngine; + +namespace KK_VR.Handlers.Helpers +{ + /// + /// Feeds ~controller position to the hFlag.xy field + /// + internal class AibuItemMover + { + internal AibuItemMover(AibuColliderKind touch, Transform anchor) + { + _itemId = (int)touch - 2; + _anchor = anchor; + _lastPos = anchor.position; + _item = HSceneInterpreter.handCtrl.useAreaItems[_itemId].obj.transform; + } + private readonly int _itemId; + private readonly Transform _item; + private readonly Transform _anchor; + private Vector3 _lastPos; + + + internal void Move() + { + var vec = (Vector2)_item.InverseTransformVector(_lastPos - _anchor.position); + vec.y = 0f - vec.y; + HSceneInterpreter.hFlag.xy[_itemId] += vec * 10f; + _lastPos = _anchor.position; + } + + } +} diff --git a/Shared/Handlers/Helpers/ItemLag.cs b/Shared/Handlers/Helpers/ItemLag.cs new file mode 100644 index 0000000..7fdf9d6 --- /dev/null +++ b/Shared/Handlers/Helpers/ItemLag.cs @@ -0,0 +1,100 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Core; + +namespace KK_VR.Handlers +{ + /// + /// Stabilizes orientation of a model representing the controller + /// + internal class ItemLag + { + private readonly Transform _transform; + private readonly Quaternion[] _prevRotations; + private readonly Vector3[] _prevPositions; + + //private readonly int _frameFloor; + //private readonly int _frameCeiling; + private readonly float[] _frameCoefs; + + private int _frameIndex; + private readonly int _frameCurAmount; + private readonly float _frameCurAmountCoef; + + internal ItemLag(Transform transform, int frameAvg) + { + _transform = transform; + _frameCurAmount = frameAvg; + _frameCurAmountCoef = 1f / frameAvg; + //_frameFloor = frameAvg - 5; + //_frameCeiling = frameAvg + 5; + _prevRotations = new Quaternion[frameAvg]; + _prevPositions = new Vector3[frameAvg]; + _frameCoefs = new float[frameAvg]; + + // Coefficients can be customized to change rotation follow type, even non-linear should look good. + for (var i = 0; i < frameAvg; i++) + { + _frameCoefs[i] = 1f / (i + 2f); + } + var pos = _transform.position; + var rot = _transform.rotation; + for (var i = 0; i < _frameCurAmount; i++) + { + _prevRotations[i] = rot; + _prevPositions[i] = pos; + } + } + + internal void SetPositionAndRotation(Vector3 position, Quaternion rotation) + { + _prevRotations[_frameIndex] = rotation; + _prevPositions[_frameIndex] = position; + //} + _frameIndex++; + if (_frameIndex == _frameCurAmount) _frameIndex = 0; + UpdatePositionAndRotation(); + } + + private void UpdatePositionAndRotation() + { + // The most stale frame is grabbed on init part. + var count = _frameCurAmount - 1; + + // The most stale rotation. Doesn't get touched in the loop. + var avgRot = _prevRotations[_frameIndex]; + // Position that won't get touched in the loop. Don't care about the order, we use average. + var avgPos = _prevPositions[count]; + + var j = _frameIndex; + for (var i = 0; i < count; i++) + { + if (++j == _frameCurAmount) j = 0; + avgRot = Quaternion.Lerp(avgRot, _prevRotations[j], _frameCoefs[i]); + avgPos += _prevPositions[i]; + } + + avgPos *= _frameCurAmountCoef; + _transform.SetPositionAndRotation(avgPos, avgRot); + } + internal void SetPosition(Vector3 position) + { + _prevPositions[_frameIndex] = position; + _frameIndex++; + if (_frameIndex == _frameCurAmount) _frameIndex = 0; + UpdatePosition(); + } + private void UpdatePosition() + { + var avgPos = _prevPositions[0]; + for (var i = 1; i < _frameCurAmount; i++) + { + avgPos += _prevPositions[i]; + } + _transform.position = avgPos * _frameCurAmountCoef; + } + } +} diff --git a/Shared/Handlers/Helpers/KissHelper.cs b/Shared/Handlers/Helpers/KissHelper.cs new file mode 100644 index 0000000..9ec133b --- /dev/null +++ b/Shared/Handlers/Helpers/KissHelper.cs @@ -0,0 +1,58 @@ +using KK_VR.Interpreters; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Core; +using Random = UnityEngine.Random; + +namespace KK_VR.Handlers +{ + /// + /// Starts proactive kiss + /// + internal class KissHelper + { + private float _kissAttemptTimestamp; + private float _kissAttemptChance; + private Transform _eyes; + private Transform _shoulders; + + internal KissHelper(Transform eyes, Transform shoulders) + { + _eyes = eyes; + _shoulders = shoulders; + var heroine = HSceneInterpreter.hFlag.lstHeroine[0]; + _kissAttemptChance = 0.1f + ((int)heroine.HExperience - 1) * 0.15f + (heroine.weakPoint == 0 ? 0.1f : 0f); + } + internal void AttemptProactiveKiss() + { + if (_kissAttemptTimestamp < Time.time) + { + //VRPlugin.Logger.LogDebug($"AttemptProactiveKiss:chance - {_kissAttemptChance}"); + var headPos = VR.Camera.Head.position; + if (Random.value < _kissAttemptChance + && !HSceneInterpreter.IsVoiceActive + && HSceneInterpreter.IsIdleOutside(HSceneInterpreter.hFlag.nowAnimStateName) + && Mathf.Abs(Mathf.DeltaAngle(_shoulders.eulerAngles.y, _eyes.eulerAngles.y)) < 30f + && Vector3.Distance(_eyes.position, headPos) < 0.55f + && Vector3.Angle(headPos - _eyes.position, _eyes.forward) < 30f) + { + //_kissAttempt = true; + //_kissDistance = 0.4f; + HSceneInterpreter.LeanToKiss(); + SetAttemptTimestamp(2f + Random.value * 2f); + } + else + { + SetAttemptTimestamp(); + } + } + } + private void SetAttemptTimestamp(float modifier = 1f) + { + _kissAttemptTimestamp = Time.time + (20f * modifier); + } + } +} diff --git a/Shared/Handlers/Helpers/SmoothDamp.cs b/Shared/Handlers/Helpers/SmoothDamp.cs new file mode 100644 index 0000000..949b0c2 --- /dev/null +++ b/Shared/Handlers/Helpers/SmoothDamp.cs @@ -0,0 +1,34 @@ +using ADV.Commands.Base; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace KK_VR.Handlers +{ + /// + /// Wrapped Mathf.SmoothDamp for comfy use + /// + internal class SmoothDamp + { + internal SmoothDamp(float smoothTime = 1f, float target = 1f) + { + _smoothTime = smoothTime; + _target = target; + } + private float _current; + private float _currentVelocity; + private readonly float _target; + private readonly float _smoothTime; + internal float Current => _current; + internal float Increase() + { + return _current = Mathf.SmoothDamp(_current, _target, ref _currentVelocity, _smoothTime); + } + internal float Decrease() + { + return _current = Mathf.SmoothDamp(_current, 0f, ref _currentVelocity, _smoothTime); + } + } +} diff --git a/Shared/Handlers/ItemHandler.cs b/Shared/Handlers/ItemHandler.cs new file mode 100644 index 0000000..5857047 --- /dev/null +++ b/Shared/Handlers/ItemHandler.cs @@ -0,0 +1,175 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; +using VRGIN.Core; +using VRGIN.Controls; +using VRGIN.Helpers; +using HarmonyLib; +using UnityEngine; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Fixes; +using KK_VR.Features; +using KK_VR.Controls; +using RootMotion.FinalIK; +using static HandCtrl; +using KK_VR.Caress; +using ADV.Commands.Game; +using KK_VR.Trackers; +using System.Runtime.Remoting.Messaging; +using KK_VR.Holders; + +namespace KK_VR.Handlers +{ + /// + /// Implementation for a controller based component + /// + class ItemHandler : Handler + { + protected ControllerTracker _tracker; + protected override Tracker Tracker + { + get => _tracker; + set => _tracker = value is ControllerTracker t ? t : null; + } + protected HandHolder _hand; + protected KoikatuSettings _settings; + protected Controller _controller; + //protected ModelHandler.ItemType _item; + private bool _unwind; + private float _timer; + private Rigidbody _rigidBody; + internal override bool IsBusy => _tracker.colliderInfo != null && _tracker.colliderInfo.chara != null; + + // Default velocity is in local space of a controller or camera origin. +#if KK + protected Vector3 GetVelocity => _controller.Input.velocity; +#else + protected Vector3 GetVelocity => _controller.Tracking.GetVelocity(); +#endif + internal void Init(HandHolder hand) + { + _rigidBody = GetComponent(); + _hand = hand; + _tracker = new ControllerTracker(); + _tracker.SetBlacklistDic(_hand.Grasp.GetBlacklistDic); + + _settings = VR.Context.Settings as KoikatuSettings; + _controller = _hand.Controller; + } + protected virtual void Update() + { + if (_unwind) + { + _timer = Mathf.Clamp01(_timer - Time.deltaTime); + _rigidBody.velocity *= _timer; + if (_timer == 0f) + { + _unwind = false; + } + } + } + + protected override void OnTriggerEnter(Collider other) + { + if (_tracker.AddCollider(other)) + { + if (_tracker.colliderInfo.behavior.touch > AibuColliderKind.mouth + && _tracker.colliderInfo.behavior.touch < AibuColliderKind.reac_head) + { + _hand.SetCollisionState(false); + } + var velocity = GetVelocity.sqrMagnitude; + if (velocity > 1.5f || _tracker.reactionType != Tracker.ReactionType.None) + { + DoReaction(velocity); + } + if (_tracker.firstTrack) + { + DoStartSfx(velocity); + } + else if (!_hand.Noise.IsPlaying) + { + DoSfx(velocity); + } + } + } + + protected void DoStartSfx(float velocity) + { + var fast = velocity > 1.5f; + _hand.Noise.PlaySfx( + fast ? 0.5f + velocity * 0.2f : 1f, + fast ? HandNoise.Sfx.Slap : HandNoise.Sfx.Tap, + GetSurfaceType(_tracker.colliderInfo.behavior.part), + GetIntensityType(_tracker.colliderInfo.behavior.part), + overwrite: true + ); + } + + protected void DoSfx(float velocity) + { + _tracker.SetSuggestedInfo(); + _hand.Noise.PlaySfx( + velocity > 1.5f ? 0.5f + velocity * 0.2f : 1f, + velocity > 0.5f ? HandNoise.Sfx.Tap : HandNoise.Sfx.Traverse, + GetSurfaceType(_tracker.colliderInfo.behavior.part), + GetIntensityType(_tracker.colliderInfo.behavior.part), + overwrite: false + ); + } + + protected HandNoise.Surface GetSurfaceType(Tracker.Body part) + { + return part switch + { + Tracker.Body.Head => HandNoise.Surface.Hair, + _ => Interactors.Undresser.IsBodyPartClothed(_tracker.colliderInfo.chara, part) ? HandNoise.Surface.Cloth : HandNoise.Surface.Skin + }; + } + protected HandNoise.Intensity GetIntensityType(Tracker.Body part) + { + return part switch + { + Tracker.Body.Asoko => HandNoise.Intensity.Wet, + Tracker.Body.MuneL or Tracker.Body.MuneR or Tracker.Body.ThighL or Tracker.Body.ThighR or Tracker.Body.Groin => HandNoise.Intensity.Soft, + _ => HandNoise.Intensity.Rough + }; + } + + protected override void OnTriggerExit(Collider other) + { + if (_tracker.RemoveCollider(other)) + { + if (!IsBusy) + { + // RigidBody is being rigid, unwind it. + _unwind = true; + _timer = 1f; + // Do we need this? + HSceneInterpreter.SetSelectKindTouch(AibuColliderKind.none); + _hand.SetCollisionState(true); + } + } + } + + internal Tracker.Body GetTrackPartName(ChaControl tryToAvoidChara = null, int preferredSex = -1) + { + return tryToAvoidChara == null && preferredSex == -1 ? _tracker.GetGraspBodyPart() : _tracker.GetGraspBodyPart(tryToAvoidChara, preferredSex); + } + internal void RemoveCollider(Collider other) + { + _tracker.RemoveCollider(other); + } + internal void DebugShowActive() + { + _tracker.DebugShowActive(); + } + protected virtual void DoReaction(float velocity) + { + + } + } +} \ No newline at end of file diff --git a/Shared/Handlers/MouthGuide.cs b/Shared/Handlers/MouthGuide.cs new file mode 100644 index 0000000..417d03f --- /dev/null +++ b/Shared/Handlers/MouthGuide.cs @@ -0,0 +1,720 @@ +using KK_VR.Caress; +using KK_VR.Features; +using KK_VR.Holders; +using KK_VR.Interpreters; +using KK_VR.Settings; +using KK_VR.Trackers; +using KKAPI.Utilities; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using UnityEngine; +using VRGIN.Core; +using static HandCtrl; + +namespace KK_VR.Handlers +{ + /// + /// Moves camera on kiss/lick + /// + internal class MouthGuide : Handler + { + internal static MouthGuide Create() + { + var mouthGuide = VR.Camera.transform.Find("MouthGuide"); + if (mouthGuide != null && mouthGuide.GetComponent() != null) + { + return mouthGuide.GetComponent(); + } + mouthGuide = new GameObject("MouthGuide") { layer = 10 }.transform; + mouthGuide.SetParent(VR.Camera.transform, false); + mouthGuide.localScale = new Vector3(0.1f, 0.1f, 0.1f); + mouthGuide.localPosition = new Vector3(0, -0.07f, 0.03f); + + return mouthGuide.gameObject.AddComponent(); + } + internal static MouthGuide Instance => _instance; + private static MouthGuide _instance; + internal bool PauseInteractions + { + get => _pauseInteractions || ActiveCo; + set => _pauseInteractions = value; + } + internal Transform LookAt => _lookAt; + private static bool _pauseInteractions; + internal bool IsActive => ActiveCo; + private bool ActiveCo + { + get => _activeCo; + set + { + _activeCo = value; + KoikatuInterpreter.SceneInput.SetBusy(value); + } + } + private bool _activeCo; + private bool _disengage; + private ChaControl _lastChara; + private float _kissDistance = 0.2f; + private bool _mousePress; + + private bool _followRotation; + + private Transform _followAfter; + private Vector3 _followOffsetPos; + private Quaternion _followOffsetRot; + + private Transform _lookAt; + private Vector3 _lookOffsetPos; + //private Quaternion _lookOffsetRot; + private KissHelper _kissHelper; + private static bool _aibu; + + private float _proximityTimestamp; + private Transform _eyes; + private Transform _shoulders; + private bool _gripMove; + + private readonly Quaternion _reverse = Quaternion.Euler(0f, 180f, 0f); + private readonly Dictionary> _mouthBlacklistDic = []; + + + private void Awake() + { + _instance = this; + var collider = gameObject.AddComponent(); + collider.isTrigger = true; + var rigidBody = gameObject.AddComponent(); + rigidBody.isKinematic = true; + Tracker = new Tracker(); + + _eyes = HSceneInterpreter.lstFemale[0].objHeadBone.transform.Find("cf_J_N_FaceRoot/cf_J_FaceRoot/cf_J_FaceBase/cf_J_FaceUp_ty/cf_J_FaceUp_tz/cf_J_Eye_tz"); + _shoulders = HSceneInterpreter.lstFemale[0].objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_backsk_00"); + + _kissHelper = new KissHelper(_eyes, _shoulders); + _aibu = HSceneInterpreter.mode == HFlag.EMode.aibu; + Tracker.SetBlacklistDic(_mouthBlacklistDic); + } + internal void OnImpersonation(ChaControl chara) + { + _mouthBlacklistDic.Clear(); + _mouthBlacklistDic.Add(chara, [Tracker.Body.None]); + } + internal void OnUnImpersonation() + { + _mouthBlacklistDic.Clear(); + } + private void Update() + { + // Current version might be able to handle cross fader? test it? + if (_aibu && !PauseInteractions && !CrossFader.InTransition) + { + if (!HandleKissing()) + { + _kissHelper.AttemptProactiveKiss(); + } + } + } + + private bool HandleKissing() + { + if (KoikatuInterpreter.Settings.AssistedKissing) + { + var head = VR.Camera.Head; + if (Vector3.Distance(_eyes.position, head.position) < _kissDistance + && Quaternion.Angle(_eyes.rotation, head.rotation * _reverse) < 60f + && IsKissingAllowed()) + { + StartKiss(); + return true; + } + } + return false; + } + protected override void OnTriggerEnter(Collider other) + { + if (Tracker.AddCollider(other)) + { + var touch = Tracker.colliderInfo.behavior.touch; + if (touch != AibuColliderKind.none && !PauseInteractions && (_aibu || KoikatuInterpreter.SceneInput.IsGripMove())) + { + if (touch == AibuColliderKind.mouth && KoikatuInterpreter.Settings.AssistedKissing) + { + StartKiss(); + } + else if (touch < AibuColliderKind.reac_head && KoikatuInterpreter.Settings.AssistedLicking) + { + StartLick(touch); + } + } + } + } + protected override void OnTriggerExit(Collider other) + { + if (Tracker.AddCollider(other)) + { + if (!IsBusy) + { + HSceneInterpreter.SetSelectKindTouch(AibuColliderKind.none); + } + } + } + internal void OnGripMove(bool active) + { + if (_disengage) + { + Halt(disengage: false); + } + _gripMove = active; + } + internal void OnTriggerPress() + { + Halt(disengage: !_disengage); + } + private IEnumerator KissCo() + { + // Init part. + //VRPlugin.Logger.LogDebug($"KissCo:Start"); + var origin = VR.Camera.Origin; + var head = VR.Camera.Head; + var hand = HSceneInterpreter.handCtrl; + + var messageDelivered = false; + hand.selectKindTouch = AibuColliderKind.mouth; + HandCtrlHooks.InjectMouseButtonDown(0, () => messageDelivered = true); + _mousePress = true; + _followAfter = _eyes; + _lookAt = _eyes; + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnKissStart(AibuColliderKind.none); + } + + while (!messageDelivered) + { + hand.selectKindTouch = AibuColliderKind.mouth; + yield return null; + } + DestroyGripMove(); + yield return CoroutineUtils.WaitForEndOfFrame; + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnKissStart(AibuColliderKind.mouth); + } + + // Movement part. + // In retrospect, it's amazing that all those vec offsets work out. + + // Find desirable roll. + //var rotDelta = Quaternion.Inverse(head.rotation * Quaternion.Euler(0f, 180f, 0f)) * _lastChara.objHeadBone.transform.rotation; + var rollDelta = -Mathf.DeltaAngle((Quaternion.Inverse(head.rotation * _reverse) * _lastChara.objHeadBone.transform.rotation).eulerAngles.z, 0f); + + var angleModRight = rollDelta / 90f; // 0.0111f;// /90f; + var absModRight = Mathf.Abs(angleModRight); + var angleModUp = 1f - absModRight; + if (absModRight > 1f) + angleModRight = absModRight - (angleModRight - absModRight); + + var offsetRight = angleModRight / 15f; // 0.0667f; // /15f; + var offsetForward = KoikatuInterpreter.Settings.ProximityDuringKiss; + var offsetUp = -0.04f - (Math.Abs(offsetRight) * 0.5f); + var startDistance = Vector3.Distance(_eyes.position, head.position) - offsetForward; + + _followOffsetPos = new Vector3(offsetRight, offsetUp, offsetForward); + //var fullOffsetVec = new Vector3(offsetRight, offsetUp, offsetForward); + var rightOffsetVec = new Vector3(offsetRight, 0f, 0f); + var oldEyesPos = _eyes.position; + var timestamp = Time.time + 2f; + while (timestamp > Time.time && !_gripMove) + { + // Get closer first. + // Position is simple MoveTowards + added delta of head movement from previous frame. + // Rotation is LookRotation at eyes position with tailored offsets highly influenced by camera rotation. + var moveTowards = Vector3.MoveTowards(head.position, _eyes.TransformPoint(_followOffsetPos), Time.deltaTime * 0.07f); + var lookRotation = Quaternion.LookRotation(_eyes.TransformPoint(rightOffsetVec) - moveTowards, (_eyes.up * angleModUp) + (_eyes.right * angleModRight)); // + _eyes.forward * -0.1f); + origin.rotation = Quaternion.RotateTowards(origin.rotation, lookRotation, Time.deltaTime * 60f); + origin.position += moveTowards + (_eyes.position - oldEyesPos) - head.position; + oldEyesPos = _eyes.position; + yield return CoroutineUtils.WaitForEndOfFrame; + } + _followRotation = KoikatuInterpreter.Settings.FollowRotationDuringKiss; + _followOffsetRot = Quaternion.Inverse(_followAfter.rotation) * VR.Camera.Origin.rotation; + + while (true) + { + if (_gripMove) + { + if (Vector3.Distance(_eyes.position, head.position) > 0.2f) + { + Halt(); + } + } + else + { + var moveTowards = Vector3.MoveTowards(head.position, _eyes.TransformPoint(_followOffsetPos), Time.deltaTime * 0.05f); + if (_followRotation) + { + origin.rotation = _eyes.rotation * _followOffsetRot; + } + origin.position += moveTowards + (_eyes.position - oldEyesPos) - head.position; + } + oldEyesPos = _eyes.position; + yield return CoroutineUtils.WaitForEndOfFrame; + } + } + internal static void OnPoseChange(HFlag.EMode mode) + { + _aibu = mode == HFlag.EMode.aibu; + if (Instance != null) + { + Instance.Halt(disengage: false); + } + } + internal void UpdateOrientationOffsets() + { + var head = VR.Camera.Head; + _followRotation = KoikatuInterpreter.Settings.FollowRotationDuringKiss; + _followOffsetRot = Quaternion.Inverse(_followAfter.rotation) * VR.Camera.Origin.rotation; + _followOffsetPos = _followAfter.InverseTransformPoint(head.position); + if (_lookAt != null) + { + _lookOffsetPos = _lookAt.InverseTransformPoint(head.TransformPoint(0f, 0f, Mathf.Max(0.1f, Vector3.Distance(_lookAt.position, head.position)))); + } + } + private void StartKiss() + { + Halt(disengage: false); + _lastChara = HSceneInterpreter.lstFemale[0]; + ActiveCo = true; + StartCoroutine(KissCo()); + } + private void StartLick(AibuColliderKind colliderKind) + { + if (IsLickingAllowed(colliderKind, out var layerNum)) + { + Halt(disengage: false); + DestroyGripMove(); + _lastChara = HSceneInterpreter.lstFemale[0]; + ActiveCo = true; + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnLickStart(AibuColliderKind.none); + } + StartCoroutine(AttachCoEx(colliderKind, layerNum)); + StartCoroutine(AttachCo(colliderKind)); + } + } + private bool IsLickingAllowed(AibuColliderKind colliderKind, out int layerNum) + { + layerNum = 0; + var hand = HSceneInterpreter.handCtrl; + int bodyPartId = (int)colliderKind - 2; +#if KK + var layerInfos = hand.dicAreaLayerInfos[bodyPartId]; +#else + var layerInfos = HandCtrl.dicAreaLayerInfos[bodyPartId]; +#endif + int clothState = hand.GetClothState(colliderKind); + var layerKv = layerInfos + .Where(kv => kv.Value.useArray == 2) + .FirstOrDefault(); + var layerInfo = layerKv.Value; + layerNum = layerKv.Key; + if (layerInfo == null) + { + VRLog.Warn("Licking not ok: no layer found"); + return false; + } + if (colliderKind == AibuColliderKind.muneL || colliderKind == AibuColliderKind.muneR) + { + // Modify dic instead. + // No clue if i modify dic somewhere or we still need this.. so it stays. + var chara = GetChara; + if ((chara.IsClothes(0) && chara.fileStatus.clothesState[0] == 0) || (chara.IsClothes(2) && chara.fileStatus.clothesState[2] == 0)) + { + return false; + } + } + if (layerInfo.plays[clothState] == -1) + { + return false; + } + var heroine = hand.flags.lstHeroine[0]; + if (hand.flags.mode != HFlag.EMode.aibu && + colliderKind == AibuColliderKind.anal && + !heroine.denial.anal && + heroine.hAreaExps[3] == 0f) + { + return false; + } + return true; + } + + private bool IsKissingAllowed() + { + //VRPlugin.Logger.LogDebug($"VRMouth:IsKissingAllowed"); + //if (!_disengage) + //{ + if (!HSceneInterpreter.hFlag.isFreeH) + { + + var heroine = +#if KK + Manager.Game.Instance.HeroineList +#else + Manager.Game.HeroineList +#endif + .Where(h => h.chaCtrl == _lastChara) + .FirstOrDefault(); + if (heroine != null && heroine.denial.kiss == false && heroine.isGirlfriend == false) + { + if (HSceneInterpreter.IsVoiceActive) + { + HSceneInterpreter.hFlag.voice.playVoices[0] = 103; + _proximityTimestamp = Time.time + 10f; + } + return false; + } + } + else + { + return true; + } + return true; + } + + private IEnumerator AttachCoEx(AibuColliderKind colliderKind, int layerNum) + { + // We inject full synthetic click first, then wait for crossfade to end, + // after that we inject button down and wait for an aibu item to activate, then inform SensH and we good to go. + // Not sure if we still can get a bad state, but just in case. + + var hand = HSceneInterpreter.handCtrl; + + int bodyPartId = (int)colliderKind - 2; + var usedItem = hand.useAreaItems[bodyPartId]; + if (usedItem != null && usedItem.idUse != 2) + { + hand.DetachItemByUseItem(usedItem.idUse); + } + hand.areaItem[bodyPartId] = layerNum; + + hand.selectKindTouch = colliderKind; + yield return CaressUtil.ClickCo(); + yield return new WaitUntil(() => !CrossFader.InTransition); + + _mousePress = true; + HandCtrlHooks.InjectMouseButtonDown(0); + var timer = Time.time + 3f; + while (!GameCursor.isLock || HSceneInterpreter.handCtrl.GetUseAreaItemActive() == -1) + { + hand.selectKindTouch = colliderKind; + if (timer < Time.time) + { + Halt(); + } + yield return null; + } + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnLickStart(colliderKind); + } + HSceneInterpreter.EnableNip(colliderKind); + } + + private IEnumerator AttachCo(AibuColliderKind colliderKind) + { + ActiveCo = true; + //VRPlugin.Logger.LogDebug($"MouthGuide:AttachCo:Start"); + yield return CoroutineUtils.WaitForEndOfFrame; + var origin = VR.Camera.Origin; + var head = VR.Camera.Head; + + var dic = PoI[colliderKind]; + var lookAt = _lastChara.objBodyBone.transform.Find(dic.path); + _lookAt = lookAt; + var prevLookAt = lookAt.position; + var hand = HSceneInterpreter.handCtrl; + _lookOffsetPos = lookAt.InverseTransformPoint(head.TransformPoint(0f, 0f, Mathf.Max(0.1f, Vector3.Distance(lookAt.position, head.position)))); + while (hand.useItems[2] == null) + { + // Wait for item - phase. + // We move together with the point of interest during "Touch" animation. + origin.position += lookAt.position - prevLookAt;// * 1.5f; + prevLookAt = lookAt.position; + yield return CoroutineUtils.WaitForEndOfFrame; + if (HSceneInterpreter.hFlag.isDenialvoiceWait) + { + // There is a proper kill switch for bad states now, this shouldn't be necessary. + Halt(); + yield break; + } + } + // Actual attachment point. + var tongue = hand.useItems[2].obj.transform.Find("cf_j_tangroot"); + + // Currently there can be a bit of confusion with enabled/disabled item renderers. Making sure. + hand.useItems[2].objBody.GetComponent().enabled = true; + // Reference point to update offsets on demand. + _followAfter = tongue; + + //_offsetPos = new Vector3(0f, dic.itemOffsetUp, dic.itemOffsetForward); + //_followOffsetPos = tongue.InverseTransformPoint(head.position); + + // Use sampled offset together with custom tongue once implemented. + + _followOffsetPos = new Vector3(0f, dic.itemOffsetUp, dic.itemOffsetForward); + _followOffsetRot = Quaternion.Inverse(tongue.transform.rotation) * head.rotation; + //var lookAtOffset = new Vector3(0f, dic.poiOffsetUp, 0f); + var smoothDamp = new SmoothDamp(1f); + var oldTonguePos = tongue.position; + while (true) + { + // Engage phase. + // Get close to the tongue and wait for '_Touch' animation to end while also mimicking tongue movements. + var step = Time.deltaTime * smoothDamp.Increase(); + var adjTongue = tongue.TransformPoint(_followOffsetPos); + var moveTo = Vector3.MoveTowards(head.position, adjTongue, step * 0.2f); + origin.rotation = Quaternion.RotateTowards(origin.rotation, Quaternion.LookRotation(lookAt.TransformPoint(_lookOffsetPos) - moveTo), step * 30f); + origin.position += (moveTo - head.position) + (tongue.position - oldTonguePos); + if (_gripMove || (!HSceneInterpreter.IsTouch && Vector3.Distance(adjTongue, head.position) < 0.002f)) + { + break; + } + oldTonguePos = tongue.position; + yield return CoroutineUtils.WaitForEndOfFrame; + } + while (true) + { + if (_gripMove) + { + + } + else + { + var targetPos = tongue.TransformPoint(_followOffsetPos); + var moveTo = Vector3.MoveTowards(head.position, targetPos, Time.deltaTime * 0.05f); + + origin.rotation = Quaternion.RotateTowards(origin.rotation, Quaternion.LookRotation(lookAt.TransformPoint(_lookOffsetPos) - moveTo), Time.deltaTime * 15f); + origin.position += (moveTo - head.position); + } + yield return CoroutineUtils.WaitForEndOfFrame; + } + } + + internal IEnumerator DisengageCo() + { + //VRPlugin.Logger.LogDebug($"Mouth:Disengage:Start"); + + ActiveCo = true; + _disengage = true; + yield return new WaitUntil(() => !_gripMove); + yield return CoroutineUtils.WaitForEndOfFrame; + + // Pov can handle itself without this just fine. + if (!PoV.Active) + { + var origin = VR.Camera.Origin; + var head = VR.Camera.Head; + // One of kill-switch-postfixes can bring this. + var lookAt = (_lookAt == null ? head : _lookAt).position; + + // If pitch is too high - keep it, prob preferable. + var headPitch = Math.Abs(Mathf.DeltaAngle(head.eulerAngles.x, 0f)) > 40f; + + var uprightRot = Quaternion.identity; + var lerpMultiplier = 0f; + var startRot = origin.rotation; + var lerp = 0f; + var sDamp = new SmoothDamp(1f); + while (true) + { + var dist = Vector3.Distance(lookAt, head.position); + var close = dist < 0.25f; + var pos = close ? head.TransformPoint(0f, 0f, -(Time.deltaTime * 0.12f * sDamp.Increase())) : head.position; + if (lerp < 1f && dist > 0.13f) + { + if (lerp == 0f) + { + //if (KoikatuInterpreter.Settings.ImperfectRotation) + //{ + // uprightRot = Quaternion.Euler( + // headPitch ? head.eulerAngles.x : 0f, + // origin.eulerAngles.y, + // headPitch ? head.eulerAngles.z : 0f); + + // uprightRot *= Quaternion.Inverse(origin.rotation) * head.rotation; + //} + //else + //{ + uprightRot = Quaternion.Euler( + headPitch ? origin.eulerAngles.x : 0f, + origin.eulerAngles.y, + headPitch ? origin.eulerAngles.z : 0f); + // } + lerpMultiplier = KoikatuInterpreter.Settings.FlightSpeed * 30f / Quaternion.Angle(origin.rotation, uprightRot); + } + var sStep = Mathf.SmoothStep(0f, 1f, lerp += Time.deltaTime * lerpMultiplier); + origin.rotation = Quaternion.Lerp(startRot, uprightRot, sStep); + } + else if (!close) + { + break; + } + // //Quaternion.RotateTowards(origin.rotation, Quaternion.Euler(origin.eulerAngles.x, origin.eulerAngles.y, 0f), step * 45f); + origin.position += pos - head.position; + yield return CoroutineUtils.WaitForEndOfFrame; + } + } + else + { + // Let pov handle re-engage on slower then usual speed. + PoV.Instance.CameraIsFar(0.25f); + PauseInteractions = true; + } + + //VRPlugin.Logger.LogDebug($"MouthGuide:Disengage:End"); + ActiveCo = false; + _disengage = false; + } + private void DoReaction() + { + + } + internal void Halt(bool disengage = true) + { + //VRPlugin.Logger.LogDebug($"MouthGuide:Halt:Disengage = {disengage}");//\n{new StackTrace(0)}"); + + if (ActiveCo) + { + StopAllCoroutines(); + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnKissEnd(); + } + ActiveCo = false; + _disengage = false; + _followRotation = false; + HSceneInterpreter.handCtrl.DetachItemByUseItem(2); + HSceneInterpreter.handCtrl.selectKindTouch = AibuColliderKind.none; + UnlazyGripMove(); + } + if (_mousePress) + { + HandCtrlHooks.InjectMouseButtonUp(0); + _mousePress = false; + } + if (disengage) + { + StartCoroutine(DisengageCo()); + } + } + + private void DestroyGripMove() + { + foreach (var hand in HandHolder.GetHands) + { + hand.Tool.DestroyGripMove(); + } + _gripMove = false; + } + private void UnlazyGripMove() + { + foreach (var hand in HandHolder.GetHands) + { + hand.Tool.UnlazyGripMove(); + } + } + + // About to be obsolete in favor of dynamic offsets and bootleg tongue. + private struct LickItem + { + internal string path; + internal float itemOffsetForward; + internal float itemOffsetUp; + internal float poiOffsetUp; + internal float directionUp; + internal float directionForward; + } + + private readonly Dictionary PoI = new() + { + // There are inconsistencies depending on the pose. Not fixed: ass, anal. + { + AibuColliderKind.muneL, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_bust00/cf_s_bust00_L/cf_d_bust01_L" + + "/cf_j_bust01_L/cf_d_bust02_L/cf_j_bust02_L/cf_d_bust03_L/cf_j_bust03_L/cf_s_bust03_L/k_f_mune03L_02", + itemOffsetForward = 0.08f, + itemOffsetUp = 0f, + poiOffsetUp = 0.05f, + directionUp = 1f, + directionForward = 0f + } + }, + { + AibuColliderKind.muneR, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_bust00/cf_s_bust00_R/cf_d_bust01_R" + + "/cf_j_bust01_R/cf_d_bust02_R/cf_j_bust02_R/cf_d_bust03_R/cf_j_bust03_R/cf_s_bust03_R/k_f_mune03R_02", + itemOffsetForward = 0.08f, + itemOffsetUp = 0f, + poiOffsetUp = 0.05f, + directionUp = 1f, + directionForward = 0f + } + }, + { + AibuColliderKind.kokan, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_s_waist02/k_f_kosi02_02", + itemOffsetForward = 0.06f, + itemOffsetUp = 0.03f, + poiOffsetUp = 0f, + directionUp = 0.5f, + directionForward = 0.5f + } + }, + { + AibuColliderKind.anal, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_s_waist02/k_f_kosi02_02", + itemOffsetForward = -0.05f,// -0.05f, + itemOffsetUp = -0.08f, + poiOffsetUp = 0f, + directionUp = 1f, + directionForward = 0f + } + }, + { + AibuColliderKind.siriL, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/aibu_hit_siri_L", + itemOffsetForward = -0.08f, // -0.04f + itemOffsetUp = 0f,//0.04f, + poiOffsetUp = 0.2f, + directionUp = 1f, + directionForward = 0f + } + }, + { + AibuColliderKind.siriR, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/aibu_hit_siri_R", + itemOffsetForward = -0.08f,// -0.04f + itemOffsetUp = 0f,//0.04f, + poiOffsetUp = 0.2f, + directionUp = 1f, + directionForward = 0f + } + }, + { + AibuColliderKind.none, new LickItem { + path = "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_s_waist02/k_f_kosi02_02", + itemOffsetForward = -0.07f, + itemOffsetUp = -0.01f, + poiOffsetUp = 0f, + directionUp = 0f, + directionForward = -1f + } + }, + }; + + } +} diff --git a/Shared/Holders/HandHolder.cs b/Shared/Holders/HandHolder.cs new file mode 100644 index 0000000..bdc37ac --- /dev/null +++ b/Shared/Holders/HandHolder.cs @@ -0,0 +1,546 @@ +using BepInEx; +using KK_VR.Features; +using KK_VR.Fixes; +using KK_VR.Interpreters; +using ADV.Commands.Object; +using Illusion.Game; +using IllusionUtility.GetUtility; +using SceneAssist; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using UnityEngine; +using UnityEngine.Networking; +using VRGIN.Core; +using static HandCtrl; +using static RootMotion.FinalIK.RagdollUtility; +using KK_VR.Interactors; +using VRGIN.Controls; +using ADV.Commands.Base; +using KK_VR.Controls; +using RootMotion.FinalIK; +using KK_VR.Handlers; +using KK_VR.Grasp; +using System.Text.RegularExpressions; +using UnityEngine.EventSystems; + +namespace KK_VR.Holders +{ + // We adapt animated aibu items as controller models. To see why we do this in SUCH a roundabout way + // grab default disabled ones in HScene and scroll through their animation layers, + // their orientations are outright horrible for our purposes. + internal class HandHolder : Holder + { + private static readonly List _instances = []; + private readonly List _itemList = []; + private Transform _controller; + private ItemLag _itemLag; + private bool _parent; + internal bool IsParent => _parent; + private HandNoise _handNoise; + internal HandNoise Noise => _handNoise; + internal Controller Controller { get; private set; } + internal int Index { get; private set; } + internal ItemHandler Handler => _handler; + internal GraspController Grasp { get; private set; } + internal GameplayTool Tool { get; private set; } + + /// + /// Clutch for unruly colliders in roaming. + /// + internal static void SetKinematic(bool state) + { + foreach (var inst in _instances) + { + inst._rigidBody.isKinematic = state; + } + } + internal static List GetHands => _instances; + private readonly int[] _itemIDs = [0, 2, 5, 7, 9, 11]; + internal static HandHolder GetHand(int index) => _instances[index]; + internal void Init(int index) + { + _instances.Add(this); + Index = index; + Controller = index == 0 ? VR.Mode.Left : VR.Mode.Right; + _controller = Controller.transform; + Tool = Controller.GetComponent(); + if (_loadedAssetsList.Count == 0) + { + LoadAssets(); + HandNoise.Init(); + } + SetItems(index); + Grasp = new GraspController(this); + _handNoise = new HandNoise(gameObject.AddComponent()); + } + + internal static void OnBecomingBusy() + { + foreach (var inst in _instances) + { + inst.Tool.HideLaser(); + } + } + + internal static void UpdateHandlers() + where T : ItemHandler + { + foreach (var inst in _instances) + { + inst.RemoveHandler(); + inst.AddHandler(); + } + } + private void AddHandler() + where T : ItemHandler + { + _handler = gameObject.AddComponent(); + _handler.Init(this); + } + private void RemoveHandler() + { + if (_handler != null) + { + UnityEngine.Component.Destroy(_handler); + } + } + internal static void DestroyHandlers() + { + foreach (var inst in _instances) + { + inst.RemoveHandler(); + } + } + + private void SetItems(int index) + { + _anchor = transform; + _anchor.SetParent(VR.Manager.transform, false); + _offset = new GameObject("offset").transform; + _offset.SetParent(_anchor, false); + _rigidBody = gameObject.AddComponent(); + _rigidBody.useGravity = false; + _rigidBody.freezeRotation = true; + VRBoop.AddDB(gameObject.AddComponent()); + VRBoop.AddDB(_offset.gameObject.AddComponent()); + + + + for (var i = 0; i < +#if KK + 4; +#else + 6; +#endif + i++) + { + InitItem(i, index); + } + + _activeItem = _itemList[0]; + ActivateItem(); + Controller.Model.gameObject.SetActive(false); + } + private void InitItem(int i, int index) + { + var item = new ItemType( + i, + _asset: _loadedAssetsList[_itemIDs[i] + index] + ); + _itemList.Add(item); + } + + private void SetPhysMat(PhysicMaterial material) + { + material.staticFriction = 1f; + material.dynamicFriction = 1f; + material.bounciness = 0f; + } + private void SetColliders(int index) + { + + foreach (var collider in gameObject.GetComponents()) + { + UnityEngine.Component.Destroy(collider); + } + foreach (var collider in _offset.GetComponents()) + { + UnityEngine.Component.Destroy(collider); + } + if (index < 2) + { + //First collider is a main collision shape that gets disabled when necessary. + // Hands + + var capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.025f; + capsule.height = 0.1f; + capsule.center = new Vector3(0f, 0.01f, 0f); + SetPhysMat(capsule.material); + + // A bit bigger copy-trigger. + capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.035f; + capsule.height = 0.11f; + capsule.center = new Vector3(0f, 0.01f, 0f); + capsule.isTrigger = true; + } + else if (index == 2) + { + // Massager + + var capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.032f; + capsule.height = 0.05f; + capsule.center = new Vector3(0f, 0f, 0.115f); + SetPhysMat(capsule.material); + + // A bit bigger copy-trigger. + capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.038f; + capsule.height = 0.06f; + capsule.center = new Vector3(0f, 0f, 0.115f); + capsule.isTrigger = true; + + // Extra capsule for handle + capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.025f; + capsule.height = 0.1f; + SetPhysMat(capsule.material); + + } + else if (index == 3) + { + // Vibrator + + var capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.02f; + capsule.height = 0.28f; + capsule.center = new Vector3(0f, 0f, 0.1f); + SetPhysMat(capsule.material); + + // A bit bigger copy-trigger. + capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.025f; + capsule.height = 0.32f; + capsule.center = new Vector3(0f, 0f, 0.1f); + capsule.isTrigger = true; + } + else if (index == 4) + { + var capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.02f; + capsule.height = 0.2f; + capsule.center = new Vector3(0f, -0.01f, 0.1f); + SetPhysMat(capsule.material); + + capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.024f; + capsule.height = 0.22f; + capsule.center = new Vector3(0f, -0.01f, 0.1f); + capsule.isTrigger = true; + } + else if (index == 5) + { + // Rotor + + var capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.015f; + capsule.height = 0.04f; + SetPhysMat(capsule.material); + + capsule = gameObject.AddComponent(); + capsule.direction = 2; + capsule.radius = 0.02f; + capsule.height = 0.06f; + capsule.isTrigger = true; + } + } + private void UpdateDynamicBoneColliders() + { + var infos = _activeItem.animParam.dbcInfo[Array.IndexOf(_activeItem.animParam.layers, _activeItem.layer)]; + for (var i = 0; i < 2; i++) + { + var info = infos[i]; + var db = (i == 0 ? transform : _offset).GetComponent(); + if (info != null) + { + db.enabled = true; + db.m_Center = info.center; + db.m_Radius = info.radius; + db.m_Height = info.height; + db.m_Direction = (DynamicBoneCollider.Direction)info.direction; + if (i == 1) + { + _offset.localRotation = info.localRot; + } + } + else + { + db.m_Radius = 0f; + db.m_Height = 0f; + db.enabled = false; + } + } + } + + //public static void SetHandColor(ChaControl chara) + //{ + // // Different something (material, shader?) so the colors wont match from the get go. + // var color = chara.fileBody.skinMainColor; + // for (var i = 0; i < 4; i++) + // { + // aibuItemList[i].SetHandColor(color); + // } + //} + private void ActivateItem() + { + _activeOffsetPos = _activeItem.animParam.positionOffset; + _activeOffsetRot = _activeItem.animParam.rotationOffset; + _anchor.SetPositionAndRotation(_controller.TransformPoint(_activeOffsetPos), _controller.rotation); + _activeItem.rootPoint.localScale = Fixes.Util.Divide(Vector3.Scale(Vector3.one, _activeItem.rootPoint.localScale), _activeItem.rootPoint.lossyScale); + _activeItem.rootPoint.gameObject.SetActive(true); + SetStartLayer(); + SetColliders(_activeItem.animParam.index); + // Assign this one on basis of player's character scale? + // No clue where ChaFile hides the height. + } + private void DeactivateItem() + { + _activeItem.rootPoint.gameObject.SetActive(false); + //_activeItem.rootPoint.SetParent(VR.Manager.transform, false); + StopSE(); + } + public void SetRotation(float x, float y, float z) + { + _activeOffsetRot = Quaternion.Euler(x, y, z); + } + private void LateUpdate() + { + if (_itemLag != null) + { + _itemLag.SetPositionAndRotation(_controller.TransformPoint(_activeOffsetPos), _controller.rotation); + } + if (_activeItem != null) + { + _activeItem.rootPoint.rotation = _anchor.rotation * _activeOffsetRot * Quaternion.Inverse(_activeItem.movingPoint.rotation) * _activeItem.rootPoint.rotation; + _activeItem.rootPoint.position += _anchor.position - _activeItem.movingPoint.position; + //_activeItem.rootPoint.SetPositionAndRotation( + // _activeItem.rootPoint.position + (_anchor.position - _activeItem.movingPoint.position), + // _anchor.rotation * _activeItem.rotationOffset * Quaternion.Inverse(_activeItem.movingPoint.rotation) * _activeItem.rootPoint.rotation + // ); + } + } + + private void FixedUpdate() + { + if (_itemLag == null) + { + _rigidBody.MoveRotation(_controller.rotation); + _rigidBody.MovePosition(_controller.TransformPoint(_activeOffsetPos)); + } + } + + // Due to scarcity of hotkeys, we'll go with increase only. + /// + /// Scroll hand item. + /// + internal void ChangeItem() + { + DeactivateItem(); + + _activeItem = _itemList[(_itemList.IndexOf(_activeItem) + 1) % _itemList.Count]; + ActivateItem(); + } + private void PlaySE() + { + var aibuItem = _activeItem.aibuItem; + if (aibuItem.pathSEAsset.IsNullOrEmpty()) return; + + if (aibuItem.transformSound == null) + { + var se = new Utils.Sound.Setting +#if KK + { + type = Manager.Sound.Type.GameSE3D, + assetBundleName = aibuItem.pathSEAsset, + assetName = aibuItem.nameSEFile, + }; +#else + (); + se.loader.type = Manager.Sound.Type.GameSE3D; + se.loader.bundle = aibuItem.pathSEAsset; + se.loader.asset = aibuItem.nameSEFile; +#endif + aibuItem.transformSound = Utils.Sound.Play(se).transform; + aibuItem.transformSound.GetComponent().loop = true; + aibuItem.transformSound.SetParent(_activeItem.movingPoint, false); + } + else + { + aibuItem.transformSound.GetComponent().Play(); + } + } + private void StopSE() + { + if (_activeItem.aibuItem.transformSound != null) + { + _activeItem.aibuItem.transformSound.GetComponent().Stop(); + } + } + public void SetStartLayer() + { + _activeItem.aibuItem.anm.SetLayerWeight(_activeItem.layer, 0f); + _activeItem.aibuItem.anm.SetLayerWeight(_activeItem.animParam.startLayer, 1f); + _activeItem.layer = _activeItem.animParam.startLayer; + UpdateDynamicBoneColliders(); + } + public void ChangeLayer(bool increase, bool skipTransition = false) + { + if (_activeItem.animParam.layers == null) return; + StopSE(); + var anm = _activeItem.aibuItem.anm; + var oldLayer = _activeItem.layer; + + var oldIndex = Array.IndexOf(_activeItem.animParam.layers, oldLayer); + var newIndex = increase ? (oldIndex + 1) % _activeItem.animParam.layers.Length : oldIndex <= 0 ? _activeItem.animParam.layers.Length - 1 : oldIndex - 1; + //VRPlugin.Logger.LogDebug($"oldIndex:{oldIndex}:newIndex:{newIndex}"); + var newLayer = _activeItem.animParam.layers[newIndex]; + + if (skipTransition) + { + anm.SetLayerWeight(newLayer, 1f); + anm.SetLayerWeight(oldLayer, 0f); + _activeItem.layer = newLayer; + UpdateDynamicBoneColliders(); + } + else + { + StartCoroutine(ChangeLayerCo(anm, oldLayer, newLayer)); + } + + if (newLayer != 0 && _activeItem.aibuItem.pathSEAsset != null) + { + PlaySE(); + } + } + + private IEnumerator ChangeLayerCo(Animator anm, int oldLayer, int newLayer) + { + var timer = 0f; + var stop = false; + while (!stop) + { + timer += Time.deltaTime * 2f; + if (timer > 1f) + { + timer = 1f; + stop = true; + } + anm.SetLayerWeight(newLayer, timer); + anm.SetLayerWeight(oldLayer, 1f - timer); + yield return null; + } + _activeItem.layer = newLayer; + UpdateDynamicBoneColliders(); + } + + + internal void OnLimbSyncStart() + { + DeactivateItem(); + AddLag(10); + } + + internal void OnLimbSyncStop() + { + ActivateItem(); + RemoveLag(); + } + + + internal void OnGraspHold() + { + // We adjust position after release of rigidBody, as it most likely had some velocity on it. + // Can't arbitrary move controller with this SteamVR version, kinda given tbh. + if (_parent) + { + _parent = false; + AddLag(20); + } + else + { + Shackle(20); + } + } + internal void Shackle(int amount) + { + // We compensate the release of rigidBody's velocity by applying offset (target point of rigidBody). + var pos = _anchor.position; + _rigidBody.isKinematic = true; + _anchor.position = pos; + _activeOffsetPos = _controller.InverseTransformPoint(_anchor.position); + AddLag(amount); + } + internal void OnGraspRelease() + { + if (!_parent) + { + Unshackle(); + } + else + { + // Next GraspRelease will unparent limb from controller. + _parent = false; + } + } + internal void Unshackle() + { + RemoveLag(); + _rigidBody.isKinematic = false; + _activeOffsetPos = _activeItem.animParam.positionOffset; + } + internal void AddLag(int numberOfFrames) + { + _itemLag = new ItemLag(_anchor, KoikatuInterpreter.ScaleWithFps(numberOfFrames)); + } + internal void RemoveLag() + { + _itemLag = null; + } + internal void OnBecomingParent() + { + _parent = true; + AddLag(10); + _rigidBody.isKinematic = true; + } + + //private readonly List _colliderParentListStartsWith = + // [ + // "cf_j_middle02_", + // "cf_j_index02_", + // "cf_j_ring02_", + // "cf_j_thumb02_", + // "cf_s_hand_", + //]; + //private readonly List _colliderParentListEndsWith = + // [ + // "_head_00", + // "J_vibe_02", + // "J_vibe_05", + //]; + } +} diff --git a/Shared/Holders/HandNoise.cs b/Shared/Holders/HandNoise.cs new file mode 100644 index 0000000..278eeaa --- /dev/null +++ b/Shared/Holders/HandNoise.cs @@ -0,0 +1,205 @@ +using BepInEx; +using KK_VR.Interpreters; +using System; +using System.Collections; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine.Networking; +using UnityEngine; +using VRGIN.Core; + +namespace KK_VR.Holders +{ + /// + /// Provides SFX for character/controller interactions + /// + internal class HandNoise + { + private readonly AudioSource _audioSource; + internal bool IsPlaying => _audioSource.isPlaying; + internal HandNoise(AudioSource audioSource) + { + _audioSource = audioSource; + } + internal static void Init() + { + PopulateDic(); + } + internal void PlaySfx(float volume, Sfx sfx, Surface surface, Intensity intensity, bool overwrite) + { + if (_audioSource.isPlaying) + { + if (!overwrite) return; + _audioSource.Stop(); + } + + //VRPlugin.Logger.LogInfo($"AttemptToPlay:{sfx}:{surface}:{intensity}:{volume}"); + AdjustInput(sfx, ref surface, ref intensity); + var audioClipList = sfxDic[sfx][(int)surface][(int)intensity]; + var count = audioClipList.Count; + if (count != 0) + { + _audioSource.volume = Mathf.Clamp01(volume); + _audioSource.pitch = 0.9f + UnityEngine.Random.value * 0.2f; + _audioSource.clip = audioClipList[UnityEngine.Random.Range(0, count)]; + _audioSource.Play(); + } + + } + private void AdjustInput(Sfx sfx, ref Surface surface, ref Intensity intensity) + { + // Because currently we have far from every category covered. + if (intensity == Intensity.Wet) + { + surface = Surface.Skin; + } + else if (sfx == Sfx.Slap) + { + surface = Surface.Skin; + } + else if (sfx == Sfx.Traverse) + { + if (surface == Surface.Hair) + { + intensity = Intensity.Soft; + } + else if (surface == Surface.Skin) + { + intensity = Intensity.Soft; + } + } + else if (sfx == Sfx.Undress) + { + + } + } + + // As of now categories are highly inconsistent, perhaps revamp of sorts? + internal enum Sfx + { + Tap, + Slap, + Traverse, + Undress, + } + internal enum Surface + { + Skin, + Cloth, + Hair + } + internal enum Intensity + { + Soft, + Rough, + Wet + } + + + //internal enum Intensity + //{ + // // Think about: + // // Soft as something smallish and soft and on slower side of things, like boobs or ass. + // // Rough as something flattish and big and at times intense, like tummy or thighs. + // // Wet as.. I yet to mix something proper for it. WIP. + // Soft, + // Rough, + // Wet + //} + private static readonly Dictionary>>> sfxDic = []; + private static void InitDic() + { + for (var i = 0; i < Enum.GetNames(typeof(Sfx)).Length; i++) + { + var key = (Sfx)i; + sfxDic.Add(key, []); + for (var j = 0; j < Enum.GetNames(typeof(Surface)).Length; j++) + { + sfxDic[key].Add([]); + for (var k = 0; k < Enum.GetNames(typeof(Intensity)).Length; k++) + { + sfxDic[key][j].Add([]); + } + } + } + } + private static void PopulateDic() + { + InitDic(); + for (var i = 0; i < sfxDic.Count; i++) + { + var key = (Sfx)i; + for (var j = 0; j < sfxDic[key].Count; j++) + { + for (var k = 0; k < sfxDic[key][j].Count; k++) + { + var directory = BepInEx.Utility.CombinePaths( + [ + Paths.PluginPath, + "SFX", + key.ToString(), + ((Surface)j).ToString(), + ((Intensity)k).ToString() + ]); + if (Directory.Exists(directory)) + { + var dirInfo = new DirectoryInfo(directory); + var clipNames = new List(); + //foreach (var file in dirInfo.GetFiles("*.wav")) + //{ + // clipNames.Add(file.Name); + //} + foreach (var file in dirInfo.GetFiles("*.ogg")) + { + clipNames.Add(file.Name); + } + if (clipNames.Count == 0) continue; + VRManager.Instance.StartCoroutine(LoadAudioFile(directory, clipNames, sfxDic[key][j][k])); + } + } + } + } + } + + private static IEnumerator LoadAudioFile(string path, List clipNames, List destination) + { + foreach (var name in clipNames) + { + //UnityWebRequest audioFile; + //if (name.EndsWith(".wav")) + //{ + // audioFile = UnityWebRequest.GetAudioClip(Path.Combine(path, name), AudioType.WAV); + //} + //else + //{ +#if KK + + var audioFile = UnityWebRequest.GetAudioClip(Path.Combine(path, name), AudioType.OGGVORBIS); +#else + var audioFile = UnityWebRequestMultimedia.GetAudioClip(Path.Combine(path, name), AudioType.OGGVORBIS); +#endif + //} +#if KK + + yield return audioFile.Send(); + if (audioFile.isError) +#else + yield return audioFile.SendWebRequest(); + if (audioFile.isHttpError || audioFile.isNetworkError) +#endif + { + VRPlugin.Logger.LogWarning($"{audioFile.error} - {Path.Combine(path, name)}"); + } + else + { + var clip = DownloadHandlerAudioClip.GetContent(audioFile); + clip.name = name; + destination.Add(clip); + //VRPlugin.Logger.LogDebug($"Loaded:SFX:{name}"); + } + } + } + } +} diff --git a/Shared/Holders/Holder.cs b/Shared/Holders/Holder.cs new file mode 100644 index 0000000..dd58b3f --- /dev/null +++ b/Shared/Holders/Holder.cs @@ -0,0 +1,530 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using static HandCtrl; +using static KK_VR.Holders.HandHolder; +using VRGIN.Core; +using IllusionUtility.GetUtility; +using KK_VR.Handlers; +using Illusion.Extensions; + +namespace KK_VR.Holders +{ + /// + /// Provides orientation of the held model + /// + internal abstract class Holder : MonoBehaviour + { + protected Rigidbody _rigidBody; + protected ItemType _activeItem; + + // Can't arbitrary move VR controllers like in KK, have to do it with offsets. + protected Vector3 _activeOffsetPos; + protected Quaternion _activeOffsetRot; + + internal Transform Anchor => _anchor; + protected Transform _anchor; + protected Transform _offset; + protected static readonly Dictionary _loadedAssetsList = []; + + // Transparent thingy. + internal static Material Material { get; private set; } + protected ItemHandler _handler; + protected readonly struct AnimParam + { + internal AnimParam(int _index, int[] _layers, List _dbcInfo, int _startLayer, string _movePartName, Vector3 _posOffset, Quaternion _rotOffset) + { + index = _index; + layers = _layers; + dbcInfo = _dbcInfo; + startLayer = _startLayer; + movingPartName = _movePartName; + positionOffset = _posOffset; + rotationOffset = _rotOffset; + } + internal readonly int index; + internal readonly int[] layers; + internal readonly List dbcInfo; + internal readonly int startLayer; + internal readonly string movingPartName; + internal readonly Vector3 positionOffset; + internal readonly Quaternion rotationOffset; + } + + protected class ColInfo + { + internal ColInfo(Vector3 _center, float _radius, float _height, int _direction, Quaternion _localRot) + { + center = _center; + radius = _radius; + height = _height; + direction = _direction; + localRot = _localRot; + } + internal readonly Vector3 center; + internal readonly float radius; + internal readonly float height; + internal readonly int direction; + internal readonly Quaternion localRot; + } + + protected private static readonly List _defaultAnimParamList = + [ + // Hand + new AnimParam( + _index: 0, + _layers: [4, 7, 10], + _dbcInfo: + [ + [ + new ColInfo(new Vector3(0f, 0.01f, 0f), 0.025f, 0.1f, 2, Quaternion.identity), + null, + ], + [ + new ColInfo(new Vector3(0f, 0.01f, 0f), 0.025f, 0.1f, 2, Quaternion.identity), + null, + ], + [ + new ColInfo(new Vector3(0f, 0.01f, 0f), 0.025f, 0.1f, 2, Quaternion.identity), + null, + ] + ], + _startLayer: 10, + _movePartName: "cf_j_handroot_", + _posOffset: new Vector3(0f, -0.02f, -0.07f), + _rotOffset: Quaternion.identity + ), + // Finger + new AnimParam( + _index: 1, + _layers: [1, 3, 9, 4, 6], + _dbcInfo: + [ + [ // 1 + new ColInfo(new Vector3(0f, 0.005f, 0f), 0.025f, 0.04f, 2, Quaternion.identity), + new ColInfo(new Vector3(-0.018f, 0.02f, 0.03f), 0.01f, 0.06f, 2, Quaternion.Euler(45, 0, 0)), + ], + [ // 3 + new ColInfo(new Vector3(0f, 0.005f, 0f), 0.025f, 0.04f, 2, Quaternion.identity), + new ColInfo(new Vector3(-0.018f, 0.015f, 0.02f), 0.01f, 0.05f, 2, Quaternion.Euler(60, 0, 0)), + ], + [ // 9 + new ColInfo(new Vector3(0f, 0.005f, 0f), 0.025f, 0.04f, 2, Quaternion.identity), + new ColInfo(new Vector3(-0.015f, 0.01f, 0.025f), 0.01f, 0.1f, 2, Quaternion.identity), + ], + [ // 4 + new ColInfo(new Vector3(0f, 0.005f, 0f), 0.025f, 0.04f, 2, Quaternion.identity), + new ColInfo(new Vector3(-0.015f, 0.01f, 0.035f), 0.01f, 0.08f, 2, Quaternion.Euler(15, 0, 0)), + ], + [ // 6 + new ColInfo(new Vector3(0f, 0.005f, 0f), 0.025f, 0.04f, 2, Quaternion.identity), + new ColInfo(new Vector3(-0.01f, 0.02f, 0.03f), 0.013f, 0.07f, 2, Quaternion.Euler(45, 0, 0)) + ] + ], + _startLayer: 9, + _movePartName: "cf_j_handroot_", + _posOffset: new Vector3(0f, -0.02f, -0.07f), + _rotOffset: Quaternion.identity + ), + // Massager + new AnimParam( + _index: 2, + _layers: [0, 1], + _dbcInfo: + [ + [ + new ColInfo(new Vector3(0f, 0f, 0.115f), 0.032f, 0.05f, 2, Quaternion.identity), + new ColInfo(Vector3.zero, 0.025f, 0.1f, 2, Quaternion.identity), + ], + [ + new ColInfo(new Vector3(0f, 0f, 0.115f), 0.032f, 0.05f, 2, Quaternion.identity), + new ColInfo(Vector3.zero, 0.025f, 0.1f, 2, Quaternion.identity), + ] + ], + _startLayer: 0, + _movePartName: "N_massajiki_", + _posOffset: new Vector3(0f, 0f, -0.05f), + _rotOffset: Quaternion.Euler(-90f, 180f, 0f) + ), + // Vibe + new AnimParam( + _index: 3, + _layers: [0, 1], + _dbcInfo: + [ + [ + new ColInfo(new Vector3(0f, 0f, 0.1f), 0.02f, 0.28f, 2, Quaternion.identity), + new ColInfo(new Vector3(0f, -0.0325f, 0.1100f), 0.012f, 0.03f, 2, Quaternion.Euler(-30, 0, 0)), + ], + [ + new ColInfo(new Vector3(0f, 0f, 0.1f), 0.02f, 0.28f, 2, Quaternion.identity), + new ColInfo(new Vector3(0f, -0.0325f, 0.1100f), 0.012f, 0.03f, 2, Quaternion.Euler(-30, 0, 0)), + ] + ], + _startLayer: 0, + _movePartName: "N_vibe_Angle", + _posOffset: new Vector3(0f, 0f, -0.1f), + _rotOffset: Quaternion.Euler(-90f, 180f, 0f) + ), + + // Dildo + new AnimParam( + _index: 4, + _layers: [1, 3], + _dbcInfo: + [ + [ + new ColInfo(new Vector3(0f, -0.0350f, 0.03f), 0.02f, 0.06f, 0, Quaternion.identity), + new ColInfo(new Vector3(0f, 0.0075f, 0.1f), 0.02f, 0.18f, 2, Quaternion.Euler(7, 0, 0)), + ], + [ + new ColInfo(new Vector3(0f, -0.0350f, 0.03f), 0.02f, 0.06f, 0, Quaternion.identity), + new ColInfo(new Vector3(0f, 0.0075f, 0.1f), 0.02f, 0.18f, 2, Quaternion.Euler(7, 0, 0)), + ] + ], + _startLayer: 1, + _movePartName: "N_dildo_Angle", + _posOffset: new Vector3(0f, 0f, -0.1f), + _rotOffset: Quaternion.Euler(90f, 0f, 0f) + ), + + // Rotor + new AnimParam( + _index: 5, + _layers: [0, 1], + _dbcInfo: + [ + [ + new ColInfo(new Vector3(0f, 0f, 0f), 0.015f, 0.04f, 2, Quaternion.identity), + null, + ], + [ + new ColInfo(new Vector3(0f, 0f, 0f), 0.015f, 0.04f, 2, Quaternion.identity), + null, + ] + ], + _startLayer: 0, + _movePartName: "N_move_", + _posOffset: new Vector3(0.005f, -0.01f, -0.025f), + _rotOffset: Quaternion.Euler(90f, 0f, 0f) + ), + + //new AnimParam + //{ + // // 6 - Tongue + // /* + // * 21, 16, 18, 19 - licking haphazardly + // * 7 - at lower angle + // * 9 - at lower angle very slow + // * 10 - at higher angle + // * 12 - at higher angle very slow + // * + // * 13 - very high angle, flopping + // * + // * 15 - very high angle, back forth + // * 1, 3, 4, 6, - licking + // * + // * 1 - Lick + // * 2 - Lick stop. + // * 3 - Lick + // * 4 - Lick + // * 5 - Lick stop. + // * 6 - Lick + // * 7 - Lick no pos move, angular same. + // * 8 - Lick no pos move, angular same stop. + // * 9 - Lick no pos move, slow angular + // * 10 - Lick no pos move, fast angular + // * 11 - Lick no pos move, fast(slow) angular, stop + // * 12 - Lick no pos move, slow angular + // * 13 - Doing "fish our of sea" movements + // * 14 - Doing "fish our of sea" movements stop + // * 15 - Tube-like back and forth + // * 16 - Intense lick + // * 17 - Intense lick stop + // * 18 - Intense lick + // * 19 - Extra intense lick + // * 20 - Extra intense lick stop + // * 21 - Lick + // */ + // index = 6, + // availableLayers = [1, 7, 9, 10, 12, 13, 15, 16], + // movingPartName = "cf_j_tang_01", // cf_j_tang_01 / cf_j_tangangle + // //handlerParentName = "cf_j_tang_03", + // positionOffset = new Vector3(0f, -0.04f, 0.05f), + // rotationOffset = Quaternion.identity, // Quaternion.Euler(-90f, 0f, 0f) + //}, + ]; + protected class ItemType + { + internal readonly AibuItem aibuItem; + //internal readonly GameObject handlerParent; + internal readonly Transform rootPoint; + internal readonly Transform movingPoint; + internal readonly AnimParam animParam; + //internal readonly Quaternion rotationOffset; + //internal readonly Vector3 positionOffset; + internal int layer; + //internal readonly int[] availableLayers; + //internal readonly int startLayer; + + internal ItemType(int index, AibuItem _asset) + { + aibuItem = _asset; + rootPoint = _asset.obj.transform; + rootPoint.transform.SetParent(VR.Manager.transform, false); + animParam = _defaultAnimParamList[index]; + movingPoint = rootPoint.GetComponentsInChildren() + .Where(t => t.name.StartsWith(animParam.movingPartName, StringComparison.Ordinal)) + .FirstOrDefault(); + //handlerParent = rootPoint.GetComponentsInChildren() + // .Where(t => t.name.StartsWith(animParam.handlerParentName, StringComparison.Ordinal) + // || t.name.EndsWith(animParam.handlerParentName, StringComparison.Ordinal)) + // .FirstOrDefault().gameObject; + + } + } + + + + internal void SetCollisionState(bool state) + { + // Surprisingly we can't enable isKinematic during collision/intersection, + // whole collision system goes haywire otherwise. + // So we disable first collider instead. +#if KK + var collider = gameObject.GetComponent(); + if (collider != null) +#else + if (gameObject.TryGetComponent(out var collider)) +#endif + { + collider.enabled = state; + } + } +#if KK + protected void LoadAssets() + { + // Straight from HandCtrl. + var textAsset = GlobalMethod.LoadAllListText("h/list/", "AibuItemObject", null); + GlobalMethod.GetListString(textAsset, out var array); + for (int i = 0; i < array.GetLength(0); i++) + { + int num = 0; + int num2 = 0; + + int.TryParse(array[i, num++], out num2); + + if (!_loadedAssetsList.TryGetValue(num2, out var aibuItem)) + { + _loadedAssetsList.Add(num2, new AibuItem()); + aibuItem = _loadedAssetsList[num2]; + } + aibuItem.SetID(num2); + + + var manifestName = array[i, num++]; + var text2 = array[i, num++]; + var assetName = array[i, num++]; + aibuItem.SetObj(CommonLib.LoadAsset(text2, assetName, true, manifestName)); + //this.flags.hashAssetBundle.Add(text2); + var text3 = array[i, num++]; + var isSilhouetteChange = array[i, num++] == "1"; + var flag = array[i, num++] == "1"; + if (!text3.IsNullOrEmpty()) + { + aibuItem.objBody = aibuItem.obj.transform.FindLoop(text3); + if (aibuItem.objBody) + { + aibuItem.renderBody = aibuItem.objBody.GetComponent(); + if (flag) + { + aibuItem.mHand = aibuItem.renderBody.material; + + } + } + } + aibuItem.isSilhouetteChange = isSilhouetteChange; + text3 = array[i, num++]; + if (!text3.IsNullOrEmpty()) + { + aibuItem.objSilhouette = aibuItem.obj.transform.FindLoop(text3); + if (aibuItem.objSilhouette) + { + aibuItem.renderSilhouette = aibuItem.objSilhouette.GetComponent(); + aibuItem.mSilhouette = aibuItem.renderSilhouette.material; + aibuItem.renderSilhouette.enabled = false; + aibuItem.SetHandColor(new Color(0.960f, 0.887f, 0.864f, 1.000f)); + if (Material == null) + Material = aibuItem.renderSilhouette.material; + } + } + int.TryParse(array[i, num++], out num2); + aibuItem.SetIdObj(num2); + int.TryParse(array[i, num++], out num2); + aibuItem.SetIdUse(num2); + if (aibuItem.obj) + { + //EliminateScale[] componentsInChildren = aibuItem.obj.GetComponentsInChildren(true); + //if (componentsInChildren != null && componentsInChildren.Length != 0) + //{ + // componentsInChildren[componentsInChildren.Length - 1].LoadList(aibuItem.id); + //} + var components = aibuItem.obj.transform.GetComponentsInChildren(true); + foreach (var component in components) + { + //component.enabled = false; + UnityEngine.Component.Destroy(component); + } + aibuItem.SetAnm(aibuItem.obj.GetComponent()); + //aibuItem.obj.SetActive(false); + //aibuItem.obj.transform.SetParent(VR.Manager.transform, false); + } + aibuItem.pathSEAsset = array[i, num++]; + aibuItem.nameSEFile = array[i, num++]; + aibuItem.saveID = int.Parse(array[i, num++]); + aibuItem.isVirgin = (array[i, num++] == "1"); + aibuItem.obj.SetActive(false); + } + } +#else + // CopyPaste from the game. + internal static readonly List loadLists = + [ + "h/list/00.unity3d", + "h/list/01.unity3d", + "h/list/30.unity3d", + "h/list/31.unity3d", + "h/list/60.unity3d", + "h/list/70.unity3d", + "h/list/71.unity3d", + "h/list/81.unity3d", + "h/list/91.unity3d", + "h/list/bfwapartment1.unity3d", + "h/list/hs2suite.unity3d", + "h/list/mmbath.unity3d", + "h/list/mmhouse01.unity3d", + "h/list/poolmap.unity3d", + "h/list/sakuraroom.unity3d", + ]; + + internal static bool BundleCheck(string path, string targetName) + { + bool result = false; + string[] allAssetName = AssetBundleCheck.GetAllAssetName(path, false, "", false); + for (int i = 0; i < allAssetName.Length; i++) + { + if (allAssetName[i].Compare(targetName, true)) + { + result = true; + break; + } + } + return result; + } + + protected void LoadAssets() + { + // KKS HandCtrl. + + string text = "AibuItemObject"; + foreach (string text2 in loadLists) + { + if (AssetBundleCheck.IsFile(text2, text) && (AssetBundleCheck.IsSimulation || BundleCheck(text2, text))) + { + AibuItemObjectData aibuItemObjectData = CommonLib.LoadAsset(text2, text, true, "", false); + AssetBundleManager.UnloadAssetBundle(text2, true, null, false); + if (!(aibuItemObjectData == null)) + { + foreach (AibuItemObjectData.Param param in aibuItemObjectData.param) + { + if (!_loadedAssetsList.TryGetValue(param.id, out var aibuItem)) + { + _loadedAssetsList.Add(param.id, new AibuItem()); + aibuItem = _loadedAssetsList[param.id]; + } + aibuItem.SetID(param.id); + aibuItem.SetObj(CommonLib.LoadAsset(param.bundle, param.asset, true, param.manifest, true)); + //this.flags.hashAssetBundle.Add(param.bundle); + if (!param.nomalObj.IsNullOrEmpty()) + { + aibuItem.objBody = aibuItem.obj.transform.FindLoop(param.nomalObj); + if (aibuItem.objBody) + { + aibuItem.renderBody = aibuItem.objBody.GetComponent(); + //aibuItem.meshRenderBody = aibuItem.objBody.GetComponent(); + if (param.isMaterialGet) + { + if (aibuItem.renderBody != null) + { + aibuItem.mHand = aibuItem.renderBody.material; + } + //else if (aibuItem.meshRenderBody != null) + //{ + // aibuItem.mHand = aibuItem.meshRenderBody.material; + //} + } + } + } + aibuItem.isSilhouetteChange = param.isSilhouetteChange; + if (!param.objSilhouette.IsNullOrEmpty()) + { + aibuItem.objSilhouette = aibuItem.obj.transform.FindLoop(param.objSilhouette); + if (aibuItem.objSilhouette) + { + aibuItem.renderSilhouette = aibuItem.objSilhouette.GetComponent(); + //aibuItem.meshRenderSilhouette = aibuItem.objSilhouette.GetComponent(); + if (aibuItem.renderSilhouette != null) + { + aibuItem.mSilhouette = aibuItem.renderSilhouette.material; + } + //else if (aibuItem.meshRenderSilhouette != null) + //{ + // aibuItem.mSilhouette = aibuItem.meshRenderSilhouette.material; + //} + aibuItem.renderSilhouette.enabled = false; + aibuItem.SetHandColor(new Color(0.960f, 0.887f, 0.864f, 1.000f)); + if (!Material) + Material = aibuItem.renderSilhouette.material; + } + } + aibuItem.SetIdObj(param.objKind); + aibuItem.SetIdUse(param.setKind); + if (aibuItem.obj != null) + { + //EliminateScale[] componentsInChildren = aibuItem.obj.GetComponentsInChildren(true); + //if (componentsInChildren != null && componentsInChildren.Length != 0) + //{ + // componentsInChildren[componentsInChildren.Length - 1].LoadList(aibuItem.id); + //} + foreach (var component in aibuItem.obj.transform.GetComponentsInChildren(true)) + { + UnityEngine.Component.Destroy(component); + } + aibuItem.SetAnm(aibuItem.obj.GetComponent()); + aibuItem.obj.SetActive(false); + } + aibuItem.pathSEAsset = param.bundleSE; + aibuItem.nameSEFile = param.nameSE; + aibuItem.saveID = param.saveID; + aibuItem.isVirgin = param.isVirgin; + } + } + } + } + } +#endif + + //protected virtual void LateUpdate() + //{ + // _lastAnchorPos = _anchor.position; + //} + + internal void SetItemRenderer(bool show) + { + if (_activeItem.aibuItem == null) return; + _activeItem.aibuItem.objBody.GetComponent().enabled = show; + } + } +} diff --git a/Shared/Holders/TongueHolder.cs b/Shared/Holders/TongueHolder.cs new file mode 100644 index 0000000..4355a19 --- /dev/null +++ b/Shared/Holders/TongueHolder.cs @@ -0,0 +1,183 @@ +//using KK_VR.Features; +//using KK_VR.Fixes; +//using Studio; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Runtime.InteropServices; +//using System.Text; +//using UnityEngine; +//using VRGIN.Controls; +//using VRGIN.Core; +//using Random = UnityEngine.Random; + +//namespace KK_VR.Holders +//{ +// internal class TongueHolder : Holder +// { +// private void SetItems(GameObject gameObject) +// { +// _anchor = gameObject.transform; +// //_anchor.SetParent(VR.Manager.transform, false); +// _rigidBody = _anchor.gameObject.AddComponent(); +// _rigidBody.useGravity = false; +// _rigidBody.freezeRotation = true; +// //_audioSource = _anchor.gameObject.AddComponent(); + + +// //_activeItem = new ItemType( +// // _loadedAssetsList[4], _defaultAnimParamList[4] +// // ); +// //SetColliders(); +// //AddDynamicBones(); +// ActivateItem(); +// } +// private void ActivateItem() +// { +// _activeItem.rootPoint.SetParent(_anchor, false); +// _activeItem.rootPoint.gameObject.SetActive(true); +// _anchor.SetParent(VR.Camera.Head, false); +// _anchor.localPosition = _activeItem.animParam.positionOffset; +// _anchor.localScale = Util.Divide(Vector3.Scale(Vector3.one, _anchor.localScale), _anchor.lossyScale); +// //_anchor.SetPositionAndRotation(VR.Camera.Head.TransformPoint(_activeItem.positionOffset), VR.Camera.Head.rotation); +// //_activeItem.rootPoint.localScale = Util.Divide(Vector3.Scale(Vector3.one, _activeItem.rootPoint.localScale), _activeItem.rootPoint.lossyScale); +// //SetStartLayer(); +// } +// private void DeactivateItem() +// { +// _activeItem.rootPoint.gameObject.SetActive(false); +// //_activeItem.rootPoint.SetParent(VR.Manager.transform, false); +// //StopSE(); +// } + +// //private void AddDynamicBones() +// //{ +// // var gameObjectList = _activeItem.aibuItem.obj.GetComponentsInChildren(includeInactive: true) +// // .Where(t => t.name.Equals("cf_j_tang_04", StringComparison.Ordinal)) +// // .Select(t => t.gameObject); + +// // VRBoop.InitDB(gameObjectList); +// //} + +// private bool _lick; +// private float _timestamp; +// private int _newLayer; +// private float _layerWeight; +// private Animator _anm; +// private bool _active; +// private float _intensity; +// private Quaternion _rotTarget = Quaternion.identity; +// private Quaternion _rotOffset = Quaternion.identity; +// /// +// /// Can be in -1 to 1 range for retracted/extended states. +// /// +// private float _state; + +// private readonly Vector3 _posOffset = new(0f, 0f, 0.05f); + +// internal void StartLick() +// { +// _lick = true; +// _timestamp = Time.time + 3f + 6f * Random.value; +// _newLayer = _activeItem.animParam.layers[Random.Range(0, _activeItem.animParam.layers.Length)]; +// } +// private void Enable() +// { + +// } +// //private void Lick() +// //{ +// // if (_state < 1f) +// // { +// // _state += Time.deltaTime; +// // _activeItem.positionOffset = _posOffset * _state; +// // } +// // else if (_intensity > 0f) +// // { +// // if (_rotOffset == Quaternion.identity) +// // { + +// // _rotOffset = Quaternion.Inverse(_rotOffset) * Quaternion.Euler(Random.Range(-30f, 30f), Random.Range(-30f, 30f), 0f); +// // _rotTarget = Quaternion.Inverse(_rotTarget) * + +// // } +// // } +// //} + +// //private void DoLick() +// //{ +// // if (_active) +// // { +// // switch (_state) +// // { +// // case State.Disabled: +// // } + +// // } + + +// // _anm.SetLayerWeight(_newLayer, _layerWeight); +// // _anm.SetLayerWeight(_activeItem.layer, timer); + + + +// // if (_timestamp < Time.time) +// // { +// // _newLayer = _activeItem.availableLayers[Random.Range(0, _activeItem.availableLayers.Length)]; +// // //_newLayer = _newLayer == newLayer ? +// // // _activeItem.availableLayers[(Array.IndexOf(_activeItem.availableLayers, _newLayer) + 1) % _activeItem.availableLayers.Length] +// // // : newLayer; +// // _timestamp = Time.time + 3f + 6f * Random.value; +// // } + +// //} + + + +// //public static void TestLayer(bool increase, bool skipTransition = false) +// //{ +// // var item = controllersDic[0][0]; + +// // var anm = item.aibuItem.anm; +// // var oldLayer = item.layer; +// // var oldIndex = Array.IndexOf(item.availableLayers, oldLayer); +// // var newIndex = increase ? (oldIndex + 1) % item.availableLayers.Length : oldIndex <= 0 ? item.availableLayers.Length - 1 : oldIndex - 1; +// // var newLayer = item.availableLayers[newIndex]; + +// // //var newRotationOffset = newLayer == 13 || newLayer == 15 ? Quaternion.Euler(0f, 0f, 180f) : Quaternion.identity;// Quaternion.Euler(-90f, 0f, 0f); + +// // if (skipTransition) +// // { +// // anm.SetLayerWeight(newLayer, 1f); +// // anm.SetLayerWeight(oldLayer, 0f); +// // item.layer = newLayer; +// // } +// // else +// // { +// // KoikatuInterpreter.Instance.StartCoroutine(ChangeTongueCo(item, anm, oldLayer, newLayer)); +// // } +// // //VRPlugin.Logger.LogDebug($"TestLayer:{newLayer}"); + +// //} +// //private static IEnumerator ChangeTongueCo(ItemType item, Animator anm, int oldLayer, int newLayer) +// //{ +// // var timer = 0f; +// // var stop = false; +// // //var initRotOffset = item.rotationOffset; +// // while (!stop) +// // { +// // timer += Time.deltaTime * 2f; +// // if (timer > 1f) +// // { +// // timer = 1f; +// // stop = true; +// // } +// // //item.rotationOffset = Quaternion.Lerp(initRotOffset, newRotationOffset, timer); +// // anm.SetLayerWeight(newLayer, timer); +// // anm.SetLayerWeight(oldLayer, 1f - timer); +// // yield return null; +// // } +// // item.layer = newLayer; +// //} +// } +//} diff --git a/Shared/IK/Amplifier.cs b/Shared/IK/Amplifier.cs new file mode 100644 index 0000000..623ac5e --- /dev/null +++ b/Shared/IK/Amplifier.cs @@ -0,0 +1,11 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; + +//namespace KK_VR.IK +//{ +// internal class Amplifier +// { +// } +//} diff --git a/Shared/IK/AnimLoaderHelper.cs b/Shared/IK/AnimLoaderHelper.cs new file mode 100644 index 0000000..6424e0a --- /dev/null +++ b/Shared/IK/AnimLoaderHelper.cs @@ -0,0 +1,160 @@ +using Illusion.Component.Correct; +using Illusion.Component.Correct.Process; +using KK_VR.Grasp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace KK_VR.IK +{ + // AnimationLoader doesn't implement MotionIK data, and as result game doesn't set up FBBIK properly. + // We set it up to salvage some references and abandon after. Have to do this after every position change on postfix. + // Fixing it properly would require filling all the motion data by hand and storing with plugin assets, while not that big of an endeavor, + // it only responsible for offsets from attachment points, and VR hardly cares about it, we have Grasp and soon VRIK. + + /// + /// Fixes IK on animations added by AnimationLoader plugin. + /// Doesn't add MotionIK data i.e. offsets for different body sizes + /// + public class AnimLoaderHelper + { + internal static void FixExtraAnim(ChaControl chara, List bodyPartList) + { + for (var i = 5; i < 9; i++) + { + var bodyPart = bodyPartList[i]; + if (bodyPart.baseData.bone != null) return; + + var ikBeforeProcess = bodyPart.baseData.gameObject.GetComponent(); + if (ikBeforeProcess != null) + { + bodyPart.baseData.bone = chara.objAnim.transform.Find(cf_pv_bones_efTargets[i - 5]); + bodyPart.baseData.enabled = true; + ikBeforeProcess.enabled = true; + ikBeforeProcess.type = BaseProcess.Type.Sync; + } + var bendGoal = bodyPart.chain.bendConstraint.bendGoal; + var baseData = bendGoal.GetComponent(); + ikBeforeProcess = bendGoal.GetComponent(); + if (baseData != null && ikBeforeProcess != null) + { + baseData.bone = chara.objAnim.transform.Find(cf_pv_bones_bendGoals[i - 5]); + baseData.enabled = true; + ikBeforeProcess.enabled = true; + ikBeforeProcess.type = BaseProcess.Type.Sync; + } + } + } + + public static void FindMissingBones(RootMotion.FinalIK.FullBodyBipedIK ik) + { + // Limbs only. + for (var i = 5; i < 9; i++) + //for (var i = 0; i < ik.solver.effectors.Length; i++) + { + var target = ik.solver.effectors[i].target; + //if (target == null) + //{ + // ik.solver.effectors[i].target = ik.transform.Find(cf_t_bones_efTargets[i]); + //} + // We want only limbs. + if (target != null) + //if (target != null && i > 4) + { + // Our IK anchor. Look at its parent instead. + if (target.name.StartsWith("ik_", StringComparison.Ordinal)) + { + target = target.parent; + } + if (target != null && target.name.StartsWith("cf_t", StringComparison.Ordinal)) + { + var baseData = target.GetComponent(); + var ikBeforeProcess = target.GetComponent(); + if (baseData != null && ikBeforeProcess != null) + { + if (baseData.bone == null) + { + baseData.bone = ik.transform.Find(cf_pv_bones_efTargets[i - 5]); + } + //VRPlugin.Logger.LogWarning($"FindMissingBones[{i}]"); + //baseData.pos = ik.transform.InverseTransformDirection(ik.solver.effectors[i].bone.position - baseData.bone.position); + baseData.enabled = true; + ikBeforeProcess.enabled = true; + ikBeforeProcess.type = BaseProcess.Type.Sync; + } + } + } + } + // Set bend constraint bones too. + // 1st chain is body, nothing there. + for (var i = 1; i < ik.solver.chain.Length; i++) + { + var bendGoal = ik.solver.chain[i].bendConstraint.bendGoal; + // Game should do this. + //if (bendGoal == null) + //{ + // bendGoal = ik.transform.Find(cf_t_bones_bendGoals[i - 1]); + //} + if (bendGoal != null) + { + var baseData = bendGoal.GetComponent(); + var ikBeforeProcess = bendGoal.GetComponent(); + if (baseData != null && ikBeforeProcess != null) + { + if (baseData.bone == null) + { + baseData.bone = ik.transform.Find(cf_pv_bones_bendGoals[i - 1]); + } + baseData.enabled = true; + ikBeforeProcess.enabled = true; + ikBeforeProcess.type = BaseProcess.Type.Sync; + } + } + } + } + private static readonly List cf_pv_bones_efTargets = + [ + // No clue what moves those bones. They are offset through MotionIK though. + // They move separately with the body, on some controllers/states don't move at all, and that's all under default anim controller. + + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_hand_L", + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_hand_R", + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_leg_L", + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_leg_R" + ]; + //private static readonly List cf_t_bones_efTargets = + // [ + // // We have to have them all on corresponding effector.target with their baseData when animator changes state, + // // otherwise the native code will be out for a spanking. + + // "cf_t_root/cf_t_hips", + // "cf_t_root/cf_t_hips/cf_t_shoulder_L", + // "cf_t_root/cf_t_hips/cf_t_shoulder_R", + // "cf_t_root/cf_t_hips/cf_t_waist_L", + // "cf_t_root/cf_t_hips/cf_t_waist_R", + + // "cf_t_root/cf_t_hand_L", + // "cf_t_root/cf_t_hand_R", + // "cf_t_root/cf_t_leg_L", + // "cf_t_root/cf_t_leg_R" + // ]; + + //private static readonly List cf_t_bones_bendGoals = + // [ + // "cf_t_root/cf_t_elbo_L", + // "cf_t_root/cf_t_elbo_R", + // "cf_t_root/cf_t_knee_L", + // "cf_t_root/cf_t_knee_R" + // ]; + + private static readonly List cf_pv_bones_bendGoals = + [ + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_elbo_L", + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_elbo_R", + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_knee_L", + "cf_j_root/cf_n_height/cf_pv_root/cf_pv_knee_R" + ]; + } +} diff --git a/Shared/IK/BeforeIK.cs b/Shared/IK/BeforeIK.cs new file mode 100644 index 0000000..c418945 --- /dev/null +++ b/Shared/IK/BeforeIK.cs @@ -0,0 +1,69 @@ +using KK_VR.Fixes; +using KK_VR.Interactors; +using System; +using System.Collections; +using System.Collections.Generic; +using UnityEngine; + +namespace KK_VR.IK +{ + /// + /// Component that assumes orientation of the target bone just before IK solver reads it. + /// + [DefaultExecutionOrder(9900)] + public class BeforeIK : MonoBehaviour + { + private Transform _bone; + /// + /// Creates new GameObject with this component, initiates it + /// and attaches to "cf_t_root" bone of particular chara. + /// + internal static Transform CreateObj(string name, ChaControl chara, Transform targetBone) + { + + var beforeIKObj = new GameObject("ik_b4_" + name).transform; + beforeIKObj.SetPositionAndRotation(targetBone.position, targetBone.rotation); + beforeIKObj.parent = chara.transform.Find("BodyTop/p_cf_body_bone/cf_t_root"); + beforeIKObj.gameObject.AddComponent().Init(targetBone); + return beforeIKObj; + } + internal static Transform CreateObj(string name, ChaControl chara, Transform targetBone, Transform targetRotation) + { + + var beforeIKObj = new GameObject("ik_b4_" + name).transform; + beforeIKObj.SetPositionAndRotation(targetBone.position, targetBone.rotation); + beforeIKObj.parent = chara.transform.Find("BodyTop/p_cf_body_bone/cf_t_root"); + beforeIKObj.gameObject.AddComponent().Init(targetBone); + return beforeIKObj; + } + //private Vector3 _offsetPos; + + // Default quaternion isn't identity. + //private Quaternion _offsetRot = Quaternion.identity; + public void Init(Transform bone) + { + _bone = bone; + } + //public void SetOffsets(Vector3 offsetPosition, Quaternion offsetRotation) + //{ + // _offsetPos = offsetPosition; + // _offsetRot = offsetRotation; + //} + //public void SetDebug(float x, float y, float z) + //{ + // _offsetRot = Quaternion.Euler(x, y, z); + //} + //public void Retarget(Transform bone) => _bone = bone; + + //public void UpdateTransform() + //{ + // if (_bone == null || _chara == null) return; + // transform.SetPositionAndRotation(_bone.TransformPoint(_offsetPos), _bone.rotation * _offsetRot); + //} + public void LateUpdate() + { + if (_bone == null) return; + transform.SetPositionAndRotation(_bone.position, _bone.rotation); + } + } +} diff --git a/Shared/IK/FBBIK.cs b/Shared/IK/FBBIK.cs new file mode 100644 index 0000000..e2e88dd --- /dev/null +++ b/Shared/IK/FBBIK.cs @@ -0,0 +1,158 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace KK_VR.IK +{ + /// + /// Copies config from old FullBodyBipedIK to the new one + /// + internal static class FBBIK + { + // IKSolver exec order is 9999 + // NeckLook is 11000. + // EyeLook is 10800 + internal static KK.RootMotion.FinalIK.FullBodyBipedIK UpdateFBIK(ChaControl chara) + { + var newFbik = chara.objAnim.GetComponent(); + if (newFbik != null) return newFbik; + + var oldFbik = chara.objAnim.GetComponent(); + if (oldFbik == null) return null; + newFbik = chara.objAnim.AddComponent(); + + newFbik.references.root = oldFbik.references.root; + newFbik.references.pelvis = oldFbik.references.pelvis; + newFbik.references.leftThigh = oldFbik.references.leftThigh; + newFbik.references.leftCalf = oldFbik.references.leftCalf; + newFbik.references.leftFoot = oldFbik.references.leftFoot; + newFbik.references.rightThigh = oldFbik.references.rightThigh; + newFbik.references.rightCalf = oldFbik.references.rightCalf; + newFbik.references.rightFoot = oldFbik.references.rightFoot; + newFbik.references.leftUpperArm = oldFbik.references.leftUpperArm; + newFbik.references.leftForearm = oldFbik.references.leftForearm; + newFbik.references.leftHand = oldFbik.references.leftHand; + newFbik.references.rightUpperArm = oldFbik.references.rightUpperArm; + newFbik.references.rightForearm = oldFbik.references.rightForearm; + newFbik.references.rightHand = oldFbik.references.rightHand; + newFbik.references.head = chara.objHeadBone.transform.parent; + //newFbik.references.head = oldFbik.references.head; + newFbik.references.spine = oldFbik.references.spine; + //newFbik.references.spine = + // [ + // oldFbik.references.spine[1], + // oldFbik.references.spine[1].Find("cf_j_spine03"), + // oldFbik.references.spine[2] + // ]; + newFbik.SetReferences(newFbik.references, oldFbik.solver.rootNode); // newFbik.references.spine[0]); // oldFbik.solver.rootNode); + + for (var i = 0; i < newFbik.solver.effectors.Length; i++) + { + newFbik.solver.effectors[i].target = oldFbik.solver.effectors[i].target; + newFbik.solver.effectors[i].positionWeight = oldFbik.solver.effectors[i].positionWeight; + newFbik.solver.effectors[i].rotationWeight = oldFbik.solver.effectors[i].rotationWeight; + } + for (var i = 0; i < newFbik.solver.chain.Length; i++) + { + newFbik.solver.chain[i].bendConstraint.bendGoal = oldFbik.solver.chain[i].bendConstraint.bendGoal; + newFbik.solver.chain[i].bendConstraint.weight = oldFbik.solver.chain[i].bendConstraint.weight; + newFbik.solver.chain[i].reach = oldFbik.solver.chain[i].reach; + newFbik.solver.chain[i].pull = oldFbik.solver.chain[i].pull; + newFbik.solver.chain[i].pin = oldFbik.solver.chain[i].pin; + newFbik.solver.chain[i].push = oldFbik.solver.chain[i].push; + + } + oldFbik.enabled = false; + newFbik.fixTransforms = true; + newFbik.solver.pullBodyHorizontal = 0.5f; + chara.objTop.SetActive(false); + chara.objTop.SetActive(true); + return newFbik; + } + + internal static KK.RootMotion.FinalIK.FBBIKHeadEffector CreateHeadEffector(ChaControl chara, Transform anchor) + { + // We don't use actual root-head bone, as neck-aim script gets in the way there on LateUpdate/FixedUpdate, + // instead we use direct descendant. While not mazing-amazing, script is just fine, i'd rather not tinker/rewrite it. + + var newFbik = chara.objAnim.GetComponent(); + var oldFbik = chara.objAnim.GetComponent(); + + // Head effector is a target in of itself, therefore we use it as an anchor. + //var headBone = new GameObject("ank_ik_head"); + //headBone.transform.SetParent(newFbik.references.head, false); + + var headEffector = anchor.gameObject.AddComponent(); + headEffector.ik = newFbik; + headEffector.positionWeight = 0.1f; + headEffector.rotationWeight = 1f; + headEffector.bodyWeight = 0.6f; + headEffector.thighWeight = 0.5f; + headEffector.bodyClampWeight = 0f; + headEffector.headClampWeight = 0f; + headEffector.bendWeight = 1f; + + + // The most important thing. + // Whole behavior is dictated by it. + // Must have picks: + // cf_j_waist01, + // + headEffector.bendBones = + [ + // cf_j_waist01 a game changer + // Can be a hit, or requires a bit of adjustment to translate from miss into an even bigger hit, depends on the animator. AnimStates can be generalized. + //new() { transform = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01"), weight = 1f }, + + //new() { transform = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02"), weight = 0.5f }, + + // cf_j_spine01 + new() { transform = oldFbik.references.spine[0], weight = 0.6f }, // 0.8f + + // cf_j_spine02 + new() { transform = oldFbik.references.spine[1], weight = 0.8f }, + + //new() { transform = oldFbik.references.spine[1].Find("cf_j_spine03"), weight = 0.9f }, + + // cf_j_neck + new() { transform = oldFbik.references.spine[2], weight = 1f } + ]; + + headEffector.CCDWeight = 0.5f; + headEffector.stretchBones = + [ + //chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01"), + //chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02"), + oldFbik.references.spine[0], + oldFbik.references.spine[1], + //oldFbik.references.spine[1].Find("cf_j_spine03"), + oldFbik.references.spine[2] + ]; + headEffector.postStretchWeight = 0.2f; + headEffector.maxStretch = 0.07f; + headEffector.CCDBones = + [ + // cf_j_waist01 - solid pick + //chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01"), + + //// cf_j_waist02 - good pick when together with cf_j_waist01. + //chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02"), + + // cf_j_spine01 + oldFbik.references.spine[0], + + // cf_j_spine02 + oldFbik.references.spine[1], + + // cf_j_spine03 + //oldFbik.references.spine[1].Find("cf_j_spine03"), + + // cf_j_neck + oldFbik.references.spine[2] + ]; + return headEffector; + } + } +} diff --git a/Shared/IK/LookAt.cs b/Shared/IK/LookAt.cs new file mode 100644 index 0000000..cc319ba --- /dev/null +++ b/Shared/IK/LookAt.cs @@ -0,0 +1,45 @@ +using KK.RootMotion.FinalIK; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; +using VRGIN.Core; + +namespace KK_VR.IK +{ + // Requires proper implementation with added scripts to manage it in SensibleH (WIP). + internal class LookAt + { + internal static LookAtController SetupLookAtIK(ChaControl chara) + { + return null; + var fbbik = chara.objAnim.GetComponent(); + if (fbbik == null) return null; + var lookAt = chara.objAnim.AddComponent(); + Transform[] spine = + [ + fbbik.references.spine[0], + fbbik.references.spine[1], + fbbik.references.spine[1].Find("cf_j_spine03"), + fbbik.references.spine[2] + ]; + lookAt.solver.SetChain(spine, fbbik.references.head, null, fbbik.references.root); + lookAt.solver.bodyWeight = 0.6f; + lookAt.solver.headWeight = 0.8f; + var lookAtController = chara.objAnim.AddComponent(); + lookAtController.ik = lookAt; + lookAtController.weightSmoothTime = 1f; + lookAtController.targetSwitchSmoothTime = 1f; + lookAtController.maxRadiansDelta = 0.25f; + lookAtController.maxMagnitudeDelta = 0.25f; + lookAtController.slerpSpeed = 1f; + lookAtController.maxRootAngle = 180f; + + //lookController.target = VR.Camera.Head; + //_spine03 = fbbik.references.spine[1].Find("cf_j_spine03"); + lookAtController.target = VR.Camera.Head; //hFlag.ctrlCamera.transform; + return lookAtController; + } + } +} diff --git a/Shared/IK/NoPosBeforeIK.cs b/Shared/IK/NoPosBeforeIK.cs new file mode 100644 index 0000000..c8043fd --- /dev/null +++ b/Shared/IK/NoPosBeforeIK.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Text; +using UnityEngine; + +namespace KK_VR.IK +{ + /// + /// Component that assumes rotation of the target bone just before IK solver reads it. + /// + [DefaultExecutionOrder(9900)] + internal class NoPosBeforeIK : MonoBehaviour + { + private Transform _bone; + internal void Init(Transform bone) + { + _bone = bone; + } + internal void LateUpdate() + { + if (_bone == null) return; + transform.rotation = _bone.rotation; + } + } +} diff --git a/Shared/IK/OffsetEffector.cs b/Shared/IK/OffsetEffector.cs new file mode 100644 index 0000000..11b637e --- /dev/null +++ b/Shared/IK/OffsetEffector.cs @@ -0,0 +1,31 @@ +//using UnityEngine; +//using System.Collections; +//using KK.RootMotion.FinalIK; +//using System.Collections.Generic; +//using KK_VR.Grasp; +//using RootMotion.FinalIK; + +//namespace KK_VR.IK +//{ + +// /// +// /// Custom positionOffset effector for FBBIK. +// /// +// internal class OffsetEffector : MonoBehaviour +// { +// // Main link. +// private KK.RootMotion.FinalIK.IKEffector _effector; +// //private readonly float weight = 1f; + +// internal void Init(KK.RootMotion.FinalIK.IKEffector effector) +// { +// //_ik = ik; +// _effector = effector; +// //_ik.solver.OnPreUpdate += ModifyOffset; +// } +// private void LateUpdate() +// { +// _effector.positionOffset += transform.position - _effector.bone.position; +// } +// } +//} diff --git a/Shared/IK/OffsetManipulator.cs b/Shared/IK/OffsetManipulator.cs new file mode 100644 index 0000000..0350377 --- /dev/null +++ b/Shared/IK/OffsetManipulator.cs @@ -0,0 +1,48 @@ +using KK_VR.Grasp; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using UnityEngine; + +namespace KK_VR.IK +{ + // Simplified version from FinalIK scripts + internal abstract class OffsetManipulator : OffsetModifier + { + protected class EffectorLink + { + internal EffectorLink(KK.RootMotion.FinalIK.IKEffector _effector, Vector3 _offset, float _weight) + { + effector = _effector; + defaultWeight = _weight; + weight = _weight; + offset = _offset; + } + internal readonly KK.RootMotion.FinalIK.IKEffector effector; + internal readonly Vector3 offset; + internal readonly float defaultWeight; + internal float weight; + internal void MultiplyWeight(float multiplier) => weight = Mathf.Clamp01(weight * multiplier); + } + protected readonly List _linkList = []; + + protected void OnInit(KK.RootMotion.FinalIK.FullBodyBipedIK ik) + { + _ik = ik; + _ik.solver.OnPreUpdate += ModifyOffset; + } + internal void Add(BodyPart bodyPart, float weight) + { + _linkList.Add(new EffectorLink(bodyPart.effector, this.transform.InverseTransformPoint(bodyPart.anchor.position), weight)); + } + protected override void OnModifyOffset() + { + foreach (var link in _linkList) + { + link.effector.positionOffset += (transform.TransformPoint(link.offset) - (link.effector.bone.position + link.effector.positionOffset)) * link.weight; + } + } + + } +} diff --git a/Shared/IK/OffsetModifier.cs b/Shared/IK/OffsetModifier.cs new file mode 100644 index 0000000..c8d8704 --- /dev/null +++ b/Shared/IK/OffsetModifier.cs @@ -0,0 +1,122 @@ +using UnityEngine; +using System.Collections; +using KK.RootMotion.FinalIK; +using RootMotion.FinalIK; + +namespace KK_VR.IK +{ + // Simplified version from FinalIK scripts + /// + /// Base class for all FBBIK effector positionOffset modifiers. Works with animatePhysics, safe delegates, offset limits. + /// + public abstract class OffsetModifier : MonoBehaviour + { + + ///// + ///// Limiting effector position offsets + ///// + //[System.Serializable] + //public class OffsetLimits + //{ + // public KK.RootMotion.FinalIK.FullBodyBipedEffector effector; + // /// + // /// Spring force, if zero then this is a hard limit, if not, offset can exceed the limit. + // /// + // public float spring = 0f; + // /// + // /// Axes to limit the offset on + // /// + // public bool x, y, z; + // /// + // /// Limits + // /// + // public float minX, maxX, minY, maxY, minZ, maxZ; + + // // Apply the limit to the effector + // public void Apply(KK.RootMotion.FinalIK.IKEffector e, Quaternion rootRotation) + // { + // Vector3 offset = Quaternion.Inverse(rootRotation) * e.positionOffset; + + // if (spring <= 0f) + // { + // // Hard limits + // if (x) offset.x = Mathf.Clamp(offset.x, minX, maxX); + // if (y) offset.y = Mathf.Clamp(offset.y, minY, maxY); + // if (z) offset.z = Mathf.Clamp(offset.z, minZ, maxZ); + // } + // else + // { + // // Soft limits + // if (x) offset.x = SpringAxis(offset.x, minX, maxX); + // if (y) offset.y = SpringAxis(offset.y, minY, maxY); + // if (z) offset.z = SpringAxis(offset.z, minZ, maxZ); + // } + + // // Apply to the effector + // e.positionOffset = rootRotation * offset; + // } + + // // Just math for limiting floats + // private float SpringAxis(float value, float min, float max) + // { + // if (value > min && value < max) return value; + // if (value < min) return Spring(value, min, true); + // return Spring(value, max, false); + // } + + // // Spring math + // private float Spring(float value, float limit, bool negative) + // { + // float illegal = value - limit; + // float s = illegal * spring; + + // if (negative) return value + Mathf.Clamp(-s, 0, -illegal); + // return value - Mathf.Clamp(s, 0, illegal); + // } + //} + + protected private KK.RootMotion.FinalIK.FullBodyBipedIK _ik; + + // not using Time.deltaTime or Time.fixedDeltaTime here, because we don't know if animatePhysics is true or not on the character, so we have to keep track of time ourselves. + //protected float deltaTime { get { return Time.time - lastTime; } } + //protected float deltaTime { get { return Time.deltaTime; } } + protected abstract void OnModifyOffset(); + + //protected float lastTime; + + + // The main function that checks for all conditions and calls OnModifyOffset if they are met + protected void ModifyOffset() + { + if (gameObject.activeSelf) + { + OnModifyOffset(); + } + //if (weight <= 0f) return; + //if (ik == null) return; + //weight = Mathf.Clamp01(weight); + //if (deltaTime <= 0f) return; + + + //lastTime = Time.time; + } + + //protected void ApplyLimits(OffsetLimits[] limits) + //{ + // // Apply the OffsetLimits + // foreach (var limit in limits) + // { + // limit.Apply(ik.solver.GetEffector(limit.effector), transform.rotation); + // } + //} + + // Remove the delegate when destroyed + + + protected virtual void OnDestroy() + { + if (_ik != null) _ik.solver.OnPreUpdate -= ModifyOffset; + } + } + +} diff --git a/Shared/IK/VRIK.cs b/Shared/IK/VRIK.cs new file mode 100644 index 0000000..c256be8 --- /dev/null +++ b/Shared/IK/VRIK.cs @@ -0,0 +1,98 @@ +//using KK.RootMotion.FinalIK; +//using KK_VR.Fixes; +//using KK_VR.Handlers; +//using KK_VR.Holders; +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using VRGIN.Core; + +//namespace KK_VR.Features +//{ +// public class VRIK +// { +// public static VRIK Instance => _instance ?? new(); +// private static VRIK _instance; + + +// public void TestRun(ChaControl chara) +// { +// PrepareVRIK(chara); +// } + +// private KK.RootMotion.FinalIK.VRIK PrepareVRIK(ChaControl chara) +// { +// var ik = chara.animBody.GetComponent(); +// if (ik == null) return null; +// ik.enabled = false; +// var refs = ik.references; +// var vrik = chara.animBody.gameObject.AddComponent(); +// var vRef = vrik.references; + +// vRef.root = refs.root; +// vRef.pelvis = refs.pelvis; +// vRef.spine = refs.spine[1]; // cf_j_spine02 +// vRef.chest = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03"); +// vRef.neck = refs.spine[2]; +// vRef.head = refs.head; + +// vRef.leftShoulder = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_L/cf_j_shoulder_L"); +// vRef.leftUpperArm = refs.leftUpperArm; +// vRef.leftForearm = refs.leftForearm; +// vRef.leftHand = refs.leftHand; + +// vRef.rightShoulder = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_R/cf_j_shoulder_R"); +// vRef.rightUpperArm = refs.rightUpperArm; +// vRef.rightForearm = refs.rightForearm; +// vRef.rightHand = refs.rightHand; + +// vRef.leftThigh = refs.leftThigh; +// vRef.leftCalf = refs.leftCalf; +// vRef.leftFoot = refs.leftFoot; +// vRef.leftToes = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_L/cf_j_leg01_L/cf_j_leg03_L/cf_j_foot_L/cf_j_toes_L"); + +// vRef.rightThigh = refs.rightThigh; +// vRef.rightCalf = refs.rightCalf; +// vRef.rightFoot = refs.rightFoot; +// vRef.rightToes = chara.objBodyBone.transform.Find("cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_R/cf_j_leg01_R/cf_j_leg03_R/cf_j_foot_R/cf_j_toes_R"); +// return vrik; +// } + +// private readonly Vector3[] _handPosOffset = +// [ +// new(0f, 0f, -0.1f), +// new(0f, 0f, -0.1f) +// ]; +// private readonly Quaternion[] _handRotOffset = +// [ +// Quaternion.Euler(-30f, 90f, 0f), +// Quaternion.Euler(-30f, -90f, 0f), +// ]; +// private void SyncWithRig(KK.RootMotion.FinalIK.VRIK vrik) +// { +// var hands = HandHolder.GetHands; +// vrik.solver.leftArm.target = AddHandOffset(0, hands[0].Anchor); +// vrik.solver.rightArm.target = AddHandOffset(1, hands[1].Anchor); +// var headTarget = new GameObject("headTarget").transform; +// headTarget.SetParent(VR.Camera.Head, false); +// headTarget.localPosition = Vector3.zero; +// headTarget.localRotation = Quaternion.identity; +// vrik.solver.spine.headTarget = headTarget; + +// Util.CreatePrimitive(PrimitiveType.Sphere, new Vector3(0.05f, 0.05f, 0.05f), VR.Mode.Left.transform, Color.yellow, 0.5f); +// Util.CreatePrimitive(PrimitiveType.Sphere, new Vector3(0.05f, 0.05f, 0.05f), VR.Mode.Right.transform, Color.yellow, 0.5f); +// } + +// private Transform AddHandOffset(int index, Transform parent) +// { +// var gameObject = new GameObject("VRIK_hand_anchor"); +// gameObject.transform.SetParent(parent, false); +// // For some reason when we deal with VRGIN objects, orientation can get weird out of nowhere. +// gameObject.transform.localPosition = _handPosOffset[index]; +// gameObject.transform.localRotation = _handRotOffset[index]; +// return gameObject.transform; +// } +// } +//} diff --git a/Shared/IK/VRIKGuide.cs b/Shared/IK/VRIKGuide.cs new file mode 100644 index 0000000..b3c7f96 --- /dev/null +++ b/Shared/IK/VRIKGuide.cs @@ -0,0 +1,61 @@ +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using UnityEngine; +//using VRGIN.Controls; + +//namespace KK_VR.Features +//{ +// // Component to atleast somehow walk in VRIK. +// // Proper setup would require retargeting bunch of animations on KK body rig, +// // and that would require someone who knows their way around the blender. +// // Based on FinalIK demo. +// internal class VRIKGuide : MonoBehaviour +// { +// private Animator _animator; +// private Controller _controller; +// private Transform _root; +// private bool _run; +// private float _speed; +// private float _angleVel; +// private float _speedVel; +// private float _accelerationTime = 0.2f; + +// internal void StartMovement() +// { + +// } +// internal void OnTrigger(bool press) +// { + +// } +// //private float GetStickAngle() +// //{ +// // return +// //} + +// private void UpdatePosition() +// { + +// var xy = _controller.Input.GetAxis(); +// var deg = Mathf.Atan2(xy.y, xy.x) * Mathf.Rad2Deg + 90f; +// Move(xy.magnitude); +// } +// private void Rotate() +// { +// // Base current rotation on Head.forward + angle from joystick. +// //var angle = + +// } +// private void Move(float distance) +// { +// _speed = Mathf.SmoothDamp(_speed, _run ? 1f : 0.5f, ref _speedVel, _accelerationTime); +// distance *= _speed; + +// _animator.SetFloat("Locomotion", distance); + +// _root.position += Time.deltaTime * distance * _root.forward; +// } +// } +//} diff --git a/Shared/Interpreters/ActionSceneInterpreter.cs b/Shared/Interpreters/ActionSceneInterpreter.cs new file mode 100644 index 0000000..a9c2eeb --- /dev/null +++ b/Shared/Interpreters/ActionSceneInterpreter.cs @@ -0,0 +1,176 @@ +using UnityEngine; +using VRGIN.Core; +using StrayTech; +using KK_VR.Settings; +using KK_VR.Features; +using KK_VR.Camera; +using Manager; +using VRGIN.Controls; +using Valve.VR; +using KK_VR.Handlers; +using static VRGIN.Controls.Controller; +using System.Collections.Generic; +using KK_VR.Controls; +using ADV.Commands.Object; +using WindowsInput.Native; +using KK_VR.Holders; +using UnityEngine.SceneManagement; + +namespace KK_VR.Interpreters +{ + internal class ActionSceneInterpreter : SceneInterpreter + { + // Roaming is in a sorry state, waiting for the VRIK to rework it. + // But VRIK for this requires custom animations, + // which I yet to figure out how to retarget. + + internal static ActionScene actionScene; + + internal static Transform FakeCamera; + private GameObject _map; + private GameObject _cameraSystem; + private bool _resetCamera; + private float _originAngle; + + + internal override void OnStart() + { +#if KK + actionScene = Game.Instance.actScene; +#else + actionScene = ActionScene.instance; +#endif + HandHolder.SetKinematic(true); + + _resetCamera = true; + //ResetCamera(); + //ResetState(); + DisableCameraSystem(); + if (_settings.ShadowsOptimization == KoikatuSettings.ShadowType.Auto) + { + KoikatuInterpreter.TweakShadowSettings(KoikatuSettings.ShadowType.Average); + } + base.OnStart(); + } + + internal override void OnDisable() + { + VRLog.Info("ActionScene OnDisable"); + + HandHolder.SetKinematic(false); + //ResetState(); + EnableCameraSystem(); + } + internal override void OnSceneLoaded(UnityEngine.SceneManagement.Scene scene, LoadSceneMode mode) + { + _resetCamera = true; + } + internal override void OnUpdate() + { + if (_resetCamera) + { + ResetCamera(); + } + } + //internal override void OnUpdate() + //{ + // var map = actionScene.Map.mapRoot?.gameObject; + + // if (map != _map) + // { + // ResetCamera(); + + // VRLog.Info("! map changed."); + + // //ResetState(); + // _map = map; + // //_resetCamera = true; + // } + // //if (_resetCamera) + // //{ + // //} + // base.OnUpdate(); + //} + private void CreateFakeCamera() + { + if (FakeCamera == null) + { + FakeCamera = new GameObject("FakeCamera").transform; + FakeCamera.SetParent(MonoBehaviourSingleton.Instance.CurrentCamera.transform, worldPositionStays: false); + } + } + + + //private void ResetState() + //{ + // VRLog.Info("ActionScene ResetState"); + + // //_sceneInput.StandUp(); + // //_sceneInput.StopWalking(); + // //_resetCamera = false; + //} + + private void ResetCamera() + { + + if (actionScene.Player.chaCtrl != null + && actionScene.Player.chaCtrl.objTop != null + && actionScene.Player.chaCtrl.objTop.activeSelf) + { + _cameraSystem = MonoBehaviourSingleton.Instance.gameObject; + + // トイレなどでFPS視点になっている場合にTPS視点に戻す + _cameraSystem.GetComponent().ModeChangeForce((ActionGame.CameraMode?)ActionGame.CameraMode.TPS, true); + //scene.GetComponent().isCursorLock = false; + + // カメラをプレイヤーの位置に移動 + ((ActionSceneInput)KoikatuInterpreter.SceneInput).ResetState(); + ((ActionSceneInput)KoikatuInterpreter.SceneInput).CameraToPlayer(); + + // Something interferes rarely. + actionScene.Player.chaCtrl.visibleAll = true; + + _resetCamera = false; + VRLog.Info("ResetCamera succeeded"); + } +#if KKS + // KKS swaps VFX all the time gotta keep up, KK doesn't seem like. + VREffector.Refresh(); +#endif + + } + private void DisableCameraSystem() + { + //VRLog.Info("ActionScene HoldCamera"); + + _cameraSystem = MonoBehaviourSingleton.Instance.gameObject; + + if (_cameraSystem != null) + { + _cameraSystem.SetActive(false); + + //VRLog.Info("succeeded"); + } + } + + private void EnableCameraSystem() + { + //VRLog.Info("ActionScene ReleaseCamera"); + + if (_cameraSystem != null) + { + _cameraSystem.SetActive(true); + + //VRLog.Info("succeeded"); + } + } + + + + + + + + + } +} diff --git a/Shared/Interpreters/CustomSceneInterpreter.cs b/Shared/Interpreters/CustomSceneInterpreter.cs new file mode 100644 index 0000000..5df9b86 --- /dev/null +++ b/Shared/Interpreters/CustomSceneInterpreter.cs @@ -0,0 +1,7 @@ +namespace KK_VR.Interpreters +{ + internal class CustomSceneInterpreter : SceneInterpreter + { + + } +} diff --git a/Shared/Interpreters/Extras/IntegrationSensibleH.cs b/Shared/Interpreters/Extras/IntegrationSensibleH.cs new file mode 100644 index 0000000..a0c8e4e --- /dev/null +++ b/Shared/Interpreters/Extras/IntegrationSensibleH.cs @@ -0,0 +1,76 @@ +using HarmonyLib; +using System; +using System.Collections.Generic; +using System.Text; +using static HandCtrl; + +namespace KK_VR +{ + internal class IntegrationSensibleH + { + internal static bool active; + + // Concedes control of an aibu item. + internal static Action ReleaseItem; + + // Way too much illegitimate stuff is going on in auto caress, so we can't use normal one, + // this one among other things will refuse to do it if we are about to break the game. + internal static Action JudgeProc; + + /// + /// Uses StartsWith to find and click the button, or picks any if not specified (in this case ignores fast/slow in houshi). + /// + internal static Action ClickButton; + + // Changes current loop between Weak/Strong/Orgasm. + internal static Action ChangeLoop; + + // Disables H AutoMode, will get re-enabled from inputs/automatically depending on the settings. + internal static Action StopAuto; + + /// + /// -1 for all, (HFlag.EMode) 0...2 for specific, or anything higher(e.g. 3) for current EMode. + /// + internal static Action ChangeAnimation; + + // Prevents SensibleH side from breaking. + internal static Action OnLickStart; + + // Provides neat neck and hooks up SensibleH version of CyuVR. + internal static Action OnKissStart; + + // Neatly cleans/restores everything. + internal static Action OnKissEnd; + + // Hook to start AutoMode. + internal static Action OnUserInput; + + // Custom top of the excitement gauge to trigger orgasm, set by SensibleH dynamically. + internal static Func GetFemaleCeiling; + internal static Func GetMaleCeiling; + + internal static void Init() + { + var type = AccessTools.TypeByName("KK_SensibleH.AutoMode.LoopController"); + active = type != null; + + if (active) + { + ClickButton = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("ClickButton"))); + ChangeLoop = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("AlterLoop"))); + ChangeAnimation = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("PickAnimation"))); + StopAuto = AccessTools.MethodDelegate(AccessTools.FirstMethod(type, m => m.Name.Equals("Sleep"))); + OnUserInput = AccessTools.MethodDelegate(AccessTools.FirstMethod(type, m => m.Name.Equals("OnUserInput"))); + + type = AccessTools.TypeByName("KK_SensibleH.Caress.MoMiController"); + + ReleaseItem = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("ReleaseItem"))); + JudgeProc = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("MoMiJudgeProc"))); + OnLickStart = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("OnLickStart"))); + OnKissStart = AccessTools.MethodDelegate>(AccessTools.FirstMethod(type, m => m.Name.Equals("OnKissStart"))); + OnKissEnd = AccessTools.MethodDelegate(AccessTools.FirstMethod(type, m => m.Name.Equals("OnKissEnd"))); + + } + } + } +} diff --git a/Shared/Interpreters/Extras/MeshCollider.cs b/Shared/Interpreters/Extras/MeshCollider.cs new file mode 100644 index 0000000..5c35509 --- /dev/null +++ b/Shared/Interpreters/Extras/MeshCollider.cs @@ -0,0 +1,13 @@ +//using System; + +//namespace KK_VR +//{ +// internal class MeshCollider +// { +// //internal static void AddRascal(ChaControl chara) +// //{ +// // chara.gameObject.AddComponent(); +// //} +// } +//} + diff --git a/Shared/Interpreters/Extras/SceneExtras.cs b/Shared/Interpreters/Extras/SceneExtras.cs new file mode 100644 index 0000000..a5083a8 --- /dev/null +++ b/Shared/Interpreters/Extras/SceneExtras.cs @@ -0,0 +1,681 @@ +using ADV.Commands.Base; +using IllusionUtility.GetUtility; +using KK_VR.Fixes; +using KK_VR.Handlers; +using Manager; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Text; +using UnityEngine; +using static HandCtrl; +using Random = UnityEngine.Random; + +namespace KK_VR.Interpreters +{ + internal static class SceneExtras + { + private static Transform _dirLight; + private static Transform _oldParent; + internal static void RepositionDirLight(ChaControl chara) + { +#if KK + _dirLight = Component.FindObjectsOfType() + .Where(g => g.name.Equals("Directional Light") && g.gameObject.active) + .Select(g => g.transform) + .FirstOrDefault(); +#else + _dirLight = Component.FindObjectsOfType() + .Where(c => c.gameObject.activeSelf && c.name.Equals("Directional Light") + && c.transform.parent != null && c.transform.parent.name.Contains("Camera")) + .Select(c => c.transform) + .FirstOrDefault(); +#endif + if (_dirLight != null) + { + _oldParent = _dirLight.parent; + + // We find look rotation from base of chara to the center of the scene (0,0,0). + // Then we create rotation towards it from the chara for random degrees, and elevate it a bit. + // And place our camera at chara's head position + Vector.forward with above rotation. + // Consistent, doesn't defy logic too often, and is much better then camera directional light, that in vr makes one question own eyes. + + var lowHeight = (chara.objHeadBone.transform.position.y - chara.transform.position.y) < 0.5f; + var yDeviation = Random.Range(15f, 45f); + var xDeviation = Random.Range(15f, lowHeight ? 60f : 30f); + var lookRot = Quaternion.LookRotation(new Vector3(0f, chara.transform.position.y, 0f) - chara.transform.position); + _dirLight.parent = null;// transform.SetParent(, worldPositionStays: true); + _dirLight.position = chara.objHeadBone.transform.position + Quaternion.RotateTowards(chara.transform.rotation, lookRot, yDeviation) + * Quaternion.Euler(-xDeviation, 0f, 0f) * Vector3.forward; + _dirLight.rotation = Quaternion.LookRotation((lowHeight ? chara.objBody : chara.objHeadBone).transform.position - _dirLight.position); + } + } + internal static void ReturnDirLight() + { + if (_oldParent != null && _dirLight != null) + { + _dirLight.SetParent(_oldParent, false); + } + } + internal static void AddTalkColliders(IEnumerable charas) + { + AddColliders(charas, _talkColliders); + } + internal static void AddHColliders(IEnumerable charas) + { + AddColliders(charas, _hColliders); + } + + private static void AddColliders(IEnumerable charas, string[,] colliders) + { + foreach (var chara in charas) + { + if (chara == null) continue; + for (var i = 0; i < colliders.GetLength(0); i++) + { + var target = chara.objBodyBone.transform.Find(colliders[i, 0]); + if (target != null && target.Find(colliders[i, 2]) == null) + { + var collider = CommonLib.LoadAsset(colliders[i, 1], colliders[i, 2], true); + collider.transform.SetParent(target, false); + + // No clue about this. + AssetBundleManager.UnloadAssetBundle(colliders[i, 1], true); + } + } + } + } + + internal static void EnableDynamicBones(IEnumerable charas) + { + foreach (var chara in charas) + { + EnableDynamicBones(chara); + } + } + internal static void EnableDynamicBones(ChaControl chara) + { + if (chara == null) return; + + foreach (var db in chara.objAnim.GetComponents()) + { + db.enabled = true; + } + + } + + private static readonly string[,] _talkColliders = + { + { + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_L/cf_j_shoulder_L/cf_j_arm00_L/cf_j_forearm01_L/cf_j_hand_L", + "communication/hit_00.unity3d", + "com_hit_hand_L" + }, + { + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_d_shoulder_R/cf_j_shoulder_R/cf_j_arm00_R/cf_j_forearm01_R/cf_j_hand_R", + "communication/hit_00.unity3d", + "com_hit_hand_R" + }, + { + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_j_neck/cf_j_head", + "communication/hit_00.unity3d", + "com_hit_head" + } + }; + private static readonly string[,] _hColliders = + { + { + "cf_n_height/cf_j_hips/cf_j_spine01/cf_j_spine02/cf_j_spine03/cf_j_neck/cf_j_head/cf_s_head", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_hit_mouth" + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_kokan", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_hit_kokan", + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_d_ana", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_hit_ana", + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_hit_siri_L", + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_hit_siri_R", + }, + //{ + // "cf_j_waist02", + // "h/common/01.unity3d", + // "aibu_hit_block_koshi", + //}, + //{ + // "cf_d_bust00", + // "h/common/01.unity3d", + // "aibu_hit_block_mune", + //}, + //{ + // "cf_s_head", + // "h/common/01.unity3d", + // "aibu_hit_head", + //}, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_L/cf_j_leg01_L/cf_s_leg01_L/cf_hit_leg01_L", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_reaction_legL", + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_R/cf_j_leg01_R/cf_s_leg01_R/cf_hit_leg01_R", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_reaction_legR", + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_L/cf_d_thigh02_L/cf_s_thigh02_L/cf_hit_thigh02_L", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_reaction_thighL", + }, + { + "cf_n_height/cf_j_hips/cf_j_waist01/cf_j_waist02/cf_j_thigh00_R/cf_d_thigh02_R/cf_s_thigh02_R/cf_hit_thigh02_R", +#if KK + "h/common/00_00.unity3d", +#else + "h/common/01.unity3d", +#endif + "aibu_reaction_thighR", + } + }; + + //internal static void AddHColliders(IEnumerable charas) + //{ + // var _strAssetFolderPath = "h/list/"; + // var _file = "parent_object_base_female"; + // var text = GlobalMethod.LoadAllListText(_strAssetFolderPath, _file, null); + // if (text == string.Empty) return; + // //string[,] array; + // GlobalMethod.GetListString(text, out var array); + // var length = array.GetLength(0); + // var length2 = array.GetLength(1); + // foreach (var chara in charas) + // { + // if (chara == null) continue; + // for (int i = 0; i < length; i++) + // { + // for (int j = 0; j < length2; j += 3) + // { + // var parentName = array[i, j]; + // var assetName = array[i, j + 1]; + // var colliderName = array[i, j + 2]; + // if (parentName.IsNullOrEmpty() && assetName.IsNullOrEmpty() && colliderName.IsNullOrEmpty()) + // { + // break; + // } + // var parent = chara.objBodyBone.transform.FindLoop(parentName); + // if (parent.transform.Find(colliderName) != null) + // { + // //VRPlugin.Logger.LogDebug($"Extras:Colliders:H:AlreadyHaveOne:{colliderName}"); + // continue; + // } + // //else + // //{ + // // //VRPlugin.Logger.LogDebug($"Extras:Colliders:H:Add:{colliderName}"); + // //} + // var collider = CommonLib.LoadAsset(assetName, colliderName, true, string.Empty); + // AssetBundleManager.UnloadAssetBundle(assetName, true, null, false); + // var componentsInChildren = collider.GetComponentsInChildren(true); + // foreach (var eliminateScale in componentsInChildren) + // { + // eliminateScale.chaCtrl = chara; + // } + // if (parent != null && collider != null) + // { + // collider.transform.SetParent(parent.transform, false); + // } + // //if (!this.dicObject.ContainsKey(text4)) + // //{ + // // this.dicObject.Add(text4, gameObject2); + // //} + // //else + // //{ + // // UnityEngine.Object.Destroy(this.dicObject[text4]); + // // this.dicObject[text4] = gameObject2; + // //} + // } + // } + // } + + //} + + internal static Dictionary dicNowReactions = new Dictionary + { + { + 0, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List(), + lstParam = new List + { + new ReactionParam + { + id = 0, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 1, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + { + 1, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List(), + lstParam = new List + { + new ReactionParam + { + id = 0, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 1, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + { + 2, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List(), + lstParam = new List + { + new ReactionParam + { + id = 2, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 3, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + { + 3, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List + { + 0 + }, + lstParam = new List + { + new ReactionParam + { + id = 4, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.zero, + max = Vector3.up + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 5, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.zero, + max = Vector3.up + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + { + 4, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List + { + 1 + }, + lstParam = new List + { + new ReactionParam + { + id = 6, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.zero, + max = Vector3.up + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 7, + lstMinMax = new List + { + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + }, + new ReactionMinMax + { + min = Vector3.zero, + max = Vector3.up + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + { + 5, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List(), + lstParam = new List + { + new ReactionParam + { + id = 8, + lstMinMax = new List + { + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 9, + lstMinMax = new List + { + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + { + 6, new ReactionInfo + { + isPlay = true, + weight = 0.3f, + lstReleaseEffector = new List(), + lstParam = new List + { + new ReactionParam + { + id = 10, + lstMinMax = new List + { + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + }, + new ReactionParam + { + id = 11, + lstMinMax = new List + { + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = new Vector3(-1f, 0f, -1f), + max = new Vector3(1f, 0f, 1f), + }, + new ReactionMinMax + { + min = Vector3.back, + max = Vector3.forward + } + } + } + } + } + }, + }; + } +} diff --git a/Shared/Interpreters/HSceneInterpreter.cs b/Shared/Interpreters/HSceneInterpreter.cs new file mode 100644 index 0000000..cdb6565 --- /dev/null +++ b/Shared/Interpreters/HSceneInterpreter.cs @@ -0,0 +1,533 @@ +using UnityEngine; +using VRGIN.Core; +using HarmonyLib; +using System.Collections.Generic; +using KK_VR.Camera; +using KK_VR.Features; +using System; +using Manager; +using System.Linq; +using System.Collections; +using KK_VR.Interpreters; +using KK_VR.Caress; +using Random = UnityEngine.Random; +using static HFlag; +using static HandCtrl; +using static VRGIN.Controls.Controller; +using Valve.VR; +using KK_VR.Handlers; +using KK_VR.Controls; +using RootMotion.FinalIK; +using ADV.Commands.H; +using ADV; +using KK_VR.Fixes; +using System.Runtime.Serialization.Formatters; +using KK_VR.Trackers; +using KK_VR.Interactors; +using KK_VR.Patches; +using KK_VR.Grasp; +using KK_VR.Holders; +using System.Diagnostics; +using KK_VR.Settings; + +namespace KK_VR.Interpreters +{ + internal class HSceneInterpreter : SceneInterpreter + { + + private readonly PoV _pov; + private HPointMove _hPointMove; + + private readonly static List _lstIKEffectLateUpdate = []; + private static bool _lateHitReaction; + + internal static HFlag hFlag; + internal static HSprite sprite; + internal static EMode mode; + internal static HandCtrl handCtrl; + internal static HandCtrl handCtrl1; + internal static HAibu hAibu; + internal static HVoiceCtrl hVoice; + internal static List lstProc; + internal static List lstFemale; + internal static ChaControl male; + private static int _backIdle; + private static bool adjustDirLight; + private readonly MouthGuide _mouth; + private static HitReaction _hitReaction; + + internal static bool IsInsertIdle(string nowAnim) => nowAnim.EndsWith("InsertIdle", StringComparison.Ordinal); + internal static bool IsIdleOutside(string nowAnim) => nowAnim.Equals("Idle"); + internal static bool IsAfterClimaxInside(string nowAnim) => nowAnim.EndsWith("IN_A", StringComparison.Ordinal); + internal static bool IsAfterClimaxOutside(string nowAnim) => nowAnim.EndsWith("OUT_A", StringComparison.Ordinal); + internal static bool IsClimaxHoushiInside(string nowAnim) => nowAnim.StartsWith("Oral", StringComparison.Ordinal); + internal static bool IsAfterClimaxHoushiInside(string nowAnim) => nowAnim.Equals("Drink_A") || nowAnim.Equals("Vomit_A"); + internal static bool IsFinishLoop => hFlag.finish != FinishKind.none && IsOrgasmLoop; + internal static bool IsWeakLoop => hFlag.nowAnimStateName.EndsWith("WLoop", StringComparison.Ordinal); + internal static bool IsStrongLoop => hFlag.nowAnimStateName.EndsWith("SLoop", StringComparison.Ordinal); + internal static bool IsOrgasmLoop => hFlag.nowAnimStateName.EndsWith("OLoop", StringComparison.Ordinal); + internal static bool IsKissAnim => hFlag.nowAnimStateName.StartsWith("K_", StringComparison.Ordinal); + internal static bool IsTouch => hFlag.nowAnimStateName.EndsWith("Touch", StringComparison.Ordinal); + internal HPointMove GetHPointMove => _hPointMove == null ? _hPointMove = UnityEngine.Object.FindObjectOfType() : _hPointMove; + internal static int GetBackIdle => _backIdle; +#if KK + internal static bool IsHPointMove => Scene.Instance.AddSceneName.Equals("HPointMove"); +#else + internal static bool IsHPointMove => Scene.AddSceneName.Equals("HPointMove"); +#endif + internal static bool IsVoiceActive => hVoice.nowVoices[0].state != HVoiceCtrl.VoiceKind.breath || IsKissAnim; + internal static bool IsHandAttached => handCtrl.useItems[0] != null || handCtrl.useItems[1] != null; + internal static bool IsHandActive => handCtrl.GetUseAreaItemActive() != -1; + internal static bool IsActionLoop + { + get + { + return mode switch + { + EMode.aibu => handCtrl.IsKissAction() || handCtrl.IsItemTouch(), + EMode.houshi or EMode.sonyu => hFlag.nowAnimStateName.EndsWith("Loop", StringComparison.Ordinal), + _ => false, + }; + } + } + + private static readonly List _aibuAnims = + [ + "Idle", // 0 + "M_Touch", // 1 + "A_Touch", // 2 + "S_Touch", // 3 + "K_Touch" // 4 + ]; + + private List GetHPointCategoryList + { + get + { + var list = GetHPointMove.dicObj.Keys.ToList(); + list.Sort(); + return list; + } + } + + internal HSceneInterpreter(MonoBehaviour proc) + { + var traverse = Traverse.Create(proc); + hFlag = traverse.Field("flags").GetValue(); + sprite = traverse.Field("sprite").GetValue(); + handCtrl = traverse.Field("hand").GetValue(); + handCtrl1 = traverse.Field("hand1").GetValue(); + lstProc = traverse.Field("lstProc").GetValue>(); + hVoice = traverse.Field("voice").GetValue(); + lstFemale = traverse.Field("lstFemale").GetValue>(); + male = traverse.Field("male").GetValue(); + hAibu = (HAibu)lstProc[0]; + + CrossFader.HSceneHooks.SetFlag(hFlag); + + var charas = new List() { male }; + charas.AddRange(lstFemale); + var distinctCharas = charas.Distinct(); + VRBoop.RefreshDynamicBones(distinctCharas); + + SceneExtras.EnableDynamicBones(distinctCharas); + SceneExtras.AddTalkColliders(distinctCharas); + SceneExtras.AddHColliders(distinctCharas); + GraspController.Init(distinctCharas); + + _mouth = MouthGuide.Create(); + _pov = PoV.Create(); + adjustDirLight = true; + // Init after everything. +//#if KKS +// MeshCollider.AddRascal(lstFemale[0]); +//#endif + HitReactionInitialize(distinctCharas); + LocationPicker.AddComponents(); +#if KK + // If disabled, camera won't know where to move. + Manager.Config.EtcData.HInitCamera = true; +#else + Manager.Config.HData.HInitCamera = true; +#endif + if (_settings.ShadowsOptimization == KoikatuSettings.ShadowType.Auto) + { + KoikatuInterpreter.TweakShadowSettings(KoikatuSettings.ShadowType.Close); + } + if (KoikatuInterpreter.Settings.AutoEnterPov) + { + SmoothMover.Instance.MoveToPoV(); + } + } + + + internal override void OnDisable() + { + SmoothMover.Instance.MakeUpright(); + Component.Destroy(_pov); + GameObject.Destroy(_mouth.gameObject); + + SceneExtras.ReturnDirLight(); + HandHolder.DestroyHandlers(); + LocationPicker.DestroyComponents(); + TalkSceneInterpreter.afterH = true; +#if KKS + ObiCtrlFix.OnHSceneEnd(); +#endif + if (GraspHelper.Instance != null) + { + Component.Destroy(GraspHelper.Instance); + } + } + + internal override void OnUpdate() + { + // Exit through the title button in config doesn't trigger hook. + if (hFlag == null) KoikatuInterpreter.EndScene(KoikatuInterpreter.SceneType.HScene); + base.OnUpdate(); + } + + internal override void OnLateUpdate() + { + if (_lateHitReaction) + { + _lateHitReaction = false; + _hitReaction.ReleaseEffector(); + _hitReaction.SetEffector(_lstIKEffectLateUpdate); + _lstIKEffectLateUpdate.Clear(); + } + if (adjustDirLight) + { + SceneExtras.RepositionDirLight(lstFemale[0]); + adjustDirLight = false; + } + } + + internal static void EnableNip(AibuColliderKind colliderKind) + { + if (colliderKind == AibuColliderKind.muneL || colliderKind == AibuColliderKind.muneR) + { + var number = colliderKind == AibuColliderKind.muneL ? 0 : 1; + handCtrl.female.DisableShapeNip(number, false); + handCtrl.female.DisableShapeBodyID(number, ChaFileDefine.cf_ShapeMaskNipStand, false); + //if (number == 1) + //{ + // handCtrl.female.DisableShapeBust(number, false); + //} + } + } + + internal static void ShowAibuHand(AibuColliderKind colliderKind, bool show) + { + handCtrl.useAreaItems[(int)colliderKind - 2].objBody.GetComponent().enabled = show; + } + + internal void ToggleAibuHandVisibility(AibuColliderKind colliderKind) + { + var renderer = handCtrl.useAreaItems[(int)colliderKind - 2].objBody.GetComponent(); + renderer.enabled = !renderer.enabled; + EnableNip(colliderKind); + } + + internal static bool PlayShort(ChaControl chara, bool voiceWait = true) + { + if (lstFemale.Contains(chara)) + { + if (!voiceWait || !IsVoiceActive) + { + hFlag.voice.playShorts[lstFemale.IndexOf(chara)] = Random.Range(0, 9); + } + return true; + } + else + { + Features.LoadVoice.PlayVoice(Features.LoadVoice.VoiceType.Short, chara, voiceWait); + } + return false; + } + + internal IEnumerator RandomHPointMove(bool startScene) + { + if (startScene) + { + hFlag.click = ClickKind.pointmove; + yield return new WaitUntil(() => IsHPointMove); + } + var hPoint = GetHPointMove; + var key = hPoint.dicObj.ElementAt(Random.Range(0, hPoint.dicObj.Count)).Key; + ChangeCategory(GetHPointCategoryList.IndexOf(key)); + yield return null; + var dicList = hPoint.dicObj[hPoint.nowCategory]; + var hPointData = dicList[Random.Range(0, dicList.Count)].GetComponent(); + hPoint.actionSelect(hPointData, hPoint.nowCategory); +#if KK + Singleton.Instance.UnLoad(); +#else + Scene.Unload(); +#endif + } + + internal static void SetSelectKindTouch(AibuColliderKind colliderKind) + { + if (handCtrl != null) handCtrl.selectKindTouch = colliderKind; + } + + private int GetCurrentBackIdleIndex() + { + var twoLetters = hFlag.nowAnimStateName.Remove(2); + var anim = _aibuAnims + .Where(anim => anim.StartsWith(twoLetters, StringComparison.Ordinal)) + .FirstOrDefault(); + var index = _aibuAnims.IndexOf(anim); + _backIdle = index == 4 ? 0 : index; + return index; + } + + internal static void LeanToKiss() + { + HScenePatches.HoldKissLoop(); + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnKissStart(AibuColliderKind.none); + } + SetPlay(_aibuAnims[4]); + } + + internal void ScrollAibuAnim(bool increase) + { + var index = GetCurrentBackIdleIndex() + (increase ? 1 : -1); + if (index > 3) + { + index = 1; + } + else if (index < 1) + { + index = 3; + } + _pov.StartCoroutine(PlayAnimOverTime(index)); + } + + internal static void PlayReaction() + { + var nowAnim = hFlag.nowAnimStateName; + switch (mode) + { + case EMode.houshi: + if (IsActionLoop) + { + if (hFlag.nowAnimationInfo.kindHoushi == 1) + { + handCtrl.Reaction(AibuColliderKind.reac_head); + } + else if (hFlag.nowAnimationInfo.kindHoushi == 2) + { + handCtrl.Reaction(AibuColliderKind.reac_bodyup); + } + else + { + handCtrl.Reaction(AibuColliderKind.reac_armR); + } + } + else + { + goto default; + } + break; + case EMode.sonyu: + if (IsAfterClimaxInside(nowAnim) || IsInsertIdle(nowAnim) || IsActionLoop) + { + handCtrl.Reaction(AibuColliderKind.reac_bodydown); + } + else + { + goto default; + } + break; + default: + var items = handCtrl.GetUseItemNumber(); + var count = items.Count; + if (count != 0) + { + var item = items[Random.Range(0, count)]; + handCtrl.Reaction(handCtrl.useItems[item].kindTouch < AibuColliderKind.kokan ? AibuColliderKind.reac_bodyup : AibuColliderKind.reac_bodydown); + } + break; + } + } + + private IEnumerator PlayAnimOverTime(int index) + { + PlayReaction(); + yield return new WaitForSeconds(0.25f); + hAibu.backIdle = -1; + HScenePatches.suppressSetIdle = true; + SetPlay(_aibuAnims[index]); + } + + internal static void SetPlay(string animation) + { + lstProc[(int)hFlag.mode].SetPlay(animation, true); + } + + internal void MoveCategory(bool increase) + { + var list = GetHPointCategoryList; + var index = list.IndexOf(GetHPointMove.nowCategory); + if (increase) + { + if (index == list.Count - 1) + { + index = 0; + } + else + { + index++; + } + } + else + { + if (index == 0) + { + index = list.Count - 1; + } + else + { + index--; + } + } + ChangeCategory(index); + } + + private void ChangeCategory(int index) + { + var list = GetHPointCategoryList; + GetHPointMove.SelectPointVisible(list[index], true); + GetHPointMove.nowCategory = list[index]; + } + + internal int GetCurrentLoop(bool increase) + { + if (IsWeakLoop) + { + return increase ? 1 : 0; + } + if (IsStrongLoop) + { + return increase ? 2 : 0; + } + // OLoop + return increase ? 2 : 1; + } + + internal static void OnPoseChange(HSceneProc.AnimationListInfo anim) + { + mode = anim.mode switch + { + EMode.houshi or EMode.houshi3P or EMode.houshi3PMMF => EMode.houshi, + EMode.sonyu or EMode.sonyu3P or EMode.sonyu3PMMF => EMode.sonyu, + _ => anim.mode, + }; + adjustDirLight = true; + GraspController.OnSpotPoseChange(); + MouthGuide.OnPoseChange(anim.mode); + if (male != null) + { + // KK has it by default? KKS definitely disables them for the male. + SceneExtras.EnableDynamicBones(male); + } + } + + internal static void OnSpotChange() + { + adjustDirLight = true; + GraspController.OnSpotPoseChange(); + } + + public void HitReactionInitialize(IEnumerable charas) + { + if (_hitReaction == null) + { + _hitReaction = handCtrl1.hitReaction; + } + ControllerTracker.Initialize(charas); + HandHolder.UpdateHandlers(); + } + + public static void HitReactionPlay(AibuColliderKind aibuKind, ChaControl chara, bool voiceWait) + { + // This roundabout way is to allow player to touch anybody present, including himself, janitor, + // and charas from kPlug (actually don't know if they have FullBodyBipedIK or not, because we need it). + + // TODO voice is a placeHolder, in h we have a good dic lying around with the proper ones. + + //VRPlugin.Logger.LogDebug($"HScene:Reaction:{aibuKind}:{chara}"); + _hitReaction.ik = chara.objAnim.GetComponent(); + + var dic = handCtrl.dicNowReaction; + if (dic.Count == 0) + { + dic = SceneExtras.dicNowReactions; + } + var key = aibuKind - AibuColliderKind.reac_head; + var index = Random.Range(0, dic[key].lstParam.Count); + var reactionParam = dic[key].lstParam[index]; + var array = new Vector3[reactionParam.lstMinMax.Count]; + for (int i = 0; i < reactionParam.lstMinMax.Count; i++) + { + array[i] = new Vector3(Random.Range(reactionParam.lstMinMax[i].min.x, reactionParam.lstMinMax[i].max.x), + Random.Range(reactionParam.lstMinMax[i].min.y, reactionParam.lstMinMax[i].max.y), + Random.Range(reactionParam.lstMinMax[i].min.z, reactionParam.lstMinMax[i].max.z)); + array[i] = chara.transform.TransformDirection(array[i].normalized); + } + _hitReaction.weight = dic[key].weight; + _hitReaction.HitsEffector(reactionParam.id, array); + _lateHitReaction = true; + _lstIKEffectLateUpdate.AddRange(dic[key].lstReleaseEffector); + + PlayShort(chara, voiceWait); + } + + //private bool SetHand() + //{ + // //VRPlugin.Logger.LogDebug($"Interpreter:HScene:SetHand"); + // if (handCtrl.useItems[0] == null || handCtrl.useItems[1] == null) + // { + // var list = new List(); + // for (int i = 0; i < 6; i++) + // { + // if (handCtrl.useAreaItems[i] == null) + // { + // list.Add(i); + // } + // } + // list = list.OrderBy(a => Random.Range(0, 100)).ToList(); + // var index = 0; + // foreach (var item in list) + // { + // //VRPlugin.Logger.LogDebug($"Interpreter:HScene:SetHand:Loop:{item}"); + // var clothState = handCtrl.GetClothState((AibuColliderKind)(item + 2)); + // //var layerInfo = handCtrl.dicAreaLayerInfos[item][handCtrl.areaItem[item]]; + // var layerInfo = handCtrl.dicAreaLayerInfos[item][0]; + // if (layerInfo.plays[clothState] == -1) + // { + // continue; + // } + // index = item; + // break; + + // } + // //VRPlugin.Logger.LogDebug($"Interpreter:HScene:SetHand:Required:Choice - {index}"); + + // handCtrl.selectKindTouch = (AibuColliderKind)(index + 2); + // _pov.StartCoroutine(CaressUtil.ClickCo(() => handCtrl.selectKindTouch = AibuColliderKind.none)); + // return false; + // } + // else + // { + // //VRPlugin.Logger.LogDebug($"Interpreter:HScene:SetHand:NotRequired"); + // PlayReaction(); + // return true; + // } + //} + } +} diff --git a/Shared/Interpreters/Input/ActionSceneInput.cs b/Shared/Interpreters/Input/ActionSceneInput.cs new file mode 100644 index 0000000..d665045 --- /dev/null +++ b/Shared/Interpreters/Input/ActionSceneInput.cs @@ -0,0 +1,346 @@ +using System; +using System.Collections.Generic; +using System.Text; +using KK_VR.Camera; +using KK_VR.Features; +using KK_VR.Holders; +using UnityEngine; +using VRGIN.Core; +using WindowsInput.Native; +using static KK_VR.Interpreters.ActionSceneInterpreter; +using static VRGIN.Controls.Controller; + +namespace KK_VR.Interpreters +{ + internal class ActionSceneInput : SceneInput + { + private bool _standing = true; + /// + /// For button prompted crouch. + /// + private bool _crouching; + private bool _walking; + private float _continuousRotation; + private Pressed _buttons; + internal Transform _eyes; + + internal Vector3 GetEyesPosition() + { + if (_eyes == null) + { + _eyes = actionScene.Player.chaCtrl.objHeadBone.transform.Find("cf_J_N_FaceRoot/cf_J_FaceRoot/cf_J_FaceBase/cf_J_FaceUp_ty/cf_J_FaceUp_tz/cf_J_Eye_tz"); + } + return _eyes.TransformPoint(0f, _settings.PositionOffsetY, _settings.PositionOffsetZ); + } + + enum Pressed + { + LeftMouse = 1, + RightMouse = 2, + Shift = 4, + Ctrl = 8, + Z = 16 + } + private bool IsButtonPressed(Pressed button) + { + return (_buttons & button) != 0; + } + private void PressButton(Pressed button) + { + if (!IsButtonPressed(button)) + { + _buttons |= button; + switch (button) + { + case Pressed.LeftMouse: + VR.Input.Mouse.LeftButtonDown(); + break; + case Pressed.RightMouse: + VR.Input.Mouse.RightButtonDown(); + break; + case Pressed.Shift: + VR.Input.Keyboard.KeyDown(VirtualKeyCode.LSHIFT); + break; + case Pressed.Ctrl: + VR.Input.Keyboard.KeyDown(VirtualKeyCode.CONTROL); + break; + case Pressed.Z: + VR.Input.Keyboard.KeyDown(VirtualKeyCode.VK_Z); + break; + } + } + } + private void ReleaseButton(Pressed button) + { + if (IsButtonPressed(button)) + { + _buttons &= ~button; + switch (button) + { + case Pressed.LeftMouse: + VR.Input.Mouse.LeftButtonUp(); + break; + case Pressed.RightMouse: + VR.Input.Mouse.RightButtonUp(); + break; + case Pressed.Shift: + VR.Input.Keyboard.KeyUp(VirtualKeyCode.LSHIFT); + break; + case Pressed.Ctrl: + VR.Input.Keyboard.KeyUp(VirtualKeyCode.CONTROL); + break; + case Pressed.Z: + VR.Input.Keyboard.KeyUp(VirtualKeyCode.VK_Z); + break; + } + } + } + + internal override void OnDisable() + { + ResetState(); + } + + internal override void HandleInput() + { + base.HandleInput(); + if (_continuousRotation != 0f) + { + ContinuousRotation(_continuousRotation); + } + if (_walking) + { + CameraToPlayer(true); + } + UpdateCrouch(); + } + internal override bool OnDirectionDown(int index, TrackpadDirection direction) + { + switch (direction) + { + case TrackpadDirection.Up: + if (actionScene.Player.isGateHit + || actionScene.Player.actionTarget != null + || actionScene.Player.isActionPointHit) + { + PressButton(Pressed.RightMouse); + } + else if (_walking) + { + ToggleDash(); + } + break; + case TrackpadDirection.Down: + if (!_crouching) + { + Crouch(buttonPrompt: true); + } + else + { + StandUp(); + } + break; + case TrackpadDirection.Left: + Rotation(-_settings.RotationAngle); + break; + case TrackpadDirection.Right: + Rotation(_settings.RotationAngle); + break; + } + return false; + } + + internal override void OnDirectionUp(int index, TrackpadDirection direction) + { + StopRotation(); + } + + protected override bool OnTrigger(int index, bool press) + { + if (press) + { + _pressedButtons[index, 0] = true; + StartWalking(); + } + else + { + _pressedButtons[index, 0] = false; + ResetState(); + } + return false; + } + + private void Rotation(float degrees) + { + if (_settings.ContinuousRotation) + { + _continuousRotation = degrees; + } + else + { + SnapRotation(degrees); + } + } + + private void StopRotation() + { + _continuousRotation = 0f; + } + + /// + /// Rotate the camera. If we are in Roaming, rotate the protagonist as well. + /// + private void SnapRotation(float degrees) + { + //VRLog.Debug("Rotating {0} degrees", degrees); + CameraToPlayer(true); + + var camera = VR.Camera.transform; + var newRotation = Quaternion.AngleAxis(degrees, Vector3.up) * camera.rotation; + VRCameraMover.Instance.MoveTo(camera.position, newRotation); + PlayerToCamera(); + } + private void ContinuousRotation(float degrees) + { + var origin = VR.Camera.Origin; + var head = VR.Camera.Head; + var oldPos = head.position; + origin.rotation = Quaternion.Euler(0f, degrees * (Time.deltaTime * 2f), 0f) * origin.rotation; + origin.position += oldPos - head.position; + + if (!_walking) + { + PlayerToCamera(); + } + } + + public void CameraToPlayer(bool onlyPosition = false) + { + + //var headCam = VR.Camera.transform; + + var pos = GetEyesPosition(); + if (!_settings.UsingHeadPos) + { + var player = actionScene.Player; + pos.y = player.position.y + (_standing ? _settings.StandingCameraPos : _settings.CrouchingCameraPos); + } + + VR.Mode.MoveToPosition(pos, onlyPosition ? Quaternion.Euler(0f, VR.Camera.transform.eulerAngles.y, 0f) : _eyes.rotation, false); + //VRMover.Instance.MoveTo( + // //pos + cf * 0.23f, // 首が見えるとうざいのでほんの少し前目にする + // pos, + // onlyPosition ? headCam.rotation : _eyes.rotation, + // false, + // quiet); + } + + public void PlayerToCamera() + { + var player = actionScene.Player; + var head = VR.Camera.Head; + + var vec = player.position - GetEyesPosition(); + if (!_settings.UsingHeadPos) + { + var attachPoint = player.position; + attachPoint.y = _standing ? _settings.StandingCameraPos : _settings.CrouchingCameraPos; + vec = player.position - attachPoint; + } + player.rotation = Quaternion.Euler(0f, head.eulerAngles.y, 0f); + player.position = head.position + vec; + } + + private void UpdateCrouch() + { + if (!_crouching && actionScene.Player.chaCtrl.objTop != null) + { + var objTop = actionScene.Player.chaCtrl.objTop; + if (_settings.CrouchByCameraPos && objTop.activeInHierarchy == true) + { + var delta_y = VR.Camera.transform.position.y - objTop.transform.position.y; + + if (_standing && delta_y < 0.8f) + { + Crouch(buttonPrompt: false); + } + else if (!_standing && delta_y > 1f) + { + StandUp(); + } + } + } + } + + internal void StartWalking() + { + PlayerToCamera(); + PressButton(Pressed.Shift); + PressButton(Pressed.LeftMouse); + _walking = true; + HideMaleHead.ForceHideHead = true; + } + + private void ToggleDash() + { + if (IsButtonPressed(Pressed.Shift)) + { + ReleaseButton(Pressed.Shift); + } + else + { + PressButton(Pressed.Shift); + } + } + + internal void ResetState() + { + foreach (Pressed button in Enum.GetValues(typeof(Pressed))) + { + ReleaseButton(button); + } + _walking = false; + _standing = true; + _crouching = false; + HideMaleHead.ForceHideHead = false; + } + + internal void Crouch(bool buttonPrompt) + { + if (_standing) + { + _standing = false; + _crouching = buttonPrompt; + if (!Manager.Config.ActData.CrouchCtrlKey) + { + PressButton(Pressed.Z); + } + else + { + PressButton(Pressed.Ctrl); + } + } + } + internal void StandUp() + { + if (!_standing) + { + _standing = true; + _crouching = false; + if (_walking) + { + // Don't run after standing up. + PressButton(Pressed.Shift); + } + if (!Manager.Config.ActData.CrouchCtrlKey) + { + ReleaseButton(Pressed.Z); + } + else + { + ReleaseButton(Pressed.Ctrl); + } + } + } + } +} diff --git a/Shared/Interpreters/Input/HSceneInput.cs b/Shared/Interpreters/Input/HSceneInput.cs new file mode 100644 index 0000000..1a05940 --- /dev/null +++ b/Shared/Interpreters/Input/HSceneInput.cs @@ -0,0 +1,852 @@ +using KK_VR.Features; +using KK_VR.Handlers; +using KK_VR.Holders; +using Valve.VR; +using static HandCtrl; +using static HFlag; +using static VRGIN.Controls.Controller; +using static KK_VR.Interpreters.HSceneInterpreter; +using VRGIN.Core; +using Random = UnityEngine.Random; +using UnityEngine; + +namespace KK_VR.Interpreters +{ + internal class HSceneInput : SceneInput + { + private int _frameWait; + private bool _manipulateSpeed; + private TrackpadDirection _lastDirection; + private HSceneInterpreter _interpreter; + private PoV _pov; + private MouthGuide _mouth; + private readonly AibuColliderKind[] _lastAibuKind = new AibuColliderKind[2]; + internal HSceneInput(HSceneInterpreter interpreter) + { + _interpreter = interpreter; + _pov = PoV.Instance; + _mouth = MouthGuide.Instance; + } + private HSceneHandler GetHandler(int index) => (HSceneHandler)HandHolder.GetHand(index).Handler; + + internal override void HandleInput() + { + base.HandleInput(); + if (_manipulateSpeed) HandleSpeed(); + } + + private void HandleSpeed() + { + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.StopAuto(); + } + if (_lastDirection == TrackpadDirection.Up) + { + SpeedUp(); + } + else + { + SlowDown(); + } + } + + private void SpeedUp() + { + if (mode == EMode.aibu) + { + hFlag.SpeedUpClickAibu(Time.deltaTime, hFlag.speedMaxAibuBody, true); + } + else + { + if (hFlag.speedCalc < 1f) + { + hFlag.SpeedUpClick(Time.deltaTime * 0.2f, 1f); + //hFlag.speedCalc += Time.deltaTime * 0.2f; + if (hFlag.speedCalc > 1f) + { + hFlag.speedCalc = 1f; + } + } + else + { + AttemptFinish(); + } + } + } + + private void SlowDown() + { + if (mode == EMode.aibu) + { + hFlag.SpeedUpClickAibu(-Time.deltaTime, hFlag.speedMaxAibuBody, true); + } + else + { + if (hFlag.speedCalc > 0f) + { + hFlag.SpeedUpClick(Time.deltaTime * -0.2f, 1f); + + //hFlag.speedCalc -= Time.deltaTime * 0.2f; + if (hFlag.speedCalc < 0f) + { + hFlag.speedCalc = 0f; + } + } + else + { + AttemptStop(); + } + } + } + + private void AttemptFinish() + { + // Grab SensH ceiling. + if (hFlag.gaugeMale == 100f) + { + // There will be only one finish appropriate for the current mode/setting. + ClickRandomButton(); + _manipulateSpeed = false; + } + } + + private void AttemptStop() + { + // Happens only when we recently pressed the button. + _manipulateSpeed = false; + Pull(); + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.StopAuto(); + } + } + + internal override void OnGripMove(int index, bool press) + { + if (press) + { + _pov.OnGripMove(press); + _mouth.OnGripMove(press); + // _hands[index].Grasp.OnGripRelease(); + AddInputState(InputState.Move); + if (_mouth.IsActive) + { + var hand = HandHolder.GetHand(index); + hand.Tool.LazyGripMove(KoikatuInterpreter.ScaleWithFps(15)); + hand.Tool.AttachGripMove(_mouth.LookAt); + } + } + else + { + // Check if another controller still gripMoves. + if (!IsGripMove()) + { + _pov.OnGripMove(press); + _mouth.OnGripMove(press); + RemoveInputState(InputState.Move); + if (_mouth.IsActive) + { + _mouth.UpdateOrientationOffsets(); + } + } + } + } + protected override void PickButtonAction(InputWait wait, Timing timing) + { + var handler = GetHandler(wait.index); + var grasp = HandHolder.GetHand(wait.index).Grasp; + switch (wait.button) + { + case EVRButtonId.k_EButton_SteamVR_Touchpad: + if (timing == Timing.Full) + { + if (handler.IsBusy) + { + handler.UpdateTracker(tryToAvoid: PoV.Active ? PoV.Target : null); + + // We attempt to reset active body part (held parts reset on press); + if (!grasp.OnTouchpadResetActive(handler.GetTrackPartName(), handler.GetChara)) + { + // We update tracker to remove bias from PoV target we set beforehand. + handler.UpdateTracker(); + + // We attempt to impersonate, false if already impersonating/or setting. + var chara = handler.GetChara; + if (PoV.Active && PoV.Target == chara && grasp.OnTouchpadSyncStart(handler.GetTrackPartName(), chara)) + { + _pov.OnLimbSync(start: true); + } + } + + } + else + { + if (HandHolder.GetHand(wait.index).Grasp.OnTouchpadSyncStop()) + { + _pov.OnLimbSync(start: false); + } + else + { + _pov.TryEnable(); + } + } + } + break; + case EVRButtonId.k_EButton_SteamVR_Trigger: + if (grasp.IsBusy) + { + grasp.OnTriggerPress(temporarily: timing == Timing.Full); + } + break; + } + } + protected override bool OnTrigger(int index, bool press) + { + // With present 'Wait' for 'Direction' (no buttons pressed) trigger simply finishes 'Wait' and prompts the action, + // but if button is present, it in addition also offers alternative mode. Currently TouchpadPress only. + var handler = GetHandler(index); + var grasp = HandHolder.GetHand(index).Grasp; + if (press) + { + _pressedButtons[index, 0] = true; + if (IsInputState(InputState.Caress)) + { + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.JudgeProc(_lastAibuKind[index]); + } + else + { + HSceneInterpreter.handCtrl.JudgeProc(); + } + } + else if (IsInputState(InputState.Grasp)) + { + AddWait(index, EVRButtonId.k_EButton_SteamVR_Trigger, _settings.ShortPress); + } + else + { + if (_mouth.IsActive && !IsInputState(InputState.Move)) + { + _mouth.OnTriggerPress(); + } + else if (handler.IsBusy) + { + //Merge this with usual PickAction. + if (IsTouchpadPress(index) && grasp.OnTouchpadResetEverything(handler.GetChara)) + { + // Touchpad pressed + trigger = premature total reset of tracked character. + RemoveWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad); + } + else + { + // Send synthetic click. + handler.UpdateTracker(); + handler.TriggerPress(); + } + } + else if (IsWait) // && !IsTouchpadPress(index)) + { + PickAction(Timing.Full); + } + } + } + else + { + _pressedButtons[index, 0] = false; + + grasp.OnTriggerRelease(); + handler.TriggerRelease(); + PickAction(index, EVRButtonId.k_EButton_SteamVR_Trigger); + } + return false; + } + + private TrackpadDirection SwapSides(TrackpadDirection direction) + { + return direction switch + { + TrackpadDirection.Left => TrackpadDirection.Right, + TrackpadDirection.Right => TrackpadDirection.Left, + _ => direction + }; + + } + internal override bool OnDirectionDown(int index, TrackpadDirection direction) + { + var wait = 0f; + var speed = false; + var handler = GetHandler(index); + var grasp = HandHolder.GetHand(index).Grasp; + + if (index == 0) + { + // We respect lefties now. + direction = SwapSides(direction); + } + switch (direction) + { + case TrackpadDirection.Up: + case TrackpadDirection.Down: + if (grasp.IsBusy) + { + grasp.OnVerticalScroll(direction == TrackpadDirection.Up); + } + else if (handler.IsBusy) + { + handler.UpdateTracker(); + if (handler.DoUndress(direction == TrackpadDirection.Down)) + { + + } + else + { + + } + } + else + { + if (HSceneInterpreter.IsHPointMove) + { + _interpreter.MoveCategory(direction == TrackpadDirection.Down); + } + else if (HSceneInterpreter.IsActionLoop) + { + if (HSceneInterpreter.mode == EMode.aibu) + { + if (HSceneInterpreter.IsHandActive) + { + // Reaction if too long, speed meanwhile. + wait = 3f; + speed = true; + } + else + { + // Reaction/Lean to kiss. + wait = _settings.LongPress; + } + } + else + { + speed = true; + } + } + else + { + // ?? is this. + wait = 0.5f; + } + } + break; + case TrackpadDirection.Left: + case TrackpadDirection.Right: + if (grasp.IsBusy) + { + grasp.OnBusyHorizontalScroll(direction == TrackpadDirection.Right); + } + else if (handler.IsBusy) + { + handler.UpdateTracker(); + if (grasp.OnFreeHorizontalScroll(handler.GetTrackPartName(), handler.GetChara, direction == TrackpadDirection.Right)) + { + + } + else if (handler.IsAibuItemPresent(out var touch)) + { + if (IsHandActive) + { + _interpreter.ToggleAibuHandVisibility(touch); + } + else + { + SetSelectKindTouch(touch); + VR.Input.Mouse.VerticalScroll(direction == TrackpadDirection.Right ? -1 : 1); + } + } + } + else + { + if (IsTriggerPress(index)) + { + HandHolder.GetHand(index).ChangeLayer(direction == TrackpadDirection.Right); + } + else if (IsHPointMove) + { + if (direction == TrackpadDirection.Right) + wait = _settings.LongPress; + else + _interpreter.GetHPointMove.Return(); + } + else if (IsActionLoop) + { + if (mode == EMode.aibu) + { + _interpreter.ScrollAibuAnim(direction == TrackpadDirection.Right); + } + else if (IntegrationSensibleH.active) + { + IntegrationSensibleH.ChangeLoop(_interpreter.GetCurrentLoop(direction == TrackpadDirection.Right)); + } + } + else + wait = _settings.LongPress; + } + break; + } + _manipulateSpeed = speed; + _lastDirection = direction; + if (wait != 0f) + { + AddWait(index, direction, speed, wait); + return true; + } + else + return false; + } + + internal override void OnDirectionUp(int index, TrackpadDirection direction) + { + if (IsWait) + { + PickAction(index, direction); + } + else if (_manipulateSpeed) + { + _manipulateSpeed = false; + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnUserInput(); + } + } + HandHolder.GetHand(index).Grasp.OnScrollRelease(); + } + + protected override bool OnTouchpad(int index, bool press) + { + if (press) + { + _pressedButtons[index, 2] = true; + + if (IsInputState(InputState.Move)) + { + if (!_pov.OnTouchpad(true)) + { + if (!IsTriggerPress(index)) + { + // Reset to upright. + AddWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad, _settings.LongPress - 0.1f); // 0.7f + } + } + + } + else + { + if (IsTriggerPress(index)) + { + HandHolder.GetHand(index).ChangeItem(); + } + else if (!HandHolder.GetHand(index).Grasp.OnTouchpadResetHeld()) + { + AddWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad, _settings.ShortPress); + } + } + + } + else + { + _pressedButtons[index, 2] = false; + + PickAction(index, EVRButtonId.k_EButton_SteamVR_Touchpad); + } + return false; + } + protected override bool OnGrip(int index, bool press) + { + var handler = GetHandler(index); + //VRPlugin.Logger.LogDebug($"OnGrip:{handler.IsBusy}"); + if (press) + { + _pressedButtons[index, 1] = true; + if (HandHolder.GetHand(index).IsParent) + { + HandHolder.GetHand(index).Grasp.OnGripRelease(); + } + else if (handler.IsBusy) + { + handler.UpdateTracker(); + if (handler.IsAibuItemPresent(out var touch)) + { + //AddInputState(InputState.Move); + AddInputState(InputState.Caress); + handler.StartMovingAibuItem(touch); + _lastAibuKind[index] = touch; + if (IntegrationSensibleH.active && HSceneInterpreter.handCtrl.GetUseAreaItemActive() != -1) + { + IntegrationSensibleH.ReleaseItem(touch); + } + HSceneInterpreter.EnableNip(touch); + if (_settings.HideHandOnUserInput != Settings.KoikatuSettings.HandType.None) + { + if (_settings.HideHandOnUserInput > Settings.KoikatuSettings.HandType.ControllerItem) + { + HSceneInterpreter.ShowAibuHand(touch, false); + } + if (_settings.HideHandOnUserInput != Settings.KoikatuSettings.HandType.CaressItem) + { + HandHolder.GetHand(index).SetItemRenderer(false); + } + } + } + else + { + // Shouldn't appear anymore, blacks are cleared properly. + if (!handler.InBlack) + { + // We grasped something, don't start GripMove. + //AddInputState(InputState.Move); + AddInputState(InputState.Grasp); + HandHolder.GetHand(index).Grasp.OnGripPress(handler.GetTrackPartName(), handler.GetChara); + } + } + return true; + } + } + else + { + RemoveInputState(InputState.Move); + RemoveInputState(InputState.Caress); + if (IsInputState(InputState.Grasp)) + { + RemoveInputState(InputState.Grasp); + _pov.OnGraspEnd(); + } + _pressedButtons[index, 1] = false; + + handler.StopMovingAibuItem(); + HandHolder.GetHand(index).Grasp.OnGripRelease(); + } + return false; + } + private string GetButtonName(bool anal, bool noVoice) + { + string name; + switch (mode) + { + case EMode.sonyu: + name = "Insert"; + if (anal) + { + name += "Anal"; + } + if (noVoice) + { + name += "_novoice"; + } + break; + default: + name = ""; + break; + } + return name; + } + /// + /// Empty string to click whatever is there(except houshi slow/fast), otherwise checks start of the string and clicks corresponding button. + /// + private void ClickRandomButton() + { + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.ClickButton(""); + } + } + private bool IsFrameWait() + { + // Clutch to skip frames while changeing speed. + if (_frameWait != 0) + { + //VRPlugin.Logger.LogDebug($"FrameWait"); + if (!CrossFader.InTransition) + { + _frameWait--; + } + _manipulateSpeed = true; + return true; + } + return false; + } + private void WaitFrame(int count) + { + _frameWait = count; + _manipulateSpeed = true; + } + private void Pull() + { + if (!IsFrameWait() && PullHelper()) + { + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.ClickButton("Pull"); + } + else + { + hFlag.click = ClickKind.pull; + } + } + } + private bool PullHelper() + { + var nowAnim = hFlag.nowAnimStateName; + + if (mode == EMode.sonyu) + { + if (IsIdleOutside(nowAnim) || IsAfterClimaxOutside(nowAnim)) + { + // When outside pull back to get condom on. Extra plugin disables auto condom on denial. + sprite.CondomClick(); + } + else if (IsFinishLoop) + { + hFlag.finish = FinishKind.outside; + } + else if (IsActionLoop) + { + hFlag.click = ClickKind.modeChange; + WaitFrame(3); + } + else + { + return true; + } + } + else if (mode == EMode.houshi) + { + if (IsClimaxHoushiInside(nowAnim)) + { + hFlag.click = ClickKind.vomit; + } + else if (IsActionLoop) + { + lstProc[(int)hFlag.mode].MotionChange(0); + } + else + { + return true; + } + } + return false; + } + private void Insert(bool noVoice, bool anal) + { + if (InsertHelper()) + { + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.ClickButton(GetButtonName(anal, hFlag.isDenialvoiceWait || noVoice)); + } + else if (mode == EMode.sonyu) + { + // Houshi is done mostly by helper. + hFlag.click = anal ? noVoice ? ClickKind.insert_anal : ClickKind.insert_anal_voice : noVoice ? ClickKind.insert : ClickKind.insert_voice; + } + } + } + private bool InsertHelper() + { + var nowAnim = hFlag.nowAnimStateName; + if (mode == EMode.sonyu) + { + if (IsInsertIdle(nowAnim) || IsAfterClimaxInside(nowAnim)) + { + // Sonyu start auto. + hFlag.click = ClickKind.modeChange; + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnUserInput(); + } + } + else// if (!hFlag.voiceWait) + { + return true; + } + } + else if (mode == EMode.houshi) + { + if (IsClimaxHoushiInside(nowAnim)) + { + hFlag.click = ClickKind.drink; + } + else if (IsIdleOutside(nowAnim)) + { + // Start houshi after pose change/long pause after finish. + hFlag.click = ClickKind.speedup; + if (IntegrationSensibleH.active) + { + IntegrationSensibleH.OnUserInput(); + } + } + else if (IsAfterClimaxHoushiInside(nowAnim) || IsAfterClimaxOutside(nowAnim)) + { + // Restart houshi. + ClickRandomButton(); + } + else + { + return true; + } + } + else + { + return true; + } + return false; + } + protected override void PickDirectionAction(InputWait wait, Timing timing) + { + _manipulateSpeed = false; + switch (wait.direction) + { + case TrackpadDirection.Up: + if (mode == EMode.aibu) + { + if (IsActionLoop) + { + switch (timing) + { + case Timing.Fraction: + if (!IsHandActive && IsHandAttached) + { + PlayReaction(); + } + break; + case Timing.Half: + break; + case Timing.Full: + break; + } + } + else // Non-action Aibu mode. + { + switch (timing) + { + case Timing.Fraction: + if (Random.value < 0.5f) + { + PlayShort(lstFemale[0]); + } + break; + case Timing.Half: + // Put in denial + voice. + break; + case Timing.Full: + //SetHand(); + break; + } + } + } + else // Non-Aibu mode. + { + switch (timing) + { + case Timing.Fraction: + case Timing.Half: + PlayReaction(); + break; + case Timing.Full: + Insert(noVoice: IsTriggerPress(wait.index), anal: IsTouchpadPress(wait.index)); + break; + } + } + break; + case TrackpadDirection.Down: + if (mode == EMode.aibu) + { + if (IsActionLoop) + { + switch (timing) + { + case Timing.Fraction: + case Timing.Half: + break; + case Timing.Full: + + LeanToKiss(); + + break; + } + } + else // Non-action Aibu mode. + { + switch (timing) + { + case Timing.Fraction: + case Timing.Half: + break; + case Timing.Full: + LeanToKiss(); + break; + } + } + } + else // Non-Aibu mode. + { + switch (timing) + { + case Timing.Fraction: + case Timing.Half: + PlayReaction(); + break; + case Timing.Full: + Pull(); + break; + } + } + break; + case TrackpadDirection.Right: + switch (timing) + { + case Timing.Fraction: + case Timing.Half: + PlayShort(lstFemale[0]); + break; + case Timing.Full: + if (!IsHPointMove) + { + hFlag.click = ClickKind.pointmove; + } + else + { + _pov.StartCoroutine(_interpreter.RandomHPointMove(startScene: false)); + } + break; + } + break; + case TrackpadDirection.Left: + switch (timing) + { + case Timing.Fraction: + case Timing.Half: + PlayShort(lstFemale[0]); + break; + case Timing.Full: + if (IntegrationSensibleH.active) + { + if (IsTriggerPress(wait.index)) + { + // Any animation goes. + IntegrationSensibleH.ChangeAnimation(-1); + } + else + { + // SameMode. + IntegrationSensibleH.ChangeAnimation(3); + } + } + break; + } + break; + } + } + + } +} diff --git a/Shared/Interpreters/Input/InputWait.cs b/Shared/Interpreters/Input/InputWait.cs new file mode 100644 index 0000000..1f57a07 --- /dev/null +++ b/Shared/Interpreters/Input/InputWait.cs @@ -0,0 +1,45 @@ +using UnityEngine; +using static VRGIN.Controls.Controller; +using Valve.VR; + +namespace KK_VR.Interpreters +{ + internal readonly struct InputWait + { + internal InputWait(int _index, TrackpadDirection _direction, bool _manipulateSpeed, float _duration) + { + index = _index; + direction = _direction; + manipulateSpeed = _manipulateSpeed; + + duration = _duration; + timestamp = Time.time; + finish = Time.time + _duration; + } + internal InputWait(int _index, TrackpadDirection _direction, float _duration) + { + index = _index; + direction = _direction; + + duration = _duration; + timestamp = Time.time; + finish = Time.time + _duration; + } + internal InputWait(int _index, EVRButtonId _button, float _duration) + { + index = _index; + button = _button; + + duration = _duration; + timestamp = Time.time; + finish = Time.time + _duration; + } + internal readonly int index; + internal readonly TrackpadDirection direction; + internal readonly EVRButtonId button; + internal readonly bool manipulateSpeed; + internal readonly float timestamp; + internal readonly float duration; + internal readonly float finish; + } +} diff --git a/Shared/Interpreters/Input/SceneInput.cs b/Shared/Interpreters/Input/SceneInput.cs new file mode 100644 index 0000000..5626dbf --- /dev/null +++ b/Shared/Interpreters/Input/SceneInput.cs @@ -0,0 +1,362 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using KK_VR.Camera; +using KK_VR.Holders; +using KK_VR.Settings; +using UnityEngine; +using Valve.VR; +using VRGIN.Controls; +using VRGIN.Core; +using static VRGIN.Controls.Controller; + +namespace KK_VR.Interpreters +{ + /// + /// Manages input of the corresponding scene, has access to the interpreter but not vice versa. + /// + internal class SceneInput + { + protected readonly KoikatuSettings _settings = VR.Context.Settings as KoikatuSettings; + protected readonly List _waitList = []; + protected InputState _inputState; + protected bool IsWait => _waitList.Count != 0; + + /// + /// 0 - Trigger. + /// 1 - Grip. + /// 2 - Touchpad (Joystick click). + /// + protected readonly bool[,] _pressedButtons = new bool[2, 3]; + internal virtual bool IsTriggerPress(int index) => _pressedButtons[index, 0]; + internal virtual bool IsGripPress(int index) => _pressedButtons[index, 1]; + internal virtual bool IsTouchpadPress(int index) => _pressedButtons[index, 2]; + internal virtual bool IsGripMove() => _pressedButtons[0, 1] || _pressedButtons[1, 1]; + + /// + /// Something doesn't want to share input. + /// + internal bool IsBusy => _busy;// _inputState != InputState.None; + private bool _busy; + protected enum InputState + { + Caress = 1, + Grasp = 2, + Move = 4, + Busy = 8, + } + protected enum Timing + { + Fraction, + Half, + Full + } + internal void SetBusy(bool state) + { + if (state) + { + AddInputState(InputState.Busy); + } + else + { + RemoveInputState(InputState.Busy); + } + } + protected void AddInputState(InputState state) + { + _inputState |= state; + var wasBusy = _busy; + + _busy = IsInputStateNotDefault(); + if (!wasBusy && _busy) + { + HandHolder.OnBecomingBusy(); + } + } + protected void RemoveInputState(InputState state) + { + _inputState &= ~state; + _busy = IsInputStateNotDefault(); + } + protected bool IsInputState(InputState state) => (_inputState & state) != 0; + private bool IsInputStateNotDefault() + { + return IsInputState(InputState.Caress) || IsInputState(InputState.Grasp) || IsInputState(InputState.Move) || IsInputState(InputState.Busy); + } + internal virtual void OnDisable() + { + + } + private Timing GetTiming(float timestamp, float duration) + { + var timing = Time.time - timestamp; + if (timing > duration) return Timing.Full; + if (timing > duration * 0.5f) return Timing.Half; + return Timing.Fraction; + } + + internal virtual void HandleInput() + { + foreach (var wait in _waitList) + { + if (wait.finish < Time.time) + { + PickAction(wait); + return; + } + } + } + + protected void PickAction() + { + PickAction( + _waitList + .OrderByDescending(w => w.button) + .FirstOrDefault()); + } + + protected void PickAction(Timing timing) + { + PickAction( + _waitList + .OrderByDescending(w => w.button) + .FirstOrDefault(), timing); + } + + protected void PickAction(int index, EVRButtonId button) + { + if (_waitList.Count == 0) return; + PickAction( + _waitList + .Where(w => w.button == button && w.index == index) + .FirstOrDefault()); + } + + protected void PickAction(int index, TrackpadDirection direction) + { + if (_waitList.Count == 0) return; + PickAction( + _waitList + .Where(w => w.direction == direction && w.index == index) + .FirstOrDefault()); + } + + protected virtual void PickDirectionAction(InputWait wait, Timing timing) + { + + + } + + protected virtual void PickButtonAction(InputWait wait, Timing timing) + { + + } + + protected virtual void PickButtonActionGripMove(InputWait wait, Timing timing) + { + switch (wait.button) + { + case EVRButtonId.k_EButton_SteamVR_Touchpad: + if (timing == Timing.Full) + { + if (!IsTriggerPress(wait.index)) + { + SmoothMover.Instance.MakeUpright(); + } + } + break; + } + } + + private void PickAction(InputWait wait, Timing timing) + { + // Main entry. + if (wait.button == EVRButtonId.k_EButton_System) + PickDirectionAction(wait, timing); + else + { + if (IsInputState(InputState.Move)) + { + PickButtonActionGripMove(wait, timing); + } + else + { + PickButtonAction(wait, timing); + } + } + RemoveWait(wait); + } + private void PickAction(InputWait wait) + { + PickAction(wait, GetTiming(wait.timestamp, wait.duration)); + } + + internal bool OnButtonDown(int index, EVRButtonId buttonId, TrackpadDirection direction) + { + return buttonId switch + { + EVRButtonId.k_EButton_SteamVR_Trigger => OnTrigger(index, press: true), + EVRButtonId.k_EButton_Grip => OnGrip(index, press: true) || IsWait, + EVRButtonId.k_EButton_SteamVR_Touchpad => OnTouchpad(index, press: true), + EVRButtonId.k_EButton_ApplicationMenu => OnMenu(index, press: true), + _ => false, + }; + } + + internal void OnButtonUp(int index, EVRButtonId buttonId, TrackpadDirection direction) + { + switch (buttonId) + { + case EVRButtonId.k_EButton_SteamVR_Trigger: + OnTrigger(index, press: false); + break; + case EVRButtonId.k_EButton_Grip: + OnGrip(index, press: false); + break; + case EVRButtonId.k_EButton_SteamVR_Touchpad: + OnTouchpad(index, press: false); + break; + } + } + + internal virtual bool OnDirectionDown(int index, TrackpadDirection direction) + { + switch (direction) + { + case TrackpadDirection.Left: + case TrackpadDirection.Right: + if (IsTriggerPress(index)) + { + HandHolder.GetHand(index).ChangeLayer(direction == TrackpadDirection.Right); + } + break; + } + + return false; + } + + internal virtual void OnDirectionUp(int index, TrackpadDirection direction) + { + if (IsWait) + { + PickAction(index, direction); + } + } + + protected virtual bool OnTrigger(int index, bool press) + { + if (press) + { + _pressedButtons[index, 0] = true; + if (IsWait) + { + PickAction(Timing.Full); + } + } + else + { + _pressedButtons[index, 0] = false; + PickAction(index, EVRButtonId.k_EButton_SteamVR_Trigger); + } + return false; + } + + /// + /// Return false to start GripMove. + /// + protected virtual bool OnGrip(int index, bool press) + { + if (press) + { + _pressedButtons[index, 1] = true; + } + else + { + _pressedButtons[index, 1] = false; + } + return false; + } + + protected virtual bool OnTouchpad(int index, bool press) + { + if (press) + { + _pressedButtons[index, 2] = true; + + if (IsInputState(InputState.Move)) + { + if (!IsTriggerPress(index)) + { + AddWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad, _settings.LongPress - 0.1f); + } + } + else + { + if (IsTriggerPress(index)) + { + HandHolder.GetHand(index).ChangeItem(); + } + } + } + else + { + _pressedButtons[index, 2] = false; + + PickAction(index, EVRButtonId.k_EButton_SteamVR_Touchpad); + } + return false; + } + + protected virtual bool OnMenu(int index, bool press) + { + return false; + } + + internal virtual void OnGripMove(int index, bool active) + { + if (active) + { + AddInputState(InputState.Move); + } + else + { + RemoveInputState(InputState.Move); + } + } + + protected void AddWait(int index, EVRButtonId button, float duration) + { + _waitList.Add(new InputWait(index, button, duration)); + } + + protected void AddWait(int index, TrackpadDirection direction, bool manipulateSpeed, float duration) + { + _waitList.Add(new InputWait(index, direction, manipulateSpeed, duration)); + } + + protected void AddWait(int index, TrackpadDirection direction, float duration) + { + _waitList.Add(new InputWait(index, direction, duration)); + } + + private void RemoveWait(InputWait wait) + { + _waitList.Remove(wait); + } + + protected void RemoveWait(int index, EVRButtonId button) + { + RemoveWait(_waitList + .Where(w => w.index == index && w.button == button) + .FirstOrDefault()); + } + + protected void RemoveWait(int index, Controller.TrackpadDirection direction) + { + RemoveWait(_waitList + .Where(w => w.index == index && w.direction == direction) + .FirstOrDefault()); + } + } +} diff --git a/Shared/Interpreters/Input/TalkSceneInput.cs b/Shared/Interpreters/Input/TalkSceneInput.cs new file mode 100644 index 0000000..9299b69 --- /dev/null +++ b/Shared/Interpreters/Input/TalkSceneInput.cs @@ -0,0 +1,607 @@ +using System; +using System.Collections.Generic; +using System.Text; +using ADV; +using KK_VR.Handlers; +using KK_VR.Holders; +using KK_VR.Interpreters; +using Valve.VR; +using static VRGIN.Controls.Controller; +using static KK_VR.Interpreters.TalkSceneInterpreter; +using Manager; +using UnityEngine.UI; +using System.Linq; +using UnityEngine; +using Random = UnityEngine.Random; +using VRGIN.Core; +using KK_VR.Controls; + +namespace KK_VR.Interpreters +{ + internal class TalkSceneInput : SceneInput + { + TalkSceneInterpreter _interpreter; + private Button _lastSelectedCategory; + private Button _lastSelectedButton; + internal TalkSceneInput(TalkSceneInterpreter interpreter) + { + _interpreter = interpreter; + } + private bool IsADV => advScene.isActiveAndEnabled; + private bool IsADVChoice => advScene.scenario.isChoice; +#if KK + enum State + { + Talk, + None, + Event + } +#else + enum State + { + None = -1, + Talk, + Listen, + Topic, + Event, + R18 + } +#endif + private TalkSceneHandler GetHandler(int index) => (TalkSceneHandler)HandHolder.GetHand(index).Handler; + internal override void OnDirectionUp(int index, TrackpadDirection direction) + { + if (_interpreter.IsStart) return; + base.OnDirectionUp(index, direction); + HandHolder.GetHand(index).Grasp.OnScrollRelease(); + } + protected override bool OnTrigger(int index, bool press) + { + if (_interpreter.IsStart) return false; + var handler = GetHandler(index); + var grasp = HandHolder.GetHand(index).Grasp; + if (press) + { + _pressedButtons[index, 0] = true; + if (IsInputState(InputState.Grasp)) + { + AddWait(index, EVRButtonId.k_EButton_SteamVR_Trigger, _settings.ShortPress); + } + else + { + if (IsWait && !IsTouchpadPress(index)) + { + PickAction(); + } + else if (handler.IsBusy) + { + if (IsTouchpadPress(index) && grasp.OnTouchpadResetEverything(handler.GetChara)) + { + // Touchpad pressed + trigger = premature total reset of tracked character. + RemoveWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad); + } + else + { + // Send synthetic click. + handler.UpdateTracker(); + handler.TriggerPress(); + } + } + } + } + else + { + _pressedButtons[index, 0] = false; + + grasp.OnTriggerRelease(); + handler.TriggerRelease(); + PickAction(index, EVRButtonId.k_EButton_SteamVR_Trigger); + } + return false; + } + + protected override bool OnGrip(int index, bool press) + { + if (_interpreter.IsStart) return false; + var handler = GetHandler(index); + if (press) + { + _pressedButtons[index, 1] = true; + + if (HandHolder.GetHand(index).IsParent) + { + HandHolder.GetHand(index).Grasp.OnGripRelease(); + } + else if (handler.IsBusy) // if (!handler.InBlack) + { + AddInputState(InputState.Grasp); + HandHolder.GetHand(index).Grasp.OnGripPress(handler.GetTrackPartName(), handler.GetChara); + } + else + { + return false; + } + return true; + + } + else + { + RemoveInputState(InputState.Move); + RemoveInputState(InputState.Grasp); + + _pressedButtons[index, 1] = false; + HandHolder.GetHand(index).Grasp.OnGripRelease(); + return false; + } + } + + protected override bool OnTouchpad(int index, bool press) + { + if (_interpreter.IsStart) return false; + if (press) + { + _pressedButtons[index, 2] = true; + + if (IsInputState(InputState.Move)) + { + if (!IsTriggerPress(index)) + { + AddWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad, _settings.LongPress - 0.1f); + } + } + else + { + if (IsTriggerPress(index)) + { + HandHolder.GetHand(index).ChangeItem(); + } + else if (!HandHolder.GetHand(index).Grasp.OnTouchpadResetHeld()) + { + AddWait(index, EVRButtonId.k_EButton_SteamVR_Touchpad, _settings.ShortPress); + } + } + } + else + { + _pressedButtons[index, 2] = false; + + PickAction(index, EVRButtonId.k_EButton_SteamVR_Touchpad); + } + return false; + } + + internal override bool OnDirectionDown(int index, TrackpadDirection direction) + { + if (_interpreter.IsStart) return false; + var adv = IsADV; + var handler = GetHandler(index); + var grasp = HandHolder.GetHand(index).Grasp; + switch (direction) + { + case TrackpadDirection.Up: + case TrackpadDirection.Down: + if (grasp.IsBusy) + { + grasp.OnVerticalScroll(direction == TrackpadDirection.Up); + } + else if (handler.IsBusy) + { + handler.UpdateTracker(); + if (handler.DoUndress(direction == TrackpadDirection.Down, out var chara)) + { + _interpreter.SynchronizeClothes(chara); + } + else + { + + } + } + else + { + if (!adv || IsADVChoice) + { + ScrollButtons(direction == TrackpadDirection.Down, adv); + } + else + { + + } + } + break; + case TrackpadDirection.Left: + case TrackpadDirection.Right: + if (grasp.IsBusy) + { + grasp.OnBusyHorizontalScroll(direction == TrackpadDirection.Right); + } + else if (handler.IsBusy) + { + handler.UpdateTracker(); + grasp.OnFreeHorizontalScroll(handler.GetTrackPartName(), handler.GetChara, direction == TrackpadDirection.Right); + + } + else + { + if (IsTriggerPress(index)) + { + HandHolder.GetHand(index).ChangeLayer(direction == TrackpadDirection.Right); + } + else + { + AddWait(index, direction, _settings.LongPress); + } + } + break; + } + return false; + } + + + protected override void PickButtonAction(InputWait wait, Timing timing) + { + var handler = GetHandler(wait.index); + var grasp = HandHolder.GetHand(wait.index).Grasp; + switch (wait.button) + { + case EVRButtonId.k_EButton_SteamVR_Touchpad: + if (timing == Timing.Full) + { + if (handler.IsBusy) + { + handler.UpdateTracker(); + var chara = handler.GetChara; + var partName = handler.GetTrackPartName(); + if (!grasp.OnTouchpadResetActive(partName, chara)) + { + grasp.OnTouchpadSyncStart(partName, chara); + } + } + else + { + HandHolder.GetHand(wait.index).Grasp.OnTouchpadSyncStop(); + } + } + break; + case EVRButtonId.k_EButton_SteamVR_Trigger: + if (grasp.IsBusy) + { + grasp.OnTriggerPress(temporarily: timing == Timing.Full); + } + break; + } + } + + protected override void PickDirectionAction(InputWait wait, Timing timing) + { + var adv = IsADV; + switch (wait.direction) + { + case TrackpadDirection.Up: + case TrackpadDirection.Down: + break; + case TrackpadDirection.Left: + case TrackpadDirection.Right: + if (adv) + { + if (!IsADVChoice) + { + if (timing == Timing.Full) + { + SetAutoADV(); + } + else + { + VR.Input.Mouse.VerticalScroll(-1); + } + } + else + { + EnterState(adv); + } + } + else + { + if (timing == Timing.Full && ClickLastButton()) + { + return; + } + + if (wait.direction == TrackpadDirection.Left) + { + EnterState(adv); + } + else + { + LeaveState(adv); + } + } + break; + } + } + + private void SetAutoADV() + { + advScene.Scenario.isAuto = !advScene.Scenario.isAuto; + } + private void ScrollButtons(bool increase, bool adv) + { + var state = GetState(); +#if KKS + if (state == State.Listen || state == State.Topic) + { + + } + else +#endif + { + var buttons = GetRelevantButtons(state, adv); + var length = buttons.Length; + if (length == 0) + { + return; + } + var selectedBtn = GetSelectedButton(buttons, adv); + var index = increase ? 1 : -1; + if (selectedBtn != null) + { + index += Array.IndexOf(buttons, selectedBtn); + } + else + { + index = 0; + } + if (index == length) + { + index = 0; + } + else if (index < 0) + { + index = length - 1; + } + MarkButton(buttons[index], adv); + } + } + + private bool ClickLastButton() + { + if (_lastSelectedButton != null && _lastSelectedButton.enabled) + { + _lastSelectedButton.onClick.Invoke(); + return true; + } + return false; + } + private void LeaveState(bool adv) + { + var state = GetState(); +#if KKS + if (state == State.Listen) + { + ReplyTopic(false); + } + else if (state == State.Topic) + { + RandomTopic(); + } + else +#endif + { + var buttons = GetRelevantButtons(state, adv); + var button = GetSelectedButton(buttons, adv); + if (adv) + { + button.onClick.Invoke(); + } + else if (state != State.None) + { + buttons = GetRelevantButtons(State.None, adv); + buttons[(int)state].onClick.Invoke(); + } + } + } + private Button GetSelectedButton(Button[] buttons, bool adv) + { + foreach (var button in buttons) + { + // Adv buttons are huge so they often intersect with mouse cursor and catch focus unintentionally. + if (button.name.EndsWith("+", StringComparison.Ordinal) + || (adv && button.currentSelectionState == Selectable.SelectionState.Highlighted)) + { + button.name = button.name.TrimEnd('+'); + button.DoStateTransition(Selectable.SelectionState.Normal, false); + return button; + } + } + return null; + } + private Button[] GetRelevantButtons(State state, bool adv) + { + return adv ? GetADVChoices() : state == State.None ? GetMainButtons() : GetCurrentContents(state); + } + private void MarkButton(Button button, bool adv) + { + // We modify button name to not lose track in case the player + // manually highlights the button with the controller. + button.DoStateTransition(adv ? Selectable.SelectionState.Highlighted : Selectable.SelectionState.Pressed, false); + button.name += "+"; + } + private Button[] GetMainButtons() + { +#if KK + + return [talkScene.buttonTalk, talkScene.buttonListen, talkScene.buttonEvent]; +#else + var length = talkScene.buttonInfos.Length; + var buttons = new Button[length]; + for (int i = 0; i < length; i++) + { + buttons[i] = talkScene.buttonInfos[i].button; + } + return buttons; +#endif + //return new Button[] { talkScene.buttonInfos. talkScene.buttonTalk, talkScene.buttonListen, talkScene.buttonEvent }; + } + + private Button[] GetADVChoices() + { +#if KK + + return Game.Instance.actScene.advScene.scenario.choices.GetComponentsInChildren