using Mirror; using OWML.Common; using QSB.Player; using QSB.Utility; using QSB.WorldSync; using System; using System.Linq; using UnityEngine; /* * Rewrite number : 11 * God has cursed me for my hubris, and my work is never finished. */ namespace QSB.Syncs; public abstract class SyncBase : QSBNetworkTransform { /// /// valid if IsPlayerObject, otherwise null /// public PlayerInfo Player { get { if (_player == null) { DebugLog.ToConsole($"Error - trying to get SyncBase.Player for {netId} before Start has been called! " + "this really should not be happening!\n" + Environment.StackTrace, MessageType.Error); } return _player; } } private PlayerInfo _player; private bool IsInitialized; protected virtual bool CheckReady() { if (netId is uint.MaxValue or 0) { return false; } if (!QSBWorldSync.AllObjectsReady) { return false; } if (IsPlayerObject) { if (_player == null) { return false; } if (!isLocalPlayer && !_player.IsReady) { return false; } } return true; } protected override bool CheckValid() { if (!IsInitialized) { return false; } if (!base.CheckValid()) { return false; } if (!AttachedTransform) { DebugLog.ToConsole($"Error - AttachedObject {this} is null!", MessageType.Error); return false; } if (!AllowInactiveAttachedObject && !AttachedTransform.gameObject.activeInHierarchy) { return false; } if (!AllowNullReferenceTransform && !ReferenceTransform) { DebugLog.ToConsole($"Warning - {this}'s ReferenceTransform is null.", MessageType.Warning); return false; } return true; } protected abstract bool UseInterpolation { get; } protected virtual bool AllowInactiveAttachedObject => false; protected abstract bool AllowNullReferenceTransform { get; } protected virtual bool IsPlayerObject => false; public Transform AttachedTransform { get; private set; } public Transform ReferenceTransform { get; private set; } public string Name => AttachedTransform ? AttachedTransform.name : ""; public override string ToString() => (IsPlayerObject ? $"{Player.PlayerId}." : string.Empty) + $"{netId}:{GetType().Name} ({Name})"; protected virtual float DistanceChangeThreshold => 5f; private float _prevDistance; private const float SmoothTime = 0.1f; private Vector3 _positionSmoothVelocity; private Quaternion _rotationSmoothVelocity; protected Vector3 SmoothPosition { get; private set; } protected Quaternion SmoothRotation { get; private set; } private bool _interpolating; protected abstract Transform InitAttachedTransform(); public override void OnStartClient() { base.OnStartClient(); if (IsPlayerObject) { // get player objects spawned before this object (or is this one) // and use the closest one _player = QSBPlayerManager.PlayerList .Where(x => x.PlayerId <= netId) .MaxBy(x => x.PlayerId); } QSBSceneManager.OnPreSceneLoad += OnPreSceneLoad; } public override void OnStopClient() { base.OnStopClient(); QSBSceneManager.OnPreSceneLoad -= OnPreSceneLoad; if (IsInitialized) { SafeUninit(); } } private void OnPreSceneLoad(OWScene oldScene, OWScene newScene) { if (IsInitialized) { SafeUninit(); } } private const float _pauseTimerDelay = 10; private float _pauseTimer; private void SafeInit() { this.Try("initializing", () => { Init(); IsInitialized = true; }); if (!IsInitialized) { _pauseTimer = _pauseTimerDelay; } } private void SafeUninit() { this.Try("uninitializing", () => { Uninit(); IsInitialized = false; }); if (IsInitialized) { _pauseTimer = _pauseTimerDelay; } } protected virtual void Init() => AttachedTransform = InitAttachedTransform(); protected virtual void Uninit() { if (IsPlayerObject && !isOwned && AttachedTransform) { Destroy(AttachedTransform.gameObject); } } /// /// call the base method FIRST /// protected override bool HasChanged() { GetFromAttached(); if (UseInterpolation) { SmoothPosition = transform.position; SmoothRotation = transform.rotation; } return base.HasChanged(); } /// /// called right before checking HasChanged /// protected abstract void GetFromAttached(); /// /// call the base method LAST /// protected override void Deserialize(NetworkReader reader) { base.Deserialize(reader); if (UseInterpolation) { _interpolating = true; } ApplyToAttached(); } /// /// called right after deserializing /// protected abstract void ApplyToAttached(); protected sealed override void Update() { if (_pauseTimer > 0) { _pauseTimer -= Time.unscaledDeltaTime; return; } if (!IsInitialized && CheckReady()) { SafeInit(); } else if (IsInitialized && !CheckReady()) { SafeUninit(); } base.Update(); if (IsValid && _interpolating) { if (isOwned) { _interpolating = false; return; } Interpolate(); ApplyToAttached(); } } private void Interpolate() { var distance = Vector3.Distance(SmoothPosition, transform.position); var angle = Quaternion.Angle(SmoothRotation, transform.rotation); if (Mathf.Abs(distance - _prevDistance) > DistanceChangeThreshold || distance < .001f && angle < .001f) { SmoothPosition = transform.position; SmoothRotation = transform.rotation; _interpolating = false; } else { SmoothPosition = Vector3.SmoothDamp(SmoothPosition, transform.position, ref _positionSmoothVelocity, SmoothTime); SmoothRotation = QuaternionHelper.SmoothDamp(SmoothRotation, transform.rotation, ref _rotationSmoothVelocity, SmoothTime); } _prevDistance = distance; } public virtual void SetReferenceTransform(Transform referenceTransform) { if (ReferenceTransform == referenceTransform) { return; } ReferenceTransform = referenceTransform; if (IsPlayerObject && !isOwned && AttachedTransform) { AttachedTransform.parent = ReferenceTransform; AttachedTransform.localScale = Vector3.one; } if (UseInterpolation && !isOwned && AttachedTransform && ReferenceTransform) { SmoothPosition = ReferenceTransform.ToRelPos(AttachedTransform.position); SmoothRotation = ReferenceTransform.ToRelRot(AttachedTransform.rotation); } } protected virtual void OnRenderObject() { if (!QSBCore.DebugSettings.DrawLines || !IsValid || !ReferenceTransform) { return; } /* * Red Cube = Where visible object should be * Green cube = Where visible object is * Red Line = Connection between Red Cube and Green Cube * Magenta cube = Reference transform * Cyan Line = Connection between Green cube and Magenta cube */ var colorMul = _interpolating ? .5f : 1; Popcron.Gizmos.Cube(ReferenceTransform.FromRelPos(transform.position), ReferenceTransform.FromRelRot(transform.rotation), Vector3.one / 8, Color.red * colorMul); Popcron.Gizmos.Cube(AttachedTransform.position, AttachedTransform.rotation, Vector3.one / 6, Color.green * colorMul); Popcron.Gizmos.Line(ReferenceTransform.FromRelPos(transform.position), AttachedTransform.position, Color.red * colorMul); Popcron.Gizmos.Cube(ReferenceTransform.position, ReferenceTransform.rotation, Vector3.one / 8, Color.magenta * colorMul); Popcron.Gizmos.Line(AttachedTransform.position, ReferenceTransform.position, Color.cyan * colorMul); } private void OnGUI() { if (!QSBCore.DebugSettings.DrawLabels || Event.current.type != EventType.Repaint || !IsValid || !ReferenceTransform) { return; } if (DebugActions.WorldObjectSelection == null) { DebugGUI.DrawLabel(AttachedTransform, ToString()); } } }