Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix for .net client failing to connect in AP 0.4 #54

Merged
merged 5 commits into from
Jul 15, 2022
Merged
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
200 changes: 200 additions & 0 deletions Archipelago.MultiClient.Net.Tests/PlayerHelperFixture.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Helpers;
using Archipelago.MultiClient.Net.Models;
using Archipelago.MultiClient.Net.Packets;
using NSubstitute;
using NUnit.Framework;
using System.Collections.Generic;
using System.Linq;

namespace Archipelago.MultiClient.Net.Tests
{
class PlayerHelperFixture
{
[Test]
public void Should_return_empty_collection_when_not_yet_initialized()
{
var socket = Substitute.For<IArchipelagoSocketHelper>();

var sut = new PlayerHelper(socket);

Assert.That(sut.AllPlayers, Is.Empty);
}

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

var sut = new PlayerHelper(socket);

var connectedPacket = new ConnectedPacket {
Players = new[] {
new NetworkPlayer { Name = "1", Alias = "One", Slot = 1, Team = 1 },
new NetworkPlayer { Name = "2", Alias = "Two", Slot = 2, Team = 1 },
new NetworkPlayer { Name = "3", Alias = "Three", Slot = 1, Team = 2 },
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(connectedPacket);

var playerOne = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 1);
Assert.That(playerOne.Name, Is.EqualTo("1"));
Assert.That(playerOne.Alias, Is.EqualTo("One"));

var playerTwo = sut.AllPlayers.First(p => p.Slot == 2 && p.Team == 1);
Assert.That(playerTwo.Name, Is.EqualTo("2"));
Assert.That(playerTwo.Alias, Is.EqualTo("Two"));

var playerThree = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 2);
Assert.That(playerThree.Name, Is.EqualTo("3"));
Assert.That(playerThree.Alias, Is.EqualTo("Three"));
}

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

var sut = new PlayerHelper(socket);

var connectedPacket = new ConnectedPacket {
Players = new[] {
new NetworkPlayer { Name = "1", Alias = "One", Slot = 1, Team = 1 },
new NetworkPlayer { Name = "2", Alias = "Two", Slot = 2, Team = 1 },
new NetworkPlayer { Name = "3", Alias = "Three", Slot = 1, Team = 2 },
},
SlotInfo = new Dictionary<int, NetworkSlot> {
{ 1, new NetworkSlot { Type = SlotType.Player, Game = "Game1" } },
{ 2, new NetworkSlot { Type = SlotType.Player, Game = "Game2" } }
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(connectedPacket);

var playerOne = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 1);
Assert.That(playerOne.Game, Is.EqualTo("Game1"));

var playerTwo = sut.AllPlayers.First(p => p.Slot == 2 && p.Team == 1);
Assert.That(playerTwo.Game, Is.EqualTo("Game2"));

var playerThree = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 2);
Assert.That(playerThree.Game, Is.EqualTo("Game1"));
}

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

var sut = new PlayerHelper(socket);

var connectedPacket = new ConnectedPacket
{
Players = new[] {
new NetworkPlayer { Name = "1", Alias = "One", Slot = 1, Team = 1 },
new NetworkPlayer { Name = "2", Alias = "Two", Slot = 2, Team = 1 },
new NetworkPlayer { Name = "3", Alias = "Three", Slot = 3, Team = 1 },
},
SlotInfo = new Dictionary<int, NetworkSlot> {
{ 1, new NetworkSlot { Type = SlotType.Player, Game = "Game1" } },
{ 2, new NetworkSlot { Type = SlotType.Player, Game = "Game2" } },
{ 3, new NetworkSlot { Type = SlotType.Player, Game = "Game1" } },
{ 4, new NetworkSlot { Type = SlotType.Group, Game = "Game1",
Name = "Player3Personal", GroupMembers = new []{ 3 }} },
{ 5, new NetworkSlot { Type = SlotType.Group, Game = "Game1",
Name = "Game1All", GroupMembers = new []{ 1,3 }} },
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(connectedPacket);

var playerOne = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 1);
Assert.That(playerOne.Groups.Length, Is.EqualTo(1));
var playerOneGroup = playerOne.Groups.First();
Assert.That(playerOneGroup.Name, Is.EqualTo("Game1All"));
Assert.That(playerOneGroup.Game, Is.EqualTo("Game1"));
Assert.That(playerOneGroup.GroupMembers, Is.EquivalentTo(new []{ 1, 3 }));

var playerTwo = sut.AllPlayers.First(p => p.Slot == 2 && p.Team == 1);
Assert.That(playerTwo.Groups, Is.Empty);

var playerThree = sut.AllPlayers.First(p => p.Slot == 3 && p.Team == 1);
Assert.That(playerThree.Groups.Length, Is.EqualTo(2));
var playerThreeGroupGame1All = playerThree.Groups.First(g => g.Name == "Game1All");
Assert.That(playerThreeGroupGame1All.Game, Is.EqualTo("Game1"));
Assert.That(playerThreeGroupGame1All.GroupMembers, Is.EquivalentTo(new[] { 1, 3 }));
var playerThreeGroupPersonal = playerThree.Groups.First(g => g.Name == "Player3Personal");
Assert.That(playerThreeGroupPersonal.Game, Is.EqualTo("Game1"));
Assert.That(playerThreeGroupPersonal.GroupMembers, Is.EquivalentTo(new[] { 3 }));
}

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

var sut = new PlayerHelper(socket);

var connectedPacket = new ConnectedPacket
{
Players = new[] {
new NetworkPlayer { Name = "1", Alias = "One", Slot = 1, Team = 1 },
new NetworkPlayer { Name = "2", Alias = "Two", Slot = 1, Team = 2 },
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(connectedPacket);

var playerOne = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 1);
Assert.That(playerOne.Name, Is.EqualTo("1"));
Assert.That(playerOne.Alias, Is.EqualTo("One"));

var playerTwo = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 2);
Assert.That(playerTwo.Name, Is.EqualTo("2"));
Assert.That(playerTwo.Alias, Is.EqualTo("Two"));

var roomInfoUpdatedPacket = new RoomUpdatePacket {
Players = new[] {
new NetworkPlayer { Name = "Henk", Alias = "Terminator", Slot = 1, Team = 1 },
new NetworkPlayer { Name = "Frank", Alias = "Destroyer", Slot = 2, Team = 1 },
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(roomInfoUpdatedPacket);

var playerOneUpdated = sut.AllPlayers.First(p => p.Slot == 1 && p.Team == 1);
Assert.That(playerOneUpdated.Name, Is.EqualTo("Henk"));
Assert.That(playerOneUpdated.Alias, Is.EqualTo("Terminator"));

var playerTwoUpdated = sut.AllPlayers.First(p => p.Slot == 2 && p.Team == 1);
Assert.That(playerTwoUpdated.Name, Is.EqualTo("Frank"));
Assert.That(playerTwoUpdated.Alias, Is.EqualTo("Destroyer"));
}

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

var sut = new PlayerHelper(socket);

var connectedPacket = new ConnectedPacket
{
Players = new[] {
new NetworkPlayer { Name = "1", Alias = "One", Slot = 1, Team = 1 },
}
};

socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(connectedPacket);

var roomInfoUpdatedPacket = new RoomUpdatePacket();

Assert.DoesNotThrow(() => {
socket.PacketReceived += Raise.Event<ArchipelagoSocketHelper.PacketReceivedHandler>(roomInfoUpdatedPacket);
});

Assert.That(sut.AllPlayers.Count, Is.EqualTo(1));
}
}
}
10 changes: 9 additions & 1 deletion Archipelago.MultiClient.Net/Helpers/DataStorageHelper.cs
Original file line number Diff line number Diff line change
Expand Up @@ -117,9 +117,17 @@ private JToken GetValue(string key)

GetAsync(key, v => value = v);

int iterations = 0;
while (value == null)
{
Thread.Sleep(10);
Thread.Sleep(100);
if (++iterations > 10)
{
throw new TimeoutException($"Timed out retrieving data for key `{key}`. " +
$"This may be due to an attempt to retrieve a value from the DataStorageHelper in a synchronous fashion from within a PacketReceived handler. " +
$"When using the DataStorageHelper from within code which runs on the websocket thread then use the asynchronous getters. Ex: `DataStorageHelper[\"{key}\"].GetAsync(x => {{}});`" +
$"Be aware that DataStorageHelper calls tend to cause packet responses, so making a call from within a PacketReceived handler may cause an infinite loop.");
}
}

return value;
Expand Down
66 changes: 23 additions & 43 deletions Archipelago.MultiClient.Net/Helpers/PlayerHelper.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
using Archipelago.MultiClient.Net.Models;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Models;
using Archipelago.MultiClient.Net.Packets;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;

Expand All @@ -12,9 +14,9 @@ public class PlayerHelper
/// <summary>
/// A collection of PlayerInfo's where the index is the player their slot
/// </summary>
public ReadOnlyCollection<PlayerInfo> AllPlayers => new ReadOnlyCollection<PlayerInfo>(players);
public ReadOnlyCollection<PlayerInfo> AllPlayers => new ReadOnlyCollection<PlayerInfo>(players ?? new PlayerInfo[0]);

internal PlayerHelper(ArchipelagoSocketHelper socket)
internal PlayerHelper(IArchipelagoSocketHelper socket)
{
socket.PacketReceived += PacketReceived;
}
Expand Down Expand Up @@ -82,62 +84,39 @@ private void PacketReceived(ArchipelagoPacketBase packet)
switch (packet)
{
case ConnectedPacket connectedPacket:
OnConnectedPacketReceived(connectedPacket);
CreatePlayerInfo(connectedPacket.Players, connectedPacket.SlotInfo);
break;
case RoomUpdatePacket roomUpdatePacket:
OnRoomUpdatedPacketReceived(roomUpdatePacket);
UpdatePlayerInfo(roomUpdatePacket.Players);
break;
case RoomInfoPacket roomInfoPacket:
OnRoomInfoPacketReceived(roomInfoPacket);
break;
}
}

private void OnRoomInfoPacketReceived(RoomInfoPacket packet)
{
UpdateGames(packet.Games);
}

private void OnConnectedPacketReceived(ConnectedPacket packet)
{
UpdatePlayerInfo(packet.Players);
}

private void OnRoomUpdatedPacketReceived(RoomUpdatePacket packet)
{
if (packet.Players != null && packet.Players.Length > 0)
{
UpdatePlayerInfo(packet.Players);
}
}

private void UpdateGames(string[] games)
private void CreatePlayerInfo(NetworkPlayer[] networkPlayers, Dictionary<int, NetworkSlot> slotInfos)
{
if (players == null)
NetworkSlot[] groups;
if (slotInfos == null)
{
players = games.Select(g => new PlayerInfo { Game = g }).ToArray();
groups = new NetworkSlot[0];
}
else
{
for (int i = 0; i < games.Length; i++)
{
players[i].Game = games[i];
}
groups = slotInfos.Values.Where(s => s.Type == SlotType.Group).ToArray();
}

players = networkPlayers.Select(p => new PlayerInfo {
Team = p.Team,
Slot = p.Slot,
Name = p.Name,
Alias = p.Alias,
Game = slotInfos?[p.Slot].Game,
Groups = groups.Where(g => g.GroupMembers.Contains(p.Slot)).ToArray()
}).ToArray();
}

private void UpdatePlayerInfo(NetworkPlayer[] networkPlayers)
{
if (players == null)
{
players = networkPlayers.Select(p => new PlayerInfo {
Team = p.Team,
Slot = p.Slot,
Name = p.Name,
Alias = p.Alias
}).ToArray();
}
else
if (networkPlayers != null && networkPlayers.Length > 0)
{
for (int i = 0; i < networkPlayers.Length; i++)
{
Expand All @@ -157,5 +136,6 @@ public class PlayerInfo
public string Alias { get; internal set; }
public string Name { get; internal set; }
public string Game { get; internal set; }
public NetworkSlot[] Groups { get; internal set; }
}
}
5 changes: 2 additions & 3 deletions Archipelago.MultiClient.Net/Models/NetworkSlot.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
using System.Collections.Generic;
using Archipelago.MultiClient.Net.Enums;
using Archipelago.MultiClient.Net.Enums;
using Newtonsoft.Json;

namespace Archipelago.MultiClient.Net.Models
Expand All @@ -13,6 +12,6 @@ public struct NetworkSlot
[JsonProperty("type")]
public SlotType Type { get; set; }
[JsonProperty("group_members")]
public List<int> GroupMembers { get; set; }
public int[] GroupMembers { get; set; }
}
}