diff --git a/EOLib.Config/IniReader.cs b/EOLib.Config/IniReader.cs index 0bd14c6b7..33d87eb76 100644 --- a/EOLib.Config/IniReader.cs +++ b/EOLib.Config/IniReader.cs @@ -68,6 +68,30 @@ public bool Load() return true; } + public void Save() + { + try + { + using (var sw = new StreamWriter(_filename)) + { + foreach (var section in _sections) + { + sw.WriteLine($"[{section.Key}]"); + + foreach (var kvp in section.Value) + { + sw.WriteLine($"{kvp.Key}={kvp.Value}"); + } + + sw.WriteLine(); + } + } + } + catch (IOException) + { + } + } + public bool GetValue(string section, string key, out string value) { value = null; diff --git a/EOLib.IO/Extensions/ItemSizeExtensions.cs b/EOLib.IO/Extensions/ItemSizeExtensions.cs new file mode 100644 index 000000000..56a14d1aa --- /dev/null +++ b/EOLib.IO/Extensions/ItemSizeExtensions.cs @@ -0,0 +1,21 @@ +using System; + +namespace EOLib.IO.Extensions +{ + public static class ItemSizeExtensions + { + public static (int Width, int Height) GetDimensions(this ItemSize itemSize) + { + var sizeStr = Enum.GetName(typeof(ItemSize), itemSize); + if (sizeStr == null || sizeStr.Length != 7) + { + return (0, 0); + } + + var width = Convert.ToInt32(sizeStr.Substring(4, 1)); + var height = Convert.ToInt32(sizeStr.Substring(6, 1)); + + return (width, height); + } + } +} diff --git a/EOLib.IO/Map/Matrix.cs b/EOLib.IO/Map/Matrix.cs index e43f92c49..553b51a9c 100644 --- a/EOLib.IO/Map/Matrix.cs +++ b/EOLib.IO/Map/Matrix.cs @@ -6,7 +6,7 @@ namespace EOLib.IO.Map { public class Matrix : IReadOnlyMatrix { - private static readonly Matrix _empty = new Matrix(0, 0); + private static readonly Matrix _empty = new Matrix(new T[0,0]); public static Matrix Empty => _empty; private readonly T[,] _arr; @@ -16,22 +16,22 @@ public class Matrix : IReadOnlyMatrix public int Rows { get; } public int Cols { get; } - private Matrix(int rows, int cols) + private Matrix(T[,] other) { - Rows = rows; - Cols = cols; - _arr = new T[rows, cols]; + Rows = other.GetLength(0); + Cols = other.GetLength(1); + _arr = other; } public Matrix(int rows, int cols, T defaultValue) - : this(rows, cols) + : this(new T[rows, cols]) { _default = defaultValue; Fill(defaultValue); } public Matrix(Matrix other) - : this(other.Rows, other.Cols) + : this(new T[other.Rows, other.Cols]) { for (int row = 0; row < other.Rows; ++row) for (int col = 0; col < other.Cols; ++col) @@ -70,16 +70,12 @@ public T[] GetRow(int rowIndex) public static implicit operator T[,](Matrix array) { - var ret = new T[array.Rows, array.Cols]; - Array.Copy(array._arr, ret, ret.Length); - return ret; + return array._arr; } public static implicit operator Matrix(T[,] array) { - var ret = new Matrix(array.GetLength(0), array.GetLength(1)); - Array.Copy(array, ret._arr, array.Length); - return ret; + return new Matrix(array); } public IEnumerator> GetEnumerator() diff --git a/EOLib/Domain/Character/CharacterInventoryRepository.cs b/EOLib/Domain/Character/CharacterInventoryRepository.cs index c5783edcd..8fd40cade 100644 --- a/EOLib/Domain/Character/CharacterInventoryRepository.cs +++ b/EOLib/Domain/Character/CharacterInventoryRepository.cs @@ -5,31 +5,31 @@ namespace EOLib.Domain.Character { public interface ICharacterInventoryRepository { - List ItemInventory { get; set; } + HashSet ItemInventory { get; set; } - List SpellInventory { get; set; } + HashSet SpellInventory { get; set; } } public interface ICharacterInventoryProvider { - IReadOnlyList ItemInventory { get; } + IReadOnlyCollection ItemInventory { get; } - IReadOnlyList SpellInventory { get; } + IReadOnlyCollection SpellInventory { get; } } [AutoMappedType(IsSingleton = true)] public class CharacterInventoryRepository : ICharacterInventoryRepository, ICharacterInventoryProvider { - public List ItemInventory { get; set; } - public List SpellInventory { get; set; } + public HashSet ItemInventory { get; set; } + public HashSet SpellInventory { get; set; } - IReadOnlyList ICharacterInventoryProvider.ItemInventory => ItemInventory; - IReadOnlyList ICharacterInventoryProvider.SpellInventory => SpellInventory; + IReadOnlyCollection ICharacterInventoryProvider.ItemInventory => ItemInventory; + IReadOnlyCollection ICharacterInventoryProvider.SpellInventory => SpellInventory; public CharacterInventoryRepository() { - ItemInventory = new List(32); - SpellInventory = new List(32); + ItemInventory = new HashSet(); + SpellInventory = new HashSet(); } } } diff --git a/EOLib/Domain/Character/InventoryItem.cs b/EOLib/Domain/Character/InventoryItem.cs index 46494bf10..4cbcdcbd1 100644 --- a/EOLib/Domain/Character/InventoryItem.cs +++ b/EOLib/Domain/Character/InventoryItem.cs @@ -16,6 +16,21 @@ public IInventoryItem WithAmount(int newAmount) { return new InventoryItem(ItemID, newAmount); } + + public override bool Equals(object obj) + { + var other = obj as InventoryItem; + if (other == null) return false; + return other.ItemID == ItemID && other.Amount == Amount; + } + + public override int GetHashCode() + { + int hashCode = 1754760722; + hashCode = hashCode * -1521134295 + ItemID.GetHashCode(); + hashCode = hashCode * -1521134295 + Amount.GetHashCode(); + return hashCode; + } } public interface IInventoryItem diff --git a/EOLib/Domain/Character/InventorySpell.cs b/EOLib/Domain/Character/InventorySpell.cs index 4682e133a..cbc3af8d3 100644 --- a/EOLib/Domain/Character/InventorySpell.cs +++ b/EOLib/Domain/Character/InventorySpell.cs @@ -16,6 +16,21 @@ public IInventorySpell WithLevel(short newLevel) { return new InventorySpell(ID, newLevel); } + + public override bool Equals(object obj) + { + var other = obj as InventorySpell; + if (other == null) return false; + return other.ID == ID && other.Level == Level; + } + + public override int GetHashCode() + { + int hashCode = 1754760722; + hashCode = hashCode * -1521134295 + ID.GetHashCode(); + hashCode = hashCode * -1521134295 + Level.GetHashCode(); + return hashCode; + } } public interface IInventorySpell diff --git a/EOLib/Domain/Login/ILoginRequestGrantedData.cs b/EOLib/Domain/Login/ILoginRequestGrantedData.cs index b9058a2ed..296a845f2 100644 --- a/EOLib/Domain/Login/ILoginRequestGrantedData.cs +++ b/EOLib/Domain/Login/ILoginRequestGrantedData.cs @@ -1,5 +1,6 @@ using System.Collections.Generic; using EOLib.Domain.Character; +using EOLib.IO; using EOLib.Net.Translators; namespace EOLib.Domain.Login @@ -32,7 +33,7 @@ public interface ILoginRequestGrantedData : ITranslatedData ICharacterStats CharacterStats { get; } - IReadOnlyList Paperdoll { get; } + IReadOnlyDictionary Paperdoll { get; } byte GuildRankNum { get; } short JailMap { get; } @@ -59,7 +60,7 @@ public interface ILoginRequestGrantedData : ITranslatedData ILoginRequestGrantedData WithGuildTag(string guildTag); ILoginRequestGrantedData WithAdminLevel(AdminLevel adminLevel); ILoginRequestGrantedData WithCharacterStats(ICharacterStats stats); - ILoginRequestGrantedData WithPaperdoll(IEnumerable paperdollItemIDs); + ILoginRequestGrantedData WithPaperdoll(IReadOnlyDictionary paperdoll); ILoginRequestGrantedData WithGuildRankNum(byte rankNum); ILoginRequestGrantedData WithJailMap(short jailMapID); ILoginRequestGrantedData WithFirstTimePlayer(bool isFirstTimePlayer); diff --git a/EOLib/Domain/Login/LoginActions.cs b/EOLib/Domain/Login/LoginActions.cs index 606b18b87..57ae02db5 100644 --- a/EOLib/Domain/Login/LoginActions.cs +++ b/EOLib/Domain/Login/LoginActions.cs @@ -3,11 +3,9 @@ using System.Threading.Tasks; using AutomaticTypeMapper; using EOLib.Domain.Character; -using EOLib.Domain.Chat; using EOLib.Domain.Map; using EOLib.Domain.NPC; using EOLib.Domain.Protocol; -using EOLib.Localization; using EOLib.Net; using EOLib.Net.Communication; using EOLib.Net.FileTransfer; @@ -35,14 +33,12 @@ public LoginActions(IPacketSendService packetSendService, IPacketTranslator loginPacketTranslator, IPacketTranslator loginRequestGrantedPacketTranslator, IPacketTranslator loginRequestCompletedPacketTranslator, - ILocalizedStringFinder localizedStringFinder, ICharacterSelectorRepository characterSelectorRepository, IPlayerInfoRepository playerInfoRepository, ICharacterRepository characterRepository, ICurrentMapStateRepository currentMapStateRepository, ILoginFileChecksumRepository loginFileChecksumRepository, INewsRepository newsRepository, - IChatRepository chatRepository, ICharacterInventoryRepository characterInventoryRepository, IPaperdollRepository paperdollRepository) { @@ -116,6 +112,15 @@ public async Task RequestCharacterLogin(ICharacter character) _playerInfoRepository.IsFirstTimePlayer = data.FirstTimePlayer; _currentMapStateRepository.CurrentMapID = data.MapID; + _paperdollRepository.VisibleCharacterPaperdolls[data.SessionID] = new PaperdollData() + .WithName(data.Name) + .WithTitle(data.Title) + .WithGuild(data.GuildName) + .WithRank(data.GuildRank) + .WithClass(data.ClassID) + .WithPlayerID(data.SessionID) + .WithPaperdoll(data.Paperdoll); + _loginFileChecksumRepository.MapChecksum = data.MapRID.ToArray(); _loginFileChecksumRepository.MapLength = data.MapLen; @@ -171,8 +176,8 @@ public async Task CompleteCharacterLogin(short sessionID) .WithStats(stats) .WithRenderProperties(mainCharacter.RenderProperties); - _characterInventoryRepository.ItemInventory = data.CharacterItemInventory.ToList(); - _characterInventoryRepository.SpellInventory = data.CharacterSpellInventory.ToList(); + _characterInventoryRepository.ItemInventory = new HashSet(data.CharacterItemInventory); + _characterInventoryRepository.SpellInventory = new HashSet(data.CharacterSpellInventory); _currentMapStateRepository.Characters = data.MapCharacters.Except(new[] { mainCharacter }).ToDictionary(k => k.ID, v => v); _currentMapStateRepository.NPCs = new HashSet(data.MapNPCs); diff --git a/EOLib/Domain/Login/LoginRequestGrantedData.cs b/EOLib/Domain/Login/LoginRequestGrantedData.cs index bc43bfbb1..0d155e5f5 100644 --- a/EOLib/Domain/Login/LoginRequestGrantedData.cs +++ b/EOLib/Domain/Login/LoginRequestGrantedData.cs @@ -33,8 +33,8 @@ public class LoginRequestGrantedData : ILoginRequestGrantedData public ICharacterStats CharacterStats { get; private set; } - private List _paperdoll = new List((int)EquipLocation.PAPERDOLL_MAX); - public IReadOnlyList Paperdoll => _paperdoll; + private IReadOnlyDictionary _paperdoll = new Dictionary(); + public IReadOnlyDictionary Paperdoll => _paperdoll; public byte GuildRankNum { get; private set; } public short JailMap { get; private set; } @@ -187,10 +187,10 @@ public ILoginRequestGrantedData WithCharacterStats(ICharacterStats stats) return copy; } - public ILoginRequestGrantedData WithPaperdoll(IEnumerable paperdollItemIDs) + public ILoginRequestGrantedData WithPaperdoll(IReadOnlyDictionary paperdoll) { var copy = MakeCopy(this); - copy._paperdoll = paperdollItemIDs.ToList(); + copy._paperdoll = paperdoll; return copy; } diff --git a/EOLib/Net/Translators/LoginRequestCompletedPacketTranslator.cs b/EOLib/Net/Translators/LoginRequestCompletedPacketTranslator.cs index 332d9171e..f5907de6f 100644 --- a/EOLib/Net/Translators/LoginRequestCompletedPacketTranslator.cs +++ b/EOLib/Net/Translators/LoginRequestCompletedPacketTranslator.cs @@ -39,6 +39,9 @@ public override ILoginRequestCompletedData TranslatePacket(IPacket packet) var maxWeight = packet.ReadChar(); var inventoryItems = GetInventoryItems(packet).ToList(); + if (!inventoryItems.Any(x => x.ItemID == 1)) + inventoryItems.Insert(0, new InventoryItem(1, 0)); + var inventorySpells = GetInventorySpells(packet).ToList(); if (inventoryItems.All(x => x.ItemID != 1)) diff --git a/EOLib/Net/Translators/LoginRequestGrantedPacketTranslator.cs b/EOLib/Net/Translators/LoginRequestGrantedPacketTranslator.cs index 7d2c7b92a..979ec6937 100644 --- a/EOLib/Net/Translators/LoginRequestGrantedPacketTranslator.cs +++ b/EOLib/Net/Translators/LoginRequestGrantedPacketTranslator.cs @@ -1,4 +1,5 @@ -using System.IO; +using System.Collections.Generic; +using System.IO; using System.Linq; using AutomaticTypeMapper; using EOLib.Domain.Character; @@ -93,10 +94,10 @@ public ILoginRequestGrantedData TranslatePacket(IPacket packet) .WithNewStat(CharacterStat.Constituion, dispCon) .WithNewStat(CharacterStat.Charisma, dispCha); - var paperDoll = new short[(int)EquipLocation.PAPERDOLL_MAX]; - for (int i = 0; i < (int)EquipLocation.PAPERDOLL_MAX; ++i) + var paperDoll = new Dictionary(); + for (var equipLocation = (EquipLocation)0; equipLocation < EquipLocation.PAPERDOLL_MAX; ++equipLocation) { - paperDoll[i] = packet.ReadShort(); + paperDoll[equipLocation] = packet.ReadShort(); } var guildRankNum = packet.ReadChar(); diff --git a/EOLib/Net/Translators/OnlineListPacketTranslator.cs b/EOLib/Net/Translators/OnlineListPacketTranslator.cs index 426cc8087..d42043077 100644 --- a/EOLib/Net/Translators/OnlineListPacketTranslator.cs +++ b/EOLib/Net/Translators/OnlineListPacketTranslator.cs @@ -18,7 +18,7 @@ public OnlineListPacketTranslator(IECFFileProvider classFileProvider) public IOnlineListData TranslatePacket(IPacket packet) { - var reply = (InitReply)packet.ReadChar(); + var reply = (InitReply)packet.ReadByte(); if (reply != InitReply.AllPlayersList && reply != InitReply.FriendPlayersList) throw new MalformedPacketException($"Expected online list or friend list init data, but was {reply}", packet); diff --git a/EOLib/PacketHandlers/ItemEquipHandler.cs b/EOLib/PacketHandlers/ItemEquipHandler.cs index 9efa9882c..876eeee43 100644 --- a/EOLib/PacketHandlers/ItemEquipHandler.cs +++ b/EOLib/PacketHandlers/ItemEquipHandler.cs @@ -98,8 +98,10 @@ protected bool HandlePaperdollPacket(IPacket packet, bool itemUnequipped) .Match(some: invItem => invItem.WithAmount(itemUnequipped ? invItem.Amount + amount : amount), none: () => new InventoryItem(itemId, amount)); - _characterInventoryRepository.ItemInventory.RemoveAll(x => x.ItemID == itemId); - _characterInventoryRepository.ItemInventory.Add(updatedItem); + _characterInventoryRepository.ItemInventory.RemoveWhere(x => x.ItemID == itemId); + + if (updatedItem.Amount > 0) + _characterInventoryRepository.ItemInventory.Add(updatedItem); } else { diff --git a/EOLib/misc.cs b/EOLib/misc.cs index 9ed5fb536..442169185 100644 --- a/EOLib/misc.cs +++ b/EOLib/misc.cs @@ -45,6 +45,8 @@ public static class Constants public const string FriendListFile = "config/friends.ini"; public const string IgnoreListFile = "config/ignore.ini"; + public const string InventoryFile = "config/inventory.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 public static readonly int[] TrapSpikeGFXObjectIDs = {449, 450, 451, 452}; diff --git a/EndlessClient/Dialogs/Old/BankAccountDialog.cs b/EndlessClient/Dialogs/Old/BankAccountDialog.cs index 79d8d32c2..52af6d17d 100644 --- a/EndlessClient/Dialogs/Old/BankAccountDialog.cs +++ b/EndlessClient/Dialogs/Old/BankAccountDialog.cs @@ -199,16 +199,16 @@ public override void Update(GameTime gt) { if (!Game.IsActive) return; - if (EOGame.Instance.Hud.IsInventoryDragging()) - { - shouldClickDrag = false; - SuppressParentClickDrag(true); - } - else - { - shouldClickDrag = true; - SuppressParentClickDrag(false); - } + //if (EOGame.Instance.Hud.IsInventoryDragging()) + //{ + // shouldClickDrag = false; + // SuppressParentClickDrag(true); + //} + //else + //{ + // shouldClickDrag = true; + // SuppressParentClickDrag(false); + //} base.Update(gt); } diff --git a/EndlessClient/Dialogs/Old/ChestDialog.cs b/EndlessClient/Dialogs/Old/ChestDialog.cs index 4c090175d..46dc7a13b 100644 --- a/EndlessClient/Dialogs/Old/ChestDialog.cs +++ b/EndlessClient/Dialogs/Old/ChestDialog.cs @@ -93,28 +93,28 @@ public void InitializeItems(IList initialItems) OldListDialogItem sender = o as OldListDialogItem; if (sender == null) return; - if (!EOGame.Instance.Hud.InventoryFits(sender.ID)) - { - string _message = OldWorld.GetString(EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT); - string _caption = OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING); - EOMessageBox.Show(_message, _caption, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT); - } - else if (rec.Weight * item.Amount + OldWorld.Instance.MainPlayer.ActiveCharacter.Weight > - OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_ITS_TOO_HEAVY_WEIGHT), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - else - { - if (!m_api.ChestTakeItem(CurrentChestX, CurrentChestY, sender.ID)) - { - Close(); - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - } + //if (!EOGame.Instance.Hud.InventoryFits(sender.ID)) + //{ + // string _message = OldWorld.GetString(EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT); + // string _caption = OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING); + // EOMessageBox.Show(_message, _caption, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + // ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT); + //} + //else if (rec.Weight * item.Amount + OldWorld.Instance.MainPlayer.ActiveCharacter.Weight > + // OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight) + //{ + // EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_ITS_TOO_HEAVY_WEIGHT), + // OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), + // EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + //} + //else + //{ + // if (!m_api.ChestTakeItem(CurrentChestX, CurrentChestY, sender.ID)) + // { + // Close(); + // EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + // } + //} }; } } @@ -141,16 +141,16 @@ public override void Update(GameTime gt) { if (!Game.IsActive) return; - if (EOGame.Instance.Hud.IsInventoryDragging()) - { - shouldClickDrag = false; - SuppressParentClickDrag(true); - } - else - { - shouldClickDrag = true; - SuppressParentClickDrag(false); - } + //if (EOGame.Instance.Hud.IsInventoryDragging()) + //{ + // shouldClickDrag = false; + // SuppressParentClickDrag(true); + //} + //else + //{ + // shouldClickDrag = true; + // SuppressParentClickDrag(false); + //} base.Update(gt); } diff --git a/EndlessClient/Dialogs/Old/LockerDialog.cs b/EndlessClient/Dialogs/Old/LockerDialog.cs index 6cbb32276..23538b8e4 100644 --- a/EndlessClient/Dialogs/Old/LockerDialog.cs +++ b/EndlessClient/Dialogs/Old/LockerDialog.cs @@ -80,13 +80,13 @@ public int GetNewItemAmount(short id, int amount) private void _removeItem(EIFRecord item, int amount) { - if (!EOGame.Instance.Hud.InventoryFits((short)item.ID)) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - return; - } + //if (!EOGame.Instance.Hud.InventoryFits((short)item.ID)) + //{ + // EOMessageBox.Show(OldWorld.GetString(EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT), + // OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), + // EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + // return; + //} if (OldWorld.Instance.MainPlayer.ActiveCharacter.Weight + item.Weight * amount > OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight) { @@ -103,16 +103,16 @@ private void _removeItem(EIFRecord item, int amount) public override void Update(GameTime gt) { if (!Game.IsActive) return; - if (EOGame.Instance.Hud.IsInventoryDragging()) - { - shouldClickDrag = false; - SuppressParentClickDrag(true); - } - else - { - shouldClickDrag = true; - SuppressParentClickDrag(false); - } + //if (EOGame.Instance.Hud.IsInventoryDragging()) + //{ + // shouldClickDrag = false; + // SuppressParentClickDrag(true); + //} + //else + //{ + // shouldClickDrag = true; + // SuppressParentClickDrag(false); + //} base.Update(gt); } diff --git a/EndlessClient/Dialogs/Old/ShopDialog.cs b/EndlessClient/Dialogs/Old/ShopDialog.cs index 4cac1b7aa..f4bc9829d 100644 --- a/EndlessClient/Dialogs/Old/ShopDialog.cs +++ b/EndlessClient/Dialogs/Old/ShopDialog.cs @@ -249,13 +249,13 @@ private void _buySellItem(ShopItem item) var rec = OldWorld.Instance.EIF[item.ID]; if (isBuying) { - if (!EOGame.Instance.Hud.InventoryFits((short)item.ID)) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRANSFER_NOT_ENOUGH_SPACE), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - return; - } + //if (!EOGame.Instance.Hud.InventoryFits((short)item.ID)) + //{ + // EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRANSFER_NOT_ENOUGH_SPACE), + // OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), + // EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + // return; + //} if (rec.Weight + OldWorld.Instance.MainPlayer.ActiveCharacter.Weight > OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight) @@ -343,13 +343,13 @@ private void _craftItem(CraftItem item) } } - if (!EOGame.Instance.Hud.InventoryFits((short)item.ID)) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRANSFER_NOT_ENOUGH_SPACE), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - return; - } + //if (!EOGame.Instance.Hud.InventoryFits((short)item.ID)) + //{ + // EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRANSFER_NOT_ENOUGH_SPACE), + // OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), + // EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + // return; + //} string _message2 = OldWorld.GetString(EOResourceID.DIALOG_SHOP_CRAFT_PUT_INGREDIENTS_TOGETHER) + "\n\n"; foreach (var ingred in item.Ingredients) diff --git a/EndlessClient/Dialogs/Old/TradeDialog.cs b/EndlessClient/Dialogs/Old/TradeDialog.cs index 40b6eded2..4b2956717 100644 --- a/EndlessClient/Dialogs/Old/TradeDialog.cs +++ b/EndlessClient/Dialogs/Old/TradeDialog.cs @@ -282,16 +282,16 @@ public void CompleteTrade(short p1, List p1items, short p2, List< throw new ArgumentException("Invalid player ID for trade session!"); int weightDelta = 0; - foreach (var item in mainCollection) - { - m_main.UpdateInventoryItem(item.ItemID, -item.Amount, true); - weightDelta -= OldWorld.Instance.EIF[item.ItemID].Weight * item.Amount; - } - foreach (var item in otherCollection) - { - m_main.UpdateInventoryItem(item.ItemID, item.Amount, true); - weightDelta += OldWorld.Instance.EIF[item.ItemID].Weight * item.Amount; - } + //foreach (var item in mainCollection) + //{ + // m_main.UpdateInventoryItem(item.ItemID, -item.Amount, true); + // weightDelta -= OldWorld.Instance.EIF[item.ItemID].Weight * item.Amount; + //} + //foreach (var item in otherCollection) + //{ + // m_main.UpdateInventoryItem(item.ItemID, item.Amount, true); + // weightDelta += OldWorld.Instance.EIF[item.ItemID].Weight * item.Amount; + //} m_main.Weight += (byte)weightDelta; ((EOGame)Game).Hud.RefreshStats(); @@ -325,14 +325,14 @@ private void _buttonOkClicked(object sender, EventArgs e) List otherCollection = m_main.ID == m_leftPlayerID ? m_rightItems : m_leftItems; //make sure that the items will fit! - if (!((EOGame)Game).Hud.ItemsFit( - otherCollection.Select(_item => new InventoryItem(_item.ID, _item.Amount)).ToList(), - mainCollection.Select(_item => new InventoryItem(_item.ID, _item.Amount)).ToList())) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRANSFER_NOT_ENOUGH_SPACE), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - return; - } + //if (!((EOGame)Game).Hud.ItemsFit( + // otherCollection.Select(_item => new InventoryItem(_item.ID, _item.Amount)).ToList(), + // mainCollection.Select(_item => new InventoryItem(_item.ID, _item.Amount)).ToList())) + //{ + // EOMessageBox.Show(OldWorld.GetString(EOResourceID.DIALOG_TRANSFER_NOT_ENOUGH_SPACE), + // OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + // return; + //} //make sure the change in weight + existing weight is not greater than the max weight! int weightDelta = otherCollection.Sum(itemRef => OldWorld.Instance.EIF[itemRef.ID].Weight * itemRef.Amount); @@ -387,16 +387,16 @@ private void _removeItem(int id) public override void Update(GameTime gt) { - if (EOGame.Instance.Hud.IsInventoryDragging()) - { - shouldClickDrag = false; - SuppressParentClickDrag(true); - } - else - { - shouldClickDrag = true; - SuppressParentClickDrag(false); - } + //if (EOGame.Instance.Hud.IsInventoryDragging()) + //{ + // shouldClickDrag = false; + // SuppressParentClickDrag(true); + //} + //else + //{ + // shouldClickDrag = true; + // SuppressParentClickDrag(false); + //} //do the hiding logic for both sides List scrollBars = new List { m_leftScroll, m_rightScroll }; diff --git a/EndlessClient/EndlessClient.csproj b/EndlessClient/EndlessClient.csproj index 19d7294ef..1bd447a7a 100644 --- a/EndlessClient/EndlessClient.csproj +++ b/EndlessClient/EndlessClient.csproj @@ -60,6 +60,6 @@ - + diff --git a/EndlessClient/HUD/Controls/HUD.cs b/EndlessClient/HUD/Controls/HUD.cs index 26cd2a536..131d04c84 100644 --- a/EndlessClient/HUD/Controls/HUD.cs +++ b/EndlessClient/HUD/Controls/HUD.cs @@ -26,7 +26,6 @@ public class HUD : DrawableGameComponent private const int HUD_CONTROL_DRAW_ORDER = 101; private readonly OldChatRenderer chatRenderer; - private OldEOInventory inventory; private readonly OldEOPartyPanel m_party; private OldActiveSpells activeSpells; @@ -95,7 +94,6 @@ public override void Initialize() //the draw orders are adjusted for child items in the constructor. //calling SetParent will break this. - //inventory = new OldEOInventory(pnlInventory, m_packetAPI); //activeSpells = new OldActiveSpells(pnlActiveSpells, m_packetAPI); activeSpells.Initialize(); @@ -141,33 +139,8 @@ public void SetStatusLabel(EOResourceID type, string detail) //SetStatusLabelText(string.Format("[ {0} ] {1}", typeText, detail)); } - public bool UpdateInventory(InventoryItem item) - { - if (item.Amount <= 0) - inventory.RemoveItem(item.ItemID); - else - return inventory.UpdateItem(item); - return true; - } - public bool IsInventoryDragging() - { - return !inventory.NoItemsDragging(); - } - public bool InventoryFits(short id) - { - return inventory.ItemFits(id); - } - public bool ItemsFit(List newItems, List oldItems = null) - { - return inventory.ItemsFit(newItems, oldItems); - } - public void DisableEffectPotionUse() { inventory.DisableEffectPotions(); } - public void EnableEffectPotionUse() { inventory.EnableEffectPotions(); } - public void RefreshStats() { - if(inventory != null) - inventory.UpdateWeightLabel(); if (activeSpells != null) activeSpells.RefreshTotalSkillPoints(); } @@ -194,7 +167,6 @@ protected override void Dispose(bool disposing) { m_packetAPI.Dispose(); - inventory.Dispose(); chatRenderer.Dispose(); m_expInfo.Close(); diff --git a/EndlessClient/HUD/IStatusLabelSetter.cs b/EndlessClient/HUD/IStatusLabelSetter.cs index 4b247d1f8..bae49c721 100644 --- a/EndlessClient/HUD/IStatusLabelSetter.cs +++ b/EndlessClient/HUD/IStatusLabelSetter.cs @@ -7,5 +7,7 @@ public interface IStatusLabelSetter void SetStatusLabel(EOResourceID type, EOResourceID text, string appended = ""); void SetStatusLabel(EOResourceID type, string prepended, EOResourceID text); + + void SetStatusLabel(EOResourceID type, string text); } } diff --git a/EndlessClient/HUD/Inventory/InventoryPanelItem.cs b/EndlessClient/HUD/Inventory/InventoryPanelItem.cs new file mode 100644 index 000000000..f91bdc04a --- /dev/null +++ b/EndlessClient/HUD/Inventory/InventoryPanelItem.cs @@ -0,0 +1,253 @@ +using EndlessClient.HUD.Panels; +using EOLib; +using EOLib.Domain.Character; +using EOLib.Graphics; +using EOLib.IO.Extensions; +using EOLib.IO.Pub; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Graphics; +using Microsoft.Xna.Framework.Input; +using System; +using System.Diagnostics; +using XNAControls; + +namespace EndlessClient.HUD.Inventory +{ + public class InventoryPanelItem : XNAControl + { + public class ItemDragCompletedEventArgs + { + public bool ContinueDrag { get; set; } = false; + + public bool RestoreOriginalSlot { get; set; } = false; + + public EIFRecord Data { get; } + + public ItemDragCompletedEventArgs(EIFRecord data) => Data = data; + } + + // uses absolute coordinates + private static readonly Rectangle InventoryGridArea = new Rectangle(114, 338, 363, 102); + + private readonly InventoryPanel _inventoryPanel; + private readonly Texture2D _itemGraphic; + private readonly Texture2D _highlightBackground; + private readonly XNALabel _nameLabel; + + private int _slot; + + private readonly Stopwatch _clickTimer; + private int _recentClicks; + + private ulong _updateTick; + + // Ru Paul's drag properties + private bool _beingDragged; + private Vector2 _oldOffset; + + private bool MousePressed => CurrentMouseState.LeftButton == ButtonState.Pressed && PreviousMouseState.LeftButton == ButtonState.Released; + + private bool MouseReleased => CurrentMouseState.LeftButton == ButtonState.Released && PreviousMouseState.LeftButton == ButtonState.Pressed; + + private bool MouseHeld => CurrentMouseState.LeftButton == ButtonState.Pressed && PreviousMouseState.LeftButton == ButtonState.Pressed; + + public int Slot + { + get => _slot; + set + { + _slot = value; + DrawPosition = GetPosition(_slot); + DrawOrder = 102 - (_slot % InventoryPanel.InventoryRowSlots) * 2; + } + } + + public IInventoryItem InventoryItem { get; set; } + + public string Text + { + get => _nameLabel.Text; + set + { + _nameLabel.Text = value; + _nameLabel.ResizeBasedOnText(16, 9); + } + } + + public bool IsDragging => _beingDragged; + + public EIFRecord Data { get; } + + public event EventHandler DoubleClick; + public event EventHandler DoneDragging; + + public InventoryPanelItem(IItemNameColorService itemNameColorService, InventoryPanel inventoryPanel, int slot, IInventoryItem inventoryItem, EIFRecord data) + { + _inventoryPanel = inventoryPanel; + Slot = slot; + InventoryItem = inventoryItem; + Data = data; + + _itemGraphic = inventoryPanel.NativeGraphicsManager.TextureFromResource(GFXTypes.Items, 2 * data.Graphic, transparent: true); + _highlightBackground = new Texture2D(Game.GraphicsDevice, 1, 1); + _highlightBackground.SetData(new[] { Color.FromNonPremultiplied(200, 200, 200, 60) }); + + _nameLabel = new XNALabel(Constants.FontSize08) + { + Visible = false, + AutoSize = false, + TextAlign = LabelAlignment.MiddleCenter, + ForeColor = itemNameColorService.GetColorForInventoryDisplay(Data), + BackColor = Color.FromNonPremultiplied(30, 30, 30, 160), + Text = string.Empty + }; + + OnMouseEnter += (_, _) => _nameLabel.Visible = !_beingDragged; + OnMouseLeave += (_, _) => _nameLabel.Visible = false; + + var (slotWidth, slotHeight) = Data.Size.GetDimensions(); + SetSize(slotWidth * 26 - 3, slotHeight * 26 - 3); + + _clickTimer = new Stopwatch(); + } + + public int GetCurrentSlotBasedOnPosition() + { + if (!_beingDragged) + return Slot; + + return (int)((DrawPosition.X - _oldOffset.X) / 26) + InventoryPanel.InventoryRowSlots * (int)((DrawPosition.Y - _oldOffset.Y) / 26); + } + + public void StartDragging() + { + _beingDragged = true; + _nameLabel.Visible = false; + + _oldOffset = DrawPositionWithParentOffset - DrawPosition; + + // todo: drag without unparenting this control + SetControlUnparented(); + AddControlToDefaultGame(); + + DrawOrder = 1000; + } + + public override void Initialize() + { + _nameLabel.Initialize(); + _nameLabel.SetParentControl(this); + _nameLabel.ResizeBasedOnText(16, 9); + + base.Initialize(); + } + + protected override void OnUpdateControl(GameTime gameTime) + { + if (_recentClicks > 0) + { + if (_clickTimer.Elapsed.TotalMilliseconds > 500) + { + _clickTimer.Restart(); + _recentClicks--; + } + else if (_clickTimer.Elapsed.TotalMilliseconds > 200 && _inventoryPanel.NoItemsDragging()) + { + StartDragging(); + } + } + + if (MousePressed) + _updateTick = 0; + + if (!_beingDragged && MouseOver && MouseOverPreviously && MouseReleased) + { + _clickTimer.Restart(); + _recentClicks++; + + if (_recentClicks == 2) + { + DoubleClick?.Invoke(this, Data); + _recentClicks = 0; + } + } + else if (++_updateTick % 8 == 0 && !_beingDragged && MouseOver && MouseOverPreviously && MouseHeld) + { + if (_inventoryPanel.NoItemsDragging()) + { + StartDragging(); + } + } + else if (_beingDragged) + { + DrawPosition = new Vector2(CurrentMouseState.X - (DrawArea.Width / 2), CurrentMouseState.Y - (DrawArea.Height / 2)); + + if (MouseReleased) + { + var args = new ItemDragCompletedEventArgs(Data); + DoneDragging?.Invoke(this, args); + + if (!args.ContinueDrag) + { + if (Game.Components.Contains(this)) + Game.Components.Remove(this); + + SetParentControl(_inventoryPanel); + + if (!args.RestoreOriginalSlot) + Slot = GetCurrentSlotBasedOnPosition(); + else + DrawPosition = GetPosition(Slot); + + _beingDragged = false; + _nameLabel.Visible = false; + } + } + } + + base.OnUpdateControl(gameTime); + } + + protected override void OnDrawControl(GameTime gameTime) + { + _spriteBatch.Begin(); + + if (MouseOver) + { + if (!_beingDragged || InventoryGridArea.Contains(CurrentMouseState.Position)) + { + // slot based on current mouse position if being dragged + var currentSlot = GetCurrentSlotBasedOnPosition(); + var drawPosition = GetPosition(currentSlot) + (_beingDragged ? _oldOffset : ImmediateParent.DrawPositionWithParentOffset); + + if (InventoryGridArea.Contains(DrawArea.WithPosition(drawPosition))) + { + _spriteBatch.Draw(_highlightBackground, DrawArea.WithPosition(drawPosition), Color.White); + } + } + } + + _spriteBatch.Draw(_itemGraphic, DrawPositionWithParentOffset, Color.FromNonPremultiplied(255, 255, 255, _beingDragged ? 128 : 255)); + + _spriteBatch.End(); + + base.OnDrawControl(gameTime); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _nameLabel.Dispose(); + _highlightBackground.Dispose(); + } + + base.Dispose(disposing); + } + + private static Vector2 GetPosition(int slot) + { + return new Vector2(13 + 26 * (slot % InventoryPanel.InventoryRowSlots), 9 + 26 * (slot / InventoryPanel.InventoryRowSlots)); + } + } +} diff --git a/EndlessClient/HUD/Inventory/InventoryService.cs b/EndlessClient/HUD/Inventory/InventoryService.cs new file mode 100644 index 000000000..f111b8af1 --- /dev/null +++ b/EndlessClient/HUD/Inventory/InventoryService.cs @@ -0,0 +1,113 @@ +using AutomaticTypeMapper; +using EndlessClient.HUD.Panels; +using EOLib.IO; +using EOLib.IO.Extensions; +using Optional; +using System.Collections.Generic; + +namespace EndlessClient.HUD.Inventory +{ + [AutoMappedType] + public class InventoryService : IInventoryService + { + public Option GetNextOpenSlot(bool[,] usedSlots, ItemSize size, Option preferredSlot) + { + var (sizeWidth, sizeHeight) = size.GetDimensions(); + + var preferredSlotIsValid = preferredSlot.Match( + some: slot => IsSlotOpen(usedSlots, Option.None(), slot, size), + none: () => false); + + if (preferredSlotIsValid) + return preferredSlot; + + for (int r = 0; r < usedSlots.GetLength(0); r++) + { + for (int c = 0; c < usedSlots.GetLength(1); c++) + { + var slot = r * InventoryPanel.InventoryRowSlots + c; + if (!usedSlots[r, c] && IsSlotOpen(usedSlots, Option.None(), slot, size)) + return Option.Some(slot); + } + } + + return Option.None(); + } + + public void SetSlots(bool[,] usedSlots, int slot, ItemSize size) + { + SetSlotValue(usedSlots, slot, size, value: true); + } + + public void ClearSlots(bool[,] usedSlots, int slot, ItemSize size) + { + SetSlotValue(usedSlots, slot, size, value: false); + } + + public bool FitsInSlot(bool[,] usedSlots, int oldSlot, int newSlot, ItemSize size) => + IsSlotOpen(usedSlots, Option.Some(oldSlot), newSlot, size); + + public bool FitsInSlot(bool[,] usedSlots, int newSlot, ItemSize size) => + IsSlotOpen(usedSlots, Option.None(), newSlot, size); + + private bool IsSlotOpen(bool[,] usedSlots, Option oldSlot, int slot, ItemSize size) + { + var (sizeWidth, sizeHeight) = size.GetDimensions(); + + var ignorePoints = new List<(int X, int Y)>(); + oldSlot.MatchSome(s => + { + var oldCol = s % InventoryPanel.InventoryRowSlots; + var oldRow = s / InventoryPanel.InventoryRowSlots; + + for (int r = oldRow; r < oldRow + sizeHeight; r++) + for (int c = oldCol; c < oldCol + sizeWidth; c++) + ignorePoints.Add((c, r)); + }); + + var col = slot % InventoryPanel.InventoryRowSlots; + var row = slot / InventoryPanel.InventoryRowSlots; + for (int r = row; r < row + sizeHeight; r++) + { + for (int c = col; c < col + sizeWidth; c++) + { + if (r >= usedSlots.GetLength(0) || c >= usedSlots.GetLength(1) || + r < 0 || c < 0 || + (!ignorePoints.Contains((c, r)) && usedSlots[r, c])) + return false; + } + } + + return true; + } + + private void SetSlotValue(bool[,] usedSlots, int slot, ItemSize size, bool value) + { + var (sizeWidth, sizeHeight) = size.GetDimensions(); + + var slotCol = slot % InventoryPanel.InventoryRowSlots; + var slotRow = slot / InventoryPanel.InventoryRowSlots; + + for (int r = slotRow; r < slotRow + sizeHeight; r++) + { + for (int c = slotCol; c < slotCol + sizeWidth; c++) + { + if (r < usedSlots.GetLength(0) && c < usedSlots.GetLength(1)) + usedSlots[r, c] = value; + } + } + } + } + + public interface IInventoryService + { + Option GetNextOpenSlot(bool[,] usedSlots, ItemSize size, Option preferredSlot); + void SetSlots(bool[,] usedSlots, int slot, ItemSize size); + + void ClearSlots(bool[,] usedSlots, int slot, ItemSize size); + + bool FitsInSlot(bool[,] usedSlots, int oldSlot, int newSlot, ItemSize size); + + bool FitsInSlot(bool[,] usedSlots, int newSlot, ItemSize size); + } +} diff --git a/EndlessClient/HUD/Inventory/InventorySlotRepository.cs b/EndlessClient/HUD/Inventory/InventorySlotRepository.cs new file mode 100644 index 000000000..1da3bc145 --- /dev/null +++ b/EndlessClient/HUD/Inventory/InventorySlotRepository.cs @@ -0,0 +1,29 @@ +using AutomaticTypeMapper; +using EndlessClient.HUD.Panels; +using EOLib.IO.Map; + +namespace EndlessClient.HUD.Inventory +{ + public interface IInventorySlotRepository + { + Matrix FilledSlots { get; set; } + } + + public interface IInventorySlotProvider + { + IReadOnlyMatrix FilledSlots { get; } + } + + [AutoMappedType(IsSingleton = true)] + public class InventorySlotRepository : IInventorySlotProvider, IInventorySlotRepository + { + public Matrix FilledSlots { get; set; } + + IReadOnlyMatrix IInventorySlotProvider.FilledSlots => FilledSlots; + + public InventorySlotRepository() + { + FilledSlots = new Matrix(InventoryPanel.InventoryRows, InventoryPanel.InventoryRowSlots, false); + } + } +} diff --git a/EndlessClient/HUD/Inventory/InventorySpaceValidator.cs b/EndlessClient/HUD/Inventory/InventorySpaceValidator.cs index 6dcfd18c5..2d2aa416e 100644 --- a/EndlessClient/HUD/Inventory/InventorySpaceValidator.cs +++ b/EndlessClient/HUD/Inventory/InventorySpaceValidator.cs @@ -1,7 +1,9 @@ using AutomaticTypeMapper; using EOLib.Domain.Map; using EOLib.IO; +using EOLib.IO.Map; using EOLib.IO.Repositories; +using Optional; namespace EndlessClient.HUD.Inventory { @@ -9,10 +11,16 @@ namespace EndlessClient.HUD.Inventory public class InventorySpaceValidator : IInventorySpaceValidator { private readonly IEIFFileProvider _eifFileProvider; + private readonly IInventorySlotProvider _inventorySlotProvider; + private readonly IInventoryService _inventoryService; - public InventorySpaceValidator(IEIFFileProvider eifFileProvider) + public InventorySpaceValidator(IEIFFileProvider eifFileProvider, + IInventorySlotProvider inventorySlotProvider, + IInventoryService inventoryService) { _eifFileProvider = eifFileProvider; + _inventorySlotProvider = inventorySlotProvider; + _inventoryService = inventoryService; } public bool ItemFits(IItem item) @@ -22,8 +30,9 @@ public bool ItemFits(IItem item) public bool ItemFits(ItemSize itemSize) { - // todo: inventory grid management - return true; + return _inventoryService + .GetNextOpenSlot((Matrix)_inventorySlotProvider.FilledSlots, itemSize, Option.None()) + .HasValue; } } @@ -32,5 +41,7 @@ public interface IInventorySpaceValidator bool ItemFits(IItem item); bool ItemFits(ItemSize itemSize); + + // todo: need "ItemsFit" method for trading } } diff --git a/EndlessClient/HUD/Inventory/OldEOInventoryItem.cs b/EndlessClient/HUD/Inventory/OldEOInventoryItem.cs deleted file mode 100644 index 3368771a8..000000000 --- a/EndlessClient/HUD/Inventory/OldEOInventoryItem.cs +++ /dev/null @@ -1,590 +0,0 @@ -using System; -using System.Linq; -using System.Threading; -using EndlessClient.Dialogs; -using EndlessClient.Dialogs.Old; -using EndlessClient.HUD.Panels.Old; -using EndlessClient.Old; -using EOLib; -using EOLib.Domain.Character; -using EOLib.Graphics; -using EOLib.IO; -using EOLib.IO.Extensions; -using EOLib.IO.Pub; -using EOLib.Localization; -using EOLib.Net.API; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using Microsoft.Xna.Framework.Input; -using XNAControls.Old; - -namespace EndlessClient.HUD.Inventory -{ - //Name conflict: InventoryItem already exists. - //Keeping the EO prefix on this one since naming is hard - public class OldEOInventoryItem : XNAControl - { - private readonly EIFRecord m_itemData; - public EIFRecord ItemData => m_itemData; - - private InventoryItem m_inventory; - public InventoryItem Inventory { get { return m_inventory; } set { m_inventory = value; } } - - public int Slot { get; private set; } - - private readonly Texture2D m_itemgfx, m_highlightBG; - private XNALabel m_nameLabel; - - private bool m_beingDragged; - private int m_alpha = 255; - private int m_preDragDrawOrder; - private XNAControl m_preDragParent; - private int m_oldOffX, m_oldOffY; - public bool Dragging => m_beingDragged; - - private int m_recentClickCount; - private readonly Timer m_recentClickTimer; - - private readonly PacketAPI m_api; - private static bool safetyCommentHasBeenShown; - - public OldEOInventoryItem(PacketAPI api, int slot, EIFRecord itemData, InventoryItem itemInventoryInfo, OldEOInventory inventory) - : base(null, null, inventory) - { - m_api = api; - m_itemData = itemData; - m_inventory = itemInventoryInfo; - Slot = slot; - - UpdateItemLocation(Slot); - - m_itemgfx = ((EOGame)Game).GFXManager.TextureFromResource(GFXTypes.Items, 2 * itemData.Graphic, true); - - m_highlightBG = new Texture2D(Game.GraphicsDevice, DrawArea.Width - 3, DrawArea.Height - 3); - Color[] highlight = new Color[(drawArea.Width - 3) * (drawArea.Height - 3)]; - for (int i = 0; i < highlight.Length; ++i) { highlight[i] = Color.FromNonPremultiplied(200, 200, 200, 60); } - m_highlightBG.SetData(highlight); - - _initItemLabel(); - - m_recentClickTimer = new Timer( - _state => { if (m_recentClickCount > 0) Interlocked.Decrement(ref m_recentClickCount); }, null, 0, 1000); - } - - public override void Update(GameTime gameTime) - { - if (!Game.IsActive || !Enabled) return; - - //check for drag-drop here - MouseState currentState = Mouse.GetState(); - - if (!m_beingDragged && MouseOverPreviously && MouseOver && PreviousMouseState.LeftButton == ButtonState.Pressed && currentState.LeftButton == ButtonState.Pressed) - { - //Conditions for starting are the mouse is over, the button is pressed, and no other items are being dragged - if (((OldEOInventory)parent).NoItemsDragging()) - { - //start the drag operation and hide the item label - m_beingDragged = true; - m_nameLabel.Visible = false; - m_preDragDrawOrder = DrawOrder; - m_preDragParent = parent; - - //make sure the offsets are maintained! - //required to enable dragging past bounds of the inventory panel - m_oldOffX = xOff; - m_oldOffY = yOff; - SetParent(null); - - m_alpha = 128; - DrawOrder = 200; //arbitrarily large constant so drawn on top while dragging - } - } - - if (m_beingDragged && PreviousMouseState.LeftButton == ButtonState.Pressed && - currentState.LeftButton == ButtonState.Pressed) - { - //dragging has started. continue dragging until mouse is released, update position based on mouse location - DrawLocation = new Vector2(currentState.X - (DrawArea.Width / 2), currentState.Y - (DrawArea.Height / 2)); - } - else if (m_beingDragged && PreviousMouseState.LeftButton == ButtonState.Pressed && - currentState.LeftButton == ButtonState.Released) - { - //need to check for: drop on map (drop action) - // drop on button junk/drop - // drop on grid (inventory move action) - // drop on [x] dialog ([x] add action) - - m_alpha = 255; - SetParent(m_preDragParent); - - if (((OldEOInventory)parent).IsOverDrop() || (OldWorld.Instance.ActiveMapRenderer.MouseOver - //&& ChestDialog.Instance == null && EOPaperdollDialog.Instance == null && LockerDialog.Instance == null - && BankAccountDialog.Instance == null && TradeDialog.Instance == null)) - { - Point loc = OldWorld.Instance.ActiveMapRenderer.MouseOver ? OldWorld.Instance.ActiveMapRenderer.GridCoords : - new Point(OldWorld.Instance.MainPlayer.ActiveCharacter.X, OldWorld.Instance.MainPlayer.ActiveCharacter.Y); - - //in range if maximum coordinate difference is <= 2 away - bool inRange = Math.Abs(Math.Max(OldWorld.Instance.MainPlayer.ActiveCharacter.X - loc.X, OldWorld.Instance.MainPlayer.ActiveCharacter.Y - loc.Y)) <= 2; - - if (m_itemData.Special == ItemSpecial.Lore) - { - EOMessageBox.Show(DialogResourceID.ITEM_IS_LORE_ITEM, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - else if (OldWorld.Instance.JailMap == OldWorld.Instance.MainPlayer.ActiveCharacter.CurrentMap) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.JAIL_WARNING_CANNOT_DROP_ITEMS), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - else if (m_inventory.Amount > 1 && inRange) - { - ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.DropItems, - m_inventory.Amount); - dlg.DialogClosing += (sender, args) => - { - if (args.Result == XNADialogResult.OK) - { - //note: not sure of the actual limit. 10000 is arbitrary here - if (dlg.SelectedAmount > 10000 && m_inventory.ItemID == 1 && !safetyCommentHasBeenShown) - EOMessageBox.Show(DialogResourceID.DROP_MANY_GOLD_ON_GROUND, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader, - (o, e) => { safetyCommentHasBeenShown = true; }); - else if (!m_api.DropItem(m_inventory.ItemID, dlg.SelectedAmount, (byte)loc.X, (byte)loc.Y)) - ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - } - }; - } - else if (inRange) - { - if (!m_api.DropItem(m_inventory.ItemID, 1, (byte)loc.X, (byte)loc.Y)) - ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - } - else /*if (!inRange)*/ - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_ITEM_DROP_OUT_OF_RANGE); - } - } - else if (((OldEOInventory)parent).IsOverJunk()) - { - //if (m_inventory.Amount > 1) - //{ - // ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.JunkItems, - // m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_JUNK); - // dlg.DialogClosing += (sender, args) => - // { - // if (args.Result == XNADialogResult.OK && !m_api.JunkItem(m_inventory.ItemID, dlg.SelectedAmount)) - // ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - // }; - //} - //else if (!m_api.JunkItem(m_inventory.ItemID, 1)) - // ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - } - else if (ChestDialog.Instance != null && ChestDialog.Instance.MouseOver && ChestDialog.Instance.MouseOverPreviously) - { - if (m_itemData.Special == ItemSpecial.Lore) - { - EOMessageBox.Show(DialogResourceID.ITEM_IS_LORE_ITEM, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - else if (m_inventory.Amount > 1) - { - ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.DropItems, m_inventory.Amount); - dlg.DialogClosing += (sender, args) => - { - if (args.Result == XNADialogResult.OK && - !m_api.ChestAddItem(ChestDialog.Instance.CurrentChestX, ChestDialog.Instance.CurrentChestY, - m_inventory.ItemID, dlg.SelectedAmount)) - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - }; - } - else - { - if (!m_api.ChestAddItem(ChestDialog.Instance.CurrentChestX, ChestDialog.Instance.CurrentChestY, m_inventory.ItemID, 1)) - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - } - //else if (EOPaperdollDialog.Instance != null && EOPaperdollDialog.Instance.MouseOver && EOPaperdollDialog.Instance.MouseOverPreviously) - //{ - // //equipable items should be equipped - // //other item types should do nothing - // switch (m_itemData.Type) - // { - // case ItemType.Accessory: - // case ItemType.Armlet: - // case ItemType.Armor: - // case ItemType.Belt: - // case ItemType.Boots: - // case ItemType.Bracer: - // case ItemType.Gloves: - // case ItemType.Hat: - // case ItemType.Necklace: - // case ItemType.Ring: - // case ItemType.Shield: - // case ItemType.Weapon: - // _handleDoubleClick(); - // break; - // } - //} - else if (LockerDialog.Instance != null && LockerDialog.Instance.MouseOver && LockerDialog.Instance.MouseOverPreviously) - { - byte x = LockerDialog.Instance.X; - byte y = LockerDialog.Instance.Y; - if (m_inventory.ItemID == 1) - { - EOMessageBox.Show(DialogResourceID.LOCKER_DEPOSIT_GOLD_ERROR, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - else if (m_inventory.Amount > 1) - { - ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.ShopTransfer, m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_TRANSFER); - dlg.DialogClosing += (sender, args) => - { - if (args.Result == XNADialogResult.OK) - { - if (LockerDialog.Instance.GetNewItemAmount(m_inventory.ItemID, dlg.SelectedAmount) > Constants.LockerMaxSingleItemAmount) - EOMessageBox.Show(DialogResourceID.LOCKER_FULL_SINGLE_ITEM_MAX, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - else if (!m_api.LockerAddItem(x, y, m_inventory.ItemID, dlg.SelectedAmount)) - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - }; - } - else - { - if (LockerDialog.Instance.GetNewItemAmount(m_inventory.ItemID, 1) > Constants.LockerMaxSingleItemAmount) - EOMessageBox.Show(DialogResourceID.LOCKER_FULL_SINGLE_ITEM_MAX, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - else if (!m_api.LockerAddItem(x, y, m_inventory.ItemID, 1)) - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - } - else if (BankAccountDialog.Instance != null && BankAccountDialog.Instance.MouseOver && BankAccountDialog.Instance.MouseOverPreviously && m_inventory.ItemID == 1) - { - if (m_inventory.Amount == 0) - { - EOMessageBox.Show(DialogResourceID.BANK_ACCOUNT_UNABLE_TO_DEPOSIT, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - else if (m_inventory.Amount > 1) - { - ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.BankTransfer, - m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_DEPOSIT); - dlg.DialogClosing += (o, e) => - { - if (e.Result == XNADialogResult.Cancel) - return; - - if (!m_api.BankDeposit(dlg.SelectedAmount)) - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - }; - } - else - { - if (!m_api.BankDeposit(1)) - EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - } - else if (TradeDialog.Instance != null && TradeDialog.Instance.MouseOver && TradeDialog.Instance.MouseOverPreviously - && !TradeDialog.Instance.MainPlayerAgrees) - { - if (m_itemData.Special == ItemSpecial.Lore) - { - EOMessageBox.Show(DialogResourceID.ITEM_IS_LORE_ITEM); - } - else if (m_inventory.Amount > 1) - { - ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.TradeItems, - m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_OFFER); - dlg.DialogClosing += (o, e) => - { - if (e.Result != XNADialogResult.OK) return; - - if (!m_api.TradeAddItem(m_inventory.ItemID, dlg.SelectedAmount)) - { - TradeDialog.Instance.Close(XNADialogResult.NO_BUTTON_PRESSED); - ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - } - }; - } - else if (!m_api.TradeAddItem(m_inventory.ItemID, 1)) - { - TradeDialog.Instance.Close(XNADialogResult.NO_BUTTON_PRESSED); - ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - } - } - - //update the location - if it isn't on the grid, the bounds check will set it back to where it used to be originally - //Item amount will be updated or item will be removed in packet response to the drop operation - UpdateItemLocation(ItemCurrentSlot()); - - //mouse has been released. finish dragging. - m_beingDragged = false; - m_nameLabel.Visible = true; - DrawOrder = m_preDragDrawOrder; - } - - if (!m_beingDragged && PreviousMouseState.LeftButton == ButtonState.Pressed && - currentState.LeftButton == ButtonState.Released && MouseOver && MouseOverPreviously) - { - Interlocked.Increment(ref m_recentClickCount); - if (m_recentClickCount == 2) - { - _handleDoubleClick(); - } - } - - if (!MouseOverPreviously && MouseOver && !m_beingDragged) - { - m_nameLabel.Visible = true; - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ITEM, m_nameLabel.Text); - } - else if (!MouseOver && !m_beingDragged && m_nameLabel != null && m_nameLabel.Visible) - { - m_nameLabel.Visible = false; - } - - base.Update(gameTime); //sets mouseoverpreviously = mouseover, among other things - } - - public override void Draw(GameTime gameTime) - { - if (!Visible) return; - - SpriteBatch.Begin(); - if (MouseOver) - { - int currentSlot = ItemCurrentSlot(); - Vector2 drawLoc = m_beingDragged - ? new Vector2(m_oldOffX + 13 + 26 * (currentSlot % OldEOInventory.INVENTORY_ROW_LENGTH), - m_oldOffY + 9 + 26 * (currentSlot / OldEOInventory.INVENTORY_ROW_LENGTH)) //recalculate the top-left point for the highlight based on the current drag position - : new Vector2(DrawAreaWithOffset.X, DrawAreaWithOffset.Y); - - if (OldEOInventory.GRID_AREA.Contains(DrawAreaWithOffset)) - SpriteBatch.Draw(m_highlightBG, drawLoc, Color.White); - } - if (m_itemgfx != null) - SpriteBatch.Draw(m_itemgfx, new Vector2(DrawAreaWithOffset.X, DrawAreaWithOffset.Y), Color.FromNonPremultiplied(255, 255, 255, m_alpha)); - SpriteBatch.End(); - base.Draw(gameTime); - } - - private void UpdateItemLocation(int newSlot) - { - if (Slot != newSlot && ((OldEOInventory)parent).MoveItem(this, newSlot)) Slot = newSlot; - - //top-left grid slot in the inventory is 115, 339 - //parent top-left is 103, 330 - //grid size is 26*26 (w/o borders 23*23) - int width, height; - OldEOInventory._getItemSizeDeltas(m_itemData.Size, out width, out height); - DrawLocation = new Vector2(13 + 26 * (Slot % OldEOInventory.INVENTORY_ROW_LENGTH), 9 + 26 * (Slot / OldEOInventory.INVENTORY_ROW_LENGTH)); - _setSize(width * 26, height * 26); - - if (m_nameLabel != null) //fix the position of the name label too if we aren't creating the inventoryitem - { - m_nameLabel.DrawLocation = new Vector2(DrawArea.Width, 0); - if (!OldEOInventory.GRID_AREA.Contains(m_nameLabel.DrawAreaWithOffset)) - m_nameLabel.DrawLocation = new Vector2(-m_nameLabel.DrawArea.Width, 0); //show on the right if it isn't in bounds! - m_nameLabel.ResizeBasedOnText(16, 9); - } - } - - private int ItemCurrentSlot() - { - if (!m_beingDragged) return Slot; - - //convert the current draw area to a slot number (for when the item is dragged) - return (int)((DrawLocation.X - m_oldOffX - 13) / 26) + OldEOInventory.INVENTORY_ROW_LENGTH * (int)((DrawLocation.Y - m_oldOffY - 9) / 26); - } - - public void UpdateItemLabel() - { - m_nameLabel.Text = GetNameString(m_inventory.ItemID, m_inventory.Amount); - m_nameLabel.ResizeBasedOnText(16, 9); - } - - protected override void Dispose(bool disposing) - { - if (m_recentClickTimer != null) m_recentClickTimer.Dispose(); - if (m_nameLabel != null) m_nameLabel.Dispose(); - if (m_highlightBG != null) m_highlightBG.Dispose(); - - base.Dispose(disposing); - } - - private void _initItemLabel() - { - if (m_nameLabel != null) m_nameLabel.Dispose(); - - m_nameLabel = new XNALabel(new Rectangle(DrawArea.Width, 0, 150, 23), Constants.FontSize08) - { - Visible = false, - AutoSize = false, - TextAlign = LabelAlignment.MiddleCenter, - ForeColor = Color.FromNonPremultiplied(200, 200, 200, 255), - BackColor = Color.FromNonPremultiplied(30, 30, 30, 160) - }; - - UpdateItemLabel(); - - m_nameLabel.ForeColor = GetItemTextColor((short)m_itemData.ID); - - m_nameLabel.SetParent(this); - m_nameLabel.ResizeBasedOnText(16, 9); - } - - private void _handleDoubleClick() - { - OldCharacter c = OldWorld.Instance.MainPlayer.ActiveCharacter; - - //bool useItem = false; - switch (m_itemData.Type) - { - //equippable items - case ItemType.Accessory: - case ItemType.Armlet: - case ItemType.Armor: - case ItemType.Belt: - case ItemType.Boots: - case ItemType.Bracer: - case ItemType.Gloves: - case ItemType.Hat: - case ItemType.Necklace: - case ItemType.Ring: - case ItemType.Shield: - case ItemType.Weapon: - byte subLoc = 0; - if (m_itemData.Type == ItemType.Armlet || m_itemData.Type == ItemType.Ring || m_itemData.Type == ItemType.Bracer) - { - if (c.PaperDoll[(int)m_itemData.GetEquipLocation()] == 0) - subLoc = 0; - else if (c.PaperDoll[(int)m_itemData.GetEquipLocation() + 1] == 0) - subLoc = 1; - else - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, - EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); - break; - } - } - else if (m_itemData.Type == ItemType.Armor && - m_itemData.Gender != c.RenderData.gender) - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, - EOResourceID.STATUS_LABEL_ITEM_EQUIP_DOES_NOT_FIT_GENDER); - break; - } - - //check stat requirements - int[] reqs = new int[6]; - string[] reqNames = { "STR", "INT", "WIS", "AGI", "CON", "CHA" }; - if ((reqs[0] = m_itemData.StrReq) > c.Stats.Str || (reqs[1] = m_itemData.IntReq) > c.Stats.Int - || (reqs[2] = m_itemData.WisReq) > c.Stats.Wis || (reqs[3] = m_itemData.AgiReq) > c.Stats.Agi - || (reqs[4] = m_itemData.ConReq) > c.Stats.Con || (reqs[5] = m_itemData.ChaReq) > c.Stats.Cha) - { - int reqIndex = reqs.ToList().FindIndex(x => x > 0); - - ((EOGame)Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, - EOResourceID.STATUS_LABEL_ITEM_EQUIP_THIS_ITEM_REQUIRES, - $" {reqs[reqIndex]} {reqNames[reqIndex]}"); - break; - } - //check class requirement - if (m_itemData.ClassReq > 0 && m_itemData.ClassReq != c.Class) - { - ((EOGame) Game).Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, - EOResourceID.STATUS_LABEL_ITEM_EQUIP_CAN_ONLY_BE_USED_BY, - OldWorld.Instance.ECF[m_itemData.ClassReq].Name); - break; - } - - if (c.EquipItem(m_itemData.Type, (short)m_itemData.ID, - (short)m_itemData.DollGraphic)) - { - //if (!m_api.EquipItem((short)m_itemData.ID, subLoc)) - // EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); - } - else - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, - EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); - - break; - //usable items - case ItemType.Teleport: - if (!OldWorld.Instance.ActiveMapRenderer.MapRef.Properties.CanScroll) - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_NOTHING_HAPPENED); - break; - } - if (m_itemData.ScrollMap == OldWorld.Instance.MainPlayer.ActiveCharacter.CurrentMap && - m_itemData.ScrollX == OldWorld.Instance.MainPlayer.ActiveCharacter.X && - m_itemData.ScrollY == OldWorld.Instance.MainPlayer.ActiveCharacter.Y) - break; //already there - no need to scroll! - //useItem = true; - break; - case ItemType.Heal: - case ItemType.HairDye: - case ItemType.Beer: - //useItem = true; - break; - case ItemType.CureCurse: - //note: don't actually set the useItem bool here. Call API.UseItem if the dialog result is OK. - if (c.PaperDoll.Select(id => OldWorld.Instance.EIF[id]) - .Any(rec => rec.Special == ItemSpecial.Cursed)) - { - EOMessageBox.Show(DialogResourceID.ITEM_CURSE_REMOVE_PROMPT, EODialogButtons.OkCancel, EOMessageBoxStyle.SmallDialogSmallHeader, - (o, e) => - { - //if (e.Result == XNADialogResult.OK && !m_api.UseItem((short)m_itemData.ID)) - //{ - // ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - //} - }); - } - break; - case ItemType.EXPReward: - //useItem = true; - break; - case ItemType.EffectPotion: - //useItem = true; - break; - //Not implemented server-side - //case ItemType.SkillReward: - // break; - //case ItemType.StatReward: - // break; - } - - //if (useItem && !m_api.UseItem((short)m_itemData.ID)) - // ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); - - m_recentClickCount = 0; - } - - public static Color GetItemTextColor(short id) //also used in map renderer for mapitems - { - var data = OldWorld.Instance.EIF[id]; - switch (data.Special) - { - case ItemSpecial.Lore: - case ItemSpecial.Unique: - return Color.FromNonPremultiplied(0xff, 0xf0, 0xa5, 0xff); - case ItemSpecial.Rare: - return Color.FromNonPremultiplied(0xf5, 0xc8, 0x9c, 0xff); - } - - return Color.White; - } - - public static string GetNameString(short id, int amount) - { - var data = OldWorld.Instance.EIF[id]; - switch (data.ID) - { - case 1: - return $"{amount} {data.Name}"; - default: - if (amount == 1) - return data.Name; - if (amount > 1) - return $"{data.Name} x{amount}"; - throw new Exception("There shouldn't be an item in the inventory with amount zero"); - } - } - } -} diff --git a/EndlessClient/HUD/ItemNameColorService.cs b/EndlessClient/HUD/ItemNameColorService.cs new file mode 100644 index 000000000..d37a3217c --- /dev/null +++ b/EndlessClient/HUD/ItemNameColorService.cs @@ -0,0 +1,37 @@ +using AutomaticTypeMapper; +using EOLib.Graphics; +using EOLib.IO; +using EOLib.IO.Pub; +using Microsoft.Xna.Framework; + +namespace EndlessClient.HUD +{ + [AutoMappedType] + public class ItemNameColorService : IItemNameColorService + { + public Color GetColorForInventoryDisplay(EIFRecord itemData) => GetColor(itemData, ColorConstants.LightGrayText); + + public Color GetColorForMapDisplay(EIFRecord itemData) => GetColor(itemData, Color.White); + + private static Color GetColor(EIFRecord itemData, Color defaultColor) + { + switch (itemData.Special) + { + case ItemSpecial.Lore: + case ItemSpecial.Unique: + return Color.FromNonPremultiplied(0xff, 0xf0, 0xa5, 0xff); + case ItemSpecial.Rare: + return Color.FromNonPremultiplied(0xf5, 0xc8, 0x9c, 0xff); + } + + return defaultColor; + } + } + + public interface IItemNameColorService + { + Color GetColorForMapDisplay(EIFRecord itemData); + + Color GetColorForInventoryDisplay(EIFRecord itemData); + } +} diff --git a/EndlessClient/HUD/Panels/HudPanelFactory.cs b/EndlessClient/HUD/Panels/HudPanelFactory.cs index 07755e770..0a68e02c1 100644 --- a/EndlessClient/HUD/Panels/HudPanelFactory.cs +++ b/EndlessClient/HUD/Panels/HudPanelFactory.cs @@ -2,14 +2,18 @@ using EndlessClient.Content; using EndlessClient.Controllers; using EndlessClient.ControlSets; +using EndlessClient.Dialogs.Actions; using EndlessClient.Dialogs.Factories; +using EndlessClient.HUD.Inventory; using EndlessClient.Rendering.Chat; using EndlessClient.Services; using EOLib; using EOLib.Domain.Character; using EOLib.Domain.Chat; +using EOLib.Domain.Item; using EOLib.Domain.Login; using EOLib.Graphics; +using EOLib.IO.Repositories; using Microsoft.Xna.Framework.Graphics; namespace EndlessClient.HUD.Panels @@ -20,40 +24,70 @@ public class HudPanelFactory : IHudPanelFactory private const int HUD_CONTROL_LAYER = 130; private readonly INativeGraphicsManager _nativeGraphicsManager; + private readonly IInGameDialogActions _inGameDialogActions; + private readonly ICharacterActions _characterActions; private readonly IContentProvider _contentProvider; private readonly IHudControlProvider _hudControlProvider; private readonly INewsProvider _newsProvider; private readonly IChatProvider _chatProvider; + private readonly IPlayerInfoProvider _playerInfoProvider; private readonly ICharacterProvider _characterProvider; private readonly ICharacterInventoryProvider _characterInventoryProvider; private readonly IExperienceTableProvider _experienceTableProvider; + private readonly IPubFileProvider _pubFileProvider; + private readonly IPaperdollProvider _paperdollProvider; + private readonly IInventorySlotRepository _inventorySlotRepository; private readonly IEOMessageBoxFactory _messageBoxFactory; private readonly ITrainingController _trainingController; private readonly IFriendIgnoreListService _friendIgnoreListService; + private readonly IStatusLabelSetter _statusLabelSetter; + private readonly IItemStringService _itemStringService; + private readonly IItemNameColorService _itemNameColorService; + private readonly IInventoryService _inventoryService; public HudPanelFactory(INativeGraphicsManager nativeGraphicsManager, + IInGameDialogActions inGameDialogActions, + ICharacterActions characterActions, IContentProvider contentProvider, IHudControlProvider hudControlProvider, INewsProvider newsProvider, IChatProvider chatProvider, + IPlayerInfoProvider playerInfoProvider, ICharacterProvider characterProvider, ICharacterInventoryProvider characterInventoryProvider, IExperienceTableProvider experienceTableProvider, + IPubFileProvider pubFileProvider, + IPaperdollProvider paperdollProvider, + IInventorySlotRepository inventorySlotRepository, IEOMessageBoxFactory messageBoxFactory, ITrainingController trainingController, - IFriendIgnoreListService friendIgnoreListService) + IFriendIgnoreListService friendIgnoreListService, + IStatusLabelSetter statusLabelSetter, + IItemStringService itemStringService, + IItemNameColorService itemNameColorService, + IInventoryService inventoryService) { _nativeGraphicsManager = nativeGraphicsManager; + _inGameDialogActions = inGameDialogActions; + _characterActions = characterActions; _contentProvider = contentProvider; _hudControlProvider = hudControlProvider; _newsProvider = newsProvider; _chatProvider = chatProvider; + _playerInfoProvider = playerInfoProvider; _characterProvider = characterProvider; _characterInventoryProvider = characterInventoryProvider; _experienceTableProvider = experienceTableProvider; + _pubFileProvider = pubFileProvider; + _paperdollProvider = paperdollProvider; + _inventorySlotRepository = inventorySlotRepository; _messageBoxFactory = messageBoxFactory; _trainingController = trainingController; _friendIgnoreListService = friendIgnoreListService; + _statusLabelSetter = statusLabelSetter; + _itemStringService = itemStringService; + _itemNameColorService = itemNameColorService; + _inventoryService = inventoryService; } public NewsPanel CreateNewsPanel() @@ -68,7 +102,19 @@ public NewsPanel CreateNewsPanel() public InventoryPanel CreateInventoryPanel() { - return new InventoryPanel(_nativeGraphicsManager) { DrawOrder = HUD_CONTROL_LAYER }; + return new InventoryPanel(_nativeGraphicsManager, + _inGameDialogActions, + _characterActions, + _statusLabelSetter, + _itemStringService, + _itemNameColorService, + _inventoryService, + _inventorySlotRepository, + _playerInfoProvider, + _characterProvider, + _paperdollProvider, + _characterInventoryProvider, + _pubFileProvider) { DrawOrder = HUD_CONTROL_LAYER }; } public ActiveSpellsPanel CreateActiveSpellsPanel() diff --git a/EndlessClient/HUD/Panels/InventoryPanel.cs b/EndlessClient/HUD/Panels/InventoryPanel.cs index c31cc1da5..cb1322fbc 100644 --- a/EndlessClient/HUD/Panels/InventoryPanel.cs +++ b/EndlessClient/HUD/Panels/InventoryPanel.cs @@ -1,19 +1,733 @@ -using EOLib.Graphics; +using EndlessClient.Dialogs.Actions; +using EndlessClient.HUD.Inventory; +using EOLib; +using EOLib.Config; +using EOLib.Domain.Character; +using EOLib.Domain.Item; +using EOLib.Domain.Login; +using EOLib.Graphics; +using EOLib.IO; +using EOLib.IO.Extensions; +using EOLib.IO.Pub; +using EOLib.IO.Repositories; +using EOLib.Localization; +using Microsoft.Win32; using Microsoft.Xna.Framework; +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 InventoryPanel : XNAPanel, IHudPanel { - private readonly INativeGraphicsManager _nativeGraphicsManager; + public const int InventoryRows = 4; + public const int InventoryRowSlots = 14; - public InventoryPanel(INativeGraphicsManager nativeGraphicsManager) + private readonly ICharacterActions _characterActions; + private readonly IStatusLabelSetter _statusLabelSetter; + private readonly IItemStringService _itemStringService; + private readonly IItemNameColorService _itemNameColorService; + private readonly IInventoryService _inventoryService; + private readonly IInventorySlotRepository _inventorySlotRepository; + private readonly IPlayerInfoProvider _playerInfoProvider; + private readonly ICharacterProvider _characterProvider; + private readonly IPaperdollProvider _paperdollProvider; + private readonly ICharacterInventoryProvider _characterInventoryProvider; + private readonly IPubFileProvider _pubFileProvider; + + private readonly Dictionary _itemSlotMap; + private readonly List _childItems = new List(); + + private readonly IXNALabel _weightLabel; + private readonly IXNAButton _drop, _junk, _paperdoll; + //private readonly ScrollBar _scrollBar; + + private Option _cachedStats; + private HashSet _cachedInventory; + + public INativeGraphicsManager NativeGraphicsManager { get; } + + public InventoryPanel(INativeGraphicsManager nativeGraphicsManager, + IInGameDialogActions inGameDialogActions, + ICharacterActions characterActions, + IStatusLabelSetter statusLabelSetter, + IItemStringService itemStringService, + IItemNameColorService itemNameColorService, + IInventoryService inventoryService, + IInventorySlotRepository inventorySlotRepository, + IPlayerInfoProvider playerInfoProvider, + ICharacterProvider characterProvider, + IPaperdollProvider paperdollProvider, + ICharacterInventoryProvider characterInventoryProvider, + IPubFileProvider pubFileProvider) { - _nativeGraphicsManager = nativeGraphicsManager; + NativeGraphicsManager = nativeGraphicsManager; + _characterActions = characterActions; + _statusLabelSetter = statusLabelSetter; + _itemStringService = itemStringService; + _itemNameColorService = itemNameColorService; + _inventoryService = inventoryService; + _inventorySlotRepository = inventorySlotRepository; + _playerInfoProvider = playerInfoProvider; + _characterProvider = characterProvider; + _paperdollProvider = paperdollProvider; + _characterInventoryProvider = characterInventoryProvider; + _pubFileProvider = pubFileProvider; + _weightLabel = new XNALabel(Constants.FontSize08pt5) + { + DrawArea = new Rectangle(385, 37, 88, 18), + ForeColor = ColorConstants.LightGrayText, + TextAlign = LabelAlignment.MiddleCenter, + Visible = true, + AutoSize = false + }; + + _itemSlotMap = GetItemSlotMap(_playerInfoProvider.LoggedInAccountName, _characterProvider.MainCharacter.Name); + + var weirdOffsetSheet = NativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 27); - BackgroundImage = _nativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 44); + _paperdoll = new XNAButton(weirdOffsetSheet, new Vector2(385, 9), new Rectangle(39, 385, 88, 19), new Rectangle(126, 385, 88, 19)); + _paperdoll.OnMouseEnter += MouseOverButton; + _paperdoll.OnClick += (_, _) => inGameDialogActions.ShowPaperdollDialog(characterProvider.MainCharacter, isMainCharacter: true); + _drop = new XNAButton(weirdOffsetSheet, new Vector2(389, 68), new Rectangle(0, 15, 38, 37), new Rectangle(0, 52, 38, 37)); + _drop.OnMouseEnter += MouseOverButton; + _junk = new XNAButton(weirdOffsetSheet, new Vector2(431, 68), new Rectangle(0, 89, 38, 37), new Rectangle(0, 126, 38, 37)); + _junk.OnMouseEnter += MouseOverButton; + + _cachedStats = Option.None(); + _cachedInventory = new HashSet(); + + BackgroundImage = NativeGraphicsManager.TextureFromResource(GFXTypes.PostLoginUI, 44); DrawArea = new Rectangle(102, 330, BackgroundImage.Width, BackgroundImage.Height); + + Game.Exiting += SaveInventoryFile; + } + + public bool NoItemsDragging() => _childItems.All(x => !x.IsDragging); + + public override void Initialize() + { + _weightLabel.Initialize(); + _weightLabel.SetParentControl(this); + + _paperdoll.Initialize(); + _paperdoll.SetParentControl(this); + + _drop.Initialize(); + _drop.SetParentControl(this); + + _junk.Initialize(); + _junk.SetParentControl(this); + + base.Initialize(); + } + + protected override void OnUpdateControl(GameTime gameTime) + { + _cachedStats.Match( + some: stats => + { + stats.SomeWhen(s => s != _characterProvider.MainCharacter.Stats) + .MatchSome(_ => + { + var newStats = _characterProvider.MainCharacter.Stats; + _cachedStats = Option.Some(newStats); + _weightLabel.Text = $"{newStats[CharacterStat.Weight]} / {newStats[CharacterStat.MaxWeight]}"; + }); + }, + none: () => + { + var stats = _characterProvider.MainCharacter.Stats; + _cachedStats = Option.Some(stats); + _weightLabel.Text = $"{stats[CharacterStat.Weight]} / {stats[CharacterStat.MaxWeight]}"; + }); + + if (!_cachedInventory.SetEquals(_characterInventoryProvider.ItemInventory)) + { + var added = _characterInventoryProvider.ItemInventory.Where(i => !_cachedInventory.Any(j => i.ItemID == j.ItemID)); + var removed = _cachedInventory.Where(i => !_characterInventoryProvider.ItemInventory.Any(j => i.ItemID == j.ItemID)); + var updated = _characterInventoryProvider.ItemInventory.Except(added); + + foreach (var item in removed) + { + var matchedItem = _childItems.SingleOrNone(x => x.InventoryItem.ItemID == item.ItemID); + matchedItem.MatchSome(childItem => + { + childItem.SetControlUnparented(); + childItem.Dispose(); + _childItems.Remove(childItem); + + var itemData = _pubFileProvider.EIFFile[item.ItemID]; + _inventoryService.ClearSlots(_inventorySlotRepository.FilledSlots, childItem.Slot, itemData.Size); + }); + } + + foreach (var item in updated) + { + var itemData = _pubFileProvider.EIFFile[item.ItemID]; + + var matchedItem = _childItems.SingleOrNone(x => x.InventoryItem.ItemID == item.ItemID); + matchedItem.MatchSome(childItem => + { + childItem.InventoryItem = item; + childItem.Text = _itemStringService.GetStringForMapDisplay(itemData, item.Amount); + }); + } + + foreach (var item in added) + { + var itemData = _pubFileProvider.EIFFile[item.ItemID]; + + var preferredSlot = _itemSlotMap.SingleOrNone(x => x.Value == item.ItemID).Map(x => x.Key); + var actualSlot = _inventoryService.GetNextOpenSlot(_inventorySlotRepository.FilledSlots, itemData.Size, preferredSlot); + + actualSlot.MatchSome(slot => + { + _inventoryService.SetSlots(_inventorySlotRepository.FilledSlots, slot, itemData.Size); + + var newItem = new InventoryPanelItem(_itemNameColorService, this, slot, item, itemData); + newItem.Initialize(); + newItem.SetParentControl(this); + newItem.Text = _itemStringService.GetStringForMapDisplay(itemData, item.Amount); + + newItem.OnMouseEnter += (_, _) => _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ITEM, newItem.Text); + newItem.DoubleClick += HandleItemDoubleClick; + newItem.DoneDragging += HandleItemDoneDragging; + + // side-effect of calling newItem.SetParentControl(this) is that the draw order gets reset + // setting the slot manually here resets it so the item labels render appropriately + newItem.Slot = slot; + + _childItems.Add(newItem); + }); + } + + _cachedInventory = _characterInventoryProvider.ItemInventory.ToHashSet(); + } + + base.OnUpdateControl(gameTime); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + _paperdoll.OnMouseEnter -= MouseOverButton; + _drop.OnMouseEnter -= MouseOverButton; + _junk.OnMouseEnter -= MouseOverButton; + Game.Exiting -= SaveInventoryFile; + + SaveInventoryFile(null, EventArgs.Empty); + } + + base.Dispose(disposing); + } + + private void MouseOverButton(object sender, EventArgs e) + { + var id = sender == _paperdoll + ? EOResourceID.STATUS_LABEL_INVENTORY_SHOW_YOUR_PAPERDOLL + : sender == _drop + ? EOResourceID.STATUS_LABEL_INVENTORY_DROP_BUTTON + : EOResourceID.STATUS_LABEL_INVENTORY_JUNK_BUTTON; + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_BUTTON, id); + } + + private static Dictionary GetItemSlotMap(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 < InventoryRowSlots * 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}\\inventory".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 SaveInventoryFile(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.InventoryItem.ItemID}"; + + inventory.Sections[_playerInfoProvider.LoggedInAccountName] = section; + + inventory.Save(); + } + + private void HandleItemDoubleClick(object sender, EIFRecord itemData) + { + var c = _characterProvider.MainCharacter; + if (!_paperdollProvider.VisibleCharacterPaperdolls.ContainsKey(c.ID)) + return; + + var isAlternateEquipLocation = false; + + switch (itemData.Type) + { + case ItemType.Armlet: + case ItemType.Bracer: + case ItemType.Ring: + var paperdoll = _paperdollProvider.VisibleCharacterPaperdolls[c.ID].Paperdoll; + + var equipLocation = itemData.GetEquipLocation(); + if (paperdoll[equipLocation] != 0) + { + isAlternateEquipLocation = true; + if (paperdoll[equipLocation + 1] != 0) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); + return; + } + } + + goto case ItemType.Weapon; + case ItemType.Armor: + if (c.RenderProperties.Gender != itemData.Gender) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_DOES_NOT_FIT_GENDER); + return; + } + + goto case ItemType.Weapon; + case ItemType.Accessory: + case ItemType.Belt: + case ItemType.Boots: + case ItemType.Gloves: + case ItemType.Hat: + case ItemType.Necklace: + case ItemType.Shield: + case ItemType.Weapon: + var reqs = new int[6]; + var reqNames = new[] { "STR", "INT", "WIS", "AGI", "CON", "CHA" }; + if ((reqs[0] = itemData.StrReq) > c.Stats[CharacterStat.Strength] || (reqs[1] = itemData.IntReq) > c.Stats[CharacterStat.Intelligence] + || (reqs[2] = itemData.WisReq) > c.Stats[CharacterStat.Wisdom] || (reqs[3] = itemData.AgiReq) > c.Stats[CharacterStat.Agility] + || (reqs[4] = itemData.ConReq) > c.Stats[CharacterStat.Constituion] || (reqs[5] = itemData.ChaReq) > c.Stats[CharacterStat.Charisma]) + { + var req = reqs.Select((i, n) => new { Req = n, Ndx = i }).First(x => x.Req > 0); + + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, + EOResourceID.STATUS_LABEL_ITEM_EQUIP_THIS_ITEM_REQUIRES, + $" {reqs[req.Ndx]} {reqNames[req.Ndx]}"); + return; + } + + if (itemData.ClassReq > 0 && itemData.ClassReq != c.ClassID) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, + EOResourceID.STATUS_LABEL_ITEM_EQUIP_CAN_ONLY_BE_USED_BY, + _pubFileProvider.ECFFile[itemData.ClassReq].Name); + return; + } + + paperdoll = _paperdollProvider.VisibleCharacterPaperdolls[c.ID].Paperdoll; + equipLocation = itemData.GetEquipLocation(); + + if (paperdoll[equipLocation] != 0 && !isAlternateEquipLocation) + { + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); + return; + } + + _characterActions.EquipItem((short)itemData.ID, isAlternateEquipLocation); + + break; + //usable items + case ItemType.Teleport: + //if (!OldWorld.Instance.ActiveMapRenderer.MapRef.Properties.CanScroll) + //{ + // EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_ACTION, EOResourceID.STATUS_LABEL_NOTHING_HAPPENED); + // break; + //} + //if (m_itemData.ScrollMap == OldWorld.Instance.MainPlayer.ActiveCharacter.CurrentMap && + // m_itemData.ScrollX == OldWorld.Instance.MainPlayer.ActiveCharacter.X && + // m_itemData.ScrollY == OldWorld.Instance.MainPlayer.ActiveCharacter.Y) + break; //already there - no need to scroll! + //useItem = true; + break; + case ItemType.Heal: + case ItemType.HairDye: + case ItemType.Beer: + //useItem = true; + break; + case ItemType.CureCurse: + //note: don't actually set the useItem bool here. Call API.UseItem if the dialog result is OK. + //if (c.PaperDoll.Select(id => OldWorld.Instance.EIF[id]) + // .Any(rec => rec.Special == ItemSpecial.Cursed)) + //{ + // EOMessageBox.Show(DialogResourceID.ITEM_CURSE_REMOVE_PROMPT, EODialogButtons.OkCancel, EOMessageBoxStyle.SmallDialogSmallHeader, + // (o, e) => + // { + // //if (e.Result == XNADialogResult.OK && !m_api.UseItem((short)m_itemData.ID)) + // //{ + // // ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + // //} + // }); + //} + break; + case ItemType.EXPReward: + //useItem = true; + break; + case ItemType.EffectPotion: + //useItem = true; + break; + //Not implemented server-side + //case ItemType.SkillReward: + // break; + //case ItemType.StatReward: + // break; + } + + //if (useItem && !m_api.UseItem((short)m_itemData.ID)) + // ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + + } + + private void HandleItemDoneDragging(object sender, InventoryPanelItem.ItemDragCompletedEventArgs e) + { + var item = sender as InventoryPanelItem; + if (item == null) + return; + + var oldSlot = item.Slot; + var newSlot = item.GetCurrentSlotBasedOnPosition(); + + // check overlapping items: + // 1. If there's multiple items under it, snap it back to the original slot + // 2. If there's only one item under it, start dragging that item + // 3. If there's nothing under it, make sure it fits in the inventory, otherwise snap back to original slot + + var overlapped = GetOverlappingTakenSlots(newSlot, e.Data.Size, _childItems.Except(new[] { item }).Select(x => (x.Slot, x.Data.Size))) + .ToList(); + + if (overlapped.Count > 1) + { + e.RestoreOriginalSlot = true; + + if (!_inventoryService.FitsInSlot(_inventorySlotRepository.FilledSlots, oldSlot, e.Data.Size)) + e.ContinueDrag = true; + } + else if (overlapped.Count == 1) + { + _inventoryService.ClearSlots(_inventorySlotRepository.FilledSlots, oldSlot, e.Data.Size); + _inventoryService.SetSlots(_inventorySlotRepository.FilledSlots, newSlot, e.Data.Size); + + // start a chained drag on another item (see below comment) + _childItems.Single(x => x.Slot == overlapped[0]).StartDragging(); + } + else if (oldSlot != newSlot) + { + if (!_inventoryService.FitsInSlot(_inventorySlotRepository.FilledSlots, oldSlot, newSlot, e.Data.Size)) + { + // if the original slot no longer fits (because this is a chained drag), don't stop dragging this item + if (!_inventoryService.FitsInSlot(_inventorySlotRepository.FilledSlots, oldSlot, e.Data.Size)) + e.ContinueDrag = true; + else + e.RestoreOriginalSlot = true; + } + else + { + _inventoryService.ClearSlots(_inventorySlotRepository.FilledSlots, oldSlot, e.Data.Size); + _inventoryService.SetSlots(_inventorySlotRepository.FilledSlots, newSlot, e.Data.Size); + } + } + + if (e.ContinueDrag || e.RestoreOriginalSlot) + return; + + // todo: handle drag to things (dialog, map, buttons) + #region Unimplemented drag action + /* + if (((OldEOInventory)parent).IsOverDrop() || (OldWorld.Instance.ActiveMapRenderer.MouseOver + //&& ChestDialog.Instance == null && EOPaperdollDialog.Instance == null && LockerDialog.Instance == null + && BankAccountDialog.Instance == null && TradeDialog.Instance == null)) + { + Point loc = OldWorld.Instance.ActiveMapRenderer.MouseOver ? OldWorld.Instance.ActiveMapRenderer.GridCoords : + new Point(OldWorld.Instance.MainPlayer.ActiveCharacter.X, OldWorld.Instance.MainPlayer.ActiveCharacter.Y); + + //in range if maximum coordinate difference is <= 2 away + bool inRange = Math.Abs(Math.Max(OldWorld.Instance.MainPlayer.ActiveCharacter.X - loc.X, OldWorld.Instance.MainPlayer.ActiveCharacter.Y - loc.Y)) <= 2; + + if (m_itemData.Special == ItemSpecial.Lore) + { + EOMessageBox.Show(DialogResourceID.ITEM_IS_LORE_ITEM, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + } + else if (OldWorld.Instance.JailMap == OldWorld.Instance.MainPlayer.ActiveCharacter.CurrentMap) + { + EOMessageBox.Show(OldWorld.GetString(EOResourceID.JAIL_WARNING_CANNOT_DROP_ITEMS), + OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), + EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + } + else if (m_inventory.Amount > 1 && inRange) + { + ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.DropItems, + m_inventory.Amount); + dlg.DialogClosing += (sender, args) => + { + if (args.Result == XNADialogResult.OK) + { + //note: not sure of the actual limit. 10000 is arbitrary here + if (dlg.SelectedAmount > 10000 && m_inventory.ItemID == 1 && !safetyCommentHasBeenShown) + EOMessageBox.Show(DialogResourceID.DROP_MANY_GOLD_ON_GROUND, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader, + (o, e) => { safetyCommentHasBeenShown = true; }); + else if (!m_api.DropItem(m_inventory.ItemID, dlg.SelectedAmount, (byte)loc.X, (byte)loc.Y)) + ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + } + }; + } + else if (inRange) + { + if (!m_api.DropItem(m_inventory.ItemID, 1, (byte)loc.X, (byte)loc.Y)) + ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + } + else //if (!inRange) + { + EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_ITEM_DROP_OUT_OF_RANGE); + } + } + else if (((OldEOInventory)parent).IsOverJunk()) + { + if (m_inventory.Amount > 1) + { + ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.JunkItems, + m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_JUNK); + dlg.DialogClosing += (sender, args) => + { + if (args.Result == XNADialogResult.OK && !m_api.JunkItem(m_inventory.ItemID, dlg.SelectedAmount)) + ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + }; + } + else if (!m_api.JunkItem(m_inventory.ItemID, 1)) + ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + } + else if (ChestDialog.Instance != null && ChestDialog.Instance.MouseOver && ChestDialog.Instance.MouseOverPreviously) + { + if (m_itemData.Special == ItemSpecial.Lore) + { + EOMessageBox.Show(DialogResourceID.ITEM_IS_LORE_ITEM, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + } + else if (m_inventory.Amount > 1) + { + ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.DropItems, m_inventory.Amount); + dlg.DialogClosing += (sender, args) => + { + if (args.Result == XNADialogResult.OK && + !m_api.ChestAddItem(ChestDialog.Instance.CurrentChestX, ChestDialog.Instance.CurrentChestY, + m_inventory.ItemID, dlg.SelectedAmount)) + EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + }; + } + else + { + if (!m_api.ChestAddItem(ChestDialog.Instance.CurrentChestX, ChestDialog.Instance.CurrentChestY, m_inventory.ItemID, 1)) + EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + } + } + else if (EOPaperdollDialog.Instance != null && EOPaperdollDialog.Instance.MouseOver && EOPaperdollDialog.Instance.MouseOverPreviously) + { + //equipable items should be equipped + //other item types should do nothing + switch (m_itemData.Type) + { + case ItemType.Accessory: + case ItemType.Armlet: + case ItemType.Armor: + case ItemType.Belt: + case ItemType.Boots: + case ItemType.Bracer: + case ItemType.Gloves: + case ItemType.Hat: + case ItemType.Necklace: + case ItemType.Ring: + case ItemType.Shield: + case ItemType.Weapon: + _handleDoubleClick(); + break; + } + } + else if (LockerDialog.Instance != null && LockerDialog.Instance.MouseOver && LockerDialog.Instance.MouseOverPreviously) + { + byte x = LockerDialog.Instance.X; + byte y = LockerDialog.Instance.Y; + if (m_inventory.ItemID == 1) + { + EOMessageBox.Show(DialogResourceID.LOCKER_DEPOSIT_GOLD_ERROR, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + } + else if (m_inventory.Amount > 1) + { + ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.ShopTransfer, m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_TRANSFER); + dlg.DialogClosing += (sender, args) => + { + if (args.Result == XNADialogResult.OK) + { + if (LockerDialog.Instance.GetNewItemAmount(m_inventory.ItemID, dlg.SelectedAmount) > Constants.LockerMaxSingleItemAmount) + EOMessageBox.Show(DialogResourceID.LOCKER_FULL_SINGLE_ITEM_MAX, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + else if (!m_api.LockerAddItem(x, y, m_inventory.ItemID, dlg.SelectedAmount)) + EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + } + }; + } + else + { + if (LockerDialog.Instance.GetNewItemAmount(m_inventory.ItemID, 1) > Constants.LockerMaxSingleItemAmount) + EOMessageBox.Show(DialogResourceID.LOCKER_FULL_SINGLE_ITEM_MAX, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + else if (!m_api.LockerAddItem(x, y, m_inventory.ItemID, 1)) + EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + } + } + else if (BankAccountDialog.Instance != null && BankAccountDialog.Instance.MouseOver && BankAccountDialog.Instance.MouseOverPreviously && m_inventory.ItemID == 1) + { + if (m_inventory.Amount == 0) + { + EOMessageBox.Show(DialogResourceID.BANK_ACCOUNT_UNABLE_TO_DEPOSIT, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); + } + else if (m_inventory.Amount > 1) + { + ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.BankTransfer, + m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_DEPOSIT); + dlg.DialogClosing += (o, e) => + { + if (e.Result == XNADialogResult.Cancel) + return; + + if (!m_api.BankDeposit(dlg.SelectedAmount)) + EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + }; + } + else + { + if (!m_api.BankDeposit(1)) + EOGame.Instance.DoShowLostConnectionDialogAndReturnToMainMenu(); + } + } + else if (TradeDialog.Instance != null && TradeDialog.Instance.MouseOver && TradeDialog.Instance.MouseOverPreviously + && !TradeDialog.Instance.MainPlayerAgrees) + { + if (m_itemData.Special == ItemSpecial.Lore) + { + EOMessageBox.Show(DialogResourceID.ITEM_IS_LORE_ITEM); + } + else if (m_inventory.Amount > 1) + { + ItemTransferDialog dlg = new ItemTransferDialog(m_itemData.Name, ItemTransferDialog.TransferType.TradeItems, + m_inventory.Amount, EOResourceID.DIALOG_TRANSFER_OFFER); + dlg.DialogClosing += (o, e) => + { + if (e.Result != XNADialogResult.OK) return; + + if (!m_api.TradeAddItem(m_inventory.ItemID, dlg.SelectedAmount)) + { + TradeDialog.Instance.Close(XNADialogResult.NO_BUTTON_PRESSED); + ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + } + }; + } + else if (!m_api.TradeAddItem(m_inventory.ItemID, 1)) + { + TradeDialog.Instance.Close(XNADialogResult.NO_BUTTON_PRESSED); + ((EOGame)Game).DoShowLostConnectionDialogAndReturnToMainMenu(); + } + }*/ + #endregion + } + + private static IEnumerable GetOverlappingTakenSlots(int newSlot, ItemSize size, IEnumerable<(int Slot, ItemSize Size)> items) + { + var slotX = newSlot % InventoryRowSlots; + var slotY = newSlot / InventoryRowSlots; + var slotItemDim = size.GetDimensions(); + + var newSlotCoords = new List<(int X, int Y)>(); + for (int r = slotY; r < slotY + slotItemDim.Height; r++) + for (int c = slotX; c < slotX + slotItemDim.Width; c++) + newSlotCoords.Add((c, r)); + + foreach (var item in items) + { + var itemX = item.Slot % InventoryRowSlots; + var itemY = item.Slot / InventoryRowSlots; + var itemDim = item.Size.GetDimensions(); + + var @break = false; + for (int r = itemY; r < itemY + itemDim.Height; r++) + { + if (@break) + break; + + for (int c = itemX; c < itemX + itemDim.Width; c++) + { + if (newSlotCoords.Contains((c, r))) + { + yield return item.Slot; + @break = true; + break; + } + } + } + } } } } \ No newline at end of file diff --git a/EndlessClient/HUD/Panels/Old/OldInventoryPanel.cs b/EndlessClient/HUD/Panels/Old/OldInventoryPanel.cs deleted file mode 100644 index 157fef214..000000000 --- a/EndlessClient/HUD/Panels/Old/OldInventoryPanel.cs +++ /dev/null @@ -1,463 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using EndlessClient.Dialogs; -using EndlessClient.HUD.Inventory; -using EndlessClient.Old; -using EOLib; -using EOLib.Domain.Character; -using EOLib.Graphics; -using EOLib.IO; -using EOLib.IO.Pub; -using EOLib.Localization; -using EOLib.Net.API; -using Microsoft.Win32; -using Microsoft.Xna.Framework; -using Microsoft.Xna.Framework.Graphics; -using XNAControls.Old; - -namespace EndlessClient.HUD.Panels.Old -{ - public class OldEOInventory : XNAControl - { - /// - /// number of slots in an inventory row - /// - public const int INVENTORY_ROW_LENGTH = 14; - - /// - /// Area of the grid portion of the inventory (uses absolute coordinates) - /// - public static Rectangle GRID_AREA = new Rectangle(110, 334, 377, 116); - - private readonly bool[,] m_filledSlots = new bool[4, INVENTORY_ROW_LENGTH]; //4 rows, 14 columns = 56 total in grid - private readonly RegistryKey m_inventoryKey; - private readonly List m_childItems = new List(); - - private readonly XNALabel m_lblWeight; - private readonly XNAButton m_btnDrop, m_btnJunk, m_btnPaperdoll; - - private readonly PacketAPI m_api; - - public OldEOInventory(XNAPanel parent, PacketAPI api) - : base(null, null, parent) - { - m_api = api; - - //load info from registry - Dictionary localItemSlotMap = new Dictionary(); - m_inventoryKey = _tryGetCharacterRegKey(); - if (m_inventoryKey != null) - { - const string itemFmt = "item{0}"; - for (int i = 0; i < INVENTORY_ROW_LENGTH * 4; ++i) - { - int id; - try - { - id = Convert.ToInt32(m_inventoryKey.GetValue(string.Format(itemFmt, i))); - } - catch { continue; } - localItemSlotMap.Add(i, id); - } - } - - //add the inventory items that were retrieved from the server - List localInv = OldWorld.Instance.MainPlayer.ActiveCharacter.Inventory; - if (localInv.Find(_item => _item.ItemID == 1).ItemID != 1) - localInv.Insert(0, new InventoryItem(amount: 0, itemID: 1)); //add 0 gold if there isn't any gold - - bool dialogShown = false; - foreach (var item in localInv) - { - var rec = OldWorld.Instance.EIF[item.ItemID]; - int slot = localItemSlotMap.ContainsValue(item.ItemID) - ? localItemSlotMap.First(_pair => _pair.Value == item.ItemID).Key - : _getNextOpenSlot(rec.Size); - - List> points; - if (!_fitsInSlot(slot, rec.Size, out points)) - slot = _getNextOpenSlot(rec.Size); - - if (!_addItemToSlot(slot, rec, item.Amount) && !dialogShown) - { - dialogShown = true; - EOMessageBox.Show("Something doesn't fit in the inventory. Rearrange items or get rid of them.", "Warning", EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - } - } - - //coordinates for parent of EOInventory: 102, 330 (pnlInventory in InGameHud) - //extra in photoshop right now: 8, 31 - - //current weight label (member variable, needs to have text updated when item amounts change) - m_lblWeight = new XNALabel(new Rectangle(385, 37, 88, 18), Constants.FontSize08pt5) - { - ForeColor = ColorConstants.LightGrayText, - TextAlign = LabelAlignment.MiddleCenter, - Visible = true, - AutoSize = false - }; - m_lblWeight.SetParent(this); - UpdateWeightLabel(); - - Texture2D thatWeirdSheet = ((EOGame)Game).GFXManager.TextureFromResource(GFXTypes.PostLoginUI, 27); //oh my gawd the offsets on this bish - - //(local variables, added to child controls) - //'paperdoll' button - m_btnPaperdoll = new XNAButton(thatWeirdSheet, new Vector2(385, 9), /*new Rectangle(39, 385, 88, 19)*/null, new Rectangle(126, 385, 88, 19)); - m_btnPaperdoll.SetParent(this); - //m_btnPaperdoll.OnClick += (s, e) => m_api.RequestPaperdoll((short)OldWorld.Instance.MainPlayer.ActiveCharacter.ID); - //'drop' button - //491, 398 -> 389, 68 - //0,15,38,37 - //0,52,38,37 - m_btnDrop = new XNAButton(thatWeirdSheet, new Vector2(389, 68), new Rectangle(0, 15, 38, 37), new Rectangle(0, 52, 38, 37)); - m_btnDrop.SetParent(this); - OldWorld.IgnoreDialogs(m_btnDrop); - //'junk' button - 4 + 38 on the x away from drop - m_btnJunk = new XNAButton(thatWeirdSheet, new Vector2(431, 68), new Rectangle(0, 89, 38, 37), new Rectangle(0, 126, 38, 37)); - m_btnJunk.SetParent(this); - OldWorld.IgnoreDialogs(m_btnJunk); - } - - //----------------------------------------------------- - // Overrides / Control Interface - //----------------------------------------------------- - public override void Update(GameTime gameTime) - { - if (!Visible || !Game.IsActive) return; - - if (IsOverDrop()) - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_BUTTON, EOResourceID.STATUS_LABEL_INVENTORY_DROP_BUTTON); - } - else if (IsOverJunk()) - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_BUTTON, EOResourceID.STATUS_LABEL_INVENTORY_JUNK_BUTTON); - } - else if (m_btnPaperdoll.MouseOver && !m_btnPaperdoll.MouseOverPreviously) - { - EOGame.Instance.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_BUTTON, EOResourceID.STATUS_LABEL_INVENTORY_SHOW_YOUR_PAPERDOLL); - } - - base.Update(gameTime); - } - - protected override void Dispose(bool disposing) - { - m_inventoryKey.Dispose(); - m_btnPaperdoll.Dispose(); - m_btnJunk.Dispose(); - m_btnDrop.Dispose(); - } - - private bool _addItemToSlot(int slot, EIFRecord item, int count = 1) - { - return _addItemToSlot(m_filledSlots, slot, item, count); - } - - private bool _addItemToSlot(bool[,] filledSlots, int slot, EIFRecord item, int count = 1) - { - //this is ADD item - don't allow adding items that have been added already - if (slot < 0 || m_childItems.Count(_item => _item.Slot == slot) > 0) return false; - - List> points; - if (!_fitsInSlot(filledSlots, slot, item.Size, out points)) return false; - points.ForEach(point => filledSlots[point.Item1, point.Item2] = true); //flag that the spaces are taken - - m_inventoryKey.SetValue($"item{slot}", item.ID, RegistryValueKind.String); //update the registry - m_childItems.Add(new OldEOInventoryItem(m_api, slot, item, new InventoryItem(amount: count, itemID: (short)item.ID), this)); //add the control wrapper for the item - m_childItems[m_childItems.Count - 1].DrawOrder = (int) ControlDrawLayer.DialogLayer - (2 + slot%INVENTORY_ROW_LENGTH); - return true; - } - - public bool ItemFits(short id) - { - //it will fit if the inventory already has it. - if (m_childItems.Find(_i => _i.ItemData.ID == id) != null) - return true; - - var rec = OldWorld.Instance.EIF[id]; - int nextSlot = _getNextOpenSlot(rec.Size); - List> dummy; - return _fitsInSlot(nextSlot, rec.Size, out dummy); - } - - /// - /// Checks if a list of Item IDs will fit in the inventory based on their item record sizes. Does not modify current inventory. - /// - /// List of Items to check - /// Optional list of items to remove from filled slots before checking new IDs (ie items that will be traded) - /// True if everything fits, false otherwise. - public bool ItemsFit(List newItems, List oldItems = null) - { - bool[,] tempFilledSlots = new bool[4, INVENTORY_ROW_LENGTH]; - for (int row = 0; row < 4; ++row) - { - for (int col = 0; col < INVENTORY_ROW_LENGTH; ++col) - { - tempFilledSlots[row, col] = m_filledSlots[row, col]; - } - } - - if(oldItems != null) - foreach (InventoryItem item in oldItems) - { - OldEOInventoryItem control = m_childItems.Find(_item => _item.ItemData.ID == item.ItemID); - if (control != null && control.Inventory.Amount - item.Amount <= 0) - _unmarkItemSlots(tempFilledSlots, _getTakenSlots(control.Slot, control.ItemData.Size)); - } - - foreach (InventoryItem item in newItems) - { - if (oldItems != null && oldItems.FindIndex(_itm => _itm.ItemID == item.ItemID) < 0) - { - if (item.ItemID == 1 || m_childItems.Find(_item => _item.ItemData.ID == item.ItemID) != null) - continue; //already in inventory: skip, since it isn't a new item - } - - var rec = OldWorld.Instance.EIF[item.ItemID]; - - int nextSlot = _getNextOpenSlot(tempFilledSlots, rec.Size); - List> points; - if (nextSlot < 0 || !_fitsInSlot(tempFilledSlots, nextSlot, rec.Size, out points)) - return false; - - foreach (var pt in points) - tempFilledSlots[pt.Item1, pt.Item2] = true; - } - return true; - } - - private void _unmarkItemSlots(bool[,] slots, List> points) - { - points.ForEach(_p => slots[_p.Item1, _p.Item2] = false); - } - - private void _removeItemFromSlot(int slot, int count = 1) - { - OldEOInventoryItem control = m_childItems.Find(_control => _control.Slot == slot); - if (control == null || slot < 0) return; - - int numLeft = control.Inventory.Amount - count; - - if (numLeft <= 0 && control.Inventory.ItemID != 1) - { - ItemSize sz = control.ItemData.Size; - _unmarkItemSlots(m_filledSlots, _getTakenSlots(control.Slot, sz)); - - m_inventoryKey.SetValue($"item{slot}", 0, RegistryValueKind.String); - m_childItems.Remove(control); - control.Visible = false; - control.Close(); - } - else - { - control.Inventory = new InventoryItem(amount: numLeft, itemID: control.Inventory.ItemID); - control.UpdateItemLabel(); - } - } - - public bool MoveItem(OldEOInventoryItem childItem, int newSlot) - { - if (childItem.Slot == newSlot) return true; // We did it, Reddit! - - List> oldPoints = _getTakenSlots(childItem.Slot, childItem.ItemData.Size); - List> points; - if (!_fitsInSlot(newSlot, childItem.ItemData.Size, out points, oldPoints)) return false; - - oldPoints.ForEach(_p => m_filledSlots[_p.Item1, _p.Item2] = false); - points.ForEach(_p => m_filledSlots[_p.Item1, _p.Item2] = true); - - m_inventoryKey.SetValue($"item{childItem.Slot}", 0, RegistryValueKind.String); - m_inventoryKey.SetValue($"item{newSlot}", childItem.ItemData.ID, RegistryValueKind.String); - - childItem.DrawOrder = (int)ControlDrawLayer.DialogLayer - (2 + childItem.Slot % INVENTORY_ROW_LENGTH); - - return true; - } - - private int _getNextOpenSlot(ItemSize size) - { - return _getNextOpenSlot(m_filledSlots, size); - } - - private int _getNextOpenSlot(bool[,] slots, ItemSize size) - { - int width, height; - _getItemSizeDeltas(size, out width, out height); - - //outer loops: iterating over every grid space (56 spaces total) - for (int row = 0; row < 4; ++row) - { - for (int col = 0; col < INVENTORY_ROW_LENGTH; ++col) - { - if (slots[row, col]) continue; - - if (!slots[row, col] && size == ItemSize.Size1x1) - return row*INVENTORY_ROW_LENGTH + col; - - //inner loops: iterating over grid spaces starting at (row, col) for the item size (width, height) - bool ok = true; - for (int y = row; y < row + height; ++y) - { - if (y >= 4) - { - ok = false; - continue; - } - for (int x = col; x < col + width; ++x) - if (x >= INVENTORY_ROW_LENGTH || slots[y, x]) ok = false; - } - - if (ok) return row*INVENTORY_ROW_LENGTH + col; - } - } - - return -1; - } - - public void UpdateWeightLabel(string text = "") - { - if (string.IsNullOrEmpty(text)) - m_lblWeight.Text = - $"{OldWorld.Instance.MainPlayer.ActiveCharacter.Weight} / {OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight}"; - else - m_lblWeight.Text = text; - } - - public bool NoItemsDragging() - { - return m_childItems.Count(invItem => invItem.Dragging) == 0; - } - - public bool UpdateItem(InventoryItem item) - { - OldEOInventoryItem ctrl; - if((ctrl = m_childItems.Find(_ctrl => _ctrl.ItemData.ID == item.ItemID)) != null) - { - ctrl.Inventory = item; - ctrl.UpdateItemLabel(); - } - else - { - var rec = OldWorld.Instance.EIF[item.ItemID]; - return _addItemToSlot(_getNextOpenSlot(rec.Size), rec, item.Amount); - } - - return true; - } - - public void RemoveItem(int id) - { - OldEOInventoryItem ctrl; - if ((ctrl = m_childItems.Find(_ctrl => _ctrl.ItemData.ID == id)) != null) - { - _removeItemFromSlot(ctrl.Slot, ctrl.Inventory.Amount); - } - } - - public bool IsOverDrop() - { - return m_btnDrop.MouseOver && m_btnDrop.MouseOverPreviously; - } - - public bool IsOverJunk() - { - return m_btnJunk.MouseOver && m_btnJunk.MouseOverPreviously; - } - - //----------------------------------------------------- - // Helper methods - //----------------------------------------------------- - private static RegistryKey _tryGetCharacterRegKey() - { - try - { - using (RegistryKey currentUser = Registry.CurrentUser) - { - return currentUser.CreateSubKey( - $"Software\\EndlessClient\\{OldWorld.Instance.MainPlayer.AccountName}\\{OldWorld.Instance.MainPlayer.ActiveCharacter.Name}\\inventory", - RegistryKeyPermissionCheck.ReadWriteSubTree); - } - } - catch (NullReferenceException) { } - return null; - } - - private static List> _getTakenSlots(int slot, ItemSize sz) - { - var ret = new List>(); - - int width, height; - _getItemSizeDeltas(sz, out width, out height); - int y = slot / INVENTORY_ROW_LENGTH, x = slot % INVENTORY_ROW_LENGTH; - for (int row = y; row < height + y; ++row) - { - for (int col = x; col < width + x; ++col) - { - ret.Add(new Tuple(row, col)); - } - } - - return ret; - } - - private bool _fitsInSlot(int slot, ItemSize sz, out List> points, List> itemPoints = null) - { - return _fitsInSlot(m_filledSlots, slot, sz, out points, itemPoints); - } - - private bool _fitsInSlot(bool[,] slots, int slot, ItemSize sz, out List> points, List> itemPoints = null) - { - points = new List>(); - - if (slot < 0 || slot >= 4*INVENTORY_ROW_LENGTH) return false; - - //check the 'filled slots' array to see if the item can go in 'slot' based on its size - int y = slot / INVENTORY_ROW_LENGTH, x = slot % INVENTORY_ROW_LENGTH; - if (y >= 4 || x >= INVENTORY_ROW_LENGTH) return false; - if (itemPoints == null && slots[y, x]) return false; - - points = _getTakenSlots(slot, sz); - if (points.Count(_t => _t.Item1 < 0 || _t.Item1 > 3 || _t.Item2 < 0 || _t.Item2 >= INVENTORY_ROW_LENGTH) > 0) - return false; //some of the coordinates are out of bounds of the maximum inventory length - - List> overLaps = points.FindAll(_pt => slots[_pt.Item1, _pt.Item2]); - if (overLaps.Count > 0 && (itemPoints == null || overLaps.Count(itemPoints.Contains) != overLaps.Count)) - return false; //more than one overlapping point, and the points in overLaps are not contained in itemPoints - - return true; - } - - //this is public because C# doesn't have a 'friend' keyword and I need it in EOInventoryItem - public static void _getItemSizeDeltas(ItemSize size, out int width, out int height) - { - //enum ItemSize: Size[width]x[height], - // where [width] is index 4 and [height] is index 6 (string of length 7) - string sizeStr = Enum.GetName(typeof(ItemSize), size); - if (sizeStr == null || sizeStr.Length != 7) - { - width = height = 0; - return; - } - - width = Convert.ToInt32(sizeStr.Substring(4, 1)); - height = Convert.ToInt32(sizeStr.Substring(6, 1)); - } - - public void EnableEffectPotions() - { - var effectPotions = m_childItems.Where(x => x.ItemData.Type == ItemType.EffectPotion && !x.Enabled); - foreach (var potion in effectPotions) - potion.Enabled = true; - } - - public void DisableEffectPotions() - { - var effectPotions = m_childItems.Where(x => x.ItemData.Type == ItemType.EffectPotion && x.Enabled); - foreach (var potion in effectPotions) - potion.Enabled = false; - } - } -} diff --git a/EndlessClient/HUD/StatusLabelSetter.cs b/EndlessClient/HUD/StatusLabelSetter.cs index d66f81ce5..6506a5925 100644 --- a/EndlessClient/HUD/StatusLabelSetter.cs +++ b/EndlessClient/HUD/StatusLabelSetter.cs @@ -34,6 +34,12 @@ public void SetStatusLabel(EOResourceID type, string prepended, EOResourceID tex _localizedStringFinder.GetString(text)); } + public void SetStatusLabel(EOResourceID type, string text) + { + CheckStatusLabelType(type); + SetStatusLabelText(_localizedStringFinder.GetString(type), text); + } + private void SetStatusLabelText(string type, string text, string extra = "") { _statusLabelTextRepository.StatusText = $"[ {type} ] {text}{extra}"; diff --git a/EndlessClient/Old/OldCharacter.cs b/EndlessClient/Old/OldCharacter.cs index e6cd59f00..9a6128887 100644 --- a/EndlessClient/Old/OldCharacter.cs +++ b/EndlessClient/Old/OldCharacter.cs @@ -430,92 +430,6 @@ public void DoneEmote() RenderData.SetEmoteFrame(-1); } - public void UpdateInventoryItem(short id, int characterAmount, bool add = false) - { - InventoryItem rec; - if ((rec = Inventory.Find(item => item.ItemID == id)).ItemID == id) - { - InventoryItem newRec = new InventoryItem(id, add ? characterAmount + rec.Amount : characterAmount); - if (!Inventory.Remove(rec)) - throw new Exception("Unable to remove from inventory!"); - if (newRec.Amount > 0) - { - Inventory.Add(newRec); - } - if (this == OldWorld.Instance.MainPlayer.ActiveCharacter) EOGame.Instance.Hud.UpdateInventory(newRec); - } - else //if unequipping an item that isn't in the inventory yet - { - InventoryItem newRec = new InventoryItem(amount: characterAmount, itemID: id); - Inventory.Add(newRec); - if (this == OldWorld.Instance.MainPlayer.ActiveCharacter) EOGame.Instance.Hud.UpdateInventory(newRec); - } - } - - public void UpdateInventoryItem(short id, int characterAmount, byte characterWeight, byte characterMaxWeight, bool addToExistingAmount = false) - { - InventoryItem rec; - if ((rec = Inventory.Find(item => item.ItemID == id)).ItemID == id) - { - InventoryItem newRec = new InventoryItem - (amount: addToExistingAmount ? characterAmount + rec.Amount : characterAmount, - itemID: id); - if (this == OldWorld.Instance.MainPlayer.ActiveCharacter) - { - //false when AddItem fails to find a good spot - if (!EOGame.Instance.Hud.UpdateInventory(newRec)) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - return; - } - } - - //if we can hold it, update local inventory and weight stats - if (!Inventory.Remove(rec)) - throw new Exception("Unable to remove from inventory!"); - if (newRec.Amount > 0) - { - Inventory.Add(newRec); - } - Weight = characterWeight; - MaxWeight = characterMaxWeight; - if (this == OldWorld.Instance.MainPlayer.ActiveCharacter) EOGame.Instance.Hud.RefreshStats(); - } - else - { - //for item_get/chest_get packets, the item may not be in the inventory yet - InventoryItem newRec = new InventoryItem(amount: characterAmount, itemID: id); - if (newRec.Amount <= 0) return; - - Inventory.Add(newRec); - if (this == OldWorld.Instance.MainPlayer.ActiveCharacter) - { - //false when AddItem fails to find a good spot - if (!EOGame.Instance.Hud.UpdateInventory(newRec)) - { - EOMessageBox.Show(OldWorld.GetString(EOResourceID.STATUS_LABEL_ITEM_PICKUP_NO_SPACE_LEFT), - OldWorld.GetString(EOResourceID.STATUS_LABEL_TYPE_WARNING), - EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); - return; - } - } - Weight = characterWeight; - MaxWeight = characterMaxWeight; - if (this == OldWorld.Instance.MainPlayer.ActiveCharacter) EOGame.Instance.Hud.RefreshStats(); - } - } - - public void SetDisplayItemsFromRenderData(CharRenderData newRenderData) - { - EquipItem(ItemType.Boots, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Boots && x.DollGraphic == newRenderData.boots) ?? new EIFRecord()).ID, newRenderData.boots, true); - EquipItem(ItemType.Armor, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Armor && x.DollGraphic == newRenderData.armor) ?? new EIFRecord()).ID, newRenderData.armor, true); - EquipItem(ItemType.Hat, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Hat && x.DollGraphic == newRenderData.hat) ?? new EIFRecord()).ID, newRenderData.hat, true); - EquipItem(ItemType.Shield, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Shield && x.DollGraphic == newRenderData.shield) ?? new EIFRecord()).ID, newRenderData.shield, true); - EquipItem(ItemType.Weapon, (short)(OldWorld.Instance.EIF.SingleOrDefault(x => x.Type == ItemType.Weapon && x.DollGraphic == newRenderData.weapon) ?? new EIFRecord()).ID, newRenderData.weapon, true); - } - public ChestKey CanOpenChest(ChestSpawnMapEntity chest) { ChestKey permission = chest.Key; diff --git a/EndlessClient/Old/PacketAPICallbackManager.cs b/EndlessClient/Old/PacketAPICallbackManager.cs index 1ceb34037..6e861a678 100644 --- a/EndlessClient/Old/PacketAPICallbackManager.cs +++ b/EndlessClient/Old/PacketAPICallbackManager.cs @@ -115,14 +115,14 @@ private void _chestAgree(ChestData data) private void _chestAddItem(short id, int amount, byte weight, byte maxWeight, ChestData data) { - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, maxWeight); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, maxWeight); ChestDialog.Instance.InitializeItems(data.Items); m_game.Hud.RefreshStats(); } private void _chestGetItem(short id, int amount, byte weight, byte maxWeight, ChestData data) { - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, maxWeight, true); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, maxWeight, true); ChestDialog.Instance.InitializeItems(data.Items); m_game.Hud.RefreshStats(); } @@ -161,7 +161,7 @@ private void _dropItem(int characterAmount, byte weight, byte maxWeight, OldMapI OldWorld.Instance.ActiveMapRenderer.AddMapItem(item); if (characterAmount >= 0) //will be -1 when another player drops { - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(item.ItemID, characterAmount, weight, maxWeight); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(item.ItemID, characterAmount, weight, maxWeight); var rec = OldWorld.Instance.EIF[item.ItemID]; m_game.Hud.AddChat(ChatTab.System, "", @@ -174,8 +174,8 @@ private void _dropItem(int characterAmount, byte weight, byte maxWeight, OldMapI private void _itemChange(bool wasItemObtained, short id, int amount, byte weight) { - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, - OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight, wasItemObtained); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, + // OldWorld.Instance.MainPlayer.ActiveCharacter.MaxWeight, wasItemObtained); } private void _removeItemFromMap(short itemuid) @@ -213,7 +213,7 @@ private void _bankChange(int gold, int bankGold) { if (BankAccountDialog.Instance == null) return; - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, gold); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, gold); BankAccountDialog.Instance.AccountBalance = $"{bankGold}"; } @@ -225,16 +225,16 @@ private void _shopOpen(int shopid, string name, List tradeitems, List< private void _shopTrade(int gold, short itemID, int amount, byte weight, byte maxWeight, bool isBuy) { - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, gold); - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(itemID, amount, weight, maxWeight, isBuy); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, gold); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(itemID, amount, weight, maxWeight, isBuy); } private void _shopCraft(short id, byte weight, byte maxWeight, List ingredients) { - OldCharacter c = OldWorld.Instance.MainPlayer.ActiveCharacter; - c.UpdateInventoryItem(id, 1, weight, maxWeight, true); - foreach (var ingred in ingredients) - c.UpdateInventoryItem(ingred.ItemID, ingred.Amount); + //OldCharacter c = OldWorld.Instance.MainPlayer.ActiveCharacter; + //c.UpdateInventoryItem(id, 1, weight, maxWeight, true); + //foreach (var ingred in ingredients) + // c.UpdateInventoryItem(ingred.ItemID, ingred.Amount); } private void _lockerOpen(byte x, byte y, List items) @@ -247,14 +247,14 @@ private void _lockerOpen(byte x, byte y, List items) private void _lockerItemChange(short id, int amount, byte weight, byte maxWeight, bool existingAmount, List items) { if (LockerDialog.Instance == null) return; - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, maxWeight, existingAmount); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(id, amount, weight, maxWeight, existingAmount); LockerDialog.Instance.SetLockerData(items); } private void _lockerUpgrade(int remaining, byte upgrades) { if (BankAccountDialog.Instance == null) return; - OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, remaining); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, remaining); BankAccountDialog.Instance.LockerUpgrades = upgrades; m_game.Hud.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_LOCKER_SPACE_INCREASED); } @@ -390,7 +390,7 @@ 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); + //OldWorld.Instance.MainPlayer.ActiveCharacter.UpdateInventoryItem(1, remaining); m_game.Hud.AddNewSpellToActiveSpellsByID(id); } diff --git a/EndlessClient/Rendering/Factories/MouseCursorRendererFactory.cs b/EndlessClient/Rendering/Factories/MouseCursorRendererFactory.cs index eb9084944..19c444b9e 100644 --- a/EndlessClient/Rendering/Factories/MouseCursorRendererFactory.cs +++ b/EndlessClient/Rendering/Factories/MouseCursorRendererFactory.cs @@ -1,6 +1,7 @@ using AutomaticTypeMapper; using EndlessClient.Controllers; using EndlessClient.Dialogs; +using EndlessClient.HUD; using EndlessClient.Input; using EOLib.Domain.Character; using EOLib.Domain.Item; @@ -18,6 +19,7 @@ public class MouseCursorRendererFactory : IMouseCursorRendererFactory private readonly IRenderOffsetCalculator _renderOffsetCalculator; private readonly IMapCellStateProvider _mapCellStateProvider; private readonly IItemStringService _itemStringService; + private readonly IItemNameColorService _itemNameColorService; private readonly IEIFFileProvider _eifFileProvider; private readonly ICurrentMapProvider _currentMapProvider; private readonly IMapInteractionController _mapInteractionController; @@ -30,6 +32,7 @@ public MouseCursorRendererFactory(INativeGraphicsManager nativeGraphicsManager, IRenderOffsetCalculator renderOffsetCalculator, IMapCellStateProvider mapCellStateProvider, IItemStringService itemStringService, + IItemNameColorService itemNameColorService, IEIFFileProvider eifFileProvider, ICurrentMapProvider currentMapProvider, IMapInteractionController mapInteractionController, @@ -42,6 +45,7 @@ public MouseCursorRendererFactory(INativeGraphicsManager nativeGraphicsManager, _renderOffsetCalculator = renderOffsetCalculator; _mapCellStateProvider = mapCellStateProvider; _itemStringService = itemStringService; + _itemNameColorService = itemNameColorService; _eifFileProvider = eifFileProvider; _currentMapProvider = currentMapProvider; _mapInteractionController = mapInteractionController; @@ -57,6 +61,7 @@ public IMouseCursorRenderer Create() _renderOffsetCalculator, _mapCellStateProvider, _itemStringService, + _itemNameColorService, _eifFileProvider, _currentMapProvider, _mapInteractionController, diff --git a/EndlessClient/Rendering/MouseCursorRenderer.cs b/EndlessClient/Rendering/MouseCursorRenderer.cs index acd7d0434..f5bca6a43 100644 --- a/EndlessClient/Rendering/MouseCursorRenderer.cs +++ b/EndlessClient/Rendering/MouseCursorRenderer.cs @@ -1,5 +1,6 @@ using EndlessClient.Controllers; using EndlessClient.Dialogs; +using EndlessClient.HUD; using EndlessClient.Input; using EOLib; using EOLib.Domain.Character; @@ -40,6 +41,7 @@ private enum CursorIndex private readonly IRenderOffsetCalculator _renderOffsetCalculator; private readonly IMapCellStateProvider _mapCellStateProvider; private readonly IItemStringService _itemStringService; + private readonly IItemNameColorService _itemNameColorService; private readonly IEIFFileProvider _eifFileProvider; private readonly ICurrentMapProvider _currentMapProvider; private readonly IMapInteractionController _mapInteractionController; @@ -63,6 +65,7 @@ public MouseCursorRenderer(INativeGraphicsManager nativeGraphicsManager, IRenderOffsetCalculator renderOffsetCalculator, IMapCellStateProvider mapCellStateProvider, IItemStringService itemStringService, + IItemNameColorService itemNameColorService, IEIFFileProvider eifFileProvider, ICurrentMapProvider currentMapProvider, IMapInteractionController mapInteractionController, @@ -75,6 +78,7 @@ public MouseCursorRenderer(INativeGraphicsManager nativeGraphicsManager, _renderOffsetCalculator = renderOffsetCalculator; _mapCellStateProvider = mapCellStateProvider; _itemStringService = itemStringService; + _itemNameColorService = itemNameColorService; _eifFileProvider = eifFileProvider; _currentMapProvider = currentMapProvider; _mapInteractionController = mapInteractionController; @@ -219,7 +223,7 @@ private void UpdateMapItemLabel(Option item) _mapItemText.Visible = true; _mapItemText.Text = text; _mapItemText.ResizeBasedOnText(); - _mapItemText.ForeColor = GetColorForMapDisplay(data); + _mapItemText.ForeColor = _itemNameColorService.GetColorForMapDisplay(data); //relative to cursor DrawPosition, since this control is a parent of MapItemText _mapItemText.DrawPosition = new Vector2(_drawArea.X + 32 - _mapItemText.ActualWidth / 2f, @@ -280,21 +284,6 @@ private void UpdateCursorIndexForTileSpec(TileSpec tileSpec) } } - //todo: extract this into a service (also used by inventory) - private static Color GetColorForMapDisplay(EIFRecord record) - { - switch (record.Special) - { - case ItemSpecial.Lore: - case ItemSpecial.Unique: - return Color.FromNonPremultiplied(0xff, 0xf0, 0xa5, 0xff); - case ItemSpecial.Rare: - return Color.FromNonPremultiplied(0xf5, 0xc8, 0x9c, 0xff); - } - - return Color.White; - } - private async Task CheckForClicks(IMapCellState cellState) { var currentMouseState = _userInputProvider.CurrentMouseState;