// CharacterPhysicsModes.cs using System.Collections; using System.Collections.Generic; using InHouseXR.Simulation; public class CharacterPhysicsModes : SimuScriptBase { public SceneObject DropButton; public SceneObject StandButton; public SceneObject FallStartTrigger; public SceneObject FallStopTrigger; public SceneObject FallStartAudioObject; public SceneObject FallStopAudioObject; public SceneObject DropAudioObject; public float dropRecoverSeconds = 3.0f; public float fallRecoverSeconds = 0.0f; public float fallStopRecoverSeconds = 0.0f; public float standTransitionSeconds = 0.8f; public bool resetUsersOnJoin = true; public bool highlightButtons = true; public float interactDistance = 10f; public int audioSourceIndex = 0; public bool includeAudioChildren = true; public bool stopAudioBeforePlay = true; Dictionary activeFallTokens = new Dictionary(); Dictionary landedFallTokens = new Dictionary(); int nextFallToken; public override void Start() { RegisterButtons(); RegisterFallTriggers(); } public override void OnUserJoin(SVUserInfo user) { string username = user != null ? user.Username : null; if (resetUsersOnJoin && !string.IsNullOrWhiteSpace(username)) { ClearFallState(username); StopCharacterPhysicsMode(username, standTransitionSeconds); } RegisterButtons(username); } void RegisterButtons(string targetUsername = null) { if (DropButton != null && !DropButton.IsNull) { MakeInteractable( DropButton, OnDropClicked, highlightButtons, targetUsername, true, interactDistance, "Drop"); } if (StandButton != null && !StandButton.IsNull) { MakeInteractable( StandButton, OnStandClicked, highlightButtons, targetUsername, true, interactDistance, "Stand"); } } void RegisterFallTriggers() { if (FallStartTrigger != null && !FallStartTrigger.IsNull) RegisterTrigger(FallStartTrigger, OnFallStartEnter); if (FallStopTrigger != null && !FallStopTrigger.IsNull) RegisterTrigger(FallStopTrigger, OnFallStopEnter); } void OnDropClicked(SVInteractionInfo info) { if (info == null || string.IsNullOrWhiteSpace(info.Username)) return; ApplyCharacterImpact( info.Username, Vector3.zero, 0f, 0f, true, dropRecoverSeconds, SVCharacterImpactPart.All); PlayOptionalAudio(DropAudioObject); } void OnStandClicked(SVInteractionInfo info) { if (info == null || string.IsNullOrWhiteSpace(info.Username)) return; ClearFallState(info.Username); StopCharacterPhysicsMode(info.Username, standTransitionSeconds); } void OnFallStartEnter(SVTriggerInfo info) { if (info == null || !info.IsPlayer || string.IsNullOrWhiteSpace(info.Username)) return; string username = info.Username.Trim(); int token = StartNewFallCycle(username); StopCharacterPhysicsMode(username, 0f); PlayOptionalAudio(FallStartAudioObject); StartCoroutine(SetFallingNextFrame(username, token)); } IEnumerator SetFallingNextFrame(string username, int token) { yield return null; if (!IsActiveFall(username, token)) yield break; SetCharacterPhysicsMode( username, SVCharacterPhysicsMode.Falling, 0.15f, 0f); ScheduleFallAutoRecover(username, token); } void OnFallStopEnter(SVTriggerInfo info) { if (info == null || !info.IsPlayer || string.IsNullOrWhiteSpace(info.Username)) return; string username = info.Username.Trim(); int token; if (!activeFallTokens.TryGetValue(username, out token)) return; int landedToken; if (landedFallTokens.TryGetValue(username, out landedToken) && landedToken == token) return; landedFallTokens[username] = token; PlayOptionalAudio(FallStopAudioObject); ScheduleFallStop(username, token); } int StartNewFallCycle(string username) { nextFallToken++; if (nextFallToken == int.MaxValue) nextFallToken = 1; activeFallTokens[username] = nextFallToken; landedFallTokens.Remove(username); return nextFallToken; } bool IsActiveFall(string username, int token) { int activeToken; return activeFallTokens.TryGetValue(username, out activeToken) && activeToken == token; } void ScheduleFallAutoRecover(string username, int token) { float delaySeconds = Mathf.Max(0f, fallRecoverSeconds); if (delaySeconds > 0f) StartCoroutine(AutoRecoverFallAfterDelay(username, token, delaySeconds)); } IEnumerator AutoRecoverFallAfterDelay(string username, int token, float delaySeconds) { yield return new WaitForSeconds(delaySeconds); int landedToken; if (IsActiveFall(username, token) && !landedFallTokens.TryGetValue(username, out landedToken)) CompleteFallCycle(username, token); } void ScheduleFallStop(string username, int token) { float delaySeconds = GetFallStopRecoverSeconds(); if (delaySeconds <= 0f) { CompleteFallCycle(username, token); return; } StartCoroutine(StopFallingAfterDelay(username, token, delaySeconds)); } float GetFallStopRecoverSeconds() { if (fallStopRecoverSeconds > 0f) return fallStopRecoverSeconds; return Mathf.Max(0f, fallRecoverSeconds); } IEnumerator StopFallingAfterDelay(string username, int token, float delaySeconds) { yield return new WaitForSeconds(delaySeconds); CompleteFallCycle(username, token); } void CompleteFallCycle(string username, int token) { if (!IsActiveFall(username, token)) return; activeFallTokens.Remove(username); landedFallTokens.Remove(username); StopCharacterPhysicsMode(username, standTransitionSeconds); } void ClearFallState(string username) { activeFallTokens.Remove(username); landedFallTokens.Remove(username); } void PlayOptionalAudio(SceneObject audioObject) { if (audioObject == null || audioObject.IsNull) return; if (stopAudioBeforePlay) StopAudio(audioObject, null, audioSourceIndex, includeAudioChildren); PlayAudio(audioObject, null, audioSourceIndex, includeAudioChildren); } }