mirror of
https://github.com/daffyyyy/CS2-SimpleAdmin.git
synced 2026-06-17 11:37:35 +00:00
Compare commits
24 Commits
build-1.7.
...
build-1.8.
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ca55c7f1a6 | ||
|
|
d870f23b01 | ||
|
|
a7ad6fce77 | ||
|
|
eea700bfb4 | ||
|
|
91615ffc67 | ||
|
|
eb9b438315 | ||
|
|
3d23b8981b | ||
|
|
39cbfdab1e | ||
|
|
4599f08fd7 | ||
|
|
4c43f14c82 | ||
|
|
f16f8cf1a5 | ||
|
|
193685826c | ||
|
|
fe73fa9917 | ||
|
|
bdada2df1e | ||
|
|
310a43fcd9 | ||
|
|
58243e813a | ||
|
|
d53446e0fe | ||
|
|
2404c1bc03 | ||
|
|
665962565e | ||
|
|
c2e8b4a898 | ||
|
|
9723a4faee | ||
|
|
4865b76262 | ||
|
|
0dded66e5d | ||
|
|
038641dbdf |
23
.github/workflows/build.yml
vendored
23
.github/workflows/build.yml
vendored
@@ -35,7 +35,7 @@ jobs:
|
|||||||
- name: Setup .NET
|
- name: Setup .NET
|
||||||
uses: actions/setup-dotnet@v4
|
uses: actions/setup-dotnet@v4
|
||||||
with:
|
with:
|
||||||
dotnet-version: 8.0.x
|
dotnet-version: 10.0.x
|
||||||
|
|
||||||
- name: Get Version
|
- name: Get Version
|
||||||
id: get_version
|
id: get_version
|
||||||
@@ -67,28 +67,12 @@ jobs:
|
|||||||
- name: Zip Main Build Output
|
- name: Zip Main Build Output
|
||||||
run: zip -r CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }}
|
run: zip -r CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip ${{ env.OUTPUT_PATH }}
|
||||||
|
|
||||||
- name: Extract & Zip StatusBlocker Linux
|
|
||||||
run: |
|
|
||||||
mkdir -p statusblocker-linux &&
|
|
||||||
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-linux.tar.gz -C statusblocker-linux &&
|
|
||||||
cd statusblocker-linux &&
|
|
||||||
zip -r ../StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip ./*
|
|
||||||
|
|
||||||
- name: Extract & Zip StatusBlocker Windows
|
|
||||||
run: |
|
|
||||||
mkdir -p statusblocker-windows &&
|
|
||||||
tar -xzf Modules/CS2-SimpleAdmin_StealthModule/METAMOD\ PLUGIN/StatusBlocker-v*-windows.tar.gz -C statusblocker-windows &&
|
|
||||||
cd statusblocker-windows &&
|
|
||||||
zip -r ../StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip ./*
|
|
||||||
|
|
||||||
- name: Upload all artifacts
|
- name: Upload all artifacts
|
||||||
uses: actions/upload-artifact@v4
|
uses: actions/upload-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: CS2-SimpleAdmin-Build-Artifacts
|
name: CS2-SimpleAdmin-Build-Artifacts
|
||||||
path: |
|
path: |
|
||||||
CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip
|
CS2-SimpleAdmin-${{ steps.get_version.outputs.VERSION }}.zip
|
||||||
StatusBlocker-linux-${{ steps.get_version.outputs.VERSION }}.zip
|
|
||||||
StatusBlocker-windows-${{ steps.get_version.outputs.VERSION }}.zip
|
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
needs: build
|
needs: build
|
||||||
@@ -109,8 +93,6 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
artifacts: |
|
artifacts: |
|
||||||
CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip
|
CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip
|
||||||
StatusBlocker-linux-${{ needs.build.outputs.build_version }}.zip
|
|
||||||
StatusBlocker-windows-${{ needs.build.outputs.build_version }}.zip
|
|
||||||
name: "CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}"
|
name: "CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}"
|
||||||
tag: "build-${{ needs.build.outputs.build_version }}"
|
tag: "build-${{ needs.build.outputs.build_version }}"
|
||||||
body: |
|
body: |
|
||||||
@@ -121,8 +103,5 @@ jobs:
|
|||||||
After the first launch, configure the plugin using the JSON config file at:
|
After the first launch, configure the plugin using the JSON config file at:
|
||||||
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
addons/counterstrikesharp/configs/plugins/CS2-SimpleAdmin/CS2-SimpleAdmin.json
|
||||||
|
|
||||||
- StatusBlocker:
|
|
||||||
Place the plugin files directly into the addons directory.
|
|
||||||
This plugin is a Metamod module for the StealthModule and does not require a subfolder.
|
|
||||||
|
|
||||||
Remember to restart or reload your game server after installing and configuring the plugins.
|
Remember to restart or reload your game server after installing and configuring the plugins.
|
||||||
|
|||||||
2
.gitignore
vendored
2
.gitignore
vendored
@@ -10,3 +10,5 @@ Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSetti
|
|||||||
CS2-SimpleAdmin_BanSoundModule — kopia
|
CS2-SimpleAdmin_BanSoundModule — kopia
|
||||||
*.user
|
*.user
|
||||||
CLAUDE.md
|
CLAUDE.md
|
||||||
|
/Modules/CS2-SimpleAdmin_BanSoundModule
|
||||||
|
/Modules/CS2-SimpleAdmin_StealthModule/METAMOD PLUGIN
|
||||||
|
|||||||
@@ -14,20 +14,19 @@ using MySqlConnector;
|
|||||||
|
|
||||||
namespace CS2_SimpleAdmin;
|
namespace CS2_SimpleAdmin;
|
||||||
|
|
||||||
[MinimumApiVersion(300)]
|
[MinimumApiVersion(369)]
|
||||||
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
|
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
|
||||||
{
|
{
|
||||||
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
|
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
|
||||||
|
|
||||||
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
|
||||||
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
|
||||||
public override string ModuleAuthor => "daffyy & Dliix66";
|
public override string ModuleAuthor => "daffyy";
|
||||||
public override string ModuleVersion => "1.7.8-beta-4";
|
public override string ModuleVersion => "1.8.1a";
|
||||||
|
|
||||||
public override void Load(bool hotReload)
|
public override void Load(bool hotReload)
|
||||||
{
|
{
|
||||||
Instance = this;
|
Instance = this;
|
||||||
|
|
||||||
if (hotReload)
|
if (hotReload)
|
||||||
{
|
{
|
||||||
ServerLoaded = false;
|
ServerLoaded = false;
|
||||||
@@ -47,7 +46,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
CachedPlayers.Clear();
|
CachedPlayers.Clear();
|
||||||
BotPlayers.Clear();
|
BotPlayers.Clear();
|
||||||
|
|
||||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
|
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && p is { Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToArray())
|
||||||
{
|
{
|
||||||
if (!player.IsBot)
|
if (!player.IsBot)
|
||||||
PlayerManager.LoadPlayerData(player, true);
|
PlayerManager.LoadPlayerData(player, true);
|
||||||
@@ -56,14 +55,12 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
};
|
};
|
||||||
});
|
});
|
||||||
|
|
||||||
PlayersTimer?.Kill();
|
|
||||||
PlayersTimer = null;
|
|
||||||
}
|
}
|
||||||
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));
|
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));
|
||||||
|
|
||||||
SimpleAdminApi = new Api.CS2_SimpleAdminApi();
|
SimpleAdminApi = new Api.CS2_SimpleAdminApi();
|
||||||
Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi);
|
Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi);
|
||||||
|
|
||||||
PlayersTimer?.Kill();
|
PlayersTimer?.Kill();
|
||||||
PlayersTimer = null;
|
PlayersTimer = null;
|
||||||
PlayerManager.CheckPlayersTimer();
|
PlayerManager.CheckPlayersTimer();
|
||||||
@@ -84,9 +81,9 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
Unload(false);
|
Unload(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
AddTimer(6.0f, () => ReloadAdmins(null));
|
|
||||||
RegisterEvents();
|
RegisterEvents();
|
||||||
AddTimer(0.5f, RegisterCommands.InitializeCommands);
|
AddTimer(0.5f, RegisterCommands.InitializeCommands);
|
||||||
|
AddTimer(3.0f, () => ReloadAdmins(null));
|
||||||
|
|
||||||
if (!CoreConfig.UnlockConCommands)
|
if (!CoreConfig.UnlockConCommands)
|
||||||
{
|
{
|
||||||
@@ -96,20 +93,11 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
$"to rejoin the server for 60 seconds. " +
|
$"to rejoin the server for 60 seconds. " +
|
||||||
$"To enable instant banning, set 'UnlockConCommands': true"
|
$"To enable instant banning, set 'UnlockConCommands': true"
|
||||||
);
|
);
|
||||||
_logger?.LogError(
|
|
||||||
$"⚠️ Warning: 'UnlockConCommands' is disabled in core.json. " +
|
|
||||||
$"Players will not be automatically banned when kicked and will be able " +
|
|
||||||
$"to rejoin the server for 60 seconds. " +
|
|
||||||
$"To enable instant banning, set 'UnlockConCommands': true"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public void OnConfigParsed(CS2_SimpleAdminConfig config)
|
public void OnConfigParsed(CS2_SimpleAdminConfig config)
|
||||||
{
|
{
|
||||||
if (System.Diagnostics.Debugger.IsAttached)
|
|
||||||
Environment.FailFast(":(!");
|
|
||||||
|
|
||||||
Helper.UpdateConfig(config);
|
Helper.UpdateConfig(config);
|
||||||
|
|
||||||
_logger = Logger;
|
_logger = Logger;
|
||||||
@@ -165,8 +153,11 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (missing)
|
if (missing)
|
||||||
Server.ExecuteCommand($"css_plugins unload {ModuleName}");
|
{
|
||||||
|
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
Instance = this;
|
Instance = this;
|
||||||
|
|
||||||
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))
|
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))
|
||||||
@@ -261,6 +252,7 @@ public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdmin
|
|||||||
CacheManager = null;
|
CacheManager = null;
|
||||||
PlayersTimer?.Kill();
|
PlayersTimer?.Kill();
|
||||||
PlayersTimer = null;
|
PlayersTimer = null;
|
||||||
|
|
||||||
UnregisterEvents();
|
UnregisterEvents();
|
||||||
|
|
||||||
if (hotReload)
|
if (hotReload)
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<RootNamespace>CS2_SimpleAdmin</RootNamespace>
|
<RootNamespace>CS2_SimpleAdmin</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
@@ -19,16 +19,16 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
|
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.369">
|
||||||
<PrivateAssets>none</PrivateAssets>
|
<PrivateAssets>none</PrivateAssets>
|
||||||
<ExcludeAssets>runtime</ExcludeAssets>
|
<ExcludeAssets>runtime</ExcludeAssets>
|
||||||
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
<PackageReference Include="Dapper" Version="2.1.66" />
|
<PackageReference Include="Dapper" Version="2.1.79" />
|
||||||
<PackageReference Include="MySqlConnector" Version="2.4.0" />
|
<PackageReference Include="MySqlConnector" Version="2.6.0" />
|
||||||
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
|
||||||
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
|
<PackageReference Include="System.Linq.Async" Version="7.0.1" />
|
||||||
<PackageReference Include="ZLinq" Version="1.5.2" />
|
<PackageReference Include="ZLinq" Version="1.5.6" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
@@ -27,7 +27,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
|
|
||||||
var targets = GetTarget(command);
|
var targets = GetTarget(command);
|
||||||
if (targets == null) return;
|
if (targets == null) return;
|
||||||
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList();
|
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.Connected, IsHLTV: false }).ToList();
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
||||||
{
|
{
|
||||||
@@ -373,7 +373,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
|
|
||||||
var targets = GetTarget(command);
|
var targets = GetTarget(command);
|
||||||
if (targets == null) return;
|
if (targets == null) return;
|
||||||
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList();
|
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.Connected && !player.IsHLTV).ToList();
|
||||||
|
|
||||||
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -501,13 +501,13 @@ public partial class CS2_SimpleAdmin
|
|||||||
|
|
||||||
Task.Run(async () =>
|
Task.Run(async () =>
|
||||||
{
|
{
|
||||||
await PermissionManager.CrateGroupsJsonFile();
|
await PermissionManager.CreateGroupsJsonFile();
|
||||||
await PermissionManager.CreateAdminsJsonFile();
|
await PermissionManager.CreateAdminsJsonFile();
|
||||||
|
|
||||||
var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json");
|
var adminsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/admins.json");
|
||||||
var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json");
|
var groupsFile = await File.ReadAllTextAsync(Instance.ModuleDirectory + "/data/groups.json");
|
||||||
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
await Server.NextWorldUpdateAsync(() =>
|
||||||
{
|
{
|
||||||
AddTimer(1, () =>
|
AddTimer(1, () =>
|
||||||
{
|
{
|
||||||
@@ -521,7 +521,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
_logger?.LogInformation("Loaded admins!");
|
_logger?.LogInformation("Loaded admins!");
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
//_ = _adminManager.GiveAllGroupsFlags();
|
//_ = _adminManager.GiveAllGroupsFlags();
|
||||||
//_ = _adminManager.GiveAllFlags();
|
//_ = _adminManager.GiveAllFlags();
|
||||||
|
|||||||
@@ -44,7 +44,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
/// <param name="command">Optional command info for logging.</param>
|
/// <param name="command">Optional command info for logging.</param>
|
||||||
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
internal static void Slay(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
|
||||||
{
|
{
|
||||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected) return;
|
if (!player.IsValid || player.Connected != PlayerConnectedState.Connected) return;
|
||||||
if (!caller.CanTarget(player)) return;
|
if (!caller.CanTarget(player)) return;
|
||||||
|
|
||||||
// Set default caller name if not provided
|
// Set default caller name if not provided
|
||||||
@@ -93,7 +93,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
|
|
||||||
playersToTarget.ForEach(player =>
|
playersToTarget.ForEach(player =>
|
||||||
{
|
{
|
||||||
if (player.Connected != PlayerConnectedState.PlayerConnected)
|
if (player.Connected != PlayerConnectedState.Connected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
if (caller!.CanTarget(player))
|
if (caller!.CanTarget(player))
|
||||||
@@ -207,7 +207,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
|
internal static void ChangeTeam(CCSPlayerController? caller, CCSPlayerController player, string teamName, CsTeam teamNum, bool kill, CommandInfo? command = null)
|
||||||
{
|
{
|
||||||
// Check if the player is valid and connected
|
// Check if the player is valid and connected
|
||||||
if (!player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected)
|
if (!player.IsValid || player.Connected != PlayerConnectedState.Connected)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Ensure the caller can target the player
|
// Ensure the caller can target the player
|
||||||
@@ -284,7 +284,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
playersToTarget.ForEach(player =>
|
playersToTarget.ForEach(player =>
|
||||||
{
|
{
|
||||||
// Check if the player is connected and can be targeted
|
// Check if the player is connected and can be targeted
|
||||||
if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
|
if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Determine message key and arguments for the rename notification
|
// Determine message key and arguments for the rename notification
|
||||||
@@ -330,7 +330,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
playersToTarget.ForEach(player =>
|
playersToTarget.ForEach(player =>
|
||||||
{
|
{
|
||||||
// Check if the player is connected and can be targeted
|
// Check if the player is connected and can be targeted
|
||||||
if (player.Connected != PlayerConnectedState.PlayerConnected || !caller!.CanTarget(player))
|
if (player.Connected != PlayerConnectedState.Connected || !caller!.CanTarget(player))
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// Determine message key and arguments for the rename notification
|
// Determine message key and arguments for the rename notification
|
||||||
@@ -379,7 +379,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
destinationPlayer = targets.Players.FirstOrDefault(p =>
|
destinationPlayer = targets.Players.FirstOrDefault(p =>
|
||||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
||||||
|
|
||||||
if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
|
if (destinationPlayer == null || !caller.CanTarget(destinationPlayer) || caller.PlayerPawn.Value == null)
|
||||||
return;
|
return;
|
||||||
@@ -399,7 +399,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
playersToTeleport = targets.Players
|
playersToTeleport = targets.Players
|
||||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
if (!playersToTeleport.Any())
|
if (!playersToTeleport.Any())
|
||||||
@@ -476,7 +476,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
destinationPlayer = caller;
|
destinationPlayer = caller;
|
||||||
|
|
||||||
playersToTeleport = targets.Players
|
playersToTeleport = targets.Players
|
||||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller.CanTarget(p))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
@@ -486,7 +486,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
destinationPlayer = destination.Players.FirstOrDefault(p =>
|
||||||
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE });
|
||||||
|
|
||||||
if (destinationPlayer == null)
|
if (destinationPlayer == null)
|
||||||
return;
|
return;
|
||||||
@@ -497,7 +497,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
return;
|
return;
|
||||||
|
|
||||||
playersToTeleport = targets.Players
|
playersToTeleport = targets.Players
|
||||||
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
|
.Where(p => p is { IsValid: true, IsHLTV: false, Connected: PlayerConnectedState.Connected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE } && caller!.CanTarget(p))
|
||||||
.ToList();
|
.ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ public interface IDatabaseProvider
|
|||||||
string GetUpdateBanStatusQuery();
|
string GetUpdateBanStatusQuery();
|
||||||
string GetExpireBansQuery(bool multiServer);
|
string GetExpireBansQuery(bool multiServer);
|
||||||
string GetExpireIpBansQuery(bool multiServer);
|
string GetExpireIpBansQuery(bool multiServer);
|
||||||
|
string GetExpireOldPlayerIpsQuery();
|
||||||
|
|
||||||
// MuteManager
|
// MuteManager
|
||||||
string GetAddMuteQuery(bool includePlayerName);
|
string GetAddMuteQuery(bool includePlayerName);
|
||||||
|
|||||||
@@ -1,33 +1,33 @@
|
|||||||
-- Migration 016: Optimize tables and indexes
|
-- -- Migration 016: Optimize tables and indexes
|
||||||
-- Add proper indexes for all tables to improve query performance
|
-- -- Add proper indexes for all tables to improve query performance
|
||||||
|
|
||||||
-- Optimize sa_players_ips table indexes
|
-- -- Optimize sa_players_ips table indexes
|
||||||
-- Add index on used_at for efficient date-based queries
|
-- -- Add index on used_at for efficient date-based queries
|
||||||
ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
-- ALTER TABLE `sa_players_ips` ADD INDEX IF NOT EXISTS `idx_used_at` (`used_at` DESC);
|
||||||
|
|
||||||
-- Optimize sa_bans table indexes
|
-- -- Optimize sa_bans table indexes
|
||||||
-- Add composite indexes for common query patterns
|
-- -- Add composite indexes for common query patterns
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
-- CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
-- CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
-- CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
||||||
|
|
||||||
-- Optimize sa_admins table indexes
|
-- -- Optimize sa_admins table indexes
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
-- CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
||||||
|
|
||||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
-- -- Optimize sa_mutes table indexes (in addition to migration 014)
|
||||||
-- Add index for expire queries
|
-- -- Add index for expire queries
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
-- CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
||||||
|
|
||||||
-- Optimize sa_warns table indexes (if exists)
|
-- -- Optimize sa_warns table indexes (if exists)
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
-- CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
-- CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
||||||
|
|
||||||
-- Add index on sa_servers for faster lookups
|
-- -- Add index on sa_servers for faster lookups
|
||||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
-- CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
TRUNCATE TABLE `sa_players_ips`;
|
DELETE FROM sa_players_ips;
|
||||||
|
|
||||||
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
|
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL;
|
||||||
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);
|
CREATE INDEX IF NOT EXISTS `idx_sa_players_ips_used_at` ON `sa_players_ips` (`used_at` DESC);
|
||||||
@@ -1,33 +0,0 @@
|
|||||||
-- Migration 016: Optimize tables and indexes
|
|
||||||
-- Add proper indexes for all tables to improve query performance
|
|
||||||
|
|
||||||
-- Optimize sa_players_ips table indexes
|
|
||||||
-- Add index on used_at for efficient date-based queries
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_used_at` ON `sa_players_ips` (`used_at` DESC);
|
|
||||||
|
|
||||||
-- Optimize sa_bans table indexes
|
|
||||||
-- Add composite indexes for common query patterns
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_steamid_status` ON `sa_bans` (`player_steamid`, `status`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_ip_status` ON `sa_bans` (`player_ip`, `status`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_status_ends` ON `sa_bans` (`status`, `ends`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_server_status` ON `sa_bans` (`server_id`, `status`, `ends`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_bans_created` ON `sa_bans` (`created` DESC);
|
|
||||||
|
|
||||||
-- Optimize sa_admins table indexes
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_steamid` ON `sa_admins` (`player_steamid`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_server_ends` ON `sa_admins` (`server_id`, `ends`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_admins_ends` ON `sa_admins` (`ends`);
|
|
||||||
|
|
||||||
-- Optimize sa_mutes table indexes (in addition to migration 014)
|
|
||||||
-- Add index for expire queries
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_status_ends` ON `sa_mutes` (`status`, `ends`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_server_status` ON `sa_mutes` (`server_id`, `status`, `ends`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_mutes_created` ON `sa_mutes` (`created` DESC);
|
|
||||||
|
|
||||||
-- Optimize sa_warns table indexes (if exists)
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_steamid_status` ON `sa_warns` (`player_steamid`, `status`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_status_ends` ON `sa_warns` (`status`, `ends`);
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_warns_server_status` ON `sa_warns` (`server_id`, `status`, `ends`);
|
|
||||||
|
|
||||||
-- Add index on sa_servers for faster lookups
|
|
||||||
CREATE INDEX IF NOT EXISTS `idx_servers_hostname` ON `sa_servers` (`hostname`);
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
|||||||
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
|
||||||
await cmd.ExecuteNonQueryAsync();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
cmd.CommandText = "SET time_zone = '+00:00';";
|
// cmd.CommandText = "SET time_zone = '+00:00';";
|
||||||
await cmd.ExecuteNonQueryAsync();
|
await cmd.ExecuteNonQueryAsync();
|
||||||
|
|
||||||
return connection;
|
return connection;
|
||||||
@@ -251,6 +251,11 @@ public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
|
|||||||
? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime"
|
? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime"
|
||||||
: "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid";
|
: "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public string GetExpireOldPlayerIpsQuery()
|
||||||
|
{
|
||||||
|
return "DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime";
|
||||||
|
}
|
||||||
|
|
||||||
public string GetAddMuteQuery(bool includePlayerName) =>
|
public string GetAddMuteQuery(bool includePlayerName) =>
|
||||||
includePlayerName
|
includePlayerName
|
||||||
|
|||||||
@@ -166,6 +166,9 @@ public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider
|
|||||||
multiServer
|
multiServer
|
||||||
? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime"
|
? "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime"
|
||||||
: "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid";
|
: "UPDATE sa_bans SET player_ip = NULL WHERE status = 'ACTIVE' AND ends <= @ipBansTime AND server_id = @serverid";
|
||||||
|
|
||||||
|
public string GetExpireOldPlayerIpsQuery() =>
|
||||||
|
"DELETE FROM sa_players_ips WHERE used_at <= @ipBansTime";
|
||||||
|
|
||||||
public string GetAdminsQuery() =>
|
public string GetAdminsQuery() =>
|
||||||
"""
|
"""
|
||||||
|
|||||||
@@ -23,7 +23,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
{
|
{
|
||||||
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
RegisterListener<Listeners.OnMapStart>(OnMapStart);
|
||||||
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||||
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
|
||||||
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
|
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
|
||||||
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
|
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
|
||||||
if (Config.OtherSettings.UserMessageGagChatType)
|
if (Config.OtherSettings.UserMessageGagChatType)
|
||||||
@@ -77,7 +77,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
new ServerManager().LoadServerData();
|
new ServerManager().LoadServerData();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GameEventHandler(HookMode.Pre)]
|
[GameEventHandler]
|
||||||
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
|
||||||
{
|
{
|
||||||
if (@event.Reason is 149 or 6)
|
if (@event.Reason is 149 or 6)
|
||||||
@@ -91,14 +91,15 @@ public partial class CS2_SimpleAdmin
|
|||||||
|
|
||||||
if (player == null || !player.IsValid || player.IsHLTV)
|
if (player == null || !player.IsValid || player.IsHLTV)
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
|
||||||
BotPlayers.Remove(player);
|
|
||||||
CachedPlayers.Remove(player);
|
|
||||||
|
|
||||||
|
CachedPlayers.Remove(player);
|
||||||
|
BotPlayers.Remove(player);
|
||||||
SilentPlayers.Remove(player.Slot);
|
SilentPlayers.Remove(player.Slot);
|
||||||
|
|
||||||
if (player.IsBot)
|
if (player.IsBot)
|
||||||
|
{
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
}
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
Logger.LogCritical("[OnClientDisconnect] After Check");
|
Logger.LogCritical("[OnClientDisconnect] After Check");
|
||||||
@@ -176,6 +177,9 @@ public partial class CS2_SimpleAdmin
|
|||||||
if (player == null || !player.IsValid || player.IsBot)
|
if (player == null || !player.IsValid || player.IsBot)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
if (!CachedPlayers.Contains(player))
|
||||||
|
CachedPlayers.Add(player);
|
||||||
|
|
||||||
PlayerManager.LoadPlayerData(player);
|
PlayerManager.LoadPlayerData(player);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -255,7 +259,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
{
|
{
|
||||||
var player = Utilities.GetPlayerFromSteamId(list.Key);
|
var player = Utilities.GetPlayerFromSteamId(list.Key);
|
||||||
|
|
||||||
if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected)
|
if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.Connected)
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
if (player.PlayerName.Equals(list.Value))
|
if (player.PlayerName.Equals(list.Value))
|
||||||
@@ -324,7 +328,7 @@ public partial class CS2_SimpleAdmin
|
|||||||
? Utilities.GetPlayerFromUserid(userId)
|
? Utilities.GetPlayerFromUserid(userId)
|
||||||
: null;
|
: null;
|
||||||
|
|
||||||
if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected)
|
if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.Connected)
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
|
||||||
return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue;
|
return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue;
|
||||||
@@ -444,13 +448,13 @@ public partial class CS2_SimpleAdmin
|
|||||||
|
|
||||||
private void OnMapStart(string mapName)
|
private void OnMapStart(string mapName)
|
||||||
{
|
{
|
||||||
if (!ServerLoaded || ServerId == null)
|
|
||||||
AddTimer(2.0f, OnGameServerSteamAPIActivated);
|
|
||||||
|
|
||||||
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
|
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
|
||||||
AddTimer(5.0f, () => ReloadAdmins(null));
|
ReloadAdmins(null);
|
||||||
|
|
||||||
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
|
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
|
||||||
|
|
||||||
|
if (!ServerLoaded || ServerId == null)
|
||||||
|
AddTimer(1.5f, OnGameServerSteamAPIActivated);
|
||||||
|
|
||||||
// AddTimer(34, () =>
|
// AddTimer(34, () =>
|
||||||
// {
|
// {
|
||||||
@@ -458,35 +462,18 @@ public partial class CS2_SimpleAdmin
|
|||||||
// OnGameServerSteamAPIActivated();
|
// OnGameServerSteamAPIActivated();
|
||||||
// });
|
// });
|
||||||
|
|
||||||
GodPlayers.Clear();
|
|
||||||
SilentPlayers.Clear();
|
SilentPlayers.Clear();
|
||||||
|
|
||||||
PlayerPenaltyManager.RemoveAllPenalties();
|
PlayerPenaltyManager.RemoveAllPenalties();
|
||||||
}
|
}
|
||||||
|
|
||||||
[GameEventHandler]
|
|
||||||
public HookResult OnPlayerHurt(EventPlayerHurt @event, GameEventInfo info)
|
|
||||||
{
|
|
||||||
var player = @event.Userid;
|
|
||||||
|
|
||||||
if (player is null || @event.Attacker is null || player.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE || player.PlayerPawn.Value == null)
|
|
||||||
return HookResult.Continue;
|
|
||||||
|
|
||||||
if (!GodPlayers.Contains(player.Slot)) return HookResult.Continue;
|
|
||||||
|
|
||||||
player.PlayerPawn.Value.Health = player.PlayerPawn.Value.MaxHealth;
|
|
||||||
player.PlayerPawn.Value.ArmorValue = 100;
|
|
||||||
|
|
||||||
return HookResult.Continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
[GameEventHandler]
|
[GameEventHandler]
|
||||||
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
|
||||||
{
|
{
|
||||||
var player = @event.Userid;
|
var player = @event.Userid;
|
||||||
|
|
||||||
if (player?.UserId == null || !player.IsValid || player.IsHLTV ||
|
if (player?.UserId == null || !player.IsValid || player.IsHLTV ||
|
||||||
player.Connected != PlayerConnectedState.PlayerConnected || !PlayersInfo.ContainsKey(player.SteamID) ||
|
player.Connected != PlayerConnectedState.Connected || !PlayersInfo.ContainsKey(player.SteamID) ||
|
||||||
@event.Attacker == null)
|
@event.Attacker == null)
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
|
||||||
@@ -512,17 +499,13 @@ public partial class CS2_SimpleAdmin
|
|||||||
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
|
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
|
||||||
{
|
{
|
||||||
var player = @event.Userid;
|
var player = @event.Userid;
|
||||||
if (player == null || !player.IsValid || player.IsBot)
|
if (player == null || !player.IsValid || player.IsBot || !SilentPlayers.Contains(player.Slot))
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
|
|
||||||
if (!SilentPlayers.Contains(player.Slot))
|
if (@event is not { Oldteam: <= 1, Team: >= 1 }) return HookResult.Continue;
|
||||||
return HookResult.Continue;
|
|
||||||
|
SilentPlayers.Remove(player.Slot);
|
||||||
if (@event is { Oldteam: <= 1, Team: >= 1 })
|
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
||||||
{
|
|
||||||
SilentPlayers.Remove(player.Slot);
|
|
||||||
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
return HookResult.Continue;
|
return HookResult.Continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -11,12 +11,14 @@ using CounterStrikeSharp.API.ValveConstants.Protobuf;
|
|||||||
using CS2_SimpleAdminApi;
|
using CS2_SimpleAdminApi;
|
||||||
using Microsoft.Extensions.Localization;
|
using Microsoft.Extensions.Localization;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
|
using System.Diagnostics.CodeAnalysis;
|
||||||
using System.Reflection;
|
using System.Reflection;
|
||||||
using System.Runtime.CompilerServices;
|
using System.Runtime.CompilerServices;
|
||||||
using System.Runtime.InteropServices;
|
using System.Runtime.InteropServices;
|
||||||
using System.Runtime.Serialization;
|
using System.Runtime.Serialization;
|
||||||
using System.Text;
|
using System.Text;
|
||||||
using System.Text.Json;
|
using System.Text.Json;
|
||||||
|
using System.Text.Json.Nodes;
|
||||||
using System.Text.RegularExpressions;
|
using System.Text.RegularExpressions;
|
||||||
using CounterStrikeSharp.API.Core.Plugin.Host;
|
using CounterStrikeSharp.API.Core.Plugin.Host;
|
||||||
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
using CounterStrikeSharp.API.Modules.Entities.Constants;
|
||||||
@@ -64,7 +66,7 @@ internal static class Helper
|
|||||||
|
|
||||||
public static List<CCSPlayerController> GetValidPlayers()
|
public static List<CCSPlayerController> GetValidPlayers()
|
||||||
{
|
{
|
||||||
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().ToList();
|
return CS2_SimpleAdmin.CachedPlayers.AsValueEnumerable().Where(p => p.IsValid && p.Connected == PlayerConnectedState.Connected).ToList();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
public static List<CCSPlayerController> GetValidPlayersWithBots()
|
||||||
@@ -583,27 +585,32 @@ internal static class Helper
|
|||||||
}
|
}
|
||||||
|
|
||||||
public static void DisplayCenterMessage(
|
public static void DisplayCenterMessage(
|
||||||
CCSPlayerController player,
|
CCSPlayerController player,
|
||||||
string messageKey,
|
string messageKey,
|
||||||
string? callerName = null,
|
string? callerName = null,
|
||||||
params object[] messageArgs)
|
params object[] messageArgs)
|
||||||
{
|
{
|
||||||
if (CS2_SimpleAdmin._localizer == null) return;
|
if (CS2_SimpleAdmin._localizer == null) return;
|
||||||
|
|
||||||
|
// Determine the localized message key
|
||||||
var localizedMessageKey = $"{messageKey}";
|
var localizedMessageKey = $"{messageKey}";
|
||||||
|
|
||||||
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
|
var formattedMessageArgs = messageArgs.Select(arg => arg?.ToString() ?? string.Empty).ToArray();
|
||||||
|
|
||||||
|
// Replace placeholder based on showActivityType
|
||||||
for (var i = 0; i < formattedMessageArgs.Length; i++)
|
for (var i = 0; i < formattedMessageArgs.Length; i++)
|
||||||
{
|
{
|
||||||
var arg = formattedMessageArgs[i];
|
var arg = formattedMessageArgs[i]; // Convert argument to string if not null
|
||||||
|
// Replace "CALLER" placeholder in the argument string
|
||||||
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
formattedMessageArgs[i] = CS2_SimpleAdmin.Instance.Config.OtherSettings.ShowActivityType switch
|
||||||
{
|
{
|
||||||
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
|
1 => arg.Replace("CALLER", CS2_SimpleAdmin._localizer["sa_admin"]),
|
||||||
|
2 => arg.Replace("CALLER", callerName ?? "Console"),
|
||||||
_ => arg
|
_ => arg
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Print the localized message to the center of the screen for the player
|
||||||
using (new WithTemporaryCulture(player.GetLanguage()))
|
using (new WithTemporaryCulture(player.GetLanguage()))
|
||||||
{
|
{
|
||||||
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
|
player.PrintToCenter(CS2_SimpleAdmin._localizer[localizedMessageKey, formattedMessageArgs.Cast<object>().ToArray()]);
|
||||||
@@ -850,26 +857,34 @@ internal static class Helper
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void UpdateConfig<T>(T config) where T : BasePluginConfig, new()
|
public static void UpdateConfig(BasePluginConfig config)
|
||||||
{
|
{
|
||||||
// get newest config version
|
// get newest config version
|
||||||
var newCfgVersion = new T().Version;
|
var configType = config.GetType();
|
||||||
|
var newCfgVersion = ((BasePluginConfig)Activator.CreateInstance(configType)!).Version;
|
||||||
|
|
||||||
// loaded config is up to date
|
// loaded config is up to date
|
||||||
if (config.Version == newCfgVersion)
|
if (config.Version == newCfgVersion)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
// update the version
|
// Load existing JSON file and update version property
|
||||||
config.Version = newCfgVersion;
|
if (!File.Exists(CfgPath))
|
||||||
|
return;
|
||||||
|
|
||||||
// serialize the updated config back to json
|
var json = File.ReadAllText(CfgPath);
|
||||||
var updatedJsonContent = JsonSerializer.Serialize(config,
|
var node = JsonNode.Parse(json);
|
||||||
new JsonSerializerOptions
|
|
||||||
|
if (node != null)
|
||||||
|
{
|
||||||
|
node["Version"] = newCfgVersion;
|
||||||
|
var updatedJsonContent = node.ToJsonString(new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
Encoder = System.Text.Encodings.Web.JavaScriptEncoder.UnsafeRelaxedJsonEscaping
|
||||||
});
|
});
|
||||||
File.WriteAllText(CfgPath, updatedJsonContent);
|
|
||||||
|
File.WriteAllText(CfgPath, updatedJsonContent);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString)
|
public static void TryLogCommandOnDiscord(CCSPlayerController? caller, string commandString)
|
||||||
@@ -996,6 +1011,10 @@ public static class PluginInfo
|
|||||||
Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|");
|
Console.WriteLine("|_______||___| |_| |_||___| |_______||_______||__| |__||______| |_| |_||___| |_| |__|");
|
||||||
Console.WriteLine(" >> Version: " + moduleVersion);
|
Console.WriteLine(" >> Version: " + moduleVersion);
|
||||||
Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin");
|
Console.WriteLine(" >> GitHub: https://github.com/daffyyyy/CS2-SimpleAdmin");
|
||||||
|
var _m1 = new List<int> { 70,117,99,107,32,121,111,117,32,114,97,118,105,100,44,32,119,104,121,32,121,111,117,32,108,121,105,110,103,32,97,98,111,117,116,32,110,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,63,32,74,117,115,116,32,116,101,108,108,32,116,114,117,116,104,44,32,110,105,99,101,32,116,114,121,32,116,111,32,112,114,111,106,101,99,116,32,116,97,107,101,111,118,101,114 };
|
||||||
|
var _m2 = new List<int> { 32,32,58,68,32,40,97,108,108,32,119,104,111,32,116,114,105,101,100,32,116,111,32,99,111,110,116,97,99,116,32,119,105,116,104,32,109,101,44,32,106,117,115,116,32,119,114,105,116,101,32,109,101,115,115,97,103,101,32,116,111,32,109,101,32,120,68,41 };
|
||||||
|
Console.WriteLine(" >> " + new string(_m1.Select(c => (char)c).ToArray()));
|
||||||
|
Console.WriteLine(" >> " + new string(_m2.Select(c => (char)c).ToArray()));
|
||||||
Console.WriteLine(" ");
|
Console.WriteLine(" ");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -431,6 +431,9 @@ public async Task UnbanPlayer(string playerPattern, string adminSteamId, string
|
|||||||
var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
||||||
sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
|
||||||
await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId });
|
await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId });
|
||||||
|
|
||||||
|
sql = databaseProvider.GetExpireOldPlayerIpsQuery();
|
||||||
|
await connection.ExecuteAsync(sql, new { ipBansTime });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
catch (Exception)
|
catch (Exception)
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ using System.Collections.Concurrent;
|
|||||||
using CS2_SimpleAdmin.Database;
|
using CS2_SimpleAdmin.Database;
|
||||||
using CS2_SimpleAdmin.Models;
|
using CS2_SimpleAdmin.Models;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
|
using Microsoft.Extensions.Logging;
|
||||||
using ZLinq;
|
using ZLinq;
|
||||||
|
|
||||||
namespace CS2_SimpleAdmin.Managers;
|
namespace CS2_SimpleAdmin.Managers;
|
||||||
@@ -16,6 +17,7 @@ internal class CacheManager: IDisposable
|
|||||||
private HashSet<uint> _cachedIgnoredIps = [];
|
private HashSet<uint> _cachedIgnoredIps = [];
|
||||||
|
|
||||||
private DateTime _lastUpdateTime = DateTime.MinValue;
|
private DateTime _lastUpdateTime = DateTime.MinValue;
|
||||||
|
private DateTime? _lastDatabaseTime = null; // Track actual time from database
|
||||||
private bool _isInitialized;
|
private bool _isInitialized;
|
||||||
private bool _disposed;
|
private bool _disposed;
|
||||||
|
|
||||||
@@ -156,13 +158,20 @@ internal class CacheManager: IDisposable
|
|||||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||||
IEnumerable<BanRecord> updatedBans;
|
IEnumerable<BanRecord> updatedBans;
|
||||||
|
|
||||||
|
// Get current time from database in local timezone (CURRENT_TIMESTAMP uses session timezone, not UTC)
|
||||||
|
var currentDatabaseTime = await connection.QueryFirstAsync<DateTime>("SELECT CURRENT_TIMESTAMP");
|
||||||
|
|
||||||
// Optimization: Only get IDs for comparison if we need to check for deletions
|
// Optimization: Only get IDs for comparison if we need to check for deletions
|
||||||
// Most of the time bans are just added/updated, not deleted
|
// Most of the time bans are just added/updated, not deleted
|
||||||
HashSet<int>? allIds = null;
|
HashSet<int>? allIds = null;
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
|
||||||
{
|
{
|
||||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
// Use previous database time or start from far past if first run
|
||||||
|
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||||
|
|
||||||
|
// Get recently updated bans by timestamp (using database time to avoid timezone issues)
|
||||||
|
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||||
"""
|
"""
|
||||||
SELECT id AS Id,
|
SELECT id AS Id,
|
||||||
player_name AS PlayerName,
|
player_name AS PlayerName,
|
||||||
@@ -171,33 +180,68 @@ internal class CacheManager: IDisposable
|
|||||||
status AS Status
|
status AS Status
|
||||||
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
|
||||||
""",
|
""",
|
||||||
new { lastUpdate = _lastUpdateTime }
|
new { lastUpdate = lastCheckTime }
|
||||||
));
|
)).ToList();
|
||||||
|
|
||||||
|
// Detect changes: new bans or status changes
|
||||||
|
var updatedList = new List<BanRecord>();
|
||||||
|
foreach (var ban in updatedBans_Query)
|
||||||
|
{
|
||||||
|
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||||
|
{
|
||||||
|
// New ban
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
else if (cachedBan.Status != ban.Status)
|
||||||
|
{
|
||||||
|
// Status changed
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optimization: Only fetch all IDs if there were updates
|
|
||||||
var updatedList = updatedBans.ToList();
|
|
||||||
if (updatedList.Count > 0)
|
if (updatedList.Count > 0)
|
||||||
{
|
{
|
||||||
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
|
||||||
}
|
}
|
||||||
updatedBans = updatedList;
|
updatedBans = updatedList;
|
||||||
|
|
||||||
|
// Update last check time to current database time
|
||||||
|
_lastDatabaseTime = currentDatabaseTime;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
updatedBans = (await connection.QueryAsync<BanRecord>(
|
// Use previous database time or start from far past if first run
|
||||||
|
var lastCheckTime = _lastDatabaseTime ?? DateTime.MinValue;
|
||||||
|
|
||||||
|
// Get recently updated bans for this server by timestamp (using database time to avoid timezone issues)
|
||||||
|
var updatedBans_Query = (await connection.QueryAsync<BanRecord>(
|
||||||
"""
|
"""
|
||||||
SELECT id AS Id,
|
SELECT id AS Id,
|
||||||
player_name AS PlayerName,
|
player_name AS PlayerName,
|
||||||
player_steamid AS PlayerSteamId,
|
player_steamid AS PlayerSteamId,
|
||||||
player_ip AS PlayerIp,
|
player_ip AS PlayerIp,
|
||||||
status AS Status
|
status AS Status
|
||||||
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
|
FROM `sa_bans` WHERE server_id = @serverId AND (updated_at > @lastUpdate OR created > @lastUpdate) ORDER BY updated_at DESC
|
||||||
""",
|
""",
|
||||||
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
|
new { serverId = CS2_SimpleAdmin.ServerId, lastUpdate = lastCheckTime }
|
||||||
));
|
)).ToList();
|
||||||
|
|
||||||
|
// Detect changes: new bans or status changes
|
||||||
|
var updatedList = new List<BanRecord>();
|
||||||
|
foreach (var ban in updatedBans_Query)
|
||||||
|
{
|
||||||
|
if (!_banCache.TryGetValue(ban.Id, out var cachedBan))
|
||||||
|
{
|
||||||
|
// New ban
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
else if (cachedBan.Status != ban.Status)
|
||||||
|
{
|
||||||
|
// Status changed
|
||||||
|
updatedList.Add(ban);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Optimization: Only fetch all IDs if there were updates
|
|
||||||
var updatedList = updatedBans.ToList();
|
|
||||||
if (updatedList.Count > 0)
|
if (updatedList.Count > 0)
|
||||||
{
|
{
|
||||||
allIds = (await connection.QueryAsync<int>(
|
allIds = (await connection.QueryAsync<int>(
|
||||||
@@ -206,6 +250,9 @@ internal class CacheManager: IDisposable
|
|||||||
)).ToHashSet();
|
)).ToHashSet();
|
||||||
}
|
}
|
||||||
updatedBans = updatedList;
|
updatedBans = updatedList;
|
||||||
|
|
||||||
|
// Update last check time to current database time
|
||||||
|
_lastDatabaseTime = currentDatabaseTime;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Optimization: Only process deletions if we have the full ID list
|
// Optimization: Only process deletions if we have the full ID list
|
||||||
@@ -225,12 +272,12 @@ internal class CacheManager: IDisposable
|
|||||||
|
|
||||||
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
|
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
|
||||||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
|
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
|
||||||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
|
!_ipIndex.TryGetValue(ipUInt, out var ipBans))
|
||||||
{
|
continue;
|
||||||
ipBans.RemoveAll(b => b.Id == id);
|
|
||||||
if (ipBans.Count == 0)
|
ipBans.RemoveAll(b => b.Id == id);
|
||||||
_ipIndex.TryRemove(ipUInt, out _);
|
if (ipBans.Count == 0)
|
||||||
}
|
_ipIndex.TryRemove(ipUInt, out _);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -276,16 +323,19 @@ internal class CacheManager: IDisposable
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update cache with new/modified bans
|
// Update cache with new/modified bans
|
||||||
var hasUpdates = false;
|
var needsRebuild = false;
|
||||||
foreach (var ban in updatedBans)
|
foreach (var ban in updatedBans)
|
||||||
{
|
{
|
||||||
|
if (_banCache.TryGetValue(ban.Id, out var oldBan) && oldBan.Status != ban.Status)
|
||||||
|
{
|
||||||
|
// Ban status changed (e.g., ACTIVE -> EXPIRED/UNBANNED), need to rebuild indexes
|
||||||
|
needsRebuild = true;
|
||||||
|
}
|
||||||
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
|
||||||
hasUpdates = true;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Always rebuild indexes if there were any updates
|
// Rebuild indexes if there were updates or status changes
|
||||||
// This ensures status changes (ACTIVE -> UNBANNED) are reflected
|
if (updatedBans.Any() || needsRebuild)
|
||||||
if (hasUpdates)
|
|
||||||
{
|
{
|
||||||
RebuildIndexes();
|
RebuildIndexes();
|
||||||
}
|
}
|
||||||
@@ -436,32 +486,41 @@ internal class CacheManager: IDisposable
|
|||||||
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||||
if (record != null)
|
if (record != null)
|
||||||
{
|
{
|
||||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
// Double-check the ban is still active in cache (handle race conditions)
|
||||||
(!record.PlayerSteamId.HasValue))
|
if (_banCache.TryGetValue(record.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||||
{
|
{
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||||
|
(!record.PlayerSteamId.HasValue))
|
||||||
|
{
|
||||||
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(ipAddress) ||
|
if (string.IsNullOrEmpty(ipAddress) ||
|
||||||
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
|
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
|
||||||
_cachedIgnoredIps.Contains(ipUInt) ||
|
_cachedIgnoredIps.Contains(ipUInt) ||
|
||||||
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
|
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
|
||||||
|
|
||||||
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||||
if (record == null) return false;
|
if (record == null) return false;
|
||||||
|
|
||||||
|
// Double-check the ban is still active in cache (handle race conditions)
|
||||||
|
if (!_banCache.TryGetValue(record.Id, out var cachedBanIp) || cachedBanIp.StatusEnum != BanStatus.ACTIVE)
|
||||||
|
return false;
|
||||||
|
|
||||||
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
|
||||||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
(!record.PlayerSteamId.HasValue && steamId.HasValue))
|
||||||
{
|
{
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
}
|
}
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -547,52 +606,100 @@ internal class CacheManager: IDisposable
|
|||||||
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||||
if (activeBan != null)
|
if (activeBan != null)
|
||||||
{
|
{
|
||||||
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
|
// Double-check the ban is still active in cache (handle race conditions)
|
||||||
|
if (_banCache.TryGetValue(activeBan.Id, out var cachedBan) && cachedBan.StatusEnum == BanStatus.ACTIVE)
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
|
||||||
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 || string.IsNullOrEmpty(ipAddress))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (_cachedIgnoredIps.Contains(ipUInt))
|
||||||
|
return false;
|
||||||
|
|
||||||
|
// Direct ip ban (ban record has player_ip set)
|
||||||
|
if (_ipIndex.TryGetValue(ipUInt, out var ipBanRecords))
|
||||||
|
{
|
||||||
|
var ipBan = ipBanRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
||||||
|
if (ipBan != null && _banCache.TryGetValue(ipBan.Id, out var cachedIpBan) && cachedIpBan.StatusEnum == BanStatus.ACTIVE)
|
||||||
|
{
|
||||||
|
var expireOldIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans;
|
||||||
|
if (expireOldIpBans <= 0 || ipBan.Created >= Time.ActualDateTime().AddDays(-expireOldIpBans))
|
||||||
|
{
|
||||||
|
if (string.IsNullOrEmpty(ipBan.PlayerName))
|
||||||
|
ipBan.PlayerName = playerName;
|
||||||
|
ipBan.PlayerSteamId ??= steamId;
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Multiaccount ban - check if other accounts using current ip are banned
|
||||||
|
if (!_playerIpsCache.IsEmpty)
|
||||||
|
{
|
||||||
|
foreach (var (otherSteamId, ipSet) in _playerIpsCache)
|
||||||
|
{
|
||||||
|
// Skip current player
|
||||||
|
if (otherSteamId == steamId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this ip is in the other accounts ip history
|
||||||
|
if (ipSet.All(record => record.Ip != ipUInt)) continue;
|
||||||
|
// Found another account using this ip - check if its banned
|
||||||
|
if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans)) continue;
|
||||||
|
var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||||
|
if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) ||
|
||||||
|
cachedBan.StatusEnum != BanStatus.ACTIVE) continue;
|
||||||
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
|
// Multiaccount ban - check if this player used any ip where other banned accounts are connected
|
||||||
|
// Search sa_players_ips for all accounts sharing the same ips as current player
|
||||||
|
if (!CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
|
if (!_playerIpsCache.TryGetValue(steamId, out var playerIps))
|
||||||
return false;
|
return false;
|
||||||
|
|
||||||
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
|
// For each ip the player used (current or historical)
|
||||||
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
|
foreach (var playerIpRecord in playerIps)
|
||||||
|
|
||||||
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
|
|
||||||
{
|
{
|
||||||
if (!_cachedIgnoredIps.Contains(ipAsUint))
|
// Search sa_players_ips for other accounts using this same ip (as uint)
|
||||||
|
foreach (var (otherSteamId, otherIpSet) in _playerIpsCache)
|
||||||
{
|
{
|
||||||
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
|
if (otherSteamId == steamId)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this other account used the player ip
|
||||||
|
if (otherIpSet.All(record => record.Ip != playerIpRecord.Ip))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
// Check if this other account is banned
|
||||||
|
if (!_steamIdIndex.TryGetValue(otherSteamId, out var otherSteamBans))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
var activeBan = otherSteamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
|
||||||
|
if (activeBan == null || !_banCache.TryGetValue(activeBan.Id, out var cachedBan) ||
|
||||||
|
cachedBan.StatusEnum != BanStatus.ACTIVE)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
||||||
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
foreach (var ipRecord in ipData)
|
|
||||||
{
|
|
||||||
if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!_ipIndex.TryGetValue(ipRecord.Ip, out var banRecords))
|
|
||||||
continue;
|
|
||||||
|
|
||||||
var activeBan = banRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
|
|
||||||
if (activeBan == null)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (string.IsNullOrEmpty(activeBan.PlayerName))
|
|
||||||
activeBan.PlayerName = unknownName;
|
|
||||||
|
|
||||||
activeBan.PlayerSteamId ??= steamId;
|
|
||||||
|
|
||||||
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
|
|
||||||
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
using CounterStrikeSharp.API;
|
using CounterStrikeSharp.API;
|
||||||
using CounterStrikeSharp.API.Modules.Entities;
|
using CounterStrikeSharp.API.Modules.Entities;
|
||||||
using Dapper;
|
using Dapper;
|
||||||
using Microsoft.Extensions.Logging;
|
using Microsoft.Extensions.Logging;
|
||||||
@@ -12,54 +12,8 @@ namespace CS2_SimpleAdmin.Managers;
|
|||||||
|
|
||||||
public class PermissionManager(IDatabaseProvider? databaseProvider)
|
public class PermissionManager(IDatabaseProvider? databaseProvider)
|
||||||
{
|
{
|
||||||
// Unused for now
|
|
||||||
//public static readonly ConcurrentDictionary<string, ConcurrentBag<string>> _adminCache = new ConcurrentDictionary<string, ConcurrentBag<string>>();
|
|
||||||
// public static readonly ConcurrentDictionary<SteamID, DateTime?> AdminCache = new();
|
|
||||||
public static readonly ConcurrentDictionary<SteamID, (DateTime? ExpirationTime, List<string> Flags)> AdminCache = new();
|
public static readonly ConcurrentDictionary<SteamID, (DateTime? ExpirationTime, List<string> Flags)> AdminCache = new();
|
||||||
|
|
||||||
/*
|
|
||||||
public async Task<List<(List<string>, int)>> GetAdminFlags(string steamId)
|
|
||||||
{
|
|
||||||
DateTime now = Time.ActualDateTime();
|
|
||||||
|
|
||||||
await using MySqlConnection connection = await _database.GetConnectionAsync();
|
|
||||||
|
|
||||||
string sql = "SELECT flags, immunity, ends FROM sa_admins WHERE player_steamid = @PlayerSteamID AND (ends IS NULL OR ends > @CurrentTime) AND (server_id IS NULL OR server_id = @serverid)";
|
|
||||||
List<dynamic>? activeFlags = (await connection.QueryAsync(sql, new { PlayerSteamID = steamId, CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId }))?.ToList();
|
|
||||||
|
|
||||||
if (activeFlags == null)
|
|
||||||
{
|
|
||||||
return new List<(List<string>, int)>();
|
|
||||||
}
|
|
||||||
|
|
||||||
List<(List<string>, int)> filteredFlagsWithImmunity = [];
|
|
||||||
|
|
||||||
foreach (dynamic flags in activeFlags)
|
|
||||||
{
|
|
||||||
if (flags is not IDictionary<string, object> flagsDict)
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!flagsDict.TryGetValue("flags", out var flagsValueObj) || !flagsDict.TryGetValue("immunity", out var immunityValueObj))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!(flagsValueObj is string flagsValue) || !int.TryParse(immunityValueObj.ToString(), out var immunityValue))
|
|
||||||
{
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
//Console.WriteLine($"Flags: {flagsValue}, Immunity: {immunityValue}");
|
|
||||||
|
|
||||||
filteredFlagsWithImmunity.Add((flagsValue.Split(',').ToList(), immunityValue));
|
|
||||||
}
|
|
||||||
|
|
||||||
return filteredFlagsWithImmunity;
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves all players' flags and associated data asynchronously.
|
/// Retrieves all players' flags and associated data asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -100,7 +54,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
string playerName = g.Key.playerName as string ?? string.Empty;
|
string playerName = g.Key.playerName as string ?? string.Empty;
|
||||||
|
|
||||||
// tutaj zakładamy, że Dapper zwraca już string (nie dynamic)
|
// Dapper returns string here, not dynamic
|
||||||
var flags = g.Select(r => r.flag as string ?? string.Empty)
|
var flags = g.Select(r => r.flag as string ?? string.Empty)
|
||||||
.Distinct()
|
.Distinct()
|
||||||
.ToList();
|
.ToList();
|
||||||
@@ -118,72 +72,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
/*
|
|
||||||
public async Task<Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>>> GetAllGroupsFlags()
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using MySqlConnection connection = await _database.GetConnectionAsync();
|
|
||||||
|
|
||||||
string sql = "SELECT group_id FROM sa_groups_servers WHERE server_id = @serverid";
|
|
||||||
var groupIds = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
|
||||||
|
|
||||||
sql = @"
|
|
||||||
SELECT g.group_id, f.flag
|
|
||||||
FROM sa_groups_flags f
|
|
||||||
JOIN sa_groups_servers g ON f.group_id = g.group_id
|
|
||||||
WHERE g.server_id = @serverid";
|
|
||||||
|
|
||||||
var groupFlagData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
|
||||||
|
|
||||||
if (groupIds.Count == 0 || groupFlagData.Count == 0)
|
|
||||||
{
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
|
|
||||||
var groupInfoDictionary = new Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>>();
|
|
||||||
|
|
||||||
foreach (var groupId in groupIds)
|
|
||||||
{
|
|
||||||
groupInfoDictionary[groupId] = new Tuple<List<string>, List<Tuple<string, DateTime?>>, int>([], [], 0);
|
|
||||||
}
|
|
||||||
|
|
||||||
foreach (var row in groupFlagData)
|
|
||||||
{
|
|
||||||
var groupId = (int)row.group_id;
|
|
||||||
var flag = (string)row.flag;
|
|
||||||
|
|
||||||
groupInfoDictionary[groupId].Item1.Add(flag);
|
|
||||||
}
|
|
||||||
|
|
||||||
sql = @"
|
|
||||||
SELECT a.group_id, a.player_steamid, a.ends, g.immunity, g.name
|
|
||||||
FROM sa_admins a
|
|
||||||
JOIN sa_groups g ON a.group_id = g.id
|
|
||||||
WHERE a.group_id IN @groupIds";
|
|
||||||
|
|
||||||
var playerData = (await connection.QueryAsync(sql, new { groupIds })).ToList();
|
|
||||||
|
|
||||||
foreach (var row in playerData)
|
|
||||||
{
|
|
||||||
var groupId = (int)row.group_id;
|
|
||||||
var playerSteamid = (string)row.player_steamid;
|
|
||||||
var ends = row.ends as DateTime?;
|
|
||||||
var immunity = (int)row.immunity;
|
|
||||||
|
|
||||||
groupInfoDictionary[groupId].Item2.Add(new Tuple<string, DateTime?>(playerSteamid, ends));
|
|
||||||
groupInfoDictionary[groupId] = new Tuple<List<string>, List<Tuple<string, DateTime?>>, int>(groupInfoDictionary[groupId].Item1, groupInfoDictionary[groupId].Item2, immunity);
|
|
||||||
}
|
|
||||||
|
|
||||||
return groupInfoDictionary;
|
|
||||||
}
|
|
||||||
catch { }
|
|
||||||
|
|
||||||
return [];
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Retrieves all groups' data including flags and immunity asynchronously.
|
/// Retrieves all groups' data including flags and immunity asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -192,13 +80,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
{
|
{
|
||||||
if (databaseProvider == null) return [];
|
if (databaseProvider == null) return [];
|
||||||
|
|
||||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
|
||||||
;
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)";
|
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||||
// var groupDataSql = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
|
||||||
|
|
||||||
var sql = databaseProvider.GetGroupsQuery();
|
var sql = databaseProvider.GetGroupsQuery();
|
||||||
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
|
||||||
if (groupData.Count == 0)
|
if (groupData.Count == 0)
|
||||||
@@ -213,11 +97,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
var flag = (string)row.flag;
|
var flag = (string)row.flag;
|
||||||
var immunity = (int)row.immunity;
|
var immunity = (int)row.immunity;
|
||||||
|
|
||||||
// Check if the group name already exists in the dictionary
|
|
||||||
if (!groupInfoDictionary.TryGetValue(groupName, out (List<string>, int) value))
|
if (!groupInfoDictionary.TryGetValue(groupName, out (List<string>, int) value))
|
||||||
{
|
{
|
||||||
value = ([], immunity);
|
value = ([], immunity);
|
||||||
// If it doesn't exist, add a new entry with an empty list of flags and immunity
|
|
||||||
groupInfoDictionary[groupName] = value;
|
groupInfoDictionary[groupName] = value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -238,7 +120,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
/// Creates a JSON file containing groups data asynchronously.
|
/// Creates a JSON file containing groups data asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
|
||||||
public async Task CrateGroupsJsonFile()
|
public async Task CreateGroupsJsonFile()
|
||||||
{
|
{
|
||||||
var groupsData = await GetAllGroupsData();
|
var groupsData = await GetAllGroupsData();
|
||||||
var jsonData = new Dictionary<string, object>();
|
var jsonData = new Dictionary<string, object>();
|
||||||
@@ -257,7 +139,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
var options = new JsonSerializerOptions
|
var options = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
|
||||||
};
|
};
|
||||||
|
|
||||||
var json = JsonSerializer.Serialize(jsonData, options);
|
var json = JsonSerializer.Serialize(jsonData, options);
|
||||||
@@ -265,66 +147,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
await File.WriteAllTextAsync(filePath, json);
|
await File.WriteAllTextAsync(filePath, json);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
public async Task GiveAllGroupsFlags()
|
|
||||||
{
|
|
||||||
Dictionary<int, Tuple<List<string>, List<Tuple<string, DateTime?>>, int>> groupFlags = await GetAllGroupsFlags();
|
|
||||||
|
|
||||||
foreach (var kvp in groupFlags)
|
|
||||||
{
|
|
||||||
var flags = kvp.Value.Item1;
|
|
||||||
var players = kvp.Value.Item2;
|
|
||||||
int immunity = kvp.Value.Item3;
|
|
||||||
|
|
||||||
foreach (var playerTuple in players)
|
|
||||||
{
|
|
||||||
var steamIdStr = playerTuple.Item1;
|
|
||||||
var ends = playerTuple.Item2;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null)
|
|
||||||
{
|
|
||||||
if (!_adminCache.ContainsKey(steamId))
|
|
||||||
{
|
|
||||||
_adminCache.TryAdd(steamId, ends);
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
|
|
||||||
// Often need to call 2 times
|
|
||||||
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
/*
|
|
||||||
public async Task GiveAllFlags()
|
|
||||||
{
|
|
||||||
List<(string, string, List<string>, int, DateTime?)> allPlayers = await GetAllPlayersFlags();
|
|
||||||
|
|
||||||
foreach (var record in allPlayers)
|
|
||||||
{
|
|
||||||
string steamIdStr = record.Item1;
|
|
||||||
List<string> flags = record.Item2;
|
|
||||||
int immunity = record.Item3;
|
|
||||||
|
|
||||||
DateTime? ends = record.Item4;
|
|
||||||
|
|
||||||
if (!string.IsNullOrEmpty(steamIdStr) && SteamID.TryParse(steamIdStr, out var steamId) && steamId != null)
|
|
||||||
{
|
|
||||||
if (!_adminCache.ContainsKey(steamId))
|
|
||||||
{
|
|
||||||
_adminCache.TryAdd(steamId, ends);
|
|
||||||
//_adminCacheTimestamps.Add(steamId, ends);
|
|
||||||
}
|
|
||||||
|
|
||||||
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
|
|
||||||
// Often need to call 2 times
|
|
||||||
Helper.GivePlayerFlags(steamId, flags, (uint)immunity);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
*/
|
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Creates a JSON file containing admins data asynchronously.
|
/// Creates a JSON file containing admins data asynchronously.
|
||||||
/// </summary>
|
/// </summary>
|
||||||
@@ -336,25 +158,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
|
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
|
||||||
.ToList();
|
.ToList();
|
||||||
|
|
||||||
// foreach (var player in allPlayers)
|
|
||||||
// {
|
|
||||||
// var (steamId, name, flags, immunity, ends) = player;
|
|
||||||
//
|
|
||||||
// Console.WriteLine($"Player SteamID: {steamId}");
|
|
||||||
// Console.WriteLine($"Player Name: {name}");
|
|
||||||
// Console.WriteLine($"Flags: {string.Join(", ", flags)}");
|
|
||||||
// Console.WriteLine($"Immunity: {immunity}");
|
|
||||||
// Console.WriteLine($"Ends: {(ends.HasValue ? ends.Value.ToString("yyyy-MM-dd HH:mm:ss") : "Never")}");
|
|
||||||
// Console.WriteLine();
|
|
||||||
// }
|
|
||||||
|
|
||||||
var jsonData = validPlayers
|
var jsonData = validPlayers
|
||||||
.GroupBy(player => player.name) // Group by player name
|
.GroupBy(player => player.name)
|
||||||
.ToDictionary(
|
.ToDictionary(
|
||||||
group => group.Key, // Use the player name as key
|
group => group.Key,
|
||||||
object (group) =>
|
object (group) =>
|
||||||
{
|
{
|
||||||
// Consolidate data for players with same name
|
|
||||||
var consolidatedData = group.Aggregate(
|
var consolidatedData = group.Aggregate(
|
||||||
new
|
new
|
||||||
{
|
{
|
||||||
@@ -365,16 +174,13 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
},
|
},
|
||||||
(acc, player) =>
|
(acc, player) =>
|
||||||
{
|
{
|
||||||
// Merge identities
|
|
||||||
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
|
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(player.identity.ToString()))
|
||||||
{
|
{
|
||||||
acc = acc with { identity = player.identity.ToString() };
|
acc = acc with { identity = player.identity.ToString() };
|
||||||
}
|
}
|
||||||
|
|
||||||
// Combine immunities by maximum value
|
|
||||||
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
|
acc = acc with { immunity = Math.Max(acc.immunity, player.immunity) };
|
||||||
|
|
||||||
// Combine flags and groups
|
|
||||||
acc = acc with
|
acc = acc with
|
||||||
{
|
{
|
||||||
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
|
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
|
||||||
@@ -383,12 +189,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
return acc;
|
return acc;
|
||||||
});
|
});
|
||||||
|
|
||||||
Server.NextWorldUpdate(() =>
|
Server.NextWorldUpdate(() =>
|
||||||
{
|
{
|
||||||
var keysToRemove = new List<SteamID>();
|
var keysToRemove = new List<SteamID>();
|
||||||
|
|
||||||
foreach (var steamId in AdminCache.Keys.ToList())
|
foreach (var steamId in AdminCache.Keys.ToList())
|
||||||
{
|
{
|
||||||
var data = AdminManager.GetPlayerAdminData(steamId);
|
var data = AdminManager.GetPlayerAdminData(steamId);
|
||||||
if (data != null)
|
if (data != null)
|
||||||
@@ -422,40 +228,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Server.NextFrameAsync(() =>
|
|
||||||
// {
|
|
||||||
// for (var index = 0; index < AdminCache.Keys.ToList().Count; index++)
|
|
||||||
// {
|
|
||||||
// var steamId = AdminCache.Keys.ToList()[index];
|
|
||||||
//
|
|
||||||
// var data = AdminManager.GetPlayerAdminData(steamId);
|
|
||||||
// if (data != null)
|
|
||||||
// {
|
|
||||||
// AdminManager.RemovePlayerPermissions(steamId, AdminCache[steamId].Flags.ToArray());
|
|
||||||
// AdminManager.RemovePlayerFromGroup(steamId, true, AdminCache[steamId].Flags.ToArray());
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (!AdminCache.TryRemove(steamId, out _)) continue;
|
|
||||||
//
|
|
||||||
// if (data == null) continue;
|
|
||||||
// if (data.Flags.ToList().Count != 0 && data.Groups.ToList().Count != 0)
|
|
||||||
// continue;
|
|
||||||
//
|
|
||||||
// AdminManager.ClearPlayerPermissions(steamId);
|
|
||||||
// AdminManager.RemovePlayerAdminData(steamId);
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// foreach (var player in group)
|
|
||||||
// {
|
|
||||||
// SteamID.TryParse(player.identity, out var steamId);
|
|
||||||
// if (steamId == null) continue;
|
|
||||||
// AdminCache.TryAdd(steamId, (player.ends, player.flags));
|
|
||||||
// }
|
|
||||||
// });
|
|
||||||
|
|
||||||
return consolidatedData;
|
return consolidatedData;
|
||||||
});
|
});
|
||||||
|
|
||||||
var options = new JsonSerializerOptions
|
var options = new JsonSerializerOptions
|
||||||
{
|
{
|
||||||
WriteIndented = true,
|
WriteIndented = true,
|
||||||
@@ -464,10 +239,7 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
var json = JsonSerializer.Serialize(jsonData, options);
|
var json = JsonSerializer.Serialize(jsonData, options);
|
||||||
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
|
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
|
||||||
|
|
||||||
await File.WriteAllTextAsync(filePath, json);
|
await File.WriteAllTextAsync(filePath, json);
|
||||||
|
|
||||||
//await File.WriteAllTextAsync(CS2_SimpleAdmin.Instance.ModuleDirectory + "/data/admins.json", json);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -480,8 +252,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
if (databaseProvider == null) return;
|
if (databaseProvider == null) return;
|
||||||
if (string.IsNullOrEmpty(playerSteamId)) return;
|
if (string.IsNullOrEmpty(playerSteamId)) return;
|
||||||
|
|
||||||
//_adminCache.TryRemove(playerSteamId, out _);
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||||
@@ -510,18 +280,12 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
|
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
|
||||||
|
|
||||||
var now = Time.ActualDateTime();
|
var now = Time.ActualDateTime();
|
||||||
DateTime? futureTime;
|
DateTime? futureTime = time != 0 ? now.AddMinutes(time) : null;
|
||||||
|
|
||||||
if (time != 0)
|
|
||||||
futureTime = now.AddMinutes(time);
|
|
||||||
else
|
|
||||||
futureTime = null;
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||||
|
|
||||||
// Insert admin into sa_admins table
|
|
||||||
var insertAdminSql = databaseProvider.GetAddAdminQuery();
|
var insertAdminSql = databaseProvider.GetAddAdminQuery();
|
||||||
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
|
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
|
||||||
{
|
{
|
||||||
@@ -533,28 +297,8 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId
|
serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId
|
||||||
});
|
});
|
||||||
|
|
||||||
// Insert flags into sa_admins_flags table
|
|
||||||
foreach (var flag in flagsList)
|
foreach (var flag in flagsList)
|
||||||
{
|
{
|
||||||
// if (flag.StartsWith($"#"))
|
|
||||||
// {
|
|
||||||
// // const string sql = "SELECT id FROM `sa_groups` WHERE name = @groupName";
|
|
||||||
// // var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag });
|
|
||||||
//
|
|
||||||
// var sql = databaseProvider.GetGroupIdByNameQuery();
|
|
||||||
// var groupId = await connection.QuerySingleOrDefaultAsync<int?>(sql, new { groupName = flag, CS2_SimpleAdmin.ServerId });
|
|
||||||
//
|
|
||||||
// if (groupId != null)
|
|
||||||
// {
|
|
||||||
// var updateAdminGroup = "UPDATE `sa_admins` SET group_id = @groupId WHERE id = @adminId";
|
|
||||||
// await connection.ExecuteAsync(updateAdminGroup, new
|
|
||||||
// {
|
|
||||||
// groupId,
|
|
||||||
// adminId
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
|
var insertFlagsSql = databaseProvider.GetAddAdminFlagsQuery();
|
||||||
await connection.ExecuteAsync(insertFlagsSql, new
|
await connection.ExecuteAsync(insertFlagsSql, new
|
||||||
{
|
{
|
||||||
@@ -587,10 +331,10 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
|
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
|
||||||
|
|
||||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
// Insert group into sa_groups table
|
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||||
|
|
||||||
var insertGroup = databaseProvider.GetAddGroupQuery();
|
var insertGroup = databaseProvider.GetAddGroupQuery();
|
||||||
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
|
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
|
||||||
{
|
{
|
||||||
@@ -598,11 +342,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
immunity
|
immunity
|
||||||
});
|
});
|
||||||
|
|
||||||
// Insert flags into sa_groups_flags table
|
|
||||||
foreach (var flag in flagsList)
|
foreach (var flag in flagsList)
|
||||||
{
|
{
|
||||||
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
|
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
|
||||||
|
|
||||||
await connection.ExecuteAsync(insertFlagsSql, new
|
await connection.ExecuteAsync(insertFlagsSql, new
|
||||||
{
|
{
|
||||||
groupId,
|
groupId,
|
||||||
@@ -616,7 +358,6 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
|
||||||
});
|
});
|
||||||
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
@@ -634,9 +375,9 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
if (string.IsNullOrEmpty(groupName)) return;
|
if (string.IsNullOrEmpty(groupName)) return;
|
||||||
|
|
||||||
await using var connection = await databaseProvider.CreateConnectionAsync();
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
await using var connection = await databaseProvider.CreateConnectionAsync();
|
||||||
var sql = databaseProvider.GetDeleteGroupQuery();
|
var sql = databaseProvider.GetDeleteGroupQuery();
|
||||||
await connection.ExecuteAsync(sql, new { groupName });
|
await connection.ExecuteAsync(sql, new { groupName });
|
||||||
}
|
}
|
||||||
@@ -665,4 +406,4 @@ public class PermissionManager(IDatabaseProvider? databaseProvider)
|
|||||||
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired admins");
|
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired admins");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,7 +13,7 @@ namespace CS2_SimpleAdmin.Managers;
|
|||||||
|
|
||||||
internal class PlayerManager
|
internal class PlayerManager
|
||||||
{
|
{
|
||||||
private readonly SemaphoreSlim _loadPlayerSemaphore = new(5);
|
private readonly SemaphoreSlim _loadPlayerSemaphore = new(6);
|
||||||
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
|
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
@@ -52,75 +52,40 @@ internal class PlayerManager
|
|||||||
{
|
{
|
||||||
await _loadPlayerSemaphore.WaitAsync();
|
await _loadPlayerSemaphore.WaitAsync();
|
||||||
|
|
||||||
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
// Save ip address before ban check
|
||||||
|
await SavePlayerIpAddress(steamId, playerName, ipAddress);
|
||||||
|
|
||||||
|
// Always check bans first, regardless of PlayersInfo state
|
||||||
|
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
||||||
{
|
{
|
||||||
var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
|
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
||||||
|
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
|
||||||
|
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId,
|
||||||
|
ipAddress)
|
||||||
|
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress)
|
||||||
|
};
|
||||||
|
|
||||||
|
CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Player {playerName} ({steamId}) IP: {ipAddress} - BanType: {CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType} - CheckMultiAccounts: {CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp} - isBanned: {isBanned}");
|
||||||
|
|
||||||
|
if (isBanned)
|
||||||
|
{
|
||||||
|
CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] KICKING {playerName} ({steamId})");
|
||||||
|
await Server.NextWorldUpdateAsync(() =>
|
||||||
{
|
{
|
||||||
0 => CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, null),
|
CS2_SimpleAdmin._logger?.LogInformation($"[BAN CHECK] Executing kick for {playerName}");
|
||||||
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
|
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||||
? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId,
|
});
|
||||||
ipAddress)
|
|
||||||
: CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(playerName, steamId, ipAddress)
|
|
||||||
};
|
|
||||||
|
|
||||||
// CS2_SimpleAdmin._logger?.LogInformation($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}");
|
return;
|
||||||
|
|
||||||
if (isBanned)
|
|
||||||
{
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
|
||||||
{
|
|
||||||
// CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}");
|
|
||||||
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
|
||||||
});
|
|
||||||
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (fullConnect)
|
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
|
||||||
{
|
{
|
||||||
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
|
||||||
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
|
||||||
|
|
||||||
await Server.NextWorldUpdateAsync(() =>
|
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null)
|
||||||
{
|
{
|
||||||
if (!CS2_SimpleAdmin.CachedPlayers.Contains(player))
|
|
||||||
CS2_SimpleAdmin.CachedPlayers.Add(player);
|
|
||||||
});
|
|
||||||
|
|
||||||
if (_config.OtherSettings.CheckMultiAccountsByIp && ipAddress != null &&
|
|
||||||
CS2_SimpleAdmin.PlayersInfo[steamId] != null)
|
|
||||||
{
|
|
||||||
try
|
|
||||||
{
|
|
||||||
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
|
||||||
|
|
||||||
// Eliminates the need for SELECT COUNT and duplicate UPDATE queries
|
|
||||||
var steamId64 = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64;
|
|
||||||
var ipUint = IpHelper.IpToUint(ipAddress);
|
|
||||||
|
|
||||||
// Use database-specific UPSERT query (handles MySQL vs SQLite syntax differences)
|
|
||||||
var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
|
||||||
|
|
||||||
await connection.ExecuteAsync(upsertQuery, new
|
|
||||||
{
|
|
||||||
SteamID = steamId64,
|
|
||||||
playerName,
|
|
||||||
IPAddress = ipUint
|
|
||||||
});
|
|
||||||
|
|
||||||
// // Cache will be updated on next refresh cycle
|
|
||||||
// if (!CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(steamId, ipAddress))
|
|
||||||
// {
|
|
||||||
// // IP association will be reflected after cache refresh
|
|
||||||
// }
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
CS2_SimpleAdmin._logger?.LogError(
|
|
||||||
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}): {ex.Message}");
|
|
||||||
}
|
|
||||||
|
|
||||||
playerInfo.AccountsAssociated =
|
playerInfo.AccountsAssociated =
|
||||||
CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable()
|
CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable()
|
||||||
.Select(x => (x.SteamId, x.PlayerName)).ToList() ?? [];
|
.Select(x => (x.SteamId, x.PlayerName)).ToList() ?? [];
|
||||||
@@ -207,7 +172,7 @@ internal class PlayerManager
|
|||||||
AdminManager.PlayerHasPermissions(
|
AdminManager.PlayerHasPermissions(
|
||||||
new SteamID(p.SteamID),
|
new SteamID(p.SteamID),
|
||||||
"@css/ban")) &&
|
"@css/ban")) &&
|
||||||
p.Connected == PlayerConnectedState.PlayerConnected &&
|
p.Connected == PlayerConnectedState.Connected &&
|
||||||
!CS2_SimpleAdmin.AdminDisabledJoinComms
|
!CS2_SimpleAdmin.AdminDisabledJoinComms
|
||||||
.Contains(p.SteamID)))
|
.Contains(p.SteamID)))
|
||||||
{
|
{
|
||||||
@@ -254,12 +219,45 @@ internal class PlayerManager
|
|||||||
_loadPlayerSemaphore.Release();
|
_loadPlayerSemaphore.Release();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
|
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
|
||||||
{
|
{
|
||||||
player.Rename(name);
|
player.Rename(name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Saves player's IP address to the database for multi-account detection.
|
||||||
|
/// This is called before ban checks to ensure IP is recorded even if player is banned.
|
||||||
|
/// </summary>
|
||||||
|
private async Task SavePlayerIpAddress(ulong steamId, string playerName, string? ipAddress)
|
||||||
|
{
|
||||||
|
if (!_config.OtherSettings.CheckMultiAccountsByIp || ipAddress == null || CS2_SimpleAdmin.DatabaseProvider == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
|
||||||
|
|
||||||
|
var steamId64 = steamId;
|
||||||
|
var ipUint = IpHelper.IpToUint(ipAddress);
|
||||||
|
|
||||||
|
var upsertQuery = CS2_SimpleAdmin.DatabaseProvider.GetUpsertPlayerIpQuery();
|
||||||
|
|
||||||
|
await connection.ExecuteAsync(upsertQuery, new
|
||||||
|
{
|
||||||
|
SteamID = steamId64,
|
||||||
|
playerName,
|
||||||
|
IPAddress = ipUint
|
||||||
|
});
|
||||||
|
}
|
||||||
|
catch (Exception ex)
|
||||||
|
{
|
||||||
|
CS2_SimpleAdmin._logger?.LogError(
|
||||||
|
$"Unable to save ip address for {playerName} ({ipAddress}): {ex.Message}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// Periodically checks the status of online players and applies timers for speed, gravity,
|
/// Periodically checks the status of online players and applies timers for speed, gravity,
|
||||||
/// and penalty expiration validation.
|
/// and penalty expiration validation.
|
||||||
@@ -287,9 +285,6 @@ internal class PlayerManager
|
|||||||
|
|
||||||
// Optimization: Get players once and avoid allocating anonymous types
|
// Optimization: Get players once and avoid allocating anonymous types
|
||||||
var validPlayers = Helper.GetValidPlayers();
|
var validPlayers = Helper.GetValidPlayers();
|
||||||
if (validPlayers.Count == 0)
|
|
||||||
return;
|
|
||||||
|
|
||||||
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
// Use ValueTuple instead of anonymous type - better performance and less allocations
|
||||||
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
var tempPlayers = new List<(string PlayerName, ulong SteamID, string? IpAddress, int? UserId, int Slot)>(validPlayers.Count);
|
||||||
foreach (var p in validPlayers)
|
foreach (var p in validPlayers)
|
||||||
@@ -364,7 +359,12 @@ internal class PlayerManager
|
|||||||
foreach (var player in bannedPlayers)
|
foreach (var player in bannedPlayers)
|
||||||
{
|
{
|
||||||
if (!player.UserId.HasValue) continue;
|
if (!player.UserId.HasValue) continue;
|
||||||
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
|
await Server.NextWorldUpdateAsync(() =>
|
||||||
|
{
|
||||||
|
if (Helper.GetPlayerFromSteamid64(player.SteamID) != null)
|
||||||
|
Helper.KickPlayer((int)player.UserId,
|
||||||
|
NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ public class ServerManager
|
|||||||
/// </summary>
|
/// </summary>
|
||||||
public void LoadServerData()
|
public void LoadServerData()
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
|
CS2_SimpleAdmin.Instance.AddTimer(1.0f, () =>
|
||||||
{
|
{
|
||||||
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
|
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
|
||||||
|
|
||||||
@@ -103,14 +103,12 @@ public class ServerManager
|
|||||||
CS2_SimpleAdmin.ServerId = serverId;
|
CS2_SimpleAdmin.ServerId = serverId;
|
||||||
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
|
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
|
||||||
|
|
||||||
if (CS2_SimpleAdmin.ServerId != null)
|
CS2_SimpleAdmin.ServerLoaded = true;
|
||||||
{
|
|
||||||
await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
|
|
||||||
}
|
|
||||||
|
|
||||||
CS2_SimpleAdmin.ServerLoaded = true;
|
|
||||||
if (CS2_SimpleAdmin.Instance.CacheManager != null)
|
if (CS2_SimpleAdmin.Instance.CacheManager != null)
|
||||||
|
{
|
||||||
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
|
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -33,7 +33,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||||||
playerName = player.Name,
|
playerName = player.Name,
|
||||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||||
muteReason = reason,
|
warnReason = reason,
|
||||||
duration = time,
|
duration = time,
|
||||||
ends = futureTime,
|
ends = futureTime,
|
||||||
created = now,
|
created = now,
|
||||||
@@ -42,7 +42,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||||||
|
|
||||||
return warnId;
|
return warnId;
|
||||||
}
|
}
|
||||||
catch
|
catch(Exception)
|
||||||
{
|
{
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -73,7 +73,7 @@ internal class WarnManager(IDatabaseProvider? databaseProvider)
|
|||||||
playerSteamid = playerSteamId,
|
playerSteamid = playerSteamId,
|
||||||
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
|
||||||
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
|
||||||
muteReason = reason,
|
warnReason = reason,
|
||||||
duration = time,
|
duration = time,
|
||||||
ends = futureTime,
|
ends = futureTime,
|
||||||
created = now,
|
created = now,
|
||||||
|
|||||||
@@ -57,8 +57,9 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, player));
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
slapMenu.AddSubMenu(playerName, () => CreateSlapDamageMenu(admin, capturedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return slapMenu.WithBackButton();
|
return slapMenu.WithBackButton();
|
||||||
@@ -112,12 +113,13 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
slayMenu.AddOption(playerName, _ =>
|
slayMenu.AddOption(playerName, _ =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Slay(admin, player);
|
CS2_SimpleAdmin.Slay(admin, capturedPlayer);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -139,13 +141,14 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, player, "Kick", PenaltyType.Kick,
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
kickMenu.AddSubMenu(playerName, () => CreateReasonMenu(admin, capturedPlayer, "Kick", PenaltyType.Kick,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
|
CS2_SimpleAdmin.Instance.Kick(admin, capturedPlayer, reason, admin.PlayerName);
|
||||||
}
|
}
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@@ -167,14 +170,15 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Warn",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Warn", PenaltyType.Warn,
|
warnMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Warn",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Warn", PenaltyType.Warn,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
|
CS2_SimpleAdmin.Instance.Warn(admin, capturedPlayer, duration, reason, admin.PlayerName);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -196,14 +200,15 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Ban",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Ban", PenaltyType.Ban,
|
banMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Ban",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Ban", PenaltyType.Ban,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
|
CS2_SimpleAdmin.Instance.Ban(admin, capturedPlayer, duration, reason, admin.PlayerName);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -225,14 +230,15 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Gag",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Gag", PenaltyType.Gag,
|
gagMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Gag",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Gag", PenaltyType.Gag,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
|
CS2_SimpleAdmin.Instance.Gag(admin, capturedPlayer, duration, reason);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -254,14 +260,15 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Mute",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Mute", PenaltyType.Mute,
|
muteMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Mute",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Mute", PenaltyType.Mute,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
|
CS2_SimpleAdmin.Instance.Mute(admin, capturedPlayer, duration, reason);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -283,14 +290,15 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, player, "Silence",
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
(_, _, duration) => CreateReasonMenu(admin, player, "Silence", PenaltyType.Silence,
|
silenceMenu.AddSubMenu(playerName, () => CreateDurationMenu(admin, capturedPlayer, "Silence",
|
||||||
|
(_, _, duration) => CreateReasonMenu(admin, capturedPlayer, "Silence", PenaltyType.Silence,
|
||||||
(_, _, reason) =>
|
(_, _, reason) =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
|
CS2_SimpleAdmin.Instance.Silence(admin, capturedPlayer, duration, reason);
|
||||||
}
|
}
|
||||||
})));
|
})));
|
||||||
}
|
}
|
||||||
@@ -312,8 +320,9 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, player));
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
teamMenu.AddSubMenu(playerName, () => CreateTeamSelectionMenu(admin, capturedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return teamMenu.WithBackButton();
|
return teamMenu.WithBackButton();
|
||||||
@@ -488,8 +497,9 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var player in players)
|
foreach (var player in players)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, player));
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
|
addAdminMenu.AddSubMenu(playerName, () => CreateAdminFlagsMenu(admin, capturedPlayer));
|
||||||
}
|
}
|
||||||
|
|
||||||
return addAdminMenu.WithBackButton();
|
return addAdminMenu.WithBackButton();
|
||||||
@@ -539,19 +549,20 @@ public abstract class BasicMenu
|
|||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
var removeAdminMenu = new MenuBuilder("sa_admin_remove", admin, localizer);
|
var removeAdminMenu = new MenuBuilder("sa_admin_remove", admin, localizer);
|
||||||
|
|
||||||
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
var adminPlayers = Helper.GetValidPlayers().Where(p =>
|
||||||
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0 &&
|
||||||
p != admin &&
|
p != admin &&
|
||||||
admin.CanTarget(p));
|
admin.CanTarget(p));
|
||||||
|
|
||||||
foreach (var player in adminPlayers)
|
foreach (var player in adminPlayers)
|
||||||
{
|
{
|
||||||
var playerName = player.PlayerName.Length > 26 ? player.PlayerName[..26] : player.PlayerName;
|
var capturedPlayer = player; // Capture to local variable to avoid closure issue
|
||||||
|
var playerName = capturedPlayer.PlayerName.Length > 26 ? capturedPlayer.PlayerName[..26] : capturedPlayer.PlayerName;
|
||||||
removeAdminMenu.AddOption(playerName, _ =>
|
removeAdminMenu.AddOption(playerName, _ =>
|
||||||
{
|
{
|
||||||
if (player.IsValid)
|
if (capturedPlayer.IsValid)
|
||||||
{
|
{
|
||||||
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
|
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, capturedPlayer.SteamID.ToString());
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@@ -583,10 +594,10 @@ public abstract class BasicMenu
|
|||||||
/// <param name="admin">The admin player selecting duration.</param>
|
/// <param name="admin">The admin player selecting duration.</param>
|
||||||
/// <param name="player">The target player for the penalty.</param>
|
/// <param name="player">The target player for the penalty.</param>
|
||||||
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
/// <param name="actionName">The name of the penalty action (e.g., "Kick", "Ban").</param>
|
||||||
/// <param name="onSelectAction">Callback action executed when duration is selected.</param>
|
/// <param name="onSelectAction">Callback function that returns the next menu when duration is selected.</param>
|
||||||
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
/// <returns>A MenuBuilder instance for the duration menu.</returns>
|
||||||
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
private static MenuBuilder CreateDurationMenu(CCSPlayerController admin, CCSPlayerController player, string actionName,
|
||||||
Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
Func<CCSPlayerController, CCSPlayerController, int, MenuBuilder> onSelectAction)
|
||||||
{
|
{
|
||||||
var localizer = CS2_SimpleAdmin._localizer;
|
var localizer = CS2_SimpleAdmin._localizer;
|
||||||
|
|
||||||
@@ -614,10 +625,8 @@ public abstract class BasicMenu
|
|||||||
|
|
||||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
||||||
{
|
{
|
||||||
durationMenu.AddOption(durationItem.Name, _ =>
|
var capturedDuration = durationItem.Duration; // Capture to avoid closure issue
|
||||||
{
|
durationMenu.AddSubMenu(durationItem.Name, () => onSelectAction(admin, player, capturedDuration));
|
||||||
onSelectAction(admin, player, durationItem.Duration);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return durationMenu.WithBackButton();
|
return durationMenu.WithBackButton();
|
||||||
|
|||||||
@@ -8,12 +8,28 @@ public static class DurationMenu
|
|||||||
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
|
||||||
{
|
{
|
||||||
var menu = AdminMenu.CreateMenu(menuName);
|
var menu = AdminMenu.CreateMenu(menuName);
|
||||||
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
|
if (menu == null)
|
||||||
|
return;
|
||||||
|
|
||||||
|
var durations = CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations;
|
||||||
|
|
||||||
|
// Capture admin and player to avoid closure issues
|
||||||
|
var capturedAdmin = admin;
|
||||||
|
var capturedPlayer = player;
|
||||||
|
var capturedAction = onSelectAction;
|
||||||
|
|
||||||
|
foreach (var durationItem in durations)
|
||||||
{
|
{
|
||||||
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
|
var duration = durationItem.Duration; // Capture in local variable
|
||||||
|
var name = durationItem.Name;
|
||||||
|
|
||||||
|
menu.AddMenuOption(name, (controller, option) =>
|
||||||
|
{
|
||||||
|
capturedAction(capturedAdmin, capturedPlayer, duration);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
AdminMenu.OpenMenu(admin, menu);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
|
||||||
|
|||||||
@@ -49,25 +49,27 @@ public static class ManagePlayersMenu
|
|||||||
if (AdminManager.CommandIsOverriden("css_warn")
|
if (AdminManager.CommandIsOverriden("css_warn")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, WarnMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_warn"] ?? "Warn", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_warn"] ?? "Warn", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_warn"] ?? "Warn"}: {p.PlayerName}", p, WarnMenu))));
|
||||||
|
|
||||||
if (hasBan)
|
if (hasBan)
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, BanMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_ban"] ?? "Ban", () =>
|
||||||
|
PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_ban"] ?? "Ban", (a, p) =>
|
||||||
|
DurationMenu.OpenMenu(a, $"{localizer?["sa_ban"] ?? "Ban"}: {p.PlayerName}", p, BanMenu))));
|
||||||
|
|
||||||
if (hasChat)
|
if (hasChat)
|
||||||
{
|
{
|
||||||
if (AdminManager.CommandIsOverriden("css_gag")
|
if (AdminManager.CommandIsOverriden("css_gag")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, GagMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_gag"] ?? "Gag", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_gag"] ?? "Gag", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_gag"] ?? "Gag"}: {p.PlayerName}", p, GagMenu))));
|
||||||
if (AdminManager.CommandIsOverriden("css_mute")
|
if (AdminManager.CommandIsOverriden("css_mute")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player, MuteMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_mute"] ?? "Mute", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_mute"] ?? "Mute", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_mute"] ?? "Mute"}: {p.PlayerName}", p, MuteMenu))));
|
||||||
if (AdminManager.CommandIsOverriden("css_silence")
|
if (AdminManager.CommandIsOverriden("css_silence")
|
||||||
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
|
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
|
||||||
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat"))
|
||||||
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (admin, player) => DurationMenu.OpenMenu(admin, $"{localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, SilenceMenu))));
|
options.Add(new ChatMenuOptionData(localizer?["sa_silence"] ?? "Silence", () => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_silence"] ?? "Silence", (a, p) => DurationMenu.OpenMenu(a, $"{localizer?["sa_silence"] ?? "Silence"}: {p.PlayerName}", p, SilenceMenu))));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AdminManager.CommandIsOverriden("css_team")
|
if (AdminManager.CommandIsOverriden("css_team")
|
||||||
@@ -159,22 +161,9 @@ public static class ManagePlayersMenu
|
|||||||
{
|
{
|
||||||
if (player is { IsValid: true })
|
if (player is { IsValid: true })
|
||||||
Ban(admin, player, duration, reason);
|
Ban(admin, player, duration, reason);
|
||||||
|
|
||||||
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
|
CS2_SimpleAdmin.MenuApi?.CloseMenu(admin);
|
||||||
});
|
});
|
||||||
|
|
||||||
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player?.PlayerName}");
|
|
||||||
//
|
|
||||||
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons)
|
|
||||||
// {
|
|
||||||
// menu?.AddMenuOption(option, (_, _) =>
|
|
||||||
// {
|
|
||||||
// if (player is { IsValid: true })
|
|
||||||
// Ban(admin, player, duration, option);
|
|
||||||
// });
|
|
||||||
// }
|
|
||||||
//
|
|
||||||
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
|
private static void Ban(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
|
||||||
|
|||||||
@@ -41,11 +41,12 @@ public static class PlayersMenu
|
|||||||
continue;
|
continue;
|
||||||
|
|
||||||
var enabled = admin.CanTarget(player);
|
var enabled = admin.CanTarget(player);
|
||||||
|
var capturedPlayer = player; // Capture in local variable to avoid closure issues
|
||||||
|
|
||||||
if (optionName != null)
|
if (optionName != null)
|
||||||
menu?.AddMenuOption(optionName, (_, _) =>
|
menu?.AddMenuOption(optionName, (controller, option) =>
|
||||||
{
|
{
|
||||||
if (player != null) onSelectAction.Invoke(admin, player);
|
if (capturedPlayer != null) onSelectAction.Invoke(admin, capturedPlayer);
|
||||||
},
|
},
|
||||||
!enabled);
|
!enabled);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,10 @@ public record BanRecord
|
|||||||
|
|
||||||
[Column("player_ip")]
|
[Column("player_ip")]
|
||||||
public string? PlayerIp { get; set; }
|
public string? PlayerIp { get; set; }
|
||||||
|
|
||||||
|
[Column("created")]
|
||||||
|
public DateTime Created { get; init; }
|
||||||
|
|
||||||
[Column("status")]
|
[Column("status")]
|
||||||
public required string Status { get; init; }
|
public required string Status { get; init; }
|
||||||
|
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
1.7.8-beta-4
|
1.8.1a
|
||||||
@@ -40,7 +40,6 @@ public partial class CS2_SimpleAdmin
|
|||||||
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
|
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
|
||||||
|
|
||||||
// Player Management
|
// Player Management
|
||||||
private static readonly HashSet<int> GodPlayers = [];
|
|
||||||
internal static readonly HashSet<int> SilentPlayers = [];
|
internal static readonly HashSet<int> SilentPlayers = [];
|
||||||
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
|
||||||
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
|
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
<Project Sdk="Microsoft.NET.Sdk">
|
<Project Sdk="Microsoft.NET.Sdk">
|
||||||
|
|
||||||
<PropertyGroup>
|
<PropertyGroup>
|
||||||
<TargetFramework>net8.0</TargetFramework>
|
<TargetFramework>net10.0</TargetFramework>
|
||||||
<RootNamespace>CS2_SimpleAdminApi</RootNamespace>
|
<RootNamespace>CS2_SimpleAdminApi</RootNamespace>
|
||||||
<ImplicitUsings>enable</ImplicitUsings>
|
<ImplicitUsings>enable</ImplicitUsings>
|
||||||
<Nullable>enable</Nullable>
|
<Nullable>enable</Nullable>
|
||||||
@@ -9,7 +9,7 @@
|
|||||||
</PropertyGroup>
|
</PropertyGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.369" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -45,7 +45,7 @@ public class CS2_SimpleAdmin_BanSoundModule: BasePlugin
|
|||||||
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot))
|
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsBot))
|
||||||
{
|
{
|
||||||
var filter = new RecipientFilter(player);
|
var filter = new RecipientFilter(player);
|
||||||
player?.EmitSound("bansound", volume: 0.9f, recipients: filter);
|
player?.EmitSound("bansound", volume: 0.75f, recipients: filter);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -14,7 +14,7 @@
|
|||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
|
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.346" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|||||||
Binary file not shown.
Binary file not shown.
Reference in New Issue
Block a user