[DesktopVRSwitch] Testing

This commit is contained in:
NotAKidoS 2023-06-19 19:44:23 -05:00
parent 03514305be
commit 61a45f97bc
30 changed files with 957 additions and 524 deletions

View file

@ -0,0 +1,30 @@
using ABI_RC.Core.Savior;
using UnityEngine;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CVRGestureRecognizerTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
CVRGestureRecognizer _cvrGestureRecognizer = CVRGestureRecognizer.Instance;
if (_cvrGestureRecognizer == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CVRGestureRecognizer!");
return;
}
DesktopVRSwitch.Logger.Msg("Updating CVRGestureRecognizer _camera to active camera.");
_cvrGestureRecognizer._camera = Utils.GetPlayerCameraObject(intoVR).GetComponent<Camera>();
}
}

View file

@ -0,0 +1,41 @@
using ABI_RC.Core.Savior;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CVRInputManagerTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
void OnPostSwitch(bool intoVR)
{
CVRInputManager _cvrInputManager = CVRInputManager.Instance;
if (_cvrInputManager == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CVRInputManager!");
return;
}
DesktopVRSwitch.Logger.Msg("Resetting CVRInputManager inputs.");
_cvrInputManager.inputEnabled = true;
//just in case
_cvrInputManager.blockedByUi = false;
//sometimes head can get stuck, so just in case
_cvrInputManager.independentHeadToggle = false;
//just nice to load into desktop with idle gesture
_cvrInputManager.gestureLeft = 0f;
_cvrInputManager.gestureLeftRaw = 0f;
_cvrInputManager.gestureRight = 0f;
_cvrInputManager.gestureRightRaw = 0f;
//turn off finger tracking input
_cvrInputManager.individualFingerTracking = false;
}
}

View file

@ -0,0 +1,34 @@
using ABI.CCK.Components;
using UnityEngine;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CVRPickupObjectTracker : MonoBehaviour
{
private CVRPickupObject _pickupObject;
private Transform _storedGripOrigin;
public CVRPickupObjectTracker(CVRPickupObject pickupObject, Transform storedGripOrigin)
{
this._pickupObject = pickupObject;
this._storedGripOrigin = storedGripOrigin;
}
private void OnDestroy()
{
}
public void OnPostSwitch(bool intoVR)
{
if (_pickupObject != null)
{
// Drop the object if it is being held locally
if (_pickupObject._controllerRay != null)
_pickupObject._controllerRay.DropObject(true);
// Swap the grip origins
(_storedGripOrigin, _pickupObject.gripOrigin) = (_pickupObject.gripOrigin, _storedGripOrigin);
}
}
}

View file

@ -0,0 +1,29 @@
using ABI_RC.Core.InteractionSystem;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CVR_InteractableManagerTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
CVR_InteractableManager _cvrInteractableManager = CVR_InteractableManager.Instance;
if (_cvrInteractableManager == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CVR_InteractableManager!");
return;
}
DesktopVRSwitch.Logger.Msg($"Setting CVRInputManager inputEnabled & CVR_InteractableManager enableInteractions to {!intoVR}");
CVR_InteractableManager.enableInteractions = !intoVR;
}
}

View file

@ -0,0 +1,44 @@
using ABI_RC.Core.InteractionSystem;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CVR_MenuManagerTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPreVRModeSwitch += OnPreSwitch;
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPreVRModeSwitch -= OnPreSwitch;
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPreSwitch(bool intoVR)
{
CVR_MenuManager _cvrMenuManager = CVR_MenuManager.Instance;
if (_cvrMenuManager == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CVR_MenuManager!");
return;
}
DesktopVRSwitch.Logger.Msg("Closing CVR_MenuManager - Quick Menu.");
_cvrMenuManager.ToggleQuickMenu(false);
}
private void OnPostSwitch(bool intoVR)
{
CVR_MenuManager _cvrMenuManager = CVR_MenuManager.Instance;
if (_cvrMenuManager == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CVR_MenuManager!");
return;
}
DesktopVRSwitch.Logger.Msg("Updating CVR_Menu_Data core data.");
_cvrMenuManager.coreData.core.inVr = intoVR;
}
}

View file

@ -0,0 +1,27 @@
using ABI_RC.Core.Util.Object_Behaviour;
using UnityEngine;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CameraFacingObjectTracker : MonoBehaviour
{
private CameraFacingObject _cameraFacingObject;
public CameraFacingObjectTracker(CameraFacingObject cameraFacingObject)
{
this._cameraFacingObject = cameraFacingObject;
}
private void OnDestroy()
{
}
public void OnPreSwitch(bool intoVR) { }
public void OnFailedSwitch(bool intoVR) { }
public void OnPostSwitch(bool intoVR)
{
_cameraFacingObject.m_Camera = Utils.GetPlayerCameraObject(intoVR).GetComponent<Camera>();
}
}

View file

@ -0,0 +1,29 @@
using ABI_RC.Core.Savior;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CheckVRTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
CheckVR _checkVR = CheckVR.Instance;
if (_checkVR == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CheckVR!");
return;
}
DesktopVRSwitch.Logger.Msg($"Setting CheckVR hasVrDeviceLoaded to {intoVR}.");
_checkVR.hasVrDeviceLoaded = intoVR;
}
}

View file

@ -0,0 +1,34 @@
using ABI_RC.Core.Player;
using ABI_RC.Core.UI;
using UnityEngine;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class CohtmlHudTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
CohtmlHud _cohtmlHud = CohtmlHud.Instance;
if (_cohtmlHud == null)
{
DesktopVRSwitch.Logger.Error("Error while getting CohtmlHud!");
return;
}
DesktopVRSwitch.Logger.Msg("Configuring new hud affinity for CohtmlHud.");
_cohtmlHud.gameObject.transform.parent = intoVR ? PlayerSetup.Instance.vrCamera.transform : PlayerSetup.Instance.desktopCamera.transform;
// This handles rotation and position
ABI_RC.Core.CVRTools.ConfigureHudAffinity();
_cohtmlHud.gameObject.transform.localScale = new Vector3(1.2f, 1f, 1.2f);
}
}

View file

@ -0,0 +1,30 @@
using ABI_RC.Core.Player;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class HudOperationsTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
HudOperations _hudOperations = HudOperations.Instance;
if (_hudOperations == null)
{
DesktopVRSwitch.Logger.Error("Error while getting HudOperations!");
return;
}
DesktopVRSwitch.Logger.Msg("Switching HudOperations worldLoadingItem & worldLoadStatus.");
_hudOperations.worldLoadingItem = intoVR ? _hudOperations.worldLoadingItemVr : _hudOperations.worldLoadingItemDesktop;
_hudOperations.worldLoadStatus = intoVR ? _hudOperations.worldLoadStatusVr : _hudOperations.worldLoadStatusDesktop;
}
}

View file

@ -0,0 +1,87 @@
using ABI_RC.Systems.IK;
using ABI_RC.Systems.IK.SubSystems;
using ABI_RC.Systems.IK.TrackingModules;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class IKSystemTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPreVRModeSwitch += OnPreSwitch;
VRModeSwitchManager.OnFailVRModeSwitch += OnFailedSwitch;
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPreVRModeSwitch -= OnPreSwitch;
VRModeSwitchManager.OnFailVRModeSwitch -= OnFailedSwitch;
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPreSwitch(bool intoVR)
{
BodySystem.TrackingEnabled = false;
BodySystem.TrackingPositionWeight = 0f;
BodySystem.TrackingLocomotionEnabled = false;
if (IKSystem.vrik != null)
IKSystem.vrik.enabled = false;
}
private void OnFailedSwitch(bool intoVR)
{
BodySystem.TrackingEnabled = true;
BodySystem.TrackingPositionWeight = 1f;
BodySystem.TrackingLocomotionEnabled = true;
if (IKSystem.vrik != null)
IKSystem.vrik.enabled = true;
}
private void OnPostSwitch(bool intoVR)
{
if (IKSystem.vrik != null)
UnityEngine.Object.DestroyImmediate(IKSystem.vrik);
// Make sure you are fully tracking
BodySystem.TrackingEnabled = true;
BodySystem.TrackingPositionWeight = 1f;
BodySystem.TrackingLocomotionEnabled = true;
BodySystem.isCalibratedAsFullBody = false;
BodySystem.isCalibrating = false;
BodySystem.isRecalibration = false;
// Make it so you don't instantly end up in FBT from Desktop
IKSystem.firstAvatarLoaded = DesktopVRSwitch.EntryEnterCalibrationOnSwitch.Value;
// Turn off finger tracking just in case the user switched controllers
if (IKSystem.Instance != null)
IKSystem.Instance.FingerSystem.controlActive = false;
SetupSteamVRTrackingModule(intoVR);
}
private void SetupSteamVRTrackingModule(bool enableVR)
{
var openVRModule = IKSystem.Instance._trackingModules.OfType<SteamVRTrackingModule>().FirstOrDefault();
if (openVRModule != null)
{
if (enableVR)
{
openVRModule.ModuleStart();
}
else
{
openVRModule.ModuleDestroy();
}
}
else if (enableVR)
{
var newVRModule = new SteamVRTrackingModule();
IKSystem.Instance.AddTrackingModule(newVRModule);
}
}
}

View file

@ -0,0 +1,44 @@
using ABI_RC.Core.Savior;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class MetaPortTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
MetaPort _metaPort = MetaPort.Instance;
if (_metaPort == null)
{
DesktopVRSwitch.Logger.Error("Error while getting MetaPort!");
return;
}
DesktopVRSwitch.Logger.Msg($"Setting MetaPort isUsingVr to {intoVR}.");
// Main thing most of the game checks for if using VR
_metaPort.isUsingVr = intoVR;
// Hacky way of updating rich presence
if (_metaPort.settings.GetSettingsBool("ImplementationRichPresenceDiscordEnabled", true))
{
DesktopVRSwitch.Logger.Msg("Forcing Discord Rich Presence update.");
_metaPort.settings.SetSettingsBool("ImplementationRichPresenceDiscordEnabled", false);
_metaPort.settings.SetSettingsBool("ImplementationRichPresenceDiscordEnabled", true);
}
if (_metaPort.settings.GetSettingsBool("ImplementationRichPresenceSteamEnabled", true))
{
DesktopVRSwitch.Logger.Msg("Forcing Steam Rich Presence update.");
_metaPort.settings.SetSettingsBool("ImplementationRichPresenceSteamEnabled", false);
_metaPort.settings.SetSettingsBool("ImplementationRichPresenceSteamEnabled", true);
}
}
}

View file

@ -0,0 +1,48 @@
using ABI_RC.Systems.MovementSystem;
using UnityEngine;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class MovementSystemTracker : VRModeTracker
{
private MovementSystem _movementSystem;
private Vector3 preSwitchWorldPosition;
private Quaternion preSwitchWorldRotation;
public override void TrackerInit()
{
VRModeSwitchManager.OnPreVRModeSwitch += OnPreSwitch;
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPreVRModeSwitch -= OnPreSwitch;
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPreSwitch(bool intoVR)
{
_movementSystem = MovementSystem.Instance;
Vector3 position = _movementSystem.rotationPivot.transform.position;
position.y = _movementSystem.transform.position.y;
preSwitchWorldPosition = position;
preSwitchWorldRotation = _movementSystem.rotationPivot.transform.rotation;
_movementSystem.ChangeCrouch(false);
_movementSystem.ChangeProne(false);
}
private void OnPostSwitch(bool intoVR)
{
_movementSystem.rotationPivot = Utils.GetPlayerCameraObject(intoVR).transform;
_movementSystem.TeleportToPosRot(preSwitchWorldPosition, preSwitchWorldRotation, false);
if (!intoVR)
_movementSystem.UpdateColliderCenter(_movementSystem.transform.position);
_movementSystem.ChangeCrouch(false);
_movementSystem.ChangeProne(false);
}
}

View file

@ -0,0 +1,38 @@
using ABI_RC.Core.Player;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class PlayerSetupTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
PlayerSetup _playerSetup = PlayerSetup.Instance;
if (_playerSetup == null)
{
DesktopVRSwitch.Logger.Error("Error while getting PlayerSetup!");
return;
}
DesktopVRSwitch.Logger.Msg("Switching active PlayerSetup camera rigs. Updating Desktop camera FOV.");
_playerSetup.desktopCameraRig.SetActive(!intoVR);
_playerSetup.vrCameraRig.SetActive(intoVR);
// This might error if we started in VR.
// '_cam' is not set until Start().
CVR_DesktopCameraController.UpdateFov();
// UICamera has a script that copies the FOV from the desktop cam.
// Toggling the cameras on/off resets the aspect ratio,
// so when rigs switch, that is already handled.
}
}

View file

@ -0,0 +1,31 @@
using ABI_RC.Systems.Camera;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class PortableCameraTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
PortableCamera _portableCamera = PortableCamera.Instance;
if (_portableCamera == null)
{
DesktopVRSwitch.Logger.Error("Error while getting PortableCamera!");
return;
}
DesktopVRSwitch.Logger.Msg("Forcing PortableCamera canvas mirroring off.");
// Tell the game we are in mirror mode so it'll disable it (if enabled)
_portableCamera.mode = MirroringMode.Mirror;
_portableCamera.ChangeMirroring();
}
}

View file

@ -0,0 +1,185 @@
using ABI_RC.Systems.UI;
using System.Collections;
using UnityEngine;
using UnityEngine.Events;
using UnityEngine.XR;
using Valve.VR;
/**
I am unsure about this observer approach as only a few things need OnPre and OnFailed switch.
Those wouldn't be needed if I can start OpenVR before all that anyways.
Or... I just start OpenVR and see if it worked. OnPreSwitch would only be needed by menus & transition.
I think I should just use Unity Events as they would allow easier mod support. Subscribe to what you need.
**/
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class VRModeSwitchManager : MonoBehaviour
{
public static VRModeSwitchManager Instance { get; private set; }
// I don't think I *need* this. Only using cause I don't want stuff just floating off.
private static readonly List<VRModeTracker> _vrModeTrackers = new List<VRModeTracker>();
public static event UnityAction<bool> OnPreVRModeSwitch;
public static event UnityAction<bool> OnPostVRModeSwitch;
public static event UnityAction<bool> OnFailVRModeSwitch;
public static void RegisterVRModeTracker(VRModeTracker observer)
{
_vrModeTrackers.Add(observer);
observer.TrackerInit();
}
public static void UnregisterVRModeTracker(VRModeTracker observer)
{
_vrModeTrackers.Remove(observer);
observer.TrackerDestroy();
}
// Settings
private bool _useWorldTransition = true;
private bool _reloadLocalAvatar = true;
// Internal
private bool _switchInProgress = false;
void Awake()
{
if (Instance != null)
{
DestroyImmediate(this);
return;
}
Instance = this;
}
public void StartSwitch()
{
StartCoroutine(StartSwitchCoroutine());
}
private IEnumerator StartSwitchCoroutine()
{
if (_switchInProgress)
{
yield break;
}
_switchInProgress = true;
yield return null;
if (_useWorldTransition) // start visual transition and wait for it to complete
yield return WorldTransitionSystem.Instance.StartTransitionCoroutine();
// Check if OpenVR is running
bool isUsingVr = IsInVR();
InvokeOnPreSwitch(isUsingVr);
// Start switch
if (!isUsingVr)
{
yield return StartCoroutine(StartOpenVR());
}
else
{
yield return StartCoroutine(StopOpenVR());
}
// Check for updated VR mode
if (isUsingVr != IsInVR())
{
InvokeOnPostSwitch(!isUsingVr);
// reload the local avatar
// only reload on success
if (_reloadLocalAvatar)
Utils.ReloadLocalAvatar();
}
else
{
InvokeOnFailedSwitch(!isUsingVr);
}
if (_useWorldTransition) // finish the visual transition and wait
yield return WorldTransitionSystem.Instance.ContinueTransitionCoroutine();
_switchInProgress = false;
yield break;
}
private void SafeInvokeUnityEvent(UnityAction<bool> switchEvent, bool isUsingVr)
{
try
{
switchEvent.Invoke(isUsingVr);
}
catch (Exception e)
{
Debug.Log($"Error in event handler: {e}");
}
}
private void InvokeOnPreSwitch(bool isUsingVr)
{
SafeInvokeUnityEvent(OnPreVRModeSwitch, isUsingVr);
}
private void InvokeOnPostSwitch(bool isUsingVr)
{
SafeInvokeUnityEvent(OnPostVRModeSwitch, isUsingVr);
}
private void InvokeOnFailedSwitch(bool isUsingVr)
{
SafeInvokeUnityEvent(OnFailVRModeSwitch, isUsingVr);
}
public bool IsInVR() => XRSettings.enabled;
private IEnumerator StartOpenVR()
{
XRSettings.LoadDeviceByName("OpenVR");
yield return null; //wait a frame before checking
if (!string.IsNullOrEmpty(XRSettings.loadedDeviceName))
{
DesktopVRSwitch.Logger.Msg("Starting SteamVR...");
XRSettings.enabled = true;
SteamVR_Input.Initialize(true);
yield return null;
yield break;
}
DesktopVRSwitch.Logger.Error("Initializing VR Failed. Is there no VR device connected?");
yield return null;
yield break;
}
private IEnumerator StopOpenVR()
{
SteamVR_Behaviour.instance.enabled = false;
yield return null;
if (!string.IsNullOrEmpty(XRSettings.loadedDeviceName))
{
SteamVR_Input.actionSets[0].Deactivate(SteamVR_Input_Sources.Any);
XRSettings.LoadDeviceByName("");
XRSettings.enabled = false;
yield return null;
yield break;
}
DesktopVRSwitch.Logger.Error("Attempted to exit VR without a VR device loaded.");
yield return null;
yield break;
}
}

View file

@ -0,0 +1,7 @@
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class VRModeTracker
{
public virtual void TrackerInit() { }
public virtual void TrackerDestroy() { }
}

View file

@ -0,0 +1,32 @@
using ABI_RC.Core.Player;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class VRTrackerManagerTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPostVRModeSwitch += OnPostSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPostVRModeSwitch -= OnPostSwitch;
}
private void OnPostSwitch(bool intoVR)
{
VRTrackerManager _vrTrackerManager = VRTrackerManager.Instance;
if (_vrTrackerManager == null)
{
DesktopVRSwitch.Logger.Error("Error while getting VRTrackerManager!");
return;
}
DesktopVRSwitch.Logger.Msg($"Resetting VRTrackerManager.");
_vrTrackerManager.poses = null;
_vrTrackerManager.leftHand = null;
_vrTrackerManager.rightHand = null;
_vrTrackerManager.hasCheckedForKnuckles = false;
}
}

View file

@ -0,0 +1,29 @@
using ABI_RC.Core.InteractionSystem;
namespace NAK.DesktopVRSwitch.VRModeTrackers;
public class ViewManagerTracker : VRModeTracker
{
public override void TrackerInit()
{
VRModeSwitchManager.OnPreVRModeSwitch += OnPreSwitch;
}
public override void TrackerDestroy()
{
VRModeSwitchManager.OnPreVRModeSwitch -= OnPreSwitch;
}
public void OnPreSwitch(bool intoVR)
{
ViewManager _viewManager = ViewManager.Instance;
if (_viewManager == null)
{
DesktopVRSwitch.Logger.Error("Error while getting ViewManager!");
return;
}
DesktopVRSwitch.Logger.Msg("Closing ViewManager - Main Menu.");
_viewManager.UiStateToggle(false);
}
}