Skip to content

Commit

Permalink
Extract validator services out of InventoryController for drop/equip …
Browse files Browse the repository at this point in the history
…of items
  • Loading branch information
ethanmoffat committed Mar 29, 2022
1 parent 83727d7 commit 97dfc6c
Show file tree
Hide file tree
Showing 5 changed files with 191 additions and 87 deletions.
10 changes: 10 additions & 0 deletions EOLib/Domain/Item/ItemDropResult.cs
@@ -0,0 +1,10 @@
namespace EOLib.Domain.Item
{
public enum ItemDropResult
{
Ok,
Lore,
Jail,
TooFar
}
}
48 changes: 48 additions & 0 deletions 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);
}
}
12 changes: 12 additions & 0 deletions EOLib/Domain/Item/ItemEquipResult.cs
@@ -0,0 +1,12 @@
namespace EOLib.Domain.Item
{
public enum ItemEquipResult
{
Ok,
AlreadyEquipped,
WrongGender,
StatRequirementNotMet,
ClassRequirementNotMet,
NotEquippable
}
}
85 changes: 85 additions & 0 deletions 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);
}
}
123 changes: 36 additions & 87 deletions EndlessClient/Controllers/InventoryController.cs
Expand Up @@ -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;
Expand All @@ -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,
Expand All @@ -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;
Expand Down Expand Up @@ -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;
Expand All @@ -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:
Expand All @@ -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)
Expand All @@ -219,31 +169,30 @@ public void UnequipItem(EquipLocation equipLocation)
public void DropItem(EIFRecord itemData, IInventoryItem inventoryItem)
{
var mapRenderer = _hudControlProvider.GetComponent<IMapRenderer>(HudControlIdentifier.MapRenderer);
if (_activeDialogProvider.ActiveDialogs.Any() && mapRenderer.MouseOver)
if (_activeDialogProvider.ActiveDialogs.Any(x => x.HasValue) && mapRenderer.MouseOver)
return;

var rp = _characterProvider.MainCharacter.RenderProperties;
var dropPoint = mapRenderer.MouseOver
? 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,
EOResourceID.STATUS_LABEL_TYPE_WARNING,
EODialogButtons.Ok, EOMessageBoxStyle.SmallDialogSmallHeader);
msgBox.ShowDialog();
}
else if (inRange)
else if (validationResult == ItemDropResult.Ok)
{
if (inventoryItem.Amount > 1)
{
Expand Down Expand Up @@ -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);
}
Expand Down

0 comments on commit 97dfc6c

Please sign in to comment.