using Microsoft.Xna.Framework; using System; using System.Collections.Generic; namespace Barotrauma { class CameraTransition { private static List activeTransitions = new List(); public bool Running { get; private set; } public Camera AssignedCamera; private readonly Alignment? cameraStartPos; private readonly Alignment? cameraEndPos; private readonly float? startZoom; private readonly float? endZoom; public readonly float WaitDuration; public float EndWaitDuration = 0.1f; public readonly float PanDuration; public readonly bool FadeOut; public readonly bool LosFadeIn; private readonly CoroutineHandle updateCoroutine; private Character prevControlled; public bool AllowInterrupt = false; public bool RemoveControlFromCharacter = true; public bool RunWhilePaused = true; public CameraTransition(ISpatialEntity targetEntity, Camera cam, Alignment? cameraStartPos, Alignment? cameraEndPos, bool fadeOut = true, bool losFadeIn = false, float waitDuration = 0f, float panDuration = 10.0f, float? startZoom = null, float? endZoom = null) { WaitDuration = waitDuration; PanDuration = panDuration; FadeOut = fadeOut; LosFadeIn = losFadeIn; this.cameraStartPos = cameraStartPos; this.cameraEndPos = cameraEndPos; this.startZoom = startZoom; this.endZoom = endZoom; AssignedCamera = cam; if (targetEntity == null) { return; } Running = true; prevControlled = Character.Controlled; activeTransitions.RemoveAll(a => !CoroutineManager.IsCoroutineRunning(a.updateCoroutine)); foreach (var activeTransition in activeTransitions) { if (activeTransition.prevControlled != null) { prevControlled ??= activeTransition.prevControlled; } activeTransition.Stop(); } updateCoroutine = CoroutineManager.StartCoroutine(Update(targetEntity, cam), "CameraTransition"); activeTransitions.Add(this); } public void Stop() { CoroutineManager.StopCoroutines(updateCoroutine); Running = false; #if CLIENT if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; } if (prevControlled != null && !prevControlled.Removed) { Character.Controlled = prevControlled; } #endif } private float DeltaTime => CoroutineManager.Paused && !RunWhilePaused ? 0 : CoroutineManager.DeltaTime; private IEnumerable Update(ISpatialEntity targetEntity, Camera cam) { if (targetEntity == null || (targetEntity is Entity e && e.Removed)) { yield return CoroutineStatus.Success; } prevControlled ??= Character.Controlled; if (RemoveControlFromCharacter) { #if CLIENT GameMain.LightManager.LosEnabled = false; #endif Character.Controlled = null; } cam.TargetPos = Vector2.Zero; float startZoom = this.startZoom ?? cam.Zoom; float endZoom = this.endZoom ?? 0.5f; Vector2 initialCameraPos = cam.Position; Vector2? initialTargetPos = targetEntity?.WorldPosition; Vector2 endPos = cam.Position; float timer = -WaitDuration; while (timer < PanDuration) { float clampedTimer = Math.Max(timer, 0f); if (Screen.Selected != GameMain.GameScreen) { yield return new WaitForSeconds(0.1f); #if CLIENT if (FadeOut) { GUI.ScreenOverlayColor = Color.TransparentBlack; } #endif Running = false; yield return CoroutineStatus.Success; } //switched control to some other character during the transition -> remove control again if (Character.Controlled != null) { prevControlled = Character.Controlled; if (RemoveControlFromCharacter) { #if CLIENT GameMain.LightManager.LosEnabled = false; #endif Character.Controlled = null; } } if (prevControlled != null && prevControlled.Removed) { prevControlled = null; } #if CLIENT if (AllowInterrupt && PlayerInput.KeyHit(Microsoft.Xna.Framework.Input.Keys.Escape)) { break; } #endif Vector2 minPos = targetEntity.WorldPosition; Vector2 maxPos = targetEntity.WorldPosition; if (targetEntity is Submarine sub) { minPos = new Vector2(sub.WorldPosition.X - sub.Borders.Width / 2, sub.WorldPosition.Y - sub.Borders.Height / 2); maxPos = new Vector2(sub.WorldPosition.X + sub.Borders.Width / 2, sub.WorldPosition.Y + sub.Borders.Height / 2); } Vector2 startPos = cameraStartPos.HasValue ? new Vector2( MathHelper.Lerp(minPos.X, maxPos.X, (cameraStartPos.Value.ToVector2().X + 1.0f) / 2.0f), MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraStartPos.Value.ToVector2().Y + 1.0f) / 2.0f)) : initialCameraPos; if (!cameraStartPos.HasValue && initialTargetPos.HasValue) { startPos += targetEntity.WorldPosition - initialTargetPos.Value; } endPos = cameraEndPos.HasValue ? new Vector2( MathHelper.Lerp(minPos.X, maxPos.X, (cameraEndPos.Value.ToVector2().X + 1.0f) / 2.0f), MathHelper.Lerp(maxPos.Y, minPos.Y, (cameraEndPos.Value.ToVector2().Y + 1.0f) / 2.0f)) : prevControlled?.WorldPosition ?? targetEntity.WorldPosition; Vector2 cameraPos = Vector2.SmoothStep(startPos, endPos, clampedTimer / PanDuration) + cam.ShakePosition; cam.Translate(cameraPos - cam.Position); #if CLIENT cam.Zoom = MathHelper.SmoothStep(startZoom, endZoom, clampedTimer / PanDuration); if (clampedTimer / PanDuration > 0.9f) { if (FadeOut) { GUI.ScreenOverlayColor = Color.Lerp(Color.TransparentBlack, Color.Black, ((clampedTimer / PanDuration) - 0.9f) * 10.0f); } } if (LosFadeIn && clampedTimer / PanDuration > 0.8f) { if (!GameMain.DevMode) { GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosAlpha = ((clampedTimer / PanDuration) - 0.8f) * 5.0f; } Lights.LightManager.ViewTarget = prevControlled ?? (targetEntity as Entity); } #endif timer += DeltaTime; yield return CoroutineStatus.Running; } float endTimer = 0.0f; while (endTimer <= EndWaitDuration) { cam.Translate(endPos - cam.Position); cam.Zoom = endZoom; endTimer += DeltaTime; yield return CoroutineStatus.Running; } Running = false; #if CLIENT GUI.ScreenOverlayColor = Color.TransparentBlack; if (!GameMain.DevMode) { GameMain.LightManager.LosEnabled = true; GameMain.LightManager.LosAlpha = 1f; } #endif if (prevControlled != null && !prevControlled.Removed) { Character.Controlled = prevControlled; } yield return CoroutineStatus.Success; } } }