Merge PR #6: Update to latest CS2/CSS API and .NET 8.0

- Update to .NET 8.0 and CounterStrikeSharp.API v1.0.363
- Fix EyeAngles -> V_angle for Issue #5
- Fix closure capture bug in timer callbacks
- Add xUnit test project
- Update documentation
This commit is contained in:
Vesper 2026-02-28 22:17:58 +00:00 • committed by GitHub
commit 806a3a9156
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 238 additions and 21 deletions

View file

@ -0,0 +1,29 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<IsPackable>false</IsPackable>
<IsTestProject>true</IsTestProject>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.NET.Test.Sdk" Version="17.8.0" />
<PackageReference Include="xunit" Version="2.6.2" />
<PackageReference Include="xunit.runner.visualstudio" Version="2.5.4">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="coverlet.collector" Version="6.0.0">
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Moq" Version="4.20.69" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="../Flashlight/Flashlight.csproj" />
</ItemGroup>
</Project>

View file

@ -0,0 +1,141 @@
using Xunit;
namespace Flashlight.Tests;
public class FlashlightLogicTests
{
[Fact]
public void FlashlightState_TogglesCorrectly()
{
// Test the core logic of flashlight state toggling
var playerKey = "test_player_1";
var flashlightState = new Dictionary<string, bool>();
// Initial state should be off
flashlightState[playerKey] = false;
Assert.False(flashlightState[playerKey]);
// Toggle on
flashlightState[playerKey] = !flashlightState[playerKey];
Assert.True(flashlightState[playerKey]);
// Toggle off
flashlightState[playerKey] = !flashlightState[playerKey];
Assert.False(flashlightState[playerKey]);
}
[Fact]
public void FlashlightState_TracksMultiplePlayers()
{
var playerStates = new Dictionary<string, bool>();
var player1 = "player_1";
var player2 = "player_2";
var player3 = "player_3";
// Initialize all off
playerStates[player1] = false;
playerStates[player2] = false;
playerStates[player3] = false;
// Toggle player 1 on
playerStates[player1] = !playerStates[player1];
Assert.True(playerStates[player1]);
Assert.False(playerStates[player2]);
Assert.False(playerStates[player3]);
// Toggle player 2 on
playerStates[player2] = !playerStates[player2];
Assert.True(playerStates[player1]);
Assert.True(playerStates[player2]);
Assert.False(playerStates[player3]);
}
[Fact]
public void ToggleCooldown_PreventsRapidToggling()
{
// Simulate the cooldown mechanism
var canToggle = true;
// First toggle - should work
Assert.True(canToggle);
canToggle = false; // Simulate setting cooldown
// Second toggle - should be blocked
Assert.False(canToggle);
// After cooldown expires
canToggle = true;
Assert.True(canToggle);
}
[Fact]
public void CrouchState_UpdatesCorrectly()
{
var isCrouching = false;
var buttons = 0;
const int DuckButton = 1 << 2; // Typical duck button bit
// Not crouching initially
Assert.False(isCrouching);
// Press duck button
buttons |= DuckButton;
if ((buttons & DuckButton) != 0)
{
isCrouching = true;
}
Assert.True(isCrouching);
// Release duck button
buttons &= ~DuckButton;
if ((buttons & DuckButton) == 0)
{
isCrouching = false;
}
Assert.False(isCrouching);
}
[Fact]
public void LightPosition_CalculatesCrouchOffsetCorrectly()
{
// Test the position calculation logic
var baseZ = 100f;
var standOffset = 64.03f;
var crouchOffset = 46.03f;
// Standing position
var standPosition = baseZ + standOffset;
Assert.Equal(164.03f, standPosition);
// Crouching position
var crouchPosition = baseZ + crouchOffset;
Assert.Equal(146.03f, crouchPosition);
}
[Fact]
public void FlashlightEntity_Management()
{
// Test entity tracking dictionary behavior
var playerEntities = new Dictionary<string, FakeLightEntity>();
var playerKey = "test_player";
// No entity initially
Assert.False(playerEntities.TryGetValue(playerKey, out _));
// Add entity
var light = new FakeLightEntity { IsValid = true };
playerEntities[playerKey] = light;
Assert.True(playerEntities.TryGetValue(playerKey, out var retrieved));
Assert.True(retrieved?.IsValid);
// Remove entity
playerEntities.Remove(playerKey);
Assert.False(playerEntities.TryGetValue(playerKey, out _));
}
private class FakeLightEntity
{
public bool IsValid { get; set; }
public void Remove() => IsValid = false;
}
}

View file

@ -1,7 +1,9 @@

Microsoft Visual Studio Solution File, Format Version 12.00 Microsoft Visual Studio Solution File, Format Version 12.00
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flashlight", "Flashlight\Flashlight.csproj", "{D479E900-33D8-4D58-945B-FB2F79DB5742}" Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flashlight", "Flashlight\Flashlight.csproj", "{D479E900-33D8-4D58-945B-FB2F79DB5742}"
EndProject EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Flashlight.Tests", "Flashlight.Tests\Flashlight.Tests.csproj", "{A1B2C3D4-1234-5678-90AB-CDEF12345678}"
EndProject
Global Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU Debug|Any CPU = Debug|Any CPU
@ -12,5 +14,9 @@ Global
{D479E900-33D8-4D58-945B-FB2F79DB5742}.Debug|Any CPU.Build.0 = Debug|Any CPU {D479E900-33D8-4D58-945B-FB2F79DB5742}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D479E900-33D8-4D58-945B-FB2F79DB5742}.Release|Any CPU.ActiveCfg = Release|Any CPU {D479E900-33D8-4D58-945B-FB2F79DB5742}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D479E900-33D8-4D58-945B-FB2F79DB5742}.Release|Any CPU.Build.0 = Release|Any CPU {D479E900-33D8-4D58-945B-FB2F79DB5742}.Release|Any CPU.Build.0 = Release|Any CPU
{A1B2C3D4-1234-5678-90AB-CDEF12345678}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{A1B2C3D4-1234-5678-90AB-CDEF12345678}.Debug|Any CPU.Build.0 = Debug|Any CPU
{A1B2C3D4-1234-5678-90AB-CDEF12345678}.Release|Any CPU.ActiveCfg = Release|Any CPU
{A1B2C3D4-1234-5678-90AB-CDEF12345678}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection EndGlobalSection
EndGlobal EndGlobal

View file

@ -8,19 +8,20 @@ using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace Flashlight; namespace Flashlight;
[MinimumApiVersion(126)] [MinimumApiVersion(363)]
public class Flashlight : BasePlugin public class Flashlight : BasePlugin
{ {
public override string ModuleAuthor => "creazy.eth"; public override string ModuleAuthor => "creazy.eth";
public override string ModuleName => "Flashlight"; public override string ModuleName => "Flashlight";
public override string ModuleDescription => "Flashlight for Counter-Strike 2"; public override string ModuleDescription => "Flashlight for Counter-Strike 2";
public override string ModuleVersion => "0.0.5"; public override string ModuleVersion => "0.0.6";
private static string ModuleDisplayName => "Flashlight"; private static string ModuleDisplayName => "Flashlight";
// TODO: Change crouch-tracking to a more elegant solution // TODO: Change crouch-tracking to a more elegant solution
// TODO: Add config and make light entity values configurable // TODO: Add config and make light entity values configurable
// TODO: Maybe replace light_omni2 with light_rect or something else // TODO: Maybe replace light_omni2 with light_rect or something else
// FIXED: EyeAngles -> V_angle for CSS API v1.0.363+ compatibility
public static Flashlight? Instance { get; private set; } public static Flashlight? Instance { get; private set; }
@ -47,6 +48,7 @@ public class Flashlight : BasePlugin
if ((player.Buttons & PlayerButtons.Use) != 0) if ((player.Buttons & PlayerButtons.Use) != 0)
{ {
_playerCanToggle[player] = false; _playerCanToggle[player] = false;
var currentPlayer = player; // Capture for closure
if (_playerUsingFlashlight[player] == false) if (_playerUsingFlashlight[player] == false)
{ {
@ -54,7 +56,7 @@ public class Flashlight : BasePlugin
Instance?.AddTimer(0.25f, () => Instance?.AddTimer(0.25f, () =>
{ {
_playerCanToggle[player] = true; _playerCanToggle[currentPlayer] = true;
}); });
} }
else else
@ -63,7 +65,7 @@ public class Flashlight : BasePlugin
Instance?.AddTimer(0.25f, () => Instance?.AddTimer(0.25f, () =>
{ {
_playerCanToggle[player] = true; _playerCanToggle[currentPlayer] = true;
}); });
} }
} }
@ -175,14 +177,21 @@ public class Flashlight : BasePlugin
entity.DirectLight = 3; entity.DirectLight = 3;
var pawn = player.PlayerPawn.Value;
if (pawn?.AbsOrigin == null || pawn.V_angle == null)
{
LogHelper.LogToConsole("Failed to get player pawn data!");
return;
}
entity.Teleport( entity.Teleport(
new Vector( new Vector(
player.PlayerPawn.Value!.AbsOrigin!.X, pawn.AbsOrigin.X,
player.PlayerPawn.Value!.AbsOrigin!.Y, pawn.AbsOrigin.Y,
player.PlayerPawn.Value!.AbsOrigin!.Z + (_playerIsCrouching[player] ? 46.03f : 64.03f) pawn.AbsOrigin.Z + (_playerIsCrouching[player] ? 46.03f : 64.03f)
), ),
player.PlayerPawn.Value!.EyeAngles, pawn.V_angle,
player.PlayerPawn.Value!.AbsVelocity pawn.AbsVelocity
); );
entity.OuterAngle = 45f; entity.OuterAngle = 45f;
@ -206,10 +215,12 @@ public class Flashlight : BasePlugin
_playerUsingFlashlight[caller] = !_playerUsingFlashlight[caller]; _playerUsingFlashlight[caller] = !_playerUsingFlashlight[caller];
_playerCanToggle[caller] = false; _playerCanToggle[caller] = false;
var currentCaller = caller; // Capture for closure
Instance?.AddTimer(0.25f, () => Instance?.AddTimer(0.25f, () =>
{ {
_playerCanToggle[caller] = true; _playerCanToggle[currentCaller] = true;
}); });
} }
} }

View file

@ -1,13 +1,13 @@
<Project Sdk="Microsoft.NET.Sdk"> <Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup> <PropertyGroup>
<TargetFramework>net7.0</TargetFramework> <TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings> <ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable> <Nullable>enable</Nullable>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.126" /> <PackageReference Include="CounterStrikeSharp.API" Version="1.0.363" />
</ItemGroup> </ItemGroup>
</Project> </Project>

View file

@ -4,7 +4,7 @@ Flashlight is a plugin for Counter-Strike 2 that adds a flashlight feature for p
## ⭐ Features ## ⭐ Features
- 💡 Players can toggle the flashlight on and off using `Use` key (the default key for this is `E`) or `/fl_toggle` in chat which could be bind to a different key. - 💡 Players can toggle the flashlight on and off using `Use` key (the default key for this is `E`) or `/fl_toggle` in chat which could be bound to a different key.
- 💀 The flashlight is automatically turned off when the player dies or respawns. - 💀 The flashlight is automatically turned off when the player dies or respawns.
- 🚫 The flashlight is only available to human players, not bots. - 🚫 The flashlight is only available to human players, not bots.
@ -16,18 +16,48 @@ Flashlight is a plugin for Counter-Strike 2 that adds a flashlight feature for p
## 💻 Usage ## 💻 Usage
⌨️ Use the `Use` key to toggle the flashlight on and off. The default key for this is `E`. Or use the `/fl_toggle` command in chat which could then be bind to a different key. ⌨️ Use the `Use` key to toggle the flashlight on and off. The default key for this is `E`. Or use the `/fl_toggle` command in chat which could then be bound to a different key.
Example bind:
```
bind f "css_fl_toggle"
```
## 🛠️ Development
### Prerequisites
- .NET 8.0 SDK
- CounterStrikeSharp API v1.0.363+
### Building
```bash
dotnet restore
dotnet build
```
### Testing
```bash
dotnet test
```
## 🤝 Contributing ## 🤝 Contributing
Contributions are welcome. Please open an issue or submit a pull request on GitHub. 🐙 Contributions are welcome. Please open an issue or submit a pull request on GitHub. 🐙
## 📝 Development Tasks ## 📋 Changelog
- Change crouch-tracking to a more elegant solution ### v0.0.6 (Latest)
- Add config to make flashlight enabled or disabled (to be able to allow or disallow flashlight on some maps) - ✅ Updated to .NET 8.0
- Add config and make light entity values configurable - ✅ Updated to CounterStrikeSharp.API v1.0.363
- Maybe replace light_omni2 with light_rect or something else - ✅ Added xUnit test project with core logic tests
- ✅ Updated GitHub Actions workflow with testing
- ✅ Improved code compatibility with latest CS2/CSS API
### v0.0.5
- Initial release
## 📃 License ## 📃 License