Skip to content

Commit

Permalink
Added OnGameModeChanged MP lobby event & !mp set tracking
Browse files Browse the repository at this point in the history
  • Loading branch information
hburn7 committed Feb 17, 2023
1 parent 25737ee commit 8123ebb
Show file tree
Hide file tree
Showing 5 changed files with 223 additions and 11 deletions.
2 changes: 1 addition & 1 deletion BanchoSharp/BanchoSharp.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
<PackageProjectUrl>https://github.com/hburn7/BanchoSharp</PackageProjectUrl>
<RepositoryUrl>https://github.com/hburn7/BanchoSharp</RepositoryUrl>
<PackageLicenseFile>LICENSE.txt</PackageLicenseFile>
<PackageVersion>1.3.0</PackageVersion>
<PackageVersion>1.4.0</PackageVersion>
</PropertyGroup>

<ItemGroup>
Expand Down
1 change: 1 addition & 0 deletions BanchoSharp/Interfaces/IMultiplayerLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,7 @@ public interface IMultiplayerLobby : IChatChannel, INotifyStateChanged
public event Action OnMatchAborted;
public event Action OnMatchStarted;
public event Action OnMatchFinished;
public event Action<GameMode, GameMode> OnGameModeChanged;
public event Action OnClosed;
public event Action OnAllPlayersReady;
public event Action<IMultiplayerPlayer> OnHostChanged;
Expand Down
84 changes: 84 additions & 0 deletions BanchoSharp/Messaging/MpSetParser.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
using BanchoSharp.Multiplayer;

namespace BanchoSharp.Messaging;

/// <summary>
/// Responsible for handling the parsing of BanchoBot's response to !mp set
/// </summary>
public class MpSetResponseParser
{
private readonly string _response;

public MpSetResponseParser(string banchoResponse)
{
_response = banchoResponse;

IsMpSetResponse = _response.StartsWith("Changed match settings to ");
if (!IsMpSetResponse)
{
return;
}

ResolvedConfiguration = Parse();
}

private MpSetConfig? Parse()
{
// Regex to match the format, win condition, and size
try
{
var config = new MpSetConfig();
string relevantInfo = _response.Split("Changed match settings to ")[1];
string[] tokens = relevantInfo.Split(',');

if (tokens.Length == 0)
{
return null;
}
if (tokens.Length == 1)
{
// Only the format was set
config.Format = (LobbyFormat)Enum.Parse(typeof(LobbyFormat), tokens[0].Trim());
}
else if (tokens.Length == 2)
{
// The format and win condition were set
config.Format = (LobbyFormat)Enum.Parse(typeof(LobbyFormat), tokens[0].Trim());
config.WinCondition = (WinCondition)Enum.Parse(typeof(WinCondition), tokens[1].Trim());
}
else if (tokens.Length == 3)
{
// Slots, format, and win condition were set
string sizeParse = tokens[0].Split("slots")[0].Trim();
if (int.TryParse(sizeParse, out int size))
{
config.Size = size;
}
else
{
Logger.Warn($"Failed to parse {sizeParse} as an integer. Size unable to be determined.");
}

config.Format = (LobbyFormat)Enum.Parse(typeof(LobbyFormat), tokens[1].Trim());
config.WinCondition = (WinCondition)Enum.Parse(typeof(WinCondition), tokens[2].Trim());
}

return config;
}
catch (Exception e)
{
Logger.Warn($"Failed to parse mp set response! {e.Message}");
return null;
}
}

public bool IsMpSetResponse { get; }
public MpSetConfig? ResolvedConfiguration { get; private set; }
}

public struct MpSetConfig
{
public LobbyFormat Format { get; set; } // AKA teammode in docs
public WinCondition? WinCondition { get; set; } // AKA scoremode in docs
public int? Size { get; set; }
}
56 changes: 56 additions & 0 deletions BanchoSharp/Multiplayer/MultiplayerLobby.cs
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,7 @@ public MultiplayerLobby(IBanchoClient client, long id, string name) : base($"#mp
public event Action? OnMatchAborted;
public event Action? OnMatchStarted;
public event Action? OnMatchFinished;
public event Action<GameMode, GameMode>? OnGameModeChanged;
public event Action? OnClosed;
public event Action<IMultiplayerPlayer>? OnHostChanged;
public event Action<BeatmapShell>? OnBeatmapChanged;
Expand Down Expand Up @@ -344,10 +345,32 @@ private void SetAllPlayerStates(PlayerState state)

private void UpdateLobbyFromBanchoBotSettingsResponse(string banchoResponse)
{
var parser = new MpSetResponseParser(banchoResponse);
var cfg = parser.ResolvedConfiguration;
if (parser.IsMpSetResponse && cfg.HasValue)
{
Format = cfg.Value.Format;
if (cfg.Value.WinCondition.HasValue)
{
WinCondition = cfg.Value.WinCondition.Value;
}

if (cfg.Value.Size.HasValue)
{
Size = cfg.Value.Size.Value;
}

return;
}

if (IsRoomNameNotification(banchoResponse))
{
UpdateName(banchoResponse);
}
else if (IsGameModeUpdateNotification(banchoResponse))
{
UpdateGameMode(banchoResponse);
}
else if (IsTeamModeNotification(banchoResponse))
{
UpdateFormatWincondition(banchoResponse);
Expand Down Expand Up @@ -467,6 +490,7 @@ private void UpdateLobbyFromBanchoBotSettingsResponse(string banchoResponse)
private bool IsAllPlayersReadyNotification(string banchoResponse) => banchoResponse.StartsWith("All players are ready");
private bool IsMatchAbortedNotification(string banchoResponse) => banchoResponse.StartsWith("Aborted the match");
private bool IsMatchSizeNotification(string banchoResponse) => banchoResponse.StartsWith("Changed match to size");
private bool IsGameModeUpdateNotification(string banchoResponse) => banchoResponse.StartsWith("Changed match mode to ");

private void UpdateName(string banchoResponse)
{
Expand All @@ -481,6 +505,38 @@ private void UpdateName(string banchoResponse)
InvokeOnStateChanged();
}

private void UpdateGameMode(string banchoResponse)
{
string[] splits = banchoResponse.Split("Changed match mode to ");
string token = splits[1];
GameMode? newGameMode = null;
switch (token)
{
// Parse as GameMode enum
case "Osu":
newGameMode = GameMode.osu;
break;
case "Taiko":
newGameMode = GameMode.osuTaiko;
break;
case "CatchTheBeat":
newGameMode = GameMode.osuCatch;
break;
case "OsuMania":
newGameMode = GameMode.osuMania;
break;
default:
Logger.Warn($"Could not parse game mode from BanchoBot game mode update notification: '{banchoResponse}'");
break;
}

if (newGameMode != null)
{
OnGameModeChanged?.Invoke(GameMode, newGameMode.Value);
GameMode = newGameMode.Value;
}
}

private IMultiplayerPlayer? FindPlayer(string name) => Players.FirstOrDefault(x => x.Name.Equals(name, StringComparison.OrdinalIgnoreCase));
private IMultiplayerPlayer? FindPlayer(IMultiplayerPlayer player) => Players.FirstOrDefault(x => x.Equals(player));

Expand Down
91 changes: 81 additions & 10 deletions BanchoSharpTests/MultiplayerTests.cs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,6 @@ private void InvokeEventInvoker(string message) => ((IBanchoBotEventInvoker)_cli
.ProcessMessage(PrivateIrcMessage.CreateFromParameters("BanchoBot", "DummyRecipient", message));

private void Invoke(IIrcMessage message) => _client.SimulateMessageReceived(message);

private void InvokeToLobby(string message) => Invoke(PrivateIrcMessage.CreateFromParameters("BanchoBot", _lobby.ChannelName, message));

[SetUp]
Expand Down Expand Up @@ -108,17 +107,87 @@ public void TestBeatmapChanged(int id, string artist, string title, string diff,
_lobby.OnBeatmapChanged += shell => { Assert.That(shell, Is.EqualTo(beatmap)); };
}

[TestCase("Changed match mode to Osu", GameMode.osu)]
[TestCase("Changed match mode to Taiko", GameMode.osuTaiko)]
[TestCase("Changed match mode to CatchTheBeat", GameMode.osuCatch)]
[TestCase("Changed match mode to OsuMania", GameMode.osuMania)]
public void TestMatchModeChange(string banchoResponse, GameMode mode)
{
InvokeToLobby(banchoResponse);
Assert.That(_lobby.GameMode, Is.EqualTo(mode));
}

[Test]
public void TestMpSetResponseParser()
{
var resBuilder = new StringBuilder("Changed match settings to ");
// [# slots, ]<format>, <win condition>
string[] formats = { "HeadToHead", "TagCoop", "TeamVs", "TagTeamVs" };
string[] winConditions = { "Score", "Accuracy", "Combo", "ScoreV2" };
string[] slots = { "1", "2", "3", "4", "5", "6", "7", "8", "9", "10", "11", "12", "13", "14", "15", "16" };

// Test format only
foreach (string format in formats)
{
resBuilder.Append($"{format}");
InvokeToLobby(resBuilder.ToString());
Assert.That(_lobby.Format, Is.EqualTo((LobbyFormat)Enum.Parse(typeof(LobbyFormat), format)));
resBuilder.Clear();
resBuilder.Append("Changed match settings to ");
}

// Test format and wincondition only
foreach (string format in formats)
{
foreach (string winCondition in winConditions)
{
resBuilder.Append($"{format}, {winCondition}");
InvokeToLobby(resBuilder.ToString());
Assert.Multiple(() =>
{
Assert.That(_lobby.Format, Is.EqualTo((LobbyFormat)Enum.Parse(typeof(LobbyFormat), format)));
Assert.That(_lobby.WinCondition, Is.EqualTo((WinCondition)Enum.Parse(typeof(WinCondition), winCondition)));
});

resBuilder.Clear();
resBuilder.Append("Changed match settings to ");
}
}

// Test format, wincondition and slots
foreach (string format in formats)
{
foreach (string winCondition in winConditions)
{
foreach (string slot in slots)
{
resBuilder.Append($"{slot} slots, {format}, {winCondition}");
InvokeToLobby(resBuilder.ToString());
Assert.Multiple(() =>
{
Assert.That(_lobby.Format, Is.EqualTo((LobbyFormat)Enum.Parse(typeof(LobbyFormat), format)));
Assert.That(_lobby.WinCondition, Is.EqualTo((WinCondition)Enum.Parse(typeof(WinCondition), winCondition)));
Assert.That(_lobby.Size, Is.EqualTo(int.Parse(slot)));
});

resBuilder.Clear();
resBuilder.Append("Changed match settings to ");
}
}
}
}

[Test]
public void TestMpClearhost()
{
var dummy = new MultiplayerPlayer(_lobby, "test", 1);

_lobby.Players.Add(dummy);
Assert.That(_lobby.Host, Is.Null);

InvokeToLobby("test became the host.");
Assert.That(_lobby.Host, Is.EqualTo(dummy));

InvokeToLobby("Cleared match host");
Assert.That(_lobby.Host, Is.Null);
}
Expand All @@ -141,7 +210,7 @@ public void TestMpSettingsBeatmap()
Assert.That(_lobby.CurrentBeatmap, Is.EqualTo(shell2));
});
}

[Test]
public void TestAllPlayersReady()
{
Expand Down Expand Up @@ -198,14 +267,16 @@ public void TestHostChangingBeatmap()
1738018, "THE ORAL CIGARETTES", "Flower", "Sakura")]
[TestCase(":BanchoBot!cho@ppy.sh PRIVMSG #mp_1 :Beatmap changed to: TheFatRat - Mayday (feat. Laura Brehm) [[2B] Calling Out Mayday] (https://osu.ppy.sh/b/1605148)",
1605148, "TheFatRat", "Mayday (feat. Laura Brehm)", "[2B] Calling Out Mayday")]
[TestCase(":BanchoBot!cho@ppy.sh PRIVMSG #mp_1 :Beatmap changed to: Toby Fox - MEGALOVANIA (Camellia Remix) [Tocorn x Ciyus Miapah : Inevitable Demise] (https://osu.ppy.sh/b/2169346)",
[TestCase(
":BanchoBot!cho@ppy.sh PRIVMSG #mp_1 :Beatmap changed to: Toby Fox - MEGALOVANIA (Camellia Remix) [Tocorn x Ciyus Miapah : Inevitable Demise] (https://osu.ppy.sh/b/2169346)",
2169346, "Toby Fox", "MEGALOVANIA (Camellia Remix)", "Tocorn x Ciyus Miapah : Inevitable Demise")]
public void TestMpSet(string message, int id, string artist, string title, string diff)
public void TestMpSet(string message, int id, string artist, string title,
string diff)
{
var irc = new PrivateIrcMessage(message);

_client.SimulateMessageReceived(irc);

Assert.Multiple(() =>
{
Assert.That(_lobby.CurrentBeatmap, Is.Not.Null);
Expand Down Expand Up @@ -251,7 +322,7 @@ public void TestPlayerState(string message, bool isHost, PlayerState state)
{
Assert.That(_lobby.Host, Is.EqualTo(target));
}
Assert.That(target!.State, Is.EqualTo(state));
});
}
Expand Down

0 comments on commit 8123ebb

Please sign in to comment.