using System; using System.Threading; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using ProjectZ.InGame.GameObjects.Things; using ProjectZ.InGame.Map; using ProjectZ.InGame.SaveLoad; using ProjectZ.InGame.Things; #if WINDOWS using System.Windows.Forms; #endif namespace ProjectZ.InGame.GameSystems { internal class MapTransitionSystem : GameSystem { public int AdditionalBlackScreenDelay; public bool StartDreamTransition; public bool StartTeleportTransition; public bool StartKnockoutTransition; public enum TransitionState { Idle, TransitionIn, TransitionBlank_0, TransitionBlank_1, TransitionOut, ColorMode } public TransitionState CurrentState = TransitionState.Idle; public const int ChangeMapTime = 350; public const int BlackScreenDelay = 75; // 125 private readonly MapManager _gameMapManager; private readonly ObjTransition _transitionObject; private Thread _loadingThread; private string _nextMapName; private string _nextMapPosition; private bool _nextMapCenter; private bool _nextMapStartInMiddle; private Color _nextMapColor; private bool _nextColorMode; private float _changeMapCount; // will be reset after each transition private bool _centerCamera; private bool _finishedLoading; private bool _fullColorMode; private bool _knockoutTransition; private bool _transitionEnded; private bool _introTransition; private const int DreamTransitionTimeAddition = 3000; private const int TeleportTransitionTimeAddition = 2000 - ChangeMapTime; private int _wobbleTransitionTime; private bool _wobbleTransitionOut; private bool _wobbleTransitionIn; public MapTransitionSystem(MapManager gameMapManager) { _gameMapManager = gameMapManager; _transitionObject = new ObjTransition(_gameMapManager.CurrentMap); } public override void OnLoad() { _nextMapName = null; } public override void Update() { // start map change if (_nextMapName != null) { if (!string.IsNullOrEmpty(_nextMapPosition)) MapManager.ObjLink.SetNextMapPosition(_nextMapPosition); LoadMapFromFile(_nextMapName, _nextMapCenter, _nextMapStartInMiddle, _nextMapColor, _nextColorMode); _nextMapName = null; } if (_transitionEnded) { _transitionEnded = false; Game1.GameManager.SaveManager.SetString("transition_ended", "0"); } if (CurrentState != TransitionState.Idle) Game1.GameManager.InGameOverlay.DisableOverlayToggle = true; else Game1.GbsPlayer.SetVolumeMultiplier(1); if (CurrentState == TransitionState.TransitionOut) { _changeMapCount += Game1.DeltaTime; var transitionState = _changeMapCount / ChangeMapTime; var percentage = MathHelper.Clamp((float)(Math.Sin(transitionState * 1.1) / Math.Sin(1.1)), 0, 1); MapManager.ObjLink.UpdateMapTransitionOut(percentage); if (!_wobbleTransitionOut && !_knockoutTransition) Game1.GameManager.DrawPlayerOnTopPercentage = percentage; // slowly lower the volume of the music var newVolume = 1 - MathHelper.Clamp(transitionState, 0, 1); Game1.GbsPlayer.SetVolumeMultiplier(newVolume); if (_wobbleTransitionOut) { // fade out to a white screen var wobblePercentage = MathHelper.Clamp(_changeMapCount / ChangeMapTime, 0, 1); _transitionObject.Brightness = wobblePercentage; _transitionObject.WobblePercentage = (_wobbleTransitionTime + _changeMapCount) / (_wobbleTransitionTime + ChangeMapTime); } if (_changeMapCount >= ChangeMapTime) CurrentState = TransitionState.TransitionBlank_0; } else if (CurrentState == TransitionState.TransitionBlank_0) { _changeMapCount += Game1.DeltaTime; MapManager.ObjLink.UpdateMapTransitionOut(1); // new map is loaded? if (_finishedLoading && _changeMapCount >= ChangeMapTime + BlackScreenDelay + AdditionalBlackScreenDelay) { FinishLoading(); CurrentState = TransitionState.TransitionBlank_1; } } else if (CurrentState == TransitionState.TransitionBlank_1) { _changeMapCount += Game1.DeltaTime; MapManager.ObjLink.UpdateMapTransitionIn(0); if (_changeMapCount >= ChangeMapTime + BlackScreenDelay * 2 + AdditionalBlackScreenDelay) { CurrentState = TransitionState.TransitionIn; if (_wobbleTransitionIn) _changeMapCount = _wobbleTransitionTime; else { _transitionObject.WobbleTransition = false; _changeMapCount = ChangeMapTime; } } } else if (CurrentState == TransitionState.TransitionIn) { _changeMapCount -= Game1.DeltaTime; // update the position of the player to walk into the new room var percentage = MathHelper.Clamp(_changeMapCount / ChangeMapTime, 0, 1); MapManager.ObjLink.UpdateMapTransitionIn(1 - (float)(Math.Sin(percentage * 1.1) / Math.Sin(1.1))); // slowly increase the volume of the music; the music is only playing var newVolume = 1 - percentage; if (Game1.GbsPlayer.GetVolumeMultiplier() < 1) Game1.GbsPlayer.SetVolumeMultiplier(newVolume); if (!_wobbleTransitionOut && !_knockoutTransition && !_introTransition) Game1.GameManager.DrawPlayerOnTopPercentage = percentage; if (_wobbleTransitionOut) { // fade out to a white screen var wobblePercentage = 1 - MathHelper.Clamp((_wobbleTransitionTime - _changeMapCount) / ChangeMapTime, 0, 1); _transitionObject.Brightness = wobblePercentage; _transitionObject.WobblePercentage = _changeMapCount / _wobbleTransitionTime; } // light up the scene if (_changeMapCount <= 0) { _transitionEnded = true; Game1.GameManager.SaveManager.SetString("transition_ended", "1"); CurrentState = TransitionState.Idle; EndTransition(); } } if (_knockoutTransition) { var percentage = MathHelper.Clamp((_changeMapCount - 100) / (ChangeMapTime - 100), 0, 1); _transitionObject.TransitionColor = _nextMapColor * percentage; _transitionObject.Percentage = 1; Game1.GameManager.MapManager.UpdateCameraX = false; Game1.GameManager.MapManager.UpdateCameraY = false; } if (_fullColorMode) { _transitionObject.Percentage = 1; _transitionObject.TransitionColor = _nextMapColor * TransitionPercentage(); } else if (!_knockoutTransition) { _transitionObject.Percentage = (float)Math.Sin(TransitionPercentage() * Math.PI / 2); } } public override void Draw(SpriteBatch spriteBatch) { if (CurrentState == TransitionState.Idle) return; _transitionObject.Draw(spriteBatch); } public void SetColorMode(Color color, float colorTransparency, bool playerOnTop = true) { if (colorTransparency > 0) CurrentState = TransitionState.ColorMode; else CurrentState = TransitionState.Idle; _changeMapCount = ChangeMapTime; _transitionObject.TransitionColor = color * colorTransparency; if (playerOnTop) Game1.GameManager.DrawPlayerOnTopPercentage = colorTransparency; } public bool IsTransitioningOut() { return CurrentState == TransitionState.TransitionOut; } public bool IsTransitioningIn() { return CurrentState == TransitionState.TransitionIn; } private void StartTransition() { // draw the player on top of everything MapManager.ObjLink.StartTransitioning(); _introTransition = Game1.GameManager.SaveManager.GetString("played_intro", "0") != "1"; // dont show the player for the intro sequence transition if (!_introTransition) Game1.GameManager.DrawPlayerOnTopPercentage = 1.0f; } private void EndTransition() { _knockoutTransition = false; MapManager.ObjLink.EndTransitioning(); // start the new music Game1.GbsPlayer.SetVolumeMultiplier(1); Game1.GbsPlayer.Play(); MapManager.Camera.SoftUpdate(Game1.GameManager.MapManager.GetCameraTarget()); } public float TransitionPercentage() { return MathHelper.Clamp(_changeMapCount / ChangeMapTime, 0, 1); } public void AppendMapChange(string mapName, string position) { AppendMapChange(mapName, position, false, false, Values.MapTransitionColor, false); } public void AppendMapChange(string mapName, string position, bool centerCamera, bool startFromMiddle, Color transitionColor, bool colorMode) { _nextMapName = mapName; _nextMapPosition = position; _nextMapCenter = centerCamera; _nextMapStartInMiddle = startFromMiddle; _nextMapColor = transitionColor; _nextColorMode = colorMode; MapManager.ObjLink.OnAppendMapChange(); } public void LoadMapFromFile(string mapFileName, bool centerCamera, bool startFromMiddle, Color transitionColor, bool colorMode) { // abort the old loading thread if it is still running // TODO: thread abort is not supported so it just waits till the loading is finished if (_loadingThread != null && _loadingThread.IsAlive) _loadingThread.Join(); //Debug.Assert(CurrentState == TransitionState.Idle || // CurrentState == TransitionState.ColorMode, "Tried transition while not in idle"); _finishedLoading = false; // only show the opening transition if (startFromMiddle) { _changeMapCount = ChangeMapTime; CurrentState = TransitionState.TransitionBlank_0; } else { _changeMapCount = 0; CurrentState = TransitionState.TransitionOut; } // center after loading _centerCamera = centerCamera; // add the transition object to the map _knockoutTransition = false; _fullColorMode = colorMode; _nextMapColor = transitionColor; _wobbleTransitionOut = false; _wobbleTransitionIn = false; _transitionObject.WobbleTransition = false; _transitionObject.TransitionColor = transitionColor; _transitionObject.Percentage = startFromMiddle ? 1 : 0; // start transition StartTransition(); if (StartDreamTransition) { StartDreamTransition = false; DreamTransition(); } if (StartTeleportTransition) { StartTeleportTransition = false; TeleportTransition(); } if (StartKnockoutTransition) { StartKnockoutTransition = false; KnockoutTransition(); } // start loading the new map in a thread _loadingThread = new Thread(o => ThreadLoading(mapFileName)); _loadingThread.Start(); } public void DreamTransition() { _transitionObject.WobbleTransition = true; _fullColorMode = true; _wobbleTransitionOut = true; Game1.GameManager.DrawPlayerOnTopPercentage = 0.0f; _nextMapColor = Color.White; // take longer _changeMapCount = -DreamTransitionTimeAddition; _wobbleTransitionTime = DreamTransitionTimeAddition; AdditionalBlackScreenDelay = 500; } public void TeleportTransition() { _transitionObject.WobbleTransition = true; _fullColorMode = true; _wobbleTransitionOut = true; _wobbleTransitionIn = true; Game1.GameManager.DrawPlayerOnTopPercentage = 0.0f; _nextMapColor = Color.White; // take longer _changeMapCount = -TeleportTransitionTimeAddition; _wobbleTransitionTime = TeleportTransitionTimeAddition; AdditionalBlackScreenDelay = 500; } public void KnockoutTransition() { _knockoutTransition = true; Game1.GameManager.DrawPlayerOnTopPercentage = 0.0f; _nextMapColor = Color.White; // take longer AdditionalBlackScreenDelay = 1000; } public void ThreadLoading(string mapFileName) { try { // @HACK: after loading the map the garbage collector will start and increase the frametime // this also leads to the door soundeffect cracking sometimes // if we wait a little bit the cracking gets reduced by a lot //Thread.Sleep(75); // load the map file SaveLoadMap.LoadMap(mapFileName, _gameMapManager.NextMap); // create the objects _gameMapManager.NextMap.Objects.LoadObjects(); _finishedLoading = true; } catch (Exception exception) { #if WINDOWS // show the error message instead of just crashing the game System.Windows.Forms.MessageBox.Show(exception.StackTrace, exception.Message, MessageBoxButtons.OK, MessageBoxIcon.Error); #endif throw; } } public void FinishLoading() { AdditionalBlackScreenDelay = 0; // switch to the new map var oldMap = _gameMapManager.CurrentMap; _gameMapManager.CurrentMap = _gameMapManager.NextMap; _gameMapManager.NextMap = oldMap; var currentTrack = Game1.GbsPlayer.CurrentTrack; var nextTrack = -1; for (var i = 0; i < _gameMapManager.CurrentMap.MapMusic.Length; i++) if (_gameMapManager.CurrentMap.MapMusic[i] >= 0) nextTrack = _gameMapManager.CurrentMap.MapMusic[i]; if (currentTrack != nextTrack) Game1.GbsPlayer.Pause(); Game1.GameManager.ResetMusic(); // finish loading map _gameMapManager.FinishLoadingMap(_gameMapManager.CurrentMap); MapManager.ObjLink.UpdateMapTransitionIn(0); // set the new music for (var i = 0; i < _gameMapManager.CurrentMap.MapMusic.Length; i++) if (_gameMapManager.CurrentMap.MapMusic[i] >= 0) Game1.GameManager.SetMusic(_gameMapManager.CurrentMap.MapMusic[i], i, false); // center the camera var goalPosition = Game1.GameManager.MapManager.GetCameraTarget(); if (_centerCamera) MapManager.Camera.ForceUpdate(goalPosition); else MapManager.Camera.SoftUpdate(goalPosition); } } }