using MMMaellon.LightSync; using UdonSharp; using UnityEngine; using VRC.SDK3.Components; using VRC.SDK3.Data; using VRC.SDK3.UdonNetworkCalling; using VRC.SDKBase; using VRC.Udon.Common.Interfaces; [UdonBehaviourSyncMode(BehaviourSyncMode.Manual)] public class FloorMapMarker : UdonSharpBehaviour { [SerializeField] private GameManagerRound3 _GameManager; [SerializeField] private Material _UnlitLampMaterial; [SerializeField] private Material _LitLampMaterial; [UdonSynced, SerializeField, FieldChangeCallback(nameof(Active))] private bool _Active = false; [UdonSynced, FieldChangeCallback(nameof(IsGrabbed))] private bool _IsGrabbed = false; [UdonSynced, FieldChangeCallback(nameof(IsLit))] private bool _IsLit = false; [UdonSynced] private bool _LocationFindingEnabled = false; [UdonSynced] private int _FailureCounter = 0; private int _CollisionCheckCounter = 0; private int _NoCollisionCounter = 0; private DataList _CollidingLocations = new DataList(); private Rigidbody _RigidBodyComponent; private LightSync _ObjectSync; private VRCPickup _PickupComponent; private MeshRenderer _MarkerMesh; private const int MAX_FAILURE_COUNT = 2; private const int MAX_REPEAT_COLLISION_CHECKS = 3; private const int MAX_TIME_WITH_NO_COLLISIONS = 8; private const float TIME_BETWEEN_REPEAT_COLLISION_CHECKS = 0.15f; void Start() { _RigidBodyComponent = GetComponent(); _ObjectSync = GetComponent(); _PickupComponent = GetComponent(); _MarkerMesh = GetComponent(); } public void Initialise() { _ObjectSync.TeleportToLocalSpace(Vector3.zero, Quaternion.identity, true); SetPickupable(false); Active = false; IsGrabbed = false; IsLit = false; _LocationFindingEnabled = false; _CollisionCheckCounter = 0; _NoCollisionCounter = 0; _CollidingLocations.Clear(); RequestSerialization(); } public void SetPickupable(bool Pickupable) { _PickupComponent.pickupable = Pickupable; } public void OnTriggerEnter(Collider OtherCollider) { FloorMapLocation Location = OtherCollider.GetComponent(); if (Location != null) { _CollidingLocations.Add(Location); _CollisionCheckCounter = 0; _NoCollisionCounter = 0; if (!IsGrabbed) CheckCollisions(); } } public void OnTriggerExit(Collider OtherCollider) { FloorMapLocation Location = OtherCollider.GetComponent(); if (Location != null) { _CollidingLocations.Remove(Location); _CollisionCheckCounter = 0; _NoCollisionCounter = 0; } } public override void OnPickup() { if (Active) { IsGrabbed = true; } base.OnPickup(); } public override void OnDrop() { if (Active) { IsGrabbed = false; } base.OnDrop(); } private void SwapLampMaterial() { Material[] Materials = _MarkerMesh.materials; Materials[2] = IsLit ? _LitLampMaterial : _UnlitLampMaterial; _MarkerMesh.materials = Materials; } private void ReactToGrab() { if (IsGrabbed) { _RigidBodyComponent.constraints = RigidbodyConstraints.None; _NoCollisionCounter = 0; _CollidingLocations.Clear(); } else { _LocationFindingEnabled = true; VRCPlayerApi Owner = Networking.GetOwner(gameObject); if (!Owner.IsUserInVR()) { transform.eulerAngles = new Vector3(0.0f, transform.eulerAngles.y, 0.0f); } _NoCollisionCounter = 0; CheckCollisions(); } RequestSerialization(); } public void CheckCollisions() { if (!Active || !_LocationFindingEnabled || IsGrabbed) return; if (_CollidingLocations.Count > 0 && IsUpright()) { _NoCollisionCounter = 0; for (int i = 0; i < _CollidingLocations.Count; i++) { FloorMapLocation Location = (FloorMapLocation)_CollidingLocations[i].Reference; if (Location != null) { bool FoundCorrectResponse = ConfirmChoice(Location.Country, Location.City); if (FoundCorrectResponse) { SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(SendCorrectResponse), Location.transform.position); return; } } } // We should make sure to only reach here if we don't get a correct // response from the previous loop. if (_CollisionCheckCounter < MAX_REPEAT_COLLISION_CHECKS) { SendCustomEventDelayedSeconds(nameof(CheckCollisions), TIME_BETWEEN_REPEAT_COLLISION_CHECKS); } else { SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(SendIncorrectResponse)); } _CollisionCheckCounter++; } else { _NoCollisionCounter++; if (_NoCollisionCounter >= MAX_TIME_WITH_NO_COLLISIONS) { SendCustomNetworkEvent(NetworkEventTarget.Owner, nameof(SendIncorrectResponse)); } else { // If the marker isn't sitting mostly upright and is not grabbed, // don't check collisions until later, in case it becomes upright. SendCustomEventDelayedSeconds(nameof(CheckCollisions), TIME_BETWEEN_REPEAT_COLLISION_CHECKS); } } } public bool ConfirmChoice(string Country, string City) { if (Country == _GameManager.GetCurrentCountry() && City == _GameManager.GetCurrentCity()) { return true; } return false; } [NetworkCallable] public void SendCorrectResponse(Vector3 CorrectLocation) { DisableMovementCompletely(CorrectLocation); IsLit = true; Active = false; RequestSerialization(); _GameManager.PlayCorrectSound(); _GameManager.CorrectResponse(); } [NetworkCallable] public void SendIncorrectResponse() { DisableMovementCompletely(); _CollisionCheckCounter = 0; _CollidingLocations.Clear(); _FailureCounter++; if (_FailureCounter >= MAX_FAILURE_COUNT) { Active = false; _GameManager.IncorrectResponse(); } _GameManager.PlayIncorrectSound(); RequestSerialization(); } public void Activated(bool SetActive) { Active = SetActive; RequestSerialization(); } private void ChangePickupable() { _PickupComponent.pickupable = Active; } private void DisableMovementCompletely(Vector3 CorrectLocation = new Vector3()) { _RigidBodyComponent.constraints = RigidbodyConstraints.FreezeAll; transform.eulerAngles = new Vector3(0.0f, transform.eulerAngles.y, 0.0f); if (CorrectLocation != Vector3.zero) { transform.position = CorrectLocation; } _LocationFindingEnabled = false; RequestSerialization(); } private bool IsUpright() { return (Vector3.Dot(transform.up, Vector3.up) >= 0.85f); } private bool Active { set { _Active = value; ChangePickupable(); } get => _Active; } private bool IsGrabbed { set { _IsGrabbed = value; ReactToGrab(); } get => _IsGrabbed; } private bool IsLit { set { _IsLit = value; SwapLampMaterial(); } get => _IsLit; } }