Skip to content

Commit

Permalink
Implement network actions for opening quest dialog and responding
Browse files Browse the repository at this point in the history
  • Loading branch information
ethanmoffat committed Apr 5, 2022
1 parent ca9611f commit 6397611
Show file tree
Hide file tree
Showing 10 changed files with 379 additions and 110 deletions.
12 changes: 12 additions & 0 deletions EOLib/Domain/Interact/MapNPCActions.cs
Expand Up @@ -22,10 +22,22 @@ public void RequestShop(byte index)

_packetSendService.SendPacket(packet);
}

public void RequestQuest(short index, short vendorId)
{
var packet = new PacketBuilder(PacketFamily.Quest, PacketAction.Use)
.AddShort(index)
.AddShort(vendorId)
.Build();

_packetSendService.SendPacket(packet);
}
}

public interface IMapNPCActions
{
void RequestShop(byte index);

void RequestQuest(short index, short vendorId);
}
}
8 changes: 8 additions & 0 deletions EOLib/Domain/Interact/Quest/DialogReply.cs
@@ -0,0 +1,8 @@
namespace EOLib.Domain.Interact.Quest
{
public enum DialogReply
{
Ok = 1,
Link
}
}
44 changes: 44 additions & 0 deletions EOLib/Domain/Interact/Quest/QuestActions.cs
@@ -0,0 +1,44 @@
using AutomaticTypeMapper;
using EOLib.Net;
using EOLib.Net.Communication;

namespace EOLib.Domain.Interact.Quest
{
[AutoMappedType]
public class QuestActions : IQuestActions
{
private readonly IPacketSendService _packetSendService;
private readonly IQuestDataProvider _questDataProvider;

public QuestActions(IPacketSendService packetSendService,
IQuestDataProvider questDataProvider)
{
_packetSendService = packetSendService;
_questDataProvider = questDataProvider;
}

public void RespondToQuestDialog(DialogReply reply, byte linkId = 0)
{
_questDataProvider.QuestDialogData.MatchSome(data =>
{
var builder = new PacketBuilder(PacketFamily.Quest, PacketAction.Accept)
.AddShort(data.SessionID) // ignored by eoserv
.AddShort(data.DialogID) // ignored by eoserv
.AddShort(data.QuestID)
.AddShort(data.VendorID) // ignored by eoserv
.AddChar((byte)reply);
if (reply == DialogReply.Link)
builder = builder.AddChar(linkId);
var packet = builder.Build();
_packetSendService.SendPacket(packet);
});
}
}

public interface IQuestActions
{
void RespondToQuestDialog(DialogReply reply, byte linkId);
}
}
31 changes: 31 additions & 0 deletions EOLib/Domain/Interact/Quest/QuestDataRepository.cs
@@ -0,0 +1,31 @@
using AutomaticTypeMapper;
using Optional;

namespace EOLib.Domain.Interact.Quest
{
public interface IQuestDataRepository : IResettable
{
Option<IQuestDialogData> QuestDialogData { get; set; }
}

public interface IQuestDataProvider : IResettable
{
Option<IQuestDialogData> QuestDialogData { get; }
}

[AutoMappedType(IsSingleton = true)]
public class QuestDataRepository : IQuestDataProvider, IQuestDataRepository
{
public Option<IQuestDialogData> QuestDialogData { get; set; }

public QuestDataRepository()
{
ResetState();
}

public void ResetState()
{
QuestDialogData = Option.None<IQuestDialogData>();
}
}
}
186 changes: 186 additions & 0 deletions EOLib/Domain/Interact/Quest/QuestDialogData.cs
@@ -0,0 +1,186 @@
using System.Collections.Generic;
using System.Linq;

namespace EOLib.Domain.Interact.Quest
{
public class QuestDialogData : IQuestDialogData
{
public short VendorID { get; private set; }

public short QuestID { get; private set; }

public short SessionID { get; private set; }

public short DialogID { get; private set; }

public IReadOnlyDictionary<short, string> DialogTitles { get; private set; }

public IReadOnlyList<string> PageText { get; private set; }

public IReadOnlyList<(short ActionID, string DisplayText)> Actions { get; private set; }

public QuestDialogData()
{
DialogTitles = new Dictionary<short, string>();
PageText = new List<string>();
Actions = new List<(short, string)>();
}

private QuestDialogData(short vendorID,
short questID,
short sessionID,
short dialogID,
IReadOnlyDictionary<short, string> dialogTitles,
IReadOnlyList<string> pageText,
IReadOnlyList<(short, string)> actions)
{
VendorID = vendorID;
QuestID = questID;
SessionID = sessionID;
DialogID = dialogID;
DialogTitles = dialogTitles;
PageText = pageText;
Actions = actions;
}

public IQuestDialogData WithVendorID(short vendorId)
{
var copy = MakeCopy(this);
copy.VendorID = vendorId;
return copy;
}

public IQuestDialogData WithQuestID(short questId)
{
var copy = MakeCopy(this);
copy.QuestID = questId;
return copy;
}

public IQuestDialogData WithSessionID(short sessionId)
{
var copy = MakeCopy(this);
copy.SessionID = sessionId;
return copy;
}

public IQuestDialogData WithDialogID(short dialogId)
{
var copy = MakeCopy(this);
copy.DialogID = dialogId;
return copy;
}

public IQuestDialogData WithDialogTitles(IReadOnlyDictionary<short, string> dialogTitles)
{
var copy = MakeCopy(this);
copy.DialogTitles = dialogTitles;
return copy;
}

public IQuestDialogData WithPageText(IReadOnlyList<string> pageText)
{
var copy = MakeCopy(this);
copy.PageText = pageText;
return copy;
}

public IQuestDialogData WithActions(IReadOnlyList<(short, string)> actions)
{
var copy = MakeCopy(this);
copy.Actions = actions;
return copy;
}

private static QuestDialogData MakeCopy(IQuestDialogData other)
{
return new QuestDialogData(
other.VendorID,
other.QuestID,
other.SessionID,
other.DialogID,
other.DialogTitles.ToDictionary(k => k.Key, v => v.Value),
new List<string>(other.PageText),
new List<(short, string)>(other.Actions));
}

public override bool Equals(object obj)
{
var other = obj as QuestDialogData;
if (other == null) return false;

return other.VendorID == VendorID
&& other.QuestID == QuestID
&& other.SessionID == SessionID
&& other.DialogID == DialogID
&& other.DialogTitles.SequenceEqual(DialogTitles)
&& other.PageText.SequenceEqual(PageText)
&& other.Actions.SequenceEqual(Actions);
}

public override int GetHashCode()
{
int hashCode = 170256730;
hashCode = hashCode * -1521134295 + VendorID.GetHashCode();
hashCode = hashCode * -1521134295 + QuestID.GetHashCode();
hashCode = hashCode * -1521134295 + SessionID.GetHashCode();
hashCode = hashCode * -1521134295 + DialogID.GetHashCode();
hashCode = hashCode * -1521134295 + DialogTitles.Aggregate(170256730, (a, b) => a * -1521134295 + b.GetHashCode());
hashCode = hashCode * -1521134295 + PageText.Aggregate(170256730, (a, b) => a * -1521134295 + b.GetHashCode());
hashCode = hashCode * -1521134295 + Actions.Aggregate(170256730, (a, b) => a * -1521134295 + b.GetHashCode());
return hashCode;
}
}

public interface IQuestDialogData
{
/// <summary>
/// NPC Vendor ID (<see cref="EOLib.IO.Pub.ENFRecord.VendorID" />)
/// </summary>
short VendorID { get; }

/// <summary>
/// Quest ID, for the current dialog being shown (part of quest state in EO+ parser)
/// </summary>
short QuestID { get; }

/// <summary>
/// Session ID, not used by eoserv
/// </summary>
short SessionID { get; }

/// <summary>
/// Dialog ID, not used by eoserv
/// </summary>
short DialogID { get; }

/// <summary>
/// Quest dialog titles for the current character, keyed on <see cref="VendorID" />.
/// </summary>
IReadOnlyDictionary<short, string> DialogTitles { get; }

/// <summary>
/// Text for the quest dialog, one entry per page.
/// </summary>
IReadOnlyList<string> PageText { get; }

/// <summary>
/// Links for the quest dialog, only shown on the last page.
/// </summary>
IReadOnlyList<(short ActionID, string DisplayText)> Actions { get; }

IQuestDialogData WithVendorID(short vendorId);

IQuestDialogData WithQuestID(short questId);

IQuestDialogData WithSessionID(short sessionId);

IQuestDialogData WithDialogID(short dialogId);

IQuestDialogData WithDialogTitles(IReadOnlyDictionary<short, string> dialogTitles);

IQuestDialogData WithPageText(IReadOnlyList<string> pageText);

IQuestDialogData WithActions(IReadOnlyList<(short, string)> actions);
}
}
75 changes: 0 additions & 75 deletions EOLib/Net/API/Quest.cs
Expand Up @@ -4,12 +4,6 @@

namespace EOLib.Net.API
{
public enum DialogEntry : byte
{
DialogText = 1,
DialogLink
}

public enum DialogReply : byte
{
Ok = 1,
Expand Down Expand Up @@ -116,45 +110,14 @@ internal InProgressQuestData(OldPacket pkt)

partial class PacketAPI
{
public event QuestDialogEvent OnQuestDialog;
public event ViewQuestProgressEvent OnViewQuestProgress;
public event ViewQuestHistoryEvent OnViewQuestHistory;

private void _createQuestMembers()
{
m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Quest, PacketAction.Dialog), _handleQuestDialog, true);
m_client.AddPacketHandler(new FamilyActionPair(PacketFamily.Quest, PacketAction.List), _handleQuestList, true);
}

public bool TalkToQuestNPC(short npcIndex, short questID)
{
if (!Initialized || !m_client.ConnectedAndInitialized)
return false;

OldPacket pkt = new OldPacket(PacketFamily.Quest, PacketAction.Use);
pkt.AddShort(npcIndex);
pkt.AddShort(questID);

return m_client.SendPacket(pkt);
}

public bool RespondToQuestDialog(QuestState state, DialogReply reply, byte action = 0)
{
if (!Initialized || !m_client.ConnectedAndInitialized)
return false;

OldPacket pkt = new OldPacket(PacketFamily.Quest, PacketAction.Accept);
pkt.AddShort(state.SessionID); //session ID - ignored by default EOSERV
pkt.AddShort(state.DialogID); //dialog ID - ignored by default EOSERV
pkt.AddShort(state.QuestID);
pkt.AddShort(state.NPCIndex); //npc index - ignored by default EOSERV
pkt.AddChar((byte) reply);
if (reply == DialogReply.Link)
pkt.AddChar(action);

return m_client.SendPacket(pkt);
}

public bool RequestQuestHistory(QuestPage page)
{
if (!Initialized || !m_client.ConnectedAndInitialized)
Expand All @@ -166,44 +129,6 @@ public bool RequestQuestHistory(QuestPage page)
return m_client.SendPacket(pkt);
}

private void _handleQuestDialog(OldPacket pkt)
{
if (OnQuestDialog == null) return;

int numDialogs = pkt.GetChar();
short vendorID = pkt.GetShort();
short questID = pkt.GetShort();
short sessionID = pkt.GetShort(); //not used by eoserv
short dialogID = pkt.GetShort(); //not used by eoserv
if (pkt.GetByte() != 255) return;

QuestState stateInfo = new QuestState(sessionID, dialogID, questID, vendorID);

var dialogNames = new Dictionary<short, string>(numDialogs);
for (int i = 0; i < numDialogs; ++i)
{
dialogNames.Add(pkt.GetShort(), pkt.GetBreakString());
}

var pages = new List<string>();
var links = new Dictionary<short, string>();
while (pkt.ReadPos != pkt.Length)
{
var entry = (DialogEntry) pkt.GetShort();
switch (entry)
{
case DialogEntry.DialogText:
pages.Add(pkt.GetBreakString());
break;
case DialogEntry.DialogLink:
links.Add(pkt.GetShort(), pkt.GetBreakString());
break;
}
}

OnQuestDialog(stateInfo, dialogNames, pages, links);
}

private void _handleQuestList(OldPacket pkt)
{
QuestPage page = (QuestPage) pkt.GetChar();
Expand Down

0 comments on commit 6397611

Please sign in to comment.