From 97dfc6c7bf2a9231685747c3d616dff0ad42e7ae Mon Sep 17 00:00:00 2001 From: Ethan Moffat Date: Tue, 29 Mar 2022 14:34:29 -0700 Subject: [PATCH] Extract validator services out of InventoryController for drop/equip of items --- EOLib/Domain/Item/ItemDropResult.cs | 10 ++ EOLib/Domain/Item/ItemDropValidator.cs | 48 +++++++ EOLib/Domain/Item/ItemEquipResult.cs | 12 ++ EOLib/Domain/Item/ItemEquipValidator.cs | 85 ++++++++++++ .../Controllers/InventoryController.cs | 123 +++++------------- 5 files changed, 191 insertions(+), 87 deletions(-) create mode 100644 EOLib/Domain/Item/ItemDropResult.cs create mode 100644 EOLib/Domain/Item/ItemDropValidator.cs create mode 100644 EOLib/Domain/Item/ItemEquipResult.cs create mode 100644 EOLib/Domain/Item/ItemEquipValidator.cs diff --git a/EOLib/Domain/Item/ItemDropResult.cs b/EOLib/Domain/Item/ItemDropResult.cs new file mode 100644 index 000000000..eb32175e8 --- /dev/null +++ b/EOLib/Domain/Item/ItemDropResult.cs @@ -0,0 +1,10 @@ +namespace EOLib.Domain.Item +{ + public enum ItemDropResult + { + Ok, + Lore, + Jail, + TooFar + } +} diff --git a/EOLib/Domain/Item/ItemDropValidator.cs b/EOLib/Domain/Item/ItemDropValidator.cs new file mode 100644 index 000000000..55d8ef0a6 --- /dev/null +++ b/EOLib/Domain/Item/ItemDropValidator.cs @@ -0,0 +1,48 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.Domain.Map; +using EOLib.IO; +using EOLib.IO.Repositories; +using System; + +namespace EOLib.Domain.Item +{ + [AutoMappedType] + public class ItemDropValidator : IItemDropValidator + { + private readonly IEIFFileProvider _eifFileProvider; + private readonly ICurrentMapStateProvider _currentMapStateProvider; + + public ItemDropValidator(IEIFFileProvider eifFileProvider, + ICurrentMapStateProvider currentMapStateProvider) + { + _eifFileProvider = eifFileProvider; + _currentMapStateProvider = currentMapStateProvider; + } + + public ItemDropResult ValidateItemDrop(ICharacter mainCharacter, IInventoryItem item, MapCoordinate dropPoint) + { + if (item.ItemID <= 0) + throw new ArgumentException("Item ID is invalid", nameof(item)); + + var itemData = _eifFileProvider.EIFFile[item.ItemID]; + + if (itemData.Special == ItemSpecial.Lore) + return ItemDropResult.Lore; + + if (_currentMapStateProvider.JailMapID == _currentMapStateProvider.CurrentMapID) + return ItemDropResult.Jail; + + var rp = mainCharacter.RenderProperties; + if (Math.Max(Math.Abs(rp.MapX - dropPoint.X), Math.Abs(rp.MapY - dropPoint.Y)) > 2) + return ItemDropResult.TooFar; + + return ItemDropResult.Ok; + } + } + + public interface IItemDropValidator + { + ItemDropResult ValidateItemDrop(ICharacter mainCharacter, IInventoryItem item, MapCoordinate dropPoint); + } +} diff --git a/EOLib/Domain/Item/ItemEquipResult.cs b/EOLib/Domain/Item/ItemEquipResult.cs new file mode 100644 index 000000000..7e3d1499c --- /dev/null +++ b/EOLib/Domain/Item/ItemEquipResult.cs @@ -0,0 +1,12 @@ +namespace EOLib.Domain.Item +{ + public enum ItemEquipResult + { + Ok, + AlreadyEquipped, + WrongGender, + StatRequirementNotMet, + ClassRequirementNotMet, + NotEquippable + } +} \ No newline at end of file diff --git a/EOLib/Domain/Item/ItemEquipValidator.cs b/EOLib/Domain/Item/ItemEquipValidator.cs new file mode 100644 index 000000000..d48757d5a --- /dev/null +++ b/EOLib/Domain/Item/ItemEquipValidator.cs @@ -0,0 +1,85 @@ +using AutomaticTypeMapper; +using EOLib.Domain.Character; +using EOLib.IO; +using EOLib.IO.Extensions; +using EOLib.IO.Pub; +using EOLib.IO.Repositories; +using System.Linq; + +namespace EOLib.Domain.Item +{ + [AutoMappedType] + public class ItemEquipValidator : IItemEquipValidator + { + private readonly IECFFileProvider _ecfFileProvider; + private readonly IPaperdollProvider _paperdollProvider; + + public ItemEquipValidator(IECFFileProvider ecfFileProvider, + IPaperdollProvider paperdollProvider) + { + _ecfFileProvider = ecfFileProvider; + _paperdollProvider = paperdollProvider; + } + + public (ItemEquipResult, string, bool) ValidateItemEquip(ICharacter c, EIFRecord itemData) + { + if (!_paperdollProvider.VisibleCharacterPaperdolls.ContainsKey(c.ID)) + { + // emulate client login bug: when the paperdoll doesn't exist, show an "already equipped" message + // see: https://eoserv.net/bugs/view_bug/441 + return (ItemEquipResult.AlreadyEquipped, string.Empty, false); + } + + var paperdoll = _paperdollProvider.VisibleCharacterPaperdolls[c.ID].Paperdoll; + var equipLocation = itemData.GetEquipLocation(); + + var isAlternateEquipLocation = false; + + switch (itemData.Type) + { + case ItemType.Armlet: + case ItemType.Bracer: + case ItemType.Ring: + if (paperdoll[equipLocation] != 0) + { + isAlternateEquipLocation = true; + if (paperdoll[equipLocation + 1] != 0) + return (ItemEquipResult.AlreadyEquipped, string.Empty, false); + } + break; + case ItemType.Armor: + if (c.RenderProperties.Gender != itemData.Gender) + return (ItemEquipResult.WrongGender, string.Empty, false); + break; + } + + 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); + return (ItemEquipResult.StatRequirementNotMet, $" {reqs[req.Ndx]} {reqNames[req.Ndx]}", isAlternateEquipLocation); + } + + + if (itemData.ClassReq > 0 && itemData.ClassReq != c.ClassID) + { + return (ItemEquipResult.ClassRequirementNotMet, _ecfFileProvider.ECFFile[itemData.ClassReq].Name, isAlternateEquipLocation); + } + + if (paperdoll[equipLocation] != 0 && !isAlternateEquipLocation) + { + return (ItemEquipResult.AlreadyEquipped, string.Empty, isAlternateEquipLocation); + } + + return (ItemEquipResult.Ok, string.Empty, isAlternateEquipLocation); + } + } + + public interface IItemEquipValidator + { + (ItemEquipResult Result, string Detail, bool IsAlternateEquipLocation) ValidateItemEquip(ICharacter mainCharacter, EIFRecord itemData); + } +} diff --git a/EndlessClient/Controllers/InventoryController.cs b/EndlessClient/Controllers/InventoryController.cs index 087aaa5f2..b7e611017 100644 --- a/EndlessClient/Controllers/InventoryController.cs +++ b/EndlessClient/Controllers/InventoryController.cs @@ -25,11 +25,11 @@ public class InventoryController : IInventoryController { private readonly IItemActions _itemActions; private readonly IInGameDialogActions _inGameDialogActions; + private readonly IItemEquipValidator _itemEquipValidator; + private readonly IItemDropValidator _itemDropValidator; private readonly ICharacterProvider _characterProvider; private readonly IPaperdollProvider _paperdollProvider; - private readonly IPubFileProvider _pubFileProvider; private readonly IHudControlProvider _hudControlProvider; - private readonly ICurrentMapStateProvider _currentMapStateProvider; private readonly ICurrentMapProvider _currentMapProvider; private readonly IEIFFileProvider _eifFileProvider; private readonly IActiveDialogProvider _activeDialogProvider; @@ -39,11 +39,11 @@ public class InventoryController : IInventoryController public InventoryController(IItemActions itemActions, IInGameDialogActions inGameDialogActions, + IItemEquipValidator itemEquipValidator, + IItemDropValidator itemDropValidator, ICharacterProvider characterProvider, IPaperdollProvider paperdollProvider, - IPubFileProvider pubFileProvider, IHudControlProvider hudControlProvider, - ICurrentMapStateProvider currentMapStateProvider, ICurrentMapProvider currentMapProvider, IEIFFileProvider eifFileProvider, IActiveDialogProvider activeDialogProvider, @@ -53,11 +53,11 @@ public class InventoryController : IInventoryController { _itemActions = itemActions; _inGameDialogActions = inGameDialogActions; + _itemEquipValidator = itemEquipValidator; + _itemDropValidator = itemDropValidator; _characterProvider = characterProvider; _paperdollProvider = paperdollProvider; - _pubFileProvider = pubFileProvider; _hudControlProvider = hudControlProvider; - _currentMapStateProvider = currentMapStateProvider; _currentMapProvider = currentMapProvider; _eifFileProvider = eifFileProvider; _activeDialogProvider = activeDialogProvider; @@ -93,11 +93,6 @@ public void UseItem(EIFRecord record) useItem = true; break; - case ItemType.Heal: - case ItemType.HairDye: - case ItemType.Beer: - useItem = true; - break; case ItemType.CureCurse: var paperdollItems = _paperdollProvider.VisibleCharacterPaperdolls[_characterProvider.MainCharacter.ID].Paperdoll.Values; @@ -114,10 +109,15 @@ public void UseItem(EIFRecord record) msgBox.ShowDialog(); } break; + + case ItemType.Heal: + case ItemType.HairDye: + case ItemType.Beer: case ItemType.EffectPotion: case ItemType.EXPReward: // todo: EXPReward has not been tested useItem = true; break; + // Not implemented server - side case ItemType.SkillReward: case ItemType.StatReward: @@ -132,81 +132,31 @@ public void UseItem(EIFRecord record) public void EquipItem(EIFRecord itemData) { - if (itemData.Type < ItemType.Weapon || itemData.Type > ItemType.Bracer) - throw new ArgumentException("Item is not equippable", nameof(itemData)); - - // todo: move validation logic to validator class var c = _characterProvider.MainCharacter; - if (!_paperdollProvider.VisibleCharacterPaperdolls.ContainsKey(c.ID)) - { - // emulate client login bug: when the paperdoll doesn't exist, show an "already equipped" message - // see: https://eoserv.net/bugs/view_bug/441 - _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); - return; - } + var (validationResult, detail, isAlternateEquipLocation) = _itemEquipValidator.ValidateItemEquip(c, itemData); - var isAlternateEquipLocation = false; - - var paperdoll = _paperdollProvider.VisibleCharacterPaperdolls[c.ID].Paperdoll; - var equipLocation = itemData.GetEquipLocation(); - - switch (itemData.Type) + switch (validationResult) { - case ItemType.Armlet: - case ItemType.Bracer: - case ItemType.Ring: - { - 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; - } - } - } + case ItemEquipResult.NotEquippable: + throw new ArgumentException("Item is not equippable", nameof(itemData)); + case ItemEquipResult.AlreadyEquipped: + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); break; - 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; - } - } + case ItemEquipResult.WrongGender: + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_DOES_NOT_FIT_GENDER); + break; + case ItemEquipResult.StatRequirementNotMet: + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, + EOResourceID.STATUS_LABEL_ITEM_EQUIP_THIS_ITEM_REQUIRES, detail); + break; + case ItemEquipResult.ClassRequirementNotMet: + _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, + EOResourceID.STATUS_LABEL_ITEM_EQUIP_CAN_ONLY_BE_USED_BY, detail); + break; + case ItemEquipResult.Ok: + _itemActions.EquipItem((short)itemData.ID, isAlternateEquipLocation); break; } - - 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; - } - - if (paperdoll[equipLocation] != 0 && !isAlternateEquipLocation) - { - _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_INFORMATION, EOResourceID.STATUS_LABEL_ITEM_EQUIP_TYPE_ALREADY_EQUIPPED); - return; - } - - _itemActions.EquipItem((short)itemData.ID, isAlternateEquipLocation); } public void UnequipItem(EquipLocation equipLocation) @@ -219,7 +169,7 @@ public void UnequipItem(EquipLocation equipLocation) public void DropItem(EIFRecord itemData, IInventoryItem inventoryItem) { var mapRenderer = _hudControlProvider.GetComponent(HudControlIdentifier.MapRenderer); - if (_activeDialogProvider.ActiveDialogs.Any() && mapRenderer.MouseOver) + if (_activeDialogProvider.ActiveDialogs.Any(x => x.HasValue) && mapRenderer.MouseOver) return; var rp = _characterProvider.MainCharacter.RenderProperties; @@ -227,15 +177,14 @@ public void DropItem(EIFRecord itemData, IInventoryItem inventoryItem) ? mapRenderer.GridCoordinates : new MapCoordinate(rp.MapX, rp.MapY); - var inRange = Math.Max(Math.Abs(rp.MapX - dropPoint.X), Math.Abs(rp.MapY - dropPoint.Y)) <= 2; + var validationResult = _itemDropValidator.ValidateItemDrop(_characterProvider.MainCharacter, inventoryItem, dropPoint); - // todo: move validation logic to validator class - if (itemData.Special == ItemSpecial.Lore) + if (validationResult == ItemDropResult.Lore) { var msgBox = _eoMessageBoxFactory.CreateMessageBox(DialogResourceID.ITEM_IS_LORE_ITEM, EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); msgBox.ShowDialog(); } - else if (_currentMapStateProvider.JailMapID == _currentMapStateProvider.CurrentMapID) + else if (validationResult == ItemDropResult.Jail) { var msgBox = _eoMessageBoxFactory.CreateMessageBox( EOResourceID.JAIL_WARNING_CANNOT_DROP_ITEMS, @@ -243,7 +192,7 @@ public void DropItem(EIFRecord itemData, IInventoryItem inventoryItem) EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader); msgBox.ShowDialog(); } - else if (inRange) + else if (validationResult == ItemDropResult.Ok) { if (inventoryItem.Amount > 1) { @@ -279,7 +228,7 @@ public void DropItem(EIFRecord itemData, IInventoryItem inventoryItem) _itemActions.DropItem(inventoryItem.ItemID, 1, dropPoint); } } - else + else if (validationResult == ItemDropResult.TooFar) { _statusLabelSetter.SetStatusLabel(EOResourceID.STATUS_LABEL_TYPE_WARNING, EOResourceID.STATUS_LABEL_ITEM_DROP_OUT_OF_RANGE); }