Skip to content
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
19 changes: 8 additions & 11 deletions apps/counterstrikesharp/src/FiveStack.Commands/WebMessage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using FiveStack.Utilities;

namespace FiveStack;

Expand All @@ -11,24 +12,20 @@ public partial class FiveStackPlugin
[CommandHelper(whoCanExecute: CommandUsage.SERVER_ONLY)]
public void OnWebMessage(CCSPlayerController? player, CommandInfo? command)
{
if (command == null)
if (command == null || command.ArgCount < 2)
{
return;
}

string message = command.ArgByIndex(1);
(string text, bool organizer) = ChatUtility.ParseWebChat(
Enumerable.Range(1, command.ArgCount - 1).Select(command.ArgByIndex).ToList()
);

if (message == null)
if (organizer)
{
return;
}

if (message.StartsWith("[organizer]"))
{
message =
$" {ChatColors.Red}[organizer]{ChatColors.White} {message.Replace("[organizer]", "")}";
text = $" {ChatColors.Red}{ChatUtility.OrganizerTag}{ChatColors.White} {text}";
}

_gameServer.Message(HudDestination.Chat, message);
_gameServer.Message(HudDestination.Chat, text);
}
}
15 changes: 3 additions & 12 deletions apps/counterstrikesharp/src/FiveStack.Events/GagPlayer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -28,19 +28,10 @@ public HookResult GagPlayer(CCSPlayerController? player, CommandInfo info)
return HookResult.Continue;
}

MatchMember? member = MatchUtility.GetMemberFromLineup(
matchData,
player.SteamID.ToString(),
player.PlayerName
);

if (member != null)
if (MatchUtility.IsGagged(matchData, player.SteamID.ToString(), player.PlayerName))
{
if (member.is_gagged)
{
player.PrintToChat(Localizer["gag.you_are_gagged"]);
return HookResult.Stop;
}
player.PrintToChat(Localizer["gag.you_are_gagged"]);
return HookResult.Stop;
}

return HookResult.Continue;
Expand Down
53 changes: 23 additions & 30 deletions apps/counterstrikesharp/src/FiveStack.Events/PlayerChat.cs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Utils;
using FiveStack.Entities;
using FiveStack.Enums;
using FiveStack.Utilities;

namespace FiveStack;
Expand Down Expand Up @@ -41,50 +42,42 @@ bool teamOnly
return HookResult.Continue;
}

if (player.Team == CsTeam.Spectator)
eAllChatRoute route = MatchUtility.AllChatRoute(
_matchService.GetCurrentMatch()?.GetMatchData(),
player.SteamID.ToString(),
player.PlayerName,
player.Team == CsTeam.Spectator
);

if (route == eAllChatRoute.Block)
{
return HookResult.Stop;
}

if (route == eAllChatRoute.Spectator)
{
PublishChatEvent(player, message);

string clan = string.IsNullOrEmpty(player.Clan) ? "" : $"[{player.Clan}]";
string clan = string.IsNullOrEmpty(player.Clan)
? ""
: ChatUtility.StripFormatting($"[{player.Clan}]");

string name = ChatUtility.StripFormatting(player.PlayerName);
string text = ChatUtility.StripFormatting(message);

_gameServer.Message(
HudDestination.Chat,
$" {ChatColors.Red}{clan}{ChatColors.White} {player.PlayerName}: {message}"
$" {ChatColors.Red}{clan}{ChatColors.White} {name}: {text}"
);

return HookResult.Stop;
}

MatchManager? match = _matchService.GetCurrentMatch();

if (match == null)
{
return HookResult.Continue;
}

MatchData? matchData = match.GetMatchData();

if (matchData == null)
if (route == eAllChatRoute.Publish)
{
return HookResult.Continue;
}

MatchMember? member = MatchUtility.GetMemberFromLineup(
matchData,
player.SteamID.ToString(),
player.PlayerName
);

if (member != null)
{
if (member.is_gagged)
{
return HookResult.Stop;
}
PublishChatEvent(player, message);
}

PublishChatEvent(player, message);

return HookResult.Continue;
}

Expand Down
41 changes: 34 additions & 7 deletions apps/counterstrikesharp/src/FiveStack.Utilities/MatchUtility.cs
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,31 @@ string playerName
});
}

public static eAllChatRoute AllChatRoute(
MatchData? matchData,
string steamId,
string playerName,
bool spectator
)
{
if (matchData != null && IsGagged(matchData, steamId, playerName))
{
return eAllChatRoute.Block;
}

if (spectator)
{
return eAllChatRoute.Spectator;
}

if (matchData == null)
{
return eAllChatRoute.NoMatch;
}

return eAllChatRoute.Publish;
}

public static string? GetTeamChatRelayLineupId(
MatchData matchData,
string steamId,
Expand All @@ -50,20 +75,22 @@ string playerName
return null;
}

bool gagged =
GetMemberFromLineup(matchData, steamId, playerName)?.is_gagged == true
|| matchData
.lineup_1.lineup_players.Concat(matchData.lineup_2.lineup_players)
.Any(member => member.is_gagged && member.steam_id == steamId);

if (gagged)
if (IsGagged(matchData, steamId, playerName))
{
return null;
}

return GetTeamChatLineupId(matchData, steamId, playerName);
}

public static bool IsGagged(MatchData matchData, string steamId, string playerName)
{
return GetMemberFromLineup(matchData, steamId, playerName)?.is_gagged == true
|| matchData
.lineup_1.lineup_players.Concat(matchData.lineup_2.lineup_players)
.Any(member => member.is_gagged && member.steam_id == steamId);
}

// A lineup_1 placeholder whose name prefixes a lineup_2 player would
// otherwise claim them, so exact steam ids are matched before names.
public static string? GetTeamChatLineupId(
Expand Down
174 changes: 174 additions & 0 deletions apps/counterstrikesharp/test/ChatRelayTests.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
using FiveStack.Entities;
using FiveStack.Enums;
using FiveStack.Utilities;
using Xunit;

public class ChatRelayTests
{
private const string Gagged = "76561198000000001";
private const string Free = "76561198000000002";

private static MatchData BuildMatch()
{
return new MatchData
{
lineup_1 = new MatchLineUp
{
lineup_players = new List<MatchMember>
{
new MatchMember { steam_id = Gagged, name = "Sub", is_gagged = true },
},
},
lineup_2 = new MatchLineUp
{
lineup_players = new List<MatchMember>
{
new MatchMember { steam_id = Free, name = "Free" },
},
},
};
}

private static string[] Args(string line, params string[] rest)
{
return [line, .. rest];
}

[Fact]
public void ParseWebChat_NameStartingWithOrganizerTagIsNotAnOrganizer()
{
string[][] cases = [Args("[organizer] Mallory: gg"), Args("[organizer] Mallory: gg", "0")];

foreach (string[] args in cases)
{
(string text, bool organizer) = ChatUtility.ParseWebChat(args);

Assert.False(organizer);
Assert.Equal("[organizer] Mallory: gg", text);
}
}

[Fact]
public void ParseWebChat_TokensSmuggledOutOfTheLineAreNotTheFlag()
{
Assert.False(ChatUtility.ParseWebChat(Args("Mallory: gg", "1", "", "0")).Organizer);
Assert.False(ChatUtility.ParseWebChat(Args("Mallory: gg", "1 0")).Organizer);
}

[Theory]
[InlineData("Alice: gg")]
[InlineData("[organizer] Alice: gg")]
public void ParseWebChat_OrganizerComesFromTheFlag(string line)
{
(string text, bool organizer) = ChatUtility.ParseWebChat(Args(line, "1"));

Assert.True(organizer);
Assert.Equal("Alice: gg", text);
}

[Theory]
[InlineData("Bob: [red]gg", "Bob: gg")]
[InlineData("Bob: [RED]g[Lime]g", "Bob: gg")]
[InlineData("[teamcolor]Bob: gg", "Bob: gg")]
[InlineData("Bob: gg[newline]Console: match cancelled", "Bob: ggConsole: match cancelled")]
[InlineData("Bob: [re[red]d]gg", "Bob: gg")]
[InlineData("Bob: [r\u0001ed]gg", "Bob: gg")]
[InlineData("Bob: \u0007g\u0002g\u0010", "Bob: gg")]
[InlineData("Bob: [AWP] {red} [organizer] gg", "Bob: [AWP] {red} [organizer] gg")]
public void ParseWebChat_StripsChatFormatting(string line, string expected)
{
Assert.Equal(expected, ChatUtility.ParseWebChat(Args(line)).Text);
}

[Theory]
[InlineData("default")]
[InlineData("/")]
[InlineData("white")]
[InlineData("darkred")]
[InlineData("lightpurple")]
[InlineData("green")]
[InlineData("olive")]
[InlineData("lime")]
[InlineData("red")]
[InlineData("gray")]
[InlineData("grey")]
[InlineData("lightyellow")]
[InlineData("yellow")]
[InlineData("silver")]
[InlineData("bluegrey")]
[InlineData("lightblue")]
[InlineData("blue")]
[InlineData("darkblue")]
[InlineData("purple")]
[InlineData("magenta")]
[InlineData("lightred")]
[InlineData("gold")]
[InlineData("orange")]
[InlineData("teamcolor")]
[InlineData("newline")]
public void StripFormatting_RemovesEverySwiftlyTag(string tag)
{
string upper = tag.ToUpperInvariant();

Assert.Equal("Bob: gg", ChatUtility.StripFormatting($"Bob: [{tag}]g[{upper}]g"));
}

[Fact]
public void AllChatRoute_GaggedSpectatorIsBlocked()
{
Assert.Equal(
eAllChatRoute.Block,
MatchUtility.AllChatRoute(BuildMatch(), Gagged, "Sub", spectator: true)
);
}

[Fact]
public void AllChatRoute_GaggedPlayerIsBlocked()
{
Assert.Equal(
eAllChatRoute.Block,
MatchUtility.AllChatRoute(BuildMatch(), Gagged, "Sub", spectator: false)
);
}

[Fact]
public void AllChatRoute_PlaceholderPrefixDoesNotHideAGag()
{
MatchData match = BuildMatch();
match.lineup_1.lineup_players.Insert(
0,
new MatchMember { steam_id = null, placeholder_name = "Subway" }
);

Assert.Equal(
eAllChatRoute.Block,
MatchUtility.AllChatRoute(match, Gagged, "Sub", spectator: false)
);
}

[Fact]
public void AllChatRoute_SpectatorIsRelayed()
{
Assert.Equal(
eAllChatRoute.Spectator,
MatchUtility.AllChatRoute(BuildMatch(), Free, "Free", spectator: true)
);
Assert.Equal(
eAllChatRoute.Spectator,
MatchUtility.AllChatRoute(null, Free, "Free", spectator: true)
);
}

[Fact]
public void AllChatRoute_PlayerIsPublished()
{
Assert.Equal(
eAllChatRoute.Publish,
MatchUtility.AllChatRoute(BuildMatch(), Free, "Free", spectator: false)
);
Assert.Equal(
eAllChatRoute.NoMatch,
MatchUtility.AllChatRoute(null, Free, "Free", spectator: false)
);
}
}
Loading
Loading