using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using StardewModdingAPI; using StardewModdingAPI.Enums; using StardewModdingAPI.Events; using StardewValley; using StardewValley.Menus; using StardewValley.Tools; using System; using System.Collections.Generic; using System.IO; using System.Media; using System.Threading; using System.Threading.Tasks; using System.Timers; using UIInfoSuite.Extensions; namespace UIInfoSuite.UIElements { class ExperienceBar : IDisposable { public interface LevelExtenderEvents { event EventHandler OnXPChanged; } private const int MaxBarWidth = 175; private int[] _currentExperience = new int[5]; private int[] _currentLevelExtenderExperience = new int[5]; private readonly List _experiencePointDisplays = new List(); private readonly TimeSpan _levelUpPauseTime = TimeSpan.FromSeconds(2); private readonly Color _iconColor = Color.White; private Color _experienceFillColor = Color.Blue; private Rectangle _experienceIconPosition = new Rectangle(10, 428, 10, 10); private Item _previousItem = null; private bool _experienceBarShouldBeVisible = false; private bool _shouldDrawLevelUp = false; private System.Timers.Timer _timeToDisappear = new System.Timers.Timer(); private readonly TimeSpan _timeBeforeExperienceBarFades = TimeSpan.FromSeconds(8); //private SoundEffectInstance _soundEffect; private Rectangle _levelUpIconRectangle = new Rectangle(120, 428, 10, 10); private bool _allowExperienceBarToFadeOut = true; private bool _showExperienceGain = true; private bool _showLevelUpAnimation = true; private bool _showExperienceBar = true; private readonly IModHelper _helper; private SoundPlayer _player; private LevelExtenderInterface _levelExtenderAPI; private int _currentSkillLevel = 0; private int _experienceRequiredToLevel = -1; private int _experienceFromPreviousLevels = -1; private int _experienceEarnedThisLevel = -1; public ExperienceBar(IModHelper helper) { _helper = helper; String path = string.Empty; try { path = Path.Combine(_helper.DirectoryPath, "LevelUp.wav"); _player = new SoundPlayer(path); //path = path.Replace(Environment.CurrentDirectory, ""); //path = path.TrimStart(Path.DirectorySeparatorChar); //_soundEffect = SoundEffect.FromStream(TitleContainer.OpenStream(path)).CreateInstance(); //_soundEffect.Volume = 1f; } catch (Exception ex) { ModEntry.MonitorObject.Log("Error loading sound file from " + path + ": " + ex.Message + Environment.NewLine + ex.StackTrace, LogLevel.Error); } _timeToDisappear.Elapsed += StopTimerAndFadeBarOut; helper.Events.Display.RenderingHud += OnRenderingHud; helper.Events.Player.Warped += OnWarped_RemoveAllExperiencePointDisplays; var something = _helper.ModRegistry.GetApi("DevinLematty.LevelExtender"); try { _levelExtenderAPI = _helper.ModRegistry.GetApi("DevinLematty.LevelExtender"); } catch (Exception ex) { int j = 4; } int f = 3; //if (something != null) //{ // try // { // var methods = something.GetType().GetMethods(); // var currentXPMethod = something.GetType().GetMethod("currentXP"); // foreach (var method in methods) // { // } // } // catch (Exception ex) // { // int f = 3; // } //} } private void LoadModApis(object sender, EventArgs e) { } public void Dispose() { _helper.Events.Player.LevelChanged -= OnLevelChanged; _helper.Events.Display.RenderingHud -= OnRenderingHud; _helper.Events.Player.Warped -= OnWarped_RemoveAllExperiencePointDisplays; _helper.Events.GameLoop.UpdateTicked -= OnUpdateTicked_DetermineIfExperienceHasBeenGained; _timeToDisappear.Elapsed -= StopTimerAndFadeBarOut; _timeToDisappear.Stop(); _timeToDisappear.Dispose(); _timeToDisappear = null; } public void ToggleLevelUpAnimation(bool showLevelUpAnimation) { _showLevelUpAnimation = showLevelUpAnimation; _helper.Events.Player.LevelChanged -= OnLevelChanged; if (_showLevelUpAnimation) { _helper.Events.Player.LevelChanged += OnLevelChanged; } } public void ToggleExperienceBarFade(bool allowExperienceBarToFadeOut) { _allowExperienceBarToFadeOut = allowExperienceBarToFadeOut; } public void ToggleShowExperienceGain(bool showExperienceGain) { _helper.Events.GameLoop.UpdateTicked -= OnUpdateTicked_DetermineIfExperienceHasBeenGained; for (int i = 0; i < _currentExperience.Length; ++i) _currentExperience[i] = Game1.player.experiencePoints[i]; _showExperienceGain = showExperienceGain; if (_levelExtenderAPI != null) { for (int i = 0; i < _currentLevelExtenderExperience.Length; ++i) _currentLevelExtenderExperience[i] = _levelExtenderAPI.currentXP()[i]; } if (showExperienceGain) { _helper.Events.GameLoop.UpdateTicked += OnUpdateTicked_DetermineIfExperienceHasBeenGained; } } public void ToggleShowExperienceBar(bool showExperienceBar) { _helper.Events.GameLoop.UpdateTicked -= OnUpdateTicked_DetermineIfExperienceHasBeenGained; //GraphicsEvents.OnPreRenderHudEvent -= OnPreRenderHudEvent; //PlayerEvents.Warped -= RemoveAllExperiencePointDisplays; _showExperienceBar = showExperienceBar; if (showExperienceBar) { //GraphicsEvents.OnPreRenderHudEvent += OnPreRenderHudEvent; //PlayerEvents.Warped += RemoveAllExperiencePointDisplays; _helper.Events.GameLoop.UpdateTicked += OnUpdateTicked_DetermineIfExperienceHasBeenGained; } } /// Raised after a player skill level changes. This happens as soon as they level up, not when the game notifies the player after their character goes to bed. /// The event sender. /// The event arguments. private void OnLevelChanged(object sender, LevelChangedEventArgs e) { if (_showLevelUpAnimation && e.IsLocalPlayer) { switch (e.Skill) { case SkillType.Combat: _levelUpIconRectangle.X = 120; break; case SkillType.Farming: _levelUpIconRectangle.X = 10; break; case SkillType.Fishing: _levelUpIconRectangle.X = 20; break; case SkillType.Foraging: _levelUpIconRectangle.X = 60; break; case SkillType.Mining: _levelUpIconRectangle.X = 30; break; } _shouldDrawLevelUp = true; ShowExperienceBar(); float previousAmbientVolume = Game1.options.ambientVolumeLevel; float previousMusicVolume = Game1.options.musicVolumeLevel; //if (_soundEffect != null) // _soundEffect.Volume = previousMusicVolume <= 0.01f ? 0 : Math.Min(1, previousMusicVolume + 0.3f); //Task.Factory.StartNew(() => //{ // Thread.Sleep(100); // Game1.musicCategory.SetVolume((float)Math.Max(0, Game1.options.musicVolumeLevel - 0.3)); // Game1.ambientCategory.SetVolume((float)Math.Max(0, Game1.options.ambientVolumeLevel - 0.3)); // if (_soundEffect != null) // _soundEffect.Play(); //}); Task.Factory.StartNew(() => { Thread.Sleep(100); _player.Play(); }); Task.Factory.StartNew(() => { Thread.Sleep(_levelUpPauseTime); _shouldDrawLevelUp = false; //Game1.musicCategory.SetVolume(previousMusicVolume); //Game1.ambientCategory.SetVolume(previousAmbientVolume); }); } } private void StopTimerAndFadeBarOut(object sender, ElapsedEventArgs e) { _timeToDisappear?.Stop(); _experienceBarShouldBeVisible = false; } /// Raised after a player warps to a new location. /// The event sender. /// The event arguments. private void OnWarped_RemoveAllExperiencePointDisplays(object sender, WarpedEventArgs e) { if (e.IsLocalPlayer) _experiencePointDisplays.Clear(); } /// Raised after the game state is updated (≈60 times per second). /// The event sender. /// The event arguments. private void OnUpdateTicked_DetermineIfExperienceHasBeenGained(object sender, UpdateTickedEventArgs e) { if (!e.IsMultipleOf(15)) // quarter second return; Item currentItem = Game1.player.CurrentItem; int currentLevelIndex = -1; int[] levelExtenderExperience = null; if (_levelExtenderAPI != null) levelExtenderExperience = _levelExtenderAPI.currentXP(); for (int i = 0; i < _currentExperience.Length; ++i) { if (_currentExperience[i] != Game1.player.experiencePoints[i] || (_levelExtenderAPI != null && _currentLevelExtenderExperience[i] != levelExtenderExperience[i])) { currentLevelIndex = i; break; } } if (currentLevelIndex > -1) { switch (currentLevelIndex) { case 0: { _experienceFillColor = new Color(255, 251, 35, 0.38f); _experienceIconPosition.X = 10; _currentSkillLevel = Game1.player.farmingLevel.Value; break; } case 1: { _experienceFillColor = new Color(17, 84, 252, 0.63f); _experienceIconPosition.X = 20; _currentSkillLevel = Game1.player.fishingLevel.Value; break; } case 2: { _experienceFillColor = new Color(0, 234, 0, 0.63f); _experienceIconPosition.X = 60; _currentSkillLevel = Game1.player.foragingLevel.Value; break; } case 3: { _experienceFillColor = new Color(145, 104, 63, 0.63f); _experienceIconPosition.X = 30; _currentSkillLevel = Game1.player.miningLevel.Value; break; } case 4: { _experienceFillColor = new Color(204, 0, 3, 0.63f); _experienceIconPosition.X = 120; _currentSkillLevel = Game1.player.combatLevel.Value; break; } } _experienceRequiredToLevel = GetExperienceRequiredToLevel(_currentSkillLevel); _experienceFromPreviousLevels = GetExperienceRequiredToLevel(_currentSkillLevel - 1); _experienceEarnedThisLevel = Game1.player.experiencePoints[currentLevelIndex] - _experienceFromPreviousLevels; int experiencePreviouslyEarnedThisLevel = _currentExperience[currentLevelIndex] - _experienceFromPreviousLevels; if (_experienceRequiredToLevel <= 0 && _levelExtenderAPI != null) { _experienceEarnedThisLevel = _levelExtenderAPI.currentXP()[currentLevelIndex]; _experienceFromPreviousLevels = _currentExperience[currentLevelIndex] - _experienceEarnedThisLevel; _experienceRequiredToLevel = _levelExtenderAPI.requiredXP()[currentLevelIndex] + _experienceFromPreviousLevels; } ShowExperienceBar(); if (_showExperienceGain && _experienceRequiredToLevel > 0) { int currentExperienceToUse = Game1.player.experiencePoints[currentLevelIndex]; int previousExperienceToUse = _currentExperience[currentLevelIndex]; if (_levelExtenderAPI != null && _currentSkillLevel > 9) { currentExperienceToUse = _levelExtenderAPI.currentXP()[currentLevelIndex]; previousExperienceToUse = _currentLevelExtenderExperience[currentLevelIndex]; } int experienceGain = currentExperienceToUse - previousExperienceToUse; if (experienceGain > 0) { _experiencePointDisplays.Add( new ExperiencePointDisplay( experienceGain, Game1.player.getLocalPosition(Game1.viewport))); } } _currentExperience[currentLevelIndex] = Game1.player.experiencePoints[currentLevelIndex]; if (_levelExtenderAPI != null) _currentLevelExtenderExperience[currentLevelIndex] = _levelExtenderAPI.currentXP()[currentLevelIndex]; } else if (_previousItem != currentItem) { if (currentItem is FishingRod) { _experienceFillColor = new Color(17, 84, 252, 0.63f); currentLevelIndex = 1; _experienceIconPosition.X = 20; _currentSkillLevel = Game1.player.fishingLevel.Value; } else if (currentItem is Pickaxe) { _experienceFillColor = new Color(145, 104, 63, 0.63f); currentLevelIndex = 3; _experienceIconPosition.X = 30; _currentSkillLevel = Game1.player.miningLevel.Value; } else if (currentItem is MeleeWeapon && currentItem.Name != "Scythe") { _experienceFillColor = new Color(204, 0, 3, 0.63f); currentLevelIndex = 4; _experienceIconPosition.X = 120; _currentSkillLevel = Game1.player.combatLevel.Value; } else if (Game1.currentLocation is Farm && !(currentItem is Axe)) { _experienceFillColor = new Color(255, 251, 35, 0.38f); currentLevelIndex = 0; _experienceIconPosition.X = 10; _currentSkillLevel = Game1.player.farmingLevel.Value; } else { _experienceFillColor = new Color(0, 234, 0, 0.63f); currentLevelIndex = 2; _experienceIconPosition.X = 60; _currentSkillLevel = Game1.player.foragingLevel.Value; } _experienceRequiredToLevel = GetExperienceRequiredToLevel(_currentSkillLevel); _experienceFromPreviousLevels = GetExperienceRequiredToLevel(_currentSkillLevel - 1); _experienceEarnedThisLevel = Game1.player.experiencePoints[currentLevelIndex] - _experienceFromPreviousLevels; if (_experienceRequiredToLevel <= 0 && _levelExtenderAPI != null) { _experienceEarnedThisLevel = _levelExtenderAPI.currentXP()[currentLevelIndex]; _experienceFromPreviousLevels = _currentExperience[currentLevelIndex] - _experienceEarnedThisLevel; _experienceRequiredToLevel = _levelExtenderAPI.requiredXP()[currentLevelIndex] + _experienceFromPreviousLevels; } ShowExperienceBar(); _previousItem = currentItem; } } /// Raised before drawing the HUD (item toolbar, clock, etc) to the screen. The vanilla HUD may be hidden at this point (e.g. because a menu is open). /// The event sender. /// The event arguments. private void OnRenderingHud(object sender, RenderingHudEventArgs e) { if (!Game1.eventUp) { if (_shouldDrawLevelUp) { Vector2 playerLocalPosition = Game1.player.getLocalPosition(Game1.viewport); Game1.spriteBatch.Draw( Game1.mouseCursors, new Vector2( playerLocalPosition.X - 74, playerLocalPosition.Y - 130), _levelUpIconRectangle, _iconColor, 0, Vector2.Zero, Game1.pixelZoom, SpriteEffects.None, 0.85f); Game1.drawWithBorder( _helper.SafeGetString( LanguageKeys.LevelUp), Color.DarkSlateGray, Color.PaleTurquoise, new Vector2( playerLocalPosition.X - 28, playerLocalPosition.Y - 130)); } for (int i = _experiencePointDisplays.Count - 1; i >= 0; --i) { if (_experiencePointDisplays[i].IsInvisible) { _experiencePointDisplays.RemoveAt(i); } else { _experiencePointDisplays[i].Draw(); } } if (_experienceRequiredToLevel > 0 && _experienceBarShouldBeVisible && _showExperienceBar) { int experienceDifferenceBetweenLevels = _experienceRequiredToLevel - _experienceFromPreviousLevels; int barWidth = (int)((double)_experienceEarnedThisLevel / experienceDifferenceBetweenLevels * MaxBarWidth); DrawExperienceBar(barWidth, _experienceEarnedThisLevel, experienceDifferenceBetweenLevels, _currentSkillLevel); } } } private int GetExperienceRequiredToLevel(int currentLevel) { int amount = 0; //if (currentLevel < 10) //{ switch (currentLevel) { case 0: amount = 100; break; case 1: amount = 380; break; case 2: amount = 770; break; case 3: amount = 1300; break; case 4: amount = 2150; break; case 5: amount = 3300; break; case 6: amount = 4800; break; case 7: amount = 6900; break; case 8: amount = 10000; break; case 9: amount = 15000; break; } //} //else if (_levelExtenderAPI != null && // currentLevel < 100) //{ // var requiredXP = _levelExtenderAPI.requiredXP(); // amount = requiredXP[currentLevel]; //} return amount; } private void ShowExperienceBar() { if (_timeToDisappear != null) { if (_allowExperienceBarToFadeOut) { _timeToDisappear.Interval = _timeBeforeExperienceBarFades.TotalMilliseconds; _timeToDisappear.Start(); } else { _timeToDisappear.Stop(); } } _experienceBarShouldBeVisible = true; } private void DrawExperienceBar(int barWidth, int experienceGainedThisLevel, int experienceRequiredForNextLevel, int currentLevel) { float leftSide = Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Left; if (Game1.isOutdoorMapSmallerThanViewport()) { int num3 = Game1.currentLocation.map.Layers[0].LayerWidth * Game1.tileSize; leftSide += (Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Right - num3) / 2; } Game1.drawDialogueBox( (int)leftSide, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 160, 240, 160, false, true); Game1.spriteBatch.Draw( Game1.staminaRect, new Rectangle( (int)leftSide + 32, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 63, barWidth, 31), _experienceFillColor); Game1.spriteBatch.Draw( Game1.staminaRect, new Rectangle( (int)leftSide + 32, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 63, Math.Min(4, barWidth), 31), _experienceFillColor); Game1.spriteBatch.Draw( Game1.staminaRect, new Rectangle( (int)leftSide + 32, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 63, barWidth, 4), _experienceFillColor); Game1.spriteBatch.Draw( Game1.staminaRect, new Rectangle( (int)leftSide + 32, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 36, barWidth, 4), _experienceFillColor); ClickableTextureComponent textureComponent = new ClickableTextureComponent( "", new Rectangle( (int)leftSide - 36, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 80, 260, 100), "", "", Game1.mouseCursors, new Rectangle(0, 0, 0, 0), Game1.pixelZoom); if (textureComponent.containsPoint(Game1.getMouseX(), Game1.getMouseY())) { Game1.drawWithBorder( experienceGainedThisLevel + "/" + experienceRequiredForNextLevel, Color.Black, Color.Black, new Vector2( leftSide + 33, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 70)); } else { Game1.spriteBatch.Draw( Game1.mouseCursors, new Vector2( leftSide + 54, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 62), _experienceIconPosition, _iconColor, 0, Vector2.Zero, 2.9f, SpriteEffects.None, 0.85f); Game1.drawWithBorder( currentLevel.ToString(), Color.Black * 0.6f, Color.Black, new Vector2( leftSide + 33, Game1.graphics.GraphicsDevice.Viewport.TitleSafeArea.Bottom - 70)); } } } }