using System; using System.Collections.Generic; using System.Linq; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Audio; using Microsoft.Xna.Framework.Content; using Microsoft.Xna.Framework.Graphics; using ProjectZ.InGame.GameSystems; using ProjectZ.InGame.Map; using ProjectZ.InGame.Overlay; using ProjectZ.InGame.SaveLoad; namespace ProjectZ.InGame.Things { public class GameManager { public struct MiniMapTile { public int TileIndex; public bool DiscoveryState; public int HintTileIndex; public string HintKey; } public struct MiniMapOverrides { public string SaveKey; public int PosX; public int PosY; public int TileIndex; } public class MiniMap { public int OffsetX; public int OffsetY; public MiniMapTile[,] Tiles; public MiniMapOverrides[] Overrides; } private class PlayingSoundEffect { public bool LowerMusicVolume; public float Volume; public double EndTime; public SoundEffectInstance Instance; } // _activeRenderTarget == null ??? public Matrix GetMatrix => Matrix.CreateScale(new Vector3( (float)_activeRenderTarget.Width / (int)(Game1.WindowWidth * _scaleMultiplier), (float)_activeRenderTarget.Height / (int)(Game1.WindowHeight * _scaleMultiplier), 0)); public int CurrentRenderWidth; public int CurrentRenderHeight; public float CurrentRenderScale; public int BlurRenderTargetWidth => (int)(Game1.RenderWidth / MapManager.Camera.Scale / 2) + 8; public int BlurRenderTargetHeight => (int)(Game1.RenderHeight / MapManager.Camera.Scale / 2) + 8; public int SideBlurRenderTargetWidth => BlurRenderTargetWidth * 2; public int SideBlurRenderTargetHeight => BlurRenderTargetHeight * 2; public MapManager MapManager = new MapManager(); public OverlayManager InGameOverlay = new OverlayManager(); public SaveManager SaveManager = new SaveManager(); public ItemManager ItemManager = new ItemManager(); public float ForestColorState; public bool UseShockEffect; public const int EquipmentSlots = 12; public GameItemCollected[] Equipment = new GameItemCollected[EquipmentSlots]; public List CollectedItems = new List(); // sound effects that are currently playing private Dictionary CurrentSoundEffects = new Dictionary(); // dungeon maps public Dictionary DungeonMaps = new Dictionary(); public Dictionary GameSystems = new Dictionary(); public Point PlayerDungeonPosition; // can be null if the player never left the house in the beginning public Point? PlayerMapPosition; public bool[,] MapVisibility; public string SaveName = "Link"; public float DrawPlayerOnTopPercentage; public bool FreezeWorldAroundPlayer; // save game data public string LoadedMap; public int SavePositionX; public int SavePositionY; public int SaveDirection; public int SaveSlot; private float _shakeCountX; private float _shakeCountY; private float _shakeSpeedX; private float _shakeSpeedY; private int _maxOffsetX; private int _maxOffsetY; public int DeathCount; public int MaxHearths = 3; public int CurrentHealth = 4 * 3; public int SwordLevel; public int ShieldLevel; public int StoneGrabberLevel; public bool HasMagnifyingLens; public bool DebugMode; // 0: marin // 1: mambo // 2: frog public int[] OcarinaSongs = new int[3]; public int SelectedOcarinaSong = 0; public static int CloakGreen = 0; public static int CloakBlue = 1; public static int CloakRed = 2; public int CloakType; public Color CloakColor => ItemDrawHelper.CloakColors[CloakType]; public bool GuardianAcornIsActive; public int GuardianAcornCount; public int GuardianAcornDamageCount; public bool PieceOfPowerIsActive; public int PieceOfPowerCount; public int PieceOfPowerDamageCount; private readonly Dictionary> _dialogPaths = new Dictionary>(); private DialogPath _currentDialogPath; private readonly Queue _dialogPathQueue = new Queue(); private RenderTarget2D _activeRenderTarget; private RenderTarget2D _inactiveRenderTarget1; private RenderTarget2D _inactiveRenderTarget2; private RenderTarget2D _shadowRenderTarget; private RenderTarget2D _shadowRenderTargetBlur; private RenderTarget2D _lightRenderTarget; // used for the blured tile layer; use usage of the render targets should probably be optimized public RenderTarget2D TempRT0; public RenderTarget2D TempRT1; public RenderTarget2D TempRT2; public float _scaleMultiplier; private int _currentDialogPathState; // 0: map music, 1: guardian acorn/piece of power, 2: maria singing private const int MusicChannels = 3; private int[] _musicArray = new int[MusicChannels]; // counters used to stop music private float[] _musicCounter = new float[MusicChannels]; public GameManager() { ResetMusic(); GameSystems.Add(typeof(MapTransitionSystem), new MapTransitionSystem(MapManager)); GameSystems.Add(typeof(GameOverSystem), new GameOverSystem()); GameSystems.Add(typeof(EndingSystem), new EndingSystem()); GameSystems.Add(typeof(MapShowSystem), new MapShowSystem()); } public void Load(ContentManager content) { ItemDrawHelper.Load(); InGameOverlay.Load(content); MapManager.Load(); ItemManager.Load(); // load the dialog paths DialogPathLoader.LoadScripts(Values.PathContentFolder + "scripts.zScript", _dialogPaths); } public void OnLoad() { InGameOverlay.OnLoad(); _currentDialogPath = null; _dialogPathQueue.Clear(); // this leads to the music not starting after switching from edit mode and reloading objects // not so sure if this is a problem or not ResetMusic(); foreach (var gameSystem in GameSystems) gameSystem.Value.OnLoad(); } public void UpdateGame() { InGameOverlay.Update(); UpdateSoundEffects(); UpdateMusic(); ItemDrawHelper.Update(); // update the dialogs; forced dialog update is used in sequences where the dialog should be updated but not the normal game // needs to come after the ingame overlay update because Game1.UpdateGame can be set to false by it if (Game1.UpdateGame || Game1.ForceDialogUpdate) { UpdateDialog(); Game1.ForceDialogUpdate = false; } if (Game1.UpdateGame && Game1.TotalGameTime > Game1.FreezeTime) { // update the game-systems foreach (var gameSystem in GameSystems) gameSystem.Value.Update(); if (Game1.UpdateGame) MapManager.Update(false); } else if (Game1.GameManager.InGameOverlay.UpdateCameraAndAnimation()) { MapManager.Update(true); MapManager.UpdateAnimation(); } // update screen shake if (Game1.UpdateGame) UpdateShake(); } public void DrawGame(SpriteBatch spriteBatch) { if (GameSettings.EnableShadows && MapManager.CurrentMap.UseShadows && !UseShockEffect) { // render the shadows RenderShadows(spriteBatch); Resources.BlurEffectH.Parameters["pixelX"].SetValue(1.0f / _shadowRenderTarget.Width); Resources.BlurEffectV.Parameters["pixelY"].SetValue(1.0f / _shadowRenderTarget.Height); Resources.BlurEffectH.Parameters["mult0"].SetValue(0.35f); Resources.BlurEffectH.Parameters["mult1"].SetValue(0.15f); Resources.BlurEffectV.Parameters["mult0"].SetValue(0.35f); Resources.BlurEffectV.Parameters["mult1"].SetValue(0.15f); // v blur Game1.Graphics.GraphicsDevice.SetRenderTarget(_shadowRenderTargetBlur); Game1.Graphics.GraphicsDevice.Clear(Color.Transparent); spriteBatch.Begin(SpriteSortMode.Immediate, null, SamplerState.AnisotropicClamp, null, null, Resources.BlurEffectV, null); spriteBatch.Draw(_shadowRenderTarget, Vector2.Zero, Color.White); spriteBatch.End(); // h blur Game1.Graphics.GraphicsDevice.SetRenderTarget(_shadowRenderTarget); Game1.Graphics.GraphicsDevice.Clear(Color.Transparent); spriteBatch.Begin(SpriteSortMode.Immediate, null, SamplerState.AnisotropicClamp, null, null, Resources.BlurEffectH, null); spriteBatch.Draw(_shadowRenderTargetBlur, Vector2.Zero, Color.White); spriteBatch.End(); MapManager.CurrentMap.Objects.ShadowTexture = _shadowRenderTarget; } ChangeRenderTarget(); Game1.Graphics.GraphicsDevice.Clear(Color.Black); // draw the map MapManager.Draw(spriteBatch); if (UseShockEffect) { ChangeRenderTarget(); var usedShader = MapManager.CurrentMap.UseLight ? Resources.ShockShader1 : Resources.ShockShader0; ObjectManager.SetSpriteShader(usedShader); spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointWrap, null, null, usedShader.Effect); spriteBatch.Draw(_inactiveRenderTarget1, Vector2.Zero, Color.White); spriteBatch.End(); } // @Move into the World class? if (MapManager.CurrentMap.UseLight && !UseShockEffect) { // draw the lights ChangeRenderTarget(); MapManager.DrawLight(spriteBatch); // combine the light with the game ChangeRenderTarget(); _lightRenderTarget = _inactiveRenderTarget1; Game1.Graphics.GraphicsDevice.Clear(Color.Black); Resources.LightShader.Parameters["sprLight"].SetValue(_lightRenderTarget); Resources.LightShader.Parameters["lightState"].SetValue(MapManager.CurrentMap.LightState); Resources.LightShader.Parameters["mode"].SetValue(0); Resources.LightShader.Parameters["width"].SetValue(_lightRenderTarget.Width); Resources.LightShader.Parameters["height"].SetValue(_lightRenderTarget.Height); spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.AnisotropicClamp, null, null, Resources.LightShader); spriteBatch.Draw(_inactiveRenderTarget2, Vector2.Zero, Color.White); spriteBatch.End(); } // update the game-systems foreach (var gameSystem in GameSystems) gameSystem.Value.Draw(spriteBatch); if (MapManager.CurrentMap.UseLight && !UseShockEffect && DrawPlayerOnTopPercentage > 0 && _lightRenderTarget != null) { Resources.LightShader.Parameters["sprLight"].SetValue(_lightRenderTarget); Resources.LightShader.Parameters["lightState"].SetValue(DrawPlayerOnTopPercentage); Resources.LightShader.Parameters["mode"].SetValue(1); Resources.LightShader.Parameters["width"].SetValue(_lightRenderTarget.Width); Resources.LightShader.Parameters["height"].SetValue(_lightRenderTarget.Height); spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointWrap, null, null, Resources.LightShader, MapManager.Camera.TransformMatrix); MapManager.ObjLink.DrawTransition(spriteBatch); spriteBatch.End(); } else if (DrawPlayerOnTopPercentage > 0) { spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.PointWrap, null, null, null, MapManager.Camera.TransformMatrix); MapManager.ObjLink.DrawTransition(spriteBatch); spriteBatch.End(); } // draw the output of the light and the dark shader passes ChangeRenderTarget(); Game1.Graphics.GraphicsDevice.SetRenderTarget(Game1.MainRenderTarget); spriteBatch.Begin(SpriteSortMode.Deferred, null, SamplerState.AnisotropicWrap); spriteBatch.Draw(_inactiveRenderTarget1, new Rectangle(0, 0, Game1.Graphics.PreferredBackBufferWidth, Game1.Graphics.PreferredBackBufferHeight), Color.White); // debug stuff MapManager.Camera.Draw(spriteBatch); spriteBatch.End(); } public void StartDialogPath(string dialogKey) { _dialogPathQueue.Enqueue(dialogKey); } public void AddFirstDialogPath(string dialogKey) { // @HACK var items = _dialogPathQueue.ToArray(); _dialogPathQueue.Clear(); _dialogPathQueue.Enqueue(dialogKey); foreach (var item in items) _dialogPathQueue.Enqueue(item); } // @TODO: this should probably be removed and replaced with StartDialogPath public void StartDialog(string dialogKey) { InGameOverlay.TextboxOverlay.StartDialog(Game1.LanguageManager.GetString(dialogKey, "error")); } /// /// @HACK: used by the map overlay to completly run a dialog path; this does only work for single dialogs /// the problem is that the current game dialog should be unaffected by the dialogs run by the map overlay /// /// public void RunDialog(string dialogKey) { // look if a dialog path exists for the key DialogPath dialogPath = null; if (dialogKey != null && _dialogPaths.ContainsKey(dialogKey)) { var paths = _dialogPaths[dialogKey]; for (var i = 0; i < paths.Count; i++) { if (SaveManager.GetString(paths[i].VariableKey) == null && paths[i].Condition == "0" || SaveManager.GetString(paths[i].VariableKey) == paths[i].Condition) { dialogPath = paths[i]; break; } } } // try to start a new dialog box if (dialogPath == null && !Game1.GameManager.InGameOverlay.TextboxOverlay.IsOpen) { // directly start a dialog string stateString = null; if (dialogKey != null) stateString = SaveManager.GetString(dialogKey); InGameOverlay.TextboxOverlay.StartDialog( Game1.LanguageManager.GetString(dialogKey + (stateString != null ? "_" + stateString : ""), "error")); } while (dialogPath != null) { var breakLoop = true; var dialogPathState = 0; // execute the current dialog path if (dialogPath != null) { while (dialogPath.Action.Count > dialogPathState && dialogPath.Action[dialogPathState].Execute()) { dialogPathState++; // init the next dialog action if (dialogPath.Action.Count > dialogPathState) dialogPath.Action[dialogPathState].Init(); } // do not stop executing at a empty dialog path if (dialogPath.Action.Count == 0) breakLoop = false; // finished current path? if (dialogPath.Action.Count <= dialogPathState) { breakLoop = false; dialogPath = null; } } // exit the loop if there is nothing to do if (breakLoop) break; } } public bool DialogIsRunning() { return _currentDialogPath != null || _dialogPathQueue.Count > 0; } public bool FinalDialogAction() { return false; } private void UpdateDialog() { while (_currentDialogPath != null || _dialogPathQueue != null) { var breakLoop = true; // start a new dialog path? if (_dialogPathQueue.Count > 0 && _currentDialogPath == null) { _currentDialogPath = DequeueDialogPath(); _currentDialogPathState = 0; if (_currentDialogPath != null && _currentDialogPath.Action.Count > _currentDialogPathState) _currentDialogPath.Action[_currentDialogPathState].Init(); } // execute the current dialog path if (_currentDialogPath != null) { while (_currentDialogPath.Action.Count > _currentDialogPathState && _currentDialogPath.Action[_currentDialogPathState].Execute()) { _currentDialogPathState++; // init the next dialog action if (_currentDialogPath.Action.Count > _currentDialogPathState) _currentDialogPath.Action[_currentDialogPathState].Init(); } // do not stop executing at a empty dialog path if (_currentDialogPath.Action.Count == 0) breakLoop = false; // finished current path? if (_currentDialogPath.Action.Count <= _currentDialogPathState) { breakLoop = false; _currentDialogPath = null; } } // exit the loop if there is nothing to do if (breakLoop) break; } } private DialogPath DequeueDialogPath() { Game1.GameManager.InGameOverlay.TextboxOverlay.UpdateObjects = false; var dialogKey = _dialogPathQueue.Peek(); // look if a dialog path exists for the key if (dialogKey != null && _dialogPaths.ContainsKey(dialogKey)) { var paths = _dialogPaths[dialogKey]; for (var i = 0; i < paths.Count; i++) { if (SaveManager.GetString(paths[i].VariableKey) == null && paths[i].Condition == "0" || SaveManager.GetString(paths[i].VariableKey) == paths[i].Condition) { _dialogPathQueue.Dequeue(); return paths[i]; } } } // try to start a new dialog box if (!Game1.GameManager.InGameOverlay.TextboxOverlay.IsOpen) { _dialogPathQueue.Dequeue(); // directly start a dialog string stateString = null; if (dialogKey != null) stateString = SaveManager.GetString(dialogKey); InGameOverlay.TextboxOverlay.StartDialog( Game1.LanguageManager.GetString(dialogKey + (stateString != null ? "_" + stateString : ""), "error")); } return null; } public void RenderShadows(SpriteBatch spriteBatch) { Game1.Graphics.GraphicsDevice.SetRenderTarget(_shadowRenderTarget); Game1.Graphics.GraphicsDevice.Clear(Color.Transparent); Game1.Graphics.GraphicsDevice.DepthStencilState = DepthStencilState.Default; Game1.Graphics.GraphicsDevice.SamplerStates[0] = SamplerState.AnisotropicClamp; Game1.Graphics.GraphicsDevice.BlendState = BlendState.NonPremultiplied; Game1.Graphics.GraphicsDevice.RasterizerState = RasterizerState.CullNone; MapManager.CurrentMap.Objects.DrawShadow(spriteBatch); } public void ChangeRenderTarget() { var tempActiveRt = _activeRenderTarget; _activeRenderTarget = _inactiveRenderTarget2; _inactiveRenderTarget2 = _inactiveRenderTarget1; _inactiveRenderTarget1 = tempActiveRt; SetActiveRenderTarget(); } public void SetActiveRenderTarget() { Game1.Graphics.GraphicsDevice.SetRenderTarget(_activeRenderTarget); } public RenderTarget2D GetLastRenderTarget() { return _inactiveRenderTarget1; } public void DrawTop(SpriteBatch spriteBatch) { // draw the inventory InGameOverlay.Draw(spriteBatch); } public void DrawRenderTarget(SpriteBatch spriteBatch) { // draw the rt stuff of the game ui InGameOverlay.DrawRenderTarget(spriteBatch); } public void SetGameScale(float scale) { _scaleMultiplier = MathF.Ceiling(scale) / scale; UpdateRenderTargets(); } public void OnResize() { InGameOverlay.ResolutionChanged(); Game1.RenderWidth = (int)(Game1.WindowWidth * _scaleMultiplier); Game1.RenderHeight = (int)(Game1.WindowHeight * _scaleMultiplier); MapManager.Camera.SetBounds(Game1.RenderWidth, Game1.RenderHeight); // center the player MapManager.Camera.ForceUpdate(MapManager.GetCameraTarget()); UpdateRenderTargets(); } public void OnResizeEnd() { InGameOverlay.UpdateRenderTarget(); UpdateRenderTargets(); } public void UpdateRenderTargets() { if ((CurrentRenderWidth == Game1.RenderWidth && CurrentRenderHeight == Game1.RenderHeight && CurrentRenderScale == MapManager.Camera.Scale) || Game1.RenderWidth == 0 || Game1.RenderHeight == 0) return; CurrentRenderWidth = Game1.RenderWidth; CurrentRenderHeight = Game1.RenderHeight; CurrentRenderScale = MapManager.Camera.Scale; _activeRenderTarget?.Dispose(); _inactiveRenderTarget1?.Dispose(); _inactiveRenderTarget2?.Dispose(); _shadowRenderTarget?.Dispose(); _shadowRenderTargetBlur?.Dispose(); _activeRenderTarget = new RenderTarget2D(Game1.Graphics.GraphicsDevice, Game1.RenderWidth, Game1.RenderHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); _inactiveRenderTarget1 = new RenderTarget2D(Game1.Graphics.GraphicsDevice, Game1.RenderWidth, Game1.RenderHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); _inactiveRenderTarget2 = new RenderTarget2D(Game1.Graphics.GraphicsDevice, Game1.RenderWidth, Game1.RenderHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); // shadow render targets var shadowScale = MathHelper.Clamp(MapManager.Camera.Scale / 2, 1, 10); var shadowRtWidth = (int)(Game1.RenderWidth / shadowScale); var shadowRtHeight = (int)(Game1.RenderHeight / shadowScale); _shadowRenderTarget = new RenderTarget2D(Game1.Graphics.GraphicsDevice, shadowRtWidth, shadowRtHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); _shadowRenderTargetBlur = new RenderTarget2D(Game1.Graphics.GraphicsDevice, shadowRtWidth, shadowRtHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); // temp render targets TempRT0?.Dispose(); TempRT1?.Dispose(); TempRT2?.Dispose(); TempRT0 = new RenderTarget2D(Game1.Graphics.GraphicsDevice, BlurRenderTargetWidth, BlurRenderTargetHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); TempRT1 = new RenderTarget2D(Game1.Graphics.GraphicsDevice, BlurRenderTargetWidth, BlurRenderTargetHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); TempRT2 = new RenderTarget2D(Game1.Graphics.GraphicsDevice, SideBlurRenderTargetWidth, SideBlurRenderTargetHeight, false, SurfaceFormat.Color, DepthFormat.None, 0, RenderTargetUsage.PreserveContents); } public void HealPlayer(int hearts) { CurrentHealth += hearts; if (CurrentHealth > MaxHearths * 4) CurrentHealth = MaxHearths * 4; } public void InflictDamage(int damage) { if (DebugMode) return; if (CloakType == CloakBlue) damage = (int)MathF.Ceiling(damage / 2f); if (GuardianAcornIsActive) damage = (int)MathF.Ceiling(damage / 2f); CurrentHealth -= damage; if (CurrentHealth < 0) CurrentHealth = 0; // reset count for the guardian acorn GuardianAcornCount = 0; if (GuardianAcornIsActive) { GuardianAcornDamageCount++; if (GuardianAcornDamageCount >= 3) StopGuardianAcorn(); } // piece of power if (PieceOfPowerIsActive) { PieceOfPowerDamageCount++; if (PieceOfPowerDamageCount >= 3) StopPieceOfPower(); } } public void InitGuardianAcorn() { if (PieceOfPowerIsActive) StopPieceOfPower(); GuardianAcornIsActive = true; GuardianAcornDamageCount = 0; StartPieceOfPowerMusic(); } public void StopGuardianAcorn() { GuardianAcornIsActive = false; Game1.GameManager.SetMusic(-1, 1, false); } public void InitPieceOfPower() { if (GuardianAcornIsActive) StopGuardianAcorn(); PieceOfPowerIsActive = true; PieceOfPowerDamageCount = 0; StartPieceOfPowerMusic(); } public void StartPieceOfPowerMusic() { // start playing music Game1.GameManager.SetMusic(72, 1); } public void StopPieceOfPower() { PieceOfPowerIsActive = false; Game1.GameManager.SetMusic(-1, 1, false); } public void ResetMusic() { for (var i = 0; i < MusicChannels; i++) { _musicArray[i] = -1; _musicCounter[i] = 0; } } public void UpdateMusic() { for (var i = 0; i < MusicChannels; i++) { if (_musicCounter[i] == 0) continue; _musicCounter[i] -= Game1.DeltaTime; // finished playing the music? if (_musicCounter[i] <= 0) { _musicCounter[i] = 0; _musicArray[i] = -1; PlayMusic(); } } } public void StopMusic(bool reset = false) { if (reset) ResetMusic(); Game1.GbsPlayer.Stop(); } public void StopMusic(int time, int priority) { _musicCounter[priority] = time; } public void PlayMusic(bool startPlaying = true) { for (var i = MusicChannels - 1; i >= 0; i--) { if (_musicArray[i] >= 0) { var songNumber = (byte)_musicArray[i]; if (Game1.GbsPlayer.CurrentTrack != songNumber) Game1.GbsPlayer.StartTrack(songNumber); if (startPlaying) Game1.GbsPlayer.Play(); return; } } // no music is playing? Game1.GbsPlayer.Stop(); } public void SetMusic(int songNr, int priority, bool startPlaying = true) { // @HACK: don't restart the overworld track if the version with the intro was already started; // make sure to not restart the music while showing the overworld in the final sequence if ((songNr == 4 && _musicArray[priority] == 48) || (priority != 2 && _musicArray[2] == 62)) return; _musicArray[priority] = songNr; PlayMusic(startPlaying); } public int GetCurrentMusic() { for (var i = _musicArray.Length - 1; i >= 0; i--) if (_musicArray[i] >= 0) return _musicArray[i]; return -1; } public void UpdateSoundEffects() { var lowerVolume = false; // we use ToList to be able to remove entries in the foreach loop foreach (var soundEffect in CurrentSoundEffects.ToList()) { if (CurrentSoundEffects[soundEffect.Key].LowerMusicVolume) lowerVolume = true; // update the volume of the sound effects to match the current settings soundEffect.Value.Instance.Volume = CurrentSoundEffects[soundEffect.Key].Volume * GameSettings.EffectVolume / 100 * Values.SoundEffectVolumeMult; soundEffect.Value.Instance.IsLooped = false; if (soundEffect.Value.EndTime != 0 && soundEffect.Value.EndTime < Game1.TotalGameTime) soundEffect.Value.Instance.Stop(); // finished playing? if (soundEffect.Value.Instance.State == SoundState.Stopped) CurrentSoundEffects.Remove(soundEffect.Key); } if (lowerVolume) Game1.GbsPlayer.SetVolumeMultiplier(0.35f); } public void PauseSoundEffects() { foreach (var soundEffect in CurrentSoundEffects) if (soundEffect.Value.Instance.State == SoundState.Playing) soundEffect.Value.Instance.Pause(); } public void ContinueSoundEffects() { foreach (var soundEffect in CurrentSoundEffects) if (soundEffect.Value.Instance.State == SoundState.Paused) soundEffect.Value.Instance.Resume(); } public void PlaySoundEffect(string name, bool restart, Vector2 position, float range = 256) { var playerDistance = MapManager.ObjLink.EntityPosition.Position - position; var volume = 1 - playerDistance.Length() / range; if (volume > 0) PlaySoundEffect(name, restart, volume); } public void PlaySoundEffect(string name, bool restart = true, float volume = 1, float pitch = 0, bool lowerMusicVolume = false, float playtime = 0) { CurrentSoundEffects.TryGetValue(name, out var entry); // if the same sound is playing it will be stopped and replaced with the new instance if (restart && entry!= null && entry.Instance != null) { entry.Instance.Stop(); CurrentSoundEffects.Remove(name); } if (!restart && entry != null && entry.Instance != null) { entry.Volume = volume; if (playtime != 0) entry.EndTime = Game1.TotalGameTime + playtime; entry.Instance.Volume = volume * GameSettings.EffectVolume / 100f * Values.SoundEffectVolumeMult; entry.Instance.Pitch = pitch; return; } entry = new PlayingSoundEffect() { Volume = volume, LowerMusicVolume = lowerMusicVolume }; entry.Instance = Resources.SoundEffects[name].CreateInstance(); // the volume of the sound effects is higher than the music; so scale effect volume a little down entry.Instance.Volume = volume * GameSettings.EffectVolume / 100f * Values.SoundEffectVolumeMult; entry.Instance.Pitch = pitch; if (playtime != 0) { entry.Instance.IsLooped = true; entry.EndTime = Game1.TotalGameTime + playtime; } entry.Instance.Play(); CurrentSoundEffects.Add(name, entry); } public void StopSoundEffect(string name) { if (CurrentSoundEffects.TryGetValue(name, out var entry)) entry.Instance.Stop(); } public bool IsPlaying(string name) { if (CurrentSoundEffects.TryGetValue(name, out var entry)) return entry.Instance.State == SoundState.Playing; return false; } public void ShakeScreenContinue(int time, int maxX, int maxY, float shakeSpeedX, float shakeSpeedY) { var periodsX = (_shakeCountX / 100f * _shakeSpeedX) % (MathF.PI * 2); _shakeCountX = time; if (_shakeSpeedX > 0) _shakeCountX += periodsX / _shakeSpeedX * 100f; _shakeCountY = time; _maxOffsetX = maxX; _maxOffsetY = maxY; _shakeSpeedX = shakeSpeedX; _shakeSpeedY = shakeSpeedY; } public void ShakeScreen(int time, int maxX, int maxY, float shakeSpeedX, float shakeSpeedY, int startDirX = 1, int startDirY = 1) { _shakeCountX = time; _shakeCountY = time; _maxOffsetX = maxX; _maxOffsetY = maxY; _shakeSpeedX = shakeSpeedX; _shakeSpeedY = shakeSpeedY; if (_shakeSpeedX > 0) { var periodsX = MathF.Round((time / 100f * _shakeSpeedX) / MathF.PI); if ((startDirX == -1 && periodsX % 2 == 0) || (startDirX == 1 && periodsX % 2 == 1)) periodsX += 1; _shakeCountX = (periodsX * MathF.PI) / _shakeSpeedX * 100f; } if (_shakeSpeedY > 0) { var periodsY = MathF.Round((time / 100f * _shakeSpeedY) / MathF.PI); if ((startDirY == 1 && periodsY % 2 == 0) || (startDirY == -1 && periodsY % 2 == 1)) periodsY += 1; _shakeCountY = (periodsY * MathF.PI) / _shakeSpeedY * 100f; } } public void UpdateShake() { if (_shakeCountX > 0) { _shakeCountX -= Game1.DeltaTime; MapManager.Camera.ShakeOffsetX = (float)Math.Sin(_shakeCountX / 100f * _shakeSpeedX) * _maxOffsetX; } if (_shakeCountY > 0) { _shakeCountY -= Game1.DeltaTime; MapManager.Camera.ShakeOffsetY = (float)Math.Sin(_shakeCountY / 100f * _shakeSpeedY) * _maxOffsetY; } } public bool LoadMiniMap(string mapName) { // already loaded the levels? if (DungeonMaps.ContainsKey(mapName)) return true; // load the mini map var fileName = Values.PathMinimapFolder + mapName + ".txt"; var dungeonMap = SaveLoadMap.LoadMiniMap(fileName); if (dungeonMap == null) return false; DungeonMaps.Add(mapName, dungeonMap); return true; } public void SetDungeon(string dungeonName, int dungeonLevel) { // TODO_Opt preload all the minimaps var level = 0; while (true) { if (!LoadMiniMap(dungeonName + "_" + level)) break; LoadMiniMap(dungeonName + "_" + level + "_alt"); level++; } MapManager.NextMap.DungeonMode = true; MapManager.NextMap.LocationName = dungeonName; MapManager.NextMap.LocationFullName = dungeonName + "_" + dungeonLevel; } public void DungeonUpdatePlayerPosition(Point position) { // updated map discovery state if (MapManager.CurrentMap.LocationFullName != null && DungeonMaps.ContainsKey(MapManager.CurrentMap.LocationFullName) && position.X >= 0 && position.Y >= 0 && position.X < DungeonMaps[MapManager.CurrentMap.LocationFullName].Tiles.GetLength(0) && position.Y < DungeonMaps[MapManager.CurrentMap.LocationFullName].Tiles.GetLength(1)) DungeonMaps[MapManager.CurrentMap.LocationFullName].Tiles[position.X, position.Y].DiscoveryState = true; PlayerDungeonPosition = position; } public void SetMapPosition(Point position) { if (MapVisibility == null || 0 > position.X || position.X >= MapVisibility.GetLength(0) || 0 > position.Y || position.Y >= MapVisibility.GetLength(1)) return; MapVisibility[position.X, position.Y] = true; PlayerMapPosition = position; } public GameItemCollected GetItem(string itemId) { for (var i = 0; i < Equipment.Length; i++) { if (Equipment[i] != null && Equipment[i].Name == itemId && (string.IsNullOrEmpty(Equipment[i].LocationBounding) || Equipment[i].LocationBounding == MapManager.CurrentMap.LocationName)) return Equipment[i]; } for (var i = 0; i < CollectedItems.Count; i++) { // player has item if (CollectedItems[i].Name == itemId && (string.IsNullOrEmpty(CollectedItems[i].LocationBounding) || CollectedItems[i].LocationBounding == MapManager.CurrentMap.LocationName)) return CollectedItems[i]; } return null; } public void CollectItem(GameItemCollected itemCollected, int equipmentSlot = -1) { if (itemCollected.LocationBounding == "") itemCollected.LocationBounding = null; var item = Game1.GameManager.ItemManager[itemCollected.Name]; // the base item has the max count information var baseItem = Game1.GameManager.ItemManager[item.Name]; // make sure to replace then name // this is used for items that have different variations like the normal powder or powderPD with dialog itemCollected.Name = item.Name; // add the arrow count to the bow and remove the arrows if (itemCollected.Name == "bow") { var arrow = Game1.GameManager.GetItem("arrow"); if (arrow != null) { itemCollected.Count += arrow.Count; Game1.GameManager.RemoveItem("arrow", arrow.Count); } } // if we have the bow collected change the type to bow if (itemCollected.Name == "arrow") { var bow = Game1.GameManager.GetItem("bow"); if (bow != null) { itemCollected.Name = "bow"; item = Game1.GameManager.ItemManager[itemCollected.Name]; baseItem = Game1.GameManager.ItemManager[item.Name]; } } if (itemCollected.Name == "cloakBlue") CloakType = CloakBlue; else if (itemCollected.Name == "cloakRed") CloakType = CloakRed; // unlock the ocarina songs if (itemCollected.Name == "ocarina_maria") { OcarinaSongs[0] = 1; if (SelectedOcarinaSong == -1) SelectedOcarinaSong = 0; } if (itemCollected.Name == "ocarina_manbo") { OcarinaSongs[1] = 1; if (SelectedOcarinaSong == -1) SelectedOcarinaSong = 1; } if (itemCollected.Name == "ocarina_frog") { OcarinaSongs[2] = 1; if (SelectedOcarinaSong == -1) SelectedOcarinaSong = 2; } // magnifying lens collected if (itemCollected.Name == "trade13") HasMagnifyingLens = true; if (baseItem.Equipable) { var maxCount = baseItem.MaxCount; if (itemCollected.Name == "sword1") SwordLevel = 1; else if (itemCollected.Name == "sword2") SwordLevel = 2; if (itemCollected.Name == "shield" || itemCollected.Name == "mirrorShield") ShieldLevel = item.Level; if (itemCollected.Name == "stonelifter" || itemCollected.Name == "stonelifter2") StoneGrabberLevel = item.Level; // powder, bomb or arrow? // check if the inventory was upgraded or not if (item.Name == "powder" && SaveManager.GetString("upgradePowder") == "1") maxCount += 20; if (item.Name == "bomb" && SaveManager.GetString("upgradeBomb") == "1") maxCount += 30; if (item.Name == "bow" && SaveManager.GetString("upgradeBow") == "1") maxCount += 30; // search if the player already owns the equipment for (var i = 0; i < Equipment.Length; i++) { if (Equipment[i] != null && Equipment[i].Name == item.Name) { Equipment[i].Count += itemCollected.Count; if (maxCount > 0 && Equipment[i].Count > maxCount) Equipment[i].Count = maxCount; return; } } if (maxCount > 0 && itemCollected.Count > maxCount) itemCollected.Count = maxCount; // requested equipment slot is empty? if (0 <= equipmentSlot && equipmentSlot < Equipment.Length && Equipment[equipmentSlot] == null) { SetEquipment(equipmentSlot, itemCollected); return; } // add item to the collected item list var start = equipmentSlot < 0 ? 4 : 0; for (var i = start; i < Equipment.Length; i++) { if (Equipment[i] != null) continue; SetEquipment(i, itemCollected); return; } } else { // search if the player already owns the item var found = false; for (var i = 0; i < CollectedItems.Count; i++) { if (CollectedItems[i].Name == item.Name && CollectedItems[i].LocationBounding == itemCollected.LocationBounding) { CollectedItems[i].Count += itemCollected.Count; if (baseItem.MaxCount > 0 && CollectedItems[i].Count > baseItem.MaxCount) CollectedItems[i].Count = baseItem.MaxCount; found = true; break; } } if (!found) { // add new item CollectedItems.Add(itemCollected); } if (item.Name == "heartMeter") { var heart = Game1.GameManager.GetItem("heartMeter"); // expand hearts? while (heart?.Count >= 4) { heart.Count -= 4; Game1.GameManager.MaxHearths++; Game1.GameManager.HealPlayer(99); ItemDrawHelper.EnableHeartAnimationSound(); } } else if (item.Name == "flippers") MapManager.ObjLink.HasFlippers = true; } } public int GetEquipmentSlot(string itemName) { for (var i = 0; i < Equipment.Length; i++) { if (Equipment[i] != null && Equipment[i].Name == itemName) return i; } return 0; } public bool RemoveItem(string itemName, int count) { // equipment for (var i = 0; i < Equipment.Length; i++) { if (Equipment[i] == null || Equipment[i].Name != itemName || Game1.GameManager.ItemManager[Equipment[i].Name].Level == 0 && Equipment[i].Count < count) continue; Equipment[i].Count -= count; // remove the item from the inventory if the player can only have 1 of it // bombs, powder, etc will stay in the inventory if (Equipment[i].Count <= 0 && Game1.GameManager.ItemManager[Equipment[i].Name].MaxCount == 1) Equipment[i] = null; // remove the item? // not sure what to do here // in the original powder gets removed but bombs not; what happens to arrows I do not know if (itemName == "powder" && Equipment[i] != null && Equipment[i].Count == 0) Equipment[i] = null; return true; } // items for (var i = 0; i < CollectedItems.Count; i++) { if (CollectedItems[i] == null || CollectedItems[i].Name != itemName || Game1.GameManager.ItemManager[CollectedItems[i].Name].Level == 0 && CollectedItems[i].Count < count || !string.IsNullOrEmpty(CollectedItems[i].LocationBounding) && CollectedItems[i].LocationBounding != MapManager.CurrentMap.LocationName) continue; CollectedItems[i].Count -= count; // remove the item? if (Game1.GameManager.ItemManager[CollectedItems[i].Name].Level != 0 || CollectedItems[i].Count == 0) { CollectedItems.RemoveAt(i); if (itemName == "flippers") MapManager.ObjLink.HasFlippers = false; } return true; } return false; } public void ChangeItem(int oldSlot, int newSlot) { var tempAcc = Equipment[oldSlot]; SetEquipment(oldSlot, Equipment[newSlot]); SetEquipment(newSlot, tempAcc); } public void SetEquipment(int index, GameItemCollected item) { Equipment[index] = item; UpdateEquipment(); } private void UpdateEquipment() { // check if link is carrying a shield MapManager.ObjLink.CarrySword = false; MapManager.ObjLink.CarryShield = false; for (var i = 0; i < Values.HandItemSlots; i++) { if (Equipment[i]?.Name == "sword1" || Equipment[i]?.Name == "sword2") MapManager.ObjLink.CarrySword = true; else if (Equipment[i]?.Name == "shield" || Equipment[i]?.Name == "mirrorShield") MapManager.ObjLink.CarryShield = true; } } public void StartNewGame(int slot, string slotName) { ResetStuff(); SaveName = slotName; Equipment = new GameItemCollected[EquipmentSlots]; UpdateEquipment(); SaveManager.Reset(); // set up values // debug fill the inventory if (SaveName == "DebugMode") { DebugMode = true; CollectItem(new GameItemCollected("sword1") { Count = 1 }, 0); CollectItem(new GameItemCollected("shield") { Count = 1 }, 0); CollectItem(new GameItemCollected("feather") { Count = 1 }, 0); CollectItem(new GameItemCollected("stonelifter") { Count = 1 }, 0); CollectItem(new GameItemCollected("pegasusBoots") { Count = 1 }, 0); CollectItem(new GameItemCollected("shovel") { Count = 1 }, 0); CollectItem(new GameItemCollected("flippers") { Count = 1 }, 0); CollectItem(new GameItemCollected("magicRod") { Count = 1 }, 0); CollectItem(new GameItemCollected("hookshot") { Count = 1 }, 0); CollectItem(new GameItemCollected("boomerang") { Count = 1 }, 0); CollectItem(new GameItemCollected("powder") { Count = 999 }, 0); CollectItem(new GameItemCollected("bomb") { Count = 999 }, 0); CollectItem(new GameItemCollected("bow") { Count = 999 }, 0); CollectItem(new GameItemCollected("ocarina") { Count = 1 }, 0); } CollectedItems.Clear(); DungeonMaps.Clear(); ItemDrawHelper.Init(); SwordLevel = 0; CloakType = CloakGreen; SelectedOcarinaSong = 0; OcarinaSongs[0] = 0; OcarinaSongs[1] = 0; OcarinaSongs[2] = 0; MaxHearths = 3; CurrentHealth = MaxHearths * 4; PlayerMapPosition = null; MapVisibility = new bool[16, 16]; SaveSlot = slot; // randomize the directions of the egg Game1.GameManager.SaveManager.SetString("eggDirections", Game1.RandomNumber.Next(0, 4).ToString()); // create empty map MapManager.CurrentMap = Map.Map.CreateEmptyMap(); MapManager.ObjLink.Map = MapManager.CurrentMap; MapManager.ObjLink.MapTransitionStart = MapManager.ObjLink.EntityPosition.Position; MapManager.ObjLink.MapTransitionEnd = MapManager.ObjLink.EntityPosition.Position; MapManager.ObjLink.EntityPosition.Z = 0; MapManager.ObjLink.TransitionOutWalking = false; MapManager.ObjLink.TransitionInWalking = false; MapManager.ObjLink.InitGame(); MapManager.Camera.ForceUpdate(MapManager.GetCameraTargetLink()); // load the map MapManager.ObjLink.SetNextMapPosition(new Vector2(MapManager.ObjLink.PosX, MapManager.ObjLink.PosY)); ((MapTransitionSystem)GameSystems[typeof(MapTransitionSystem)]).LoadMapFromFile("house1.map", true, true, Values.MapFirstTransitionColor, false); ((MapTransitionSystem)GameSystems[typeof(MapTransitionSystem)]).AdditionalBlackScreenDelay = Values.GameSaveBlackScreen; } public void LoadSaveFile(int slot) { ResetStuff(); MapManager.ObjLink.InitGame(); SaveGameSaveLoad.LoadSaveFile(this, slot); ItemDrawHelper.Init(); UpdateEquipment(); // create empty map MapManager.CurrentMap = Map.Map.CreateEmptyMap(); MapManager.CurrentMap.Objects.SpawnObject(MapManager.ObjLink); MapManager.ObjLink.Map = MapManager.CurrentMap; MapManager.ObjLink.SetWalkingDirection(SaveDirection); MapManager.Camera.ForceUpdate(MapManager.GetCameraTargetLink()); MapManager.ObjLink.MapTransitionStart = MapManager.ObjLink.EntityPosition.Position; MapManager.ObjLink.MapTransitionEnd = MapManager.ObjLink.EntityPosition.Position; MapManager.ObjLink.DirectionEntry = SaveDirection; MapManager.ObjLink.EntityPosition.Z = 0; MapManager.ObjLink.TransitionOutWalking = false; MapManager.ObjLink.TransitionInWalking = false; // load the map var transitionSystem = ((MapTransitionSystem)GameSystems[typeof(MapTransitionSystem)]); MapManager.ObjLink.SetNextMapPosition(new Vector2(SavePositionX, SavePositionY)); transitionSystem.LoadMapFromFile(LoadedMap, true, true, Values.MapFirstTransitionColor, false); transitionSystem.AdditionalBlackScreenDelay = Values.GameSaveBlackScreen; } public void RespawnPlayer() { if (Game1.GameManager.SaveManager.HistoryEnabled) { Game1.GameManager.SaveManager.RevertHistory(); Game1.GameManager.SaveManager.DisableHistory(); } ResetStuff(); // create empty map MapManager.CurrentMap = Map.Map.CreateEmptyMap(); MapManager.CurrentMap.Objects.SpawnObject(MapManager.ObjLink); MapManager.ObjLink.Map = MapManager.CurrentMap; MapManager.Camera.ForceUpdate(MapManager.GetCameraTargetLink()); // respawn the player MapManager.ObjLink.Respawn(); ItemDrawHelper.Init(); MapManager.ObjLink.MapTransitionStart = MapManager.ObjLink.EntityPosition.Position; MapManager.ObjLink.MapTransitionEnd = MapManager.ObjLink.EntityPosition.Position; MapManager.ObjLink.TransitionOutWalking = false; MapManager.ObjLink.TransitionInWalking = false; // respawn looking down MapManager.ObjLink.DirectionEntry = 3; MapManager.ObjLink.SetWalkingDirection(3); MapManager.ObjLink.SetNextMapPosition(MapManager.ObjLink.SavePosition); // load the map var transitionSystem = ((MapTransitionSystem)GameSystems[typeof(MapTransitionSystem)]); transitionSystem.LoadMapFromFile(MapManager.ObjLink.SaveMap, true, true, Values.MapFirstTransitionColor, false); transitionSystem.AdditionalBlackScreenDelay = Values.GameRespawnBlackScreen; } private void ResetStuff() { SaveGameSaveLoad.ClearSaveState(); Game1.GameManager.SaveManager.DisableHistory(); // this was done to support DialogActionCooldown working after loading a new save Game1.TotalGameTime = 0; Game1.TotalGameTimeLast = 0; Game1.FreezeTime = 0; _shakeCountX = 0; _shakeCountY = 0; } } }