LADXHD/InGame/Things/GameManager.cs
2023-12-14 17:21:22 -05:00

1501 lines
57 KiB
C#

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<GameItemCollected> CollectedItems = new List<GameItemCollected>();
// sound effects that are currently playing
private Dictionary<string, PlayingSoundEffect> CurrentSoundEffects = new Dictionary<string, PlayingSoundEffect>();
// dungeon maps
public Dictionary<string, MiniMap> DungeonMaps = new Dictionary<string, MiniMap>();
public Dictionary<Type, GameSystem> GameSystems = new Dictionary<Type, GameSystem>();
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<string, List<DialogPath>> _dialogPaths = new Dictionary<string, List<DialogPath>>();
private DialogPath _currentDialogPath;
private readonly Queue<string> _dialogPathQueue = new Queue<string>();
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"));
}
/// <summary>
/// @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
/// </summary>
/// <param name="dialogKey"></param>
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;
}
}
}