Skip to content

Commit

Permalink
Implement skill learning and packet handling for skill learn error
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanmoffat committed Apr 20, 2022
1 parent e29ec80 commit fbe9366
Show file tree
Hide file tree
Showing 10 changed files with 308 additions and 239 deletions.
5 changes: 5 additions & 0 deletions EOLib/Domain/Interact/INPCInteractionNotifier.cs
Original file line number Diff line number Diff line change
@@ -1,16 +1,21 @@
using AutomaticTypeMapper;
using EOLib.Domain.Interact.Skill;
using EOLib.IO;

namespace EOLib.Domain.Interact
{
public interface INPCInteractionNotifier
{
void NotifyInteractionFromNPC(NPCType npcType);

void NotifySkillLearnFail(SkillmasterReply skillmasterReply, short classId);
}

[AutoMappedType]
public class NoOpNPCInteractionNotifier : INPCInteractionNotifier
{
public void NotifyInteractionFromNPC(NPCType npcType) { }

public void NotifySkillLearnFail(SkillmasterReply skillmasterReply, short classId) { }
}
}
35 changes: 35 additions & 0 deletions EOLib/Domain/Interact/Skill/SkillmasterActions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
using AutomaticTypeMapper;
using EOLib.Net;
using EOLib.Net.Communication;

namespace EOLib.Domain.Interact.Skill
{
[AutoMappedType]
public class SkillmasterActions : ISkillmasterActions
{
private readonly IPacketSendService _packetSendService;
private readonly ISkillDataProvider _skillDataProvider;

public SkillmasterActions(IPacketSendService packetSendService,
ISkillDataProvider skillDataProvider)
{
_packetSendService = packetSendService;
_skillDataProvider = skillDataProvider;
}

public void LearnSkill(short spellId)
{
var packet = new PacketBuilder(PacketFamily.StatSkill, PacketAction.Take)
.AddInt(_skillDataProvider.ID)
.AddShort(spellId)
.Build();

_packetSendService.SendPacket(packet);
}
}

public interface ISkillmasterActions
{
void LearnSkill(short spellId);
}
}
81 changes: 0 additions & 81 deletions EOLib/Net/API/StatSkill.cs
Original file line number Diff line number Diff line change
Expand Up @@ -5,61 +5,6 @@

namespace EOLib.Net.API
{
public enum SkillMasterReply
{
// ReSharper disable once UnusedMember.Global
ErrorRemoveItems = 1,
ErrorWrongClass = 2
}

public struct Skill
{
private readonly short m_id;
private readonly byte m_levelReq;
private readonly byte m_classReq;
private readonly int m_goldCost;
private readonly short[] m_skillReq; //ids of other skills that are required to know before learning this skill
private readonly short m_strReq;
private readonly short m_intReq;
private readonly short m_wisReq;
private readonly short m_agiReq;
private readonly short m_conReq;
private readonly short m_chaReq;

public short ID => m_id;
public byte LevelReq => m_levelReq;
public byte ClassReq => m_classReq;
public int GoldReq => m_goldCost;
public short[] SkillReq => m_skillReq;
public short StrReq => m_strReq;
public short IntReq => m_intReq;
public short WisReq => m_wisReq;
public short AgiReq => m_agiReq;
public short ConReq => m_conReq;
public short ChaReq => m_chaReq;

internal Skill(OldPacket pkt)
{
m_id = pkt.GetShort();
m_levelReq = pkt.GetChar();
m_classReq = pkt.GetChar();
m_goldCost = pkt.GetInt();
m_skillReq = new[]
{
pkt.GetShort(),
pkt.GetShort(),
pkt.GetShort(),
pkt.GetShort()
};
m_strReq = pkt.GetShort();
m_intReq = pkt.GetShort();
m_wisReq = pkt.GetShort();
m_agiReq = pkt.GetShort();
m_conReq = pkt.GetShort();
m_chaReq = pkt.GetShort();
}
}

public struct StatResetData
{
private readonly short m_statpts, m_skillpts, m_hp, m_maxhp, m_tp, m_maxtp, m_maxsp;
Expand Down Expand Up @@ -112,34 +57,19 @@ internal StatResetData(OldPacket pkt)
}
}

public delegate void SpellLearnErrorEvent(SkillMasterReply reply, short classID);
public delegate void SpellForgetEvent(short spellID);

partial class PacketAPI
{
public event SpellLearnErrorEvent OnSpellLearnError;
public event SpellForgetEvent OnSpellForget;
public event Action<StatResetData> OnCharacterStatsReset;

private void _createStatSkillMembers()
{
m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Reply), _handleStatSkillReply, true);
m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Remove), _handleStatSkillRemove, true);
m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.StatSkill, PacketAction.Junk), _handleStatSkillJunk, true);
}

public bool LearnSpell(short spellID)
{
if (!m_client.ConnectedAndInitialized || !Initialized)
return false;

OldPacket pkt = new OldPacket(PacketFamily.StatSkill, PacketAction.Take);
pkt.AddInt(1234); //shop ID, ignored by eoserv - eomain may require this to be correct
pkt.AddShort(spellID);

return m_client.SendPacket(pkt);
}

public bool ForgetSpell(short spellID)
{
if (!m_client.ConnectedAndInitialized || !Initialized)
Expand All @@ -159,17 +89,6 @@ public bool ResetCharacterStatSkill()
return !m_client.ConnectedAndInitialized || !Initialized || m_client.SendPacket(pkt);
}

//handlers

//error learning a skill
private void _handleStatSkillReply(OldPacket pkt)
{
//short - should always be SKILLMASTER_REMOVE_ITEMS (1) or SKILLMASTER_WRONG_CLASS (2)
//short - character class
if (OnSpellLearnError != null)
OnSpellLearnError((SkillMasterReply)pkt.GetShort(), pkt.GetShort());
}

//forgetting a skill
private void _handleStatSkillRemove(OldPacket pkt)
{
Expand Down
41 changes: 41 additions & 0 deletions EOLib/PacketHandlers/Skill/StatskillReplyHandler.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
using AutomaticTypeMapper;
using EOLib.Domain.Interact;
using EOLib.Domain.Interact.Skill;
using EOLib.Domain.Login;
using EOLib.Net;
using EOLib.Net.Handlers;
using System.Collections.Generic;

namespace EOLib.PacketHandlers.Skill
{
/// <summary>
/// Sent when failing to learn a skill from skillmaster
/// </summary>
[AutoMappedType]
public class StatskillReplyHandler : InGameOnlyPacketHandler
{
private readonly IEnumerable<INPCInteractionNotifier> _npcInteractionNotifiers;

public override PacketFamily Family => PacketFamily.StatSkill;

public override PacketAction Action => PacketAction.Reply;

public StatskillReplyHandler(IPlayerInfoProvider playerInfoProvider,
IEnumerable<INPCInteractionNotifier> npcInteractionNotifiers)
: base(playerInfoProvider)
{
_npcInteractionNotifiers = npcInteractionNotifiers;
}

public override bool HandlePacket(IPacket packet)
{
var skillmasterReply = (SkillmasterReply)packet.ReadShort();
var classId = packet.ReadShort();

foreach (var notifier in _npcInteractionNotifiers)
notifier.NotifySkillLearnFail(skillmasterReply, classId);

return true;
}
}
}
18 changes: 1 addition & 17 deletions EndlessClient/Dialogs/Actions/InGameDialogActions.cs
Original file line number Diff line number Diff line change
@@ -1,17 +1,15 @@
using AutomaticTypeMapper;
using EndlessClient.Dialogs.Factories;
using EOLib.Domain.Character;
using EOLib.Domain.Interact;
using EOLib.Domain.Interact.Quest;
using EOLib.Domain.Interact.Shop;
using EOLib.Domain.Interact.Skill;
using EOLib.IO;
using Optional;

namespace EndlessClient.Dialogs.Actions
{
[AutoMappedType]
public class InGameDialogActions : IInGameDialogActions, INPCInteractionNotifier
public class InGameDialogActions : IInGameDialogActions
{
private readonly IFriendIgnoreListDialogFactory _friendIgnoreListDialogFactory;
private readonly IPaperdollDialogFactory _paperdollDialogFactory;
Expand Down Expand Up @@ -119,20 +117,6 @@ public void ShowPaperdollDialog(ICharacter character, bool isMainCharacter)
});
}

public void NotifyInteractionFromNPC(NPCType npcType)
{
// originally, these methods were called directly from NPCInteractionController
// however, this resulted in empty responses (e.g. no shop or quest) showing an empty dialog
// instead, wait for the response packet to notify this class and then show the dialog
// once data has been received from the server
switch (npcType)
{
case NPCType.Shop: ShowShopDialog(); break;
case NPCType.Quest: ShowQuestDialog(); break;
case NPCType.Skills: ShowSkillmasterDialog(); break;
}
}

public void ShowShopDialog()
{
_activeDialogRepository.ShopDialog.MatchNone(() =>
Expand Down
61 changes: 61 additions & 0 deletions EndlessClient/Dialogs/Actions/NpcInteractionActions.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
using AutomaticTypeMapper;
using EndlessClient.Dialogs.Factories;
using EOLib.Domain.Interact;
using EOLib.Domain.Interact.Skill;
using EOLib.IO;
using EOLib.IO.Repositories;
using EOLib.Localization;

namespace EndlessClient.Dialogs.Actions
{
[AutoMappedType]
public class NPCInteractionActions : INPCInteractionNotifier
{
private readonly IInGameDialogActions _inGameDialogActions;
private readonly IEOMessageBoxFactory _messageBoxFactory;
private readonly IECFFileProvider _ecfFileProvider;

public NPCInteractionActions(IInGameDialogActions inGameDialogActions,
IEOMessageBoxFactory messageBoxFactory,
IECFFileProvider ecfFileProvider)
{
_inGameDialogActions = inGameDialogActions;
_messageBoxFactory = messageBoxFactory;
_ecfFileProvider = ecfFileProvider;
}

public void NotifyInteractionFromNPC(NPCType npcType)
{
// originally, these methods were called directly from NPCInteractionController
// however, this resulted in empty responses (e.g. no shop or quest) showing an empty dialog
// instead, wait for the response packet to notify this class and then show the dialog
// once data has been received from the server
switch (npcType)
{
case NPCType.Shop: _inGameDialogActions.ShowShopDialog(); break;
case NPCType.Quest: _inGameDialogActions.ShowQuestDialog(); break;
case NPCType.Skills: _inGameDialogActions.ShowSkillmasterDialog(); break;
}
}

public void NotifySkillLearnFail(SkillmasterReply skillmasterReply, short classId)
{
switch (skillmasterReply)
{
//not sure if this will ever actually be sent because client validates data before trying to learn a skill
case SkillmasterReply.ErrorWrongClass:
{
var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.SKILL_LEARN_WRONG_CLASS, $" {_ecfFileProvider.ECFFile[classId].Name}!");
dlg.ShowDialog();
}
break;
case SkillmasterReply.ErrorRemoveItems:
{
var dlg = _messageBoxFactory.CreateMessageBox(DialogResourceID.SKILL_RESET_CHARACTER_CLEAR_PAPERDOLL);
dlg.ShowDialog();
}
break;
}
}
}
}
22 changes: 20 additions & 2 deletions EndlessClient/Dialogs/Factories/SkillmasterDialogFactory.cs
Original file line number Diff line number Diff line change
@@ -1,8 +1,10 @@
using AutomaticTypeMapper;
using EndlessClient.Dialogs.Services;
using EndlessClient.HUD;
using EOLib.Domain.Character;
using EOLib.Domain.Interact.Skill;
using EOLib.Graphics;
using EOLib.IO.Repositories;
using EOLib.Localization;

namespace EndlessClient.Dialogs.Factories
Expand All @@ -11,39 +13,55 @@ namespace EndlessClient.Dialogs.Factories
public class SkillmasterDialogFactory : ISkillmasterDialogFactory
{
private readonly INativeGraphicsManager _nativeGraphicsManager;
private readonly ISkillmasterActions _skillmasterActions;
private readonly IEODialogButtonService _dialogButtonService;
private readonly IEODialogIconService _dialogIconService;
private readonly ILocalizedStringFinder _localizedStringFinder;
private readonly IStatusLabelSetter _statusLabelSetter;
private readonly IEOMessageBoxFactory _messageBoxFactory;
private readonly ISkillDataProvider _skillDataProvider;
private readonly ICharacterProvider _characterProvider;
private readonly ICharacterInventoryProvider _characterInventoryProvider;
private readonly IPubFileProvider _pubFileProvider;

public SkillmasterDialogFactory(INativeGraphicsManager nativeGraphicsManager,
ISkillmasterActions skillmasterActions,
IEODialogButtonService dialogButtonService,
IEODialogIconService dialogIconService,
ILocalizedStringFinder localizedStringFinder,
IStatusLabelSetter statusLabelSetter,
IEOMessageBoxFactory messageBoxFactory,
ISkillDataProvider skillDataProvider,
ICharacterInventoryProvider characterInventoryProvider)
ICharacterProvider characterProvider,
ICharacterInventoryProvider characterInventoryProvider,
IPubFileProvider pubFileProvider)
{
_nativeGraphicsManager = nativeGraphicsManager;
_skillmasterActions = skillmasterActions;
_dialogButtonService = dialogButtonService;
_dialogIconService = dialogIconService;
_localizedStringFinder = localizedStringFinder;
_statusLabelSetter = statusLabelSetter;
_messageBoxFactory = messageBoxFactory;
_skillDataProvider = skillDataProvider;
_characterProvider = characterProvider;
_characterInventoryProvider = characterInventoryProvider;
_pubFileProvider = pubFileProvider;
}

public SkillmasterDialog Create()
{
return new SkillmasterDialog(_nativeGraphicsManager,
_skillmasterActions,
_dialogButtonService,
_dialogIconService,
_localizedStringFinder,
_statusLabelSetter,
_messageBoxFactory,
_skillDataProvider,
_characterInventoryProvider);
_characterProvider,
_characterInventoryProvider,
_pubFileProvider);
}
}

Expand Down
2 changes: 1 addition & 1 deletion EndlessClient/Dialogs/ListDialogItem.cs
Original file line number Diff line number Diff line change
Expand Up @@ -191,12 +191,12 @@ public void SetSubtextClickAction(EventHandler onClickAction)
Text = oldText.Text,
Underline = true
};
_subText.ResizeBasedOnText();

((XNAHyperLink)_subText).OnClick += onClickAction;

_subText.SetParentControl(this);
_subText.Initialize();
_subText.ResizeBasedOnText();

oldText.Dispose();
}
Expand Down
Loading

0 comments on commit fbe9366

Please sign in to comment.