Skip to content

Commit

Permalink
Merge pull request #170 from ethanmoffat/quest_dlg
Browse files Browse the repository at this point in the history
Implement NPC quest dialog and related packet handlers
  • Loading branch information
ethanmoffat authored Apr 6, 2022
2 parents 574fb4f + 7924191 commit 5e344b6
Show file tree
Hide file tree
Showing 29 changed files with 880 additions and 444 deletions.
16 changes: 16 additions & 0 deletions EOLib/Domain/Interact/INPCInteractionNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
using AutomaticTypeMapper;
using EOLib.IO;

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

[AutoMappedType]
public class NoOpNPCInteractionNotifier : INPCInteractionNotifier
{
public void NotifyInteractionFromNPC(NPCType npcType) { }
}
}
27 changes: 23 additions & 4 deletions EOLib/Domain/Interact/MapNPCActions.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
using AutomaticTypeMapper;
using EOLib.Domain.NPC;
using EOLib.IO.Repositories;
using EOLib.Net;
using EOLib.Net.Communication;

Expand All @@ -8,16 +10,31 @@ namespace EOLib.Domain.Interact
public class MapNPCActions : IMapNPCActions
{
private readonly IPacketSendService _packetSendService;
private readonly IENFFileProvider _enfFileProvider;

public MapNPCActions(IPacketSendService packetSendService)
public MapNPCActions(IPacketSendService packetSendService,
IENFFileProvider enfFileProvider)
{
_packetSendService = packetSendService;
_enfFileProvider = enfFileProvider;
}

public void RequestShop(byte index)
public void RequestShop(INPC npc)
{
var packet = new PacketBuilder(PacketFamily.Shop, PacketAction.Open)
.AddShort(index)
.AddShort(npc.Index)
.Build();

_packetSendService.SendPacket(packet);
}

public void RequestQuest(INPC npc)
{
var data = _enfFileProvider.ENFFile[npc.ID];

var packet = new PacketBuilder(PacketFamily.Quest, PacketAction.Use)
.AddShort(npc.Index)
.AddShort(data.VendorID)
.Build();

_packetSendService.SendPacket(packet);
Expand All @@ -26,6 +43,8 @@ public void RequestShop(byte index)

public interface IMapNPCActions
{
void RequestShop(byte index);
void RequestShop(INPC npc);

void RequestQuest(INPC npc);
}
}
8 changes: 8 additions & 0 deletions EOLib/Domain/Interact/Quest/DialogReply.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
namespace EOLib.Domain.Interact.Quest
{
public enum DialogReply
{
Ok = 1,
Link
}
}
15 changes: 15 additions & 0 deletions EOLib/Domain/Interact/Quest/IStatusLabelNotifier.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
using AutomaticTypeMapper;

namespace EOLib.Domain.Interact.Quest
{
public interface IStatusLabelNotifier
{
void ShowWarning(string message);
}

[AutoMappedType]
public class NoOpStatusLabelNotifier : IStatusLabelNotifier
{
public void ShowWarning(string message) { }
}
}
44 changes: 44 additions & 0 deletions EOLib/Domain/Interact/Quest/QuestActions.cs
Original file line number Diff line number Diff line change
@@ -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 = 0);
}
}
38 changes: 38 additions & 0 deletions EOLib/Domain/Interact/Quest/QuestDataRepository.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
using AutomaticTypeMapper;
using EOLib.Domain.NPC;
using Optional;

namespace EOLib.Domain.Interact.Quest
{
public interface IQuestDataRepository : IResettable
{
INPC RequestedNPC { get; set; }

Option<IQuestDialogData> QuestDialogData { get; set; }
}

public interface IQuestDataProvider : IResettable
{
INPC RequestedNPC { get; }

Option<IQuestDialogData> QuestDialogData { get; }
}

[AutoMappedType(IsSingleton = true)]
public class QuestDataRepository : IQuestDataProvider, IQuestDataRepository
{
public INPC RequestedNPC { get; set; }

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
Original file line number Diff line number Diff line change
@@ -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);
}
}
21 changes: 0 additions & 21 deletions EOLib/Net/API/Message.cs

This file was deleted.

1 change: 0 additions & 1 deletion EOLib/Net/API/PacketAPI.cs
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@ public PacketAPI(EOClient client)
_createChestMembers();
_createInitMembers();
_createLockerMembers();
_createMessageMembers();
_createMusicMembers();
_createPartyMembers();
_createNPCMembers();
Expand Down
Loading

0 comments on commit 5e344b6

Please sign in to comment.