From 6e8b3543825e7866f8dcb3c798ff57f047313135 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Sun, 10 Apr 2022 18:18:04 -0700 Subject: [PATCH 1/9] Update version to 0.9 --- azure-pipelines.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/azure-pipelines.yml b/azure-pipelines.yml index bb2a601ba..22c5e3745 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -1,4 +1,4 @@ -name: 0.8.$(rev:rrr) +name: 0.9.$(rev:rrr) trigger: - master From 6f6a54fc475d9ae452cfa40f82d5ad3219ef2b89 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Sun, 10 Apr 2022 22:54:30 -0700 Subject: [PATCH 2/9] Fix main character render position when logging in to a sitting character --- EndlessClient/Rendering/Character/CharacterRenderer.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/EndlessClient/Rendering/Character/CharacterRenderer.cs b/EndlessClient/Rendering/Character/CharacterRenderer.cs index 0c7b949a6..4a162428c 100644 --- a/EndlessClient/Rendering/Character/CharacterRenderer.cs +++ b/EndlessClient/Rendering/Character/CharacterRenderer.cs @@ -227,7 +227,7 @@ public void SetToCenterScreenPosition() { const int x = 314; // 618 / 2.0 - var skinRect = _characterTextures.Skin.SourceRectangle; + var skinRect = new Rectangle(0, 0, 18, 58); var y = (298 - skinRect.Height)/2 - skinRect.Height/4; SetAbsoluteScreenPosition(x, y); } From 8141961f76db985a3c0c2357e51aa1e148b5e74a Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 09:05:29 -0700 Subject: [PATCH 3/9] Move Spell Icon stuff to Old namespace. Prep for rewrite --- .../HUD/Panels/Old/OldActiveSpellsPanel.cs | 2 +- .../HUD/Spells/{ => Old}/EmptySpellIcon.cs | 4 ++-- EndlessClient/HUD/Spells/{ => Old}/ISpellIcon.cs | 2 +- EndlessClient/HUD/Spells/{ => Old}/SpellIcon.cs | 14 +++++++------- 4 files changed, 11 insertions(+), 11 deletions(-) rename EndlessClient/HUD/Spells/{ => Old}/EmptySpellIcon.cs (95%) rename EndlessClient/HUD/Spells/{ => Old}/ISpellIcon.cs (91%) rename EndlessClient/HUD/Spells/{ => Old}/SpellIcon.cs (94%) diff --git a/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs b/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs index 7b6603f0a..da035d8cc 100644 --- a/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs +++ b/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs @@ -2,7 +2,7 @@ using System.Collections.Generic; using System.Linq; using EndlessClient.Dialogs; -using EndlessClient.HUD.Spells; +using EndlessClient.HUD.Spells.Old; using EndlessClient.Old; using EndlessClient.UIControls; using EOLib; diff --git a/EndlessClient/HUD/Spells/EmptySpellIcon.cs b/EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs similarity index 95% rename from EndlessClient/HUD/Spells/EmptySpellIcon.cs rename to EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs index 568df0134..7cf450ff3 100644 --- a/EndlessClient/HUD/Spells/EmptySpellIcon.cs +++ b/EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs @@ -8,7 +8,7 @@ using Microsoft.Xna.Framework.Input; using XNAControls.Old; -namespace EndlessClient.HUD.Spells +namespace EndlessClient.HUD.Spells.Old { public class EmptySpellIcon : XNAControl, ISpellIcon { @@ -32,7 +32,7 @@ public virtual bool Selected { if (value) { - ((EOGame) Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_NOTHING_WAS_SELECTED); + ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_NOTHING_WAS_SELECTED); } } } diff --git a/EndlessClient/HUD/Spells/ISpellIcon.cs b/EndlessClient/HUD/Spells/Old/ISpellIcon.cs similarity index 91% rename from EndlessClient/HUD/Spells/ISpellIcon.cs rename to EndlessClient/HUD/Spells/Old/ISpellIcon.cs index 2af8ffbe2..2d4f96d81 100644 --- a/EndlessClient/HUD/Spells/ISpellIcon.cs +++ b/EndlessClient/HUD/Spells/Old/ISpellIcon.cs @@ -1,6 +1,6 @@ using EOLib.IO.Pub; -namespace EndlessClient.HUD.Spells +namespace EndlessClient.HUD.Spells.Old { public interface ISpellIcon { diff --git a/EndlessClient/HUD/Spells/SpellIcon.cs b/EndlessClient/HUD/Spells/Old/SpellIcon.cs similarity index 94% rename from EndlessClient/HUD/Spells/SpellIcon.cs rename to EndlessClient/HUD/Spells/Old/SpellIcon.cs index 168d63372..bad3eb494 100644 --- a/EndlessClient/HUD/Spells/SpellIcon.cs +++ b/EndlessClient/HUD/Spells/Old/SpellIcon.cs @@ -9,7 +9,7 @@ using Microsoft.Xna.Framework.Graphics; using Microsoft.Xna.Framework.Input; -namespace EndlessClient.HUD.Spells +namespace EndlessClient.HUD.Spells.Old { public class SpellIcon : EmptySpellIcon { @@ -61,7 +61,7 @@ public SpellIcon(OldActiveSpells parent, ESFRecord data, int slot) _spellGraphicSourceRect = new Rectangle(0, 0, _spellGraphic.Width / 2, _spellGraphic.Height); _spellLevelColor = new Texture2D(Game.GraphicsDevice, 1, 1); - _spellLevelColor.SetData(new[] {Color.FromNonPremultiplied(0xc9, 0xb8, 0x9b, 0xff)}); + _spellLevelColor.SetData(new[] { Color.FromNonPremultiplied(0xc9, 0xb8, 0x9b, 0xff) }); OnLevelChanged(); _clickTime = DateTime.Now; @@ -99,14 +99,14 @@ protected override void OnSlotChanged() private void OnSelected() { - var hud = ((EOGame) Game).Hud; + var hud = ((EOGame)Game).Hud; switch (SpellData.Target) { case SpellTarget.Normal: hud.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, SpellData.Name, EOResourceID.SPELL_WAS_SELECTED); break; case SpellTarget.Group: - if(!hud.MainPlayerIsInParty()) + if (!hud.MainPlayerIsInParty()) hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_ONLY_WORKS_ON_GROUP); break; } @@ -126,13 +126,13 @@ private void UpdateIconSourceRect() { SetIconHover(MouseOver); if (MouseOver && !_parentSpellContainer.AnySpellsDragging()) - ((EOGame) Game).Hud.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, SpellData.Name); + ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, SpellData.Name); } } private void SetIconHover(bool hover) { - var halfWidth = _spellGraphic.Width/2; + var halfWidth = _spellGraphic.Width / 2; _spellGraphicSourceRect = new Rectangle(hover ? halfWidth : 0, 0, halfWidth, _spellGraphic.Height); } @@ -226,7 +226,7 @@ private void DrawSpellIcon() alphaColor = Color.FromNonPremultiplied(255, 255, 255, 128); } - if (targetDrawArea.Width*targetDrawArea.Height == 0) + if (targetDrawArea.Width * targetDrawArea.Height == 0) return; SpriteBatch.Draw(_spellGraphic, targetDrawArea, _spellGraphicSourceRect, alphaColor); From e06e7502b01cbcd797fc68777ce176ecfb365afb Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 15:46:25 -0700 Subject: [PATCH 4/9] Initial display of spell icons in active spells panel --- EndlessClient/HUD/Panels/ActiveSpellsPanel.cs | 406 +++++++++++++++++- EndlessClient/HUD/Panels/HudPanelFactory.cs | 9 +- EndlessClient/HUD/Panels/InventoryPanel.cs | 2 + .../HUD/Spells/BaseSpellPanelItem.cs | 104 +++++ .../HUD/Spells/EmptySpellPanelItem.cs | 32 ++ EndlessClient/HUD/Spells/ISpellPanelItem.cs | 23 + EndlessClient/HUD/Spells/SpellPanelItem.cs | 179 ++++++++ 7 files changed, 749 insertions(+), 6 deletions(-) create mode 100644 EndlessClient/HUD/Spells/BaseSpellPanelItem.cs create mode 100644 EndlessClient/HUD/Spells/EmptySpellPanelItem.cs create mode 100644 EndlessClient/HUD/Spells/ISpellPanelItem.cs create mode 100644 EndlessClient/HUD/Spells/SpellPanelItem.cs diff --git a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs index da8012f79..666fa3e62 100644 --- a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs +++ b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs @@ -1,19 +1,415 @@ -using EOLib.Graphics; +using EndlessClient.Controllers; +using EndlessClient.Dialogs; +using EndlessClient.Dialogs.Factories; +using EndlessClient.HUD.Spells; +using EndlessClient.UIControls; +using EOLib; +using EOLib.Config; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Graphics; +using EOLib.IO.Repositories; +using EOLib.Localization; +using Microsoft.Win32; using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Optional; +using Optional.Collections; +using System; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using System.Runtime.InteropServices; +using System.Runtime.Versioning; using XNAControls; namespace EndlessClient.HUD.Panels { public class ActiveSpellsPanel : XNAPanel, IHudPanel { - private readonly INativeGraphicsManager _nativeGraphicsManager; + public const int SpellRows = 4; + public const int SpellRowLength = 8; - public ActiveSpellsPanel(INativeGraphicsManager nativeGraphicsManager) + private readonly ITrainingController _trainingController; + private readonly IEOMessageBoxFactory _messageBoxFactory; + private readonly IStatusLabelSetter _statusLabelSetter; + private readonly IPlayerInfoProvider _playerInfoProvider; + private readonly ICharacterProvider _characterProvider; + private readonly ICharacterInventoryProvider _characterInventoryProvider; + private readonly IESFFileProvider _esfFileProvider; + + private readonly Dictionary _spellSlotMap; + private readonly List _childItems; + + private readonly Texture2D _functionKeyLabelSheet; + private readonly Rectangle _functionKeyRow1Source, _functionKeyRow2Source; + + private XNALabel _selectedSpellName, _selectedSpellLevel, _totalSkillPoints; + private XNAButton _levelUpButton1, _levelUpButton2; + private ScrollBar _scrollBar; + + private HashSet _cachedSpells; + private bool _confirmedTraining; + + public INativeGraphicsManager NativeGraphicsManager { get; } + + public ActiveSpellsPanel(INativeGraphicsManager nativeGraphicsManager, + ITrainingController trainingController, + IEOMessageBoxFactory messageBoxFactory, + IStatusLabelSetter statusLabelSetter, + IPlayerInfoProvider playerInfoProvider, + ICharacterProvider characterProvider, + ICharacterInventoryProvider characterInventoryProvider, + IESFFileProvider esfFileProvider) { - _nativeGraphicsManager = nativeGraphicsManager; + NativeGraphicsManager = nativeGraphicsManager; + _trainingController = trainingController; + _messageBoxFactory = messageBoxFactory; + _statusLabelSetter = statusLabelSetter; + _playerInfoProvider = playerInfoProvider; + _characterProvider = characterProvider; + _characterInventoryProvider = characterInventoryProvider; + _esfFileProvider = esfFileProvider; + + _spellSlotMap = GetSpellSlotMap(_playerInfoProvider.LoggedInAccountName, _characterProvider.MainCharacter.Name); + _childItems = new List(); + ResetChildItems(); + + _cachedSpells = new HashSet(); + + _functionKeyLabelSheet = NativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 58, true); + _functionKeyRow1Source = new Rectangle(148, 51, 18, 13); + _functionKeyRow2Source = new Rectangle(148 + 18 * 8, 51, 18, 13); + + _selectedSpellName = new XNALabel(Constants.FontSize08pt5) + { + DrawArea = new Rectangle(9, 50, 81, 13), + Visible = false, + Text = "", + AutoSize = false, + TextAlign = LabelAlignment.MiddleCenter, + ForeColor = ColorConstants.LightGrayText, + }; + + _selectedSpellLevel = new XNALabel(Constants.FontSize08pt5) + { + DrawArea = new Rectangle(32, 78, 42, 15), + Visible = true, + Text = "0", + AutoSize = false, + TextAlign = LabelAlignment.MiddleLeft, + ForeColor = ColorConstants.LightGrayText, + }; + + _totalSkillPoints = new XNALabel(Constants.FontSize08pt5) + { + DrawArea = new Rectangle(32, 96, 42, 15), + Visible = true, + Text = _characterProvider.MainCharacter.Stats[CharacterStat.SkillPoints].ToString(), + AutoSize = false, + TextAlign = LabelAlignment.MiddleLeft, + ForeColor = ColorConstants.LightGrayText, + }; - BackgroundImage = _nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 62); + var buttonSheet = NativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 27, true); + _levelUpButton1 = new XNAButton(buttonSheet, new Vector2(71, 77), new Rectangle(215, 386, 19, 15), new Rectangle(234, 386, 19, 15)) + { + FlashSpeed = 500, + Visible = false + }; + _levelUpButton1.OnClick += LevelUp_Click; + + _levelUpButton2 = new XNAButton(buttonSheet, new Vector2(71, 95), new Rectangle(215, 386, 19, 15), new Rectangle(234, 386, 19, 15)) + { + FlashSpeed = 500, + Visible = false + }; + _levelUpButton2.OnClick += LevelUp_Click; + + _scrollBar = new ScrollBar(new Vector2(467, 2), new Vector2(16, 115), ScrollBarColors.LightOnMed, NativeGraphicsManager) + { + LinesToRender = 2 + }; + _scrollBar.UpdateDimensions(4); + + BackgroundImage = NativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 62); DrawArea = new Rectangle(102, 330, BackgroundImage.Width, BackgroundImage.Height); + + Game.Exiting += SaveSpellsFile; + } + + public override void Initialize() + { + _selectedSpellName.Initialize(); + _selectedSpellName.SetParentControl(this); + + _selectedSpellLevel.Initialize(); + _selectedSpellLevel.SetParentControl(this); + + _totalSkillPoints.Initialize(); + _totalSkillPoints.SetParentControl(this); + + _levelUpButton1.Initialize(); + _levelUpButton1.SetParentControl(this); + + _levelUpButton2.Initialize(); + _levelUpButton2.SetParentControl(this); + + _scrollBar.Initialize(); + _scrollBar.SetParentControl(this); + + base.Initialize(); + } + + public bool AnySpellsDragging() => _childItems.Any(x => x.IsBeingDragged); + + protected override void OnUpdateControl(GameTime gameTime) + { + if (!_cachedSpells.SetEquals(_characterInventoryProvider.SpellInventory)) + { + var added = _characterInventoryProvider.SpellInventory.Where(i => !_cachedSpells.Any(j => i.ID == j.ID)); + var removed = _cachedSpells.Where(i => !_characterInventoryProvider.SpellInventory.Any(j => i.ID == j.ID)); + var updated = _characterInventoryProvider.SpellInventory.Except(added) + .Where(i => _cachedSpells.Any(j => i.ID == j.ID && i.Level != j.Level)); + + foreach (var spell in removed) + { + var matchedSpell = _childItems.SingleOrNone(x => x.InventorySpell.ID == spell.ID); + matchedSpell.MatchSome(childControl => + { + childControl.SetControlUnparented(); + childControl.Dispose(); + _childItems.Remove(childControl); + + _childItems.Add(CreateEmptySpell(childControl.Slot)); + }); + } + + foreach (var spell in updated) + { + var matchedSpell = _childItems.SingleOrNone(x => x.InventorySpell.ID == spell.ID); + matchedSpell.MatchSome(childControl => + { + childControl.InventorySpell = spell; + }); + } + + foreach (var spell in added) + { + var spellData = _esfFileProvider.ESFFile[spell.ID]; + + var preferredSlot = _spellSlotMap.SingleOrNone(x => x.Value == spell.ID).Map(x => x.Key); + var actualSlot = preferredSlot.Match( + some: x => + { + return _childItems.Any(ci => ci.Slot == x) + ? GetNextOpenSlot(_childItems) + : Option.Some(x); + }, + none: () => GetNextOpenSlot(_childItems)); + + actualSlot.MatchSome(slot => + { + _childItems.SingleOrNone(ci => ci.Slot == slot) + .MatchSome(ci => + { + ci.SetControlUnparented(); + ci.Dispose(); + _childItems.Remove(ci); + }); + + var newChild = new SpellPanelItem(this, slot, spell, spellData); + newChild.Initialize(); + + newChild.Selected += SetSpellStatusLabelSelected; + newChild.OnMouseOver += SetSpellStatusLabelHover; + //newItem.DoubleClick += HandleItemDoubleClick; + //newItem.DoneDragging += HandleItemDoneDragging; + + _childItems.Add(newChild); + }); + } + + _cachedSpells = _characterInventoryProvider.SpellInventory.ToHashSet(); + } + + base.OnUpdateControl(gameTime); + } + + protected override void OnDrawControl(GameTime gameTime) + { + base.OnDrawControl(gameTime); + } + + private void LevelUp_Click(object sender, EventArgs args) + { + if (!_confirmedTraining) + { + //apparently this is NOT stored in the edf files... + //NOTE: copy-pasted from EOCharacterStats button event handler. Should probably be in some shared function somewhere. + var dialog = _messageBoxFactory.CreateMessageBox("Do you want to train?", + "Skill training", + EODialogButtons.OkCancel); + dialog.DialogClosing += (_, e) => + { + if (e.Result == XNADialogResult.OK) + _confirmedTraining = true; + }; + dialog.ShowDialog(); + } + else + { + //var selectedSpell = _childItems.Single(x => x.Selected); + // todo: implement in training controller + //_trainingController.LevelUpSpell(selectedSpell.SpellData.ID); + } } + + private void SetSpellStatusLabelSelected(object sender, EventArgs e) + { + var spell = ((SpellPanelItem)sender).SpellData; + + if (spell.Target == EOLib.IO.SpellTarget.Normal) + _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, spell.Name, EOResourceID.SPELL_WAS_SELECTED); + else if (spell.Target == EOLib.IO.SpellTarget.Group /*&& not in party*/) // todo: parties + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_ONLY_WORKS_ON_GROUP); + } + + private void SetSpellStatusLabelHover(object sender, EventArgs e) + { + var spell = ((SpellPanelItem)sender).SpellData; + _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, spell.Name); + } + + private static Option GetNextOpenSlot(IEnumerable childItems) + { + // get the minimum slot when there is an Empty space + return childItems.OfType() + .SomeWhen(x => x.Any()) + .Map(x => x.Min(y => y.Slot)); + } + + private void ResetChildItems() + { + for (int slot = 0; slot < SpellRows * SpellRowLength; slot++) + { + _childItems.Add(CreateEmptySpell(slot)); + } + } + + private ISpellPanelItem CreateEmptySpell(int slot) + { + var emptyItem = new EmptySpellPanelItem(this, slot); + emptyItem.Selected += (_, _) => _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_NOTHING_WAS_SELECTED); + emptyItem.Clicked += (_, _) => + { + //if (DoEmptySpellIconUpdateLogic) + //{ + // if (MouseOver && MouseOverPreviously && + // _parentSpellContainer.AnySpellsSelected() && + // !_parentSpellContainer.AnySpellsDragging() && + // Mouse.GetState().LeftButton == ButtonState.Released && + // PreviousMouseState.LeftButton == ButtonState.Pressed) + // { + // _parentSpellContainer.ClearSelectedSpell(); + // } + //} + }; + emptyItem.Initialize(); + return emptyItem; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + Game.Exiting -= SaveSpellsFile; + SaveSpellsFile(null, EventArgs.Empty); + } + + base.Dispose(disposing); + } + + #region Slot loading + + private static Dictionary GetSpellSlotMap(string accountName, string characterName) + { + var map = new Dictionary(); + + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !File.Exists(Constants.InventoryFile)) + { + using var inventoryKey = TryGetCharacterRegistryKey(accountName, characterName); + if (inventoryKey != null) + { + for (int i = 0; i < SpellRowLength * 4; ++i) + { + if (int.TryParse(inventoryKey.GetValue($"item{i}")?.ToString() ?? string.Empty, out var id)) + map[i] = id; + } + } + } + + var inventory = new IniReader(Constants.InventoryFile); + if (inventory.Load() && inventory.Sections.ContainsKey(accountName)) + { + var section = inventory.Sections[accountName]; + foreach (var key in section.Keys.Where(x => x.Contains(characterName, StringComparison.OrdinalIgnoreCase))) + { + if (!key.Contains(".")) + continue; + + var slot = key.Split(".")[1]; + if (!int.TryParse(slot, out var slotIndex)) + continue; + + if (int.TryParse(section[key], out var id)) + map[slotIndex] = id; + } + } + + return map; + } + + [SupportedOSPlatform("Windows")] + private static RegistryKey TryGetCharacterRegistryKey(string accountName, string characterName) + { + using RegistryKey currentUser = Registry.CurrentUser; + + var pathSegments = $"Software\\EndlessClient\\{accountName}\\{characterName}\\spells".Split('\\'); + var currentPath = string.Empty; + + RegistryKey retKey = null; + foreach (var segment in pathSegments) + { + retKey?.Dispose(); + + currentPath = Path.Combine(currentPath, segment); + retKey = currentUser.CreateSubKey(currentPath, RegistryKeyPermissionCheck.ReadSubTree); + } + + return retKey; + } + + private void SaveSpellsFile(object sender, EventArgs e) + { + var inventory = new IniReader(Constants.InventoryFile); + + var section = inventory.Load() && inventory.Sections.ContainsKey(_playerInfoProvider.LoggedInAccountName) + ? inventory.Sections[_playerInfoProvider.LoggedInAccountName] + : new SortedList(); + + var existing = section.Where(x => x.Key.Contains(_characterProvider.MainCharacter.Name)).Select(x => x.Key).ToList(); + foreach (var key in existing) + section.Remove(key); + + foreach (var item in _childItems) + section[$"{_characterProvider.MainCharacter.Name}.{item.Slot}"] = $"{item.InventorySpell.ID}"; + + inventory.Sections[_playerInfoProvider.LoggedInAccountName] = section; + + inventory.Save(); + } + + #endregion } } \ No newline at end of file diff --git a/EndlessClient/HUD/Panels/HudPanelFactory.cs b/EndlessClient/HUD/Panels/HudPanelFactory.cs index f7b4767c2..9be30f1fa 100644 --- a/EndlessClient/HUD/Panels/HudPanelFactory.cs +++ b/EndlessClient/HUD/Panels/HudPanelFactory.cs @@ -115,7 +115,14 @@ public InventoryPanel CreateInventoryPanel() public ActiveSpellsPanel CreateActiveSpellsPanel() { - return new ActiveSpellsPanel(_nativeGraphicsManager) { DrawOrder = HUD_CONTROL_LAYER }; + return new ActiveSpellsPanel(_nativeGraphicsManager, + _trainingController, + _messageBoxFactory, + _statusLabelSetter, + _playerInfoProvider, + _characterProvider, + _characterInventoryProvider, + _pubFileProvider) { DrawOrder = HUD_CONTROL_LAYER }; } public PassiveSpellsPanel CreatePassiveSpellsPanel() diff --git a/EndlessClient/HUD/Panels/InventoryPanel.cs b/EndlessClient/HUD/Panels/InventoryPanel.cs index a0bb496ba..a6e81835e 100644 --- a/EndlessClient/HUD/Panels/InventoryPanel.cs +++ b/EndlessClient/HUD/Panels/InventoryPanel.cs @@ -34,6 +34,7 @@ public class InventoryPanel : XNAPanel, IHudPanel { public const int InventoryRows = 4; public const int InventoryRowSlots = 14; + private readonly IInventoryController _inventoryController; private readonly IStatusLabelSetter _statusLabelSetter; private readonly IItemStringService _itemStringService; @@ -46,6 +47,7 @@ public class InventoryPanel : XNAPanel, IHudPanel private readonly IPubFileProvider _pubFileProvider; // todo: this can probably become EIFFileProvider private readonly IHudControlProvider _hudControlProvider; private readonly IActiveDialogProvider _activeDialogProvider; + private readonly Dictionary _itemSlotMap; private readonly List _childItems = new List(); diff --git a/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs b/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs new file mode 100644 index 000000000..293fba172 --- /dev/null +++ b/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs @@ -0,0 +1,104 @@ +using System; +using EndlessClient.HUD.Panels; +using EOLib.Domain.Character; +using EOLib.IO.Pub; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using XNAControls; + +namespace EndlessClient.HUD.Spells +{ + public abstract class BaseSpellPanelItem : XNAControl, ISpellPanelItem + { + protected const int ICON_AREA_WIDTH = 42, ICON_AREA_HEIGHT = 36; + + public int Slot { get; set; } + + private int _displaySlot; + public int DisplaySlot + { + get => _displaySlot; + set + { + _displaySlot = value; + + //start pos: 101, 97 + //xdelta: 45; ydelta: 52 + var row = _displaySlot / ActiveSpellsPanel.SpellRowLength; + var col = _displaySlot % ActiveSpellsPanel.SpellRowLength; + DrawPosition = new Vector2(101 + col * 45, 9 + row * 52); + } + } + + private bool _selected; + public virtual bool IsSelected + { + get => _selected; + set + { + _selected = value; + Selected?.Invoke(this, EventArgs.Empty); + } + } + + public virtual bool IsBeingDragged => false; + + public abstract IInventorySpell InventorySpell { get; set; } + + public abstract ESFRecord SpellData { get; } + + public event EventHandler Clicked; + + public event EventHandler Selected; + + private readonly Texture2D _highlightColor; + protected readonly ActiveSpellsPanel _parentPanel; + + protected BaseSpellPanelItem(ActiveSpellsPanel parent, int slot) + { + SetParentControl(parent); + _parentPanel = parent; + + Slot = slot; + DisplaySlot = slot; + + _highlightColor = new Texture2D(Game.GraphicsDevice, 1, 1); + _highlightColor.SetData(new[] { Color.White }); + + SetSize(ICON_AREA_WIDTH, ICON_AREA_HEIGHT); + } + + protected override void OnUpdateControl(GameTime gameTime) + { + if (MouseOver + && CurrentMouseState.LeftButton == ButtonState.Released + && PreviousMouseState.LeftButton == ButtonState.Pressed) + { + Clicked?.Invoke(this, EventArgs.Empty); + } + + base.OnUpdateControl(gameTime); + } + + protected override void OnDrawControl(GameTime gameTime) + { + if (MouseOver && _parentPanel.AnySpellsDragging()) + { + _spriteBatch.Begin(); + _spriteBatch.Draw(_highlightColor, DrawAreaWithParentOffset, Color.FromNonPremultiplied(200, 200, 200, 60)); + _spriteBatch.End(); + } + + base.OnDrawControl(gameTime); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + _highlightColor.Dispose(); + + base.Dispose(disposing); + } + } +} diff --git a/EndlessClient/HUD/Spells/EmptySpellPanelItem.cs b/EndlessClient/HUD/Spells/EmptySpellPanelItem.cs new file mode 100644 index 000000000..adbe3990f --- /dev/null +++ b/EndlessClient/HUD/Spells/EmptySpellPanelItem.cs @@ -0,0 +1,32 @@ +using EndlessClient.HUD.Panels; +using EOLib.Domain.Character; +using EOLib.IO.Pub; +using System; + +namespace EndlessClient.HUD.Spells +{ + public class EmptySpellPanelItem : BaseSpellPanelItem + { + public override bool IsSelected + { + get => false; + set + { + base.IsSelected = false; + } + } + + public override IInventorySpell InventorySpell + { + get => new InventorySpell(0, 0); + set => throw new InvalidOperationException("There is no Spell associated with this slot"); + } + + public override ESFRecord SpellData => new ESFRecord(); + + public EmptySpellPanelItem(ActiveSpellsPanel parent, int slot) + : base(parent, slot) + { + } + } +} diff --git a/EndlessClient/HUD/Spells/ISpellPanelItem.cs b/EndlessClient/HUD/Spells/ISpellPanelItem.cs new file mode 100644 index 000000000..669395303 --- /dev/null +++ b/EndlessClient/HUD/Spells/ISpellPanelItem.cs @@ -0,0 +1,23 @@ +using EOLib.Domain.Character; +using EOLib.IO.Pub; +using System; +using XNAControls; + +namespace EndlessClient.HUD.Spells +{ + public interface ISpellPanelItem : IXNAControl + { + int Slot { get; set; } + + bool IsSelected { get; set; } + + bool IsBeingDragged { get; } + + IInventorySpell InventorySpell { get; set; } + + ESFRecord SpellData { get; } + + event EventHandler Clicked; + event EventHandler Selected; + } +} diff --git a/EndlessClient/HUD/Spells/SpellPanelItem.cs b/EndlessClient/HUD/Spells/SpellPanelItem.cs new file mode 100644 index 000000000..4b3569497 --- /dev/null +++ b/EndlessClient/HUD/Spells/SpellPanelItem.cs @@ -0,0 +1,179 @@ +using System; +using EndlessClient.HUD.Panels; +using EOLib.Domain.Character; +using EOLib.Graphics; +using EOLib.IO.Pub; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; + +namespace EndlessClient.HUD.Spells +{ + public class SpellPanelItem : BaseSpellPanelItem + { + private readonly Texture2D _spellGraphic, _spellLevelColor; + + private Rectangle _spellGraphicSourceRect; + private DateTime _clickTime; + private bool _dragging, _followMouse; + private Rectangle _levelDestinationRectangle; + + private IInventorySpell _inventorySpell; + public override IInventorySpell InventorySpell + { + get => _inventorySpell; + set + { + _inventorySpell = value; + + //36 is full width of level bar + var width = (int)(InventorySpell.Level / 100.0 * 36); + _levelDestinationRectangle = new Rectangle(DrawAreaWithParentOffset.X + 3, DrawAreaWithParentOffset.Y + 40, width, 6); + } + } + + public override ESFRecord SpellData { get; } + + public SpellPanelItem(ActiveSpellsPanel parent, int slot, IInventorySpell spell, ESFRecord spellData) + : base(parent, slot) + { + InventorySpell = spell; + SpellData = spellData; + + _spellGraphic = _parentPanel.NativeGraphicsManager.TextureFromResource(GFXTypes.SpellIcons, SpellData.Icon); + _spellGraphicSourceRect = new Rectangle(0, 0, _spellGraphic.Width / 2, _spellGraphic.Height); + + _spellLevelColor = new Texture2D(Game.GraphicsDevice, 1, 1); + _spellLevelColor.SetData(new[] { Color.White }); + + _clickTime = DateTime.Now; + + OnMouseEnter += (_, _) => SetIconHover(true); + OnMouseLeave += (_, _) => SetIconHover(false); + } + + protected override void OnUpdateControl(GameTime gameTime) + { + DoClickAndDragLogic(); + + base.OnUpdateControl(gameTime); + } + + protected override void OnDrawControl(GameTime gameTime) + { + _spriteBatch.Begin(); + DrawSpellIcon(); + DrawSpellLevel(); + _spriteBatch.End(); + + base.OnDrawControl(gameTime); + } + + private void SetIconHover(bool hover) + { + var halfWidth = _spellGraphic.Width / 2; + _spellGraphicSourceRect = new Rectangle(hover ? halfWidth : 0, 0, halfWidth, _spellGraphic.Height); + } + + private void DoClickAndDragLogic() + { + //if (!_dragging && _parentPanel.AnySpellsDragging()) + // return; + + //var currentState = Mouse.GetState(); + //if (LeftButtonDown(currentState)) + //{ + // if (!_dragging) + // { + // _followMouse = true; + // _clickTime = DateTime.Now; + // _parentSpellContainer.SetSelectedSpellBySlot(Slot); + // } + // else + // { + // EndDragging(); + // } + //} + //else if (LeftButtonUp(currentState)) + //{ + // if (!_dragging) + // { + // var clickDelta = (DateTime.Now - _clickTime).TotalMilliseconds; + // if (clickDelta < 75) + // { + // _dragging = true; + // } + // } + // else + // { + // EndDragging(); + // } + //} + + //if (!_dragging && _followMouse && (DateTime.Now - _clickTime).TotalMilliseconds >= 75) + // _dragging = true; + } + + private bool LeftButtonDown => + MouseOver && MouseOverPreviously && + CurrentMouseState.LeftButton == ButtonState.Pressed && + PreviousMouseState.LeftButton == ButtonState.Released; + + + private bool LeftButtonUp => + CurrentMouseState.LeftButton == ButtonState.Released && + PreviousMouseState.LeftButton == ButtonState.Pressed; + + private void EndDragging() + { + //_dragging = false; + //_followMouse = false; + + //var newSlot = GetCurrentHoverSlot(); + //_parentSpellContainer.MoveItem(this, newSlot); + } + + //private int GetCurrentHoverSlot() + //{ + // return _parentSpellContainer.GetCurrentHoverSlot(); + //} + + private void DrawSpellIcon() + { + Rectangle targetDrawArea; + Color alphaColor; + if (!_followMouse) + { + targetDrawArea = new Rectangle( + DrawAreaWithParentOffset.X + (DrawAreaWithParentOffset.Width - _spellGraphicSourceRect.Width) / 2, + DrawAreaWithParentOffset.Y + (DrawAreaWithParentOffset.Height - _spellGraphicSourceRect.Height) / 2, + _spellGraphicSourceRect.Width, + _spellGraphicSourceRect.Height); + alphaColor = Color.White; + } + else + { + targetDrawArea = new Rectangle( + Mouse.GetState().X - _spellGraphicSourceRect.Width / 2, + Mouse.GetState().Y - _spellGraphicSourceRect.Height / 2, + _spellGraphicSourceRect.Width, + _spellGraphicSourceRect.Height + ); + alphaColor = Color.FromNonPremultiplied(255, 255, 255, 128); + } + + if (targetDrawArea.Width * targetDrawArea.Height == 0) + return; + + _spriteBatch.Draw(_spellGraphic, targetDrawArea, _spellGraphicSourceRect, alphaColor); + } + + private void DrawSpellLevel() + { + if (_followMouse || _dragging || _spellLevelColor == null) + return; + + _spriteBatch.Draw(_spellLevelColor, _levelDestinationRectangle, Color.FromNonPremultiplied(0xc9, 0xb8, 0x9b, 0xff)); + } + } +} From e9bd17b658b96dabb8dee21da9631a3d40c4f7cb Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 20:39:47 -0700 Subject: [PATCH 5/9] Interaction with active spells (except for leveling up) --- EOLib/misc.cs | 1 + EndlessClient/HUD/Panels/ActiveSpellsPanel.cs | 196 +++++++++++++++--- .../HUD/Spells/BaseSpellPanelItem.cs | 6 + EndlessClient/HUD/Spells/ISpellPanelItem.cs | 7 + EndlessClient/HUD/Spells/SpellPanelItem.cs | 121 ++++++----- 5 files changed, 247 insertions(+), 84 deletions(-) diff --git a/EOLib/misc.cs b/EOLib/misc.cs index aec71ad1f..aaef9e8b1 100644 --- a/EOLib/misc.cs +++ b/EOLib/misc.cs @@ -47,6 +47,7 @@ public static class Constants public const string IgnoreListFile = "config/ignore.ini"; public const string InventoryFile = "config/inventory.ini"; + public const string SpellsFile = "config/spells.ini"; //Should be easily customizable between different clients (based on graphics) //not a config option because this shouldn't be exposed at the user level diff --git a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs index 666fa3e62..009cc1818 100644 --- a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs +++ b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs @@ -13,6 +13,7 @@ using Microsoft.Win32; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; using Optional; using Optional.Collections; using System; @@ -23,6 +24,8 @@ using System.Runtime.Versioning; using XNAControls; +using static EndlessClient.HUD.Spells.SpellPanelItem; + namespace EndlessClient.HUD.Panels { public class ActiveSpellsPanel : XNAPanel, IHudPanel @@ -42,14 +45,16 @@ public class ActiveSpellsPanel : XNAPanel, IHudPanel private readonly List _childItems; private readonly Texture2D _functionKeyLabelSheet; - private readonly Rectangle _functionKeyRow1Source, _functionKeyRow2Source; + private Rectangle _functionKeyRow1Source, _functionKeyRow2Source; - private XNALabel _selectedSpellName, _selectedSpellLevel, _totalSkillPoints; - private XNAButton _levelUpButton1, _levelUpButton2; - private ScrollBar _scrollBar; + private readonly XNALabel _selectedSpellName, _selectedSpellLevel, _totalSkillPoints; + private readonly XNAButton _levelUpButton1, _levelUpButton2; + private readonly ScrollBar _scrollBar; private HashSet _cachedSpells; private bool _confirmedTraining; + private int _lastScrollOffset; + private Texture2D _activeSpellIcon; public INativeGraphicsManager NativeGraphicsManager { get; } @@ -221,10 +226,9 @@ protected override void OnUpdateControl(GameTime gameTime) var newChild = new SpellPanelItem(this, slot, spell, spellData); newChild.Initialize(); - newChild.Selected += SetSpellStatusLabelSelected; + newChild.Selected += SetSelectedSpell; newChild.OnMouseOver += SetSpellStatusLabelHover; - //newItem.DoubleClick += HandleItemDoubleClick; - //newItem.DoneDragging += HandleItemDoneDragging; + newChild.DoneDragging += ItemDraggingCompleted; _childItems.Add(newChild); }); @@ -233,12 +237,70 @@ protected override void OnUpdateControl(GameTime gameTime) _cachedSpells = _characterInventoryProvider.SpellInventory.ToHashSet(); } + if ((CurrentKeyState.IsKeyDown(Keys.RightShift) && PreviousKeyState.IsKeyUp(Keys.RightShift)) || + (CurrentKeyState.IsKeyDown(Keys.LeftShift) && PreviousKeyState.IsKeyUp(Keys.LeftShift)) || + (CurrentKeyState.IsKeyUp(Keys.RightShift) && PreviousKeyState.IsKeyDown(Keys.RightShift)) || + (CurrentKeyState.IsKeyUp(Keys.LeftShift) && PreviousKeyState.IsKeyDown(Keys.LeftShift))) + { + SwapFunctionKeySourceRectangles(); + } + + if (_lastScrollOffset != _scrollBar.ScrollOffset) + { + UpdateSpellItemsForScroll(); + } + base.OnUpdateControl(gameTime); } protected override void OnDrawControl(GameTime gameTime) { base.OnDrawControl(gameTime); + + _spriteBatch.Begin(); + + DrawFunctionKeyLabels(); + DrawActiveSpell(); + + _spriteBatch.End(); + } + + private void DrawFunctionKeyLabels() + { + if (_scrollBar.ScrollOffset >= 2) + return; + + for (int i = 0; i < 8; ++i) + { + var offset = _functionKeyRow1Source.Width * i; + + if (_scrollBar.ScrollOffset == 0) + { + _spriteBatch.Draw(_functionKeyLabelSheet, + new Vector2(202 + 45 * i, 338), + _functionKeyRow1Source.WithPosition((_functionKeyRow1Source.Location + new Point(offset, 0)).ToVector2()), + Color.White); + } + + if (_scrollBar.ScrollOffset < 2) + { + var yCoord = _scrollBar.ScrollOffset == 0 ? 390 : 338; + _spriteBatch.Draw(_functionKeyLabelSheet, + new Vector2(202 + 45 * i, yCoord), + _functionKeyRow2Source.WithPosition((_functionKeyRow2Source.Location + new Point(offset, 0)).ToVector2()), + Color.White); + } + } + } + + private void DrawActiveSpell() + { + if (_activeSpellIcon == null) + return; + + var srcRect = new Rectangle(0, 0, _activeSpellIcon.Width / 2, _activeSpellIcon.Height); + var dstRect = new Rectangle(DrawAreaWithParentOffset.X + 32, DrawAreaWithParentOffset.Y + 14, srcRect.Width, srcRect.Height); + _spriteBatch.Draw(_activeSpellIcon, dstRect, srcRect, Color.White); } private void LevelUp_Click(object sender, EventArgs args) @@ -259,20 +321,32 @@ private void LevelUp_Click(object sender, EventArgs args) } else { - //var selectedSpell = _childItems.Single(x => x.Selected); + var selectedSpell = _childItems.SingleOrNone(x => x.IsSelected); // todo: implement in training controller //_trainingController.LevelUpSpell(selectedSpell.SpellData.ID); } } - private void SetSpellStatusLabelSelected(object sender, EventArgs e) + private void SetSelectedSpell(object sender, EventArgs e) { - var spell = ((SpellPanelItem)sender).SpellData; + ClearSelectedSpell(); - if (spell.Target == EOLib.IO.SpellTarget.Normal) - _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, spell.Name, EOResourceID.SPELL_WAS_SELECTED); - else if (spell.Target == EOLib.IO.SpellTarget.Group /*&& not in party*/) // todo: parties + var spell = (SpellPanelItem)sender; + + var spellData = spell.SpellData; + if (spellData.Target == EOLib.IO.SpellTarget.Normal) + _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, spellData.Name, EOResourceID.SPELL_WAS_SELECTED); + else if (spellData.Target == EOLib.IO.SpellTarget.Group /*&& not in party*/) // todo: parties _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_ONLY_WORKS_ON_GROUP); + + _activeSpellIcon = NativeGraphicsManager.TextureFromResource(GFXTypes.SpellIcons, spellData.Icon); + + _selectedSpellName.Text = spellData.Name; + _selectedSpellName.Visible = true; + + _selectedSpellLevel.Text = spell.InventorySpell.Level.ToString(); + + _levelUpButton1.Visible = _levelUpButton2.Visible = _characterProvider.MainCharacter.Stats[CharacterStat.SkillPoints] > 0; } private void SetSpellStatusLabelHover(object sender, EventArgs e) @@ -281,6 +355,35 @@ private void SetSpellStatusLabelHover(object sender, EventArgs e) _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, spell.Name); } + private void ItemDraggingCompleted(object sender, SpellDragCompletedEventArgs e) + { + var item = (SpellPanelItem)sender; + + _childItems.SingleOrNone(x => x.MouseOver) + .Match(child => + { + if (child is SpellPanelItem && child != item) + { + e.ContinueDragging = true; + } + else if (child != item) + { + var oldSlot = item.Slot; + var oldDisplaySlot = item.DisplaySlot; + + var newSlot = child.Slot; + var newDisplaySlot = child.DisplaySlot; + + item.Slot = newSlot; + item.DisplaySlot = newDisplaySlot; + + child.Slot = oldSlot; + child.DisplaySlot = oldDisplaySlot; + } + }, + () => e.ContinueDragging = true); + } + private static Option GetNextOpenSlot(IEnumerable childItems) { // get the minimum slot when there is an Empty space @@ -303,22 +406,61 @@ private ISpellPanelItem CreateEmptySpell(int slot) emptyItem.Selected += (_, _) => _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_NOTHING_WAS_SELECTED); emptyItem.Clicked += (_, _) => { - //if (DoEmptySpellIconUpdateLogic) - //{ - // if (MouseOver && MouseOverPreviously && - // _parentSpellContainer.AnySpellsSelected() && - // !_parentSpellContainer.AnySpellsDragging() && - // Mouse.GetState().LeftButton == ButtonState.Released && - // PreviousMouseState.LeftButton == ButtonState.Pressed) - // { - // _parentSpellContainer.ClearSelectedSpell(); - // } - //} + if (!AnySpellsDragging()) + ClearSelectedSpell(); }; emptyItem.Initialize(); return emptyItem; } + private void ClearSelectedSpell() + { + _activeSpellIcon = null; + + _selectedSpellName.Text = string.Empty; + _selectedSpellName.Visible = false; + + _selectedSpellLevel.Text = "0"; + + _levelUpButton1.Visible = _levelUpButton2.Visible = false; + + foreach (var item in _childItems.Where(x => x.IsSelected)) + item.IsSelected = false; + } + private void SwapFunctionKeySourceRectangles() + { + var tmpRect = _functionKeyRow2Source; + _functionKeyRow2Source = _functionKeyRow1Source; + _functionKeyRow1Source = tmpRect; + } + + private void UpdateSpellItemsForScroll() + { + var firstValidSlot = _scrollBar.ScrollOffset * SpellRowLength; + var lastValidSlot = firstValidSlot + 2 * SpellRowLength; + + var itemsToHide = _childItems.Where(x => x.Slot < firstValidSlot || x.Slot >= lastValidSlot).ToList(); + foreach (var item in itemsToHide) + { + ((XNAControl)item).Visible = false; + item.DisplaySlot = GetDisplaySlotFromSlot(item.Slot); + } + + foreach (var item in _childItems.Except(itemsToHide)) + { + ((XNAControl)item).Visible = true; + item.DisplaySlot = item.Slot - firstValidSlot; + } + + _lastScrollOffset = _scrollBar.ScrollOffset; + } + + private int GetDisplaySlotFromSlot(int newSlot) + { + var offset = _scrollBar.ScrollOffset; + return newSlot - SpellRowLength * offset; + } + protected override void Dispose(bool disposing) { if (disposing) @@ -336,7 +478,7 @@ private static Dictionary GetSpellSlotMap(string accountName, string c { var map = new Dictionary(); - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !File.Exists(Constants.InventoryFile)) + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows) && !File.Exists(Constants.SpellsFile)) { using var inventoryKey = TryGetCharacterRegistryKey(accountName, characterName); if (inventoryKey != null) @@ -349,7 +491,7 @@ private static Dictionary GetSpellSlotMap(string accountName, string c } } - var inventory = new IniReader(Constants.InventoryFile); + var inventory = new IniReader(Constants.SpellsFile); if (inventory.Load() && inventory.Sections.ContainsKey(accountName)) { var section = inventory.Sections[accountName]; @@ -392,7 +534,7 @@ private static RegistryKey TryGetCharacterRegistryKey(string accountName, string private void SaveSpellsFile(object sender, EventArgs e) { - var inventory = new IniReader(Constants.InventoryFile); + var inventory = new IniReader(Constants.SpellsFile); var section = inventory.Load() && inventory.Sections.ContainsKey(_playerInfoProvider.LoggedInAccountName) ? inventory.Sections[_playerInfoProvider.LoggedInAccountName] diff --git a/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs b/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs index 293fba172..537579926 100644 --- a/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs +++ b/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs @@ -7,6 +7,8 @@ using Microsoft.Xna.Framework.Input; using XNAControls; +using static EndlessClient.HUD.Spells.SpellPanelItem; + namespace EndlessClient.HUD.Spells { public abstract class BaseSpellPanelItem : XNAControl, ISpellPanelItem @@ -52,6 +54,8 @@ public virtual bool IsSelected public event EventHandler Selected; + public event EventHandler DoneDragging; + private readonly Texture2D _highlightColor; protected readonly ActiveSpellsPanel _parentPanel; @@ -93,6 +97,8 @@ protected override void OnDrawControl(GameTime gameTime) base.OnDrawControl(gameTime); } + protected void InvokeDragCompleted(SpellDragCompletedEventArgs e) => DoneDragging?.Invoke(this, e); + protected override void Dispose(bool disposing) { if (disposing) diff --git a/EndlessClient/HUD/Spells/ISpellPanelItem.cs b/EndlessClient/HUD/Spells/ISpellPanelItem.cs index 669395303..1c5c00817 100644 --- a/EndlessClient/HUD/Spells/ISpellPanelItem.cs +++ b/EndlessClient/HUD/Spells/ISpellPanelItem.cs @@ -3,12 +3,16 @@ using System; using XNAControls; +using static EndlessClient.HUD.Spells.SpellPanelItem; + namespace EndlessClient.HUD.Spells { public interface ISpellPanelItem : IXNAControl { int Slot { get; set; } + int DisplaySlot { get; set; } + bool IsSelected { get; set; } bool IsBeingDragged { get; } @@ -18,6 +22,9 @@ public interface ISpellPanelItem : IXNAControl ESFRecord SpellData { get; } event EventHandler Clicked; + event EventHandler Selected; + + event EventHandler DoneDragging; } } diff --git a/EndlessClient/HUD/Spells/SpellPanelItem.cs b/EndlessClient/HUD/Spells/SpellPanelItem.cs index 4b3569497..2a1c26390 100644 --- a/EndlessClient/HUD/Spells/SpellPanelItem.cs +++ b/EndlessClient/HUD/Spells/SpellPanelItem.cs @@ -11,6 +11,11 @@ namespace EndlessClient.HUD.Spells { public class SpellPanelItem : BaseSpellPanelItem { + public class SpellDragCompletedEventArgs + { + public bool ContinueDragging { get; set; } + } + private readonly Texture2D _spellGraphic, _spellLevelColor; private Rectangle _spellGraphicSourceRect; @@ -18,19 +23,12 @@ public class SpellPanelItem : BaseSpellPanelItem private bool _dragging, _followMouse; private Rectangle _levelDestinationRectangle; - private IInventorySpell _inventorySpell; - public override IInventorySpell InventorySpell - { - get => _inventorySpell; - set - { - _inventorySpell = value; + private int _lastSlot; + private IInventorySpell _lastInventorySpell; - //36 is full width of level bar - var width = (int)(InventorySpell.Level / 100.0 * 36); - _levelDestinationRectangle = new Rectangle(DrawAreaWithParentOffset.X + 3, DrawAreaWithParentOffset.Y + 40, width, 6); - } - } + public override IInventorySpell InventorySpell { get; set; } + + public override bool IsBeingDragged => _dragging; public override ESFRecord SpellData { get; } @@ -56,6 +54,16 @@ protected override void OnUpdateControl(GameTime gameTime) { DoClickAndDragLogic(); + if (_lastSlot != DisplaySlot || _lastInventorySpell != InventorySpell) + { + //36 is full width of level bar + var width = (int)(InventorySpell.Level / 100.0 * 36); + _levelDestinationRectangle = new Rectangle(DrawAreaWithParentOffset.X + 3, DrawAreaWithParentOffset.Y + 40, width, 6); + + _lastSlot = DisplaySlot; + _lastInventorySpell = InventorySpell; + } + base.OnUpdateControl(gameTime); } @@ -77,41 +85,40 @@ private void SetIconHover(bool hover) private void DoClickAndDragLogic() { - //if (!_dragging && _parentPanel.AnySpellsDragging()) - // return; - - //var currentState = Mouse.GetState(); - //if (LeftButtonDown(currentState)) - //{ - // if (!_dragging) - // { - // _followMouse = true; - // _clickTime = DateTime.Now; - // _parentSpellContainer.SetSelectedSpellBySlot(Slot); - // } - // else - // { - // EndDragging(); - // } - //} - //else if (LeftButtonUp(currentState)) - //{ - // if (!_dragging) - // { - // var clickDelta = (DateTime.Now - _clickTime).TotalMilliseconds; - // if (clickDelta < 75) - // { - // _dragging = true; - // } - // } - // else - // { - // EndDragging(); - // } - //} - - //if (!_dragging && _followMouse && (DateTime.Now - _clickTime).TotalMilliseconds >= 75) - // _dragging = true; + if (!_dragging && _parentPanel.AnySpellsDragging()) + return; + + if (LeftButtonDown) + { + if (!_dragging) + { + _followMouse = true; + _clickTime = DateTime.Now; + IsSelected = true; + } + else + { + EndDragging(); + } + } + else if (LeftButtonUp) + { + if (!_dragging) + { + var clickDelta = (DateTime.Now - _clickTime).TotalMilliseconds; + if (clickDelta < 75) + { + _dragging = true; + } + } + else + { + EndDragging(); + } + } + + if (!_dragging && _followMouse && (DateTime.Now - _clickTime).TotalMilliseconds >= 75) + _dragging = true; } private bool LeftButtonDown => @@ -119,24 +126,24 @@ private void DoClickAndDragLogic() CurrentMouseState.LeftButton == ButtonState.Pressed && PreviousMouseState.LeftButton == ButtonState.Released; - private bool LeftButtonUp => CurrentMouseState.LeftButton == ButtonState.Released && PreviousMouseState.LeftButton == ButtonState.Pressed; private void EndDragging() { - //_dragging = false; - //_followMouse = false; + _dragging = false; + _followMouse = false; - //var newSlot = GetCurrentHoverSlot(); - //_parentSpellContainer.MoveItem(this, newSlot); - } + var args = new SpellDragCompletedEventArgs(); + InvokeDragCompleted(args); - //private int GetCurrentHoverSlot() - //{ - // return _parentSpellContainer.GetCurrentHoverSlot(); - //} + if (args.ContinueDragging) + { + _dragging = true; + _followMouse = true; + } + } private void DrawSpellIcon() { From d951cb96df59e88cafe761d6c65d24ce91878a11 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 20:40:32 -0700 Subject: [PATCH 6/9] Remove old spell icon/panel code --- EndlessClient/HUD/Controls/HUD.cs | 17 - .../HUD/Panels/Old/OldActiveSpellsPanel.cs | 477 ------------------ .../HUD/Spells/Old/EmptySpellIcon.cs | 137 ----- EndlessClient/HUD/Spells/Old/ISpellIcon.cs | 24 - EndlessClient/HUD/Spells/Old/SpellIcon.cs | 243 --------- EndlessClient/Old/PacketAPICallbackManager.cs | 23 +- .../Rendering/OldCharacterRenderer.cs | 12 +- 7 files changed, 11 insertions(+), 922 deletions(-) delete mode 100644 EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs delete mode 100644 EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs delete mode 100644 EndlessClient/HUD/Spells/Old/ISpellIcon.cs delete mode 100644 EndlessClient/HUD/Spells/Old/SpellIcon.cs diff --git a/EndlessClient/HUD/Controls/HUD.cs b/EndlessClient/HUD/Controls/HUD.cs index a591f7465..0735ce0e7 100644 --- a/EndlessClient/HUD/Controls/HUD.cs +++ b/EndlessClient/HUD/Controls/HUD.cs @@ -27,7 +27,6 @@ public class HUD : DrawableGameComponent private readonly OldChatRenderer chatRenderer; private readonly OldEOPartyPanel m_party; - private OldActiveSpells activeSpells; private ChatTextBox chatTextBox; @@ -80,9 +79,6 @@ public override void Initialize() //the draw orders are adjusted for child items in the constructor. //calling SetParent will break this. - //activeSpells = new OldActiveSpells(pnlActiveSpells, m_packetAPI); - activeSpells.Initialize(); - SessionStartTime = DateTime.Now; base.Initialize(); @@ -118,12 +114,6 @@ public void SetStatusLabel(EOResourceID type, string detail) //SetStatusLabelText(string.Format("[ {0} ] {1}", typeText, detail)); } - public void RefreshStats() - { - if (activeSpells != null) - activeSpells.RefreshTotalSkillPoints(); - } - public void SetPartyData(List party) { m_party.SetData(party); } public void AddPartyMember(PartyMember member) { m_party.AddMember(member); } public void RemovePartyMember(short memberID) { m_party.RemoveMember(memberID); } @@ -131,13 +121,6 @@ public void RefreshStats() public bool MainPlayerIsInParty() { return m_party.PlayerIsMember((short)OldWorld.Instance.MainPlayer.ActiveCharacter.ID); } public bool PlayerIsPartyMember(short playerID) { return m_party.PlayerIsMember(playerID); } - public void AddNewSpellToActiveSpellsByID(int spellID) { activeSpells.AddNewSpellToNextOpenSlot(spellID); } - public ESFRecord GetSpellFromIndex(int index) { return activeSpells.GetSpellRecordBySlot(index); } - public void SetSelectedSpell(int index) { activeSpells.SetSelectedSpellBySlot(index); } - public void RemoveSpellFromActiveSpellsByID(int spellID) { activeSpells.RemoveSpellByID(spellID); } - public void UpdateActiveSpellLevelByID(short spellID, short spellLevel) { activeSpells.UpdateSpellLevelByID(spellID, spellLevel); } - public void RemoveAllSpells() { activeSpells.RemoveAllSpells(); } - #endregion protected override void Dispose(bool disposing) diff --git a/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs b/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs deleted file mode 100644 index da035d8cc..000000000 --- a/EndlessClient/HUD/Panels/Old/OldActiveSpellsPanel.cs +++ /dev/null @@ -1,477 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EndlessClient.Dialogs; -using EndlessClient.HUD.Spells.Old; -using EndlessClient.Old; -using EndlessClient.UIControls; -using EOLib; -using EOLib.Graphics; -using EOLib.IO.Pub; -using EOLib.Net.API; -using Microsoft.Win32; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; -using XNAControls.Old; - -namespace EndlessClient.HUD.Panels.Old -{ - public class OldActiveSpells : XNAControl - { - //number of skills to display in a row - public const int SPELL_NUM_ROWS = 4; - public const int SPELL_ROW_LENGTH = 8; - - private readonly PacketAPI _api; - private readonly RegistryKey _spellsKey; - - private readonly List _childItems; - private readonly Texture2D _functionKeyGraphics; - private Rectangle _functionKeyRow1SourceRect; - private Rectangle _functionKeyRow2SourceRect; - - private Texture2D _activeSpellIcon; - - private readonly XNALabel _selectedSpellName, _selectedSpellLevel, _totalSkillPoints; - private readonly XNAButton _levelUpButton1, _levelUpButton2; - - private int _lastScrollOffset; - private readonly OldScrollBar _scroll; - - private bool _trainWarningShown; - - public OldActiveSpells(XNAPanel parent, PacketAPI api) - : base(null, null, parent) - { - _api = api; - - _childItems = new List(SPELL_NUM_ROWS * SPELL_ROW_LENGTH); - RemoveAllSpells(); - - var localSpellSlotMap = new Dictionary(); - _spellsKey = _tryGetCharacterRegKey(); - if (_spellsKey != null) - { - const string spellFmt = "item{0}"; - for (int i = 0; i < SPELL_ROW_LENGTH*4; ++i) - { - int id; - try - { - id = Convert.ToInt32(_spellsKey.GetValue(String.Format(spellFmt, i))); - } - catch { continue; } - localSpellSlotMap.Add(i, id); - } - } - - var localSpells = OldWorld.Instance.MainPlayer.ActiveCharacter.Spells; - // ReSharper disable once LoopCanBeConvertedToQuery - foreach (var spell in localSpells) - { - var rec = OldWorld.Instance.ESF[spell.ID]; - int slot = localSpellSlotMap.ContainsValue(spell.ID) - ? localSpellSlotMap.First(_pair => _pair.Value == spell.ID).Key - : _getNextOpenSlot(); - - if (slot < 0 || !_addNewSpellToSlot(slot, rec, spell.Level)) - { - EOMessageBox.Show("You have too many spells! They don't all fit.", "Warning", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - break; - } - - if (slot >= SPELL_ROW_LENGTH*(SPELL_NUM_ROWS/2)) - _childItems.Last().Visible = false; - } - - _setSize(parent.DrawArea.Width, parent.DrawArea.Height); - - _functionKeyGraphics = ((EOGame) Game).GFXManager.TextureFromResource(GFXTypes.PostLoginUI, 58, true); - _functionKeyRow1SourceRect = new Rectangle(148, 51, 18, 13); - _functionKeyRow2SourceRect = new Rectangle(148 + 18*8, 51, 18, 13); - - _selectedSpellName = new XNALabel(new Rectangle(9, 50, 81, 13), Constants.FontSize08pt5) - { - Visible = false, - Text = "", - AutoSize = false, - TextAlign = LabelAlignment.MiddleCenter, - ForeColor = ColorConstants.LightGrayText - }; - _selectedSpellName.SetParent(this); - - _selectedSpellLevel = new XNALabel(new Rectangle(32, 78, 42, 15), Constants.FontSize08pt5) - { - Visible = true, - Text = "0", - AutoSize = false, - TextAlign = LabelAlignment.MiddleLeft, - ForeColor = ColorConstants.LightGrayText - }; - _selectedSpellLevel.SetParent(this); - - var skillPoints = OldWorld.Instance.MainPlayer.ActiveCharacter.Stats.SkillPoints; - _totalSkillPoints = new XNALabel(new Rectangle(32, 96, 42, 15), Constants.FontSize08pt5) - { - Visible = true, - Text = skillPoints.ToString(), - AutoSize = false, - TextAlign = LabelAlignment.MiddleLeft, - ForeColor = ColorConstants.LightGrayText - }; - _totalSkillPoints.SetParent(this); - - var buttonSheet = ((EOGame)Game).GFXManager.TextureFromResource(GFXTypes.PostLoginUI, 27, true); - - _levelUpButton1 = new XNAButton(buttonSheet, new Vector2(71, 77), new Rectangle(215, 386, 19, 15), new Rectangle(234, 386, 19, 15)) - { - FlashSpeed = 500, - Visible = false - }; - _levelUpButton1.OnClick += LevelUp_Click; - _levelUpButton1.SetParent(this); - _levelUpButton2 = new XNAButton(buttonSheet, new Vector2(71, 95), new Rectangle(215, 386, 19, 15), new Rectangle(234, 386, 19, 15)) - { - FlashSpeed = 500, - Visible = false - }; - _levelUpButton2.OnClick += LevelUp_Click; - _levelUpButton2.SetParent(this); - - _scroll = new OldScrollBar(this, new Vector2(467, 2), new Vector2(16, 115), ScrollBarColors.LightOnMed) { LinesToRender = 2 }; - _scroll.UpdateDimensions(4); - - foreach (var child in children.Where(x => !(x is EmptySpellIcon))) - OldWorld.IgnoreDialogs(child); - } - - public void AddNewSpellToNextOpenSlot(int spellID) - { - if (_childItems.OfType().Any(x => x.SpellData.ID == spellID)) - return; - - var slot = _getNextOpenSlot(); - var record = OldWorld.Instance.ESF[spellID]; - _addNewSpellToSlot(slot, record, 0); - } - - public void SetSelectedSpellBySlot(int slot) - { - ClearSelectedSpell(); - var item = _childItems.Single(x => x.Slot == slot); - if (item != null) - { - item.Selected = true; - if (item is SpellIcon) - { - _activeSpellIcon = ((EOGame) Game).GFXManager.TextureFromResource(GFXTypes.SpellIcons, item.SpellData.Icon); - _selectedSpellName.Text = item.SpellData.Name; - _selectedSpellName.Visible = true; - _selectedSpellLevel.Text = item.Level.ToString(); - - UpdateLevelUpButtonsVisible(); - } - } - } - - public void ClearSelectedSpell() - { - if (!AnySpellsSelected()) return; - - _childItems.Single(x => x.Selected).Selected = false; - - _selectedSpellName.Visible = false; - _selectedSpellLevel.Text = "0"; - - _levelUpButton1.Visible = false; - _levelUpButton2.Visible = false; - - _activeSpellIcon = null; - } - - public ESFRecord GetSpellRecordBySlot(int slot) - { - var icon = _childItems.SingleOrDefault(x => x.Slot == slot); - - return icon == null ? null : icon.SpellData; - } - - public void RemoveSpellByID(int spellID) - { - var spellToRemove = _childItems.OfType().Single(x => x.SpellData.ID == spellID); - if (spellToRemove.Selected) - ClearSelectedSpell(); - - _childItems.Remove(spellToRemove); - spellToRemove.SetParent(null); - spellToRemove.Close(); - - var newEmptySpell = new EmptySpellIcon(this, spellToRemove.Slot); - newEmptySpell.SetDisplaySlot(GetDisplaySlotFromSlot(newEmptySpell.Slot)); - _childItems.Add(newEmptySpell); - } - - public void UpdateSpellLevelByID(short spellID, short spellLevel) - { - var spell = _childItems.OfType().Single(x => x.SpellData.ID == spellID); - spell.Level = spellLevel; - if (spell.Selected) - { - _selectedSpellLevel.Text = spell.Level.ToString(); - UpdateLevelUpButtonsVisible(); - } - } - - public bool AnySpellsDragging() - { - return _childItems.Any(x => x.IsDragging); - } - - public bool AnySpellsSelected() - { - return _childItems.Any(x => x.Selected); - } - - public int GetCurrentHoverSlot() - { - if (!_childItems.Any(x => x.MouseOver && x.Visible)) - return -1; - return _childItems.Single(x => x.MouseOver && x.Visible).Slot; - } - - public void MoveItem(ISpellIcon spellIcon, int newSlot) - { - if (spellIcon.Slot == newSlot || newSlot < 0 || newSlot > SPELL_NUM_ROWS * SPELL_ROW_LENGTH) - return; - - if (!_childItems.Contains(spellIcon)) - throw new ArgumentException("The spell was not found!", nameof(spellIcon)); - - //update the registry - var spellInDestinationSlot = _childItems.Find(x => x.Slot == newSlot); - if (spellInDestinationSlot is SpellIcon) - _setSpellSlotInRegistry(spellIcon.Slot, spellInDestinationSlot.SpellData.ID); - else - _clearSlotInRegistry(spellIcon.Slot); - _setSpellSlotInRegistry(newSlot, spellIcon.SpellData.ID); - - //set the slots of old/new items - spellInDestinationSlot.Slot = spellIcon.Slot; - spellInDestinationSlot.SetDisplaySlot(GetDisplaySlotFromSlot(spellIcon.Slot)); - spellIcon.Slot = newSlot; - spellIcon.SetDisplaySlot(GetDisplaySlotFromSlot(newSlot)); - } - - public void RefreshTotalSkillPoints() - { - var skillPoints = OldWorld.Instance.MainPlayer.ActiveCharacter.Stats.SkillPoints; - _totalSkillPoints.Text = skillPoints.ToString(); - - UpdateLevelUpButtonsVisible(); - } - - public void RemoveAllSpells() - { - ClearSelectedSpell(); - - _childItems.OfType().ToList() - .ForEach(x => - { - x.SetParent(null); - x.Close(); - }); - _childItems.Clear(); - - for (int slot = 0; slot < SPELL_NUM_ROWS * SPELL_ROW_LENGTH; ++slot) - _childItems.Add(new EmptySpellIcon(this, slot)); - } - - public override void Update(GameTime gameTime) - { - if (!ShouldUpdate()) return; - - if ((Keyboard.GetState().IsKeyDown(Keys.RightShift) && PreviousKeyState.IsKeyUp(Keys.RightShift)) || - (Keyboard.GetState().IsKeyDown(Keys.LeftShift) && PreviousKeyState.IsKeyUp(Keys.LeftShift)) || - (Keyboard.GetState().IsKeyUp(Keys.RightShift) && PreviousKeyState.IsKeyDown(Keys.RightShift)) || - (Keyboard.GetState().IsKeyUp(Keys.LeftShift) && PreviousKeyState.IsKeyDown(Keys.LeftShift))) - _swapFunctionKeySourceRectangles(); - - if (_lastScrollOffset != _scroll.ScrollOffset) - UpdateIconsForScroll(); - - base.Update(gameTime); - } - - public override void Draw(GameTime gameTime) - { - if (!Visible) return; - - //draw spell icons first - base.Draw(gameTime); - - SpriteBatch.Begin(); - DrawFunctionKeyLabels(); - DrawActiveSpell(); - SpriteBatch.End(); - } - - private void DrawFunctionKeyLabels() - { - for (int i = 0; i < 8; ++i) - { - var offset = (float) _functionKeyRow1SourceRect.Width*i; - - if (_scroll.ScrollOffset == 0) - { - SpriteBatch.Draw(_functionKeyGraphics, - new Vector2(202 + 45*i, 338), - _functionKeyRow1SourceRect.WithPosition(new Vector2(_functionKeyRow1SourceRect.X + offset, - _functionKeyRow1SourceRect.Y)), - Color.White); - } - - if (_scroll.ScrollOffset < 2) - { - var yCoord = _scroll.ScrollOffset == 0 ? 390 : 338; - SpriteBatch.Draw(_functionKeyGraphics, - new Vector2(202 + 45*i, yCoord), - _functionKeyRow2SourceRect.WithPosition(new Vector2(_functionKeyRow2SourceRect.X + offset, - _functionKeyRow2SourceRect.Y)), - Color.White); - } - } - } - - private void DrawActiveSpell() - { - if (_activeSpellIcon == null) - return; - - var srcRect = new Rectangle(0, 0, _activeSpellIcon.Width/2, _activeSpellIcon.Height); - var dstRect = new Rectangle(DrawAreaWithOffset.X + 32, DrawAreaWithOffset.Y + 14, srcRect.Width, srcRect.Height); - SpriteBatch.Draw(_activeSpellIcon, dstRect, srcRect, Color.White); - } - - private void LevelUp_Click(object sender, EventArgs args) - { - if (!_trainWarningShown) - { - //apparently this is NOT stored in the edf files... - //NOTE: copy-pasted from EOCharacterStats button event handler. Should probably be in some shared function somewhere. - EOMessageBox.Show("Do you want to train?", "Skill training", EODialogButtons.OkCancel, - EOMessageBoxStyle.SmallDialogSmallHeader, - (s, e) => - { - if (e.Result != XNADialogResult.OK) return; - _trainWarningShown = true; - }); - - return; - } - - var selectedSpell = _childItems.Single(x => x.Selected); - if (selectedSpell == null || !_api.LevelUpSpell((short) selectedSpell.SpellData.ID)) - ((EOGame) Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - } - - private static RegistryKey _tryGetCharacterRegKey() - { - try - { - using (RegistryKey currentUser = Registry.CurrentUser) - { - return currentUser.CreateSubKey( - $"Software\\EndlessClient\\{OldWorld.Instance.MainPlayer.AccountName}\\{OldWorld.Instance.MainPlayer.ActiveCharacter.Name}\\spells", - RegistryKeyPermissionCheck.ReadWriteSubTree); - } - } - catch (NullReferenceException) { } - return null; - } - - private bool _addNewSpellToSlot(int slot, ESFRecord data, short level) - { - var emptySpellInDestinationSlot = _childItems.Single(x => x.Slot == slot); - if (slot < 0 || !(emptySpellInDestinationSlot is EmptySpellIcon)) - return false; - - _setSpellSlotInRegistry(slot, data.ID); - _childItems.Remove(emptySpellInDestinationSlot); - ((XNAControl)emptySpellInDestinationSlot).SetParent(null); - ((XNAControl)emptySpellInDestinationSlot).Close(); - - var newSpell = new SpellIcon(this, data, slot) {Level = level}; - - var displaySlot = GetDisplaySlotFromSlot(slot); - newSpell.SetDisplaySlot(displaySlot); - - var scrollOffset = _scroll == null ? 0 : _scroll.ScrollOffset; - newSpell.Visible = displaySlot >= scrollOffset*SPELL_ROW_LENGTH && - displaySlot < scrollOffset*SPELL_ROW_LENGTH + 2*SPELL_ROW_LENGTH; - - _childItems.Add(newSpell); - - return true; - } - - private int _getNextOpenSlot() - { - //SpellIcon is EmptySpellIcon, so can't compare with EmptySpellIcon or we'll get SpellIcon too - if (_childItems.All(x => x is SpellIcon)) - return -1; - return _childItems.Where(x => !(x is SpellIcon)).Select(x => x.Slot).Min(); - } - - private void _setSpellSlotInRegistry(int slot, int id) - { - _spellsKey.SetValue($"item{slot}", id, RegistryValueKind.String); - } - - private void _clearSlotInRegistry(int slot) - { - _setSpellSlotInRegistry(slot, 0); - } - - private void _swapFunctionKeySourceRectangles() - { - var tmpRect = _functionKeyRow2SourceRect; - _functionKeyRow2SourceRect = _functionKeyRow1SourceRect; - _functionKeyRow1SourceRect = tmpRect; - } - - private void UpdateLevelUpButtonsVisible() - { - var pts = OldWorld.Instance.MainPlayer.ActiveCharacter.Stats.SkillPoints; - _levelUpButton1.Visible = pts > 0 && AnySpellsSelected(); - _levelUpButton2.Visible = pts > 0 && AnySpellsSelected(); - } - - private void UpdateIconsForScroll() - { - var firstValidSlot = _scroll.ScrollOffset*SPELL_ROW_LENGTH; - var lastValidSlot = firstValidSlot + 2*SPELL_ROW_LENGTH; - - var itemsToHide = _childItems.Where(x => x.Slot < firstValidSlot || x.Slot >= lastValidSlot).ToList(); - foreach (var item in itemsToHide) - { - item.Visible = false; - item.SetDisplaySlot(GetDisplaySlotFromSlot(item.Slot)); - } - - foreach (var item in _childItems.Except(itemsToHide)) - { - item.Visible = true; - item.SetDisplaySlot(item.Slot - firstValidSlot); - } - - _lastScrollOffset = _scroll.ScrollOffset; - } - - private int GetDisplaySlotFromSlot(int newSlot) - { - var offset = _scroll == null ? 0 : _scroll.ScrollOffset; - return newSlot - SPELL_ROW_LENGTH * offset; - } - } -} diff --git a/EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs b/EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs deleted file mode 100644 index 7cf450ff3..000000000 --- a/EndlessClient/HUD/Spells/Old/EmptySpellIcon.cs +++ /dev/null @@ -1,137 +0,0 @@ -using System; -using EndlessClient.HUD.Panels.Old; -using EndlessClient.Old; -using EOLib.IO.Pub; -using EOLib.Localization; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; -using XNAControls.Old; - -namespace EndlessClient.HUD.Spells.Old -{ - public class EmptySpellIcon : XNAControl, ISpellIcon - { - private const int ICON_AREA_WIDTH = 42, ICON_AREA_HEIGHT = 36; - - private int _slot; - public int Slot - { - get { return _slot; } - set - { - _slot = value; - OnSlotChanged(); - } - } - - public virtual bool Selected - { - get { return false; } - set - { - if (value) - { - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_NOTHING_WAS_SELECTED); - } - } - } - - public virtual short Level - { - get { return 0; } - set { throw new InvalidOperationException("Unable to set Level on an EmptySpellIcon"); } - } - - public virtual bool IsDragging => false; - - public virtual ESFRecord SpellData => null; - - private bool _doUpdateLogic = true; - protected virtual bool DoEmptySpellIconUpdateLogic => _doUpdateLogic; - - protected readonly OldActiveSpells _parentSpellContainer; - private readonly Texture2D _highlightColor; - private bool _doDrawLogic; - - public EmptySpellIcon(OldActiveSpells parent, int slot) - : base(null, null, parent) - { - Slot = slot; - _parentSpellContainer = parent; - - _highlightColor = new Texture2D(Game.GraphicsDevice, 1, 1); - _highlightColor.SetData(new[] { Color.FromNonPremultiplied(200, 200, 200, 60) }); - - _setSize(ICON_AREA_WIDTH, ICON_AREA_HEIGHT); - - OldWorld.IgnoreDialogs(this); - } - - public void SetDisplaySlot(int displaySlot) - { - var currentSlot = _slot; - Slot = displaySlot; - _slot = currentSlot; - } - - public override void Update(GameTime gameTime) - { - if (!ShouldUpdate()) return; - - if (DoEmptySpellIconUpdateLogic) - { - if (MouseOver && MouseOverPreviously && - _parentSpellContainer.AnySpellsSelected() && - !_parentSpellContainer.AnySpellsDragging() && - Mouse.GetState().LeftButton == ButtonState.Released && - PreviousMouseState.LeftButton == ButtonState.Pressed) - { - _parentSpellContainer.ClearSelectedSpell(); - } - } - - if (MouseOver && _parentSpellContainer.AnySpellsDragging()) - _doDrawLogic = true; - else if (_doDrawLogic) - _doDrawLogic = false; - - base.Update(gameTime); - } - - public override void Draw(GameTime gameTime) - { - if (!Visible) return; - - if (_doDrawLogic) - { - SpriteBatch.Begin(); - DrawHighlight(); - SpriteBatch.End(); - } - - base.Draw(gameTime); - } - - protected virtual void OnSlotChanged() - { - //start pos: 101, 97 - //xdelta: 45; ydelta: 52 - var row = Slot / OldActiveSpells.SPELL_ROW_LENGTH; - var col = Slot % OldActiveSpells.SPELL_ROW_LENGTH; - DrawLocation = new Vector2(101 + col * 45, 9 + row * 52); - } - - private void DrawHighlight() - { - SpriteBatch.Draw(_highlightColor, DrawAreaWithOffset, Color.White); - } - - protected override void Dispose(bool disposing) - { - if (disposing) - _highlightColor.Dispose(); - base.Dispose(disposing); - } - } -} diff --git a/EndlessClient/HUD/Spells/Old/ISpellIcon.cs b/EndlessClient/HUD/Spells/Old/ISpellIcon.cs deleted file mode 100644 index 2d4f96d81..000000000 --- a/EndlessClient/HUD/Spells/Old/ISpellIcon.cs +++ /dev/null @@ -1,24 +0,0 @@ -using EOLib.IO.Pub; - -namespace EndlessClient.HUD.Spells.Old -{ - public interface ISpellIcon - { - int Slot { get; set; } - - bool Selected { get; set; } - - bool IsDragging { get; } - - short Level { get; set; } - - ESFRecord SpellData { get; } - - void SetDisplaySlot(int displaySlot); - - //defined in XNAControl base class - bool MouseOver { get; } - - bool Visible { get; set; } - } -} \ No newline at end of file diff --git a/EndlessClient/HUD/Spells/Old/SpellIcon.cs b/EndlessClient/HUD/Spells/Old/SpellIcon.cs deleted file mode 100644 index bad3eb494..000000000 --- a/EndlessClient/HUD/Spells/Old/SpellIcon.cs +++ /dev/null @@ -1,243 +0,0 @@ -using System; -using EndlessClient.HUD.Panels.Old; -using EndlessClient.Old; -using EOLib.Graphics; -using EOLib.IO; -using EOLib.IO.Pub; -using EOLib.Localization; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; - -namespace EndlessClient.HUD.Spells.Old -{ - public class SpellIcon : EmptySpellIcon - { - private bool _selected; - public override bool Selected - { - get { return _selected; } - set - { - _selected = value; - if (_selected) - OnSelected(); - } - } - - private short _level; - public override short Level - { - get { return _level; } - set - { - if (_level != value) - { - _level = value; - OnLevelChanged(); - } - } - } - - public override bool IsDragging => _dragging; - - public override ESFRecord SpellData => _spellData; - - //stops the base class update logic from being called - protected override bool DoEmptySpellIconUpdateLogic => false; - private readonly Texture2D _spellGraphic, _spellLevelColor; - private readonly ESFRecord _spellData; - - private Rectangle _spellGraphicSourceRect; - private DateTime _clickTime; - private bool _dragging, _followMouse; - private Rectangle _levelDestinationRectangle; - - public SpellIcon(OldActiveSpells parent, ESFRecord data, int slot) - : base(parent, slot) - { - _spellData = data; - _spellGraphic = ((EOGame)Game).GFXManager.TextureFromResource(GFXTypes.SpellIcons, _spellData.Icon); - _spellGraphicSourceRect = new Rectangle(0, 0, _spellGraphic.Width / 2, _spellGraphic.Height); - - _spellLevelColor = new Texture2D(Game.GraphicsDevice, 1, 1); - _spellLevelColor.SetData(new[] { Color.FromNonPremultiplied(0xc9, 0xb8, 0x9b, 0xff) }); - OnLevelChanged(); - - _clickTime = DateTime.Now; - } - - public override void Update(GameTime gameTime) - { - if (!ShouldUpdate()) return; - - UpdateIconSourceRect(); - DoClickAndDragLogic(); - - base.Update(gameTime); - } - - public override void Draw(GameTime gameTime) - { - if (!Visible) return; - - SpriteBatch.Begin(); - DrawSpellIcon(); - DrawSpellLevel(); - SpriteBatch.End(); - - base.Draw(gameTime); - } - - protected override void OnSlotChanged() - { - base.OnSlotChanged(); - if (_spellGraphic != null) - SetIconHover(MouseOver); - OnLevelChanged(); - } - - private void OnSelected() - { - var hud = ((EOGame)Game).Hud; - switch (SpellData.Target) - { - case SpellTarget.Normal: - hud.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, SpellData.Name, EOResourceID.SPELL_WAS_SELECTED); - break; - case SpellTarget.Group: - if (!hud.MainPlayerIsInParty()) - hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_ONLY_WORKS_ON_GROUP); - break; - } - } - - private void OnLevelChanged() - { - //36 is full width of level bar - var width = (int)(Level / 100.0 * 36); - _levelDestinationRectangle = new Rectangle(DrawAreaWithOffset.X + 3, DrawAreaWithOffset.Y + 40, width, 6); - } - - private void UpdateIconSourceRect() - { - if (MouseOver && !MouseOverPreviously || - MouseOverPreviously && !MouseOver) - { - SetIconHover(MouseOver); - if (MouseOver && !_parentSpellContainer.AnySpellsDragging()) - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, SpellData.Name); - } - } - - private void SetIconHover(bool hover) - { - var halfWidth = _spellGraphic.Width / 2; - _spellGraphicSourceRect = new Rectangle(hover ? halfWidth : 0, 0, halfWidth, _spellGraphic.Height); - } - - private void DoClickAndDragLogic() - { - if (!_dragging && _parentSpellContainer.AnySpellsDragging()) - return; - - var currentState = Mouse.GetState(); - if (LeftButtonDown(currentState)) - { - if (!_dragging) - { - _followMouse = true; - _clickTime = DateTime.Now; - _parentSpellContainer.SetSelectedSpellBySlot(Slot); - } - else - { - EndDragging(); - } - } - else if (LeftButtonUp(currentState)) - { - if (!_dragging) - { - var clickDelta = (DateTime.Now - _clickTime).TotalMilliseconds; - if (clickDelta < 75) - { - _dragging = true; - } - } - else - { - EndDragging(); - } - } - - if (!_dragging && _followMouse && (DateTime.Now - _clickTime).TotalMilliseconds >= 75) - _dragging = true; - } - - private bool LeftButtonDown(MouseState currentState) - { - return MouseOver && MouseOverPreviously && - currentState.LeftButton == ButtonState.Pressed && - PreviousMouseState.LeftButton == ButtonState.Released; - } - - private bool LeftButtonUp(MouseState currentState) - { - return currentState.LeftButton == ButtonState.Released && - PreviousMouseState.LeftButton == ButtonState.Pressed; - } - - private void EndDragging() - { - _dragging = false; - _followMouse = false; - - var newSlot = GetCurrentHoverSlot(); - _parentSpellContainer.MoveItem(this, newSlot); - } - - private int GetCurrentHoverSlot() - { - return _parentSpellContainer.GetCurrentHoverSlot(); - } - - private void DrawSpellIcon() - { - Rectangle targetDrawArea; - Color alphaColor; - if (!_followMouse) - { - targetDrawArea = new Rectangle( - DrawAreaWithOffset.X + (DrawAreaWithOffset.Width - _spellGraphicSourceRect.Width) / 2, - DrawAreaWithOffset.Y + (DrawAreaWithOffset.Height - _spellGraphicSourceRect.Height) / 2, - _spellGraphicSourceRect.Width, - _spellGraphicSourceRect.Height); - alphaColor = Color.White; - } - else - { - targetDrawArea = new Rectangle( - Mouse.GetState().X - _spellGraphicSourceRect.Width / 2, - Mouse.GetState().Y - _spellGraphicSourceRect.Height / 2, - _spellGraphicSourceRect.Width, - _spellGraphicSourceRect.Height - ); - alphaColor = Color.FromNonPremultiplied(255, 255, 255, 128); - } - - if (targetDrawArea.Width * targetDrawArea.Height == 0) - return; - - SpriteBatch.Draw(_spellGraphic, targetDrawArea, _spellGraphicSourceRect, alphaColor); - } - - private void DrawSpellLevel() - { - if (_followMouse || _dragging || _spellLevelColor == null) - return; - - SpriteBatch.Draw(_spellLevelColor, _levelDestinationRectangle, Color.White); - } - } -} diff --git a/EndlessClient/Old/PacketAPICallbackManager.cs b/EndlessClient/Old/PacketAPICallbackManager.cs index fbfc0cc73..52e914838 100644 --- a/EndlessClient/Old/PacketAPICallbackManager.cs +++ b/EndlessClient/Old/PacketAPICallbackManager.cs @@ -50,7 +50,6 @@ public void AssignCallbacks() m_packetAPI.OnSpellLearnError += _statskillLearnError; m_packetAPI.OnSpellLearnSuccess += _statskillLearnSpellSuccess; m_packetAPI.OnSpellForget += _statskillForgetSpell; - m_packetAPI.OnSpellTrain += _statskillTrainSpell; m_packetAPI.OnCharacterStatsReset += _statskillReset; m_packetAPI.OnPlaySoundEffect += _playSoundEffect; @@ -204,7 +203,7 @@ private void _statskillLearnSpellSuccess(short id, int remaining) if (SkillmasterDialog.Instance != null) SkillmasterDialog.Instance.RemoveSkillByIDFromLearnList(id); //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, remaining); - m_game.Hud.AddNewSpellToActiveSpellsByID(id); + //m_game.Hud.AddNewSpellToActiveSpellsByID(id); } private void _statskillForgetSpell(short id) @@ -212,19 +211,7 @@ private void _statskillForgetSpell(short id) OldWorld.Instance.MainPlayer.ActiveCharacter.Spells.RemoveAll(_spell => _spell.ID == id); EOMessageBox.Show(DialogResourceID.SKILL_FORGET_SUCCESS, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - m_game.Hud.RemoveSpellFromActiveSpellsByID(id); - } - - private void _statskillTrainSpell(short skillPtsRemaining, short spellID, short spellLevel) - { - var character = OldWorld.Instance.MainPlayer.ActiveCharacter; - character.Stats.SkillPoints = skillPtsRemaining; - - var spellNdx = character.Spells.FindIndex(x => x.ID == spellID); - character.Spells[spellNdx] = new InventorySpell(spellID, spellLevel); - - m_game.Hud.RefreshStats(); - m_game.Hud.UpdateActiveSpellLevelByID(spellID, spellLevel); + //m_game.Hud.RemoveSpellFromActiveSpellsByID(id); } private void _statskillReset(StatResetData data) @@ -251,8 +238,8 @@ private void _statskillReset(StatResetData data) c.Stats.Accuracy = data.Accuracy; c.Stats.Evade = data.Evade; c.Stats.Armor = data.Armor; - m_game.Hud.RefreshStats(); - m_game.Hud.RemoveAllSpells(); + //m_game.Hud.RefreshStats(); + //m_game.Hud.RemoveAllSpells(); } private void _playSoundEffect(int effectID) @@ -272,7 +259,7 @@ private void _playerCastGroupSpell(short spellID, short fromPlayerID, short from if (fromPlayerID == OldWorld.Instance.MainPlayer.ActiveCharacter.ID) { OldWorld.Instance.MainPlayer.ActiveCharacter.Stats.TP = fromPlayerTP; - m_game.Hud.RefreshStats(); + //m_game.Hud.RefreshStats(); } } } diff --git a/EndlessClient/Rendering/OldCharacterRenderer.cs b/EndlessClient/Rendering/OldCharacterRenderer.cs index 49dffe5c0..ff5f26165 100644 --- a/EndlessClient/Rendering/OldCharacterRenderer.cs +++ b/EndlessClient/Rendering/OldCharacterRenderer.cs @@ -1039,13 +1039,13 @@ public void SetDamageCounterValue(int value, int pctHealth, bool isHeal = false) */ public void SelectSpell(int spellIndex) { - var toCast = ((EOGame)Game).Hud.GetSpellFromIndex(spellIndex); - if (toCast == null) return; + //var toCast = ((EOGame)Game).Hud.GetSpellFromIndex(spellIndex); + //if (toCast == null) return; - Character.SelectSpell(toCast.ID); - if (toCast.Target == SpellTarget.Self || - (toCast.Target == SpellTarget.Group && ((EOGame) Game).Hud.MainPlayerIsInParty())) - _prepareSpell(); + //Character.SelectSpell(toCast.ID); + //if (toCast.Target == SpellTarget.Self || + // (toCast.Target == SpellTarget.Group && ((EOGame) Game).Hud.MainPlayerIsInParty())) + // _prepareSpell(); } public void SetSpellTarget(DrawableGameComponent target) From 40bcd01accad18b6ffb39088bc06bfdc5552d2fd Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 21:00:27 -0700 Subject: [PATCH 7/9] Packet handling for training a spell --- ...tTrainingActions.cs => TrainingActions.cs} | 19 ++++++-- EOLib/Net/API/StatSkill.cs | 34 --------------- EOLib/PacketHandlers/SpellTrainingHandler.cs | 43 +++++++++++++++++++ .../Controllers/TrainingController.cs | 15 +++++-- EndlessClient/Dialogs/Old/TradeDialog.cs | 2 +- EndlessClient/HUD/Panels/ActiveSpellsPanel.cs | 5 +-- 6 files changed, 72 insertions(+), 46 deletions(-) rename EOLib/Domain/Character/{StatTrainingActions.cs => TrainingActions.cs} (77%) create mode 100644 EOLib/PacketHandlers/SpellTrainingHandler.cs diff --git a/EOLib/Domain/Character/StatTrainingActions.cs b/EOLib/Domain/Character/TrainingActions.cs similarity index 77% rename from EOLib/Domain/Character/StatTrainingActions.cs rename to EOLib/Domain/Character/TrainingActions.cs index 2527fdc7d..756228ac8 100644 --- a/EOLib/Domain/Character/StatTrainingActions.cs +++ b/EOLib/Domain/Character/TrainingActions.cs @@ -5,13 +5,12 @@ namespace EOLib.Domain.Character { - //todo: maybe this should go into its own namespace? Domain.Character is pretty monolithic [AutoMappedType] - public class StatTrainingActions : IStatTrainingActions + public class TrainingActions : ITrainingActions { private readonly IPacketSendService _packetSendService; - public StatTrainingActions(IPacketSendService packetSendService) + public TrainingActions(IPacketSendService packetSendService) { _packetSendService = packetSendService; } @@ -29,6 +28,16 @@ public void LevelUpStat(CharacterStat whichStat) _packetSendService.SendPacket(packet); } + public void LevelUpSkill(int spellId) + { + var packet = new PacketBuilder(PacketFamily.StatSkill, PacketAction.Add) + .AddChar((byte)TrainType.Skill) + .AddShort((short)spellId) + .Build(); + + _packetSendService.SendPacket(packet); + } + private static bool InvalidStat(CharacterStat whichStat) { switch (whichStat) @@ -59,8 +68,10 @@ private static short GetStatIndex(CharacterStat whichStat) } } - public interface IStatTrainingActions + public interface ITrainingActions { void LevelUpStat(CharacterStat whichStat); + + void LevelUpSkill(int spellId); } } diff --git a/EOLib/Net/API/StatSkill.cs b/EOLib/Net/API/StatSkill.cs index 2806faf51..d089533ca 100644 --- a/EOLib/Net/API/StatSkill.cs +++ b/EOLib/Net/API/StatSkill.cs @@ -144,7 +144,6 @@ partial class PacketAPI public event SpellLearnErrorEvent OnSpellLearnError; public event SpellLearnSuccessEvent OnSpellLearnSuccess; public event SpellForgetEvent OnSpellForget; - public event SpellTrainEvent OnSpellTrain; public event Action OnCharacterStatsReset; private void _createStatSkillMembers() @@ -153,7 +152,6 @@ private void _createStatSkillMembers() m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Reply), _handleStatSkillReply, true); m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Take), _handleStatSkillTake, true); m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Remove), _handleStatSkillRemove, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Accept), _handleStatSkillAccept, true); m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Junk), _handleStatSkillJunk, true); } @@ -192,16 +190,6 @@ public bool ForgetSpell(short spellID) return m_client.SendPacket(pkt); } - public bool LevelUpStat(short statID) - { - return _trainStatShared(statID, TrainType.Stat); - } - - public bool LevelUpSpell(short spellID) - { - return _trainStatShared(spellID, TrainType.Skill); - } - public bool ResetCharacterStatSkill() { OldPacket pkt = new OldPacket(PacketFamily.StatSkill, PacketAction.Junk); @@ -209,18 +197,6 @@ public bool ResetCharacterStatSkill() return !m_client.ConnectedAndInitialized || !Initialized || m_client.SendPacket(pkt); } - private bool _trainStatShared(short id, TrainType type) - { - if (!m_client.ConnectedAndInitialized || !Initialized) - return false; - - OldPacket pkt = new OldPacket(PacketFamily.StatSkill, PacketAction.Add); - pkt.AddChar((byte)type); - pkt.AddShort(id); - - return m_client.SendPacket(pkt); - } - //handlers private void _handleStatSkillOpen(OldPacket pkt) @@ -255,16 +231,6 @@ private void _handleStatSkillRemove(OldPacket pkt) OnSpellForget(pkt.GetShort()); } - //skill point added to spell - private void _handleStatSkillAccept(OldPacket pkt) - { - //short - character skill pts remaining - //short - stat ID (spell ID) - //short - spell level - if (OnSpellTrain != null) - OnSpellTrain(pkt.GetShort(), pkt.GetShort(), pkt.GetShort()); - } - //reset character private void _handleStatSkillJunk(OldPacket pkt) { diff --git a/EOLib/PacketHandlers/SpellTrainingHandler.cs b/EOLib/PacketHandlers/SpellTrainingHandler.cs new file mode 100644 index 000000000..ceef26d23 --- /dev/null +++ b/EOLib/PacketHandlers/SpellTrainingHandler.cs @@ -0,0 +1,43 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Net; +using EOLib.Net.Handlers; + +namespace EOLib.PacketHandlers +{ + [AutoMappedType] + public class SpellTrainingHandler : InGameOnlyPacketHandler + { + private readonly ICharacterRepository _characterRepository; + private readonly ICharacterInventoryRepository _characterInventoryRepository; + + public override PacketFamily Family => PacketFamily.StatSkill; + + public override PacketAction Action => PacketAction.Accept; + + public SpellTrainingHandler(IPlayerInfoProvider playerInfoProvider, + ICharacterRepository characterRepository, + ICharacterInventoryRepository characterInventoryRepository) + : base(playerInfoProvider) + { + _characterRepository = characterRepository; + _characterInventoryRepository = characterInventoryRepository; + } + + public override bool HandlePacket(IPacket packet) + { + var skillPoints = packet.ReadShort(); + var spellId = packet.ReadShort(); + var spellLevel = packet.ReadShort(); + + _characterInventoryRepository.SpellInventory.RemoveWhere(x => x.ID == spellId); + _characterInventoryRepository.SpellInventory.Add(new InventorySpell(spellId, spellLevel)); + + var stats = _characterRepository.MainCharacter.Stats.WithNewStat(CharacterStat.SkillPoints, skillPoints); + _characterRepository.MainCharacter = _characterRepository.MainCharacter.WithStats(stats); + + return true; + } + } +} diff --git a/EndlessClient/Controllers/TrainingController.cs b/EndlessClient/Controllers/TrainingController.cs index 140de1caa..9b53f1f9f 100644 --- a/EndlessClient/Controllers/TrainingController.cs +++ b/EndlessClient/Controllers/TrainingController.cs @@ -6,11 +6,11 @@ namespace EndlessClient.Controllers [MappedType(BaseType = typeof(ITrainingController))] public class TrainingController : ITrainingController { - private readonly IStatTrainingActions _statTrainingActions; + private readonly ITrainingActions _trainingActions; - public TrainingController(IStatTrainingActions statTrainingActions) + public TrainingController(ITrainingActions trainingActions) { - _statTrainingActions = statTrainingActions; + _trainingActions = trainingActions; } public void AddStatPoint(CharacterStat whichStat) @@ -18,7 +18,12 @@ public void AddStatPoint(CharacterStat whichStat) if (InvalidStat(whichStat)) return; - _statTrainingActions.LevelUpStat(whichStat); + _trainingActions.LevelUpStat(whichStat); + } + + public void AddSkillPoint(int spellId) + { + _trainingActions.LevelUpSkill(spellId); } private static bool InvalidStat(CharacterStat whichStat) @@ -39,5 +44,7 @@ private static bool InvalidStat(CharacterStat whichStat) public interface ITrainingController { void AddStatPoint(CharacterStat whichStat); + + void AddSkillPoint(int spellId); } } diff --git a/EndlessClient/Dialogs/Old/TradeDialog.cs b/EndlessClient/Dialogs/Old/TradeDialog.cs index 4b2956717..db5a66693 100644 --- a/EndlessClient/Dialogs/Old/TradeDialog.cs +++ b/EndlessClient/Dialogs/Old/TradeDialog.cs @@ -293,7 +293,7 @@ public void CompleteTrade(short p1, List p1items, short p2, List< // weightDelta += OldWorld.Instance.EIF[item.ItemID].Weight * item.Amount; //} m_main.Weight += (byte)weightDelta; - ((EOGame)Game).Hud.RefreshStats(); + //((EOGame)Game).Hud.RefreshStats(); Close(null, XNADialogResult.NO_BUTTON_PRESSED); EOMessageBox.Show(DialogResourceID.TRADE_SUCCESS, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); diff --git a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs index 009cc1818..d029ec22a 100644 --- a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs +++ b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs @@ -321,9 +321,8 @@ private void LevelUp_Click(object sender, EventArgs args) } else { - var selectedSpell = _childItems.SingleOrNone(x => x.IsSelected); - // todo: implement in training controller - //_trainingController.LevelUpSpell(selectedSpell.SpellData.ID); + _childItems.SingleOrNone(x => x.IsSelected) + .MatchSome(x => _trainingController.AddSkillPoint(x.SpellData.ID)); } } From bbe6283c2086b04afdcd8e01ed44e09f02d7b86f Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 22:06:52 -0700 Subject: [PATCH 8/9] Update selection logic and label/level up property changes for active spells --- EndlessClient/HUD/Panels/ActiveSpellsPanel.cs | 31 +++++++++++++++---- .../HUD/Spells/BaseSpellPanelItem.cs | 4 ++- 2 files changed, 28 insertions(+), 7 deletions(-) diff --git a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs index d029ec22a..625351f01 100644 --- a/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs +++ b/EndlessClient/HUD/Panels/ActiveSpellsPanel.cs @@ -52,6 +52,7 @@ public class ActiveSpellsPanel : XNAPanel, IHudPanel private readonly ScrollBar _scrollBar; private HashSet _cachedSpells; + private ICharacterStats _cachedStats; private bool _confirmedTraining; private int _lastScrollOffset; private Texture2D _activeSpellIcon; @@ -196,6 +197,10 @@ protected override void OnUpdateControl(GameTime gameTime) matchedSpell.MatchSome(childControl => { childControl.InventorySpell = spell; + if (childControl.IsSelected) + { + _selectedSpellLevel.Text = spell.Level.ToString(); + } }); } @@ -250,6 +255,19 @@ protected override void OnUpdateControl(GameTime gameTime) UpdateSpellItemsForScroll(); } + if (_cachedStats != _characterProvider.MainCharacter.Stats && _activeSpellIcon != null) + { + _cachedStats = _characterProvider.MainCharacter.Stats; + var skillPoints = _cachedStats[CharacterStat.SkillPoints]; + _totalSkillPoints.Text = skillPoints.ToString(); + + if (skillPoints == 0) + { + _levelUpButton1.Visible = false; + _levelUpButton2.Visible = false; + } + } + base.OnUpdateControl(gameTime); } @@ -328,18 +346,18 @@ private void LevelUp_Click(object sender, EventArgs args) private void SetSelectedSpell(object sender, EventArgs e) { - ClearSelectedSpell(); - var spell = (SpellPanelItem)sender; + ClearSelectedSpell(exclude: spell); + var spellData = spell.SpellData; if (spellData.Target == EOLib.IO.SpellTarget.Normal) - _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, spellData.Name, EOResourceID.SPELL_WAS_SELECTED); + _statusLabelSetter.SetStatusLabel(EOResourceID.SKILLMASTER_WORD_SPELL, $"{spellData.Name} ", EOResourceID.SPELL_WAS_SELECTED); else if (spellData.Target == EOLib.IO.SpellTarget.Group /*&& not in party*/) // todo: parties _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.SPELL_ONLY_WORKS_ON_GROUP); _activeSpellIcon = NativeGraphicsManager.TextureFromResource(GFXTypes.SpellIcons, spellData.Icon); - + _selectedSpellName.Text = spellData.Name; _selectedSpellName.Visible = true; @@ -412,7 +430,7 @@ private ISpellPanelItem CreateEmptySpell(int slot) return emptyItem; } - private void ClearSelectedSpell() + private void ClearSelectedSpell(params ISpellPanelItem[] exclude) { _activeSpellIcon = null; @@ -423,9 +441,10 @@ private void ClearSelectedSpell() _levelUpButton1.Visible = _levelUpButton2.Visible = false; - foreach (var item in _childItems.Where(x => x.IsSelected)) + foreach (var item in _childItems.Where(x => x.IsSelected).Except(exclude)) item.IsSelected = false; } + private void SwapFunctionKeySourceRectangles() { var tmpRect = _functionKeyRow2Source; diff --git a/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs b/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs index 537579926..a20f1ffe2 100644 --- a/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs +++ b/EndlessClient/HUD/Spells/BaseSpellPanelItem.cs @@ -40,7 +40,9 @@ public virtual bool IsSelected set { _selected = value; - Selected?.Invoke(this, EventArgs.Empty); + + if (_selected) + Selected?.Invoke(this, EventArgs.Empty); } } From 47921188aa7ea8c0cc5b8de16d2267e5b9878f52 Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 12 Apr 2022 22:08:33 -0700 Subject: [PATCH 9/9] Implement handler for StatskillTake for learning skills --- EOLib/Net/API/StatSkill.cs | 13 ------ EOLib/PacketHandlers/Skill/StatskillTake.cs | 45 +++++++++++++++++++ EndlessClient/Old/PacketAPICallbackManager.cs | 10 ----- 3 files changed, 45 insertions(+), 23 deletions(-) create mode 100644 EOLib/PacketHandlers/Skill/StatskillTake.cs diff --git a/EOLib/Net/API/StatSkill.cs b/EOLib/Net/API/StatSkill.cs index d089533ca..530c63b78 100644 --- a/EOLib/Net/API/StatSkill.cs +++ b/EOLib/Net/API/StatSkill.cs @@ -134,15 +134,12 @@ internal StatResetData(OldPacket pkt) } public delegate void SpellLearnErrorEvent(SkillMasterReply reply, short classID); - public delegate void SpellLearnSuccessEvent(short spellID, int goldRemaining); public delegate void SpellForgetEvent(short spellID); - public delegate void SpellTrainEvent(short skillPtsRemaining, short spellID, short spellLevel); partial class PacketAPI { public event Action OnSkillmasterOpen; public event SpellLearnErrorEvent OnSpellLearnError; - public event SpellLearnSuccessEvent OnSpellLearnSuccess; public event SpellForgetEvent OnSpellForget; public event Action OnCharacterStatsReset; @@ -150,7 +147,6 @@ private void _createStatSkillMembers() { m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Open), _handleStatSkillOpen, true); m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Reply), _handleStatSkillReply, true); - m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Take), _handleStatSkillTake, true); m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Remove), _handleStatSkillRemove, true); m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Junk), _handleStatSkillJunk, true); } @@ -214,15 +210,6 @@ private void _handleStatSkillReply(OldPacket pkt) OnSpellLearnError((SkillMasterReply)pkt.GetShort(), pkt.GetShort()); } - //success learning a skill - private void _handleStatSkillTake(OldPacket pkt) - { - //short - spell id - //int - character gold remaining - if (OnSpellLearnSuccess != null) - OnSpellLearnSuccess(pkt.GetShort(), pkt.GetInt()); - } - //forgetting a skill private void _handleStatSkillRemove(OldPacket pkt) { diff --git a/EOLib/PacketHandlers/Skill/StatskillTake.cs b/EOLib/PacketHandlers/Skill/StatskillTake.cs new file mode 100644 index 000000000..fa8b6de17 --- /dev/null +++ b/EOLib/PacketHandlers/Skill/StatskillTake.cs @@ -0,0 +1,45 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Login; +using EOLib.Net; +using EOLib.Net.Handlers; +using System.Linq; + +namespace EOLib.PacketHandlers.Skill +{ + /// + /// Sent when learning a skill, either via $learn command or from skillmaster + /// + [AutoMappedType] + public class StatskillTake : InGameOnlyPacketHandler + { + private readonly ICharacterInventoryRepository _characterInventoryRepository; + + public override PacketFamily Family => PacketFamily.StatSkill; + + public override PacketAction Action => PacketAction.Take; + + public StatskillTake(IPlayerInfoProvider playerInfoProvider, + ICharacterInventoryRepository characterInventoryRepository) + : base(playerInfoProvider) + { + _characterInventoryRepository = characterInventoryRepository; + } + + public override bool HandlePacket(IPacket packet) + { + var spellId = packet.ReadShort(); + var characterGold = packet.ReadInt(); + + if (!_characterInventoryRepository.SpellInventory.Any(x => x.ID == spellId)) + { + _characterInventoryRepository.SpellInventory.Add(new InventorySpell(spellId, 0)); + } + + _characterInventoryRepository.ItemInventory.RemoveWhere(x => x.ItemID == 1); + _characterInventoryRepository.ItemInventory.Add(new InventoryItem(1, characterGold)); + + return true; + } + } +} diff --git a/EndlessClient/Old/PacketAPICallbackManager.cs b/EndlessClient/Old/PacketAPICallbackManager.cs index 52e914838..aff924af6 100644 --- a/EndlessClient/Old/PacketAPICallbackManager.cs +++ b/EndlessClient/Old/PacketAPICallbackManager.cs @@ -48,7 +48,6 @@ public void AssignCallbacks() //skills m_packetAPI.OnSkillmasterOpen += _skillmasterOpen; m_packetAPI.OnSpellLearnError += _statskillLearnError; - m_packetAPI.OnSpellLearnSuccess += _statskillLearnSpellSuccess; m_packetAPI.OnSpellForget += _statskillForgetSpell; m_packetAPI.OnCharacterStatsReset += _statskillReset; @@ -197,15 +196,6 @@ private void _statskillLearnError(SkillMasterReply reply, short id) } } - private void _statskillLearnSpellSuccess(short id, int remaining) - { - OldWorld.Instance.MainPlayer.ActiveCharacter.Spells.Add(new InventorySpell(id, 0)); - if (SkillmasterDialog.Instance != null) - SkillmasterDialog.Instance.RemoveSkillByIDFromLearnList(id); - //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, remaining); - //m_game.Hud.AddNewSpellToActiveSpellsByID(id); - } - private void _statskillForgetSpell(short id) { OldWorld.Instance.MainPlayer.ActiveCharacter.Spells.RemoveAll(_spell => _spell.ID == id);