Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
99 changes: 99 additions & 0 deletions Archipelago.MultiClient.Net.Tests/DeathLinkFixture.cs
Original file line number Diff line number Diff line change
Expand Up @@ -201,5 +201,104 @@ public void Should_disable_death_link_if_its_already_enabled()
Assert.That(connectionInfo.Tags.Length, Is.EqualTo(1));
Assert.That(connectionInfo.Tags[0], Is.EqualTo("SomeTag"));
}

[Test]
public void Should_enable_death_link_with_group_tag()
{
var socket = Substitute.For<IArchipelagoSocketHelper>();
var connectionInfo = new ConnectionInfoHelper(socket) { Tags = Array.Empty<string>() };

var sut = new DeathLinkService(socket, connectionInfo, "MyGroup");

sut.EnableDeathLink();

socket.Received().SendPacket(Arg.Is<ConnectUpdatePacket>(p => p.Tags.Length == 1 && p.Tags[0] == "DeathLinkMyGroup"));

Assert.That(connectionInfo.Tags.Length, Is.EqualTo(1));
Assert.That(connectionInfo.Tags[0], Is.EqualTo("DeathLinkMyGroup"));
}

[Test]
public void Should_send_death_link_with_group_tag()
{
var socket = Substitute.For<IArchipelagoSocketHelper>();
var connectionInfo = Substitute.For<IConnectionInfoProvider>();

var sut = new DeathLinkService(socket, connectionInfo, "MyGroup");

sut.SendDeathLink(new DeathLink("TestPlayer", "Died"));

socket.Received().SendPacketAsync(Arg.Is<BouncePacket>(p => p.Tags.Count == 1 && p.Tags[0] == "DeathLinkMyGroup"));
}

[Test]
public void Should_receive_death_link_with_matching_group_tag()
{
var socket = Substitute.For<IArchipelagoSocketHelper>();
var connectionInfo = Substitute.For<IConnectionInfoProvider>();

var sut = new DeathLinkService(socket, connectionInfo, "MyGroup");

DeathLink receivedDeathLink = null;
sut.OnDeathLinkReceived += dl => receivedDeathLink = dl;

var death = new DeathLink("SomeoneElse", "Died");
var bouncePacket = new BouncedPacket {
Tags = new List<string> { "DeathLinkMyGroup" },
Data = new Dictionary<string, JToken> {
{ "source", death.Source },
{ "time", death.Timestamp.ToUnixTimeStamp() },
{ "cause", death.Cause }
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelperDelagates.PacketReceivedHandler>(bouncePacket);

Assert.That(receivedDeathLink, Is.Not.Null);
}

[Test]
public void Should_not_receive_death_link_from_a_different_group()
{
var socket = Substitute.For<IArchipelagoSocketHelper>();
var connectionInfo = Substitute.For<IConnectionInfoProvider>();

var sut = new DeathLinkService(socket, connectionInfo, "MyGroup");

DeathLink receivedDeathLink = null;
sut.OnDeathLinkReceived += dl => receivedDeathLink = dl;

// A fully-valid death in the default pool ("DeathLink") must not reach a grouped client.
var death = new DeathLink("SomeoneElse", "Died");
var bouncePacket = new BouncedPacket {
Tags = new List<string> { "DeathLink" },
Data = new Dictionary<string, JToken> {
{ "source", death.Source },
{ "time", death.Timestamp.ToUnixTimeStamp() },
{ "cause", death.Cause }
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelperDelagates.PacketReceivedHandler>(bouncePacket);

Assert.That(receivedDeathLink, Is.Null);
}

[Test]
public void Should_use_default_tag_for_blank_group()
{
var socket = Substitute.For<IArchipelagoSocketHelper>();
var connectionInfo = new ConnectionInfoHelper(socket) { Tags = Array.Empty<string>() };

// Blank group is wire-identical to standard DeathLink (default pool).
var sut = new DeathLinkService(socket, connectionInfo, "");

sut.EnableDeathLink();

socket.Received().SendPacket(Arg.Is<ConnectUpdatePacket>(p => p.Tags.Length == 1 && p.Tags[0] == "DeathLink"));

Assert.That(connectionInfo.Tags.Length, Is.EqualTo(1));
Assert.That(connectionInfo.Tags[0], Is.EqualTo("DeathLink"));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,14 @@ public static class DeathLinkProvider
/// </summary>
public static DeathLinkService CreateDeathLinkService(this ArchipelagoSession session) =>
new DeathLinkService(session.Socket, session.ConnectionInfo);

// ReSharper disable once UnusedMember.Global
/// <summary>
/// creates and returns a <see cref="DeathLinkService"/> for this <paramref name="session"/>,
/// scoped to the DeathLink <paramref name="group"/>. Only players sharing the same group name
/// send and receive deaths to one another; a null or empty group joins the default DeathLink pool.
/// </summary>
public static DeathLinkService CreateDeathLinkService(this ArchipelagoSession session, string group) =>
new DeathLinkService(session.Socket, session.ConnectionInfo, group);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ public class DeathLinkService
{
readonly IArchipelagoSocketHelper socket;
readonly IConnectionInfoProvider connectionInfoProvider;
readonly string tag;

DeathLink lastSendDeathLink;

Expand All @@ -26,10 +27,12 @@ public class DeathLinkService
/// </summary>
public event DeathLinkReceivedHandler OnDeathLinkReceived;

internal DeathLinkService(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider)
internal DeathLinkService(IArchipelagoSocketHelper socket, IConnectionInfoProvider connectionInfoProvider,
string group = null)
{
this.socket = socket;
this.connectionInfoProvider = connectionInfoProvider;
this.tag = "DeathLink" + (group ?? "");

socket.PacketReceived += OnPacketReceived;
}
Expand All @@ -38,7 +41,7 @@ void OnPacketReceived(ArchipelagoPacketBase packet)
{
switch (packet)
{
case BouncedPacket bouncedPacket when bouncedPacket.Tags.Contains("DeathLink"):
case BouncedPacket bouncedPacket when bouncedPacket.Tags.Contains(tag):
if (DeathLink.TryParse(bouncedPacket.Data, out var deathLink))
{
if (lastSendDeathLink != null && lastSendDeathLink == deathLink)
Expand Down Expand Up @@ -66,7 +69,7 @@ public void SendDeathLink(DeathLink deathLink)
{
var bouncePacket = new BouncePacket
{
Tags = new List<string> { "DeathLink" },
Tags = new List<string> { tag },
Data = new Dictionary<string, JToken> {
{"time", deathLink.Timestamp.ToUnixTimeStamp()},
{"source", deathLink.Source},
Expand All @@ -82,27 +85,29 @@ public void SendDeathLink(DeathLink deathLink)
}

/// <summary>
/// Adds "DeathLink" to your <see cref="ArchipelagoSession"/>'s tags and opts you in to receiving
/// Adds the DeathLink tag (suffixed with the group name, if any) to your
/// <see cref="ArchipelagoSession"/>'s tags and opts you in to receiving
/// <see cref="OnDeathLinkReceived"/> events
/// </summary>
public void EnableDeathLink()
{
if (Array.IndexOf(connectionInfoProvider.Tags, "DeathLink") == -1)
if (Array.IndexOf(connectionInfoProvider.Tags, tag) == -1)
connectionInfoProvider.UpdateConnectionOptions(
connectionInfoProvider.Tags.Concat(new[] { "DeathLink" }).ToArray());
connectionInfoProvider.Tags.Concat(new[] { tag }).ToArray());
}

/// <summary>
/// Removes the "DeathLink" tag from your <see cref="ArchipelagoSession"/> and opts out of further
/// Removes the DeathLink tag (suffixed with the group name, if any) from your
/// <see cref="ArchipelagoSession"/> and opts out of further
/// <see cref="OnDeathLinkReceived"/> events
/// </summary>
public void DisableDeathLink()
{
if (Array.IndexOf(connectionInfoProvider.Tags, "DeathLink") == -1)
if (Array.IndexOf(connectionInfoProvider.Tags, tag) == -1)
return;

connectionInfoProvider.UpdateConnectionOptions(
connectionInfoProvider.Tags.Where(t => t != "DeathLink").ToArray());
connectionInfoProvider.Tags.Where(t => t != tag).ToArray());
}
}
}