using System; using System.Collections.Generic; using System.Runtime.InteropServices; using System.Text; using EntityStates; using RoR2.ConVar; using UnityEngine; using UnityEngine.Events; using UnityEngine.Networking; using UnityEngine.Serialization; namespace RoR2; [DisallowMultipleComponent] public class VehicleSeat : NetworkBehaviour, IInteractable { private struct PassengerInfo { private static readonly List sharedBuffer = new List(); public readonly Transform transform; public readonly CharacterBody body; public readonly InputBankTest inputBank; public readonly InteractionDriver interactionDriver; public readonly CharacterMotor characterMotor; public readonly EntityStateMachine bodyStateMachine; public readonly CharacterModel characterModel; public readonly NetworkIdentity networkIdentity; public readonly Collider collider; public bool hasEffectiveAuthority => Util.HasEffectiveAuthority(networkIdentity); public PassengerInfo(GameObject passengerBodyObject) { transform = passengerBodyObject.transform; body = passengerBodyObject.GetComponent(); inputBank = passengerBodyObject.GetComponent(); interactionDriver = passengerBodyObject.GetComponent(); characterMotor = passengerBodyObject.GetComponent(); networkIdentity = passengerBodyObject.GetComponent(); collider = passengerBodyObject.GetComponent(); bodyStateMachine = null; passengerBodyObject.GetComponents(sharedBuffer); for (int i = 0; i < sharedBuffer.Count; i++) { EntityStateMachine entityStateMachine = sharedBuffer[i]; if (string.CompareOrdinal(entityStateMachine.customName, "Body") == 0) { bodyStateMachine = entityStateMachine; break; } } sharedBuffer.Clear(); characterModel = null; if ((bool)body.modelLocator && (bool)body.modelLocator.modelTransform) { characterModel = body.modelLocator.modelTransform.GetComponent(); } } public string GetString() { StringBuilder stringBuilder = new StringBuilder(); stringBuilder.Append("transform=").Append(transform).AppendLine(); stringBuilder.Append("body=").Append(body).AppendLine(); stringBuilder.Append("inputBank=").Append(inputBank).AppendLine(); stringBuilder.Append("interactionDriver=").Append(interactionDriver).AppendLine(); stringBuilder.Append("characterMotor=").Append(characterMotor).AppendLine(); stringBuilder.Append("bodyStateMachine=").Append(bodyStateMachine).AppendLine(); stringBuilder.Append("characterModel=").Append(characterModel).AppendLine(); stringBuilder.Append("networkIdentity=").Append(networkIdentity).AppendLine(); stringBuilder.Append("hasEffectiveAuthority=").Append(hasEffectiveAuthority).AppendLine(); return stringBuilder.ToString(); } } public delegate void InteractabilityCheckDelegate(CharacterBody activator, ref Interactability? resultOverride); public SerializableEntityStateType passengerState; public Transform seatPosition; public Transform exitPosition; public bool ejectOnCollision; public bool hidePassenger = true; public float exitVelocityFraction = 1f; public UnityEvent onPassengerEnterUnityEvent; [FormerlySerializedAs("OnPassengerExitUnityEvent")] public UnityEvent onPassengerExitUnityEvent; public string enterVehicleContextString; public string exitVehicleContextString; public bool disablePassengerMotor; public bool isEquipmentActivationAllowed; public bool shouldProximityHighlight = true; [SyncVar(hook = "SetPassenger")] private GameObject passengerBodyObject; private PassengerInfo passengerInfo; private Rigidbody rigidbody; private Collider collider; [HideInInspector] public CameraTargetParams targetParams; public CallbackCheck enterVehicleAllowedCheck = new CallbackCheck(); public CallbackCheck exitVehicleAllowedCheck = new CallbackCheck(); public CallbackCheck handleVehicleEnterRequestServer = new CallbackCheck(); public CallbackCheck handleVehicleExitRequestServer = new CallbackCheck(); private static readonly BoolConVar cvVehicleSeatDebug; private Run.FixedTimeStamp passengerAssignmentTime = Run.FixedTimeStamp.negativeInfinity; private readonly float passengerAssignmentCooldown = 0.2f; private NetworkInstanceId ___passengerBodyObjectNetId; private static int kCmdCmdEjectPassenger; public CharacterBody currentPassengerBody => passengerInfo.body; public InputBankTest currentPassengerInputBank => passengerInfo.inputBank; private static bool shouldLog => cvVehicleSeatDebug.value; public bool hasPassenger => passengerBodyObject; public GameObject NetworkpassengerBodyObject { get { return passengerBodyObject; } [param: In] set { if (NetworkServer.localClientActive && !base.syncVarHookGuard) { base.syncVarHookGuard = true; SetPassenger(value); base.syncVarHookGuard = false; } SetSyncVarGameObject(value, ref passengerBodyObject, 1u, ref ___passengerBodyObjectNetId); } } public event Action onPassengerEnter; public event Action onPassengerExit; public static event Action onPassengerEnterGlobal; public static event Action onPassengerExitGlobal; public string GetContextString(Interactor activator) { if (!passengerBodyObject) { return Language.GetString(enterVehicleContextString); } if ((object)passengerBodyObject == activator.gameObject) { return Language.GetString(exitVehicleContextString); } return null; } public Interactability GetInteractability(Interactor activator) { CharacterBody component = activator.GetComponent(); if (!passengerBodyObject) { return enterVehicleAllowedCheck.Evaluate(component) ?? Interactability.Available; } if ((object)passengerBodyObject == activator.gameObject && passengerAssignmentTime.timeSince >= passengerAssignmentCooldown) { return exitVehicleAllowedCheck.Evaluate(component) ?? Interactability.Available; } if ((bool)component && (bool)component.currentVehicle && (object)component.currentVehicle != this) { return Interactability.ConditionsNotMet; } return Interactability.Disabled; } public void OnInteractionBegin(Interactor activator) { if (!passengerBodyObject) { if (!handleVehicleEnterRequestServer.Evaluate(activator.gameObject).HasValue) { SetPassenger(activator.gameObject); } } else if ((object)activator.gameObject == passengerBodyObject && !handleVehicleExitRequestServer.Evaluate(activator.gameObject).HasValue) { SetPassenger(null); } } public bool ShouldIgnoreSpherecastForInteractibility(Interactor activator) { return false; } public bool ShouldShowOnScanner() { return true; } public bool ShouldProximityHighlight() { return shouldProximityHighlight; } private void Awake() { rigidbody = GetComponent(); collider = GetComponent(); targetParams = GetComponent(); } public override void OnStartClient() { base.OnStartClient(); if (!NetworkServer.active && (bool)passengerBodyObject) { OnPassengerEnter(passengerBodyObject); } } private void SetPassengerInternal(GameObject newPassengerBodyObject) { if ((bool)passengerBodyObject) { OnPassengerExit(passengerBodyObject); } NetworkpassengerBodyObject = newPassengerBodyObject; passengerInfo = default(PassengerInfo); passengerAssignmentTime = Run.FixedTimeStamp.now; if ((bool)passengerBodyObject) { OnPassengerEnter(passengerBodyObject); } _ = shouldLog; } private void SetPassenger(GameObject newPassengerBodyObject) { string text = (newPassengerBodyObject ? Util.GetBestBodyName(newPassengerBodyObject) : "null"); if (shouldLog) { Debug.LogFormat("SetPassenger passenger={0}", text); } if (base.syncVarHookGuard) { _ = shouldLog; NetworkpassengerBodyObject = newPassengerBodyObject; return; } _ = shouldLog; if ((object)passengerBodyObject == newPassengerBodyObject) { _ = shouldLog; return; } _ = shouldLog; SetPassengerInternal(newPassengerBodyObject); } private void OnPassengerMovementHit(ref CharacterMotor.MovementHitInfo movementHitInfo) { if (NetworkServer.active && ejectOnCollision && passengerAssignmentTime.timeSince > Time.fixedDeltaTime) { SetPassenger(null); } } private void ForcePassengerState() { if ((bool)passengerInfo.bodyStateMachine && passengerInfo.hasEffectiveAuthority) { Type type = passengerState.GetType(); if (passengerInfo.bodyStateMachine.state.GetType() != type) { passengerInfo.bodyStateMachine.SetInterruptState(EntityStateCatalog.InstantiateState(ref passengerState), InterruptPriority.Vehicle); } } } private void FixedUpdate() { ForcePassengerState(); UpdatePassengerPosition(); if ((bool)passengerInfo.characterMotor) { passengerInfo.characterMotor.velocity = Vector3.zero; } } private void UpdatePassengerPosition() { Vector3 position = seatPosition.position; if ((bool)passengerInfo.characterMotor) { passengerInfo.characterMotor.velocity = Vector3.zero; passengerInfo.characterMotor.Motor.BaseVelocity = Vector3.zero; passengerInfo.characterMotor.Motor.SetPosition(position); if (!disablePassengerMotor && Time.inFixedTimeStep) { passengerInfo.characterMotor.rootMotion = position - passengerInfo.transform.position; } } else if ((bool)passengerInfo.transform) { passengerInfo.transform.position = position; } } [Server] public bool AssignPassenger(GameObject bodyObject) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Boolean RoR2.VehicleSeat::AssignPassenger(UnityEngine.GameObject)' called on client"); return false; } if ((bool)passengerBodyObject) { return false; } if ((bool)bodyObject) { CharacterBody component = bodyObject.GetComponent(); if ((bool)component && (bool)component.currentVehicle) { component.currentVehicle.EjectPassenger(bodyObject); } } SetPassenger(bodyObject); return true; } [Server] public void EjectPassenger(GameObject bodyObject) { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.VehicleSeat::EjectPassenger(UnityEngine.GameObject)' called on client"); } else if ((object)bodyObject == passengerBodyObject) { SetPassenger(null); } } [Server] public void EjectPassenger() { if (!NetworkServer.active) { Debug.LogWarning("[Server] function 'System.Void RoR2.VehicleSeat::EjectPassenger()' called on client"); } else { SetPassenger(null); } } [Command] public void CmdEjectPassenger() { SetPassenger(null); } private void OnDestroy() { SetPassenger(null); } private void OnPassengerEnter(GameObject passenger) { passengerInfo = new PassengerInfo(passengerBodyObject); if ((bool)passengerInfo.body) { passengerInfo.body.currentVehicle = this; } if (hidePassenger && (bool)passengerInfo.characterModel) { passengerInfo.characterModel.invisibilityCount++; } ForcePassengerState(); if ((bool)passengerInfo.characterMotor) { if (disablePassengerMotor) { passengerInfo.characterMotor.enabled = false; } else { passengerInfo.characterMotor.onMovementHit += OnPassengerMovementHit; } } if ((bool)passengerInfo.collider && (bool)collider) { Physics.IgnoreCollision(collider, passengerInfo.collider, ignore: true); } if ((bool)passengerInfo.interactionDriver) { passengerInfo.interactionDriver.interactableOverride = base.gameObject; } _ = shouldLog; this.onPassengerEnter?.Invoke(passengerBodyObject); onPassengerEnterUnityEvent?.Invoke(); VehicleSeat.onPassengerEnterGlobal?.Invoke(this, passengerBodyObject); } private void OnPassengerExit(GameObject passenger) { _ = shouldLog; if (hidePassenger && (bool)passengerInfo.characterModel) { passengerInfo.characterModel.invisibilityCount--; } if ((bool)passengerInfo.body) { passengerInfo.body.currentVehicle = null; } if ((bool)passengerInfo.characterMotor) { if (disablePassengerMotor) { passengerInfo.characterMotor.enabled = true; } else { passengerInfo.characterMotor.onMovementHit -= OnPassengerMovementHit; } passengerInfo.characterMotor.velocity = Vector3.zero; passengerInfo.characterMotor.rootMotion = Vector3.zero; passengerInfo.characterMotor.Motor.BaseVelocity = Vector3.zero; } if ((bool)passengerInfo.collider && (bool)collider) { Physics.IgnoreCollision(collider, passengerInfo.collider, ignore: false); } if (passengerInfo.hasEffectiveAuthority) { if ((bool)passengerInfo.bodyStateMachine && passengerInfo.bodyStateMachine.CanInterruptState(InterruptPriority.Vehicle)) { passengerInfo.bodyStateMachine.SetNextStateToMain(); } Vector3 newPosition = (exitPosition ? exitPosition.position : seatPosition.position); TeleportHelper.TeleportGameObject(passengerInfo.transform.gameObject, newPosition); } if ((bool)passengerInfo.interactionDriver && (object)passengerInfo.interactionDriver.interactableOverride == base.gameObject) { passengerInfo.interactionDriver.interactableOverride = null; } if ((bool)rigidbody && (bool)passengerInfo.characterMotor) { passengerInfo.characterMotor.velocity = rigidbody.velocity * exitVelocityFraction; } this.onPassengerExit?.Invoke(passengerBodyObject); onPassengerExitUnityEvent?.Invoke(); VehicleSeat.onPassengerExitGlobal?.Invoke(this, passengerBodyObject); } static VehicleSeat() { cvVehicleSeatDebug = new BoolConVar("vehicle_seat_debug", ConVarFlags.None, "0", "Enables debug logging for VehicleSeat."); kCmdCmdEjectPassenger = -1985903462; NetworkBehaviour.RegisterCommandDelegate(typeof(VehicleSeat), kCmdCmdEjectPassenger, InvokeCmdCmdEjectPassenger); NetworkCRC.RegisterBehaviour("VehicleSeat", 0); } private void UNetVersion() { } protected static void InvokeCmdCmdEjectPassenger(NetworkBehaviour obj, NetworkReader reader) { if (!NetworkServer.active) { Debug.LogError("Command CmdEjectPassenger called on client."); } else { ((VehicleSeat)obj).CmdEjectPassenger(); } } public void CallCmdEjectPassenger() { if (!NetworkClient.active) { Debug.LogError("Command function CmdEjectPassenger called on server."); return; } if (base.isServer) { CmdEjectPassenger(); return; } NetworkWriter networkWriter = new NetworkWriter(); networkWriter.Write((short)0); networkWriter.Write((short)5); networkWriter.WritePackedUInt32((uint)kCmdCmdEjectPassenger); networkWriter.Write(GetComponent().netId); SendCommandInternal(networkWriter, 0, "CmdEjectPassenger"); } public override bool OnSerialize(NetworkWriter writer, bool forceAll) { if (forceAll) { writer.Write(passengerBodyObject); return true; } bool flag = false; if ((base.syncVarDirtyBits & (true ? 1u : 0u)) != 0) { if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); flag = true; } writer.Write(passengerBodyObject); } if (!flag) { writer.WritePackedUInt32(base.syncVarDirtyBits); } return flag; } public override void OnDeserialize(NetworkReader reader, bool initialState) { if (initialState) { ___passengerBodyObjectNetId = reader.ReadNetworkId(); return; } int num = (int)reader.ReadPackedUInt32(); if (((uint)num & (true ? 1u : 0u)) != 0) { SetPassenger(reader.ReadGameObject()); } } public override void PreStartClient() { if (!___passengerBodyObjectNetId.IsEmpty()) { NetworkpassengerBodyObject = ClientScene.FindLocalObject(___passengerBodyObjectNetId); } } }