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
1 change: 1 addition & 0 deletions .github/workflows/ci.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ jobs:
# legacy releases are bare v0.0.N; matching them keeps the counter from resetting
version_tag_regex: '^(?:css-)?v0\.0\.(\d+)$'
channel: ${{ needs.changes.outputs.channel }}
run_tests: true
secrets: inherit

swiftly:
Expand Down
26 changes: 26 additions & 0 deletions .github/workflows/test.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
name: Tests

on:
pull_request:
push:
branches:
- "main"
- "beta"

jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
plugin: [counterstrikesharp, swiftly]
steps:
- uses: actions/checkout@v4

- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: "10.0.x"

- name: Test
run: dotnet test apps/${{ matrix.plugin }}/test/FiveStack.Tests.csproj -c Release
18 changes: 18 additions & 0 deletions FiveStack.sln
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{DBE142A0-2
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FiveStack.Tests", "apps\swiftly\test\FiveStack.Tests.csproj", "{AFA38A0E-6497-4822-9FD7-43C96FAE658C}"
EndProject
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "test", "test", "{F7607F84-A084-CCDF-99B5-B00F006F6845}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FiveStack.Tests", "apps\counterstrikesharp\test\FiveStack.Tests.csproj", "{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Expand Down Expand Up @@ -67,6 +71,18 @@ Global
{AFA38A0E-6497-4822-9FD7-43C96FAE658C}.Release|x64.Build.0 = Release|Any CPU
{AFA38A0E-6497-4822-9FD7-43C96FAE658C}.Release|x86.ActiveCfg = Release|Any CPU
{AFA38A0E-6497-4822-9FD7-43C96FAE658C}.Release|x86.Build.0 = Release|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Debug|x64.ActiveCfg = Debug|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Debug|x64.Build.0 = Debug|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Debug|x86.ActiveCfg = Debug|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Debug|x86.Build.0 = Debug|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Release|Any CPU.Build.0 = Release|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Release|x64.ActiveCfg = Release|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Release|x64.Build.0 = Release|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Release|x86.ActiveCfg = Release|Any CPU
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
Expand All @@ -80,5 +96,7 @@ Global
{F8A6B88F-3606-414B-AC30-63F9FD2AE5B7} = {0CC2E62D-FAFA-6EC9-F250-DE74EC537044}
{DBE142A0-22E2-B003-5EB2-D7DEEB405156} = {3AE66ABB-E1E0-F8CE-025B-3DE40F5A8E41}
{AFA38A0E-6497-4822-9FD7-43C96FAE658C} = {DBE142A0-22E2-B003-5EB2-D7DEEB405156}
{F7607F84-A084-CCDF-99B5-B00F006F6845} = {7D0EF178-B40A-8254-92EC-BBDDDD1B716A}
{8A2C004D-559D-48A1-B6C5-DCDF09E5B194} = {F7607F84-A084-CCDF-99B5-B00F006F6845}
EndGlobalSection
EndGlobal
6 changes: 5 additions & 1 deletion apps/counterstrikesharp/scripts/dev.sh
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,12 @@ trap kill_dotnet_watch EXIT

directory_to_watch="/opt/5stack/apps/counterstrikesharp/src/bin/Debug/net10.0"

# css and sw dev builds share the dev volume; each uses its own subfolder
DEV_DIR="/opt/dev/css"
mkdir -p "$DEV_DIR"

while true; do
rm -f "$directory_to_watch/CounterStrikeSharp.API.dll"
inotifywait -r -e modify,create,delete,move "$directory_to_watch"
cp -r "$directory_to_watch"/* "/opt/dev"
cp -r "$directory_to_watch"/* "$DEV_DIR"
done
4 changes: 3 additions & 1 deletion apps/counterstrikesharp/scripts/setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,9 @@ create_symlinks "$BASE_SERVER_DIR" "$INSTANCE_SERVER_DIR"
if $INSTALL_5STACK_PLUGIN = true ; then
echo "---Install 5Stack---"
if [ "${DEV_SWAPPED}" == "1" ]; then
ln -s "/opt/dev" "${INSTANCE_SERVER_DIR}/game/csgo/addons/counterstrikesharp/plugins/FiveStack"
# css and sw dev builds share the dev volume; each uses its own subfolder
mkdir -p "/opt/dev/css"
ln -s "/opt/dev/css" "${INSTANCE_SERVER_DIR}/game/csgo/addons/counterstrikesharp/plugins/FiveStack"
else
ln -s "/opt/mod" "${INSTANCE_SERVER_DIR}/game/csgo/addons/counterstrikesharp/plugins/FiveStack"
fi
Expand Down
29 changes: 13 additions & 16 deletions apps/counterstrikesharp/src/FiveStack.Events/PlayerDamage.cs
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,10 @@ namespace FiveStack;

public partial class FiveStackPlugin
{
// Victim health before their next hit, per round (cleared on round start).
// Lets us report exact applied damage instead of raw overkill damage.
private readonly Dictionary<ulong, int> _victimHealth = new();

[GameEventHandler]
public HookResult OnPlayerDamage(EventPlayerHurt @event, GameEventInfo info)
{
Expand Down Expand Up @@ -36,17 +40,13 @@ public HookResult OnPlayerDamage(EventPlayerHurt @event, GameEventInfo info)
}

var attackerLocation = attacker?.PlayerPawn.Value?.AbsOrigin;
var attackedLocation = attacked?.PlayerPawn?.Value?.AbsOrigin;

var damageDealt = @event.DmgHealth;
var attackedLocation = attacked.PlayerPawn.Value?.AbsOrigin;

if (attacked != null)
{
if (attacked.PlayerPawn.Value.Health < 0)
{
damageDealt = damageDealt + attacked.PlayerPawn.Value.Health;
}
}
int priorHealth = _victimHealth.TryGetValue(attacked.SteamID, out int tracked)
? tracked
: 100;
var damageDealt = Math.Min(@event.DmgHealth, priorHealth);
_victimHealth[attacked.SteamID] = @event.Health;

_matchEvents.PublishGameEvent(
"damage",
Expand All @@ -73,12 +73,9 @@ public HookResult OnPlayerDamage(EventPlayerHurt @event, GameEventInfo info)
{ "hitgroup", $"{DamageUtility.HitGroupToString(@event.Hitgroup)}" },
{ "health", @event.Health },
{ "armor", @event.Armor },
{ "attacked_steam_id", attacked != null ? attacked.SteamID.ToString() : "" },
{
"attacked_team",
attacked != null ? $"{TeamUtility.TeamNumToString(attacked.TeamNum)}" : ""
},
{ "attacked_location", $"{attacked?.PlayerPawn.Value.LastPlaceName}" },
{ "attacked_steam_id", attacked.SteamID.ToString() },
{ "attacked_team", $"{TeamUtility.TeamNumToString(attacked.TeamNum)}" },
{ "attacked_location", $"{attacked.PlayerPawn.Value?.LastPlaceName}" },
{
"attacked_location_coordinates",
attackedLocation != null
Expand Down
2 changes: 2 additions & 0 deletions apps/counterstrikesharp/src/FiveStack.Events/RoundStart.cs
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ public partial class FiveStackPlugin
[GameEventHandler]
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
{
_victimHealth.Clear();

MatchManager? matchManager = _matchService.GetCurrentMatch();
if (matchManager == null)
{
Expand Down
31 changes: 18 additions & 13 deletions apps/counterstrikesharp/src/FiveStack.Services/GameDemos.cs
Original file line number Diff line number Diff line change
Expand Up @@ -171,6 +171,7 @@ public async Task UploadDemo(string filePath)
return;
}

// dedicated client: the S3 PUT needs an unbounded timeout
using var httpClient = new HttpClient();
httpClient.Timeout = System.Threading.Timeout.InfiniteTimeSpan;

Expand Down Expand Up @@ -207,12 +208,16 @@ public async Task UploadDemo(string filePath)
size = fileInfo.Length,
};

using var notifyClient = new HttpClient();
notifyClient.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", apiPassword);
var notifyResponse = await notifyClient.PostAsJsonAsync(
notifyEndpoint,
notifyRequest
var notifyHttpRequest = new HttpRequestMessage(HttpMethod.Post, notifyEndpoint)
{
Content = JsonContent.Create(notifyRequest),
};
notifyHttpRequest.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer",
apiPassword
);
var notifyResponse = await HttpClientProvider.Client.SendAsync(
notifyHttpRequest
);

_logger.LogInformation(
Expand Down Expand Up @@ -259,19 +264,19 @@ public async Task UploadDemo(string filePath)

string endpoint = $"{_environmentService.GetDemosUrl()}/demos/{match.id}/pre-signed";

using var httpClient = new HttpClient();
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Bearer",
apiPassword
);

var requestBody = new
{
demo = Path.GetFileName(filePath),
mapId = _matchService.GetCurrentMatch()?.GetMatchData()?.current_match_map_id,
};

var response = await httpClient.PostAsJsonAsync(endpoint, requestBody);
var request = new HttpRequestMessage(HttpMethod.Post, endpoint)
{
Content = JsonContent.Create(requestBody),
};
request.Headers.Authorization = new AuthenticationHeaderValue("Bearer", apiPassword);

var response = await HttpClientProvider.Client.SendAsync(request);

_logger.LogInformation(
$"presigned url response {(int)response.StatusCode} {response.StatusCode} (match {match.id} map {match.current_match_map_id} demo {Path.GetFileName(filePath)})"
Expand Down
43 changes: 21 additions & 22 deletions apps/counterstrikesharp/src/FiveStack.Services/GameServer.cs
Original file line number Diff line number Diff line change
Expand Up @@ -115,32 +115,31 @@ public void Ping(string pluginVersion)
endpoint += $"&steamID={serverSteamID}";
}

using (HttpClient httpClient = new HttpClient())
try
{
httpClient.Timeout = TimeSpan.FromSeconds(5);
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
var request = new HttpRequestMessage(HttpMethod.Get, endpoint);
request.Headers.Authorization = new AuthenticationHeaderValue(
"Bearer",
apiPassword
);

try
{
using var cts = new CancellationTokenSource(TimeSpan.FromSeconds(5));
HttpResponseMessage response = await httpClient.GetAsync(endpoint, cts.Token);
response.EnsureSuccessStatusCode();
}
catch (TaskCanceledException ex) when (ex.InnerException is TimeoutException)
{
_logger.LogWarning("Ping request timed out after 5 seconds");
}
catch (HttpRequestException ex)
{
_logger.LogCritical($"Unable to ping: {ex.Message}");
}
catch (Exception ex)
{
_logger.LogCritical($"Unexpected error during ping: {ex.Message}");
}
HttpResponseMessage response = await HttpClientProvider.Client.SendAsync(
request,
cts.Token
);
response.EnsureSuccessStatusCode();
}
catch (OperationCanceledException)
{
_logger.LogWarning("Ping request timed out after 5 seconds");
}
catch (HttpRequestException ex)
{
_logger.LogCritical($"Unable to ping: {ex.Message}");
}
catch (Exception ex)
{
_logger.LogCritical($"Unexpected error during ping: {ex.Message}");
}
});
}
Expand Down
Loading
Loading