diff --git a/Stickers/Stickers/StickerSystem.Main.cs b/Stickers/Stickers/StickerSystem.Main.cs index ab95877..0a5a4ef 100644 --- a/Stickers/Stickers/StickerSystem.Main.cs +++ b/Stickers/Stickers/StickerSystem.Main.cs @@ -1,124 +1,151 @@ -using ABI_RC.Core.IO; -using ABI_RC.Core.Networking.IO.Instancing; -using ABI_RC.Core.UI; -using ABI_RC.Systems.GameEventSystem; -using NAK.Stickers.Networking; -using NAK.Stickers.Utilities; - -namespace NAK.Stickers; - -public partial class StickerSystem -{ - #region Singleton - - public static StickerSystem Instance { get; private set; } - - public static void Initialize() - { - if (Instance != null) - return; - - Instance = new StickerSystem(); - - // configure decalery - DecalManager.SetPreferredMode(DecalUtils.Mode.GPU, false, 0); - - // ensure cache folder exists - EnsureStickersFolderExists(); - - // listen for game events - CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(Instance.OnPlayerSetupStart); - } - - #endregion Singleton - - #region Callback Registration - - private void OnPlayerSetupStart() - { - CVRGameEventSystem.World.OnUnload.AddListener(_ => OnWorldUnload()); - CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) OnInitialConnection(); }); - - CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); - CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); - SchedulerSystem.AddJob(Instance.OnUpdate, 10f, -1); - LoadAllImagesAtStartup(); - } - - #endregion Callback Registration - - #region Game Events - - private void OnInitialConnection() - { - ClearStickersSelf(); // clear stickers on remotes just in case we rejoined - ModNetwork.Reset(); // reset network buffers and metadata - } - - private void OnWorldUnload() - { - CleanupAllButSelf(); // release all stickers except for self - } - - #endregion Game Events - - #region Data - - // private bool _isEnabled = true; - // - // public bool IsEnabled - // { - // get => _isEnabled; - // set - // { - // if (_isEnabled == value) - // return; - // - // _isEnabled = value; - // if (!_isEnabled) ClearAllStickers(); - // ModNetwork.IsEnabled = _isEnabled; - // } - // } - - private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; - - private const float StickerKillTime = 30f; - private const float StickerCooldown = 0.2f; - private readonly Dictionary _playerStickers = new(); - internal const string PlayerLocalId = "_PLAYERLOCAL"; - - private int _selectedStickerSlot; - public int SelectedStickerSlot - { - get => _selectedStickerSlot; - set - { - _selectedStickerSlot = value < 0 ? ModSettings.MaxStickerSlots - 1 : value % ModSettings.MaxStickerSlots; - IsInStickerMode = IsInStickerMode; // refresh sticker mode - } - } - - private bool _isInStickerMode; - public bool IsInStickerMode - { - get => _isInStickerMode; - set - { - _isInStickerMode = value; - if (_isInStickerMode) - { - CohtmlHud.Instance.SelectPropToSpawn( - StickerCache.GetCohtmlResourcesPath(SelectedStickerName), - Path.GetFileNameWithoutExtension(SelectedStickerName), - "Sticker selected for stickering:"); - } - else - { - CohtmlHud.Instance.ClearPropToSpawn(); - ClearStickerPreview(); - } - } - } - - #endregion Data +using ABI_RC.Core.IO; +using ABI_RC.Core.Networking.IO.Instancing; +using ABI_RC.Core.UI; +using ABI_RC.Systems.GameEventSystem; +using JetBrains.Annotations; +using NAK.Stickers.Networking; +using NAK.Stickers.Utilities; +using System.EnterpriseServices; +using UnityEngine; +using MelonLoader; +using UnityEngine.ProBuilder.MeshOperations; +using NAK.Stickers.Integrations; + +namespace NAK.Stickers; + +public partial class StickerSystem +{ + #region Singleton + + public static bool RestrictedInstance = false; + + public static StickerSystem Instance { get; private set; } + + public static void Initialize() + { + if (Instance != null) + return; + + Instance = new StickerSystem(); + + // configure decalery + DecalManager.SetPreferredMode(DecalUtils.Mode.GPU, false, 0); + + // ensure cache folder exists + EnsureStickersFolderExists(); + + // listen for game events + CVRGameEventSystem.Initialization.OnPlayerSetupStart.AddListener(Instance.OnPlayerSetupStart); + +} + + #endregion Singleton + + #region Callback Registration + + private void OnPlayerSetupStart() + { + CVRGameEventSystem.World.OnUnload.AddListener(_ => OnWorldUnload()); + CVRGameEventSystem.World.OnLoad.AddListener(_ => OnWorldLoad()); + CVRGameEventSystem.Instance.OnConnected.AddListener((_) => { if (!Instances.IsReconnecting) OnInitialConnection(); }); + + CVRGameEventSystem.Player.OnJoinEntity.AddListener(Instance.OnPlayerJoined); + CVRGameEventSystem.Player.OnLeaveEntity.AddListener(Instance.OnPlayerLeft); + SchedulerSystem.AddJob(Instance.OnUpdate, 10f, -1); + LoadAllImagesAtStartup(); + } + + #endregion Callback Registration + + #region Game Events + + private void OnInitialConnection() + { + OnWorldLoad(); //Checks the world again in case the bundle updated. + ClearStickersSelf(); // clear stickers on remotes just in case we rejoined + ModNetwork.Reset(); // reset network buffers and metadata + } + + private void OnWorldLoad() + { + GameObject StickerWorldRestriction = GameObject.Find("[DisableStickers]"); + if (StickerWorldRestriction != null) + { + RestrictedInstance = true; + MelonLogger.Msg("This is a Restricted Instance"); + } + else + { + MelonLogger.Msg("This is NOT a Restricted Instance"); + } + BTKUIAddon.UpdateStickerMenu(); + } + + private void OnWorldUnload() + { + RestrictedInstance = false; + CleanupAllButSelf(); // release all stickers except for self + } + + #endregion Game Events + + #region Data + + // private bool _isEnabled = true; + // + // public bool IsEnabled + // { + // get => _isEnabled; + // set + // { + // if (_isEnabled == value) + // return; + // + // _isEnabled = value; + // if (!_isEnabled) ClearAllStickers(); + // ModNetwork.IsEnabled = _isEnabled; + // } + // } + + private string SelectedStickerName => ModSettings.Hidden_SelectedStickerNames.Value[_selectedStickerSlot]; + + private const float StickerKillTime = 30f; + private const float StickerCooldown = 0.2f; + private readonly Dictionary _playerStickers = new(); + internal const string PlayerLocalId = "_PLAYERLOCAL"; + + private int _selectedStickerSlot; + public int SelectedStickerSlot + { + get => _selectedStickerSlot; + set + { + _selectedStickerSlot = value < 0 ? ModSettings.MaxStickerSlots - 1 : value % ModSettings.MaxStickerSlots; + IsInStickerMode = IsInStickerMode; // refresh sticker mode + } + } + + private bool _isInStickerMode; + public bool IsInStickerMode + { + get => _isInStickerMode; + set + { + _isInStickerMode = value; + if (_isInStickerMode) + { + CohtmlHud.Instance.SelectPropToSpawn( + StickerCache.GetCohtmlResourcesPath(SelectedStickerName), + Path.GetFileNameWithoutExtension(SelectedStickerName), + "Sticker selected for stickering:"); + } + else + { + CohtmlHud.Instance.ClearPropToSpawn(); + ClearStickerPreview(); + } + } + } + + #endregion Data } \ No newline at end of file diff --git a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs index 2977dff..821aedc 100644 --- a/Stickers/Stickers/StickerSystem.StickerLifecycle.cs +++ b/Stickers/Stickers/StickerSystem.StickerLifecycle.cs @@ -1,190 +1,194 @@ -using ABI_RC.Core; -using ABI_RC.Core.Player; -using ABI_RC.Systems.InputManagement; -using NAK.Stickers.Networking; -using UnityEngine; - -namespace NAK.Stickers; - -public partial class StickerSystem -{ - #region Sticker Lifecycle - - private StickerData GetOrCreateStickerData(string playerId) - { - if (_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - return stickerData; - - stickerData = new StickerData(playerId, ModSettings.MaxStickerSlots); - _playerStickers[playerId] = stickerData; - return stickerData; - } - - public void PlaceStickerFromControllerRay(Transform transform, CVRHand hand = CVRHand.Left, bool isPreview = false) - { - Vector3 controllerForward = transform.forward; - Vector3 controllerUp = transform.up; - Vector3 playerUp = PlayerSetup.Instance.transform.up; - - // extracting angle of controller ray on forward axis - Vector3 projectedControllerUp = Vector3.ProjectOnPlane(controllerUp, controllerForward).normalized; - Vector3 projectedPlayerUp = Vector3.ProjectOnPlane(playerUp, controllerForward).normalized; - float angle = Vector3.Angle(projectedControllerUp, projectedPlayerUp); - - float angleThreshold = ModSettings.Entry_PlayerUpAlignmentThreshold.Value; - Vector3 targetUp = (angleThreshold != 0f && angle <= angleThreshold) - // leave 0.01% of the controller up vector to prevent issues with alignment on floor & ceiling in Desktop - ? Vector3.Slerp(controllerUp, playerUp, 0.99f) - : controllerUp; - - if (isPreview) - { - PlaceStickerPreview(transform.position, controllerForward, targetUp); - return; - } - - if (!PlaceStickerSelf(transform.position, transform.forward, targetUp)) - return; - - CVRInputManager.Instance.Vibrate(0f, 0.1f, 10f, 0.1f, hand); - } - - private bool PlaceStickerSelf(Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true) - { - if (!AttemptPlaceSticker(PlayerLocalId, position, forward, up, alignWithNormal, SelectedStickerSlot)) - return false; // failed - - // placed, now network - ModNetwork.SendPlaceSticker(SelectedStickerSlot, position, forward, up); - return true; - } - - private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool isPreview = false) - { - StickerData stickerData = GetOrCreateStickerData(playerId); - if (Time.time - stickerData.LastPlacedTime < StickerCooldown) - return false; - - // Every layer other than IgnoreRaycast, PlayerLocal, PlayerClone, PlayerNetwork, and UI Internal - const int LayerMask = ~((1 << 2) | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 15)); - if (!Physics.Raycast(position, forward, out RaycastHit hit, - 10f, LayerMask, QueryTriggerInteraction.Ignore)) - return false; - - // if gameobject name starts with [NoSticker] then don't place sticker - if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) - return false; - - if (isPreview) - { - stickerData.PlacePreview(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); - return true; - } - - stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); - stickerData.PlayAudio(); - return true; - } - - public void ClearStickersSelf() - { - ClearStickersForPlayer(PlayerLocalId); - ModNetwork.SendClearAllStickers(); - } - - public void ClearStickerSelf(int stickerSlot) - { - ClearStickersForPlayer(PlayerLocalId, stickerSlot); - ModNetwork.SendClearSticker(stickerSlot); - } - - private void ClearStickersForPlayer(string playerId) - { - if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - return; - - stickerData.Clear(); - } - - private void ClearStickersForPlayer(string playerId, int stickerSlot) - { - if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) - return; - - stickerData.Clear(stickerSlot); - } - - private void SetTextureSelf(byte[] imageBytes, int stickerSlot = 0) - { - Texture2D texture = new(1, 1); // placeholder - texture.LoadImage(imageBytes); - texture.Compress(true); // noachi said to do - - ClearStickerSelf(stickerSlot); // clear placed stickers in-scene so we can't replace an entire wall at once - OnPlayerStickerTextureReceived(PlayerLocalId, Guid.Empty, texture, stickerSlot); - ModNetwork.SetTexture(stickerSlot, imageBytes); - } - - public void ClearAllStickers() - { - foreach (StickerData stickerData in _playerStickers.Values) - stickerData.Clear(); - - ModNetwork.SendClearAllStickers(); - } - - public void OnPlayerStickerTextureReceived(string playerId, Guid textureHash, Texture2D texture, int stickerSlot = 0) - { - StickerData stickerData = GetOrCreateStickerData(playerId); - stickerData.SetTexture(textureHash, texture, stickerSlot); - } - - public bool HasTextureHash(string playerId, Guid textureHash) - { - StickerData stickerData = GetOrCreateStickerData(playerId); - return stickerData.CheckHasTextureHash(textureHash); - } - - public void CleanupAll() - { - foreach ((_, StickerData data) in _playerStickers) - data.Cleanup(); - - _playerStickers.Clear(); - } - - public void CleanupAllButSelf() - { - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - - foreach ((_, StickerData data) in _playerStickers) - { - if (data == localStickerData) data.Clear(); - else data.Cleanup(); - } - - _playerStickers.Clear(); - _playerStickers[PlayerLocalId] = localStickerData; - } - - public void PlaceStickerPreview(Vector3 position, Vector3 forward, Vector3 up) - { - AttemptPlaceSticker(PlayerLocalId, position, forward, up, true, SelectedStickerSlot, true); - } - - public void UpdateStickerPreview() - { - if (!IsInStickerMode) return; - - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - localStickerData.UpdatePreview(SelectedStickerSlot); - } - - public void ClearStickerPreview() - { - StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); - localStickerData.ClearPreview(); - } - - #endregion Sticker Lifecycle -} +using ABI_RC.Core; +using ABI_RC.Core.Player; +using ABI_RC.Systems.InputManagement; +using NAK.Stickers.Networking; +using UnityEngine; + +namespace NAK.Stickers; + +public partial class StickerSystem +{ + #region Sticker Lifecycle + + private StickerData GetOrCreateStickerData(string playerId) + { + if (_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return stickerData; + + stickerData = new StickerData(playerId, ModSettings.MaxStickerSlots); + _playerStickers[playerId] = stickerData; + return stickerData; + } + + public void PlaceStickerFromControllerRay(Transform transform, CVRHand hand = CVRHand.Left, bool isPreview = false) + { + Vector3 controllerForward = transform.forward; + Vector3 controllerUp = transform.up; + Vector3 playerUp = PlayerSetup.Instance.transform.up; + + // extracting angle of controller ray on forward axis + Vector3 projectedControllerUp = Vector3.ProjectOnPlane(controllerUp, controllerForward).normalized; + Vector3 projectedPlayerUp = Vector3.ProjectOnPlane(playerUp, controllerForward).normalized; + float angle = Vector3.Angle(projectedControllerUp, projectedPlayerUp); + + float angleThreshold = ModSettings.Entry_PlayerUpAlignmentThreshold.Value; + Vector3 targetUp = (angleThreshold != 0f && angle <= angleThreshold) + // leave 0.01% of the controller up vector to prevent issues with alignment on floor & ceiling in Desktop + ? Vector3.Slerp(controllerUp, playerUp, 0.99f) + : controllerUp; + + if (isPreview) + { + PlaceStickerPreview(transform.position, controllerForward, targetUp); + return; + } + + if (!PlaceStickerSelf(transform.position, transform.forward, targetUp)) + return; + + CVRInputManager.Instance.Vibrate(0f, 0.1f, 10f, 0.1f, hand); + } + + private bool PlaceStickerSelf(Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true) + { + if (!AttemptPlaceSticker(PlayerLocalId, position, forward, up, alignWithNormal, SelectedStickerSlot, RestrictedInstance)) + return false; // failed + + // placed, now network + ModNetwork.SendPlaceSticker(SelectedStickerSlot, position, forward, up); + return true; + } + + private bool AttemptPlaceSticker(string playerId, Vector3 position, Vector3 forward, Vector3 up, bool alignWithNormal = true, int stickerSlot = 0, bool RestrictedInstance = false, bool isPreview = false) + { + StickerData stickerData = GetOrCreateStickerData(playerId); + if (Time.time - stickerData.LastPlacedTime < StickerCooldown) + return false; + + // Every layer other than IgnoreRaycast, PlayerLocal, PlayerClone, PlayerNetwork, and UI Internal + const int LayerMask = ~((1 << 2) | (1 << 8) | (1 << 9) | (1 << 10) | (1 << 15)); + if (!Physics.Raycast(position, forward, out RaycastHit hit, + 10f, LayerMask, QueryTriggerInteraction.Ignore)) + return false; + + // if gameobject name starts with [NoSticker] then don't place sticker + if (hit.transform.gameObject.name.StartsWith("[NoSticker]")) + return false; + + // if the world contained a gameobject with the [DisableStickers] name and restricted the instance disable stickers! + if (RestrictedInstance == true) + return false; + + if (isPreview) + { + stickerData.PlacePreview(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); + return true; + } + + stickerData.Place(hit, alignWithNormal ? -hit.normal : forward, up, stickerSlot); + stickerData.PlayAudio(); + return true; + } + + public void ClearStickersSelf() + { + ClearStickersForPlayer(PlayerLocalId); + ModNetwork.SendClearAllStickers(); + } + + public void ClearStickerSelf(int stickerSlot) + { + ClearStickersForPlayer(PlayerLocalId, stickerSlot); + ModNetwork.SendClearSticker(stickerSlot); + } + + private void ClearStickersForPlayer(string playerId) + { + if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return; + + stickerData.Clear(); + } + + private void ClearStickersForPlayer(string playerId, int stickerSlot) + { + if (!_playerStickers.TryGetValue(playerId, out StickerData stickerData)) + return; + + stickerData.Clear(stickerSlot); + } + + private void SetTextureSelf(byte[] imageBytes, int stickerSlot = 0) + { + Texture2D texture = new(1, 1); // placeholder + texture.LoadImage(imageBytes); + texture.Compress(true); // noachi said to do + + ClearStickerSelf(stickerSlot); // clear placed stickers in-scene so we can't replace an entire wall at once + OnPlayerStickerTextureReceived(PlayerLocalId, Guid.Empty, texture, stickerSlot); + ModNetwork.SetTexture(stickerSlot, imageBytes); + } + + public void ClearAllStickers() + { + foreach (StickerData stickerData in _playerStickers.Values) + stickerData.Clear(); + + ModNetwork.SendClearAllStickers(); + } + + public void OnPlayerStickerTextureReceived(string playerId, Guid textureHash, Texture2D texture, int stickerSlot = 0) + { + StickerData stickerData = GetOrCreateStickerData(playerId); + stickerData.SetTexture(textureHash, texture, stickerSlot); + } + + public bool HasTextureHash(string playerId, Guid textureHash) + { + StickerData stickerData = GetOrCreateStickerData(playerId); + return stickerData.CheckHasTextureHash(textureHash); + } + + public void CleanupAll() + { + foreach ((_, StickerData data) in _playerStickers) + data.Cleanup(); + + _playerStickers.Clear(); + } + + public void CleanupAllButSelf() + { + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + + foreach ((_, StickerData data) in _playerStickers) + { + if (data == localStickerData) data.Clear(); + else data.Cleanup(); + } + + _playerStickers.Clear(); + _playerStickers[PlayerLocalId] = localStickerData; + } + + public void PlaceStickerPreview(Vector3 position, Vector3 forward, Vector3 up) + { + AttemptPlaceSticker(PlayerLocalId, position, forward, up, true, SelectedStickerSlot, true); + } + + public void UpdateStickerPreview() + { + if (!IsInStickerMode) return; + + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.UpdatePreview(SelectedStickerSlot); + } + + public void ClearStickerPreview() + { + StickerData localStickerData = GetOrCreateStickerData(PlayerLocalId); + localStickerData.ClearPreview(); + } + + #endregion Sticker Lifecycle +}