From d1f18adc62c74021575fd19e9e64ef8b244ae6eb Mon Sep 17 00:00:00 2001 From: redseiko Date: Sat, 18 Sep 2021 21:14:49 -0700 Subject: [PATCH 01/26] Rewrite Gizmo for ComfyGizmo use. Support rebinding keys and simplify code to one file. --- Gizmo/Gizmo/ComfyGizmo.cs | 167 +++++++++++ Gizmo/Gizmo/Gizmo.csproj | 64 +++-- Gizmo/Gizmo/Plugin.cs | 124 --------- Gizmo/Gizmo/Properties/AssemblyInfo.cs | 6 +- Gizmo/Gizmo/UpdatePlacementGhost_Patch.cs | 42 --- Gizmo/Gizmo/UpdatePlacement_Patch.cs | 20 -- Gizmo/Gizmo/Utilities/ReflectionUtil.cs | 321 ---------------------- 7 files changed, 213 insertions(+), 531 deletions(-) create mode 100644 Gizmo/Gizmo/ComfyGizmo.cs delete mode 100644 Gizmo/Gizmo/Plugin.cs delete mode 100644 Gizmo/Gizmo/UpdatePlacementGhost_Patch.cs delete mode 100644 Gizmo/Gizmo/UpdatePlacement_Patch.cs delete mode 100644 Gizmo/Gizmo/Utilities/ReflectionUtil.cs diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs new file mode 100644 index 0000000..7970dfd --- /dev/null +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -0,0 +1,167 @@ +using BepInEx; +using BepInEx.Configuration; + +using HarmonyLib; + +using RoloPogo.Utils; + +using System; +using System.Collections.Generic; +using System.Reflection; +using System.Reflection.Emit; +using UnityEngine; + +namespace Gizmo { + [BepInPlugin(PluginGUID, PluginName, PluginVersion)] + public class ComfyGizmo : BaseUnityPlugin { + public const string PluginGUID = "com.rolopogo.gizmo.comfy"; + public const string PluginName = "ComfyGizmo"; + public const string PluginVersion = "1.0.0"; + + static ConfigEntry _snapDivisions; + static ConfigEntry _xRotationKey; + static ConfigEntry _zRotationKey; + static ConfigEntry _resetRotationKey; + + static GameObject _gizmoPrefab = null; + static Transform _gizmoRoot; + + static Transform _xGizmo; + static Transform _yGizmo; + static Transform _zGizmo; + + static Transform _xGizmoRoot; + static Transform _yGizmoRoot; + static Transform _zGizmoRoot; + + static int _xRot; + static int _yRot; + static int _zRot; + + static float _snapAngle; + + Harmony _harmony; + + public void Awake() { + _snapDivisions = + Config.Bind("Gizmo", "snapDivisions", 16, "Number of snap angles per 180 degrees. Vanilla uses 8."); + + _snapDivisions.SettingChanged += (sender, eventArgs) => _snapAngle = 180f / _snapDivisions.Value; + _snapAngle = 180f / _snapDivisions.Value; + + _xRotationKey = + Config.Bind( + "Gizmo", + "xRotationKey", + new KeyboardShortcut(KeyCode.LeftShift), + "Hold this key to rotate on the x-axis/plane (red circle)."); + + _zRotationKey = + Config.Bind( + "Gizmo", + "zRotationKey", + new KeyboardShortcut(KeyCode.LeftAlt), + "Hold this key to rotate on the z-axis/plane (blue circle)."); + + _resetRotationKey = + Config.Bind( + "Gizmo", + "resetRotationKey", + new KeyboardShortcut(KeyCode.V), + "Press this key to reset the selected axis to zero rotation."); + + _gizmoPrefab = LoadGizmoPrefab(); + _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), harmonyInstanceId: PluginGUID); + } + + public void OnDestroy() { + _harmony?.UnpatchSelf(); + } + + [HarmonyPatch(typeof(Player))] + class PlayerPatch { + [HarmonyTranspiler] + [HarmonyPatch(nameof(Player.UpdatePlacementGhost))] + static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable instructions) { + return new CodeMatcher(instructions) + .MatchForward( + useEnd: false, + new CodeMatch( + OpCodes.Call, + AccessTools.Method( + typeof(Quaternion), + nameof(Quaternion.Euler), + new Type[] { typeof(float), typeof(float), typeof(float) }))) + .SetAndAdvance( + OpCodes.Call, + Transpilers.EmitDelegate>( + (x, y, z) => _xGizmoRoot.rotation).operand) + .InstructionEnumeration(); + } + + [HarmonyPostfix] + [HarmonyPatch(nameof(Player.UpdatePlacement))] + static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { + _gizmoRoot ??= CreateGizmoRoot(); + + if (__instance.m_placementMarkerInstance) { + _gizmoRoot.gameObject.SetActive(__instance.m_placementMarkerInstance.activeSelf); + _gizmoRoot.position = __instance.m_placementMarkerInstance.transform.position + (Vector3.up * 0.5f); + } + + if (!__instance.m_buildPieces || !takeInput) { + return; + } + + _xGizmo.localScale = Vector3.one; + _yGizmo.localScale = Vector3.one; + _zGizmo.localScale = Vector3.one; + + if (Input.GetKey(_xRotationKey.Value.MainKey)) { + HandleAxisInput(ref _xRot, _xGizmo); + } else if (Input.GetKey(_zRotationKey.Value.MainKey)) { + HandleAxisInput(ref _zRot, _zGizmo); + } else { + HandleAxisInput(ref _yRot, _yGizmo); + } + + _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); + _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); + _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); + } + } + + private static void HandleAxisInput(ref int rotation, Transform gizmo) { + gizmo.localScale = Vector3.one * 1.5f; + rotation = (rotation + Math.Sign(Input.GetAxis("Mouse ScrollWheel"))) % (_snapDivisions.Value * 2); + + if (Input.GetKey(_resetRotationKey.Value.MainKey)) { + rotation = 0; + } + } + + static GameObject LoadGizmoPrefab() { + AssetBundle bundle = AssetBundle.LoadFromMemory( + ResourceUtils.GetResource(Assembly.GetExecutingAssembly(), "Gizmo.Resources.gizmos")); + + GameObject prefab = bundle.LoadAsset("GizmoRoot"); + bundle.Unload(unloadAllLoadedObjects: false); + + return prefab; + } + + static Transform CreateGizmoRoot() { + _gizmoRoot = Instantiate(_gizmoPrefab).transform; + + _xGizmo = _gizmoRoot.Find("YRoot/ZRoot/XRoot/X"); + _yGizmo = _gizmoRoot.Find("YRoot/Y"); + _zGizmo = _gizmoRoot.Find("YRoot/ZRoot/Z"); + + _xGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot/XRoot"); + _yGizmoRoot = _gizmoRoot.Find("YRoot"); + _zGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot"); + + return _gizmoRoot.transform; + } + } +} diff --git a/Gizmo/Gizmo/Gizmo.csproj b/Gizmo/Gizmo/Gizmo.csproj index 94a51ca..ee44112 100644 --- a/Gizmo/Gizmo/Gizmo.csproj +++ b/Gizmo/Gizmo/Gizmo.csproj @@ -9,8 +9,11 @@ Properties Gizmo Gizmo - v4.6 + v4.8 512 + 9 + enable + true true @@ -30,56 +33,75 @@ prompt 4 + + + + $(HOME)/.steam/steam/steamapps/common/Valheim + $(HOME)/Library/Application Support/Steam/steamapps/common/Valheim/Contents/MacOS + + + + + $([MSBuild]::GetRegistryValueFromView('HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\Uninstall\Steam App 892970', 'InstallLocation', null, RegistryView.Registry64, RegistryView.Registry32)) + <_SteamLibraryPath>$([MSBuild]::GetRegistryValueFromView('HKEY_CURRENT_USER\SOFTWARE\Valve\Steam', 'SteamPath', null, RegistryView.Registry32)) + $(_SteamLibraryPath)\steamapps\common\Valheim + C:\Program Files\Steam\steamapps\common\Valheim + C:\Program Files (x86)\Steam\steamapps\common\Valheim + + + - ..\..\..\..\..\AppData\Roaming\r2modmanPlus-local\Valheim\profiles\Modded\BepInEx\core\0Harmony.dll + $(GamePath)\BepInEx\core\0Harmony.dll - - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\assembly_utils.dll + + $(GamePath)\valheim_Data\Managed\publicized_assemblies\assembly_guiutils_publicized.dll - - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\assembly_valheim.dll + + $(GamePath)\valheim_Data\Managed\publicized_assemblies\assembly_utils_publicized.dll + + + $(GamePath)\valheim_Data\Managed\publicized_assemblies\assembly_valheim_publicized.dll - ..\..\..\..\..\AppData\Roaming\r2modmanPlus-local\Valheim\profiles\Modded\BepInEx\core\BepInEx.dll + $(GamePath)\BepInEx\core\BepInEx.dll - - - - - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\UnityEngine.dll + $(GamePath)\unstripped_corlib\UnityEngine.dll - ..\..\..\..\..\AppData\Roaming\r2modmanPlus-local\Valheim\profiles\Dev\unstripped_corlib\UnityEngine.AssetBundleModule.dll + $(GamePath)\unstripped_corlib\UnityEngine.AssetBundleModule.dll - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\UnityEngine.CoreModule.dll + $(GamePath)\unstripped_corlib\UnityEngine.CoreModule.dll - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\UnityEngine.InputLegacyModule.dll + $(GamePath)\unstripped_corlib\UnityEngine.InputLegacyModule.dll - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\UnityEngine.PhysicsModule.dll + $(GamePath)\unstripped_corlib\UnityEngine.PhysicsModule.dll + + + $(GamePath)\unstripped_corlib\UnityEngine.TextRenderingModule.dll - ..\..\..\..\..\..\..\Program Files (x86)\Steam\steamapps\common\Valheim\valheim_Data\Managed\UnityEngine.UI.dll + $(GamePath)\unstripped_corlib\UnityEngine.UI.dll - + - - - + + + \ No newline at end of file diff --git a/Gizmo/Gizmo/Plugin.cs b/Gizmo/Gizmo/Plugin.cs deleted file mode 100644 index b35c139..0000000 --- a/Gizmo/Gizmo/Plugin.cs +++ /dev/null @@ -1,124 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Reflection.Emit; -using System.Text; -using System.Threading.Tasks; -using BepInEx; -using BepInEx.Configuration; -using HarmonyLib; -using RoloPogo.Utilities; -using RoloPogo.Utils; -using UnityEngine; - -namespace Gizmo -{ - [BepInPlugin("com.rolopogo.Gizmo","Gizmo", "1.0.0")] - public class Plugin : BaseUnityPlugin - { - public static Plugin instance; - - int xRot; - int yRot; - int zRot; - - Transform gizmoRoot; - - Transform xGizmo; - Transform yGizmo; - Transform zGizmo; - - Transform xGizmoRoot; - Transform yGizmoRoot; - Transform zGizmoRoot; - - private ConfigEntry snapDivisions; - private ConfigEntry xKey; - private ConfigEntry zKey; - private ConfigEntry resetKey; - - float snapAngle => 180f / snapDivisions.Value; - - GameObject gizmoPrefab; - - private void Awake() - { - instance = this; - snapDivisions = Config.Bind("General", "SnapDivisions", 16, "Number of snap angles per 180 degrees. Vanilla uses 8"); - xKey = Config.Bind("General", "xKey", "LeftShift", "Hold this key to rotate in the x plane (red circle)"); - zKey = Config.Bind("General", "zKey", "LeftAlt", "Hold this key to rotate in the z plane (blue circle)"); - resetKey = Config.Bind("General", "resetKey", "V", "Press this key to reset the selected axis to zero rotation"); - - var bundle = AssetBundle.LoadFromMemory(ResourceUtils.GetResource(Assembly.GetExecutingAssembly(), "Gizmo.Resources.gizmos")); - gizmoPrefab = bundle.LoadAsset("GizmoRoot"); - bundle.Unload(false); - - Harmony.CreateAndPatchAll(typeof(UpdatePlacementGhost_Patch)); - Harmony.CreateAndPatchAll(typeof(UpdatePlacement_Patch)); - } - - public void UpdatePlacement(Player player, GameObject placementGhost, bool takeInput) { - if (player != Player.m_localPlayer) return; - - if (!gizmoRoot) - { - gizmoRoot = Instantiate(gizmoPrefab).transform; - xGizmo = gizmoRoot.Find("YRoot/ZRoot/XRoot/X"); - yGizmo = gizmoRoot.Find("YRoot/Y"); - zGizmo = gizmoRoot.Find("YRoot/ZRoot/Z"); - xGizmoRoot = gizmoRoot.Find("YRoot/ZRoot/XRoot"); - yGizmoRoot = gizmoRoot.Find("YRoot"); - zGizmoRoot = gizmoRoot.Find("YRoot/ZRoot"); - } - var marker = player.GetPrivateField("m_placementMarkerInstance"); - if (marker) - { - gizmoRoot.gameObject.SetActive(marker.activeSelf); - gizmoRoot.position = marker.transform.position + Vector3.up * .5f; - } - - if (!player.InPlaceMode()) - return; - - if (!takeInput) - return; - - xGizmo.localScale = Vector3.one; - yGizmo.localScale = Vector3.one; - zGizmo.localScale = Vector3.one; - - var scrollWheelInput = Math.Sign(Input.GetAxis("Mouse ScrollWheel")); - - if (Enum.TryParse(xKey.Value, out var xKeyCode) && Input.GetKey(xKeyCode)) - { - HandleAxisInput(scrollWheelInput, ref xRot, xGizmo); - } - else if (Enum.TryParse(zKey.Value, out var zKeyCode) && Input.GetKey(zKeyCode)) - { - HandleAxisInput(scrollWheelInput, ref zRot, zGizmo); - } - else - { - HandleAxisInput(scrollWheelInput, ref yRot, yGizmo); - } - - xGizmoRoot.localRotation = Quaternion.Euler(xRot * snapAngle, 0, 0); - yGizmoRoot.localRotation = Quaternion.Euler(0, yRot * snapAngle, 0); - zGizmoRoot.localRotation = Quaternion.Euler(0, 0, zRot * snapAngle); - } - - private void HandleAxisInput(int scrollWheelInput, ref int rot, Transform gizmo) - { - gizmo.localScale = Vector3.one * 1.5f; - rot = (rot + scrollWheelInput) % (snapDivisions.Value*2); - if (Enum.TryParse(resetKey.Value, out var resetKeyCode) && Input.GetKey(resetKeyCode)) - rot = 0; - } - - private static Quaternion GetPlacementAngle(float x, float y, float z) - { - return instance.xGizmoRoot.rotation; - } - } -} diff --git a/Gizmo/Gizmo/Properties/AssemblyInfo.cs b/Gizmo/Gizmo/Properties/AssemblyInfo.cs index 1edf78e..3264e3d 100644 --- a/Gizmo/Gizmo/Properties/AssemblyInfo.cs +++ b/Gizmo/Gizmo/Properties/AssemblyInfo.cs @@ -10,7 +10,7 @@ [assembly: AssemblyConfiguration("")] [assembly: AssemblyCompany("")] [assembly: AssemblyProduct("Gizmo")] -[assembly: AssemblyCopyright("Copyright © 2021")] +[assembly: AssemblyCopyright("Copyright © 2021")] [assembly: AssemblyTrademark("")] [assembly: AssemblyCulture("")] @@ -32,5 +32,5 @@ // 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")] +[assembly: AssemblyVersion(Gizmo.ComfyGizmo.PluginVersion)] +[assembly: AssemblyFileVersion(Gizmo.ComfyGizmo.PluginVersion)] diff --git a/Gizmo/Gizmo/UpdatePlacementGhost_Patch.cs b/Gizmo/Gizmo/UpdatePlacementGhost_Patch.cs deleted file mode 100644 index 0101dc0..0000000 --- a/Gizmo/Gizmo/UpdatePlacementGhost_Patch.cs +++ /dev/null @@ -1,42 +0,0 @@ -using HarmonyLib; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection.Emit; -using System.Text; -using System.Threading.Tasks; - -namespace Gizmo -{ - [HarmonyPatch(typeof(Player), "UpdatePlacementGhost")] - public static class UpdatePlacementGhost_Patch - { - static IEnumerable Transpiler(IEnumerable instructions) - { - var placementAnglePatched = false; - var codes = new List(instructions); - for (var i = 0; i < codes.Count; i++) - { - if(!placementAnglePatched) - if (codes[i].opcode == OpCodes.Callvirt && - codes[i + 1].opcode == OpCodes.Ldc_R4 && - codes[i + 2].opcode == OpCodes.Ldc_R4 && - codes[i + 3].opcode == OpCodes.Ldarg_0 && - codes[i + 4].opcode == OpCodes.Ldfld && - codes[i + 5].opcode == OpCodes.Conv_R4 && - codes[i + 6].opcode == OpCodes.Mul && - codes[i + 7].opcode == OpCodes.Ldc_R4 && - codes[i + 8].opcode == OpCodes.Call - ) - - { - codes[i + 8] = CodeInstruction.Call(typeof(Plugin), "GetPlacementAngle"); - placementAnglePatched = true; - } - - - } - return codes.AsEnumerable(); - } - } -} diff --git a/Gizmo/Gizmo/UpdatePlacement_Patch.cs b/Gizmo/Gizmo/UpdatePlacement_Patch.cs deleted file mode 100644 index e730091..0000000 --- a/Gizmo/Gizmo/UpdatePlacement_Patch.cs +++ /dev/null @@ -1,20 +0,0 @@ -using HarmonyLib; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; -using UnityEngine; - -namespace Gizmo -{ - class UpdatePlacement_Patch - { - [HarmonyPatch(typeof(Player), "UpdatePlacement", new Type[] { typeof(bool), typeof(float) })] - [HarmonyPostfix] - private static void Player_UpdatePlacement(Player __instance, GameObject ___m_placementGhost, bool takeInput, float dt) - { - Plugin.instance.UpdatePlacement(__instance, ___m_placementGhost, takeInput); - } - } -} diff --git a/Gizmo/Gizmo/Utilities/ReflectionUtil.cs b/Gizmo/Gizmo/Utilities/ReflectionUtil.cs deleted file mode 100644 index 47daad9..0000000 --- a/Gizmo/Gizmo/Utilities/ReflectionUtil.cs +++ /dev/null @@ -1,321 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using UnityEngine; - -namespace RoloPogo.Utilities -{ - public static class ReflectionUtil - { - private const BindingFlags _allBindingFlags = BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic; - - - /// - /// Sets a field on the target object. specifies the the field belongs to. - /// - /// the object instance - /// the field to set - /// the value to set it to - /// the object the field belongs to - /// thrown when is not a member of - /// thrown when isn't assignable as - public static void SetField(this object obj, string fieldName, object value, Type targetType) - { - var prop = targetType.GetField(fieldName, _allBindingFlags); - if (prop == null) - throw new InvalidOperationException($"{fieldName} is not a member of {targetType.Name}"); - prop.SetValue(obj, value); - } - - /// - /// Gets the value of a field. specifies the the field belongs to. - /// - /// the object instance to pull from - /// the name of the field to read - /// the object the field belongs to - /// the value of the field - /// thrown when is not a member of - /// thrown when isn't assignable as - public static object GetField(this object obj, string fieldName, Type targetType) - { - var prop = targetType.GetField(fieldName, _allBindingFlags); - if (prop == null) - throw new InvalidOperationException($"{fieldName} is not a member of {targetType.Name}"); - return prop.GetValue(obj); - } - - /// - /// Sets a private field on the target object. specifies the the field belongs to. - /// - /// the object instance - /// the field to set - /// the value to set it to - /// the object the field belongs to - /// thrown when is not a member of - /// thrown when isn't assignable as - public static void SetPrivateField(this object obj, string fieldName, object value, Type targetType) - { - var prop = targetType.GetField(fieldName, BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); - if (prop == null) - throw new InvalidOperationException($"{fieldName} is not a member of {targetType.Name}"); - prop.SetValue(obj, value); - } - - /// - /// Gets the value of a private field. specifies the the field belongs to. - /// - /// the type of the field (result casted) - /// the object instance to pull from - /// the name of the field to read - /// the object the field belongs to - /// the value of the field - /// thrown when is not a member of - /// thrown when isn't assignable as - public static T GetPrivateField(this object obj, string fieldName, Type targetType) - { - var prop = targetType.GetField(fieldName, BindingFlags.NonPublic | BindingFlags.Instance); - if (prop == null) - throw new InvalidOperationException($"{fieldName} is not a member of {targetType.Name}"); - var value = prop.GetValue(obj); - return (T)value; - } - - - /// - /// Sets the value of a property on the target object. specifies the the property belongs to. - /// - /// the object instance - /// the property to set - /// the value to set it to - /// the object the property belongs to - /// thrown when is not a member of - /// thrown when isn't assignable as - public static void SetProperty(this object obj, string propertyName, object value, Type targetType) - { - var prop = targetType.GetProperty(propertyName, _allBindingFlags); - if (prop == null) - throw new InvalidOperationException($"{propertyName} is not a member of {targetType.Name}"); - prop.SetValue(obj, value); - } - - /// - /// Gets the value of a property. specifies the the field belongs to. - /// - /// the object instance to pull from - /// the name of the property to read - /// the object the property belongs to - /// the value of the property - /// thrown when is not a member of - /// thrown when isn't assignable as - public static object GetProperty(this object obj, string propertyName, Type targetType) - { - var prop = targetType.GetProperty(propertyName, _allBindingFlags); - if (prop == null) - throw new InvalidOperationException($"{propertyName} is not a member of {targetType.Name}"); - var value = prop.GetValue(obj); - return value; - } - - - #region Overloads - /// - /// Sets a private field on the target object. - /// - /// the object instance - /// the field to set - /// the value to set it to - /// the object the field belongs to - /// thrown when is not a member of - public static void SetPrivateField(this object obj, string fieldName, object value) => obj.SetPrivateField(fieldName, value, obj.GetType()); - - - /// - /// Gets the value of a private field. - /// - /// the type of te field (result casted) - /// the object instance to pull from - /// the name of the field to read - /// the value of the field - /// thrown when is not a member of - public static T GetPrivateField(this object obj, string fieldName) => obj.GetPrivateField(fieldName, obj.GetType()); - - /// - /// Sets a (potentially) private field on the target object. - /// - /// the object instance - /// the field to set - /// the value to set it to - /// thrown when is not a member of - public static void SetField(this object obj, string fieldName, object value) => obj.SetField(fieldName, value, obj.GetType()); - - /// - /// Gets the value of an object's field. - /// - /// the object instance to pull from - /// the name of the field to read - /// the value of the field - /// thrown when is not a member of - public static object GetField(this object obj, string fieldName) => GetField(obj, fieldName, obj.GetType()); - - - /// - /// Gets the value of a field. specifies the the field belongs to. - /// - /// the type of the field (result casted) - /// the object instance to pull from - /// the name of the field to read - /// the object the field belongs to - /// the value of the field - /// thrown when is not a member of - /// thrown when isn't assignable as - public static T GetField(this object obj, string fieldName, Type targetType) => (T)obj.GetField(fieldName, targetType); - - - /// - /// Gets the casted value of a field. - /// - /// the type of the field (result casted) - /// the object instance to pull from - /// the name of the field to read - /// the value of the field - /// thrown when is not a member of - public static T GetField(this object obj, string fieldName) => (T)GetField(obj, fieldName, obj.GetType()); - - /// - /// Sets the value of a property on the target object. - /// - /// the object instance - /// the property to set - /// the value to set it to - /// thrown when is not a member of - public static void SetProperty(this object obj, string propertyName, object value) => obj.SetProperty(propertyName, value, obj.GetType()); - - /// - /// Gets the value of a property. - /// - /// the object instance to pull from - /// the name of the property to read - /// the value of the property - /// thrown when is not a member of - public static object GetProperty(this object obj, string propertyName) => obj.GetProperty(propertyName, obj.GetType()); - - /// - /// Gets the casted value of a property. - /// - /// they type of the property - /// the object instance to pull from - /// the name of the property to read - /// the value of the property - /// thrown when is not a member of - public static T GetProperty(this object obj, string propertyName) => (T)GetProperty(obj, propertyName); - - #endregion - - - - //Invokes a (static?) private method with name "methodName" and params "methodParams", returns an object of the specified type - public static T InvokeMethod(this object obj, string methodName, params object[] methodParams) => (T)InvokeMethod(obj, methodName, methodParams); - - //Invokes a (static?) private method with name "methodName" and params "methodParams" - public static object InvokeMethod(this object obj, string methodName, params object[] methodParams) - { - MethodInfo method = obj.GetType().GetMethod(methodName, _allBindingFlags); - if (method == null) - throw new InvalidOperationException($"{methodName} is not a member of {obj.GetType().Name}"); - return method.Invoke(obj, methodParams); - } - - //Returns a constructor with the specified parameters to the specified type or object - public static object InvokeConstructor(this object obj, params object[] constructorParams) - { - Type[] types = new Type[constructorParams.Length]; - for (int i = 0; i < constructorParams.Length; i++) types[i] = constructorParams[i].GetType(); - return (obj is Type ? (Type)obj : obj.GetType()) - .GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, types, null) - .Invoke(constructorParams); - } - - //Returns a Type object which can be used to invoke static methods with the above helpers - public static Type GetStaticType(string clazz) - { - return Type.GetType(clazz); - } - - //Returns a list (of strings) of the names of all loaded assemblies - public static IEnumerable ListLoadedAssemblies() - { - return AppDomain.CurrentDomain.GetAssemblies(); - } - - //Returns a list of all loaded namespaces - //TODO: Check up on time complexity here, could potentially be parallelized - public static IEnumerable ListNamespacesInAssembly(Assembly assembly) - { - IEnumerable ret = Enumerable.Empty(); - ret = ret.Concat(assembly.GetTypes() - .Select(t => t.Namespace) - .Distinct() - .Where(n => n != null)); - return ret.Distinct(); - } - - //Returns a list of classes in a namespace - //TODO: Check up on time complexity here, could potentially be parallelized - public static IEnumerable ListClassesInNamespace(string ns) - { - //For each loaded assembly - foreach (var assembly in AppDomain.CurrentDomain.GetAssemblies()) - { - //If the assembly contains the desired namespace - if (assembly.GetTypes().Where(t => t.Namespace == ns).Any()) - { - //Select the types we want from the namespace and return them - return assembly.GetTypes() - .Where(t => t.IsClass) - .Select(t => t.Name); - } - } - return null; - } - - //(Created by taz?) Copies a component to a destination object, keeping all its field values? - public static Behaviour CopyComponent(Behaviour original, Type originalType, Type overridingType, GameObject destination) - { - Behaviour copy = null; - - try - { - copy = destination.AddComponent(overridingType) as Behaviour; - } - catch (Exception) - { - - } - - copy.enabled = false; - - //Copy types of super classes as well as our class - Type type = originalType; - while (type != typeof(MonoBehaviour)) - { - CopyForType(type, original, copy); - type = type.BaseType; - } - - copy.enabled = true; - return copy; - } - - //(Created by taz?) Copies a Component of Type type, and all its fields - private static void CopyForType(Type type, Component source, Component destination) - { - FieldInfo[] myObjectFields = type.GetFields(BindingFlags.NonPublic | BindingFlags.Public | BindingFlags.Instance | BindingFlags.GetField | BindingFlags.SetField); - - foreach (FieldInfo fi in myObjectFields) - { - fi.SetValue(destination, fi.GetValue(source)); - } - } - } -} \ No newline at end of file From 88767ec911dd21edd5adf5004e2e5e5075168dae Mon Sep 17 00:00:00 2001 From: redseiko Date: Sat, 18 Sep 2021 21:37:01 -0700 Subject: [PATCH 02/26] Turned "SnapDivisions" into a slider, moved Resources.GetResource() into the main plugin file. --- Gizmo/Gizmo/ComfyGizmo.cs | 36 ++++++++++++++++++++------ Gizmo/Gizmo/Gizmo.csproj | 1 - Gizmo/Gizmo/Utilities/ResourceUtils.cs | 20 -------------- 3 files changed, 28 insertions(+), 29 deletions(-) delete mode 100644 Gizmo/Gizmo/Utilities/ResourceUtils.cs diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 7970dfd..c6d621f 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -3,12 +3,12 @@ using HarmonyLib; -using RoloPogo.Utils; - using System; using System.Collections.Generic; +using System.IO; using System.Reflection; using System.Reflection.Emit; + using UnityEngine; namespace Gizmo { @@ -19,10 +19,13 @@ public class ComfyGizmo : BaseUnityPlugin { public const string PluginVersion = "1.0.0"; static ConfigEntry _snapDivisions; + static ConfigEntry _xRotationKey; static ConfigEntry _zRotationKey; static ConfigEntry _resetRotationKey; + static ConfigEntry _showGizmoPrefab; + static GameObject _gizmoPrefab = null; static Transform _gizmoRoot; @@ -44,32 +47,40 @@ public class ComfyGizmo : BaseUnityPlugin { public void Awake() { _snapDivisions = - Config.Bind("Gizmo", "snapDivisions", 16, "Number of snap angles per 180 degrees. Vanilla uses 8."); + Config.Bind( + "Gizmo", + "snapDivisions", + 16, + new ConfigDescription( + "Number of snap angles per 180 degrees. Vanilla uses 8.", + new AcceptableValueRange(2, 128))); _snapDivisions.SettingChanged += (sender, eventArgs) => _snapAngle = 180f / _snapDivisions.Value; _snapAngle = 180f / _snapDivisions.Value; _xRotationKey = Config.Bind( - "Gizmo", + "Keys", "xRotationKey", new KeyboardShortcut(KeyCode.LeftShift), "Hold this key to rotate on the x-axis/plane (red circle)."); _zRotationKey = Config.Bind( - "Gizmo", + "Keys", "zRotationKey", new KeyboardShortcut(KeyCode.LeftAlt), "Hold this key to rotate on the z-axis/plane (blue circle)."); _resetRotationKey = Config.Bind( - "Gizmo", + "Keys", "resetRotationKey", new KeyboardShortcut(KeyCode.V), "Press this key to reset the selected axis to zero rotation."); + _showGizmoPrefab = Config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); + _gizmoPrefab = LoadGizmoPrefab(); _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), harmonyInstanceId: PluginGUID); } @@ -105,7 +116,7 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { _gizmoRoot ??= CreateGizmoRoot(); if (__instance.m_placementMarkerInstance) { - _gizmoRoot.gameObject.SetActive(__instance.m_placementMarkerInstance.activeSelf); + _gizmoRoot.gameObject.SetActive(_showGizmoPrefab.Value && __instance.m_placementMarkerInstance.activeSelf); _gizmoRoot.position = __instance.m_placementMarkerInstance.transform.position + (Vector3.up * 0.5f); } @@ -142,7 +153,7 @@ private static void HandleAxisInput(ref int rotation, Transform gizmo) { static GameObject LoadGizmoPrefab() { AssetBundle bundle = AssetBundle.LoadFromMemory( - ResourceUtils.GetResource(Assembly.GetExecutingAssembly(), "Gizmo.Resources.gizmos")); + GetResource(Assembly.GetExecutingAssembly(), "Gizmo.Resources.gizmos")); GameObject prefab = bundle.LoadAsset("GizmoRoot"); bundle.Unload(unloadAllLoadedObjects: false); @@ -150,6 +161,15 @@ static GameObject LoadGizmoPrefab() { return prefab; } + static byte[] GetResource(Assembly assembly, string resourceName) { + Stream stream = assembly.GetManifestResourceStream(resourceName); + + byte[] data = new byte[stream.Length]; + stream.Read(data, offset: 0, count: (int) stream.Length); + + return data; + } + static Transform CreateGizmoRoot() { _gizmoRoot = Instantiate(_gizmoPrefab).transform; diff --git a/Gizmo/Gizmo/Gizmo.csproj b/Gizmo/Gizmo/Gizmo.csproj index ee44112..55560ad 100644 --- a/Gizmo/Gizmo/Gizmo.csproj +++ b/Gizmo/Gizmo/Gizmo.csproj @@ -95,7 +95,6 @@ - diff --git a/Gizmo/Gizmo/Utilities/ResourceUtils.cs b/Gizmo/Gizmo/Utilities/ResourceUtils.cs deleted file mode 100644 index 459b97a..0000000 --- a/Gizmo/Gizmo/Utilities/ResourceUtils.cs +++ /dev/null @@ -1,20 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Reflection; -using System.Text; -using System.Threading.Tasks; - -namespace RoloPogo.Utils -{ - static class ResourceUtils - { - public static byte[] GetResource(Assembly asm, string ResourceName) - { - System.IO.Stream stream = asm.GetManifestResourceStream(ResourceName); - byte[] data = new byte[stream.Length]; - stream.Read(data, 0, (int)stream.Length); - return data; - } - } -} From be321d20659a05664b3493e7f0050b9dcf43aa7e Mon Sep 17 00:00:00 2001 From: redseiko Date: Tue, 1 Mar 2022 18:18:05 -0800 Subject: [PATCH 03/26] Modified GizmoRoot instantiate to trigger on Game.Start(). Added manifest/icon/README from existing Thunderstore original Gizmo. --- Gizmo/Gizmo/ComfyGizmo.cs | 14 +++++++++++--- Gizmo/Gizmo/README.md | 36 ++++++++++++++++++++++++++++++++++++ Gizmo/Gizmo/icon.png | Bin 0 -> 52201 bytes Gizmo/Gizmo/manifest.json | 10 ++++++++++ 4 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 Gizmo/Gizmo/README.md create mode 100644 Gizmo/Gizmo/icon.png create mode 100644 Gizmo/Gizmo/manifest.json diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index c6d621f..216b1c2 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -16,7 +16,7 @@ namespace Gizmo { public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.0.0"; + public const string PluginVersion = "1.2.0"; static ConfigEntry _snapDivisions; @@ -89,6 +89,16 @@ public void OnDestroy() { _harmony?.UnpatchSelf(); } + [HarmonyPatch(typeof(Game))] + class GamePatch { + [HarmonyPostfix] + [HarmonyPatch(nameof(Game.Start))] + static void StartPostfix() { + Destroy(_gizmoRoot); + _gizmoRoot = CreateGizmoRoot(); + } + } + [HarmonyPatch(typeof(Player))] class PlayerPatch { [HarmonyTranspiler] @@ -113,8 +123,6 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable Import local mod > select `ComfyGizmo_v1.1.0.zip` + 3. Click OK on the pop-up for information + +## Changelog + +### 1.2.0 + + * Modified GizmoRoot instantiate to trigger on `Game.Start()`. + +### 1.1.0 + + * Turn SnapDivisions into a slider. + * Moved `Resources.GetResources()` into the main plugin file. + +### 1.0.0 + + * Rewrite of Gizmo for Comfy-specific use. + * Supprot re-binding of modifier keys and simplify code to one file. \ No newline at end of file diff --git a/Gizmo/Gizmo/icon.png b/Gizmo/Gizmo/icon.png new file mode 100644 index 0000000000000000000000000000000000000000..4e12b03eaaeebbff42ee3cdd15882143543f7714 GIT binary patch literal 52201 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 literal 0 HcmV?d00001 diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json new file mode 100644 index 0000000..85b2fe8 --- /dev/null +++ b/Gizmo/Gizmo/manifest.json @@ -0,0 +1,10 @@ +{ + "name": "Gizmo", + "version_number": "1.2.0", + "author": "redseiko", + "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", + "description": "Comfy-specific fork of Gizmo.", + "dependencies": [ + "denikson-BepInExPack_Valheim-5.4.1900" + ] +} \ No newline at end of file From dd9e97953350ff4055b347231f2d34eee864fe0c Mon Sep 17 00:00:00 2001 From: redseiko Date: Fri, 4 Mar 2022 17:21:30 -0800 Subject: [PATCH 04/26] Update ComfyGizmo to v1.3.0. * Added configuration option for a 'reset all rotations' hot-key (default to un-set). * Cleaned-up the UpdatePlacementGhost transpiler. --- Gizmo/Gizmo/ComfyGizmo.cs | 22 ++++++++++++++++------ Gizmo/Gizmo/Gizmo.csproj | 4 ++++ Gizmo/Gizmo/README.md | 17 +++++++++++++---- Gizmo/Gizmo/manifest.json | 2 +- 4 files changed, 34 insertions(+), 11 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 216b1c2..6f141e6 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -16,13 +16,14 @@ namespace Gizmo { public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.2.0"; + public const string PluginVersion = "1.3.0"; static ConfigEntry _snapDivisions; static ConfigEntry _xRotationKey; static ConfigEntry _zRotationKey; static ConfigEntry _resetRotationKey; + static ConfigEntry _resetAllRotationKey; static ConfigEntry _showGizmoPrefab; @@ -79,6 +80,13 @@ public void Awake() { new KeyboardShortcut(KeyCode.V), "Press this key to reset the selected axis to zero rotation."); + _resetAllRotationKey = + Config.Bind( + "Keys", + "resetAllRotationKey", + KeyboardShortcut.Empty, + "Press this key to reset _all axis_ rotations to zero rotation."); + _showGizmoPrefab = Config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); _gizmoPrefab = LoadGizmoPrefab(); @@ -113,10 +121,8 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable>( - (x, y, z) => _xGizmoRoot.rotation).operand) + .SetInstructionAndAdvance( + Transpilers.EmitDelegate>((_, _, _) => _xGizmoRoot.rotation)) .InstructionEnumeration(); } @@ -136,7 +142,11 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { _yGizmo.localScale = Vector3.one; _zGizmo.localScale = Vector3.one; - if (Input.GetKey(_xRotationKey.Value.MainKey)) { + if (Input.GetKey(_resetAllRotationKey.Value.MainKey)) { + _xRot = 0; + _yRot = 0; + _zRot = 0; + } else if (Input.GetKey(_xRotationKey.Value.MainKey)) { HandleAxisInput(ref _xRot, _xGizmo); } else if (Input.GetKey(_zRotationKey.Value.MainKey)) { HandleAxisInput(ref _zRot, _zGizmo); diff --git a/Gizmo/Gizmo/Gizmo.csproj b/Gizmo/Gizmo/Gizmo.csproj index 55560ad..3bfd480 100644 --- a/Gizmo/Gizmo/Gizmo.csproj +++ b/Gizmo/Gizmo/Gizmo.csproj @@ -99,6 +99,10 @@ + + + + diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index 8cd39b1..585b810 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -1,8 +1,12 @@ -# ComfyGizmo v1.2.0 +# ComfyGizmo v1.3.0 Comfy-specific version of Gizmo. - * Configurable modifier hot-keys. + * Configurable modifier hot-keys for: + * X-axis rotation (default: hold `LeftShift`) + * Z-axis rotation (default: hold `LeftAlt`) + * Reset selected axis rotation (default: `V`) + * Reset **ALL** axis rotations (disabled by default) * Can disable the Gizmo placement visual. * Can set the snap angles per 180 degrees from 2 - 128. * Original Euler-style rotation. @@ -16,11 +20,16 @@ Comfy-specific version of Gizmo. ### Thunderstore 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo - 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.1.0.zip` - 3. Click OK on the pop-up for information + 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.3.0.zip` + 3. Click "OK/Import local mod" on the pop-up for information ## Changelog +### 1.3.0 + + * Added configuration option for a 'reset all rotations' hot-key (default to un-set). + * Cleaned-up the UpdatePlacementGhost transpiler. + ### 1.2.0 * Modified GizmoRoot instantiate to trigger on `Game.Start()`. diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json index 85b2fe8..d2e7a22 100644 --- a/Gizmo/Gizmo/manifest.json +++ b/Gizmo/Gizmo/manifest.json @@ -1,6 +1,6 @@ { "name": "Gizmo", - "version_number": "1.2.0", + "version_number": "1.3.0", "author": "redseiko", "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", "description": "Comfy-specific fork of Gizmo.", From 902ba83822a615613758dd0358d445cbb7d39081 Mon Sep 17 00:00:00 2001 From: redseiko Date: Sun, 6 Mar 2022 13:45:38 -0800 Subject: [PATCH 05/26] Update the Gizmo main-folder README.md. --- Gizmo/README.md | 29 ++++++++++------------------- 1 file changed, 10 insertions(+), 19 deletions(-) diff --git a/Gizmo/README.md b/Gizmo/README.md index 17c6430..0538cfb 100644 --- a/Gizmo/README.md +++ b/Gizmo/README.md @@ -1,19 +1,10 @@ -# Gizmo -An intuitive advanced building tool - -Client side only. - -- Rotate objects in all three axes using modifier keys and the scroll wheel -- Intuitive gizmo helps visualize rotations -- Increased rotational snap precision -- Rotate in the Y (Green) plane with no modifier keys -- Rotate in the X (Red) plane with LShift (by default) -- Rotate in the Z (Blue) plane with LAlt (by default) -- Reset an axis by holding its modifier and hitting V (by default) - -Sample Video: https://streamable.com/wio6pk -Source: https://github.com/rolopogo/ValheimMods - -Install with BepInEx - -Copy ExploreTogether.dll into Valheim/BepInEx/plugins \ No newline at end of file +# ComfyGizmo + + * Configurable modifier hot-keys for: + * X-axis rotation (default: hold `LeftShift`) + * Z-axis rotation (default: hold `LeftAlt`) + * Reset selected axis rotation (default: `V`) + * Reset **ALL** axis rotations (disabled by default) + * Can disable the Gizmo placement visual. + * Can set the snap angles per 180 degrees from 2 - 128. + * Original Euler-style rotation. \ No newline at end of file From 930bca3e9c42456a10d0fb57dcf0dd328bcc012d Mon Sep 17 00:00:00 2001 From: redseiko Date: Sat, 20 Aug 2022 01:08:59 -0700 Subject: [PATCH 06/26] Update ComfyGizmo to `v1.3.1`. * Try to add compatability with other mods that also transpile `UpdatePlacementGhost`. --- Gizmo/Gizmo/ComfyGizmo.cs | 18 +++++++++--------- Gizmo/Gizmo/Gizmo.csproj | 35 +++++++++++++++++++++++++++++++---- Gizmo/Gizmo/README.md | 6 +++++- Gizmo/Gizmo/manifest.json | 4 ++-- 4 files changed, 47 insertions(+), 16 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 6f141e6..d87e0dc 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -16,7 +16,7 @@ namespace Gizmo { public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.3.0"; + public const string PluginVersion = "1.3.1"; static ConfigEntry _snapDivisions; @@ -115,14 +115,14 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable>((_, _, _) => _xGizmoRoot.rotation)) + new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(Player), nameof(Player.m_placeRotation))), + new CodeMatch(OpCodes.Conv_R4), + new CodeMatch(OpCodes.Mul), + new CodeMatch(OpCodes.Ldc_R4), + new CodeMatch(OpCodes.Call), + new CodeMatch(OpCodes.Stloc_S)) + .Advance(offset: 5) + .InsertAndAdvance(Transpilers.EmitDelegate>(_ => _xGizmoRoot.rotation)) .InstructionEnumeration(); } diff --git a/Gizmo/Gizmo/Gizmo.csproj b/Gizmo/Gizmo/Gizmo.csproj index 3bfd480..cf45004 100644 --- a/Gizmo/Gizmo/Gizmo.csproj +++ b/Gizmo/Gizmo/Gizmo.csproj @@ -12,9 +12,7 @@ v4.8 512 9 - enable true - true true @@ -50,6 +48,9 @@ + + $(GamePath)\BepInEx\plugins + $(GamePath)\BepInEx\core\0Harmony.dll @@ -102,9 +103,35 @@ + - - + + + + + + + + + + + + %(AssemblyVersions.Identity) + $(AssemblyVersion.Split('.')[0]) + $(AssemblyVersion.Split('.')[1]) + $(AssemblyVersion.Split('.')[2]) + $(AssemblyName)_v$(Major).$(Minor).$(Patch) + $(OutDir)$(PackageName) + + + + + + + + + + \ No newline at end of file diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index 585b810..17d0a5b 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -20,11 +20,15 @@ Comfy-specific version of Gizmo. ### Thunderstore 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo - 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.3.0.zip` + 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.3.1.zip` 3. Click "OK/Import local mod" on the pop-up for information ## Changelog +### 1.3.1 + + * Try to add compatability with other mods that also transpile `UpdatePlacementGhost`. + ### 1.3.0 * Added configuration option for a 'reset all rotations' hot-key (default to un-set). diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json index d2e7a22..af275cd 100644 --- a/Gizmo/Gizmo/manifest.json +++ b/Gizmo/Gizmo/manifest.json @@ -1,10 +1,10 @@ { "name": "Gizmo", - "version_number": "1.3.0", + "version_number": "1.3.1", "author": "redseiko", "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", "description": "Comfy-specific fork of Gizmo.", "dependencies": [ - "denikson-BepInExPack_Valheim-5.4.1900" + "denikson-BepInExPack_Valheim-5.4.1901" ] } \ No newline at end of file From 0ca17627614af26cb1e3a9074be517158e0d2d4c Mon Sep 17 00:00:00 2001 From: redseiko Date: Sat, 10 Sep 2022 14:45:10 -0700 Subject: [PATCH 07/26] WIP check-in to ensure I have a copy of wtf I'm trying to do. --- Gizmo/Gizmo/ComfyGizmo.cs | 132 ++++++++++++++++++------------------ Gizmo/Gizmo/Gizmo.csproj | 5 ++ Gizmo/Gizmo/PluginConfig.cs | 57 ++++++++++++++++ 3 files changed, 127 insertions(+), 67 deletions(-) create mode 100644 Gizmo/Gizmo/PluginConfig.cs diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index d87e0dc..a5a99d0 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -1,31 +1,24 @@ -using BepInEx; -using BepInEx.Configuration; - -using HarmonyLib; - -using System; +using System; using System.Collections.Generic; using System.IO; using System.Reflection; using System.Reflection.Emit; +using BepInEx; +using BepInEx.Configuration; + +using HarmonyLib; + using UnityEngine; +using static Gizmo.PluginConfig; + namespace Gizmo { [BepInPlugin(PluginGUID, PluginName, PluginVersion)] public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.3.1"; - - static ConfigEntry _snapDivisions; - - static ConfigEntry _xRotationKey; - static ConfigEntry _zRotationKey; - static ConfigEntry _resetRotationKey; - static ConfigEntry _resetAllRotationKey; - - static ConfigEntry _showGizmoPrefab; + public const string PluginVersion = "1.4.0"; static GameObject _gizmoPrefab = null; static Transform _gizmoRoot; @@ -38,6 +31,9 @@ public class ComfyGizmo : BaseUnityPlugin { static Transform _yGizmoRoot; static Transform _zGizmoRoot; + static GameObject _comfyGizmo; + static Transform _comfyGizmoRoot; + static int _xRot; static int _yRot; static int _zRot; @@ -47,49 +43,13 @@ public class ComfyGizmo : BaseUnityPlugin { Harmony _harmony; public void Awake() { - _snapDivisions = - Config.Bind( - "Gizmo", - "snapDivisions", - 16, - new ConfigDescription( - "Number of snap angles per 180 degrees. Vanilla uses 8.", - new AcceptableValueRange(2, 128))); - - _snapDivisions.SettingChanged += (sender, eventArgs) => _snapAngle = 180f / _snapDivisions.Value; - _snapAngle = 180f / _snapDivisions.Value; - - _xRotationKey = - Config.Bind( - "Keys", - "xRotationKey", - new KeyboardShortcut(KeyCode.LeftShift), - "Hold this key to rotate on the x-axis/plane (red circle)."); - - _zRotationKey = - Config.Bind( - "Keys", - "zRotationKey", - new KeyboardShortcut(KeyCode.LeftAlt), - "Hold this key to rotate on the z-axis/plane (blue circle)."); - - _resetRotationKey = - Config.Bind( - "Keys", - "resetRotationKey", - new KeyboardShortcut(KeyCode.V), - "Press this key to reset the selected axis to zero rotation."); - - _resetAllRotationKey = - Config.Bind( - "Keys", - "resetAllRotationKey", - KeyboardShortcut.Empty, - "Press this key to reset _all axis_ rotations to zero rotation."); - - _showGizmoPrefab = Config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); + BindConfig(Config); + + SnapDivisions.SettingChanged += (sender, eventArgs) => _snapAngle = 180f / SnapDivisions.Value; + _snapAngle = 180f / SnapDivisions.Value; _gizmoPrefab = LoadGizmoPrefab(); + _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), harmonyInstanceId: PluginGUID); } @@ -98,17 +58,21 @@ public void OnDestroy() { } [HarmonyPatch(typeof(Game))] - class GamePatch { + static class GamePatch { [HarmonyPostfix] [HarmonyPatch(nameof(Game.Start))] static void StartPostfix() { Destroy(_gizmoRoot); _gizmoRoot = CreateGizmoRoot(); + + Destroy(_comfyGizmo); + _comfyGizmo = new("ComfyGizmo"); + _comfyGizmoRoot = _comfyGizmo.transform; } } [HarmonyPatch(typeof(Player))] - class PlayerPatch { + static class PlayerPatch { [HarmonyTranspiler] [HarmonyPatch(nameof(Player.UpdatePlacementGhost))] static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable instructions) { @@ -122,7 +86,7 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable>(_ => _xGizmoRoot.rotation)) + .InsertAndAdvance(Transpilers.EmitDelegate>(_ => _comfyGizmoRoot.rotation)) .InstructionEnumeration(); } @@ -130,7 +94,7 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable + @@ -105,6 +106,10 @@ + + + + diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs new file mode 100644 index 0000000..83a4d9c --- /dev/null +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -0,0 +1,57 @@ +using BepInEx.Configuration; + +using UnityEngine; + +namespace Gizmo { + public static class PluginConfig { + public static ConfigEntry SnapDivisions { get; private set; } + + public static ConfigEntry XRotationKey; + public static ConfigEntry ZRotationKey; + public static ConfigEntry ResetRotationKey; + public static ConfigEntry ResetAllRotationKey; + + public static ConfigEntry ShowGizmoPrefab; + + public static void BindConfig(ConfigFile config) { + SnapDivisions = + config.Bind( + "Gizmo", + "snapDivisions", + 16, + new ConfigDescription( + "Number of snap angles per 180 degrees. Vanilla uses 8.", + new AcceptableValueRange(2, 128))); + + XRotationKey = + config.Bind( + "Keys", + "xRotationKey", + new KeyboardShortcut(KeyCode.LeftShift), + "Hold this key to rotate on the x-axis/plane (red circle)."); + + ZRotationKey = + config.Bind( + "Keys", + "zRotationKey", + new KeyboardShortcut(KeyCode.LeftAlt), + "Hold this key to rotate on the z-axis/plane (blue circle)."); + + ResetRotationKey = + config.Bind( + "Keys", + "resetRotationKey", + new KeyboardShortcut(KeyCode.V), + "Press this key to reset the selected axis to zero rotation."); + + ResetAllRotationKey = + config.Bind( + "Keys", + "resetAllRotationKey", + KeyboardShortcut.Empty, + "Press this key to reset _all axis_ rotations to zero rotation."); + + ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); + } + } +} From 1261e7dfc39c8a33bb3adb6d2b80bf1801c232b1 Mon Sep 17 00:00:00 2001 From: redseiko Date: Fri, 16 Sep 2022 10:30:23 -0700 Subject: [PATCH 08/26] Push WIP changes to ComfyGizmo `v1.4.0`. --- Gizmo/Gizmo/ComfyGizmo.cs | 69 ++++++++++++++++++++----------------- Gizmo/Gizmo/PluginConfig.cs | 2 +- Gizmo/Gizmo/README.md | 17 ++++++--- 3 files changed, 50 insertions(+), 38 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index a5a99d0..74b2a66 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -107,7 +107,8 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { _zGizmo.localScale = Vector3.one; if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { - _comfyGizmo.transform.localRotation = Quaternion.identity; + //_comfyGizmo.transform.localRotation = Quaternion.identity; + _xRot = 0; _yRot = 0; _zRot = 0; @@ -115,43 +116,46 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { _xGizmo.localScale = Vector3.one * 1.5f; HandleAxisInput(ref _xRot, _xGizmo); - if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; - _comfyGizmo.transform.localRotation = Quaternion.Euler(0f, rotation.y, rotation.z); - } else { - _comfyGizmo.transform.Rotate( - (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle, - 0f, - 0f); - } + //if (Input.GetKey(ResetRotationKey.Value.MainKey)) { + // Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; + // _comfyGizmo.transform.localRotation = Quaternion.Euler(0f, rotation.y, rotation.z); + //} else { + // _comfyGizmo.transform.Rotate( + // (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle, + // 0f, + // 0f); + //} } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { _zGizmo.localScale = Vector3.one * 1.5f; HandleAxisInput(ref _zRot, _zGizmo); - if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; - _comfyGizmo.transform.localRotation = Quaternion.Euler(rotation.x, rotation.y, 0f); - } else { - _comfyGizmo.transform.Rotate( - 0f, - 0f, - (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle); - } + //if (Input.GetKey(ResetRotationKey.Value.MainKey)) { + // Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; + // _comfyGizmo.transform.localRotation = Quaternion.Euler(rotation.x, rotation.y, 0f); + //} else { + // _comfyGizmo.transform.Rotate( + // 0f, + // 0f, + // (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle); + //} } else { _yGizmo.localScale = Vector3.one * 1.5f; HandleAxisInput(ref _yRot, _yGizmo); - if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; - _comfyGizmo.transform.localRotation = Quaternion.Euler(rotation.x, 0f, rotation.z); - } else { - _comfyGizmo.transform.Rotate( - 0f, - (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle, - 0f); - } + //if (Input.GetKey(ResetRotationKey.Value.MainKey)) { + // Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; + // _comfyGizmo.transform.localRotation = Quaternion.Euler(rotation.x, 0f, rotation.z); + //} else { + // _comfyGizmo.transform.Rotate( + // 0f, + // (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle, + // 0f); + //} } + _comfyGizmo.transform.localRotation = + Quaternion.Euler(_xRot * _snapAngle, _yRot * _snapAngle, _zRot * _snapAngle); + _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); @@ -189,13 +193,14 @@ static byte[] GetResource(Assembly assembly, string resourceName) { static Transform CreateGizmoRoot() { _gizmoRoot = Instantiate(_gizmoPrefab).transform; - _xGizmo = _gizmoRoot.Find("YRoot/ZRoot/XRoot/X"); + // ??? Something about quaternions. + _zGizmo = _gizmoRoot.Find("YRoot/ZRoot/XRoot/X"); _yGizmo = _gizmoRoot.Find("YRoot/Y"); - _zGizmo = _gizmoRoot.Find("YRoot/ZRoot/Z"); + _xGizmo = _gizmoRoot.Find("YRoot/ZRoot/Z"); - _xGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot/XRoot"); + _zGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot/XRoot"); _yGizmoRoot = _gizmoRoot.Find("YRoot"); - _zGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot"); + _xGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot"); return _gizmoRoot.transform; } diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index 83a4d9c..94e750a 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -21,7 +21,7 @@ public static void BindConfig(ConfigFile config) { 16, new ConfigDescription( "Number of snap angles per 180 degrees. Vanilla uses 8.", - new AcceptableValueRange(2, 128))); + new AcceptableValueRange(2, 256))); XRotationKey = config.Bind( diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index 17d0a5b..672e70b 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -1,6 +1,6 @@ -# ComfyGizmo v1.3.0 +# ComfyGizmo v1.4.0 -Comfy-specific version of Gizmo. +*Comfy-specific version of Gizmo.* * Configurable modifier hot-keys for: * X-axis rotation (default: hold `LeftShift`) @@ -19,12 +19,19 @@ Comfy-specific version of Gizmo. ### Thunderstore - 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo - 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.3.1.zip` - 3. Click "OK/Import local mod" on the pop-up for information + 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo. + 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.4.0.zip`. + 3. Click "OK/Import local mod" on the pop-up for information. ## Changelog +### 1.4.0 (WIP) + + * Create a new GameObject `ComfyGizmo` to maintain the current Quaternion rotation state. + * Re-ordered the mapping/assignment of original Gizmo's prefab XYZ roots/transforms per request by Jere. + * Increased the snap-angles maximum from 128 to 256. + * Moved plugin configuration logic into its own class `PluginConfig`. + ### 1.3.1 * Try to add compatability with other mods that also transpile `UpdatePlacementGhost`. From dbe2b55fe44b3b8fedca7fa0baf4f93a01c96ee0 Mon Sep 17 00:00:00 2001 From: redseiko Date: Fri, 16 Sep 2022 11:07:06 -0700 Subject: [PATCH 09/26] Final update for ComfyGizmo `v1.4.0`. * Create a new GameObject `ComfyGizmo` to maintain the current Quaternion rotation state. * Re-ordered the mapping/assignment of original Gizmo's prefab XYZ roots/transforms per request by Jere. * Increased the snap-angles maximum from 128 to 256. * Moved plugin configuration logic into its own class `PluginConfig`. --- Gizmo/Gizmo/ComfyGizmo.cs | 33 --------------------------------- Gizmo/Gizmo/README.md | 4 ++-- 2 files changed, 2 insertions(+), 35 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 74b2a66..927abf2 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -5,7 +5,6 @@ using System.Reflection.Emit; using BepInEx; -using BepInEx.Configuration; using HarmonyLib; @@ -107,50 +106,18 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { _zGizmo.localScale = Vector3.one; if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { - //_comfyGizmo.transform.localRotation = Quaternion.identity; - _xRot = 0; _yRot = 0; _zRot = 0; } else if (Input.GetKey(XRotationKey.Value.MainKey)) { _xGizmo.localScale = Vector3.one * 1.5f; HandleAxisInput(ref _xRot, _xGizmo); - - //if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - // Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; - // _comfyGizmo.transform.localRotation = Quaternion.Euler(0f, rotation.y, rotation.z); - //} else { - // _comfyGizmo.transform.Rotate( - // (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle, - // 0f, - // 0f); - //} } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { _zGizmo.localScale = Vector3.one * 1.5f; HandleAxisInput(ref _zRot, _zGizmo); - - //if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - // Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; - // _comfyGizmo.transform.localRotation = Quaternion.Euler(rotation.x, rotation.y, 0f); - //} else { - // _comfyGizmo.transform.Rotate( - // 0f, - // 0f, - // (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle); - //} } else { _yGizmo.localScale = Vector3.one * 1.5f; HandleAxisInput(ref _yRot, _yGizmo); - - //if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - // Vector3 rotation = _comfyGizmo.transform.localRotation.eulerAngles; - // _comfyGizmo.transform.localRotation = Quaternion.Euler(rotation.x, 0f, rotation.z); - //} else { - // _comfyGizmo.transform.Rotate( - // 0f, - // (Math.Sign(Input.GetAxis("Mouse ScrollWheel")) % (SnapDivisions.Value * 2)) * _snapAngle, - // 0f); - //} } _comfyGizmo.transform.localRotation = diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index 672e70b..d30d78a 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -8,7 +8,7 @@ * Reset selected axis rotation (default: `V`) * Reset **ALL** axis rotations (disabled by default) * Can disable the Gizmo placement visual. - * Can set the snap angles per 180 degrees from 2 - 128. + * Can set the snap angles per 180 degrees from 2 - 256. * Original Euler-style rotation. ## Installation @@ -25,7 +25,7 @@ ## Changelog -### 1.4.0 (WIP) +### 1.4.0 * Create a new GameObject `ComfyGizmo` to maintain the current Quaternion rotation state. * Re-ordered the mapping/assignment of original Gizmo's prefab XYZ roots/transforms per request by Jere. From dfa3f89e6c61b55c7d72a23fdb927dafced17258 Mon Sep 17 00:00:00 2001 From: redseiko Date: Fri, 16 Sep 2022 11:13:44 -0700 Subject: [PATCH 10/26] Update the manifest.json and README.md in ComfyGizmo. --- Gizmo/Gizmo/README.md | 6 ++++-- Gizmo/Gizmo/manifest.json | 4 ++-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index d30d78a..2139ddd 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -20,8 +20,9 @@ ### Thunderstore 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo. - 2. Go to Settings > Import local mod > select `ComfyGizmo_v1.4.0.zip`. - 3. Click "OK/Import local mod" on the pop-up for information. + 2. **Disable or uninstall** any manually installed `ComfyGizmo_v1.3.0` or earlier. + 3. Go to Settings > Import local mod > select `ComfyGizmo_v1.4.0.zip`. + 4. Click "OK/Import local mod" on the pop-up for information. ## Changelog @@ -31,6 +32,7 @@ * Re-ordered the mapping/assignment of original Gizmo's prefab XYZ roots/transforms per request by Jere. * Increased the snap-angles maximum from 128 to 256. * Moved plugin configuration logic into its own class `PluginConfig`. + * Renamed the author field in `manifest.json` to `ComfyMods`. ### 1.3.1 diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json index af275cd..c0ff269 100644 --- a/Gizmo/Gizmo/manifest.json +++ b/Gizmo/Gizmo/manifest.json @@ -1,7 +1,7 @@ { "name": "Gizmo", - "version_number": "1.3.1", - "author": "redseiko", + "version_number": "1.4.0", + "author": "ComfyMods", "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", "description": "Comfy-specific fork of Gizmo.", "dependencies": [ From af9bbcbe7a71698975fbd542d9fa2c25d1397d0f Mon Sep 17 00:00:00 2001 From: redseiko Date: Fri, 16 Sep 2022 11:32:59 -0700 Subject: [PATCH 11/26] Slightly adjust README.md. --- Gizmo/Gizmo/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index 2139ddd..07eebac 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -13,11 +13,11 @@ ## Installation -### Manual installation: +### Manual 1. Unzip `Gizmo.dll` to your `/valheim/BepInEx/plugins/` folder -### Thunderstore +### Thunderstore (manual) 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo. 2. **Disable or uninstall** any manually installed `ComfyGizmo_v1.3.0` or earlier. From 8bda0edb95a5b2bf33f70149484ab95a64baff7b Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sat, 29 Oct 2022 16:17:11 -0700 Subject: [PATCH 12/26] Added rotations for the local frame. Back quote default key to switch between rotation modes. Rotations reset on switching rotation modes. --- Gizmo/Gizmo/ComfyGizmo.cs | 124 ++++++++++++++++++++++++++++++------ Gizmo/Gizmo/PluginConfig.cs | 11 ++++ Gizmo/Gizmo/README.md | 5 ++ Gizmo/Gizmo/manifest.json | 2 +- 4 files changed, 122 insertions(+), 20 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 927abf2..ca14e6b 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -17,7 +17,7 @@ namespace Gizmo { public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.4.0"; + public const string PluginVersion = "1.4.1"; static GameObject _gizmoPrefab = null; static Transform _gizmoRoot; @@ -37,6 +37,8 @@ public class ComfyGizmo : BaseUnityPlugin { static int _yRot; static int _zRot; + static bool _localFrame; + static float _snapAngle; Harmony _harmony; @@ -48,7 +50,7 @@ public void Awake() { _snapAngle = 180f / SnapDivisions.Value; _gizmoPrefab = LoadGizmoPrefab(); - + _harmony = Harmony.CreateAndPatchAll(Assembly.GetExecutingAssembly(), harmonyInstanceId: PluginGUID); } @@ -67,6 +69,7 @@ static void StartPostfix() { Destroy(_comfyGizmo); _comfyGizmo = new("ComfyGizmo"); _comfyGizmoRoot = _comfyGizmo.transform; + _localFrame = UseLocalFrame.Value; } } @@ -101,32 +104,104 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { return; } + if (Input.GetKey(ChangeRotationModeKey.Value.MainKey)) { + UseLocalFrame.Value = !UseLocalFrame.Value; + } + + // Reset rotations on changing from default rotations to local frame rotations and vice versa + if (_localFrame != UseLocalFrame.Value) { + if(_localFrame) { + ResetRotationsLocalFrame(); + } else { + ResetRotations(); + } + + _localFrame = UseLocalFrame.Value; + return; + } + _xGizmo.localScale = Vector3.one; _yGizmo.localScale = Vector3.one; _zGizmo.localScale = Vector3.one; - if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { - _xRot = 0; - _yRot = 0; - _zRot = 0; - } else if (Input.GetKey(XRotationKey.Value.MainKey)) { - _xGizmo.localScale = Vector3.one * 1.5f; - HandleAxisInput(ref _xRot, _xGizmo); - } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { - _zGizmo.localScale = Vector3.one * 1.5f; - HandleAxisInput(ref _zRot, _zGizmo); + if (!UseLocalFrame.Value) { + Rotate(); } else { - _yGizmo.localScale = Vector3.one * 1.5f; - HandleAxisInput(ref _yRot, _yGizmo); + RotateLocalFrame(); } + } + } + + static void ResetRotations() { + _xRot = 0; + _yRot = 0; + _zRot = 0; - _comfyGizmo.transform.localRotation = - Quaternion.Euler(_xRot * _snapAngle, _yRot * _snapAngle, _zRot * _snapAngle); + _comfyGizmo.transform.localRotation = + Quaternion.Euler(_xRot * _snapAngle, _yRot * _snapAngle, _zRot * _snapAngle); + + _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); + _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); + _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); + } + + static void ResetRotationsLocalFrame() { + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.up); + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.right); + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.forward); + + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.up); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.right); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.forward); + } - _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); - _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); - _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); + static void Rotate() { + if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { + ResetRotations(); + } else if (Input.GetKey(XRotationKey.Value.MainKey)) { + _xGizmo.localScale = Vector3.one * 1.5f; + HandleAxisInput(ref _xRot, _xGizmo); + } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { + _zGizmo.localScale = Vector3.one * 1.5f; + HandleAxisInput(ref _zRot, _zGizmo); + } else { + _yGizmo.localScale = Vector3.one * 1.5f; + HandleAxisInput(ref _yRot, _yGizmo); } + + _comfyGizmo.transform.localRotation = + Quaternion.Euler(_xRot * _snapAngle, _yRot * _snapAngle, _zRot * _snapAngle); + + _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); + _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); + _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); + } + + static void RotateLocalFrame() { + if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { + ResetRotationsLocalFrame(); + return; + } + + float rotation = Math.Sign(Input.GetAxis("Mouse ScrollWheel")) * _snapAngle; + Vector3 rotVector; + + if (Input.GetKey(XRotationKey.Value.MainKey)) { + _xGizmo.localScale = Vector3.one * 1.5f; + rotVector = Vector3.right; + HandleAxisInputLocalFrame(rotVector, _xGizmo); + } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { + _zGizmo.localScale = Vector3.one * 1.5f; + rotVector = Vector3.forward; + HandleAxisInputLocalFrame(rotVector, _zGizmo); + } else { + _yGizmo.localScale = Vector3.one * 1.5f; + rotVector = Vector3.up; + HandleAxisInputLocalFrame(rotVector, _yGizmo); + } + + _comfyGizmo.transform.rotation *= Quaternion.AngleAxis(rotation, rotVector); + _gizmoRoot.rotation *= Quaternion.AngleAxis(rotation, rotVector); } static void HandleAxisInput(ref int rotation, Transform gizmo) { @@ -138,6 +213,17 @@ static void HandleAxisInput(ref int rotation, Transform gizmo) { } } + static void HandleAxisInputLocalFrame(Vector3 rotVector, Transform gizmo) { + gizmo.localScale = Vector3.one * 1.5f; + + if (Input.GetKey(ResetRotationKey.Value.MainKey)) { + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, rotVector); + _xGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); + _yGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); + _zGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); + } + } + static GameObject LoadGizmoPrefab() { AssetBundle bundle = AssetBundle.LoadFromMemory( GetResource(Assembly.GetExecutingAssembly(), "Gizmo.Resources.gizmos")); diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index 94e750a..2aebf45 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -10,8 +10,10 @@ public static class PluginConfig { public static ConfigEntry ZRotationKey; public static ConfigEntry ResetRotationKey; public static ConfigEntry ResetAllRotationKey; + public static ConfigEntry ChangeRotationModeKey; public static ConfigEntry ShowGizmoPrefab; + public static ConfigEntry UseLocalFrame; public static void BindConfig(ConfigFile config) { SnapDivisions = @@ -51,7 +53,16 @@ public static void BindConfig(ConfigFile config) { KeyboardShortcut.Empty, "Press this key to reset _all axis_ rotations to zero rotation."); + + ChangeRotationModeKey = + config.Bind( + "Keys", + "changeRotationMode", + new KeyboardShortcut(KeyCode.BackQuote), + "Press this key to reset _all axis_ rotations to zero rotation."); + ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); + UseLocalFrame = config.Bind("RotationFrame", "useLocalFrame", false, "Use the local piece coordinate system for rotations."); } } } diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index 07eebac..e414e90 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -26,6 +26,11 @@ ## Changelog +### 1.5.0 + * Added alternate rotation method for using local frame coordinates. + * Added configuration option to rotate using local frame coordinates. + * Hot key added to toggle between default and local frame rotation modes. Default set to back quote. + ### 1.4.0 * Create a new GameObject `ComfyGizmo` to maintain the current Quaternion rotation state. diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json index c0ff269..a9a998d 100644 --- a/Gizmo/Gizmo/manifest.json +++ b/Gizmo/Gizmo/manifest.json @@ -1,6 +1,6 @@ { "name": "Gizmo", - "version_number": "1.4.0", + "version_number": "1.4.1", "author": "ComfyMods", "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", "description": "Comfy-specific fork of Gizmo.", From 08b7fbf5acc91df06e334a4e8de4a0ec0bd4382c Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sat, 29 Oct 2022 16:24:41 -0700 Subject: [PATCH 13/26] Fixed issue with x and z gizmo root visuals being reversed. --- Gizmo/Gizmo/ComfyGizmo.cs | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index ca14e6b..28f15da 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -247,13 +247,13 @@ static Transform CreateGizmoRoot() { _gizmoRoot = Instantiate(_gizmoPrefab).transform; // ??? Something about quaternions. - _zGizmo = _gizmoRoot.Find("YRoot/ZRoot/XRoot/X"); + _xGizmo = _gizmoRoot.Find("YRoot/ZRoot/XRoot/X"); _yGizmo = _gizmoRoot.Find("YRoot/Y"); - _xGizmo = _gizmoRoot.Find("YRoot/ZRoot/Z"); + _zGizmo = _gizmoRoot.Find("YRoot/ZRoot/Z"); - _zGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot/XRoot"); + _xGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot/XRoot"); _yGizmoRoot = _gizmoRoot.Find("YRoot"); - _xGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot"); + _zGizmoRoot = _gizmoRoot.Find("YRoot/ZRoot"); return _gizmoRoot.transform; } From 30a4c0773f008de13a9a9578d0522760dd3489d3 Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sat, 29 Oct 2022 17:44:48 -0700 Subject: [PATCH 14/26] Added matching piece rotation with config bind. Default is unbound. Recommend Q. --- Gizmo/Gizmo/ComfyGizmo.cs | 44 +++++++++++++++++++++++++++++++++---- Gizmo/Gizmo/PluginConfig.cs | 10 ++++++++- 2 files changed, 49 insertions(+), 5 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 28f15da..08ffd1a 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -5,7 +5,7 @@ using System.Reflection.Emit; using BepInEx; - +using BepInEx.Logging; using HarmonyLib; using UnityEngine; @@ -104,6 +104,10 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { return; } + if (Input.GetKey(CopyPieceRotationKey.Value.MainKey) && __instance.m_hoveringPiece != null) { + MatchPieceRotation(__instance.m_hoveringPiece); + } + if (Input.GetKey(ChangeRotationModeKey.Value.MainKey)) { UseLocalFrame.Value = !UseLocalFrame.Value; } @@ -127,11 +131,18 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { if (!UseLocalFrame.Value) { Rotate(); } else { - RotateLocalFrame(); + int direction = Math.Sign(Input.GetAxis("Mouse ScrollWheel")); + RotateLocalFrame(direction); } } } + [HarmonyPostfix] + [HarmonyPatch(nameof(Hud.UpdateCrosshair))] + static void UpdateCrosshairPostfix(ref Hud __instance, Player player) { + + } + static void ResetRotations() { _xRot = 0; _yRot = 0; @@ -177,13 +188,13 @@ static void Rotate() { _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); } - static void RotateLocalFrame() { + static void RotateLocalFrame(int direction) { if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { ResetRotationsLocalFrame(); return; } - float rotation = Math.Sign(Input.GetAxis("Mouse ScrollWheel")) * _snapAngle; + float rotation = direction * _snapAngle; Vector3 rotVector; if (Input.GetKey(XRotationKey.Value.MainKey)) { @@ -200,6 +211,10 @@ static void RotateLocalFrame() { HandleAxisInputLocalFrame(rotVector, _yGizmo); } + RotateAxes(rotation, rotVector); + } + + static void RotateAxes(float rotation, Vector3 rotVector) { _comfyGizmo.transform.rotation *= Quaternion.AngleAxis(rotation, rotVector); _gizmoRoot.rotation *= Quaternion.AngleAxis(rotation, rotVector); } @@ -223,6 +238,27 @@ static void HandleAxisInputLocalFrame(Vector3 rotVector, Transform gizmo) { _zGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); } } + static void MatchPieceRotation(Piece target) { + if (_localFrame) { + Quaternion targetQuaternion = GetQuaternion(target); + _comfyGizmo.transform.rotation = targetQuaternion; + _gizmoRoot.rotation = targetQuaternion; + } else { + Vector3 eulerAngles = GetEulerAngles(target); + _xRot = (int)Math.Floor(eulerAngles.x/_snapAngle); + _yRot = (int)Math.Floor(eulerAngles.y / _snapAngle); + _zRot = (int)Math.Floor(eulerAngles.z / _snapAngle); + Rotate(); + } + } + + static Quaternion GetQuaternion(Piece target) { + return target.GetComponent().localRotation; + } + + static Vector3 GetEulerAngles(Piece target) { + return target.GetComponent().eulerAngles; + } static GameObject LoadGizmoPrefab() { AssetBundle bundle = AssetBundle.LoadFromMemory( diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index 2aebf45..f88e549 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -11,6 +11,7 @@ public static class PluginConfig { public static ConfigEntry ResetRotationKey; public static ConfigEntry ResetAllRotationKey; public static ConfigEntry ChangeRotationModeKey; + public static ConfigEntry CopyPieceRotationKey; public static ConfigEntry ShowGizmoPrefab; public static ConfigEntry UseLocalFrame; @@ -59,7 +60,14 @@ public static void BindConfig(ConfigFile config) { "Keys", "changeRotationMode", new KeyboardShortcut(KeyCode.BackQuote), - "Press this key to reset _all axis_ rotations to zero rotation."); + "Press this key to toggle rotation modes."); + + CopyPieceRotationKey = + config.Bind( + "Keys", + "copyPieceRotation", + KeyboardShortcut.Empty, + "Press this key to copy targeted piece's rotation."); ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); UseLocalFrame = config.Bind("RotationFrame", "useLocalFrame", false, "Use the local piece coordinate system for rotations."); From be4c0fb0a11d7c64989b4f2a2ddff015e5e923cd Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sat, 29 Oct 2022 17:48:08 -0700 Subject: [PATCH 15/26] Updated version. --- Gizmo/Gizmo/ComfyGizmo.cs | 2 +- Gizmo/Gizmo/manifest.json | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 08ffd1a..c77ac49 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -17,7 +17,7 @@ namespace Gizmo { public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.4.1"; + public const string PluginVersion = "1.5.0"; static GameObject _gizmoPrefab = null; static Transform _gizmoRoot; diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json index a9a998d..b915ac2 100644 --- a/Gizmo/Gizmo/manifest.json +++ b/Gizmo/Gizmo/manifest.json @@ -1,6 +1,6 @@ { "name": "Gizmo", - "version_number": "1.4.1", + "version_number": "1.5.0", "author": "ComfyMods", "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", "description": "Comfy-specific fork of Gizmo.", From 1a0eba94632ccc61bd45a46e5b4ccef4e77f751e Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sat, 29 Oct 2022 19:03:21 -0700 Subject: [PATCH 16/26] Fixed small error in copying target piece rotation when in default rotation mode. --- Gizmo/Gizmo/ComfyGizmo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index c77ac49..6ae8895 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -245,9 +245,9 @@ static void MatchPieceRotation(Piece target) { _gizmoRoot.rotation = targetQuaternion; } else { Vector3 eulerAngles = GetEulerAngles(target); - _xRot = (int)Math.Floor(eulerAngles.x/_snapAngle); - _yRot = (int)Math.Floor(eulerAngles.y / _snapAngle); - _zRot = (int)Math.Floor(eulerAngles.z / _snapAngle); + _xRot = (int)Math.Round(eulerAngles.x/_snapAngle); + _yRot = (int)Math.Round(eulerAngles.y / _snapAngle); + _zRot = (int)Math.Round(eulerAngles.z / _snapAngle); Rotate(); } } From 2e332c1e621edbdd715364bdfa20eb6b0b658def Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sun, 30 Oct 2022 00:16:44 -0700 Subject: [PATCH 17/26] Added feature to switch rotation frames without resetting piece rotation. Disabled by default. Configurable in F1. --- Gizmo/Gizmo/ComfyGizmo.cs | 125 +++++++++++++++++------------------- Gizmo/Gizmo/PluginConfig.cs | 4 +- 2 files changed, 61 insertions(+), 68 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 6ae8895..edd994c 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -33,9 +33,7 @@ public class ComfyGizmo : BaseUnityPlugin { static GameObject _comfyGizmo; static Transform _comfyGizmoRoot; - static int _xRot; - static int _yRot; - static int _zRot; + static Vector3 _eulerAngles; static bool _localFrame; @@ -69,7 +67,7 @@ static void StartPostfix() { Destroy(_comfyGizmo); _comfyGizmo = new("ComfyGizmo"); _comfyGizmoRoot = _comfyGizmo.transform; - _localFrame = UseLocalFrame.Value; + _localFrame = false; } } @@ -108,19 +106,28 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { MatchPieceRotation(__instance.m_hoveringPiece); } - if (Input.GetKey(ChangeRotationModeKey.Value.MainKey)) { - UseLocalFrame.Value = !UseLocalFrame.Value; - } - // Reset rotations on changing from default rotations to local frame rotations and vice versa - if (_localFrame != UseLocalFrame.Value) { + if (Input.GetKeyDown(ChangeRotationModeKey.Value.MainKey)) { if(_localFrame) { - ResetRotationsLocalFrame(); + if(ResetRotation.Value) { + ResetRotationsLocalFrame(); + } else { + _eulerAngles = _comfyGizmo.transform.eulerAngles; + ResetGizmoRoot(); + RotateGizmoComponents(_eulerAngles); + } + } else { - ResetRotations(); + if(ResetRotation.Value) { + ResetRotations(); + } else { + Quaternion currentRotation = _comfyGizmoRoot.rotation; + ResetGizmoComponents(); + _gizmoRoot.rotation = currentRotation; + } } - _localFrame = UseLocalFrame.Value; + _localFrame = !_localFrame; return; } @@ -128,7 +135,7 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { _yGizmo.localScale = Vector3.one; _zGizmo.localScale = Vector3.one; - if (!UseLocalFrame.Value) { + if (!_localFrame) { Rotate(); } else { int direction = Math.Sign(Input.GetAxis("Mouse ScrollWheel")); @@ -137,55 +144,22 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { } } - [HarmonyPostfix] - [HarmonyPatch(nameof(Hud.UpdateCrosshair))] - static void UpdateCrosshairPostfix(ref Hud __instance, Player player) { - - } - - static void ResetRotations() { - _xRot = 0; - _yRot = 0; - _zRot = 0; - - _comfyGizmo.transform.localRotation = - Quaternion.Euler(_xRot * _snapAngle, _yRot * _snapAngle, _zRot * _snapAngle); - - _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); - _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); - _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); - } - - static void ResetRotationsLocalFrame() { - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.up); - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.right); - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.forward); - - _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.up); - _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.right); - _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.forward); - } - static void Rotate() { if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { ResetRotations(); } else if (Input.GetKey(XRotationKey.Value.MainKey)) { _xGizmo.localScale = Vector3.one * 1.5f; - HandleAxisInput(ref _xRot, _xGizmo); + _eulerAngles.x = GetRotationAngle(_eulerAngles.x); } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { _zGizmo.localScale = Vector3.one * 1.5f; - HandleAxisInput(ref _zRot, _zGizmo); + _eulerAngles.z = GetRotationAngle(_eulerAngles.z); } else { _yGizmo.localScale = Vector3.one * 1.5f; - HandleAxisInput(ref _yRot, _yGizmo); + _eulerAngles.y = GetRotationAngle(_eulerAngles.y); } - _comfyGizmo.transform.localRotation = - Quaternion.Euler(_xRot * _snapAngle, _yRot * _snapAngle, _zRot * _snapAngle); - - _xGizmoRoot.localRotation = Quaternion.Euler(_xRot * _snapAngle, 0f, 0f); - _yGizmoRoot.localRotation = Quaternion.Euler(0f, _yRot * _snapAngle, 0f); - _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, _zRot * _snapAngle); + _comfyGizmo.transform.localRotation = Quaternion.Euler(_eulerAngles); + RotateGizmoComponents(_eulerAngles); } static void RotateLocalFrame(int direction) { @@ -219,13 +193,13 @@ static void RotateAxes(float rotation, Vector3 rotVector) { _gizmoRoot.rotation *= Quaternion.AngleAxis(rotation, rotVector); } - static void HandleAxisInput(ref int rotation, Transform gizmo) { - gizmo.localScale = Vector3.one * 1.5f; - rotation = (rotation + Math.Sign(Input.GetAxis("Mouse ScrollWheel"))) % (SnapDivisions.Value * 2); + static float GetRotationAngle(float rotation) { + rotation += Math.Sign(Input.GetAxis("Mouse ScrollWheel")) * _snapAngle; if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - rotation = 0; + rotation = 0f; } + return rotation; } static void HandleAxisInputLocalFrame(Vector3 rotVector, Transform gizmo) { @@ -240,24 +214,43 @@ static void HandleAxisInputLocalFrame(Vector3 rotVector, Transform gizmo) { } static void MatchPieceRotation(Piece target) { if (_localFrame) { - Quaternion targetQuaternion = GetQuaternion(target); - _comfyGizmo.transform.rotation = targetQuaternion; - _gizmoRoot.rotation = targetQuaternion; + _comfyGizmo.transform.rotation = target.GetComponent().localRotation; + _gizmoRoot.rotation = target.GetComponent().localRotation; } else { - Vector3 eulerAngles = GetEulerAngles(target); - _xRot = (int)Math.Round(eulerAngles.x/_snapAngle); - _yRot = (int)Math.Round(eulerAngles.y / _snapAngle); - _zRot = (int)Math.Round(eulerAngles.z / _snapAngle); + _eulerAngles = target.GetComponent().eulerAngles; Rotate(); } } + static void ResetRotations() { + _comfyGizmo.transform.localRotation = Quaternion.Euler(Vector3.zero); + RotateGizmoComponents(Vector3.zero); + } + + static void ResetGizmoComponents() { + _eulerAngles = Vector3.zero; + RotateGizmoComponents(Vector3.zero); + } - static Quaternion GetQuaternion(Piece target) { - return target.GetComponent().localRotation; + static void ResetGizmoRoot() { + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.up); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.right); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.forward); } - static Vector3 GetEulerAngles(Piece target) { - return target.GetComponent().eulerAngles; + static void RotateGizmoComponents(Vector3 eulerAngles) { + _xGizmoRoot.localRotation = Quaternion.Euler(eulerAngles.x, 0f, 0f); + _yGizmoRoot.localRotation = Quaternion.Euler(0f, eulerAngles.y, 0f); + _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, eulerAngles.z); + } + + static void ResetRotationsLocalFrame() { + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.up); + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.right); + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.forward); + + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.up); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.right); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.forward); } static GameObject LoadGizmoPrefab() { diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index f88e549..40a6ccb 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -14,7 +14,7 @@ public static class PluginConfig { public static ConfigEntry CopyPieceRotationKey; public static ConfigEntry ShowGizmoPrefab; - public static ConfigEntry UseLocalFrame; + public static ConfigEntry ResetRotation; public static void BindConfig(ConfigFile config) { SnapDivisions = @@ -70,7 +70,7 @@ public static void BindConfig(ConfigFile config) { "Press this key to copy targeted piece's rotation."); ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); - UseLocalFrame = config.Bind("RotationFrame", "useLocalFrame", false, "Use the local piece coordinate system for rotations."); + ResetRotation = config.Bind("RotationFrame", "resetOnModeChange", true, "Resets the piece's rotation on mode switch."); } } } From 0753249c960db755d086e520dbaee0db31ff28ce Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sun, 30 Oct 2022 00:40:53 -0700 Subject: [PATCH 18/26] Added message when toggling between rotation modes. --- Gizmo/Gizmo/ComfyGizmo.cs | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index edd994c..b5dd41e 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -106,10 +106,11 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { MatchPieceRotation(__instance.m_hoveringPiece); } - // Reset rotations on changing from default rotations to local frame rotations and vice versa + // Change Rotation Frames if (Input.GetKeyDown(ChangeRotationModeKey.Value.MainKey)) { if(_localFrame) { - if(ResetRotation.Value) { + MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, "Default rotation mode enabled"); + if (ResetRotation.Value) { ResetRotationsLocalFrame(); } else { _eulerAngles = _comfyGizmo.transform.eulerAngles; @@ -118,7 +119,8 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { } } else { - if(ResetRotation.Value) { + MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, "Local frame rotation mode enabled"); + if (ResetRotation.Value) { ResetRotations(); } else { Quaternion currentRotation = _comfyGizmoRoot.rotation; From 00e8491b268e931a6b0ffd8c9527140d82da91a8 Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sun, 30 Oct 2022 11:31:51 -0700 Subject: [PATCH 19/26] Added feature to increment and decrement snap divisions similar to gizmo reloaded. Not fixed to 8/16/32 snap divisions. Will decrease snap divisions until 2 or an odd number hit. Maximum snap divisions maintained at 256. --- Gizmo/Gizmo/ComfyGizmo.cs | 14 ++++++++++++++ Gizmo/Gizmo/PluginConfig.cs | 21 ++++++++++++++++++++- 2 files changed, 34 insertions(+), 1 deletion(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index b5dd41e..7d33027 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -102,6 +102,20 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { return; } + if(Input.GetKeyDown(SnapDivisionIncrementKey.Value.MainKey)) { + if(SnapDivisions.Value * 2 <= MaxSnapDivisions) { + MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, $"Snap divisions increased to {SnapDivisions.Value * 2}"); + SnapDivisions.Value = SnapDivisions.Value * 2; + } + } + + if (Input.GetKeyDown(SnapDivisionDecrementKey.Value.MainKey)) { + if(Math.Floor(SnapDivisions.Value/2f) == SnapDivisions.Value/2f && SnapDivisions.Value/2 >= MinSnapDivisions) { + MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, $"Snap divisions decreased to {SnapDivisions.Value / 2}"); + SnapDivisions.Value = SnapDivisions.Value / 2; + } + } + if (Input.GetKey(CopyPieceRotationKey.Value.MainKey) && __instance.m_hoveringPiece != null) { MatchPieceRotation(__instance.m_hoveringPiece); } diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index 40a6ccb..5473b11 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -12,10 +12,15 @@ public static class PluginConfig { public static ConfigEntry ResetAllRotationKey; public static ConfigEntry ChangeRotationModeKey; public static ConfigEntry CopyPieceRotationKey; + public static ConfigEntry SnapDivisionIncrementKey; + public static ConfigEntry SnapDivisionDecrementKey; public static ConfigEntry ShowGizmoPrefab; public static ConfigEntry ResetRotation; + public static int MaxSnapDivisions = 256; + public static int MinSnapDivisions = 2; + public static void BindConfig(ConfigFile config) { SnapDivisions = config.Bind( @@ -24,7 +29,7 @@ public static void BindConfig(ConfigFile config) { 16, new ConfigDescription( "Number of snap angles per 180 degrees. Vanilla uses 8.", - new AcceptableValueRange(2, 256))); + new AcceptableValueRange(MinSnapDivisions, MaxSnapDivisions))); XRotationKey = config.Bind( @@ -69,6 +74,20 @@ public static void BindConfig(ConfigFile config) { KeyboardShortcut.Empty, "Press this key to copy targeted piece's rotation."); + SnapDivisionIncrementKey = + config.Bind( + "Keys", + "snapDivisionIncrement", + new KeyboardShortcut(KeyCode.PageUp), + "Doubles snap divisions from current."); + + SnapDivisionDecrementKey = + config.Bind( + "Keys", + "snapDivisionDecrement", + new KeyboardShortcut(KeyCode.PageDown), + "Doubles snap divisions from current."); + ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); ResetRotation = config.Bind("RotationFrame", "resetOnModeChange", true, "Resets the piece's rotation on mode switch."); } From 0eb81495c01ff35f7fe2128db06eafe83054274c Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sun, 30 Oct 2022 12:19:11 -0700 Subject: [PATCH 20/26] Added toggle for resetting piece rotations enabled by default to prevent off grid divisions when quick toggling between snap divisions. --- Gizmo/Gizmo/ComfyGizmo.cs | 17 +++++++++++++++++ Gizmo/Gizmo/PluginConfig.cs | 2 ++ 2 files changed, 19 insertions(+) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 7d33027..1c77ec6 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -106,6 +106,14 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { if(SnapDivisions.Value * 2 <= MaxSnapDivisions) { MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, $"Snap divisions increased to {SnapDivisions.Value * 2}"); SnapDivisions.Value = SnapDivisions.Value * 2; + if(ResetRotationOnSnapDivisionChange.Value) { + if(_localFrame) { + ResetRotationsLocalFrame(); + } else { + ResetRotations(); + } + return; + } } } @@ -113,6 +121,14 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { if(Math.Floor(SnapDivisions.Value/2f) == SnapDivisions.Value/2f && SnapDivisions.Value/2 >= MinSnapDivisions) { MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, $"Snap divisions decreased to {SnapDivisions.Value / 2}"); SnapDivisions.Value = SnapDivisions.Value / 2; + if (ResetRotationOnSnapDivisionChange.Value) { + if (_localFrame) { + ResetRotationsLocalFrame(); + } else { + ResetRotations(); + } + return; + } } } @@ -238,6 +254,7 @@ static void MatchPieceRotation(Piece target) { } } static void ResetRotations() { + _eulerAngles = Vector3.zero; _comfyGizmo.transform.localRotation = Quaternion.Euler(Vector3.zero); RotateGizmoComponents(Vector3.zero); } diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index 5473b11..881043e 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -17,6 +17,7 @@ public static class PluginConfig { public static ConfigEntry ShowGizmoPrefab; public static ConfigEntry ResetRotation; + public static ConfigEntry ResetRotationOnSnapDivisionChange; public static int MaxSnapDivisions = 256; public static int MinSnapDivisions = 2; @@ -88,6 +89,7 @@ public static void BindConfig(ConfigFile config) { new KeyboardShortcut(KeyCode.PageDown), "Doubles snap divisions from current."); + ResetRotationOnSnapDivisionChange = config.Bind("Snap Division Change", "resetOnSnapDivisionChange", true, "Resets the piece's rotation on snap division change."); ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); ResetRotation = config.Bind("RotationFrame", "resetOnModeChange", true, "Resets the piece's rotation on mode switch."); } From f41930232d47f97176a5bf5b0861448999800403 Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Sun, 30 Oct 2022 14:48:16 -0700 Subject: [PATCH 21/26] Fixed resets in local frame which were borked due to Quaternions vs euler angles rotation. --- Gizmo/Gizmo/ComfyGizmo.cs | 38 +++++++++++++++++++------------------- Gizmo/Gizmo/README.md | 4 ++++ 2 files changed, 23 insertions(+), 19 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 1c77ec6..aad617c 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -34,6 +34,7 @@ public class ComfyGizmo : BaseUnityPlugin { static Transform _comfyGizmoRoot; static Vector3 _eulerAngles; + static float _rotation; static bool _localFrame; @@ -170,8 +171,7 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { if (!_localFrame) { Rotate(); } else { - int direction = Math.Sign(Input.GetAxis("Mouse ScrollWheel")); - RotateLocalFrame(direction); + RotateLocalFrame(); } } } @@ -194,30 +194,30 @@ static void Rotate() { RotateGizmoComponents(_eulerAngles); } - static void RotateLocalFrame(int direction) { + static void RotateLocalFrame() { if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { ResetRotationsLocalFrame(); return; } - float rotation = direction * _snapAngle; + _rotation = 0f; Vector3 rotVector; if (Input.GetKey(XRotationKey.Value.MainKey)) { _xGizmo.localScale = Vector3.one * 1.5f; rotVector = Vector3.right; - HandleAxisInputLocalFrame(rotVector, _xGizmo); + HandleAxisInputLocalFrame(ref _rotation, rotVector, _xGizmo); } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { _zGizmo.localScale = Vector3.one * 1.5f; rotVector = Vector3.forward; - HandleAxisInputLocalFrame(rotVector, _zGizmo); + HandleAxisInputLocalFrame(ref _rotation, rotVector, _zGizmo); } else { _yGizmo.localScale = Vector3.one * 1.5f; rotVector = Vector3.up; - HandleAxisInputLocalFrame(rotVector, _yGizmo); + HandleAxisInputLocalFrame(ref _rotation, rotVector, _yGizmo); } - RotateAxes(rotation, rotVector); + RotateAxes(_rotation, rotVector); } static void RotateAxes(float rotation, Vector3 rotVector) { @@ -234,14 +234,13 @@ static float GetRotationAngle(float rotation) { return rotation; } - static void HandleAxisInputLocalFrame(Vector3 rotVector, Transform gizmo) { + static void HandleAxisInputLocalFrame(ref float rotation, Vector3 rotVector, Transform gizmo) { gizmo.localScale = Vector3.one * 1.5f; + rotation = Math.Sign(Input.GetAxis("Mouse ScrollWheel")) * _snapAngle; if (Input.GetKey(ResetRotationKey.Value.MainKey)) { - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, rotVector); - _xGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); - _yGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); - _zGizmoRoot.rotation = Quaternion.AngleAxis(0f, rotVector); + rotation = 0f; + ResetRotationLocalFrameAxis(rotVector); } } static void MatchPieceRotation(Piece target) { @@ -277,13 +276,14 @@ static void RotateGizmoComponents(Vector3 eulerAngles) { } static void ResetRotationsLocalFrame() { - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.up); - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.right); - _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, Vector3.forward); + ResetRotationLocalFrameAxis(Vector3.up); + ResetRotationLocalFrameAxis(Vector3.right); + ResetRotationLocalFrameAxis(Vector3.forward); + } - _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.up); - _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.right); - _gizmoRoot.rotation = Quaternion.AngleAxis(0f, Vector3.forward); + static void ResetRotationLocalFrameAxis(Vector3 axis) { + _comfyGizmo.transform.rotation = Quaternion.AngleAxis(0f, axis); + _gizmoRoot.rotation = Quaternion.AngleAxis(0f, axis); } static GameObject LoadGizmoPrefab() { diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index e414e90..d65e232 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -27,9 +27,13 @@ ## Changelog ### 1.5.0 + * Added hotkeys for halving and doubling snap divisions. PageUp and PageDown by default. + * Added toggle for resetting piece rotation on snap division change. Enabled by default. + * Added feature to copy target piece's rotation. * Added alternate rotation method for using local frame coordinates. * Added configuration option to rotate using local frame coordinates. * Hot key added to toggle between default and local frame rotation modes. Default set to back quote. + * Added toggle to reset piece orientation which changing between rotation frames. Enabled by default. ### 1.4.0 From 1095166feb832240f570d87382d32ec444040567 Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Mon, 31 Oct 2022 13:02:46 -0700 Subject: [PATCH 22/26] Updated configuration options naming and categorization. --- Gizmo/Gizmo/ComfyGizmo.cs | 4 ++-- Gizmo/Gizmo/PluginConfig.cs | 7 ++++--- 2 files changed, 6 insertions(+), 5 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index aad617c..6ccb754 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -141,7 +141,7 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { if (Input.GetKeyDown(ChangeRotationModeKey.Value.MainKey)) { if(_localFrame) { MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, "Default rotation mode enabled"); - if (ResetRotation.Value) { + if (ResetRotationOnModeChange.Value) { ResetRotationsLocalFrame(); } else { _eulerAngles = _comfyGizmo.transform.eulerAngles; @@ -151,7 +151,7 @@ static void UpdatePlacementPostfix(ref Player __instance, ref bool takeInput) { } else { MessageHud.instance.ShowMessage(MessageHud.MessageType.TopLeft, "Local frame rotation mode enabled"); - if (ResetRotation.Value) { + if (ResetRotationOnModeChange.Value) { ResetRotations(); } else { Quaternion currentRotation = _comfyGizmoRoot.rotation; diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index 881043e..b9de884 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -16,7 +16,7 @@ public static class PluginConfig { public static ConfigEntry SnapDivisionDecrementKey; public static ConfigEntry ShowGizmoPrefab; - public static ConfigEntry ResetRotation; + public static ConfigEntry ResetRotationOnModeChange; public static ConfigEntry ResetRotationOnSnapDivisionChange; public static int MaxSnapDivisions = 256; @@ -89,9 +89,10 @@ public static void BindConfig(ConfigFile config) { new KeyboardShortcut(KeyCode.PageDown), "Doubles snap divisions from current."); - ResetRotationOnSnapDivisionChange = config.Bind("Snap Division Change", "resetOnSnapDivisionChange", true, "Resets the piece's rotation on snap division change."); ShowGizmoPrefab = config.Bind("UI", "showGizmoPrefab", true, "Show the Gizmo prefab in placement mode."); - ResetRotation = config.Bind("RotationFrame", "resetOnModeChange", true, "Resets the piece's rotation on mode switch."); + + ResetRotationOnSnapDivisionChange = config.Bind("Reset", "resetOnSnapDivisionChange", true, "Resets the piece's rotation on snap division change."); + ResetRotationOnModeChange = config.Bind("Reset", "resetOnModeChange", true, "Resets the piece's rotation on mode switch."); } } } From 75cfd276a346a86c8e5f44b542486c5c13907353 Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Mon, 31 Oct 2022 20:50:30 -0700 Subject: [PATCH 23/26] Updated version numbers in readme. --- Gizmo/Gizmo/README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index d65e232..a1ee387 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -1,4 +1,4 @@ -# ComfyGizmo v1.4.0 +# ComfyGizmo v1.5.0 *Comfy-specific version of Gizmo.* @@ -21,7 +21,7 @@ 1. **Disable or uninstall** the existing `Gizmo v1.0.0` mod by Rolo. 2. **Disable or uninstall** any manually installed `ComfyGizmo_v1.3.0` or earlier. - 3. Go to Settings > Import local mod > select `ComfyGizmo_v1.4.0.zip`. + 3. Go to Settings > Import local mod > select `ComfyGizmo_v1.5.0.zip`. 4. Click "OK/Import local mod" on the pop-up for information. ## Changelog From 91e362dacb2cbbfaa2f549b2ede32f8fbeba8d2c Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Tue, 1 Nov 2022 20:27:33 -0700 Subject: [PATCH 24/26] Fixed issue with gizmo axes rotating incorrectly and restored function similar to Gizmo v1.4.0. --- Gizmo/Gizmo/ComfyGizmo.cs | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 6ccb754..de67537 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -180,14 +180,11 @@ static void Rotate() { if (Input.GetKey(ResetAllRotationKey.Value.MainKey)) { ResetRotations(); } else if (Input.GetKey(XRotationKey.Value.MainKey)) { - _xGizmo.localScale = Vector3.one * 1.5f; - _eulerAngles.x = GetRotationAngle(_eulerAngles.x); + HandleAxisInput(ref _eulerAngles.x, _xGizmo); } else if (Input.GetKey(ZRotationKey.Value.MainKey)) { - _zGizmo.localScale = Vector3.one * 1.5f; - _eulerAngles.z = GetRotationAngle(_eulerAngles.z); + HandleAxisInput(ref _eulerAngles.z, _zGizmo); } else { - _yGizmo.localScale = Vector3.one * 1.5f; - _eulerAngles.y = GetRotationAngle(_eulerAngles.y); + HandleAxisInput(ref _eulerAngles.y, _yGizmo); } _comfyGizmo.transform.localRotation = Quaternion.Euler(_eulerAngles); @@ -225,13 +222,13 @@ static void RotateAxes(float rotation, Vector3 rotVector) { _gizmoRoot.rotation *= Quaternion.AngleAxis(rotation, rotVector); } - static float GetRotationAngle(float rotation) { + static void HandleAxisInput(ref float rotation, Transform gizmo) { + gizmo.localScale = Vector3.one * 1.5f; rotation += Math.Sign(Input.GetAxis("Mouse ScrollWheel")) * _snapAngle; if (Input.GetKey(ResetRotationKey.Value.MainKey)) { rotation = 0f; } - return rotation; } static void HandleAxisInputLocalFrame(ref float rotation, Vector3 rotVector, Transform gizmo) { @@ -270,9 +267,9 @@ static void ResetGizmoRoot() { } static void RotateGizmoComponents(Vector3 eulerAngles) { - _xGizmoRoot.localRotation = Quaternion.Euler(eulerAngles.x, 0f, 0f); + _xGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, eulerAngles.z); _yGizmoRoot.localRotation = Quaternion.Euler(0f, eulerAngles.y, 0f); - _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, eulerAngles.z); + _zGizmoRoot.localRotation = Quaternion.Euler(eulerAngles.x, 0f, 0f); } static void ResetRotationsLocalFrame() { From 1ad858889408fa3576492cc8cd165a2bfbc4f96b Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Tue, 1 Nov 2022 21:10:42 -0700 Subject: [PATCH 25/26] Restored rotation to Gizmo v1.2.0 standards allowing for additional patterns and using the 2-1-0 fixed rotation system. --- Gizmo/Gizmo/ComfyGizmo.cs | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index de67537..7c8376b 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -87,7 +87,7 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable>(_ => _comfyGizmoRoot.rotation)) + .InsertAndAdvance(Transpilers.EmitDelegate>(_ => _xGizmoRoot.rotation)) .InstructionEnumeration(); } @@ -267,9 +267,9 @@ static void ResetGizmoRoot() { } static void RotateGizmoComponents(Vector3 eulerAngles) { - _xGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, eulerAngles.z); + _xGizmoRoot.localRotation = Quaternion.Euler(eulerAngles.x, 0f, 0f); _yGizmoRoot.localRotation = Quaternion.Euler(0f, eulerAngles.y, 0f); - _zGizmoRoot.localRotation = Quaternion.Euler(eulerAngles.x, 0f, 0f); + _zGizmoRoot.localRotation = Quaternion.Euler(0f, 0f, eulerAngles.z); } static void ResetRotationsLocalFrame() { From 9be94f9beb81a46e1a856dd7f9b21ca25d120d30 Mon Sep 17 00:00:00 2001 From: BruceOfTheBow Date: Tue, 1 Nov 2022 21:23:26 -0700 Subject: [PATCH 26/26] Added toggle between old and new gizmo rotation scheme. Fixed issue with axes rotations on gizmo being misaligned with piece rotation. Updated to v1.5.1. --- Gizmo/Gizmo/ComfyGizmo.cs | 22 +++++++++++++++++++--- Gizmo/Gizmo/PluginConfig.cs | 3 +++ Gizmo/Gizmo/README.md | 4 ++++ Gizmo/Gizmo/manifest.json | 2 +- 4 files changed, 27 insertions(+), 4 deletions(-) diff --git a/Gizmo/Gizmo/ComfyGizmo.cs b/Gizmo/Gizmo/ComfyGizmo.cs index 7c8376b..46ac594 100644 --- a/Gizmo/Gizmo/ComfyGizmo.cs +++ b/Gizmo/Gizmo/ComfyGizmo.cs @@ -17,7 +17,7 @@ namespace Gizmo { public class ComfyGizmo : BaseUnityPlugin { public const string PluginGUID = "com.rolopogo.gizmo.comfy"; public const string PluginName = "ComfyGizmo"; - public const string PluginVersion = "1.5.0"; + public const string PluginVersion = "1.5.1"; static GameObject _gizmoPrefab = null; static Transform _gizmoRoot; @@ -77,7 +77,8 @@ static class PlayerPatch { [HarmonyTranspiler] [HarmonyPatch(nameof(Player.UpdatePlacementGhost))] static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable instructions) { - return new CodeMatcher(instructions) + if(NewGizmoRotation.Value) { + return new CodeMatcher(instructions) .MatchForward( useEnd: false, new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(Player), nameof(Player.m_placeRotation))), @@ -87,8 +88,23 @@ static IEnumerable UpdatePlacementGhostTranspiler(IEnumerable>(_ => _xGizmoRoot.rotation)) + .InsertAndAdvance(Transpilers.EmitDelegate>(_ => _comfyGizmoRoot.rotation)) .InstructionEnumeration(); + } else { + return new CodeMatcher(instructions) + .MatchForward( + useEnd: false, + new CodeMatch(OpCodes.Ldfld, AccessTools.Field(typeof(Player), nameof(Player.m_placeRotation))), + new CodeMatch(OpCodes.Conv_R4), + new CodeMatch(OpCodes.Mul), + new CodeMatch(OpCodes.Ldc_R4), + new CodeMatch(OpCodes.Call), + new CodeMatch(OpCodes.Stloc_S)) + .Advance(offset: 5) + .InsertAndAdvance(Transpilers.EmitDelegate>(_ => _xGizmoRoot.rotation)) + .InstructionEnumeration(); + } + } [HarmonyPostfix] diff --git a/Gizmo/Gizmo/PluginConfig.cs b/Gizmo/Gizmo/PluginConfig.cs index b9de884..705d3a0 100644 --- a/Gizmo/Gizmo/PluginConfig.cs +++ b/Gizmo/Gizmo/PluginConfig.cs @@ -18,6 +18,7 @@ public static class PluginConfig { public static ConfigEntry ShowGizmoPrefab; public static ConfigEntry ResetRotationOnModeChange; public static ConfigEntry ResetRotationOnSnapDivisionChange; + public static ConfigEntry NewGizmoRotation; public static int MaxSnapDivisions = 256; public static int MinSnapDivisions = 2; @@ -93,6 +94,8 @@ public static void BindConfig(ConfigFile config) { ResetRotationOnSnapDivisionChange = config.Bind("Reset", "resetOnSnapDivisionChange", true, "Resets the piece's rotation on snap division change."); ResetRotationOnModeChange = config.Bind("Reset", "resetOnModeChange", true, "Resets the piece's rotation on mode switch."); + + NewGizmoRotation = config.Bind("Rotation Mode", "newGizmoRotation", false, "Enables post Gizmo v1.2.0 rotation scheme. Restart required for changes to take effect."); } } } diff --git a/Gizmo/Gizmo/README.md b/Gizmo/Gizmo/README.md index a1ee387..f830449 100644 --- a/Gizmo/Gizmo/README.md +++ b/Gizmo/Gizmo/README.md @@ -26,6 +26,10 @@ ## Changelog +### 1.5.1 + * Added toggle to allow for v1.2.0 rotation scheme versus v1.3.0 rotation scheme. Restart required on enabling/disabling + * Fixed issue with gizmo visual axes not rotating correctly. + ### 1.5.0 * Added hotkeys for halving and doubling snap divisions. PageUp and PageDown by default. * Added toggle for resetting piece rotation on snap division change. Enabled by default. diff --git a/Gizmo/Gizmo/manifest.json b/Gizmo/Gizmo/manifest.json index b915ac2..f2322e9 100644 --- a/Gizmo/Gizmo/manifest.json +++ b/Gizmo/Gizmo/manifest.json @@ -1,6 +1,6 @@ { "name": "Gizmo", - "version_number": "1.5.0", + "version_number": "1.5.1", "author": "ComfyMods", "website_url": "https://github.com/redseiko/ValheimMods/tree/main/Gizmo/Gizmo", "description": "Comfy-specific fork of Gizmo.",