diff --git a/AvatarScale/AvatarScaleMod.csproj b/AvatarScale/AvatarScaleMod.csproj
index ce04547..712ea9c 100644
--- a/AvatarScale/AvatarScaleMod.csproj
+++ b/AvatarScale/AvatarScaleMod.csproj
@@ -20,7 +20,10 @@
-
+
+
+
+
@@ -28,9 +31,10 @@
$(MsBuildThisFileDirectory)\..\.ManagedLibs\BTKUILib.dll
False
+
+ $(MsBuildThisFileDirectory)\..\.ManagedLibs\ActionMenu.dll
+ False
+
-
-
-
\ No newline at end of file
diff --git a/AvatarScale/AvatarScaling/AvatarScaleManager.cs b/AvatarScale/AvatarScaling/AvatarScaleManager.cs
index e9835dd..aa39e3b 100644
--- a/AvatarScale/AvatarScaling/AvatarScaleManager.cs
+++ b/AvatarScale/AvatarScaling/AvatarScaleManager.cs
@@ -1,5 +1,7 @@
using ABI_RC.Core.IO;
using ABI_RC.Core.Player;
+using ABI_RC.Core.Player.AvatarTracking;
+using ABI_RC.Core.UI;
using ABI_RC.Systems.GameEventSystem;
using NAK.AvatarScaleMod.Components;
using NAK.AvatarScaleMod.Networking;
@@ -25,7 +27,7 @@ public class AvatarScaleManager : MonoBehaviour
set
{
if (value != _settingUniversalScaling && value == false)
- SetHeight(-1f);
+ ResetHeight();
_settingUniversalScaling = value;
}
@@ -54,13 +56,11 @@ public class AvatarScaleManager : MonoBehaviour
_settingUniversalScaling = ModSettings.EntryUseUniversalScaling.Value;
CVRGameEventSystem.Instance.OnConnected.AddListener(OnInstanceConnected);
- //SchedulerSystem.AddJob(new SchedulerSystem.Job(ForceHeightUpdate), 0f, 10f, -1);
}
private void OnDestroy()
{
CVRGameEventSystem.Instance.OnConnected.RemoveListener(OnInstanceConnected);
- //SchedulerSystem.RemoveJob(new SchedulerSystem.Job(ForceHeightUpdate));
}
#endregion
@@ -121,18 +121,25 @@ public class AvatarScaleManager : MonoBehaviour
public void ResetHeight()
{
- if (!_settingUniversalScaling)
+ if (_localAvatarScaler == null)
return;
+
+ if (!_localAvatarScaler.IsHeightAdjustedFromInitial())
+ return;
+
+ CohtmlHud.Instance.ViewDropTextImmediate("(Local) AvatarScaleMod", "Avatar Scale Reset!",
+ "Universal Scaling is now disabled.");
- if (_localAvatarScaler != null)
- _localAvatarScaler.ResetHeight();
- ModNetwork.SendNetworkHeight(-1f);
+ SetHeight(-1f);
}
public float GetHeight()
{
if (_localAvatarScaler == null)
- return -1f;
+ return PlayerAvatarPoint.defaultAvatarHeight;
+
+ if (!_localAvatarScaler.IsHeightAdjustedFromInitial())
+ return PlayerSetup.Instance.GetAvatarHeight();
return _localAvatarScaler.GetHeight();
}
@@ -161,10 +168,7 @@ public class AvatarScaleManager : MonoBehaviour
public bool IsHeightAdjustedFromInitial()
{
- if (_localAvatarScaler == null)
- return false;
-
- return _localAvatarScaler.IsHeightAdjustedFromInitial();
+ return _localAvatarScaler != null && _localAvatarScaler.IsHeightAdjustedFromInitial();
}
#endregion
@@ -174,9 +178,14 @@ public class AvatarScaleManager : MonoBehaviour
public float GetNetworkHeight(string playerId)
{
if (_networkedScalers.TryGetValue(playerId, out NetworkScaler scaler))
- return scaler.GetHeight();
+ if (scaler.IsHeightAdjustedFromInitial()) return scaler.GetHeight();
- //doesn't have mod, get from player avatar directly
+ //doesn't have mod or has no custom height, get from player avatar directly
+ CVRPlayerEntity playerEntity = CVRPlayerManager.Instance.NetworkPlayers.Find((players) => players.Uuid == playerId);
+ if (playerEntity != null && playerEntity.PuppetMaster != null)
+ return playerEntity.PuppetMaster.GetAvatarHeight();
+
+ // player is invalid???
return -1f;
}
diff --git a/AvatarScale/AvatarScaling/Components/BaseScaler.cs b/AvatarScale/AvatarScaling/Components/BaseScaler.cs
index 642a9a9..4f108ee 100644
--- a/AvatarScale/AvatarScaling/Components/BaseScaler.cs
+++ b/AvatarScale/AvatarScaling/Components/BaseScaler.cs
@@ -62,7 +62,8 @@ public class BaseScaler : MonoBehaviour
public void SetTargetHeight(float height)
{
- if (Math.Abs(height - _targetHeight) < float.Epsilon)
+ if (_isHeightAdjustedFromInitial
+ && Math.Abs(height - _targetHeight) < float.Epsilon)
return;
if (height < float.Epsilon)
@@ -70,7 +71,7 @@ public class BaseScaler : MonoBehaviour
ResetHeight();
return;
}
-
+
if (!_isHeightAdjustedFromInitial)
_legacyAnimationScale = Vector3.zero;
diff --git a/AvatarScale/AvatarScaling/Components/LocalScaler.cs b/AvatarScale/AvatarScaling/Components/LocalScaler.cs
index 68146e4..27ba198 100644
--- a/AvatarScale/AvatarScaling/Components/LocalScaler.cs
+++ b/AvatarScale/AvatarScaling/Components/LocalScaler.cs
@@ -1,4 +1,6 @@
using ABI_RC.Core.Player;
+using ABI_RC.Core.UI;
+using ABI.CCK.Components;
using NAK.AvatarScaleMod.AvatarScaling;
using UnityEngine;
@@ -16,7 +18,7 @@ public class LocalScaler : BaseScaler
_isAvatarInstantiated = false;
_isHeightAdjustedFromInitial = false;
}
-
+
#endregion
#region Overrides
@@ -25,7 +27,7 @@ public class LocalScaler : BaseScaler
{
if (avatarObject == null)
return;
-
+
base.OnAvatarInstantiated(avatarObject, initialHeight, initialScale);
await FindComponentsOfTypeAsync(scalableComponentTypes);
@@ -83,6 +85,7 @@ public class LocalScaler : BaseScaler
_legacyAnimationScale = _avatarTransform.localScale;
AvatarScaleMod.Logger.Msg("AnimationClip-based avatar scaling detected. Disabling Universal Scaling.");
+ CohtmlHud.Instance.ViewDropTextImmediate("(Local) AvatarScaleMod", "Avatar Scale Changed!", "Universal Scaling is now disabled in favor of built-in avatar scaling.");
AvatarScaleManager.Instance.ResetHeight(); // disable mod, user used a scale slider
return true;
}
diff --git a/AvatarScale/Input/DebugKeybinds.cs b/AvatarScale/Input/DebugKeybinds.cs
index 9756930..aa6835e 100644
--- a/AvatarScale/Input/DebugKeybinds.cs
+++ b/AvatarScale/Input/DebugKeybinds.cs
@@ -3,35 +3,41 @@ using UnityEngine;
namespace NAK.AvatarScaleMod.InputHandling;
-public static class DebugKeybinds
+internal static class DebugKeybinds
{
- public static void DoDebugInput()
+ private const float Step = 0.1f;
+
+ internal static void DoDebugInput()
{
if (AvatarScaleManager.Instance == null)
return;
- float currentHeight;
- const float step = 0.1f;
-
if (Input.GetKeyDown(KeyCode.Equals) || Input.GetKeyDown(KeyCode.KeypadPlus))
{
- currentHeight = AvatarScaleManager.Instance.GetHeight() + step;
- AvatarScaleManager.Instance.SetHeight(currentHeight);
-
- AvatarScaleMod.Logger.Msg($"Setting height: {currentHeight}");
+ AdjustHeight(Step);
}
else if (Input.GetKeyDown(KeyCode.Minus) || Input.GetKeyDown(KeyCode.KeypadMinus))
{
- currentHeight = AvatarScaleManager.Instance.GetHeight() - step;
- AvatarScaleManager.Instance.SetHeight(currentHeight);
-
- AvatarScaleMod.Logger.Msg($"Setting height: {currentHeight}");
+ AdjustHeight(-Step);
}
else if (Input.GetKeyDown(KeyCode.Backspace))
{
- AvatarScaleManager.Instance.ResetHeight();
-
- AvatarScaleMod.Logger.Msg($"Resetting height.");
+ ResetHeight();
}
}
+
+ private static void AdjustHeight(float adjustment)
+ {
+ float currentHeight = AvatarScaleManager.Instance.GetHeight() + adjustment;
+ currentHeight = Mathf.Max(0f, currentHeight);
+ AvatarScaleManager.Instance.SetHeight(currentHeight);
+
+ AvatarScaleMod.Logger.Msg($"[Debug] Setting height: {currentHeight}");
+ }
+
+ private static void ResetHeight()
+ {
+ AvatarScaleManager.Instance.ResetHeight();
+ AvatarScaleMod.Logger.Msg("[Debug] Resetting height.");
+ }
}
\ No newline at end of file
diff --git a/AvatarScale/Input/ScaleReconizer.cs b/AvatarScale/Input/ScaleReconizer.cs
index 444659f..7bf74de 100644
--- a/AvatarScale/Input/ScaleReconizer.cs
+++ b/AvatarScale/Input/ScaleReconizer.cs
@@ -32,7 +32,8 @@ public static class ScaleReconizer
secondGesture = CVRGestureStep.Gesture.Fist,
startDistance = 1f,
endDistance = 0.25f,
- direction = CVRGestureStep.GestureDirection.MovingIn
+ direction = CVRGestureStep.GestureDirection.MovingIn,
+ needsToBeInView = true,
});
gesture.onStart.AddListener(OnScaleStart);
gesture.onStay.AddListener(OnScaleStay);
@@ -50,7 +51,8 @@ public static class ScaleReconizer
secondGesture = CVRGestureStep.Gesture.Fist,
startDistance = 0.25f,
endDistance = 1f,
- direction = CVRGestureStep.GestureDirection.MovingOut
+ direction = CVRGestureStep.GestureDirection.MovingOut,
+ needsToBeInView = true,
});
gesture.onStart.AddListener(OnScaleStart);
gesture.onStay.AddListener(OnScaleStay);
diff --git a/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs b/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs
index c2a7ae5..6e276c4 100644
--- a/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs
+++ b/AvatarScale/Integrations/BTKUI/BTKUIAddon.cs
@@ -1,71 +1,137 @@
-using System.Runtime.CompilerServices;
+using System.IO;
+using System.Reflection;
using ABI_RC.Core.InteractionSystem;
using ABI_RC.Core.IO;
using BTKUILib;
using BTKUILib.UIObjects;
+using BTKUILib.UIObjects.Components;
using MelonLoader;
-using NAK.AvatarScaleMod.Integrations.BTKUI;
+using NAK.AvatarScaleMod.AvatarScaling;
using UnityEngine;
-namespace NAK.AvatarScaleMod.Integrations;
-
-public static class BTKUIAddon
+namespace NAK.AvatarScaleMod.Integrations
{
- #region Initialization
-
- [MethodImpl(MethodImplOptions.NoInlining)]
- public static void Initialize()
+ public static class BtkUiAddon
{
- Page as_RootPage = new(ModSettings.SettingsCategory, ModSettings.SettingsCategory, true, "")
- {
- MenuTitle = ModSettings.SettingsCategory,
- MenuSubtitle = "Avatar Scale Mod Settings"
- };
+ private static string _selectedPlayer;
- QuickMenuAPI.OnMenuRegenerate += (_) =>
- {
- SchedulerSystem.AddJob((InjectMenu), 1f, 1f, 1);
- };
- }
+ #region Initialization
- // private static void InjectMenu()
- // {
- // MelonLogger.Msg("Injecting into QM!");
- // CVR_MenuManager.Instance.quickMenu.View.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
- // }
-
- private static void InjectMenu()
- {
- MelonLogger.Msg("Injecting into QM!");
- string menuJsPath = Path.Combine(Application.streamingAssetsPath, "Cohtml", "UIResources", "AvatarScaleMod", "menu.js");
- string menuJsContent = ReadJSFile(menuJsPath);
- CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(menuJsContent);
- }
-
- private static string ReadJSFile(string path)
- {
- if(File.Exists(path))
+ public static void Initialize()
{
- return File.ReadAllText(path);
+ PrepareIcons();
+ SetupRootPage();
+ SetupPlayerSelectPage();
+ RegisterEventHandlers();
}
- MelonLogger.Warning($"File not found: {path}");
- return string.Empty;
+ private static void PrepareIcons()
+ {
+ QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightConfig", GetIconStream("ASM_Icon_AvatarHeightConfig.png"));
+ QuickMenuAPI.PrepareIcon(ModSettings.ModName, "ASM_Icon_AvatarHeightCopy", GetIconStream("ASM_Icon_AvatarHeightCopy.png"));
+ }
+
+ private static void SetupRootPage()
+ {
+ // we only need the page, as we inject our own elements into it aa
+ Page rootPage = new Page(ModSettings.ModName, ModSettings.SettingsCategory, true, "ASM_Icon_AvatarHeightConfig")
+ {
+ MenuTitle = ModSettings.SettingsCategory,
+ MenuSubtitle = "Universal Scaling Settings"
+ };
+ }
+
+ private static void SetupPlayerSelectPage()
+ {
+ // what other things would be worth adding here?
+ Category category = QuickMenuAPI.PlayerSelectPage.AddCategory(ModSettings.SettingsCategory, ModSettings.ModName);
+ Button button = category.AddButton("Copy Height", "ASM_Icon_AvatarHeightCopy", "Copy selected players Eye Height.");
+ button.OnPress += OnCopyPlayerHeight;
+ }
+
+ private static void RegisterEventHandlers()
+ {
+ QuickMenuAPI.OnPlayerSelected += (_, id) => _selectedPlayer = id;
+ QuickMenuAPI.OnMenuRegenerate += _ => ScheduleMenuInjection();
+ QuickMenuAPI.OnTabChange += OnTabChange;
+ }
+
+ private static void ScheduleMenuInjection()
+ {
+ CVR_MenuManager.Instance.quickMenu.View.BindCall("asm-AvatarHeightUpdated", new Action(OnAvatarHeightUpdated));
+ SchedulerSystem.AddJob(InjectMenu, 1f, 1f, 1);
+ }
+
+ private static void InjectMenu()
+ {
+ AvatarScaleMod.Logger.Msg("Injecting into our BTKUI AvatarScaleMod page!");
+ string menuJsPath = Path.Combine(Application.streamingAssetsPath, "Cohtml", "UIResources", "AvatarScaleMod", "menu.js");
+ string menuJsContent = File.Exists(menuJsPath) ? File.ReadAllText(menuJsPath) : string.Empty;
+
+ if (string.IsNullOrEmpty(menuJsContent))
+ {
+ AvatarScaleMod.Logger.Msg("Injecting embedded menu.js included with mod!");
+ CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(Scripts.GetEmbeddedScript("menu.js"));
+ }
+ else
+ {
+ AvatarScaleMod.Logger.Msg($"Injecting development menu.js found in: {menuJsPath}");
+ CVR_MenuManager.Instance.quickMenu.View._view.ExecuteScript(menuJsContent);
+ }
+ }
+
+ #endregion
+
+ private static void OnCopyPlayerHeight()
+ {
+ float networkHeight = AvatarScaleManager.Instance.GetNetworkHeight(_selectedPlayer);
+ if (networkHeight < 0) return;
+ AvatarScaleManager.Instance.SetHeight(networkHeight);
+ }
+
+ private static void OnAvatarHeightUpdated(float height)
+ {
+ AvatarScaleManager.Instance.SetHeight(height);
+ }
+
+ #region Private Methods
+
+ private static DateTime lastTime = DateTime.Now;
+ private static void OnTabChange(string newTab, string previousTab)
+ {
+ if (newTab == "btkUI-AvatarScaleMod-MainPage")
+ {
+ TimeSpan timeDifference = DateTime.Now - lastTime;
+ if (timeDifference.TotalSeconds <= 0.5)
+ {
+ AvatarScaleManager.Instance.ResetHeight();
+ return;
+ }
+ }
+ lastTime = DateTime.Now;
+ }
+
+ private static Stream GetIconStream(string iconName)
+ {
+ Assembly assembly = Assembly.GetExecutingAssembly();
+ string assemblyName = assembly.GetName().Name;
+ return assembly.GetManifestResourceStream($"{assemblyName}.resources.{iconName}");
+ }
+
+ #endregion
+
+ #region Melon Pref Helpers
+
+ internal static void AddMelonToggle(ref Category category, MelonPreferences_Entry entry)
+ {
+ category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
+ }
+
+ internal static void AddMelonSlider(ref Page page, MelonPreferences_Entry entry, float min, float max, int decimalPlaces = 2)
+ {
+ page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
+ }
+
+ #endregion
}
-
- #endregion
-
- #region Melon Pref Helpers
-
- internal static void AddMelonToggle(ref Category category, MelonPreferences_Entry entry)
- {
- category.AddToggle(entry.DisplayName, entry.Description, entry.Value).OnValueUpdated += b => entry.Value = b;
- }
-
- internal static void AddMelonSlider(ref Page page, MelonPreferences_Entry entry, float min, float max, int decimalPlaces = 2)
- {
- page.AddSlider(entry.DisplayName, entry.Description, entry.Value, min, max, decimalPlaces).OnValueUpdated += f => entry.Value = f;
- }
-
- #endregion
}
\ No newline at end of file
diff --git a/AvatarScale/Integrations/BTKUI/Page_AvatarScale.cs b/AvatarScale/Integrations/BTKUI/Page_AvatarScale.cs
deleted file mode 100644
index 7e2750f..0000000
--- a/AvatarScale/Integrations/BTKUI/Page_AvatarScale.cs
+++ /dev/null
@@ -1,87 +0,0 @@
-using ABI_RC.Systems.Camera;
-using BTKUILib;
-using BTKUILib.UIObjects;
-using MelonLoader;
-using UnityEngine;
-
-namespace NAK.AvatarScaleMod.Integrations.BTKUI;
-
-public class PortableCameraCategory
-{
- private static string categoryName = "Portable Camera";
-
- internal static void AddCategory(Page parent)
- {
- QuickMenuAPI.OnTabChange += OnTabChange;
-
- // Create category and add elements to it
- var category = parent.AddCategory(categoryName);
- category.AddButton("Take Photo", "TakePhoto-Icon", "Quickly take a photo. This respects set timers & other related settings.").OnPress += TakePhoto;
- category.AddButton("Cycle Delay", "CycleDelay-Icon", "Quickly cycle photo timers. Off, 3s, 5s, 10s.").OnPress += CycleCaptureDelay;
- category.AddButton("Open Folder", "OpenFolder-Icon", "Quickly open the root of the ChilloutVR screenshots folder in Windows Explorer.").OnPress += OpenScreenshotsFolder;
-
- // Clone of the default camera settings page
- var settingsPage = category.AddPage("Settings", "Settings-Icon", "Sub page of settings to configure the portable camera.", parent.MenuTitle);
- settingsPage.AddCategory("Main Settings");
- settingsPage.AddSlider("Field of View", "Field of View of portable camera.", 40f, 10f, 120f);
- settingsPage.AddSlider("Focal Length", "Focal Length of portable camera.", 50f, 24f, 200f);
- settingsPage.AddSlider("Aperture", "Aperture of portable camera.", 1.8f, 1.2f, 8f);
- }
-
- private static bool PortableCameraReady()
- {
- bool active = (bool)(PortableCamera.Instance?.IsActive());
- if (!active) CVRCamController.Instance?.Toggle();
- return active;
- }
-
- private static void TakePhoto()
- {
- MelonLogger.Msg("Took photo!");
- if (PortableCameraReady())
- PortableCamera.Instance?.MakePhoto();
- }
-
- private static void CycleCaptureDelay()
- {
- if (PortableCameraReady())
- {
- PortableCamera.Instance?.ChangeCameraCaptureDelay();
- QuickMenuAPI.ShowAlertToast("Delay set to " + PortableCamera.Instance.timerText.text, 1);
- }
- }
-
- //this was mistake, but now feature cause fuck it
- private static void PauseCamera()
- {
- MelonLogger.Msg("Paused camera!");
- GameObject camera = PortableCamera.Instance.gameObject;
- PortableCamera.Instance.gameObject.SetActive(!camera.activeSelf);
- }
-
- private static void OpenScreenshotsFolder()
- {
- MelonLogger.Msg("Opened screenshots folder!");
- string path = Path.Combine(Environment.GetFolderPath(Environment.SpecialFolder.MyPictures), "ChilloutVR");
- if (!Directory.Exists(path))
- {
- Directory.CreateDirectory(path);
- }
- Application.OpenURL("file:///" + path);
- }
-
- private static DateTime lastTime = DateTime.Now;
- private static void OnTabChange(string newTab, string previousTab)
- {
- if (newTab == "btkUI-AvatarScaleMod-MainPage")
- {
- TimeSpan timeDifference = DateTime.Now - lastTime;
- if (timeDifference.TotalSeconds <= 0.5)
- {
- // The new page and previous page are equal and were opened within 0.5 seconds of each other
- CVRCamController.Instance?.Toggle();
- }
- }
- lastTime = DateTime.Now;
- }
-}
\ No newline at end of file
diff --git a/AvatarScale/Main.cs b/AvatarScale/Main.cs
index f2d2661..b688c6e 100644
--- a/AvatarScale/Main.cs
+++ b/AvatarScale/Main.cs
@@ -11,15 +11,15 @@ public class AvatarScaleMod : MelonMod
public override void OnInitializeMelon()
{
Logger = LoggerInstance;
+
+ ModNetwork.Subscribe();
+ ModSettings.Initialize();
ApplyPatches(typeof(HarmonyPatches.PlayerSetupPatches));
ApplyPatches(typeof(HarmonyPatches.PuppetMasterPatches));
ApplyPatches(typeof(HarmonyPatches.GesturePlaneTestPatches));
- InitializeIntegration("BTKUILib", Integrations.BTKUIAddon.Initialize);
-
- ModNetwork.Subscribe();
- ModSettings.Initialize();
+ InitializeIntegration("BTKUILib", Integrations.BtkUiAddon.Initialize);
}
public override void OnUpdate()
diff --git a/AvatarScale/ModSettings.cs b/AvatarScale/ModSettings.cs
index 95a8623..f18cefd 100644
--- a/AvatarScale/ModSettings.cs
+++ b/AvatarScale/ModSettings.cs
@@ -9,10 +9,11 @@ namespace NAK.AvatarScaleMod;
internal static class ModSettings
{
// Constants
- internal const string SettingsCategory = nameof(AvatarScaleMod);
+ internal const string ModName = nameof(AvatarScaleMod);
+ internal const string SettingsCategory = "Avatar Scale Mod";
public static readonly MelonPreferences_Category Category =
- MelonPreferences.CreateCategory(SettingsCategory);
+ MelonPreferences.CreateCategory(ModName);
public static readonly MelonPreferences_Entry EntryUseUniversalScaling =
Category.CreateEntry("use_universal_scaling", true, display_name: "Use Universal Scaling", description: "Enable or disable universal scaling.");
diff --git a/AvatarScale/Networking/ModNetwork.cs b/AvatarScale/Networking/ModNetwork.cs
index 1d129e4..a94147d 100644
--- a/AvatarScale/Networking/ModNetwork.cs
+++ b/AvatarScale/Networking/ModNetwork.cs
@@ -14,6 +14,8 @@ public static class ModNetwork
{
public static bool Debug_NetworkInbound = false;
public static bool Debug_NetworkOutbound = false;
+
+ private static bool _isSubscribedToModNetwork;
#region Constants
@@ -60,11 +62,15 @@ public static class ModNetwork
internal static void Subscribe()
{
+ _isSubscribedToModNetwork = true;
ModNetworkManager.Subscribe(ModId, OnMessageReceived);
}
internal static void Update()
{
+ if (!_isSubscribedToModNetwork)
+ return;
+
ProcessOutboundQueue();
ProcessInboundQueue();
}
@@ -74,6 +80,9 @@ public static class ModNetwork
if (!IsConnectedToGameNetwork())
return;
+ if (!Enum.IsDefined(typeof(MessageType), messageType))
+ return;
+
if (!string.IsNullOrEmpty(playerId))
{
// to specific user
@@ -129,8 +138,7 @@ public static class ModNetwork
public static void RequestHeightSync()
{
var myCurrentHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
- if (myCurrentHeight > 0)
- OutboundQueue["global"] = new QueuedMessage { Type = MessageType.RequestHeight, Height = myCurrentHeight };
+ OutboundQueue["global"] = new QueuedMessage { Type = MessageType.RequestHeight, Height = myCurrentHeight };
}
#endregion
@@ -199,13 +207,12 @@ public static class ModNetwork
case MessageType.RequestHeight:
{
var myNetworkHeight = AvatarScaleManager.Instance.GetHeightForNetwork();
- if (myNetworkHeight > 0)
- OutboundQueue[message.Sender] = new QueuedMessage
- {
- Type = MessageType.SyncHeight,
- Height = myNetworkHeight,
- TargetPlayer = message.Sender
- };
+ OutboundQueue[message.Sender] = new QueuedMessage
+ {
+ Type = MessageType.SyncHeight,
+ Height = myNetworkHeight,
+ TargetPlayer = message.Sender
+ };
AvatarScaleManager.Instance.OnNetworkHeightUpdateReceived(message.Sender, message.Height);
break;
diff --git a/AvatarScale/Properties/AssemblyInfo.cs b/AvatarScale/Properties/AssemblyInfo.cs
index ba2b41c..0227b18 100644
--- a/AvatarScale/Properties/AssemblyInfo.cs
+++ b/AvatarScale/Properties/AssemblyInfo.cs
@@ -20,6 +20,7 @@ using System.Reflection;
[assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")]
[assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)]
[assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)]
+[assembly: MelonOptionalDependencies("Action Menu")]
[assembly: MelonColor(255, 241, 200, 82)]
[assembly: MelonAuthorColor(255, 114, 17, 25)]
[assembly: HarmonyDontPatchAll]
diff --git a/AvatarScale/resources/ASM_Icon_AvatarHeightConfig.png b/AvatarScale/resources/ASM_Icon_AvatarHeightConfig.png
new file mode 100644
index 0000000..08b68ea
Binary files /dev/null and b/AvatarScale/resources/ASM_Icon_AvatarHeightConfig.png differ
diff --git a/AvatarScale/resources/ASM_Icon_AvatarHeightCopy.png b/AvatarScale/resources/ASM_Icon_AvatarHeightCopy.png
new file mode 100644
index 0000000..e665a6d
Binary files /dev/null and b/AvatarScale/resources/ASM_Icon_AvatarHeightCopy.png differ
diff --git a/AvatarScale/resources/menu.js b/AvatarScale/resources/menu.js
index 2bd9ff7..5c6c697 100644
--- a/AvatarScale/resources/menu.js
+++ b/AvatarScale/resources/menu.js
@@ -1,42 +1,47 @@
(function() {
+
/* -------------------------------
* CSS Embedding
* ------------------------------- */
- {
+ function injectCSS() {
const embeddedCSS = `
- .slider-container {
- width: 80%;
- padding-left: 2em;
- position: relative;
- }
- .avatar-scale-slider-container {
+ // ass stands for Avatar Scale Slider
+
+ /* Container styles */
+ .ass-flex-container {
+ display: flex;
+ justify-content: space-between;
+ align-items: center;
+ }
+
+ /* ass Slider styles */
+ .ass-slider-container {
width: 100%;
- height: 3em;
+ }
+ .ass-slider-base {
+ height: 3.25em;
position: relative;
background: #555;
border-radius: 1.5em;
cursor: pointer;
overflow: hidden;
}
-
- .avatar-scale-track-inner {
+ .ass-slider-inner {
height: 100%;
background: #a9a9a9;
position: absolute;
top: 0;
left: 0;
}
-
- .avatar-scale-snap-point {
+ .ass-snap-point {
height: 100%;
width: 2px;
background: white;
position: absolute;
top: 0;
}
-
- .slider-display-value {
+ .ass-slider-value {
font-size: 2em;
position: relative;
left: 0.5em;
@@ -44,16 +49,79 @@
white-space: nowrap;
}
- .lock-icon {
+ /* Category (label) styles */
+ .ass-category-label {
+ font-size: 2.2em;
+ position: relative;
+ left: 0.5em;
+ color: #fff;
+ white-space: nowrap;
+ margin-right: 1em;
+ }
+
+ /* Circle Button styles */
+ .ass-circle-button {
+ height: 2em;
+ width: 2em;
+ border-radius: 50%;
+ background-color: #555;
+ border: none;
+ cursor: pointer;
+ color: #fff;
+ font-size: 1.75em;
+ display: flex;
+ align-items: center;
+ justify-content: center;
+ transition: background-color 0.3s;
+ }
+ .ass-circle-button:hover {
+ background-color: #777;
+ }
+
+ /* Custom Toggle styles */
+ .ass-custom-toggle {
+ width: 5em;
+ height: 2.5em;
+ background-color: #555;
+ border-radius: 1.25em;
position: absolute;
top: 50%;
- right: 1em;
- transform: translateY(-50%);
- width: 1.5em;
- height: 1.5em;
- background: url('path_to_lock_icon.png') no-repeat center;
- background-size: contain;
+ transform: translateY(-65%);
+ right: 0;
+ cursor: pointer;
+ transition: background-color 0.3s;
}
+
+ .ass-toggle-circle {
+ width: 2.5em;
+ height: 2.5em;
+ background-color: #a9a9a9;
+ border-radius: 50%;
+ position: absolute;
+ top: 0;
+ left: 0;
+ transition: left 0.3s;
+ }
+
+ .ass-custom-toggle.active .ass-toggle-circle {
+ left: 50%;
+ }
+
+ /* Label styles */
+ .ass-label {
+ font-size: 2em;
+ white-space: nowrap;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ position: relative;
+ z-index: 0;
+ }
+
+ .ass-toggle-setting {
+ position: relative;
+ height: 3.5em;
+ }
+
`;
const styleElement = document.createElement('style');
@@ -63,113 +131,284 @@
}
/* -------------------------------
- * Content Injection
+ * Main Content Element
* ------------------------------- */
- {
- const contentBlock = document.createElement('div');
- contentBlock.innerHTML = `
-
-
Avatar Motion Tweaker
-
-
-
-
-
-
- `;
-
- const targetElement = document.getElementById('btkUI-AvatarScaleMod-MainPage');
- if(targetElement) {
- targetElement.appendChild(contentBlock);
- } else {
- console.warn('Target element "btkUI-AvatarScaleMod-MainPage" not found!');
+ class MainContent {
+ constructor(targetId) {
+ this.element = document.createElement('div');
+ this.element.id = "AvatarScaleModContainer";
+ const targetElement = document.getElementById(targetId);
+ if (targetElement) {
+ targetElement.appendChild(this.element);
+ } else {
+ console.warn(`Target element "${targetId}" not found!`);
+ }
}
}
/* -------------------------------
- * Event Handlers & Utility Functions
+ * Generic Element Class
+ * ------------------------------- */
+ class Element {
+ constructor(tagName, parentElement) {
+ this.element = document.createElement(tagName);
+ parentElement.appendChild(this.element);
+ }
+ }
+
+ /* -------------------------------
+ * Container Object
* ------------------------------- */
- {
- const sliderContainer = document.querySelector('.avatar-scale-slider-container');
- const trackInner = document.querySelector('.avatar-scale-track-inner');
- const valueDisplay = document.querySelector('.slider-value');
+ class Container extends Element {
+ constructor(parentElement, {
+ width = '100%',
+ padding = '0em',
+ paddingTop = null,
+ paddingRight = null,
+ paddingBottom = null,
+ paddingLeft = null,
+ margin = '0em',
+ marginTop = null,
+ marginRight = null,
+ marginBottom = null,
+ marginLeft = null
+ } = {}) {
+ super('div', parentElement);
+ this.element.className = "ass-container";
+ this.element.style.width = width;
+ this.element.style.padding = padding;
+ this.element.style.margin = margin;
- const SNAP_TOLERANCE = 0.02;
- let snapPoints = [];
+ // padding values
+ if (paddingTop) this.element.style.paddingTop = paddingTop;
+ if (paddingRight) this.element.style.paddingRight = paddingRight;
+ if (paddingBottom) this.element.style.paddingBottom = paddingBottom;
+ if (paddingLeft) this.element.style.paddingLeft = paddingLeft;
- let isDragging = false;
+ // margin values
+ if (marginTop) this.element.style.marginTop = marginTop;
+ if (marginRight) this.element.style.marginRight = marginRight;
+ if (marginBottom) this.element.style.marginBottom = marginBottom;
+ if (marginLeft) this.element.style.marginLeft = marginLeft;
- sliderContainer.addEventListener('mousedown', (e) => {
- isDragging = true;
- updateTrackWidth(e.clientX);
- });
+ this.flexContainer = new Element('div', this.element);
+ this.flexContainer.element.className = "ass-flex-container";
+ }
- window.addEventListener('mousemove', (e) => {
- if (!isDragging) return;
- updateTrackWidth(e.clientX);
- });
+ appendElementToFlex(element) {
+ this.flexContainer.element.appendChild(element);
+ }
- window.addEventListener('mouseup', () => {
- isDragging = false;
- });
+ appendElement(element) {
+ this.element.appendChild(element);
+ }
+ }
- function updateTrackWidth(clientX) {
- const rect = sliderContainer.getBoundingClientRect();
+ /* -------------------------------
+ * Category (label) Class
+ * ------------------------------- */
+ class Category extends Element {
+ constructor(parentElement, text) {
+ super('span', parentElement);
+ this.element.className = "ass-category-label";
+ this.element.textContent = text;
+ }
+ }
- // Get padding values from the slider container
- const paddingLeft = parseFloat(getComputedStyle(sliderContainer).paddingLeft);
- const paddingRight = parseFloat(getComputedStyle(sliderContainer).paddingRight);
+ /* -------------------------------
+ * Circle Button Class
+ * ------------------------------- */
+ class CircleButton extends Element {
+ constructor(parentElement, text) {
+ super('button', parentElement);
+ this.element.className = "ass-circle-button";
+ this.element.textContent = text;
+ }
+ }
- // Calculate the effective width and position based on padding
+ /* -------------------------------
+ * Custom Toggle Class
+ * ------------------------------- */
+ class CustomToggle extends Element {
+ constructor(parentElement) {
+ super('div', parentElement);
+ this.element.className = "ass-custom-toggle";
+
+ this.toggleCircle = new Element('div', this.element);
+ this.toggleCircle.element.className = "ass-toggle-circle";
+ this.state = false;
+
+ this.element.addEventListener('click', () => {
+ this.toggle();
+ });
+ }
+
+ toggle() {
+ this.state = !this.state;
+ if (this.state) {
+ this.toggleCircle.element.style.left = '50%';
+ } else {
+ this.toggleCircle.element.style.left = '0';
+ }
+ }
+ }
+
+ /* -------------------------------
+ * Label Class
+ * ------------------------------- */
+ class Label extends Element {
+ constructor(parentElement, text) {
+ super('span', parentElement);
+ this.element.className = "ass-label";
+ this.element.textContent = text;
+ }
+ }
+
+ /* -------------------------------
+ * ToggleSetting Class
+ * ------------------------------- */
+ class ToggleSetting extends Element {
+ constructor(parentElement, labelText) {
+ super('div', parentElement);
+ this.element.className = "ass-toggle-setting";
+
+ const label = new Label(this.element, labelText);
+ this.toggle = new CustomToggle(this.element);
+ }
+ }
+
+ /* -------------------------------
+ * Slider Object
+ * ------------------------------- */
+ class Slider extends Element {
+ constructor(parentElement, min = 0.1, max = 5, initialValue = 1.8) {
+ super('div', parentElement);
+ this.element.className = "ass-slider-container";
+ this.min = min;
+ this.max = max;
+
+ // Value display
+ this.valueDisplay = new Element('span', this.element);
+ this.valueDisplay.element.className = "ass-slider-value";
+
+ // Slider content
+ this.sliderBase = new Element('div', this.element);
+ this.sliderBase.element.className = "ass-slider-base";
+
+ this.trackInner = new Element('div', this.sliderBase.element);
+ this.trackInner.element.className = "ass-slider-inner";
+
+ this.addEventListeners();
+
+ this.setInitialValue(initialValue);
+ }
+
+ setInitialValue(value) {
+ const percentage = (value - this.min) / (this.max - this.min);
+ this.trackInner.element.style.width = `${percentage * 100}%`;
+ this.valueDisplay.element.textContent = value.toFixed(2) + "m";
+ this.addSnapPoint(percentage);
+ }
+
+ addEventListeners() {
+ this.snapPoints = [];
+ let isDragging = false;
+
+ this.element.addEventListener('mousedown', (e) => {
+ isDragging = true;
+ this.updateTrackWidth(e.clientX);
+ });
+
+ window.addEventListener('mousemove', (e) => {
+ if (!isDragging) return;
+ this.updateTrackWidth(e.clientX);
+ });
+
+ window.addEventListener('mouseup', () => {
+ isDragging = false;
+ });
+ }
+
+ updateTrackWidth(clientX) {
+ const rect = this.element.getBoundingClientRect();
+ const paddingLeft = parseFloat(getComputedStyle(this.element).paddingLeft);
+ const paddingRight = parseFloat(getComputedStyle(this.element).paddingRight);
const effectiveWidth = rect.width - paddingLeft - paddingRight;
let x = clientX - rect.left - paddingLeft;
- // Ensure the position is within the bounds of the effective width
x = Math.min(Math.max(0, x), effectiveWidth);
- const percentage = x / effectiveWidth;
- const closestSnap = snapPoints.reduce((closest, snap) => {
+ let percentage = x / effectiveWidth;
+ const closestSnap = this.snapPoints.reduce((closest, snap) => {
return Math.abs(closest - percentage) < Math.abs(snap - percentage) ? closest : snap;
}, 1);
+ const SNAP_TOLERANCE = 0.01;
if (Math.abs(closestSnap - percentage) <= SNAP_TOLERANCE) {
x = closestSnap * effectiveWidth;
+ percentage = closestSnap;
}
- trackInner.style.width = `${x}px`;
- valueDisplay.textContent = (x / effectiveWidth * 100).toFixed(2) + "m";
+ this.trackInner.element.style.width = `${x}px`;
+
+ const value = this.min + (this.max - this.min) * percentage;
+ this.valueDisplay.element.textContent = value.toFixed(2) + "m";
+
+ engine.call("asm-AvatarHeightUpdated", value);
}
- function addSnapPoint(percentage) {
+ addSnapPoint(percentage) {
if (percentage < 0 || percentage > 1) return;
- const snap = document.createElement('div');
- snap.className = 'avatar-scale-snap-point';
- snap.style.left = `${percentage * 100}%`;
- sliderContainer.appendChild(snap);
- snapPoints.push(percentage);
+ const snap = new Element('div', this.sliderBase.element);
+ snap.element.className = 'ass-snap-point';
+ snap.element.style.left = `${percentage * 100}%`;
+ this.snapPoints.push(percentage);
}
- // To evenly space out snap points:
- function addEvenSnapPoints(count) {
+ addEvenSnapPoints(count) {
for (let i = 1; i <= count; i++) {
- addSnapPoint(i / (count + 1));
+ this.addSnapPoint(i / (count + 1));
}
}
-
- // Example usage:
- addEvenSnapPoints(5); // Adds 5 evenly spaced snap points
}
+
+ // Initialization
+ injectCSS();
+ const mainContent = new MainContent('btkUI-AvatarScaleMod-MainPage');
+ if (mainContent.element) {
+
+ const mainContainer = new Container(mainContent.element, {
+ width: '75%',
+ marginTop: '2em',
+ marginLeft: '2em',
+ marginBottom: '1em'
+ });
+
+ const slider = new Slider(mainContainer.flexContainer.element, 0.1, 3);
+ const buttonContainer = new Container(mainContainer.flexContainer.element, {
+ width: '20%',
+ marginTop: '2.5em',
+ marginLeft: '1em',
+ });
+ const circleButton1 = new CircleButton(buttonContainer.flexContainer.element, "+");
+ const circleButton2 = new CircleButton(buttonContainer.flexContainer.element, "-");
+
+ const settingsContainer = new Container(mainContent.element, {
+ width: '100%',
+ marginTop: '1em',
+ marginLeft: '1em',
+ });
+ const categoryLabel = new Category(settingsContainer.element, "Universal Scaling Settings:");
+
+ const settingsContainerInner = new Container(mainContent.element, {
+ width: '90%',
+ marginTop: '1em',
+ marginLeft: '3em',
+ });
+
+ const toggleSetting = new ToggleSetting(settingsContainerInner.element, "Universal Scaling (Mod Network)");
+ const toggleSetting2 = new ToggleSetting(settingsContainerInner.element, "Recognize Scale Gesture");
+ const toggleSetting3 = new ToggleSetting(settingsContainerInner.element, "Scale Components");
+ }
+
})();
\ No newline at end of file
diff --git a/AvatarScale/resources/nak_menu.css b/AvatarScale/resources/nak_menu.css
deleted file mode 100644
index d732c3d..0000000
--- a/AvatarScale/resources/nak_menu.css
+++ /dev/null
@@ -1,13 +0,0 @@
-.slider-container {
- margin: 10px 0;
- padding: 5px;
-}
-
-.slider-label {
- display: block;
- margin-bottom: 5px;
-}
-
-.test-slider {
- width: 100%;
-}