using Cysharp.Threading.Tasks; using OWML.Common; using QSB.Messaging; using QSB.Player; using QSB.QuantumSync.Messages; using QSB.Tools.ProbeTool; using QSB.Utility; using QSB.WorldSync; using System.Collections.Generic; using System.Linq; using System.Threading; using UnityEngine; namespace QSB.QuantumSync.WorldObjects; /// /// TODO: make it part of the ad-hoc owner interface /// /// TODO: make it so only players in the sector (which sector?) are checked for visibility /// public abstract class QSBQuantumObject : WorldObject, IQSBQuantumObject where T : QuantumObject { public virtual bool HostControls => false; public uint ControllingPlayer { get; set; } public bool IsEnabled { get; private set; } private List _visibleToProbes = new(); public override void OnRemoval() { if (HostControls) { return; } foreach (var shape in GetAttachedShapes()) { shape.OnShapeActivated -= OnEnable; shape.OnShapeDeactivated -= OnDisable; } QSBPlayerManager.OnRemovePlayer -= OnRemovePlayer; } public override async UniTask Init(CancellationToken ct) { await UniTask.DelayFrame(5, cancellationToken: ct); if (HostControls) { // first player is the host ControllingPlayer = QSBPlayerManager.PlayerList[0].PlayerId; IsEnabled = true; return; } var attachedShapes = GetAttachedShapes(); if (attachedShapes.Count == 0) { IsEnabled = false; return; } foreach (var shape in attachedShapes) { shape.OnShapeActivated += OnEnable; shape.OnShapeDeactivated += OnDisable; } if (attachedShapes.All(x => x.enabled && x.gameObject.activeInHierarchy && x.active)) { IsEnabled = true; } else { ControllingPlayer = 0u; IsEnabled = false; } QSBPlayerManager.OnRemovePlayer += OnRemovePlayer; } private void OnRemovePlayer(PlayerInfo player) { _visibleToProbes.Remove(player); AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(); } public override void SendInitialState(uint to) => ((IQSBQuantumObject)this).SendMessage(new QuantumOwnershipMessage(ControllingPlayer) { To = to }); public List GetAttachedShapes() { if (AttachedObject == null) { return new List(); } var visibilityTrackers = AttachedObject._visibilityTrackers; if (visibilityTrackers == null || visibilityTrackers.Length == 0) { return new List(); } if (visibilityTrackers.Any(x => x.GetType() == typeof(RendererVisibilityTracker))) { DebugLog.ToConsole($"Warning - {AttachedObject.name} has a RendererVisibilityTracker!", MessageType.Warning); return new List(); } var totalShapes = new List(); foreach (ShapeVisibilityTracker tracker in visibilityTrackers) { if (tracker == null) { DebugLog.ToConsole($"Warning - a ShapeVisibilityTracker in {this} is null!", MessageType.Warning); continue; } // if the tracker is not active, this won't have been set, so just do it ourselves tracker._shapes ??= tracker.GetComponents(); totalShapes.AddRange(tracker._shapes.Where(x => x != null)); } return totalShapes; } public void SetIsQuantum(bool isQuantum) => AttachedObject._isQuantum = isQuantum; public VisibilityObject GetVisibilityObject() => AttachedObject; private void OnEnable(Shape s) { if (IsEnabled) { return; } IsEnabled = true; if (!QSBWorldSync.AllObjectsReady && !QSBCore.IsHost) { return; } if (ControllingPlayer != 0) { // controlled by another player, dont care that we activate it return; } // no one is controlling this object right now, request ownership ((IQSBQuantumObject)this).SendMessage(new QuantumOwnershipMessage(QSBPlayerManager.LocalPlayerId)); } private void OnDisable(Shape s) => // we wait a frame here in case the shapes get disabled as we switch from 1 visibility tracker to another Delay.RunNextFrame(() => { if (!IsEnabled) { return; } if (GetAttachedShapes().Any(x => x.isActiveAndEnabled)) { return; } IsEnabled = false; if (!QSBWorldSync.AllObjectsReady && !QSBCore.IsHost) { return; } if (ControllingPlayer != QSBPlayerManager.LocalPlayerId) { // not being controlled by us, don't care if we leave area return; } // send event to other players that we're releasing ownership ((IQSBQuantumObject)this).SendMessage(new QuantumOwnershipMessage(0u)); }); public void OnTakeProbeSnapshot(PlayerInfo player, ProbeCamera.ID cameraId) { if (player.IsLocalPlayer) { var probe = Locator.GetProbe(); ProbeCamera probeCamera = default; switch (cameraId) { case ProbeCamera.ID.Forward: probeCamera = probe.GetForwardCamera(); break; case ProbeCamera.ID.Reverse: probeCamera = probe.GetReverseCamera(); break; case ProbeCamera.ID.Rotating: probeCamera = probe.GetRotatingCamera(); break; case ProbeCamera.ID.PreLaunch: probeCamera = player.LocalProbeLauncher._preLaunchCamera; break; } var distance = Vector3.Distance(AttachedObject.transform.position, probeCamera.transform.position); if (distance < AttachedObject._maxSnapshotLockRange && AttachedObject.IsIlluminated() && !probeCamera.HasInterference() && AttachedObject.CheckVisibilityFromProbe(probeCamera.GetOWCamera())) { if (!_visibleToProbes.Contains(player)) { _visibleToProbes.Add(player); } AttachedObject._visibleInProbeSnapshot = true; return; } } else { var probe = player.Probe; QSBProbeCamera probeCamera = default; switch (cameraId) { case ProbeCamera.ID.Forward: probeCamera = probe.GetForwardCamera(); break; case ProbeCamera.ID.Reverse: probeCamera = probe.GetReverseCamera(); break; case ProbeCamera.ID.Rotating: probeCamera = probe.GetRotatingCamera(); break; case ProbeCamera.ID.PreLaunch: //TODO : uhhhh yeah do this lol probeCamera = null; break; } var distance = Vector3.Distance(AttachedObject.transform.position, probeCamera.transform.position); if (distance < AttachedObject._maxSnapshotLockRange && AttachedObject.IsIlluminated() && !probeCamera.HasInterference() && AttachedObject.CheckVisibilityFromProbe(probeCamera.GetOWCamera())) { if (!_visibleToProbes.Contains(player)) { _visibleToProbes.Add(player); } AttachedObject._visibleInProbeSnapshot = true; return; } } if (_visibleToProbes.Contains(player)) { _visibleToProbes.Remove(player); } AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(); } public void OnRemoveProbeSnapshot(PlayerInfo player) { if (_visibleToProbes.Contains(player)) { _visibleToProbes.Remove(player); } AttachedObject._visibleInProbeSnapshot = _visibleToProbes.Any(); } public List GetVisibleToProbePlayers() => _visibleToProbes; public override void DisplayLines() { if (AttachedObject == null) { return; } var localPlayer = QSBPlayerManager.LocalPlayer; if (localPlayer == null) { return; } var body = localPlayer.Body; if (body == null) { return; } if (ControllingPlayer == 0) { if (IsEnabled) { Popcron.Gizmos.Line(AttachedObject.transform.position, body.transform.position, Color.magenta * 0.25f); } return; } var player = QSBPlayerManager.GetPlayer(ControllingPlayer); if (player == null) { return; } var playerBody = player.Body; if (playerBody == null) { return; } Popcron.Gizmos.Line(AttachedObject.transform.position, playerBody.transform.position, Color.magenta); } public override string ReturnLabel() { var label = $"{this}\r\n"; foreach (var tracker in AttachedObject._visibilityTrackers) { label += $"{tracker.name}:\r\n IsVisible:{tracker.IsVisible()}\r\n VisibleUsingCamera:{tracker.IsVisibleUsingCameraFrustum()}\r\n"; } label += $"VisibleInProbeSnapshot:{AttachedObject._visibleInProbeSnapshot}\r\n"; label += $"IsLockedByProbeSnapshot:{AttachedObject.IsLockedByProbeSnapshot()}\r\n"; label += "VisibleToProbePlayers:\r\n"; foreach (var player in GetVisibleToProbePlayers()) { label += $" ID: {player.PlayerId}\r\n"; } return label; } }