diff --git a/PropSpawnTweaks/Components/PropLoadingHexagon.cs b/PropLoadingHexagon/Components/LoadingHexagonController.cs similarity index 76% rename from PropSpawnTweaks/Components/PropLoadingHexagon.cs rename to PropLoadingHexagon/Components/LoadingHexagonController.cs index a05a479..7a2f704 100644 --- a/PropSpawnTweaks/Components/PropLoadingHexagon.cs +++ b/PropLoadingHexagon/Components/LoadingHexagonController.cs @@ -1,9 +1,11 @@ using UnityEngine; -namespace NAK.PropSpawnTweaks.Components; +namespace NAK.PropLoadingHexagon.Components; -public class PropLoadingHexagon : MonoBehaviour +public class LoadingHexagonController : MonoBehaviour { + public bool IsLoadingCanceled { get; set; } + [SerializeField] private SkinnedMeshRenderer _hexRenderer; [SerializeField] private TMPro.TextMeshPro _loadingText; [SerializeField] private Transform[] _hexTransforms; @@ -23,9 +25,12 @@ public class PropLoadingHexagon : MonoBehaviour _hexTransforms[i].Rotate(Vector3.up, 30f * Time.deltaTime * (i + 1)); } } - + public void SetLoadingText(string text) - => _loadingText.text = text; // SetAllDirty + { + if (_loadingText.text == text) return; + _loadingText.text = text; + } public void SetLoadingShape(float value) => _hexRenderer.SetBlendShapeWeight(0, value); diff --git a/PropSpawnTweaks/Integrations/ClappableLoadingHex.cs b/PropLoadingHexagon/Integrations/ClappableLoadingHex.cs similarity index 64% rename from PropSpawnTweaks/Integrations/ClappableLoadingHex.cs rename to PropLoadingHexagon/Integrations/ClappableLoadingHex.cs index 7db3013..f90e07f 100644 --- a/PropSpawnTweaks/Integrations/ClappableLoadingHex.cs +++ b/PropLoadingHexagon/Integrations/ClappableLoadingHex.cs @@ -1,15 +1,15 @@ -using NAK.PropSpawnTweaks.Components; +using NAK.PropLoadingHexagon.Components; using UnityEngine; -namespace NAK.PropSpawnTweaks.Integrations; +namespace NAK.PropLoadingHexagon.Integrations; public static class TheClapperIntegration { public static void Init() { - PropSpawnTweaksMod.OnPropPlaceholderCreated += (placeholder) => + PropLoadingHexagonMod.OnPropPlaceholderCreated += (placeholder) => { - if (placeholder.TryGetComponent(out PropLoadingHexagon loadingHexagon)) + if (placeholder.TryGetComponent(out LoadingHexagonController loadingHexagon)) ClappableLoadingHex.Create(loadingHexagon); }; } @@ -17,16 +17,16 @@ public static class TheClapperIntegration public class ClappableLoadingHex : Kafe.TheClapper.Clappable { - [SerializeField] private PropLoadingHexagon _loadingHexagon; + private LoadingHexagonController _loadingHexagon; public override void OnClapped(Vector3 clappablePosition) { if (_loadingHexagon == null) return; _loadingHexagon.IsLoadingCanceled = true; - Kafe.TheClapper.TheClapper.EmitParticles(clappablePosition, new Color(1f, 1f, 0f), 2f); + Kafe.TheClapper.TheClapper.EmitParticles(clappablePosition, new Color(1f, 1f, 0f), 2f); // why this internal } - public static void Create(PropLoadingHexagon loadingHexagon) + public static void Create(LoadingHexagonController loadingHexagon) { GameObject target = loadingHexagon.gameObject; if (!target.gameObject.TryGetComponent(out ClappableLoadingHex clappableHexagon)) diff --git a/PropLoadingHexagon/Main.cs b/PropLoadingHexagon/Main.cs new file mode 100644 index 0000000..97248fc --- /dev/null +++ b/PropLoadingHexagon/Main.cs @@ -0,0 +1,313 @@ +using System.Reflection; +using ABI_RC.Core.InteractionSystem; +using ABI_RC.Core.IO; +using ABI_RC.Core.Player; +using ABI_RC.Core.Util; +using DarkRift; +using HarmonyLib; +using MelonLoader; +using NAK.PropLoadingHexagon.Components; +using UnityEngine; +using Object = UnityEngine.Object; + +namespace NAK.PropLoadingHexagon; + +public class PropLoadingHexagonMod : MelonMod +{ + internal static Action OnPropPlaceholderCreated; + + private static readonly List Loading_Hex_List = new(); + private static GameObject loadingHexContainer; + private static GameObject loadingHexPrefab; + + #region Melon Preferences + + private static readonly MelonPreferences_Category Category = + MelonPreferences.CreateCategory(nameof(PropLoadingHexagon)); + + private static readonly MelonPreferences_Entry EntryKeepIndicatorWhenFiltered = + Category.CreateEntry("keep_indicator_when_filtered", false, + "Keep Loading Hex When Filtered", description: "Keeps the loading hexagon when the prop is filtered."); + + #endregion Melon Preferences + + #region Melon Events + + public override void OnInitializeMelon() + { + HarmonyInstance.Patch( // create prop placeholder container + typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Start), + BindingFlags.NonPublic | BindingFlags.Instance), + postfix: new HarmonyMethod(typeof(PropLoadingHexagonMod).GetMethod(nameof(OnPlayerSetupStart), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( // spawn prop placeholder + typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnPropFromNetwork), + BindingFlags.Public | BindingFlags.Static), + postfix: new HarmonyMethod(typeof(PropLoadingHexagonMod).GetMethod(nameof(OnPropSpawnedFromNetwork), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + HarmonyInstance.Patch( // delete mode on prop placeholder + typeof(ControllerRay).GetMethod(nameof(ControllerRay.DeleteSpawnable), + BindingFlags.NonPublic | BindingFlags.Instance), + prefix: new HarmonyMethod(typeof(PropLoadingHexagonMod).GetMethod(nameof(OnDeleteSpawnableCheck), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + // this luckily is called for all props, not just our own, otherwise we'd be fucked + HarmonyInstance.Patch( // check for if prop failed to load + typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.DeleteMyPropByInstanceIdOverNetwork), + BindingFlags.Public | BindingFlags.Static), + postfix: new HarmonyMethod(typeof(PropLoadingHexagonMod).GetMethod(nameof(OnDeleteMyPropByInstanceIdOverNetwork), + BindingFlags.NonPublic | BindingFlags.Static)) + ); + + LoadAssetBundle(); + + InitializeIntegration("TheClapper", Integrations.TheClapperIntegration.Init); + } + + public override void OnUpdate() + { + if (Loading_Hex_List.Count <= 0) + return; + + for (int i = Loading_Hex_List.Count - 1; i >= 0; i--) + { + LoadingPropMarker marker = Loading_Hex_List[i]; + if (marker.propData == null // i dont think can happen + || string.IsNullOrEmpty(marker.propData.InstanceId) // prop data likely recycled + || (marker.propData.Wrapper != null && marker.propData.Spawnable != null)) // prop has spawned + { + marker.Reset(); + Loading_Hex_List.RemoveAt(i); + return; + } + + if (marker.IsLoadingCanceled) + { + marker.Cancel(); + marker.Reset(); + Loading_Hex_List.RemoveAt(i); + return; + } + + marker.Update(); + } + } + + #endregion Melon Events + + #region Integrations + + private void InitializeIntegration(string modName, Action integrationAction) + { + if (RegisteredMelons.All(it => it.Info.Name != modName)) + return; + + LoggerInstance.Msg($"Initializing {modName} integration."); + integrationAction.Invoke(); + } + + #endregion Integrations + + #region Asset Bundle Loading + + private const string LoadingHexagonAssets = "loading_hexagon.assets"; + private const string LoadingHexagonPrefab = "Assets/Mods/PropLoadingHexagon/Loading_Hexagon_Root.prefab"; + + private void LoadAssetBundle() + { + LoggerInstance.Msg($"Loading required asset bundle..."); + using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(LoadingHexagonAssets); + using MemoryStream memoryStream = new(); + if (resourceStream == null) { + LoggerInstance.Error($"Failed to load {LoadingHexagonAssets}!"); + return; + } + + resourceStream.CopyTo(memoryStream); + AssetBundle assetBundle = AssetBundle.LoadFromMemory(memoryStream.ToArray()); + if (assetBundle == null) { + LoggerInstance.Error($"Failed to load {LoadingHexagonAssets}! Asset bundle is null!"); + return; + } + + loadingHexPrefab = assetBundle.LoadAsset(LoadingHexagonPrefab); + if (loadingHexPrefab == null) { + LoggerInstance.Error($"Failed to load {LoadingHexagonPrefab}! Prefab is null!"); + return; + } + + // modify prefab so nameplate billboard tmp shader is used + MeshRenderer tmp = loadingHexPrefab.GetComponentInChildren(); + tmp.sharedMaterial.shader = Shader.Find("Alpha Blend Interactive/TextMeshPro/Mobile/Distance Field-BillboardFacing"); + tmp.sharedMaterial.SetFloat(Shader.PropertyToID("_FadeStartDistance"), 0f); + tmp.sharedMaterial.SetFloat(Shader.PropertyToID("_FadeEndDistance"), 0f); + + LoggerInstance.Msg("Asset bundle successfully loaded!"); + } + + #endregion Asset Bundle Loading + + #region Harmony Patches + + private static void OnPlayerSetupStart() + { + if (loadingHexContainer != null) return; + loadingHexContainer = new GameObject("NAK.LoadingHexContainer"); + Object.DontDestroyOnLoad(loadingHexContainer); + } + + private static void OnPropSpawnedFromNetwork(Message message) + { + // thank + // https://feedback.abinteractive.net/p/gameeventsystem-spawnable-onload-is-kinda-useless + + using DarkRiftReader reader = message.GetReader(); + var assetId = reader.ReadString(); + var instanceId = reader.ReadString(); + CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == instanceId); + if (propData == null) + return; // props blocked by filter or player blocks, or just broken + + if (!CVRDownloadManager.Instance._downloadTasks.TryGetValue(assetId, out DownloadTask downloadTask)) + return; // no download task, no prop placeholder + + // create loadingPropLoadingHexagonPropHex + LoadingPropMarker loadingHex = new() { downloadTask = downloadTask }; + loadingHex.Initialize(propData); + OnPropPlaceholderCreated?.Invoke(loadingHex.loadingObject); + + // add to list + Loading_Hex_List.Add(loadingHex); + } + + private static void OnDeleteSpawnableCheck(ref ControllerRay __instance) + { + if (!__instance._interactDown) + return; // not interacted, no need to check + + if (PlayerSetup.Instance.GetCurrentPropSelectionMode() + != PlayerSetup.PropSelectionMode.Delete) + return; // not in delete mode, no need to check + + LoadingHexagonController propLoadingHex = __instance.hitTransform.GetComponentInParent(); + if (propLoadingHex != null) propLoadingHex.IsLoadingCanceled = true; // cancel loading + } + + private static void OnDeleteMyPropByInstanceIdOverNetwork(string instanceId) + { + // find prop data + CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == instanceId); + if (propData == null) return; // shouldn't happen + + // check if shits null to nuke loading hex + if (propData.Wrapper != null && propData.Spawnable != null) + return; + + LoadingPropMarker marker = Loading_Hex_List.Find(match => match.propData == propData); + if (marker == null) return; + + if (EntryKeepIndicatorWhenFiltered.Value) + { + marker.IsLikelyBlocked = true; + return; + } + + marker.Cancel(); + marker.Reset(); + Loading_Hex_List.Remove(marker); + } + + #endregion Harmony Patches + + #region LoadingPropMarker Class + + private class LoadingPropMarker + { + public bool IsLikelyBlocked { get; set; } + + internal DownloadTask downloadTask; + internal CVRSyncHelper.PropData propData; + internal GameObject loadingObject; + private LoadingHexagonController loadingHexComponent; + + // ReSharper disable once ParameterHidesMember + public void Initialize(CVRSyncHelper.PropData propData) + { + this.propData = propData; + if (loadingObject == null) + { + loadingObject = Object.Instantiate(loadingHexPrefab, Vector3.zero, Quaternion.identity, loadingHexContainer.transform); + loadingHexComponent = loadingObject.GetComponent(); + + float avatarHeight = PlayerSetup.Instance._avatarHeight; + Transform hexTransform = loadingObject.transform; + hexTransform.localScale = Vector3.one * avatarHeight / 4f; // scale modifier + hexTransform.GetChild(0).position = Vector3.up * avatarHeight / 2f; // position modifier + + Update(); // set initial position and rotation + } + } + + public bool IsLoadingCanceled + => loadingHexComponent.IsLoadingCanceled; + + public void Update() + { + string text; + if (!IsLikelyBlocked) + { + float progress = downloadTask.Progress; + if (downloadTask == null + || downloadTask.Status == DownloadTask.ExecutionStatus.Complete + || downloadTask.Progress >= 100f) + text = "Loading"; + else if (downloadTask.Status == DownloadTask.ExecutionStatus.Failed) + text = "Error"; + else + text = $"{progress} %"; + + loadingHexComponent.SetLoadingShape(progress); + } + else + { + text = "Filtered"; + } + + loadingHexComponent.SetLoadingText(text); + loadingObject.transform.SetPositionAndRotation( + new Vector3(propData.PositionX, propData.PositionY, propData.PositionZ), + Quaternion.Euler(propData.RotationX, propData.RotationY, propData.RotationZ)); + } + + public void Cancel() + { + CVRDownloadManager.Instance.CancelAttachment(propData.InstanceId); + if (propData.Spawnable == null) + propData.Recycle(); + else + propData.Spawnable.Delete(); + } + + public void Reset() + { + if (loadingObject != null) + { + Object.Destroy(loadingObject); + loadingObject = null; + } + + propData = null; + downloadTask = null; + loadingObject = null; + loadingHexComponent = null; + IsLikelyBlocked = false; + } + } + + #endregion LoadingPropMarker Class +} \ No newline at end of file diff --git a/PropSpawnTweaks/PropSpawnTweaks.csproj b/PropLoadingHexagon/PropLoadingHexagon.csproj similarity index 63% rename from PropSpawnTweaks/PropSpawnTweaks.csproj rename to PropLoadingHexagon/PropLoadingHexagon.csproj index 8c97d06..804b0ae 100644 --- a/PropSpawnTweaks/PropSpawnTweaks.csproj +++ b/PropLoadingHexagon/PropLoadingHexagon.csproj @@ -2,7 +2,13 @@ net48 + PropSpawnTweaks + + + ..\.ManagedLibs\TheClapper.dll + + loading_hexagon.assets diff --git a/PropSpawnTweaks/Properties/AssemblyInfo.cs b/PropLoadingHexagon/Properties/AssemblyInfo.cs similarity index 63% rename from PropSpawnTweaks/Properties/AssemblyInfo.cs rename to PropLoadingHexagon/Properties/AssemblyInfo.cs index 2b7045f..069c0f3 100644 --- a/PropSpawnTweaks/Properties/AssemblyInfo.cs +++ b/PropLoadingHexagon/Properties/AssemblyInfo.cs @@ -1,30 +1,31 @@ -using NAK.PropSpawnTweaks.Properties; +using NAK.PropLoadingHexagon.Properties; using MelonLoader; using System.Reflection; [assembly: AssemblyVersion(AssemblyInfoParams.Version)] [assembly: AssemblyFileVersion(AssemblyInfoParams.Version)] [assembly: AssemblyInformationalVersion(AssemblyInfoParams.Version)] -[assembly: AssemblyTitle(nameof(NAK.PropSpawnTweaks))] +[assembly: AssemblyTitle(nameof(NAK.PropLoadingHexagon))] [assembly: AssemblyCompany(AssemblyInfoParams.Author)] -[assembly: AssemblyProduct(nameof(NAK.PropSpawnTweaks))] +[assembly: AssemblyProduct(nameof(NAK.PropLoadingHexagon))] [assembly: MelonInfo( - typeof(NAK.PropSpawnTweaks.PropSpawnTweaksMod), - nameof(NAK.PropSpawnTweaks), + typeof(NAK.PropLoadingHexagon.PropLoadingHexagonMod), + nameof(NAK.PropLoadingHexagon), AssemblyInfoParams.Version, AssemblyInfoParams.Author, - downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropSpawnTweaks" + downloadLink: "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropLoadingHexagon" )] [assembly: MelonGame("Alpha Blend Interactive", "ChilloutVR")] [assembly: MelonPlatform(MelonPlatformAttribute.CompatiblePlatforms.WINDOWS_X64)] [assembly: MelonPlatformDomain(MelonPlatformDomainAttribute.CompatibleDomains.MONO)] -[assembly: MelonColor(255, 181, 137, 236)] -[assembly: MelonAuthorColor(255, 158, 21, 32)] +[assembly: MelonColor(255, 246, 25, 99)] // red-pink +[assembly: MelonAuthorColor(255, 158, 21, 32)] // red +[assembly: MelonOptionalDependencies("TheClapper")] [assembly: HarmonyDontPatchAll] -namespace NAK.PropSpawnTweaks.Properties; +namespace NAK.PropLoadingHexagon.Properties; internal static class AssemblyInfoParams { public const string Version = "1.0.0"; diff --git a/PropSpawnTweaks/README.md b/PropLoadingHexagon/README.md similarity index 67% rename from PropSpawnTweaks/README.md rename to PropLoadingHexagon/README.md index bf0dac2..9f336c0 100644 --- a/PropSpawnTweaks/README.md +++ b/PropLoadingHexagon/README.md @@ -1,6 +1,8 @@ -# PropSpawnTweaks +# PropLoadingHexagon -Gives the Drop Prop button more utility by allowing you to drop props in the air, as well as providing a prop loading indicator. +Adds a hexagon indicator to downloading props. Indicator is styled to look similar to Portals. + +Can use Delete Mode & The Clapper on loading hexagons to cancel stuck downloads. Setting is provided to display the hexagon for Blocked/Filtered props. --- diff --git a/PropLoadingHexagon/Resources/loading_hexagon.assets b/PropLoadingHexagon/Resources/loading_hexagon.assets new file mode 100644 index 0000000..87f787b Binary files /dev/null and b/PropLoadingHexagon/Resources/loading_hexagon.assets differ diff --git a/PropLoadingHexagon/format.json b/PropLoadingHexagon/format.json new file mode 100644 index 0000000..5615430 --- /dev/null +++ b/PropLoadingHexagon/format.json @@ -0,0 +1,23 @@ +{ + "_id": -1, + "name": "PropLoadingHexagon", + "modversion": "1.0.0", + "gameversion": "2024r175", + "loaderversion": "0.6.1", + "modtype": "Mod", + "author": "Exterrata & NotAKidoS", + "description": "Adds a hexagon indicator to downloading props. Indicator is styled to look similar to Portals.\n\nCan use Delete Mode & The Clapper on loading hexagons to cancel stuck downloads. Setting is provided to display the hexagon for Blocked/Filtered props.", + "searchtags": [ + "prop", + "spawn", + "indicator", + "loading" + ], + "requirements": [ + "None" + ], + "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r34/PropLoadingHexagon.dll", + "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropLoadingHexagon/", + "changelog": "- Initial release", + "embedcolor": "#f61963" +} \ No newline at end of file diff --git a/PropSpawnTweaks/Main.cs b/PropSpawnTweaks/Main.cs deleted file mode 100644 index 8356f56..0000000 --- a/PropSpawnTweaks/Main.cs +++ /dev/null @@ -1,235 +0,0 @@ -using System.Reflection; -using ABI_RC.Core.IO; -using ABI_RC.Core.Player; -using ABI_RC.Core.Util; -using DarkRift; -using HarmonyLib; -using MelonLoader; -using NAK.PropSpawnTweaks.Components; -using UnityEngine; -using Object = UnityEngine.Object; - -namespace NAK.PropSpawnTweaks; - -public class PropSpawnTweaksMod : MelonMod -{ - private static readonly ObjectPool Loading_Hex_Pool = new(0, () => new LoadingPropHex()); - private static readonly List Loading_Hex_List = new(); - private static GameObject loadingHexContainer; - private static GameObject loadingHexPrefab; - - #region Melon Events - - public override void OnInitializeMelon() - { - HarmonyInstance.Patch( // create prop placeholder container - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.Start), - BindingFlags.NonPublic | BindingFlags.Instance), - postfix: new HarmonyMethod(typeof(PropSpawnTweaksMod).GetMethod(nameof(OnPlayerSetupStart), - BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( // make drop prop actually usable - typeof(PlayerSetup).GetMethod(nameof(PlayerSetup.DropProp), - BindingFlags.Public | BindingFlags.Instance), - new HarmonyMethod(typeof(PropSpawnTweaksMod).GetMethod(nameof(OnDropProp), - BindingFlags.NonPublic | BindingFlags.Static)) - ); - - HarmonyInstance.Patch( // spawn prop placeholder - typeof(CVRSyncHelper).GetMethod(nameof(CVRSyncHelper.SpawnPropFromNetwork), - BindingFlags.Public | BindingFlags.Static), - postfix: new HarmonyMethod(typeof(PropSpawnTweaksMod).GetMethod(nameof(OnPropSpawnedFromNetwork), - BindingFlags.NonPublic | BindingFlags.Static)) - ); - - LoadAssetBundle(); - } - - public override void OnUpdate() - { - if (Loading_Hex_List.Count <= 0) - return; - - for (int i = Loading_Hex_List.Count - 1; i >= 0; i--) - { - LoadingPropHex loadingHex = Loading_Hex_List[i]; - if (loadingHex.propData == null // i dont think can happen - || string.IsNullOrEmpty(loadingHex.propData.InstanceId) // prop data likely recycled - || (loadingHex.propData.Wrapper != null && loadingHex.propData.Spawnable != null)) // prop has spawned - { - loadingHex.Reset(); - Loading_Hex_Pool.Give(loadingHex); - Loading_Hex_List.RemoveAt(i); - return; - } - - loadingHex.Update(); - } - } - - #endregion Melon Events - - #region Asset Bundle Loading - - private const string LoadingHexagonAssets = "loading_hexagon.assets"; - private const string LoadingHexagonPrefab = "Assets/Mods/PropSpawnTweaks/Loading_Hexagon_Root.prefab"; - - private void LoadAssetBundle() - { - LoggerInstance.Msg($"Loading required asset bundle..."); - using Stream resourceStream = MelonAssembly.Assembly.GetManifestResourceStream(LoadingHexagonAssets); - using MemoryStream memoryStream = new(); - if (resourceStream == null) { - LoggerInstance.Error($"Failed to load {LoadingHexagonAssets}!"); - return; - } - - resourceStream.CopyTo(memoryStream); - AssetBundle assetBundle = AssetBundle.LoadFromMemory(memoryStream.ToArray()); - if (assetBundle == null) { - LoggerInstance.Error($"Failed to load {LoadingHexagonAssets}! Asset bundle is null!"); - return; - } - - loadingHexPrefab = assetBundle.LoadAsset(LoadingHexagonPrefab); - if (loadingHexPrefab == null) { - LoggerInstance.Error($"Failed to load {LoadingHexagonPrefab}! Prefab is null!"); - return; - } - - // modify prefab so nameplate billboard tmp shader is used - MeshRenderer tmp = loadingHexPrefab.GetComponentInChildren(); - tmp.sharedMaterial.shader = Shader.Find("Alpha Blend Interactive/TextMeshPro/Mobile/Distance Field-BillboardFacing"); - - LoggerInstance.Msg("Asset bundle successfully loaded!"); - } - - #endregion Asset Bundle Loading - - #region Harmony Patches - - private static void OnPlayerSetupStart() - { - if (loadingHexContainer != null) return; - loadingHexContainer = new GameObject("NAK.LoadingHexContainer"); - Object.DontDestroyOnLoad(loadingHexContainer); - } - - private static bool OnDropProp(string propGuid, ref PlayerSetup __instance) - { - Vector3 position = __instance.activeCam.transform.position + __instance.GetPlayerForward() * 1.5f; // 1f -> 1.5f - - if (Physics.Raycast(position, - __instance.CharacterController.GetGravityDirection(), // align with gravity, not player up - out RaycastHit raycastHit, 4f, __instance.dropPlacementMask)) - { - // native method passes false, so DropProp doesn't align with gravity :) - CVRSyncHelper.SpawnProp(propGuid, raycastHit.point.x, raycastHit.point.y, raycastHit.point.z, true); - return false; - } - - // unlike original, we will still spawn prop even if raycast fails, giving the method actual utility :3 - - // hack- we want to align with *our* rotation, not affecting gravity - Vector3 ogGravity = __instance.CharacterController.GetGravityDirection(); - __instance.CharacterController.gravity = -__instance.transform.up; // align with our rotation - - // spawn prop with useTargetLocationGravity false, so it pulls our gravity dir we've modified - CVRSyncHelper.SpawnProp(propGuid, position.x, position.y, position.z, false); - - __instance.CharacterController.gravity = ogGravity; // restore gravity - return false; - } - - private static void OnPropSpawnedFromNetwork(Message message) - { - // thank - // https://feedback.abinteractive.net/p/gameeventsystem-spawnable-onload-is-kinda-useless - - using DarkRiftReader reader = message.GetReader(); - var assetId = reader.ReadString(); - var instanceId = reader.ReadString(); - CVRSyncHelper.PropData propData = CVRSyncHelper.Props.Find(match => match.InstanceId == instanceId); - if (propData == null) - return; // props blocked by filter or player blocks, or just broken - - if (!CVRDownloadManager.Instance._downloadTasks.TryGetValue(assetId, out DownloadTask downloadTask)) - return; // no download task, no prop placeholder - - // create loading hex - LoadingPropHex loadingHex = Loading_Hex_Pool.Take(); - loadingHex.downloadTask = downloadTask; - loadingHex.Initialize(propData); - - // add to list - Loading_Hex_List.Add(loadingHex); - } - - #endregion Harmony Patches - - #region LoadingPropHex Class - - private class LoadingPropHex - { - public DownloadTask downloadTask; - public CVRSyncHelper.PropData propData; - private GameObject loadingObject; - private PropLoadingHexagon loadingHexComponent; - - // ReSharper disable once ParameterHidesMember - public void Initialize(CVRSyncHelper.PropData propData) - { - this.propData = propData; - if (loadingObject == null) - { - loadingObject = Object.Instantiate(loadingHexPrefab, Vector3.zero, Quaternion.identity, loadingHexContainer.transform); - loadingHexComponent = loadingObject.GetComponent(); - - Update(); // set initial position - - float avatarHeight = PlayerSetup.Instance._avatarHeight; - Transform hexTransform = loadingObject.transform; - hexTransform.localScale = Vector3.one * avatarHeight / 4f; // scale modifier - hexTransform.GetChild(0).localPosition = Vector3.up * avatarHeight * 2f; // position modifier - } - } - - public void Update() - { - string text; - float progress = downloadTask.Progress; - - if (downloadTask == null - || downloadTask.Status == DownloadTask.ExecutionStatus.Complete - || downloadTask.Progress >= 100f) - text = "LOADING"; - else if (downloadTask.Status == DownloadTask.ExecutionStatus.Failed) - text = "ERROR"; - else - text = $"{progress} %"; - - loadingHexComponent.SetLoadingText(text); - loadingHexComponent.SetLoadingShape(progress); - loadingObject.transform.SetPositionAndRotation( - new Vector3(propData.PositionX, propData.PositionY, propData.PositionZ), - Quaternion.Euler(propData.RotationX, propData.RotationY, propData.RotationZ)); - } - - public void Reset() - { - if (loadingObject != null) - { - Object.Destroy(loadingObject); - loadingObject = null; - } - - propData = null; - downloadTask = null; - loadingObject = null; - loadingHexComponent = null; - } - } - - #endregion LoadingPropHex Class -} \ No newline at end of file diff --git a/PropSpawnTweaks/ObjectPool.cs b/PropSpawnTweaks/ObjectPool.cs deleted file mode 100644 index d8631eb..0000000 --- a/PropSpawnTweaks/ObjectPool.cs +++ /dev/null @@ -1,45 +0,0 @@ -namespace NAK.PropSpawnTweaks; - -public class ObjectPool -{ - public int Count { get; private set; } - - private readonly Queue _available; - private readonly Func _createObjectFunc; - - public ObjectPool(int numPreallocated = 0, Func createObjectFunc = null) - { - _available = new Queue(); - _createObjectFunc = createObjectFunc; - - if (numPreallocated > 0) Give(MakeObject()); - } - - public T Take() - { - T t; - if (_available.Count > 0) - { - t = _available.Dequeue(); - Count--; - } - else - { - t = MakeObject(); - } - - return t; - } - - public void Give(T t) - { - _available.Enqueue(t); - Count++; - } - - private T MakeObject() - { - if (_createObjectFunc != null) return _createObjectFunc(); - throw new InvalidOperationException("Cannot automatically create new objects without a factory method"); - } -} \ No newline at end of file diff --git a/PropSpawnTweaks/format.json b/PropSpawnTweaks/format.json deleted file mode 100644 index e731325..0000000 --- a/PropSpawnTweaks/format.json +++ /dev/null @@ -1,23 +0,0 @@ -{ - "_id": -1, - "name": "PropSpawnTweaks", - "modversion": "1.0.0", - "gameversion": "2024r175", - "loaderversion": "0.6.1", - "modtype": "Mod", - "author": "Exterrata & NotAKidoS", - "description": "Gives the Drop Prop button more utility by allowing you to drop props in the air, as well as providing a prop loading indicator.", - "searchtags": [ - "prop", - "spawn", - "indicator", - "loading" - ], - "requirements": [ - "None" - ], - "downloadlink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/releases/download/r26/PropSpawnTweaks.dll", - "sourcelink": "https://github.com/NotAKidOnSteam/NAK_CVR_Mods/tree/main/PropSpawnTweaks/", - "changelog": "- Initial Release", - "embedcolor": "#b589ec" -} \ No newline at end of file