Compare commits

...

373 Commits

Author SHA1 Message Date
Dawid Bepierszcz
cc54b9e879 Update README.md 2025-10-03 02:35:45 +02:00
Dawid Bepierszcz
640e618f3b Update README.md 2025-10-03 02:34:03 +02:00
Dawid Bepierszcz
23d174c4a5 Update build.yml 2025-10-03 02:26:07 +02:00
Dawid Bepierszcz
b7371adf26 Update build.yml 2025-10-03 02:23:07 +02:00
Dawid Bepierszcz
9154748ce6 Update build.yml 2025-10-03 02:19:05 +02:00
Dawid Bepierszcz
da9830ee05 Bump version to 1.7.7-alpha-10 and update release workflow
Updated the version to 1.7.7-alpha-10 in both the code and VERSION file. Fixed artifact naming and release name in the GitHub Actions workflow. Also, modified Helper.cs to return DateTime.UtcNow in ActualDateTime().
2025-10-03 02:16:49 +02:00
Dawid Bepierszcz
b41ac992c0 Update README.md 2025-10-03 02:13:51 +02:00
Dawid Bepierszcz
af0bda8f3a Update README.md 2025-10-03 02:04:10 +02:00
Dawid Bepierszcz
e2529cd646 Update build.yml 2025-10-03 01:44:23 +02:00
Dawid Bepierszcz
9d2cd34845 Update build.yml 2025-10-03 01:41:48 +02:00
Dawid Bepierszcz
5701455de0 Refactor database layer and add module/plugin improvements
Reworked the database layer to support both MySQL and SQLite via new provider classes and migration scripts for each backend. Updated the build workflow to support building and packaging additional modules, including StealthModule and BanSoundModule, and improved artifact handling. Refactored command registration to allow dynamic registration/unregistration and improved API event handling. Updated dependencies, project structure, and configuration checks for better reliability and extensibility. Added new language files, updated versioning, and removed obsolete files.

**⚠️ Warning: SQLite support is currently experimental.
Using this version requires reconfiguration of your database settings!
Plugin now uses UTC time. Please adjust your configurations accordingly!
**
2025-10-03 01:37:03 +02:00
Dawid Bepierszcz
b97426313b 1.7.7-alpha
- Fixed steamid only bans
- Added missing multiservermode
- Better caching
2025-05-25 01:00:14 +02:00
Dawid Bepierszcz
3ab63c05db 1.7.7-alpha-small-optimizations
- Clear bans cache on plugin reload
- Changed ip history to int
2025-05-23 03:28:10 +02:00
Dawid Bepierszcz
f654d6b085 1.7.7-alpha-connection-fix
- Fixed lag when player connecting
- Added command to force refresh ban cache (css_reloadbans)
2025-05-21 13:11:01 +02:00
Dawid Bepierszcz
676a18d9b4 1.7.7-alpha-associated-accounts (missing migration) 2025-05-21 03:13:05 +02:00
Dawid Bepierszcz
d75a092047 1.7.7-alpha-associated-accounts
- Added possibility to view player associated account (by ip)
2025-05-21 03:08:02 +02:00
Dawid Bepierszcz
d34ca64970 1.7.7-alpha-fix
- Added missing migration
- Added missing ignoredips
2025-05-21 00:38:54 +02:00
Dawid Bepierszcz
f69f1277f8 1.7.7-alpha
- Fixed (@all,@team,@lifestate) spam with discord webhook
- Currently `small` actions for ban optimizations
2025-05-20 23:51:59 +02:00
Dawid Bepierszcz
b6c876d709 1.7.6a
- Changed PawnIsAlive to LifeState
- Changed AdminCache - now it only removes flags added in the database - not all assigned to player
- Added config variable `IgnoredIps` to ignore ip check on connect, useful when proxying
2025-03-25 11:39:00 +01:00
Dawid Bepierszcz
888d6b0152 1.7.5a
- Changed api domain
- Changed link to wiki
- Added command `css_rresize` to resize player model
- Fixed saving IP addresses, from now they will not be saved if the check option in config is disabled
2025-03-09 01:01:45 +01:00
Dawid Bepierszcz
5d62c743dd AntiDLL Module Fix
Fixed banning when detecting a forbidden event
2025-02-20 01:28:28 +01:00
Dawid Bepierszcz
62b1987fde AntiDLL module
(?) Fix for AntiDLL module
2025-02-19 14:51:36 +01:00
Dawid Bepierszcz
708ae6cb90 1.7.4b
【UPDATE 1.7.4b】

**🆕  What's new and what's changed:**
- Added support for MySQL SSL (DatabaseSSlMode in config)
2025-02-19 12:27:24 +01:00
Dawid Bepierszcz
2d77e86d59 Hotfix for AntiDLL module 2025-02-18 20:18:16 +01:00
Dawid Bepierszcz
babcbc2119 1.7.4a
【UPDATE 1.7.4a】

**🆕  What's new and what's changed:**
- Exposed event `OnAdminShowActivity` - fire when the function of informing players of admin action is performed
- Exposed `ShowAdminActivity`
- Added module to integration with AntiDLL
- Added module to integration with Redis (plugin send info about penalties between all servers)
2025-02-18 20:11:05 +01:00
Dawid Bepierszcz
64e5f1156e 1.7.3a
【UPDATE 1.7.3a】

🆕  What's new and what's changed:
- Fixed problem when you can't add admin because of the same group names on multiple servers from now on it takes in order -> server id and if there is no -> global
- Added ability to add penalties via SteamID
- Added ability to display penalty messages only for admins (ShowActivityType = 3)
- Reverted OnClientConnect to EventPlayerFullConnect
2025-02-09 15:37:01 +01:00
Dawid Bepierszcz
8cc0398f6b 1.7.2c
- Fixed no permissions in menu and perm penalty (due to authorization issue on steam)
- Fixed non-async action in async method
2025-01-26 02:10:33 +01:00
Dawid Bepierszcz
ab14956ae5 1.7.2b
- Revert temp fix for kick
2025-01-16 03:28:17 +01:00
Dawid Bepierszcz
9163caeb0d Update Helper.cs 2025-01-15 13:09:27 +01:00
Dawid Bepierszcz
f2e4b84b29 1.7.2a
- Temp fix for player.Disconnect
- Fix for no reasons
- Fix for no time used
- Added check for hibernation
- Some changes in unwarn action
2025-01-15 12:56:03 +01:00
Dawid Bepierszcz
3f1b6b3bf7 1.7.1a
【UPDATE 1.7.1a】

🆕  What's new and what's changed:
- Fixed 2x player lock text
- Added immunity check when using css_addX
- MenuManagerCS2 has been updated
- Added return of lock ID by api (check example_module)
- Fixed reasons for locks, no longer need to use `“”` if there is a space in them
- Improved handling of lock times, from now on you can use:
-- 1mo - 1 month
-- 1w - 1 week
-- 1d - 1 day
-- 1h - 1 hour
-- 1m - 1 minute
-- 1 - 1 minute
You can also combine it, e.g:
10d5h - 10 days and 5 hours
2w3d - 2 weeks and 3 days
1w1d1h1 - 1 week, 1 day and 1 minute
So for example css_ban player 10d5h Super reason for my ban
- Added a `rcon_password` column in the `sa_servers` table that populates with the rcon password
- Fixed `banid` and banning
- Fixed too fast kick (it was caused by checking players online)

⚠️  **Remember to update all files + api**
2025-01-08 19:24:47 +01:00
Dawid Bepierszcz
8af805632a 1.7.0a
- Fixed css_warn (unfreeze player after 5s)
- Fixed server loading from database (reduced delay)
- Added checking the player in an earlier phase of the connection
- Fixed admin name when using action from menu
2024-12-16 01:55:38 +01:00
Dawid Bepierszcz
8c94a867d3 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-11-26 23:37:52 +01:00
Dawid Bepierszcz
023e1a031b 1.6.9c
- Added config variable to enable/disable checking for banned multiaccounts by ip `CheckMultiAccountsByIp`
- Fixed css_speed after player hurt
- Finally fixed vote kick (callvote)
- Small fix for admins loading from database
2024-11-26 23:37:49 +01:00
Dawid Bepierszcz
1f1c214357 Merge pull request #183 from 1370533448/patch-1
Update zh-Hans.json
2024-11-26 13:09:00 +01:00
Dawid Bepierszcz
2defb2fe14 Hotfix - server loading
- Fix for server loading
2024-11-22 23:01:31 +01:00
Dawid Bepierszcz
b2ebe136c3 1.6.9a
- Added `IsAdminSilent` to api
- Fixed disabling noclip via admin menu
- Fixed (?) hibernation problem
- Added discord webhook fire when adding ban or mute for offline player
- Updated css and mysqlconnector
2024-11-22 22:31:06 +01:00
Ktm
5668c0ad7b Update zh-Hans.json
Incorrect translation of time and reason leads to incorrect display
2024-11-17 03:41:30 +08:00
Dawid Bepierszcz
70a62d4b63 HOTFIX - Ban via menu 2024-11-10 19:13:39 +01:00
Dawid Bepierszcz
95818e2742 1.6.8a
- Updated css version
- Updated mysqlconnector version
- Added information for the player until when he has a chat blocked
- Closing menu after ban
2024-11-10 17:30:48 +01:00
Dawid Bepierszcz
7154843d1d Update ManageServerMenu.cs 2024-10-31 03:51:49 +01:00
Dawid Bepierszcz
c42d2ddeeb 1.6.7a
```diff
+ Added PluginsManager
```
2024-10-31 03:45:22 +01:00
Dawid Bepierszcz
7a69c5387a 1.6.6a
```diff
+ Reapply gravity/speed with timer
+ Added shake effect for slap
+ Fixed css_gravity and css_speed command
+ Fixed css_give command, for example weapon_knife returns weapon_knife instead of weapon_knife and weapon_knife_t
+ Small code improvements
```
2024-10-31 00:30:16 +01:00
Dawid Bepierszcz
82b82722a6 1.6.5a
- Possibility to force menu type by config
- Possibility to reload admins by css command `css_admins_reload`
2024-10-24 13:39:11 +02:00
Dawid Bepierszcz
b38b9a0751 Updated clean module 2024-10-19 09:07:25 +02:00
Dawid Bepierszcz
5a9367ae89 1.6.4a
- New give command
- Added `@css/permmute` flag to allow perm penalties
- Fixed mute when player silenced
- Added CleanModule - allow to clean weapons on ground
2024-10-19 03:52:33 +02:00
Dawid Bepierszcz
d30ac80a36 Update VERSION 2024-10-08 00:54:34 +02:00
Dawid Bepierszcz
9820d74095 1.6.3b
```diff
+ Small code cleanup
+ FIXED ban check on connect
```
2024-10-08 00:54:20 +02:00
Dawid Bepierszcz
d0207f3d0b Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-10-06 13:48:44 +02:00
Dawid Bepierszcz
2bee514e32 1.6.3a
```diff
+ Recompiled for css 276 - this is the minimum version on which the plugin works
+ Added ability to check player penalties for admin with `@css/kick` flag - css_comms [#userid/name].
+ Added a new setting `ShowBanMenuIfNoTime` which allows you to disable the display of the menu when you don't specify a penalty time
```
2024-10-06 13:48:42 +02:00
Dawid Bepierszcz
9d87fd8701 Merge pull request #161 from yMenn/main
Add Portuguese (Portugal) translations
2024-10-05 12:02:17 +02:00
Dawid Bepierszcz
6847da2af0 1.6.2a
```diff
+ Added duration and reason menu when admin try to ban/mute without specific time
+ Added `NotifyPenaltiesToAdminOnConnect` config setting to disable notifications globally
+ Added immunity check when using game vote to kick player

- Removed `Discord.Net.Webhook` package (too heavy)
```
2024-10-05 11:45:09 +02:00
ymenn
757a39f90e Added pt-PT translations 2024-10-05 10:17:08 +01:00
Dawid Bepierszcz
bd817d6652 1.6.1b
- Fixed rare problem when system uses other timezone than plugin
- Fixed time type in discord webhook
2024-09-29 20:55:03 +02:00
Dawid Bepierszcz
4206ad18b2 Fixed? rename 2024-09-29 18:43:36 +02:00
Dawid Bepierszcz
774d812723 Update playercommands.cs 2024-09-29 18:43:03 +02:00
Dawid Bepierszcz
cf5c1d8440 Fixed? prename 2024-09-29 18:42:04 +02:00
Dawid Bepierszcz
efa2a392d6 Translations 2024-09-29 18:32:50 +02:00
Dawid Bepierszcz
32520c7e84 1.6.1a
🆕  **What's new and what's changed:**
```diff
+ Added `css_hidecomms` command - Disable showing penalties when player connect
+ Added customizable commands in `Commands.json` file in plugin config directory
  - U can disable command by removing aliases or rename/add more aliases (remember to not remove key, edit only Aliases)
+ Added missing `Console` translation `sa_console`
+ Added `LogCommand` to api
```
2024-09-29 18:29:04 +02:00
Dawid Bepierszcz
702a0315b8 Fixed gag 2024-09-28 23:44:12 +02:00
Dawid Bepierszcz
b1ab6e0e0d Alias for css_disconnected
- css_last
- css_lastX (X = value from config)
2024-09-28 18:33:41 +02:00
Dawid Bepierszcz
e272449f3c Added example module 2024-09-28 18:24:29 +02:00
Dawid Bepierszcz
05893a90d3 1.6.0a
```diff
+ `Refactored Code`: Improved code structure for better maintainability.
+ `Player Penalties Command`: Introduced the `css_penalties` command to display player penalties.
+ `Admin Penalties Information`: Added functionality to provide information for admins regarding penalties of connecting players.
+ `Disconnected Players Command`: Added the `css_disconnected` command to show a list of disconnected players.
+ `Colorful Messages`: Implemented the `css_cssay` command to send colorful messages, e.g., `css_cssay {lightgreen}Test`.
+ `Respawn Functionality`: Updated the `css_respawn` command to respawn players at their death location.
+ `Menu Type Management`: Introduced the `css_menus` command to change the menu type via `MenuManagerCS2`.
+ `Dynamic Menu Control`: Enhanced menu interaction with dynamic controls using WASD + ER keys.
+ `Language File Updates`: Updated language files for better localization.
+ `API Integration`: Added a simple API for external interaction.
+ `Configurable Timezone`: Introduced timezone settings in the configuration.
+ `Admin Activity Display Options`: Added configurable settings for displaying admin activity:
  + `0`: Do not show
  + `1`: Hide admin name
  + `2`: Show admin name
+ `Discord Notification Customization`: Made Discord duration notifications customizable with `{relative}` and `{normal}` placeholders.
+ Improved command logging
+

`Configuration Options:`
+ `Timezone`
+ `Other Settings`
+ `Disconnected Players History Count`
+ `Show Activity Type`
```
2024-09-28 18:03:03 +02:00
Dawid Bepierszcz
a3b8d8cfa7 . 2024-09-28 17:50:47 +02:00
Dawid Bepierszcz
1cf541bb51 Merge pull request #154 from KillerRoi/patch-2
Update ar.json
2024-09-03 21:34:48 +02:00
Dawid Bepierszcz
3fb254bd0a Merge pull request #153 from KillerRoi/patch-1
Update en.json
2024-09-03 21:34:30 +02:00
Killer Roi
a26df7fedd Update en.json 2024-09-04 01:04:06 +05:30
Killer Roi
446dc9f4f5 Update ar.json 2024-09-04 01:01:40 +05:30
Killer Roi
3a8e0dd1fc Update en.json 2024-09-04 00:57:44 +05:30
Dawid Bepierszcz
b90ed05d1b Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-08-18 22:24:18 +02:00
Dawid Bepierszcz
6df98fb164 1.5.2c
- Menu overrides for fun commands
2024-08-18 22:23:30 +02:00
Dawid Bepierszcz
100482cb17 Update FunActionsMenu.cs 2024-08-17 21:28:09 +02:00
Dawid Bepierszcz
8f2f95ca23 1.5.2b
- Fix for command overrides in menu
- Fix for kick with banid when player disconnected
2024-08-17 18:43:11 +02:00
Dawid Bepierszcz
79270acafe PenaltyWebhook block thread - fix 2024-08-13 21:14:43 +02:00
Dawid Bepierszcz
f95581e7c0 mysql compatibility 2024-08-12 09:55:52 +02:00
Dawid Bepierszcz
af46f0bca1 Warn migration 2024-08-12 02:05:54 +02:00
Dawid Bepierszcz
f56e3f6fe9 Warns - last?
- Warn list + unwarn
2024-08-12 01:57:30 +02:00
Dawid Bepierszcz
4ef07c8bf7 Warns - second
- Warn menu
- Warn reasons (config)
2024-08-12 00:14:43 +02:00
Dawid Bepierszcz
0674b7e492 Warns - first
-WarnManager
- css_warn
- Warn config
- Warn languages

~ Warn menu
~ Warn table
~ *css_unwarn
~ css_addwarn
2024-08-11 22:54:54 +02:00
Dawid Bepierszcz
c4cb308147 Admin immunity + Discord
- Fixed command logging + small refactor
- Fixed admin immunity
- Separated penalty webhook
- More customizable penalty webhook
2024-08-05 03:28:42 +02:00
Dawid Bepierszcz
bb0a236f28 1.5.1a
- Possibility to use new line in translations
- Improved initializing
- More errors logging
- Localized Message refactor
2024-07-15 21:50:06 +02:00
Dawid Bepierszcz
3bc51330af 1.5.0a v3 2024-07-04 22:02:02 +02:00
Dawid Bepierszcz
5fdefc9614 1.5.0a v2 2024-07-03 23:07:12 +02:00
Dawid Bepierszcz
bcbcb83a35 1.5.0a
- Fixed immunity in menu
- Added new command `css_prename` to  perm rename player (until the server restarts - don't set new name to remove perm rename) // @css/ban)
2024-07-03 22:59:26 +02:00
Dawid Bepierszcz
6cf6b1c919 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-07-03 19:18:28 +02:00
Dawid Bepierszcz
3b98f19a7c 1.4.9a
- Plugin save all players ips to database, when player banned then all used ips are banned too (for ban evading)
- Minor changes
2024-07-03 19:18:25 +02:00
Dawid Bepierszcz
83fdf1dd56 Merge pull request #133 from Truba55/patch-1
German translation
2024-06-29 15:09:21 +02:00
Dawid Bepierszcz
6fc8b015ba 1.4.8b
- Improved admin loading
2024-06-29 15:06:32 +02:00
Dawid Bepierszcz
6b5e36087e Update Config.cs 2024-06-29 11:35:38 +02:00
Dawid Bepierszcz
a395a7d9c8 1.4.8a
- Removed message of checking server ip
- Fixed some commands
- Changed workshopmaps config
- Probably fixed votes (untested)  - let me know if not
- Minimum css version - 246
2024-06-29 11:33:41 +02:00
Truba55
d7e46525a5 German translation 2024-06-27 15:48:47 +02:00
Dawid Bepierszcz
cdd771511b 1.4.7a
- Fixed crash when using command on invalid player
- Using banid command to reduce number of queries to database (only if unlock commands enabled in css config)
- Minor changes
2024-06-23 12:59:14 +02:00
Dawid Bepierszcz
8e4724fb3e 1.4.6b
- Fix for alternative get server ip method
2024-06-18 03:12:00 +02:00
Dawid Bepierszcz
985b1ae61f 1.4.6a
- Alternative method to get server ip
- Config option to disable update check
- Improved live bans check
- Added new permission `@css/showip` to show ip in `css_who` and `css_players` commands
2024-06-17 11:08:46 +02:00
Dawid Bepierszcz
00facafdcb 1.4.5a
- Added `ReloadAdminsEveryMapChange` - Reloading sql admins on map start
- Ability to use any valid steamid instead of steamid64
- Fixed chat commands when gagged
- Votes now respect `UseChatMenu` config setting
2024-06-09 17:55:33 +02:00
Dawid Bepierszcz
962529e445 1.4.4b - Fix
- Fixed admins / group loading
2024-05-16 22:40:14 +02:00
Dawid Bepierszcz
873fed17c9 1.4.4b
- Fetch admins and groups data only once
- Added missing migration
2024-05-16 21:51:13 +02:00
Dawid Bepierszcz
fc2958c84f Update Config.cs 2024-05-06 22:59:23 +02:00
Dawid Bepierszcz
4244104d6e 1.4.4a
- MultiServerMode fix
- New feature `TimeMode`
2024-05-06 22:56:57 +02:00
Dawid Bepierszcz
d5a6ceacb6 1.4.3d
- Fixed banning without permban flag
- Updated css
2024-05-03 15:08:47 +02:00
Dawid Bepierszcz
14cc9d3b00 1.4.3c
- Fixed rare problems with expiring bans
- More checks for `BanType`
- Minor changes about validating players
- Added `css_players -duplicate` to list players with same ip address
2024-05-02 13:52:53 +02:00
Dawid Bepierszcz
9fb256d39f 1.4.3c
- Fixed rare problems with expiring bans
- More checks for `BanType`
- Minor changes about validating players
- Added `css_players -duplicate` to list players with same ip address
2024-05-02 13:51:07 +02:00
Dawid Bepierszcz
c25d3c4bda 1.4.3b
- Groups support now NULL value as `server_id` (global group)
- Added json output for `css_players`  (Panel request)
- AdminSQLManager Refactor
2024-05-01 22:46:04 +02:00
Dawid Bepierszcz
ebf9e06bcd Update 001_CreateTables.sql 2024-05-01 01:29:33 +02:00
Dawid Bepierszcz
c72c7231f7 Update 001_CreateTables.sql 2024-05-01 01:26:48 +02:00
Dawid Bepierszcz
9c870b8fc3 Merge pull request #105 from Dliix66/feature/adminSplitBan
Feature/admin split ban and more
2024-04-30 14:09:00 +02:00
Dawid Bepierszcz
cf7a87d959 Merge branch 'feature/adminSplitBan' of https://github.com/Dliix66/CS2-SimpleAdmin into pr/105 2024-04-30 14:07:58 +02:00
Dawid Bepierszcz
9759b34505 Update Config.cs 2024-04-30 14:07:34 +02:00
Dawid Bepierszcz
5a90171842 Merge branch 'main' into feature/adminSplitBan 2024-04-30 14:06:26 +02:00
Dawid Bepierszcz
19f8b68c1c 1.4.3a
- New feature permban permission (@css/permban)
- CustomCommand fix
- Version checker
- Minor changes
- Updated to CounterStrikeSharp 225
- Bump version to 1.4.3a
2024-04-30 14:05:20 +02:00
Valentin Barat
342d4f717f Fixed config loading 2024-04-29 14:18:54 +02:00
Dawid Bepierszcz
731bc229d3 1.4.2b
- Fixed migrations for mysql 5.x
2024-04-29 10:56:49 +02:00
Valentin Barat
bb8cec7cf8 Merge branch 'main' into feature/adminSplitBan 2024-04-29 10:31:37 +02:00
Valentin Barat
ad4c721a27 Merge remote-tracking branch 'origin/main'
# Conflicts:
#	Menus/CustomCommandsMenu.cs
2024-04-29 10:31:20 +02:00
Valentin Barat
7efa2cdad3 Fixed perm ban issue with console 2024-04-29 10:27:52 +02:00
Dawid Bepierszcz
c321502937 1.4.2a
- Config upgrade
- Translatable  and customizable menu
- More async
- Minor changes
2024-04-29 00:04:42 +02:00
Valentin Barat
13ab223c86 Merge branch 'main' into feature/adminSplitBan 2024-04-28 02:26:39 +02:00
Dliix66
c73584edae Merge branch 'daffyyyy:main' into main 2024-04-28 02:20:16 +02:00
Dawid Bepierszcz
aefa6c6355 1.4.1a
- Minor changes
2024-04-28 02:16:24 +02:00
Dawid Bepierszcz
806b5038ca Update 004_MoveOldFlagsToFlagsTable.sql 2024-04-27 13:29:56 +02:00
Dawid Bepierszcz
b45e112534 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-04-27 01:44:24 +02:00
Dawid Bepierszcz
e1650df72e 1.4.1a
- Full AdminManager integration
2024-04-27 01:44:12 +02:00
Dawid Bepierszcz
8caa8669ea Update 005_CreateUnbansTable.sql 2024-04-26 13:03:18 +02:00
Dawid Bepierszcz
516d9d9014 1.4.0b
- Recompiled with css v215
2024-04-26 09:15:25 +02:00
Dawid Bepierszcz
c71391bca4 1.4.0b
- Recompiled with css v215
2024-04-26 09:15:08 +02:00
Dawid Bepierszcz
2c66b899d0 Update Config.cs 2024-04-25 20:35:00 +02:00
Dawid Bepierszcz
1cfff8684f 1.4.0a
- Ability to set the plugin mode (MultiServerMode) - When its `true`, then plugin respects penalties from all servers, when its `false` then plugin respects penalties only from this server
- Renamed command `css_reladmin` to `css_reloadadmins` (It reload admins and groups)
- Groups
2024-04-25 20:33:37 +02:00
Valentin Barat
0bbf1948bf Added localization in EN/FR 2024-04-25 01:02:46 +02:00
Valentin Barat
b4103bfc25 Made base max ban duration to 1day 2024-04-25 00:52:40 +02:00
Valentin Barat
a0b2e59357 Better duration check 2024-04-25 00:47:54 +02:00
Valentin Barat
fb251aadef Added max ban duration and perm ban right 2024-04-25 00:46:23 +02:00
Dliix66
00343b6b66 Merge branch 'daffyyyy:main' into main 2024-04-25 00:26:01 +02:00
Dawid Bepierszcz
270b36fa26 1.3.9a
**MAJOR UPDATE**

- New database schema
- Added `css_admins_flags` table
- Added `css_unmutes` table
- Added `css_unbans` table
2024-04-20 18:56:41 +02:00
Dawid Bepierszcz
7a8fd066f7 1.3.8b
- Plugin checks every minute if a player is banned
- Minor changes
- Removed bot_quota warning
- NET8
2024-04-16 22:08:44 +02:00
Valentin Barat
90556e66b2 Fixed client commands in custom commands 2024-04-14 19:36:28 +02:00
Dawid Bepierszcz
143f0b7e1b Merge pull request #94 from dollannn/main
Feature: Penalty embed
2024-04-02 19:57:22 +02:00
Dawid Bepierszcz
d8a1a1e471 Merge branch 'main' of https://github.com/dollannn/CS2-SimpleAdmin into pr/94 2024-04-02 19:56:25 +02:00
Dawid Bepierszcz
3abf246f9b fixed reloadadmins 2024-04-02 19:55:48 +02:00
Dawid Bepierszcz
d1fdaa4ecf Merge branch 'main' into main 2024-04-02 19:52:22 +02:00
Dawid Bepierszcz
89d27e1bd3 Added translation
- Minor changes
- Version bump
- Added translation
2024-04-02 19:42:16 +02:00
Dawid Bepierszcz
bd82a981f5 Merge pull request #85 from Dliix66/main
Fixed Discord web hooks with the menu and several bug fixes/improvments on the menus
2024-04-02 18:34:18 +02:00
Dollan
052cea5ce3 feat: embed is now integrated with all penalties 2024-04-01 18:30:45 +02:00
Dollan
ba2bf90340 fix: add hostname as embed description 2024-04-01 18:30:03 +02:00
Dollan
8f80e13100 feat: implement penaltylog on gag 2024-03-31 01:23:35 +01:00
Dollan
a377871951 fix: if punishment is 0 ie permanent show permanent 2024-03-31 01:23:15 +01:00
Dollan
8df1e70d5a feat: add penalty embed helper function 2024-03-31 01:06:42 +01:00
Dollan
d8e30e02e9 fix: remove repetitive webhook snippet and replace it with helper function 2024-03-30 22:50:16 +01:00
Dollan
b2c56d3fa1 feat: add helper function to send logs to discord webhook 2024-03-30 22:41:27 +01:00
Dawid Bepierszcz
5609748d19 Update README.md 2024-03-30 10:57:25 +01:00
Dawid Bepierszcz
9d513a92a1 Fix css_sa_upgrade 2024-03-30 10:56:25 +01:00
Dawid Bepierszcz
3a977f688c 1.3.7a
- Throwing more informations
- Updated CounterStrikeSharp
- Fixed problem with expiring bans via `css_addban`
- Added metrics
- Minor changes
2024-03-30 10:49:06 +01:00
Dawid Bepierszcz
143dc138f0 1.3.6.d - fix 2024-03-24 07:13:20 +01:00
Dawid Bepierszcz
a8904f2d8a 1.3.6d - IMPORTANT
- Fixed pool size
- Fixed `css_rename`
- Updated CounterStrikeSharp
- Minor changes
2024-03-23 23:41:17 +01:00
Valentin Barat
39404e52ac Fixed hmtl-incompatible player names 2024-03-21 23:36:56 +01:00
Valentin Barat
0cca38b10b Improved player selection in menus 2024-03-16 16:08:28 +01:00
Valentin Barat
aac5caa565 fixed typo in freeze 2024-03-16 16:02:04 +01:00
Valentin Barat
90c0564f56 Discord webhook from menus 2024-03-16 16:01:26 +01:00
Valentin Barat
0f4a2835d2 Added gravity and set money in fun commands menu 2024-03-16 11:51:44 +01:00
Valentin Barat
c6126ab2ec Fixed Respawn menu that was disabling dead players instead of alive ones 2024-03-16 11:32:58 +01:00
Valentin Barat
60c76562f9 Added ExecuteOnClient option on custom commands 2024-03-13 11:46:09 +01:00
Valentin Barat
ac940259f7 Moved custom commands to its specific menu 2024-03-13 11:43:33 +01:00
Dawid Bepierszcz
cce265c6b7 1.3.6c
- Fixed kick reason
- Small changes
- Fixed map name in css_map command
2024-03-11 01:16:34 +01:00
Dawid Bepierszcz
525905194c 1.3.6b
- Fixed ban checking
- Small changes
- Updated css
2024-03-09 12:06:50 +01:00
Dawid Bepierszcz
2ab2f9c4dc 1.3.6a
- Minor changes
- Added `css_gravity` command
- Added `css_money` command
- Changed Utc time to LocalTime
- Updated translations (ChatGPT generated)
- Updated css version
2024-03-07 13:34:31 +01:00
Dawid Bepierszcz
da6fb2fc22 1.3.5a
- Added custom command to menu
- Better ungag/unmute/unsilence handling
- Fixed css_psay from console
2024-03-02 22:16:38 +01:00
Dawid Bepierszcz
7d5166cf4b Merge pull request #67 from Dliix66/main
Added custom server commands
2024-03-02 22:14:14 +01:00
Dawid Bepierszcz
a8c4c1f9fa - 1.3.4a
- Resolved conflicts?
2024-03-02 21:53:48 +01:00
Dawid Bepierszcz
3c42acf15e Revert "1.3.5a"
This reverts commit f61a5ff6a8.
2024-03-02 21:53:06 +01:00
Dawid Bepierszcz
f61a5ff6a8 1.3.5a
- Resolved conflicts
2024-03-02 21:52:21 +01:00
Dawid Bepierszcz
229b8d73a3 1.3.4a
- Minor changes
- Escape kick reason @poggu suggestion
- Auto-updater for config
- Using UTC time
- Added expiring IP bans after x days (`ExpireOldIpBans` in config => value = days, 0 = disabled)
- Added exception message to database error
- Fixed? ungag/unmute/unsilence commands
- Updated css version to `178`
- Changed `css_adminhelp` command to use new file `admin_help.txt` as output
2024-03-01 12:38:46 +01:00
Valentin Barat
650c115a88 Updated version to 1.4.0a 2024-03-01 02:20:28 +01:00
Valentin Barat
4d3cefc495 Updated config version to 7 2024-03-01 02:13:50 +01:00
Valentin Barat
5d58465c74 Working custom server commands 2024-03-01 02:12:10 +01:00
Dawid Bepierszcz
5bf966f9cd 1.3.3a
- Fixed godmode
- Added logging commands to simpleadmin logs file
- Fixed votes?
- Added updating player_ip and player_name after connect with ban issued by css_addban
2024-02-21 13:14:46 +01:00
Dawid Bepierszcz
619bdfbb14 lang files 2024-02-14 01:56:26 +01:00
Dawid Bepierszcz
f4f669d498 Merge pull request #62 from Dliix66/feature/menu
Fixed maps not being displayed in the menus
2024-02-14 01:54:17 +01:00
Dawid Bepierszcz
8e1a1b2ecf Small changes
- Small changes
- Added `ru` and `pt-br` lang
2024-02-14 01:53:13 +01:00
Valentin Barat
64803ebff2 Updated version to 1.3.2b 2024-02-13 23:54:57 +01:00
Valentin Barat
285429dc66 Fixed instance default value not having the config set 2024-02-13 23:46:46 +01:00
Dawid Bepierszcz
5b36a5fc62 Merge pull request #49 from Dliix66/feature/menu
Changed css_admin command to open the admin menu
2024-02-13 01:37:23 +01:00
Dawid Bepierszcz
4e898a6509 Minor changes 2024-02-13 01:35:45 +01:00
Valentin Barat
af66802b37 Added Change Team 2024-02-13 00:33:07 +01:00
Valentin Barat
6c44281ca5 using Helper.GetValidPlayers() 2024-02-13 00:15:39 +01:00
Valentin Barat
5edddbb70f added kick reason 2024-02-13 00:11:38 +01:00
Valentin Barat
a8a35544c0 Fixed freeze 2024-02-12 23:47:23 +01:00
Valentin Barat
74852f0651 added client only of css_admin 2024-02-12 23:37:55 +01:00
Valentin Barat
1882b14657 Using type instance 2024-02-12 23:32:35 +01:00
Valentin Barat
c3dd3bed10 Added player names in sub menus 2024-02-12 22:53:08 +01:00
Valentin Barat
af73447b9e Added Admins management menu 2024-02-12 22:52:50 +01:00
Valentin Barat
7386a2aae2 Made Admin menu visible only for root 2024-02-12 22:16:07 +01:00
Valentin Barat
effe97a210 enabled fun actions menu 2024-02-12 22:10:00 +01:00
Valentin Barat
3b6b7fbcab Added alive player filter to PlayersMenu 2024-02-12 22:09:52 +01:00
Valentin Barat
9a7a143478 re-refactored css_who 2024-02-12 22:03:09 +01:00
Valentin Barat
fe834d8abc Fixed kick targets 2024-02-12 22:01:25 +01:00
Valentin Barat
eb9519c156 Fun commands done 2024-02-12 22:01:09 +01:00
Valentin Barat
8750a66eef Fun menu done without guns, need to test 2024-02-12 17:46:45 +01:00
Valentin Barat
3317182b9a Created fun menu 2024-02-12 17:00:47 +01:00
Valentin Barat
fa5296fec7 Fixed slap moving the player's aim 2024-02-12 16:44:50 +01:00
Valentin Barat
0a2d19accd Added menu type config 2024-02-12 16:41:00 +01:00
Valentin Barat
c2f7b3be08 added french translation 2024-02-12 16:23:30 +01:00
Valentin Barat
b31bb871a6 Added default Maps in the config 2024-02-12 15:56:45 +01:00
Valentin Barat
00819beec5 restored css_admin to open the menu 2024-02-12 15:42:55 +01:00
Valentin Barat
ae8d9db5e4 Added Silence command in menu 2024-02-12 15:15:49 +01:00
Valentin Barat
a3ebcccb6f Merge finished 2024-02-12 15:11:31 +01:00
Valentin Barat
dd3ccf4069 Merge branch 'main' into feature/menu
# Conflicts:
#	CS2-SimpleAdmin.cs
#	Config.cs
#	Extensions/PlayerExtensions.cs
2024-02-12 14:40:55 +01:00
Valentin Barat
3413200d8e using config maps 2024-02-12 14:21:12 +01:00
Valentin Barat
6dccddf929 Updated config to add workshop maps 2024-02-12 14:21:02 +01:00
Valentin Barat
751c97b4cf Added restart game and change map menus 2024-02-12 14:19:44 +01:00
Dawid Bepierszcz
b4c259595e Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-02-12 14:11:44 +01:00
Dawid Bepierszcz
d8e8073dd6 Fix? for messages 2024-02-12 14:11:34 +01:00
Valentin Barat
377049c020 Added css_restartgame 2024-02-12 14:07:29 +01:00
Dawid Bepierszcz
74c773aaea Merge pull request #58 from daffyyyy/dev
Update CS2-SimpleAdmin.cs
2024-02-12 13:44:54 +01:00
Dawid Bepierszcz
e2df860b6f Update CS2-SimpleAdmin.cs 2024-02-12 13:44:05 +01:00
Dawid Bepierszcz
b1e497ea3f Merge pull request #57 from daffyyyy/dev
1.3.1a
2024-02-12 13:04:27 +01:00
Dawid Bepierszcz
953234078c 1.3.1a
- Added `css_tp`
- Added `css_bring`
- Added logging commands to discord
- Small refactor
2024-02-12 13:03:02 +01:00
Dawid Bepierszcz
bda704e843 test
test
2024-02-12 13:00:38 +01:00
Dawid Bepierszcz
e4e1023684 Update build.yml 2024-02-12 12:58:27 +01:00
Dawid Bepierszcz
5d02343268 Update build.yml 2024-02-12 12:56:57 +01:00
Valentin Barat
778c93b170 Fixed merge issues 2024-02-12 11:46:16 +01:00
Valentin Barat
9cecc19060 Merge branch 'main' into feature/menu
# Conflicts:
#	CS2-SimpleAdmin.cs
2024-02-12 11:41:44 +01:00
Dawid Bepierszcz
f0b5b820e2 Merge pull request #56 from RoyZ-iwnl/main
Create zh-Hans.json
2024-02-12 11:13:09 +01:00
Dawid Bepierszcz
6a182fff9d 1.3.0f
- Fixed `css_kick`
- Fixed gag/mute/silence on connect
- Additional check in immunity
2024-02-11 16:27:54 +01:00
RoyZ
cd8ee681b2 Create zh-Hans.json
add Chinese translation
2024-02-11 14:13:09 +08:00
Dawid Bepierszcz
42849c231f Merge pull request #54 from criskkky/main
1.3.0e
2024-02-11 04:16:38 +01:00
Dawid Bepierszcz
76914be555 Update es.json 2024-02-11 04:15:04 +01:00
cristóbal
aadcaa0e64 1.3.0e
Updated: Spanish Language Support
2024-02-11 00:04:08 -03:00
Dawid Bepierszcz
0b2a520a07 1.3.0e
- Added `css_sa_upgrade` command
2024-02-11 03:39:42 +01:00
Dawid Bepierszcz
79bbe0f4c5 1.3.0e
- Added `css_rename`
- Added `css_silence`
- Added `css_addsilence`
- Added `css_unsilence`
- PlayerPenaltyManager class
- Fix for invalid players

New commands localized only for `pl` and `en`, if u can please make pr for other languages
2024-02-11 03:24:27 +01:00
Dawid Bepierszcz
01ceb104c5 Update Events.cs 2024-02-10 15:31:09 +01:00
Dawid Bepierszcz
e401fe7c8b 1.3.0d
- Fixed noclip
- Fixed freeze
- Fixed vote
2024-02-09 21:54:55 +01:00
Dawid Bepierszcz
fbed647699 1.3.0c
- CounterStrikeSharp v163
2024-02-08 11:11:53 +01:00
Dawid Bepierszcz
aa95815fbd Added info about crashes with bots 2024-02-06 00:57:28 +01:00
Dawid Bepierszcz
3793385ce4 1.3.0b
- Minor changes
- Fixed `css_players`
- Probably fixed problems with taking actions with bots
2024-02-04 21:04:22 +01:00
Valentin Barat
c8c0a3f0ba Updating pawn's HP on slap 2024-02-04 20:36:18 +01:00
Valentin Barat
5991f85919 Fixed defualt kick reason 2024-02-04 20:34:14 +01:00
Valentin Barat
a85e5c69ee Last merge conflicts 2024-02-04 15:49:37 +01:00
Valentin Barat
40ad4b995c Merge branch 'main' into feature/menu
# Conflicts:
#	CS2-SimpleAdmin.cs
2024-02-04 15:10:30 +01:00
Valentin Barat
d3062b5f7e Fixed respawn 2024-02-04 14:54:08 +01:00
Dawid Bepierszcz
131030a2cd 1.3.0a
- Fixed crashing on servers with a lot of players
- Every command chat message respect player language for now css_lang or use https://github.com/aprox2/GeoLocationLanguageManagerPlugin to detect language related on player ip
- Fixed css_respawn
- Fixed css_vote player can't vote multiple times
- Added TeamSwitchType to config, if set to 0 plugin always slay player on css_team command
- Minor changes
2024-02-04 13:25:15 +01:00
Valentin Barat
a9f3196441 Merge done? 2024-02-03 23:37:01 +01:00
Valentin Barat
bdb90b0cc6 Merge branch 'main' into feature/menu
# Conflicts:
#	CS2-SimpleAdmin.cs
2024-02-03 23:30:25 +01:00
Dawid Bepierszcz
93faad27c1 Update build.yml 2024-02-02 23:00:16 +01:00
Dawid Bepierszcz
7251516bb4 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-02-02 22:57:06 +01:00
Dawid Bepierszcz
175d6df250 1.2.9a - fix
- Fixed native in non-main thread
2024-02-02 22:57:05 +01:00
Dawid Bepierszcz
7e5dc7b612 Update build.yml 2024-02-02 22:16:18 +01:00
Dawid Bepierszcz
0572ad7d32 1.2.9a
- Major changes
- Fixed `css_respawn`
- Added discord webhook
- Refactoring database class
2024-02-02 22:02:09 +01:00
Dawid Bepierszcz
86e837931c 1.2.9a
- Major changes
- Fixed `css_respawn`
- Added discord webhook
- Refactoring database class
2024-02-02 21:59:21 +01:00
Dawid Bepierszcz
b2f1afd7e7 Merge pull request #48 from originalaidn/main
huge modification
2024-02-02 21:53:23 +01:00
AiDN™
705bacccae Merge branch 'daffyyyy:main' into main 2024-02-01 13:30:04 +01:00
AiDN™
58086c4009 added windows signature
0 compile error/warning, but not quite sure if this is the safest/best way to check signatures
2024-02-01 13:29:12 +01:00
AiDN™
f15061793a edited css_say logging 2024-02-01 13:14:19 +01:00
Valentin Barat
5035f88da0 Revert to internal 2024-02-01 00:19:05 +01:00
Dawid Bepierszcz
4e597d73a5 Small changes
- Correct version
- Null warnings
2024-02-01 00:03:34 +01:00
Dawid Bepierszcz
13d36dca12 Revert "Small changes"
This reverts commit 9acab82f86.
2024-02-01 00:01:57 +01:00
Dawid Bepierszcz
9acab82f86 Small changes
- Correct version
- null warnings
2024-01-31 23:57:14 +01:00
Valentin Barat
66f0ebe08c Enabled other menus for visual screenshot 2024-01-31 23:08:26 +01:00
Valentin Barat
0cd1653185 Commented out css_who as its not relevant IMO 2024-01-31 23:06:04 +01:00
Valentin Barat
fb827f4f0d Changed commands to open the menu 2024-01-31 23:04:11 +01:00
Valentin Barat
6848847f21 default null instance 2024-01-31 22:10:45 +01:00
Valentin Barat
a80de2d7dc Change team down 2024-01-31 22:05:48 +01:00
Valentin Barat
a266f776a8 Duration in minutes 2024-01-31 21:45:42 +01:00
Valentin Barat
60ebb698a3 ban/gag/mute 2024-01-31 21:44:38 +01:00
Valentin Barat
5fbd21aec2 Slay and kick done 2024-01-31 18:03:08 +01:00
Valentin Barat
232f487d01 Fixed who 2024-01-31 17:39:03 +01:00
Valentin Barat
f622701f5e Made slap menu 2024-01-31 17:29:28 +01:00
Valentin Barat
65b33c17ea Moved css_who command to ManagePlayersMenu.cs 2024-01-31 17:19:13 +01:00
Valentin Barat
4fd268b235 Base menu structure done 2024-01-31 17:06:54 +01:00
Valentin Barat
bad4289c5c Added instance getter on the base plugin class for the menus 2024-01-31 16:05:08 +01:00
Valentin Barat
fd97e43490 Updated version to 1.3.0 2024-01-31 15:59:19 +01:00
Dawid Bepierszcz
a59cf5a7da Merge pull request #47 from KillerRoi/main
Added Arabic Translation
2024-01-31 12:58:10 +01:00
originalaidn
dd7a2f4c16 huge modification
- modified respawn, now the players need to respawn
- added stealth command as hide, removed the team selection menu (from CSS discord)
- removed silentmode
- added discord webhook logging system (need to set in config -DiscordWebhook and edit Version to 4)

in the future, need to add every color to replace thing and maybe need to remove those replace messages and add to sendwebhook but not yet.
2024-01-31 11:17:12 +01:00
KillerRoi
fbf395d327 Fixed 2024-01-28 20:49:05 +05:30
KillerRoi
8ca82804a2 Fixed sa_adminhelp not being readable 2024-01-28 20:48:02 +05:30
KillerRoi
4b1cb3573b Fixed 2024-01-28 20:47:05 +05:30
KillerRoi
5dc35ddb73 Fixed second line not being able to read 2024-01-28 20:43:11 +05:30
KillerRoi
31592bf1a5 .. 2024-01-28 20:42:24 +05:30
KillerRoi
a3471e2091 .. 2024-01-28 20:40:59 +05:30
KillerRoi
dfd9bd5329 Added Arabic Translation 2024-01-28 20:39:29 +05:30
Dawid Bepierszcz
e028bef5b0 1.2.8d
- Minor changes
- Added the ability to change the map to a workshop map via the `css_map ws:id/name` command
- Fixed the `css_wsmap` command and retained it for backward compatibility
- Changed gags from userid to steamid
2024-01-27 23:56:13 +01:00
Dawid Bepierszcz
67ad1851bf 1.2.8c
- Updated CounterStrikeSharp to 159
- Minor changes
2024-01-26 13:45:16 +01:00
Dawid Bepierszcz
e584316a28 1.2.8b
- Changing connect event, in previous sometimes player == null
2024-01-25 21:09:24 +01:00
Dawid Bepierszcz
450a7804c6 1.2.8a
- Small fixes
2024-01-25 01:00:31 +01:00
Dawid Bepierszcz
855f087a7b 1.2.8a
- Updated minimum CounterStrikeSharp version to **154**
- Fixed? issue with checking ban status for player with authorization problem
- Added additional checks for immunity
- Added new config variable `BanType` if `1` = `SteamID + IP`, if `0` = `SteamID`
2024-01-24 23:50:49 +01:00
Dawid Bepierszcz
b5600d7e73 1.2.7e
- Minor changes
2024-01-20 17:24:18 +01:00
Dawid Bepierszcz
6640d00442 Update README.md 2024-01-20 02:25:05 +01:00
Dawid Bepierszcz
8aee44f709 Create FUNDING.yml 2024-01-20 02:18:41 +01:00
Dawid Bepierszcz
374f6002eb 1.2.7d
- Fix for console commands exception (caused by silentmode)
- Updated css
2024-01-19 21:03:24 +01:00
Dawid Bepierszcz
99ce2cdf6a 1.2.7c
- `css_hide` - Hide on scoreboard
Code stolen from https://github.com/DeadSwimek/cs2-hideadmin
Thanks DeadSwimek ;]
2024-01-17 23:23:31 +01:00
Dawid Bepierszcz
38020e6509 Update README.md 2024-01-17 23:12:22 +01:00
Dawid Bepierszcz
d751bdadbd 1.2.7b
- Back to net7
- SilentMode for admins
- Adding global admins via command
2024-01-17 23:08:52 +01:00
Dawid Bepierszcz
64c806f3d7 Update CS2-SimpleAdmin.cs 2024-01-15 02:39:33 +01:00
Dawid Bepierszcz
8c4cb49d02 1.2.7a
- CounterStrikeSharp 146
2024-01-15 02:39:20 +01:00
Dawid Bepierszcz
2d41a62b52 1.2.6e
- Finally? fixed sqladmins
2024-01-13 23:12:23 +01:00
Dawid Bepierszcz
48baac0a7b Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-01-13 00:19:15 +01:00
Dawid Bepierszcz
8d365b670e 1.2.6d
- Probably workaround for sql admins
2024-01-13 00:19:13 +01:00
Dawid Bepierszcz
6a82d72244 Update README.md 2024-01-10 03:14:01 +01:00
Dawid Bepierszcz
e602eaa445 Update README.md 2024-01-10 03:13:23 +01:00
Dawid Bepierszcz
75f18d3c0a Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-01-10 00:03:37 +01:00
Dawid Bepierszcz
40611e9126 Grammar 2024-01-10 00:03:29 +01:00
Dawid Bepierszcz
fa8b7a877f Merge pull request #29 from rcon420/patch-2
lv grammar changes
2024-01-10 00:01:07 +01:00
Dawid Bepierszcz
71af90031e Database connection exception fix, additional ip check (problem with steam auth) 2024-01-09 23:05:49 +01:00
rcon_password
07924a6a74 lv grammar changes 2024-01-09 22:52:56 +02:00
Dawid Bepierszcz
f78cd888de Fix 2024-01-09 12:08:14 +01:00
Dawid Bepierszcz
d173816492 Update AdminSQLManager.cs 2024-01-08 22:34:11 +01:00
Dawid Bepierszcz
dd5e72e873 Update 1.2.6a
- Added server_id
- Added immunity
2024-01-08 22:29:36 +01:00
Dawid Bepierszcz
cfe79109a8 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2024-01-05 01:04:13 +01:00
Dawid Bepierszcz
18122cdc08 1.2.5a
-Minor changes
- Fixed `css_speed`
2024-01-05 01:04:08 +01:00
Dawid Bepierszcz
ac92f1cb4e Merge pull request #13 from BMathers35/beta
Turkish language support
2024-01-04 17:06:15 +01:00
Dawid Bepierszcz
6d6ff53b02 Merge pull request #20 from DEADLYDEVIL-IR/main
Persian Language Support
2024-01-04 17:06:05 +01:00
Dawid Bepierszcz
675c7bdcee Merge pull request #23 from criskkky/main
Spanish Language Support
2024-01-04 17:05:40 +01:00
cristóbal
c839e69804 Add files via upload 2024-01-03 22:32:38 -03:00
DEADLYDEVIL
3930d2d4bd add some changes 2023-12-25 21:54:19 +03:30
DEADLYDEVIL
6a5165a513 Add persian lang 2023-12-24 19:11:33 +03:30
daffyyyy
5dc14e3301 12.4b 2023-12-18 14:30:26 +01:00
daffyyyy
27582b72af 1.2.4b
- Cache for mysql admins
2023-12-18 14:29:56 +01:00
daffyyyy
f95031a3f5 1.2.4a
- mysql admins
- minor changes
2023-12-18 13:46:58 +01:00
BMathers
7a73212d2e Turkish language support 2023-12-15 02:16:08 +03:00
daffyyyy
270e3bd858 1.2.3a
- Fixed pern mute when entering the server
- New command `css_strip`
- Changed `css_team` command added `swap` as team, and `-k` argument to kill player
2023-12-14 21:43:40 +01:00
daffyyyy
b8075ffa8c Update lv.json 2023-12-13 20:20:55 +01:00
daffyyyy
28f6cf63fe 1.2.2a UPDATE
- Fixed css_addban, css_addmute
- New command `css_hp`
- New command `css_god`
- New command `css_speed`
2023-12-13 20:20:12 +01:00
Dawid Bepierszcz
e040dbced5 Merge pull request #11 from rcon420/patch-1
Create lv.json
2023-12-13 19:57:00 +01:00
rcon_password
7e7fc5c885 Create lv.json 2023-12-13 18:19:16 +02:00
daffyyyy
15ab2d9e65 Update CS2-SimpleAdmin.cs 2023-12-12 20:06:01 +01:00
daffyyyy
2105d80f52 Fixed css_team message 2023-12-12 20:01:30 +01:00
daffyyyy
d677551463 Update CS2-SimpleAdmin.cs 2023-12-12 19:51:39 +01:00
daffyyyy
efd9ab5c22 css_vote and small fixes 2023-12-12 19:46:19 +01:00
daffyyyy
e6fb8bc402 css_rcon caller fix 2023-12-12 13:31:27 +01:00
daffyyyy
9712f04e38 Update Events.cs 2023-12-12 12:51:31 +01:00
daffyyyy
719caeafef Probably fix for auto ungag? 2023-12-12 12:50:02 +01:00
daffyyyy
22bb7ebac0 Update README.md 2023-12-12 12:31:54 +01:00
daffyyyy
b813a422a2 Update CS2-SimpleAdmin.cs 2023-12-12 12:24:09 +01:00
daffyyyy
36e5cd48a5 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2023-12-12 12:22:16 +01:00
daffyyyy
34342c5134 1.2.0a UPDATE
Required CSS 124
2023-12-12 12:20:27 +01:00
Dawid Bepierszcz
5183661c2a Merge pull request #9 from DEADLYDEVIL-IR/main
Added CSS_GIVE
2023-12-10 15:28:17 +01:00
BuildTools
6c075cdafe fixed weapon checking 2023-12-10 01:33:59 +03:30
BuildTools
a56b5f7b3f fixed banned weapon checking 2023-12-10 01:15:52 +03:30
BuildTools
07b79cb91a Added a check for knifes 2023-12-10 00:56:30 +03:30
BuildTools
607c38e992 changed permissions to @css/give 2023-12-09 10:58:21 +03:30
BuildTools
dbdefa8168 fixed writing player name after giving waepon in chat 2023-12-09 10:53:13 +03:30
BuildTools
b8048e1500 Fixed server printing map instead of given weapon 2023-12-09 10:49:19 +03:30
BuildTools
59975bb49d Add the css_give command and add in README.MD 2023-12-09 10:43:33 +03:30
daffyyyy
454643431a Update Events.cs 2023-12-06 03:01:07 +01:00
daffyyyy
6ac370df74 Update CS2-SimpleAdmin.cs 2023-12-06 02:55:07 +01:00
daffyyyy
f3a1200492 Changed adminchat to teamchat 2023-12-06 02:49:31 +01:00
daffyyyy
90ca6c1ac2 Admin chat fix 2023-12-06 02:44:30 +01:00
daffyyyy
29fe968155 css_asay and @message 2023-12-06 02:39:08 +01:00
daffyyyy
fdbf1b1a5c Respecting immunity and css_team command 2023-12-06 01:55:30 +01:00
daffyyyy
dcc85b21c3 Hotfix for cssharp 101
Hotfix for non-main thread
2023-12-04 17:34:21 +01:00
daffyyyy
9c293e6201 . 2023-12-04 00:43:47 +01:00
daffyyyy
bd0f21b2ad Auto unmute after mute, max 30 minutes 2023-12-04 00:16:21 +01:00
daffyyyy
13e170a9c4 Changed to async, instantly mute in CS2-Tags plugin
- Changed to async
- instantly mute in CS2-Tags plugin
2023-12-03 23:37:44 +01:00
daffyyyy
696b8c3877 Added css_wsmap
- css_wsmap <name or id> - Change workshop map // @css/changemap
2023-12-03 21:18:43 +01:00
Dawid Bepierszcz
f19c594568 Merge pull request #1 from stefanx111/main
admin name in css_say
2023-12-03 21:11:00 +01:00
Dawid Bepierszcz
90efff5d24 Update README.md 2023-12-03 21:09:20 +01:00
Dawid Bepierszcz
2ea03caeed Update build.yml 2023-12-03 21:08:37 +01:00
daffyyyy
d16b92e694 IP address banning
- IP address banning

**Require CounterStrikeSharp v98**
2023-12-03 21:05:38 +01:00
daffyyyy
0f32daa4c0 KickTime in config, and gag 2023-12-03 19:22:34 +01:00
StefanX
86378324a7 revert: range syntax instead of Substring 2023-12-03 18:19:33 +02:00
StefanX
bce6bd4348 admin name in css_say 2023-12-03 17:31:10 +02:00
daffyyyy
5ea71f1679 New commands, addban and unban 2023-12-03 13:37:54 +01:00
daffyyyy
53812148b3 Fixed map permission 2023-12-03 01:23:46 +01:00
daffyyyy
d496977c76 Forgotten column with the reason for the ban
OOOooops :D
2023-12-03 01:22:18 +01:00
daffyyyy
35e6a52ad8 Additional commands 2023-12-03 00:50:02 +01:00
daffyyyy
68a446a515 css_admin command
Added css_admin command for displaying all commands
2023-12-03 00:15:55 +01:00
daffyyyy
0a0c0cfbc4 Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2023-12-02 23:28:33 +01:00
daffyyyy
39e73763b7 Fix in creating table 2023-12-02 23:28:09 +01:00
Dawid Bepierszcz
ad621b9cb8 Update README.md 2023-12-02 23:18:06 +01:00
daffyyyy
31bc91e0a4 Map and say commands
Source https://github.com/Hackmastr/css-basic-admin
2023-12-02 23:16:04 +01:00
daffyyyy
fa508c965a Merge branch 'main' of https://github.com/daffyyyy/CS2-SimpleAdmin 2023-12-02 23:02:57 +01:00
daffyyyy
d2d1f41e80 Fixed failstart :D 2023-12-02 23:02:36 +01:00
Dawid Bepierszcz
ffcd623628 Update README.md 2023-12-02 22:48:52 +01:00
Dawid Bepierszcz
e4a6c733ab Create build.yml 2023-12-02 22:47:17 +01:00
daffyyyy
6bcb10c5cf Initial commit 2023-12-02 22:44:11 +01:00
151 changed files with 18884 additions and 0 deletions

1
.github/FUNDING.yml vendored Normal file
View File

@@ -0,0 +1 @@
ko_fi: daffyy

122
.github/workflows/build.yml vendored Normal file
View File

@@ -0,0 +1,122 @@
name: Build and Publish
on:
workflow_dispatch:
push:
branches: [ "main" ]
paths-ignore:
- '**/README.md'
pull_request:
branches: [ "main" ]
paths-ignore:
- '**/README.md'
env:
PROJECT_PATH_CS2_SIMPLEADMIN: "CS2-SimpleAdmin/CS2-SimpleAdmin.csproj"
PROJECT_NAME_CS2_SIMPLEADMIN: "CS2-SimpleAdmin"
PROJECT_PATH_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi/CS2-SimpleAdminApi.csproj"
PROJECT_NAME_CS2_SIMPLEADMINAPI: "CS2-SimpleAdminApi"
PROJECT_PATH_STEALTHMODULE: "Modules/CS2-SimpleAdmin_StealthModule/CS2-SimpleAdmin_StealthModule.csproj"
PROJECT_NAME_STEALTHMODULE: "CS2-SimpleAdmin_StealthModule"
OUTPUT_PATH: "./counterstrikesharp"
TMP_PATH: "./tmp"
jobs:
build:
runs-on: ubuntu-latest
permissions: write-all
outputs:
build_version: ${{ steps.get_version.outputs.VERSION }}
steps:
- uses: actions/checkout@v4
- name: Setup .NET
uses: actions/setup-dotnet@v4
with:
dotnet-version: 8.0.x
- name: Get Version
id: get_version
run: echo "VERSION=$(cat CS2-SimpleAdmin/VERSION)" >> $GITHUB_OUTPUT
- name: Restore & Build All Projects
run: |
dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }}
dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMIN }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}
dotnet restore ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }}
dotnet build ${{ env.PROJECT_PATH_CS2_SIMPLEADMINAPI }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}
dotnet restore ${{ env.PROJECT_PATH_STEALTHMODULE }}
dotnet build ${{ env.PROJECT_PATH_STEALTHMODULE }} -c Release -o ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}
- name: Combine projects
run: |
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin
mkdir -p ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
mkdir -p ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMIN }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin/
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_STEALTHMODULE }}/* ${{ env.OUTPUT_PATH }}/plugins/CS2-SimpleAdmin_StealthModule
cp -r ${{ env.TMP_PATH }}/${{ env.PROJECT_NAME_CS2_SIMPLEADMINAPI }}/* ${{ env.OUTPUT_PATH }}/shared/CS2-SimpleAdminApi/
- name: Zip Main Build Output
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
uses: actions/upload-artifact@v4
with:
name: CS2-SimpleAdmin-Build-Artifacts
path: |
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:
needs: build
if: github.event_name == 'push'
runs-on: ubuntu-latest
permissions: write-all
steps:
- uses: actions/checkout@v4
- name: Download build artifacts
uses: actions/download-artifact@v4
with:
name: CS2-SimpleAdmin-Build-Artifacts
path: .
- name: Unzip main build artifact
run: unzip CS2-SimpleAdmin-${{ needs.build.outputs.build_version }}.zip -d ./counterstrikesharp
- name: Publish combined release
uses: ncipollo/release-action@v1.14.0
with:
artifacts: |
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 }}"
tag: "build-${{ needs.build.outputs.build_version }}"
body: |
Place the files in your server as follows:
- CS2-SimpleAdmin:
Place the files inside the addons/counterstrikesharp directory.
After the first launch, configure the plugin using the JSON config file at:
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.

11
.gitignore vendored Normal file
View File

@@ -0,0 +1,11 @@
bin/
obj/
.vs/
.git
.vscode/
.idea/
Modules/CS2-SimpleAdmin_PlayTimeModule
CS2-SimpleAdmin.sln.DotSettings.user
Modules/CS2-SimpleAdmin_ExampleModule/CS2-SimpleAdmin_ExampleModule.sln.DotSettings.user
CS2-SimpleAdmin_BanSoundModule — kopia
*.user

31
CS2-SimpleAdmin.sln Normal file
View File

@@ -0,0 +1,31 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 17
VisualStudioVersion = 17.8.34309.116
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-SimpleAdmin", "CS2-SimpleAdmin\CS2-SimpleAdmin.csproj", "{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "CS2-SimpleAdminApi", "CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj", "{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Debug|Any CPU.Build.0 = Debug|Any CPU
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Release|Any CPU.ActiveCfg = Release|Any CPU
{CC7C3B4D-26C9-4DE7-B4E1-0864350468D0}.Release|Any CPU.Build.0 = Release|Any CPU
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Debug|Any CPU.Build.0 = Debug|Any CPU
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Release|Any CPU.ActiveCfg = Release|Any CPU
{8BEF0C35-7E4E-4BAF-B632-8584FAFCA922}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {86114444-059F-4DB8-9A55-E6D1BB76238D}
EndGlobalSection
EndGlobal

Binary file not shown.

View File

@@ -0,0 +1,170 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin.Api;
public class CS2_SimpleAdminApi : ICS2_SimpleAdminApi
{
public PlayerInfo GetPlayerInfo(CCSPlayerController player)
{
if (!player.UserId.HasValue)
throw new KeyNotFoundException("Player with specific UserId not found");
return CS2_SimpleAdmin.PlayersInfo[player.SteamID];
}
public string GetConnectionString() => CS2_SimpleAdmin.Instance.DbConnectionString;
public string GetServerAddress() => CS2_SimpleAdmin.IpAddress;
public int? GetServerId() => CS2_SimpleAdmin.ServerId;
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(CCSPlayerController player)
{
return PlayerPenaltyManager.GetAllPlayerPenalties(player.Slot);
}
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
public event Action<string, string?, bool, object>? OnAdminShowActivity;
public event Action<int, bool>? OnAdminToggleSilent;
public void OnPlayerPenaltiedEvent(PlayerInfo player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
int duration, int? penaltyId) => OnPlayerPenaltied?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
public void OnPlayerPenaltiedAddedEvent(SteamID player, PlayerInfo? admin, PenaltyType penaltyType, string reason,
int duration, int? penaltyId) => OnPlayerPenaltiedAdded?.Invoke(player, admin, penaltyType, reason, duration, penaltyId, CS2_SimpleAdmin.ServerId);
public void OnAdminShowActivityEvent(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs) => OnAdminShowActivity?.Invoke(messageKey, callerName, dontPublish, messageArgs);
public void OnAdminToggleSilentEvent(int slot, bool status) => OnAdminToggleSilent?.Invoke(slot, status);
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
{
switch (penaltyType)
{
case PenaltyType.Ban:
{
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason);
break;
}
case PenaltyType.Kick:
{
CS2_SimpleAdmin.Instance.Kick(admin, player, reason);
break;
}
case PenaltyType.Gag:
{
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
break;
}
case PenaltyType.Mute:
{
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
break;
}
case PenaltyType.Silence:
{
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
break;
}
case PenaltyType.Warn:
{
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason);
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
}
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1)
{
switch (penaltyType)
{
case PenaltyType.Ban:
{
CS2_SimpleAdmin.Instance.AddBan(admin, steamid, duration, reason);
break;
}
case PenaltyType.Gag:
{
CS2_SimpleAdmin.Instance.AddGag(admin, steamid, duration, reason);
break;
}
case PenaltyType.Mute:
{
CS2_SimpleAdmin.Instance.AddMute(admin, steamid, duration, reason);
break;
}
case PenaltyType.Silence:
{
CS2_SimpleAdmin.Instance.AddSilence(admin, steamid, duration, reason);
break;
}
case PenaltyType.Warn:
{
CS2_SimpleAdmin.Instance.AddWarn(admin, steamid, duration, reason);
break;
}
default:
throw new ArgumentOutOfRangeException(nameof(penaltyType), penaltyType, null);
}
}
public void LogCommand(CCSPlayerController? caller, string command)
{
Helper.LogCommand(caller, command);
}
public void LogCommand(CCSPlayerController? caller, CommandInfo command)
{
Helper.LogCommand(caller, command);
}
public bool IsAdminSilent(CCSPlayerController player)
{
return CS2_SimpleAdmin.SilentPlayers.Contains(player.Slot);
}
public HashSet<int> ListSilentAdminsSlots()
{
return CS2_SimpleAdmin.SilentPlayers;
}
public void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback)
{
if (string.IsNullOrWhiteSpace(name))
throw new ArgumentException("Command name cannot be null or empty.", nameof(name));
ArgumentNullException.ThrowIfNull(callback);
var definition = new CommandDefinition(name, description ?? "No description", callback);
if (!RegisterCommands._commandDefinitions.TryGetValue(name, out var list))
{
list = new List<CommandDefinition>();
RegisterCommands._commandDefinitions[name] = list;
}
list.Add(definition);
}
public void UnRegisterCommand(string commandName)
{
var definitions = RegisterCommands._commandDefinitions[commandName];
if (definitions.Count == 0)
return;
foreach (var definition in definitions)
{
CS2_SimpleAdmin.Instance.RemoveCommand(commandName, definition.Callback);
}
}
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs)
{
Helper.ShowAdminActivity(messageKey, callerName, dontPublish, messageArgs);
}
}

View File

@@ -0,0 +1,268 @@
using System.Reflection;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Commands.Targeting;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
using MySqlConnector;
namespace CS2_SimpleAdmin;
[MinimumApiVersion(300)]
public partial class CS2_SimpleAdmin : BasePlugin, IPluginConfig<CS2_SimpleAdminConfig>
{
internal static CS2_SimpleAdmin Instance { get; private set; } = new();
public override string ModuleName => "CS2-SimpleAdmin" + (Helper.IsDebugBuild ? " (DEBUG)" : " (RELEASE)");
public override string ModuleDescription => "Simple admin plugin for Counter-Strike 2 :)";
public override string ModuleAuthor => "daffyy & Dliix66";
public override string ModuleVersion => "1.7.7-alpha-10";
public override void Load(bool hotReload)
{
Instance = this;
if (hotReload)
{
ServerLoaded = false;
_serverLoading = false;
CacheManager?.Dispose();
CacheManager = new CacheManager();
// OnGameServerSteamAPIActivated();
OnMapStart(string.Empty);
AddTimer(6.0f, () =>
{
if (DatabaseProvider == null) return;
PlayersInfo.Clear();
CachedPlayers.Clear();
BotPlayers.Clear();
foreach (var player in Utilities.GetPlayers().Where(p => p.IsValid && !p.IsHLTV).ToArray())
{
if (!player.IsBot)
PlayerManager.LoadPlayerData(player, true);
else
BotPlayers.Add(player);
};
});
PlayersTimer?.Kill();
PlayersTimer = null;
}
_cBasePlayerControllerSetPawnFunc = new MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>(GameData.GetSignature("CBasePlayerController_SetPawn"));
SimpleAdminApi = new Api.CS2_SimpleAdminApi();
Capabilities.RegisterPluginCapability(ICS2_SimpleAdminApi.PluginCapability, () => SimpleAdminApi);
PlayersTimer?.Kill();
PlayersTimer = null;
PlayerManager.CheckPlayersTimer();
}
public override void OnAllPluginsLoaded(bool hotReload)
{
try
{
MenuApi = MenuCapability.Get();
}
catch (Exception ex)
{
Logger.LogError("Unable to load required plugins ... \n{exception}", ex.Message);
Unload(false);
}
AddTimer(6.0f, () => ReloadAdmins(null));
RegisterEvents();
RegisterCommands.InitializeCommands();
if (!CoreConfig.UnlockConCommands)
{
_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"
);
_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)
{
if (System.Diagnostics.Debugger.IsAttached)
Environment.FailFast(":(!");
Helper.UpdateConfig(config);
_logger = Logger;
Config = config;
bool missing = false;
var cssPath = Path.Combine(Server.GameDirectory, "csgo", "addons", "counterstrikesharp");
var pluginsPath = Path.Combine(cssPath, "plugins");
var sharedPath = Path.Combine(cssPath, "shared");
foreach (var plugin in _requiredPlugins)
{
var pluginDirPath = Path.Combine(pluginsPath, plugin);
var pluginDllPath = Path.Combine(pluginDirPath, $"{plugin}.dll");
if (!Directory.Exists(pluginDirPath))
{
_logger?.LogError(
$"❌ Plugin directory '{plugin}' missing at: {pluginDirPath}"
);
missing = true;
}
if (!File.Exists(pluginDllPath))
{
_logger?.LogError(
$"❌ Plugin DLL '{plugin}.dll' missing at: {pluginDllPath}"
);
missing = true;
}
}
foreach (var shared in _requiredShared)
{
var sharedDirPath = Path.Combine(sharedPath, shared);
var sharedDllPath = Path.Combine(sharedDirPath, $"{shared}.dll");
if (!Directory.Exists(sharedDirPath))
{
_logger?.LogError(
$"❌ Shared library directory '{shared}' missing at: {sharedDirPath}"
);
missing = true;
}
if (!File.Exists(sharedDllPath))
{
_logger?.LogError(
$"❌ Shared library DLL '{shared}.dll' missing at: {sharedDllPath}"
);
missing = true;
}
}
if (missing)
Server.ExecuteCommand($"css_plugins unload {ModuleName}");
Instance = this;
if (Config.DatabaseConfig.DatabaseType.Contains("mysql", StringComparison.CurrentCultureIgnoreCase))
{
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseHost) ||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseName) ||
string.IsNullOrWhiteSpace(config.DatabaseConfig.DatabaseUser))
{
throw new Exception("[CS2-SimpleAdmin] You need to setup MySQL credentials in config!");
}
var builder = new MySqlConnectionStringBuilder()
{
Server = config.DatabaseConfig.DatabaseHost,
Database = config.DatabaseConfig.DatabaseName,
UserID = config.DatabaseConfig.DatabaseUser,
Password = config.DatabaseConfig.DatabasePassword,
Port = (uint)config.DatabaseConfig.DatabasePort,
SslMode = Enum.TryParse(config.DatabaseConfig.DatabaseSSlMode, true, out MySqlSslMode sslMode)
? sslMode
: MySqlSslMode.Preferred,
Pooling = true,
};
DbConnectionString = builder.ConnectionString;
DatabaseProvider = new MySqlDatabaseProvider(DbConnectionString);
}
else
{
if (string.IsNullOrWhiteSpace(config.DatabaseConfig.SqliteFilePath))
{
throw new Exception("[CS2-SimpleAdmin] You need to specify SQLite file path in config!");
}
DatabaseProvider = new SqliteDatabaseProvider(ModuleDirectory + "/" + config.DatabaseConfig.SqliteFilePath);
}
var (success, exception) = Task.Run(() => DatabaseProvider.CheckConnectionAsync()).GetAwaiter().GetResult();
if (!success)
{
if (exception != null)
Logger.LogError("Problem with database connection! \n{exception}", exception);
Unload(false);
return;
}
Task.Run(() => DatabaseProvider.DatabaseMigrationAsync());
if (!Directory.Exists(ModuleDirectory + "/data"))
{
Directory.CreateDirectory(ModuleDirectory + "/data");
}
_localizer = Localizer;
if (!string.IsNullOrEmpty(Config.Discord.DiscordLogWebhook))
DiscordWebhookClientLog = new DiscordManager(Config.Discord.DiscordLogWebhook);
if (Config.EnableUpdateCheck)
Task.Run(async () => await PluginInfo.CheckVersion(ModuleVersion, Logger));
PermissionManager = new PermissionManager(DatabaseProvider);
BanManager = new BanManager(DatabaseProvider);
MuteManager = new MuteManager(DatabaseProvider);
WarnManager = new WarnManager(DatabaseProvider);
}
private static TargetResult? GetTarget(CommandInfo command, int argument = 1)
{
var matches = command.GetArgTargetResult(argument);
if (!matches.Any())
{
command.ReplyToCommand($"Target {command.GetArg(argument)} not found.");
return null;
}
if (command.GetArg(argument).StartsWith('@'))
return matches;
if (matches.Count() == 1)
return matches;
command.ReplyToCommand($"Multiple targets found for \"{command.GetArg(argument)}\".");
return null;
}
public override void Unload(bool hotReload)
{
CacheManager?.Dispose();
CacheManager = null;
PlayersTimer?.Kill();
PlayersTimer = null;
UnregisterEvents();
if (hotReload)
PlayersInfo.Clear();
else
Server.ExecuteCommand($"css_plugins unload {ModuleDirectory}");
}
}

View File

@@ -0,0 +1,158 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CS2_SimpleAdmin</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<CopyLocalLockFileAssemblies>true</CopyLocalLockFileAssemblies>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<PropertyGroup Condition="'$(Configuration)' == 'Release'">
<DebugType>none</DebugType>
<DebugSymbols>false</DebugSymbols>
<PublishTrimmed>true</PublishTrimmed>
<DebuggerSupport>false</DebuggerSupport>
<GenerateDependencyFile>false</GenerateDependencyFile>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340">
<PrivateAssets>none</PrivateAssets>
<ExcludeAssets>runtime</ExcludeAssets>
<IncludeAssets>compile; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
</PackageReference>
<PackageReference Include="Dapper" Version="2.1.66" />
<PackageReference Include="MySqlConnector" Version="2.4.0" />
<PackageReference Include="System.Data.SQLite.Core" Version="1.0.119" />
<PackageReference Include="System.Linq.Async" Version="6.0.3" />
<PackageReference Include="ZLinq" Version="1.5.2" />
</ItemGroup>
<ItemGroup>
<ProjectReference Include="..\CS2-SimpleAdminApi\CS2-SimpleAdminApi.csproj" />
</ItemGroup>
<Target Name="ClearBuildFiles" AfterTargets="PostBuildEvent">
<ItemGroup>
<FilesToDelete Include="$(OutDir)Tomlyn.dll" />
<FilesToDelete Include="$(OutDir)Serilog*.dll" />
<FilesToDelete Include="$(OutDir)CS2-SimpleAdminApi.*" />
<FilesToDelete Include="$(OutDir)McMaster*" />
<FilesToDelete Include="$(OutDir)Scrutor.dll" />
</ItemGroup>
<Delete Files="@(FilesToDelete)" />
</Target>
<ItemGroup>
<None Update="lang\**\*.*" CopyToOutputDirectory="PreserveNewest" />
<None Update="Database\Migrations\Mysql\001_CreateTables.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\002_CreateFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\003_ChangeColumnsPosition.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\004_MoveOldFlagsToFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\005_CreateUnbansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\006_ServerGroupsFeature.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\007_ServerGroupsGlobal.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\008_OnlineTimeInPenalties.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\009_BanAllUsedIpAddress.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\010_CreateWarnsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\011_AddRconColumnToServersTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\012_AddUpdatedAtColumnToSaBansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\013_AddNameColumnToSaPlayerIpsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\014_AddIndexesToMutesTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Mysql\015_steamidToBigInt.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\001_CreateTables.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\002_CreateFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\003_ChangeColumnsPosition.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\004_MoveOldFlagsToFlagsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\005_CreateUnbansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\006_ServerGroupsFeature.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\007_ServerGroupsGlobal.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\008_OnlineTimeInPenalties.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\009_BanAllUsedIpAddress.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\010_CreateWarnsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\011_AddRconColumnToServersTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\012_AddUpdatedAtColumnToSaBansTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\013_AddNameColumnToSaPlayerIpsTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\014_AddIndexesToMutesTable.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
<None Update="Database\Migrations\Sqlite\015_steamidToBigInt.sql">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</None>
</ItemGroup>
<ItemGroup>
<None Update="Database\Migrations\*.sql" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<None Update="admin_help.txt" CopyToOutputDirectory="PreserveNewest" />
</ItemGroup>
<ItemGroup>
<Reference Include="MenuManagerApi">
<HintPath>3rd_party\MenuManagerApi.dll</HintPath>
<Private>False</Private>
</Reference>
</ItemGroup>
</Project>

View File

@@ -0,0 +1,260 @@
using System.Diagnostics.CodeAnalysis;
using System.Text.Json;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Commands;
using CounterStrikeSharp.API.Modules.Commands;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin;
public static class RegisterCommands
{
internal static readonly Dictionary<string, IList<CommandDefinition>> _commandDefinitions =
new(StringComparer.InvariantCultureIgnoreCase);
private delegate void CommandCallback(CCSPlayerController? caller, CommandInfo.CommandCallback callback);
private static readonly string CommandsPath = Path.Combine(CS2_SimpleAdmin.ConfigDirectory, "Commands.json");
private static readonly List<CommandMapping> CommandMappings =
[
new("css_ban", CS2_SimpleAdmin.Instance.OnBanCommand),
new("css_addban", CS2_SimpleAdmin.Instance.OnAddBanCommand),
new("css_banip", CS2_SimpleAdmin.Instance.OnBanIpCommand),
new("css_unban", CS2_SimpleAdmin.Instance.OnUnbanCommand),
new("css_warn", CS2_SimpleAdmin.Instance.OnWarnCommand),
new("css_unwarn", CS2_SimpleAdmin.Instance.OnUnwarnCommand),
new("css_asay", CS2_SimpleAdmin.Instance.OnAdminToAdminSayCommand),
new("css_cssay", CS2_SimpleAdmin.Instance.OnAdminCustomSayCommand),
new("css_say", CS2_SimpleAdmin.Instance.OnAdminSayCommand),
new("css_psay", CS2_SimpleAdmin.Instance.OnAdminPrivateSayCommand),
new("css_csay", CS2_SimpleAdmin.Instance.OnAdminCenterSayCommand),
new("css_hsay", CS2_SimpleAdmin.Instance.OnAdminHudSayCommand),
new("css_penalties", CS2_SimpleAdmin.Instance.OnPenaltiesCommand),
new("css_admin", CS2_SimpleAdmin.Instance.OnAdminCommand),
new("css_adminhelp", CS2_SimpleAdmin.Instance.OnAdminHelpCommand),
new("css_addadmin", CS2_SimpleAdmin.Instance.OnAddAdminCommand),
new("css_deladmin", CS2_SimpleAdmin.Instance.OnDelAdminCommand),
new("css_addgroup", CS2_SimpleAdmin.Instance.OnAddGroup),
new("css_delgroup", CS2_SimpleAdmin.Instance.OnDelGroupCommand),
new("css_reloadadmins", CS2_SimpleAdmin.Instance.OnRelAdminCommand),
new("css_reloadbans", CS2_SimpleAdmin.Instance.OnRelBans),
new("css_hide", CS2_SimpleAdmin.Instance.OnHideCommand),
new("css_hidecomms", CS2_SimpleAdmin.Instance.OnHideCommsCommand),
new("css_who", CS2_SimpleAdmin.Instance.OnWhoCommand),
new("css_disconnected", CS2_SimpleAdmin.Instance.OnDisconnectedCommand),
new("css_warns", CS2_SimpleAdmin.Instance.OnWarnsCommand),
new("css_players", CS2_SimpleAdmin.Instance.OnPlayersCommand),
new("css_kick", CS2_SimpleAdmin.Instance.OnKickCommand),
new("css_map", CS2_SimpleAdmin.Instance.OnMapCommand),
new("css_wsmap", CS2_SimpleAdmin.Instance.OnWorkshopMapCommand),
new("css_cvar", CS2_SimpleAdmin.Instance.OnCvarCommand),
new("css_rcon", CS2_SimpleAdmin.Instance.OnRconCommand),
new("css_rr", CS2_SimpleAdmin.Instance.OnRestartCommand),
new("css_gag", CS2_SimpleAdmin.Instance.OnGagCommand),
new("css_addgag", CS2_SimpleAdmin.Instance.OnAddGagCommand),
new("css_ungag", CS2_SimpleAdmin.Instance.OnUngagCommand),
new("css_mute", CS2_SimpleAdmin.Instance.OnMuteCommand),
new("css_addmute", CS2_SimpleAdmin.Instance.OnAddMuteCommand),
new("css_unmute", CS2_SimpleAdmin.Instance.OnUnmuteCommand),
new("css_silence", CS2_SimpleAdmin.Instance.OnSilenceCommand),
new("css_addsilence", CS2_SimpleAdmin.Instance.OnAddSilenceCommand),
new("css_unsilence", CS2_SimpleAdmin.Instance.OnUnsilenceCommand),
new("css_vote", CS2_SimpleAdmin.Instance.OnVoteCommand),
new("css_noclip", CS2_SimpleAdmin.Instance.OnNoclipCommand),
new("css_freeze", CS2_SimpleAdmin.Instance.OnFreezeCommand),
new("css_unfreeze", CS2_SimpleAdmin.Instance.OnUnfreezeCommand),
new("css_godmode", CS2_SimpleAdmin.Instance.OnGodCommand),
new("css_slay", CS2_SimpleAdmin.Instance.OnSlayCommand),
new("css_slap", CS2_SimpleAdmin.Instance.OnSlapCommand),
new("css_give", CS2_SimpleAdmin.Instance.OnGiveCommand),
new("css_strip", CS2_SimpleAdmin.Instance.OnStripCommand),
new("css_hp", CS2_SimpleAdmin.Instance.OnHpCommand),
new("css_speed", CS2_SimpleAdmin.Instance.OnSpeedCommand),
new("css_gravity", CS2_SimpleAdmin.Instance.OnGravityCommand),
new("css_resize", CS2_SimpleAdmin.Instance.OnResizeCommand),
new("css_money", CS2_SimpleAdmin.Instance.OnMoneyCommand),
new("css_team", CS2_SimpleAdmin.Instance.OnTeamCommand),
new("css_rename", CS2_SimpleAdmin.Instance.OnRenameCommand),
new("css_prename", CS2_SimpleAdmin.Instance.OnPrenameCommand),
new("css_respawn", CS2_SimpleAdmin.Instance.OnRespawnCommand),
new("css_tp", CS2_SimpleAdmin.Instance.OnGotoCommand),
new("css_bring", CS2_SimpleAdmin.Instance.OnBringCommand),
new("css_pluginsmanager", CS2_SimpleAdmin.Instance.OnPluginManagerCommand),
new("css_adminvoice", CS2_SimpleAdmin.Instance.OnAdminVoiceCommand)
];
/// <summary>
/// Initializes command registration.
/// If the commands config file does not exist, creates it and then recurses to register commands.
/// Otherwise, directly registers commands from the configuration.
/// </summary>
public static void InitializeCommands()
{
if (!File.Exists(CommandsPath))
{
CreateConfig();
InitializeCommands();
}
else
{
Register();
}
}
/// <summary>
/// Creates the default commands configuration JSON file with built-in commands and aliases.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void CreateConfig()
{
var commands = new CommandsConfig
{
Commands = new Dictionary<string, Command>
{
{ "css_ban", new Command { Aliases = ["css_ban"] } },
{ "css_addban", new Command { Aliases = ["css_addban"] } },
{ "css_banip", new Command { Aliases = ["css_banip"] } },
{ "css_unban", new Command { Aliases = ["css_unban"] } },
{ "css_warn", new Command { Aliases = ["css_warn"] } },
{ "css_unwarn", new Command { Aliases = ["css_unwarn"] } },
{ "css_asay", new Command { Aliases = ["css_asay"] } },
{ "css_cssay", new Command { Aliases = ["css_cssay"] } },
{ "css_say", new Command { Aliases = ["css_say"] } },
{ "css_psay", new Command { Aliases = ["css_psay"] } },
{ "css_csay", new Command { Aliases = ["css_csay"] } },
{ "css_hsay", new Command { Aliases = ["css_hsay"] } },
{ "css_penalties", new Command { Aliases = ["css_penalties", "css_mypenalties", "css_comms"] } },
{ "css_admin", new Command { Aliases = ["css_admin"] } },
{ "css_adminhelp", new Command { Aliases = ["css_adminhelp"] } },
{ "css_addadmin", new Command { Aliases = ["css_addadmin"] } },
{ "css_deladmin", new Command { Aliases = ["css_deladmin"] } },
{ "css_addgroup", new Command { Aliases = ["css_addgroup"] } },
{ "css_delgroup", new Command { Aliases = ["css_delgroup"] } },
{ "css_reloadadmins", new Command { Aliases = ["css_reloadadmins"] } },
{ "css_reloadbans", new Command { Aliases = ["css_reloadbans"] } },
{ "css_hide", new Command { Aliases = ["css_hide", "css_stealth"] } },
{ "css_hidecomms", new Command { Aliases = ["css_hidecomms"] } },
{ "css_who", new Command { Aliases = ["css_who"] } },
{ "css_disconnected", new Command { Aliases = ["css_disconnected", "css_last"] } },
{ "css_warns", new Command { Aliases = ["css_warns"] } },
{ "css_players", new Command { Aliases = ["css_players"] } },
{ "css_kick", new Command { Aliases = ["css_kick"] } },
{ "css_map", new Command { Aliases = ["css_map", "css_changemap"] } },
{ "css_wsmap", new Command { Aliases = ["css_wsmap", "css_changewsmap", "css_workshop"] } },
{ "css_cvar", new Command { Aliases = ["css_cvar"] } },
{ "css_rcon", new Command { Aliases = ["css_rcon"] } },
{ "css_rr", new Command { Aliases = ["css_rr", "css_rg", "css_restart", "css_restartgame"] } },
{ "css_gag", new Command { Aliases = ["css_gag"] } },
{ "css_addgag", new Command { Aliases = ["css_addgag"] } },
{ "css_ungag", new Command { Aliases = ["css_ungag"] } },
{ "css_mute", new Command { Aliases = ["css_mute"] } },
{ "css_addmute", new Command { Aliases = ["css_addmute"] } },
{ "css_unmute", new Command { Aliases = ["css_unmute"] } },
{ "css_silence", new Command { Aliases = ["css_silence"] } },
{ "css_addsilence", new Command { Aliases = ["css_addsilence"] } },
{ "css_unsilence", new Command { Aliases = ["css_unsilence"] } },
{ "css_vote", new Command { Aliases = ["css_vote"] } },
{ "css_noclip", new Command { Aliases = ["css_noclip"] } },
{ "css_freeze", new Command { Aliases = ["css_freeze"] } },
{ "css_unfreeze", new Command { Aliases = ["css_unfreeze"] } },
{ "css_godmode", new Command { Aliases = ["css_godmode"] } },
{ "css_slay", new Command { Aliases = ["css_slay"] } },
{ "css_slap", new Command { Aliases = ["css_slap"] } },
{ "css_give", new Command { Aliases = ["css_give"] } },
{ "css_strip", new Command { Aliases = ["css_strip"] } },
{ "css_hp", new Command { Aliases = ["css_hp"] } },
{ "css_speed", new Command { Aliases = ["css_speed"] } },
{ "css_gravity", new Command { Aliases = ["css_gravity"] } },
{ "css_resize", new Command { Aliases = ["css_resize", "css_size"] } },
{ "css_money", new Command { Aliases = ["css_money"] } },
{ "css_team", new Command { Aliases = ["css_team"] } },
{ "css_rename", new Command { Aliases = ["css_rename"] } },
{ "css_prename", new Command { Aliases = ["css_prename"] } },
{ "css_respawn", new Command { Aliases = ["css_respawn"] } },
{ "css_tp", new Command { Aliases = ["css_tp", "css_tpto", "css_goto"] } },
{ "css_bring", new Command { Aliases = ["css_bring", "css_tphere"] } },
{ "css_pluginsmanager", new Command { Aliases = ["css_pluginsmanager", "css_pluginmanager"] } },
{ "css_adminvoice", new Command { Aliases = ["css_adminvoice", "css_listenall"] } }
}
};
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(commands, options);
File.WriteAllText(CommandsPath, json);
}
/// <summary>
/// Reads the command configuration JSON file and registers all commands and their aliases with their callbacks.
/// Also registers any custom commands previously stored.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
private static void Register()
{
var json = File.ReadAllText(CommandsPath);
var commandsConfig = JsonSerializer.Deserialize<CommandsConfig>(json,
new JsonSerializerOptions { PropertyNameCaseInsensitive = true });
if (commandsConfig?.Commands == null) return;
foreach (var command in commandsConfig.Commands)
{
if (command.Value.Aliases == null) continue;
CS2_SimpleAdmin._logger?.LogInformation(
$"Registering command: `{command.Key}` with aliases: `{string.Join(", ", command.Value.Aliases)}`");
var mapping = CommandMappings.FirstOrDefault(m => m.CommandKey == command.Key);
if (mapping == null || command.Value.Aliases.Length == 0) continue;
foreach (var alias in command.Value.Aliases)
{
CS2_SimpleAdmin.Instance.AddCommand(alias, "", mapping.Callback);
}
}
foreach (var (name, definitions) in RegisterCommands._commandDefinitions)
{
foreach (var definition in definitions)
{
CS2_SimpleAdmin._logger?.LogInformation($"Registering custom command: `{name}`");
CS2_SimpleAdmin.Instance.AddCommand(name, definition.Description, definition.Callback);
}
}
}
/// <summary>
/// Represents the JSON configuration structure for commands.
/// </summary>
private class CommandsConfig
{
public Dictionary<string, Command>? Commands { get; init; }
}
/// <summary>
/// Represents a command definition containing a list of aliases.
/// </summary>
private class Command
{
public string[]? Aliases { get; init; }
}
/// <summary>
/// Maps a command key to its respective command callback handler.
/// </summary>
private class CommandMapping(string commandKey, CommandInfo.CommandCallback callback)
{
public string CommandKey { get; } = commandKey;
public CommandInfo.CommandCallback Callback { get; } = callback;
}
}

View File

@@ -0,0 +1,580 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Menus;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Handles the 'ban' command, allowing admins to ban one or more valid connected players.
/// </summary>
/// <param name="caller">The player issuing the ban command, or null for console.</param>
/// <param name="command">The command information including arguments.</param>
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
if (command.ArgCount < 2)
return;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, IsHLTV: false }).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player,
ManagePlayersMenu.BanMenu);
return;
}
Ban(caller, player, time, reason, callerName, BanManager, command);
});
}
/// <summary>
/// Core logic to ban a specific player, scheduling database updates, notifications, and kicks.
/// </summary>
/// <param name="caller">The player issuing the ban, or null for console.</param>
/// <param name="player">The player to be banned.</param>
/// <param name="time">Ban duration in minutes; 0 means permanent.</param>
/// <param name="reason">Reason for the ban.</param>
/// <param name="callerName">Optional caller name string. If null, defaults to player name or console.</param>
/// <param name="banManager">Optional BanManager to handle ban persistence.</param>
/// <param name="command">Optional command info object for logging.</param>
/// <param name="silent">If true, suppresses command logging.</param>
internal void Ban(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, BanManager? banManager = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Asynchronously handle banning logic
Task.Run(async () =>
{
int? penaltyId = await BanManager.BanPlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Ban, reason, time, penaltyId);
});
});
// Determine message keys and arguments based on ban time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_ban_message_perm", "sa_admin_ban_message_perm",
[reason, "CALLER"],
["CALLER", player.PlayerName, reason])
: ("sa_player_ban_message_time", "sa_admin_ban_message_time",
new object[] { reason, time, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason, time });
// Display center message to the player
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
// Display admin activity message if necessary
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Schedule a kick timer
if (player.UserId.HasValue)
{
Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED, Config.OtherSettings.KickTime);
}
// Execute ban command if necessary
if (UnlockedCommands)
{
Server.ExecuteCommand($"banid 1 {new SteamID(player.SteamID).SteamId3}");
}
if (!silent)
{
if (command == null)
Helper.LogCommand(caller, $"css_ban {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Ban, _localizer);
}
/// <summary>
/// Adds a ban for a player by their SteamID, including offline bans.
/// </summary>
/// <param name="caller">The player issuing the ban command.</param>
/// <param name="steamid">SteamID of the player to ban.</param>
/// <param name="time">Ban duration in minutes (0 means permanent).</param>
/// <param name="reason">Reason for banning.</param>
/// <param name="banManager">Optional ban manager for database operations.</param>
internal void AddBan(CCSPlayerController? caller, SteamID steamid, int time, string reason, BanManager? banManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await BanManager.AddBanBySteamid(steamid.SteamId64, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Ban, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Ban, _localizer);
}
}
/// <summary>
/// Handles banning a player by specifying their SteamID via command.
/// </summary>
/// <param name="caller">The player issuing the command, or null if console.</param>
/// <param name="command">Command information including arguments (SteamID, time, reason).</param>
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddBanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
{
command.ReplyToCommand("Invalid SteamID64.");
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time)) return;
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
: null;
var player = Helper.GetPlayerFromSteamid64(steamid);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await BanManager.AddBanBySteamid(steamid, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Ban, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Ban, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Ban has been added offline.");
}
Helper.LogCommand(caller, command);
if (UnlockedCommands)
Server.ExecuteCommand($"banid 1 {steamId.SteamId3}");
}
/// <summary>
/// Handles banning a player by their IP address, supporting offline banning if player is not online.
/// </summary>
/// <param name="caller">The player issuing the ban command.</param>
/// <param name="command">The command containing the IP, time, and reason arguments.</param>
[RequiresPermissions("@css/ban")]
[CommandHelper(minArgs: 1, usage: "<ip> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnBanIpCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller?.PlayerName ?? _localizer?["sa_console"] ?? "Console";
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
var ipAddress = command.GetArg(1);
if (!Helper.IsValidIp(ipAddress))
{
command.ReplyToCommand($"Invalid IP address.");
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidBan(caller, time)) return;
var adminInfo = caller != null && caller.UserId.HasValue
? PlayersInfo[caller.SteamID]
: null;
var players = Helper.GetPlayerFromIp(ipAddress);
if (players.Count >= 1)
{
foreach (var player in players)
{
if (player == null || !player.IsValid) continue;
if (!caller.CanTarget(player))
return;
Ban(caller, player, time, reason, callerName, silent: true);
}
}
else
{
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
await BanManager.AddBanByIp(ipAddress, adminInfo, reason, time);
});
command.ReplyToCommand($"Player with ip {ipAddress} is not online. Ban has been added offline.");
}
Helper.LogCommand(caller, command);
}
/// <summary>
/// Checks whether the ban duration is valid based on the caller's permissions and configured limits.
/// </summary>
/// <param name="caller">The player issuing the ban command.</param>
/// <param name="duration">Requested ban duration in minutes.</param>
/// <returns>True if ban duration is valid; otherwise, false.</returns>
private bool CheckValidBan(CCSPlayerController? caller, int duration)
{
if (caller == null) return true;
var canPermBan = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permban");
if (duration <= 0 && canPermBan == false)
{
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
return false;
}
if (duration <= Config.OtherSettings.MaxBanDuration || canPermBan) return true;
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxBanDuration]}");
return false;
}
/// <summary>
/// Handles unbanning players by pattern (steamid, name, or IP).
/// </summary>
/// <param name="caller">The player issuing the unban command.</param>
/// <param name="command">Command containing target pattern and optional reason.</param>
[RequiresPermissions("@css/unban")]
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnbanCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
if (command.GetArg(1).Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
return;
}
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
Task.Run(async () => await BanManager.UnbanPlayer(pattern, callerSteamId, reason));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unbanned player with pattern {pattern}.");
}
/// <summary>
/// Handles warning players, supporting multiple targets and warning durations.
/// </summary>
/// <param name="caller">The player issuing the warn command.</param>
/// <param name="command">The command containing target, time, and reason parameters.</param>
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnWarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null)
return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
if (command.ArgCount < 2)
return;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player.Connected == PlayerConnectedState.PlayerConnected && !player.IsHLTV).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
Warn(caller, player, time, reason, callerName, command);
}
});
}
/// <summary>
/// Issues a warning penalty to a specific player with optional duration and reason.
/// </summary>
/// <param name="caller">The player issuing the warning.</param>
/// <param name="player">The player to warn.</param>
/// <param name="time">Duration of the warning in minutes.</param>
/// <param name="reason">Reason for the warning.</param>
/// <param name="callerName">Optional display name of the caller.</param>
/// <param name="command">Optional command info for logging.</param>
internal void Warn(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidBan(caller, time)) return;
// Set default caller name if not provided
callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
// Freeze player pawn if alive
if (player.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE)
{
player.PlayerPawn?.Value?.Freeze();
AddTimer(5.0f, () => player.PlayerPawn?.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Asynchronously handle warning logic
Task.Run(async () =>
{
int? penaltyId = await WarnManager.WarnPlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Warn, reason, time,
penaltyId);
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(player.SteamID);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
punishCommand = value;
if (!string.IsNullOrEmpty(punishCommand))
{
await Server.NextWorldUpdateAsync(() =>
{
Server.ExecuteCommand(punishCommand.Replace("USERID", playerInfo.UserId.ToString()).Replace("STEAMID64", playerInfo.SteamId?.ToString()));
});
}
}
});
// Determine message keys and arguments based on warning time
var (messageKey, activityMessageKey, centerArgs, adminActivityArgs) = time == 0
? ("sa_player_warn_message_perm", "sa_admin_warn_message_perm",
new object[] { reason, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason })
: ("sa_player_warn_message_time", "sa_admin_warn_message_time",
[reason, time, "CALLER"],
["CALLER", player.PlayerName, reason, time]);
// Display center message to the playser
Helper.DisplayCenterMessage(player, messageKey, callerName, centerArgs);
// Display admin activity message if necessary
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Log the warning command
if (command == null)
Helper.LogCommand(caller, $"css_warn {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
// Send Discord notification for the warning
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Warn, _localizer);
}
/// <summary>
/// Adds a warning to a player by their SteamID, including support for offline players.
/// </summary>
/// <param name="caller">The player issuing the warn command.</param>
/// <param name="steamid">SteamID of the player to warn.</param>
/// <param name="time">Warning duration in minutes.</param>
/// <param name="reason">Reason for the warning.</param>
/// <param name="warnManager">Optional warn manager instance.</param>
internal void AddWarn(CCSPlayerController? caller, SteamID steamid, int time, string reason, WarnManager? warnManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Warn(caller, player, time, reason, callerName);
//command.ReplyToCommand($"Banned player {player.PlayerName}.");
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await WarnManager.AddWarnBySteamid(steamid.SteamId64, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Warn, reason, time,
penaltyId);
});
// Check for warn thresholds and execute punish command if applicable
var totalWarns = await WarnManager.GetPlayerWarnsCount(steamid.SteamId64);
if (Config.WarnThreshold.Count > 0)
{
string? punishCommand = null;
var lastKey = Config.WarnThreshold.Keys.Max();
if (totalWarns >= lastKey)
punishCommand = Config.WarnThreshold[lastKey];
else if (Config.WarnThreshold.TryGetValue(totalWarns, out var value))
punishCommand = value;
if (!string.IsNullOrEmpty(punishCommand))
{
await Server.NextWorldUpdateAsync(() =>
{
Server.ExecuteCommand(punishCommand.Replace("STEAMID64", steamid.SteamId64.ToString()));
});
}
}
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Warn, _localizer);
}
}
/// <summary>
/// Handles removing a warning (unwarn) by a pattern string.
/// </summary>
/// <param name="caller">The player issuing the unwarn command.</param>
/// <param name="command">The command containing target pattern.</param>
[RequiresPermissions("@css/kick")]
[CommandHelper(minArgs: 1, usage: "<steamid or name or ip>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnwarnCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
if (command.GetArg(1).Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
return;
}
var pattern = command.GetArg(1);
Task.Run(async () => await WarnManager.UnwarnPlayer(pattern));
Helper.LogCommand(caller, command);
command.ReplyToCommand($"Unwarned player with pattern {pattern}.");
}
}

View File

@@ -0,0 +1,150 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Memory;
using CounterStrikeSharp.API.Modules.Utils;
using System.Text;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Sends a chat message only to admins that have chat permission.
/// The message is encoded properly to handle UTF-8 characters.
/// </summary>
/// <param name="caller">The admin player sending the message, or null for console.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminToAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
{
Helper.LogCommand(caller, command);
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
foreach (var player in Helper.GetValidPlayers()
.Where(p => AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
{
if (_localizer != null)
player.PrintToChat(_localizer["sa_adminchat_template_admin",
caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName,
utf8String]);
}
}
/// <summary>
/// Sends a custom chat message to all players with color tags processed.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminCustomSayCommand(CCSPlayerController? caller, CommandInfo command)
{
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return;
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
foreach (var player in Helper.GetValidPlayers())
{
player.PrintToChat(utf8String.ReplaceColorTags());
}
}
/// <summary>
/// Sends a chat message to all players with localization prefix and color tags handled.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminSayCommand(CCSPlayerController? caller, CommandInfo command)
{
if (command.GetCommandString[command.GetCommandString.IndexOf(' ')..].Length == 0) return;
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
foreach (var player in Helper.GetValidPlayers())
{
player.SendLocalizedMessage(_localizer,
"sa_adminsay_prefix",
utf8String.ReplaceColorTags());
}
}
/// <summary>
/// Sends a private chat message from the caller to the specified target player(s).
/// </summary>
/// <param name="caller">The admin or console sending the private message.</param>
/// <param name="command">The command input containing target and message.</param>
[CommandHelper(2, "<#userid or name> <message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminPrivateSayCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
//Helper.LogCommand(caller, command);
var range = command.GetArg(0).Length + command.GetArg(1).Length + 2;
var message = command.GetCommandString[range..];
var utf8BytesString = Encoding.UTF8.GetBytes(message);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
playersToTarget.ForEach(player =>
{
player.PrintToChat($"({callerName}) {utf8String}".ReplaceColorTags());
});
command.ReplyToCommand($" Private message sent!");
}
/// <summary>
/// Broadcasts a center-screen message to all players.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminCenterSayCommand(CCSPlayerController? caller, CommandInfo command)
{
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
Helper.PrintToCenterAll(utf8String.ReplaceColorTags());
}
/// <summary>
/// Sends a HUD alert message to all players.
/// </summary>
/// <param name="caller">The admin or console sending the message.</param>
/// <param name="command">The command input containing the message.</param>
[CommandHelper(1, "<message>")]
[RequiresPermissions("@css/chat")]
public void OnAdminHudSayCommand(CCSPlayerController? caller, CommandInfo command)
{
var utf8BytesString = Encoding.UTF8.GetBytes(command.GetCommandString[command.GetCommandString.IndexOf(' ')..]);
var utf8String = Encoding.UTF8.GetString(utf8BytesString);
Helper.LogCommand(caller, command);
VirtualFunctions.ClientPrintAll(
HudDestination.Alert,
utf8String.ReplaceColorTags(),
0, 0, 0, 0, 0);
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,973 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Menus;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Processes the 'gag' command, applying a muted penalty to target players with optional time and reason.
/// </summary>
/// <param name="caller">The player issuing the gag command or null for console.</param>
/// <param name="command">The command input containing targets, time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGagCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player,
ManagePlayersMenu.GagMenu);
return;
}
Gag(caller, player, time, reason, callerName, command);
});
}
/// <summary>
/// Applies the gag penalty logic to an individual player, performing permission checks, notification, and logging.
/// </summary>
/// <param name="caller">The player issuing the gag.</param>
/// <param name="player">The player to gag.</param>
/// <param name="time">Duration of the gag in minutes, 0 is permanent.</param>
/// <param name="reason">Reason for the gag.</param>
/// <param name="callerName">Optional caller name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
/// <param name="silent">If true, suppresses logging notifications.</param>
internal void Gag(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidMute(caller, time)) return;
// Set default caller name if not provided
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Asynchronously handle gag logic
Task.Run(async () =>
{
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Gag, reason, time,
penaltyId);
});
});
// Add penalty to the player's penalty manager
PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Gag, Time.ActualDateTime().AddMinutes(time), time);
// Determine message keys and arguments based on gag time (permanent or timed)
var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0
? ("sa_player_gag_message_perm", "sa_admin_gag_message_perm",
[reason, "CALLER"],
["CALLER", player.PlayerName, reason])
: ("sa_player_gag_message_time", "sa_admin_gag_message_time",
new object[] { reason, time, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason, time });
// Display center message to the gagged player
Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs);
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Increment the player's total gags count
PlayersInfo[player.SteamID].TotalGags++;
// Log the gag command and send Discord notification
if (!silent)
{
if (command == null)
Helper.LogCommand(caller, $"css_gag {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Gag, _localizer);
}
/// <summary>
/// Adds a gag penalty to a player identified by SteamID, supporting offline players.
/// </summary>
/// <param name="caller">The player issuing the command or null for console.</param>
/// <param name="steamid">SteamID of the target player.</param>
/// <param name="time">Duration in minutes (0 for permanent).</param>
/// <param name="reason">Reason for the gag.</param>
internal void AddGag(CCSPlayerController? caller, SteamID steamid, int time, string reason)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Gag(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 3);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Gag, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Gag, _localizer);
}
}
/// <summary>
/// Handles the 'addgag' command, which adds a gag penalty to a player specified by SteamID.
/// </summary>
/// <param name="caller">The player issuing the command or null for console.</param>
/// <param name="command">Command input that includes SteamID, optional time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddGagCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
// Set caller name
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Validate command arguments
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
// Validate and extract SteamID
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
{
command.ReplyToCommand("Invalid SteamID64.");
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidMute(caller, time)) return;
// Get player and admin info
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Attempt to match player based on SteamID
var player = Helper.GetPlayerFromSteamid64(steamid);
if (player != null && player.IsValid)
{
// Check if caller can target the player
if (!caller.CanTarget(player)) return;
// Perform the gag for an online player
Gag(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous gag operation for offline players
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Gag, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Gag, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Gag has been added offline.");
}
// Log the gag command and respond to the command
Helper.LogCommand(caller, command);
}
/// <summary>
/// Handles removing a gag penalty ('ungag') of a player, either by SteamID or pattern match.
/// </summary>
/// <param name="caller">The player issuing the ungag command or null for console.</param>
/// <param name="command">Command input containing SteamID or player name and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUngagCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (pattern.Length <= 1)
{
command.ReplyToCommand($"Too short pattern to search.");
return;
}
Helper.LogCommand(caller, command);
// Check if pattern is a valid SteamID64
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
{
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
if (player != null && player.IsValid)
{
PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Gag);
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason);
});
command.ReplyToCommand($"Ungaged player {player.PlayerName}.");
return;
}
}
// If not a valid SteamID64, check by player name
var nameMatches = Helper.GetPlayerFromName(pattern);
var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null;
if (namePlayer != null && namePlayer.IsValid)
{
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Gag);
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalGags > 0)
PlayersInfo[namePlayer.SteamID].TotalGags--;
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason);
});
command.ReplyToCommand($"Ungaged player {namePlayer.PlayerName}.");
}
else
{
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(pattern, callerSteamId, reason);
});
command.ReplyToCommand($"Ungaged offline player with pattern {pattern}.");
}
}
/// <summary>
/// Processes the 'mute' command, applying a voice mute penalty to target players with optional time and reason.
/// </summary>
/// <param name="caller">The player issuing the mute command or null for console.</param>
/// <param name="command">The command input containing targets, time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnMuteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_mute"] ?? "Mute"}: {player.PlayerName}", player,
ManagePlayersMenu.MuteMenu);
return;
}
Mute(caller, player, time, reason, callerName, command);
});
}
/// <summary>
/// Applies the mute penalty logic to an individual player, handling permissions, notifications, logging, and countdown timers.
/// </summary>
/// <param name="caller">The player issuing the mute.</param>
/// <param name="player">The player to mute.</param>
/// <param name="time">Duration in minutes, 0 indicates permanent mute.</param>
/// <param name="reason">Reason for the mute.</param>
/// <param name="callerName">Optional caller name for notification messages.</param>
/// <param name="command">Optional command info for logging.</param>
/// <param name="silent">If true, suppresses some logging.</param>
internal void Mute(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidMute(caller, time)) return;
// Set default caller name if not provided
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Set player's voice flags to muted
player.VoiceFlags = VoiceFlags.Muted;
// Asynchronously handle mute logic
Task.Run(async () =>
{
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 1);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Mute, reason, time,
penaltyId);
});
});
// Add penalty to the player's penalty manager
PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Mute, Time.ActualDateTime().AddMinutes(time), time);
// Determine message keys and arguments based on mute time (permanent or timed)
var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0
? ("sa_player_mute_message_perm", "sa_admin_mute_message_perm",
[reason, "CALLER"],
["CALLER", player.PlayerName, reason])
: ("sa_player_mute_message_time", "sa_admin_mute_message_time",
new object[] { reason, time, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason, time });
// Display center message to the muted player
Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs);
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Increment the player's total mutes count
PlayersInfo[player.SteamID].TotalMutes++;
// Log the mute command and send Discord notification
if (!silent)
{
if (command == null)
Helper.LogCommand(caller, $"css_mute {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Mute, _localizer);
}
/// <summary>
/// Handles the 'addmute' command that adds a mute penalty to a player specified by SteamID.
/// </summary>
/// <param name="caller">The player issuing the command or null for console.</param>
/// <param name="command">Command input includes SteamID, optional time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddMuteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
// Set caller name
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Validate command arguments
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
// Validate and extract SteamID
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
{
command.ReplyToCommand("Invalid SteamID64.");
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidMute(caller, time)) return;
// Get player and admin info
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Attempt to match player based on SteamID
var player = Helper.GetPlayerFromSteamid64(steamid);
if (player != null && player.IsValid)
{
// Check if caller can target the player
if (!caller.CanTarget(player)) return;
// Perform the mute for an online player
Mute(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous mute operation for offline players
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 1);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Mute, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Mute, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Mute has been added offline.");
}
// Log the mute command and respond to the command
Helper.LogCommand(caller, command);
}
/// <summary>
/// Asynchronously adds a mute penalty to a player by Steam ID. Handles both online and offline players.
/// </summary>
/// <param name="caller">The admin/player issuing the mute.</param>
/// <param name="steamid">The Steam ID of the player to mute.</param>
/// <param name="time">Duration of the mute in minutes.</param>
/// <param name="reason">Reason for the mute.</param>
/// <param name="muteManager">Optional mute manager instance for handling database ops.</param>
internal void AddMute(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Mute(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 1);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Mute, reason, time,
penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Mute, _localizer);
}
}
/// <summary>
/// Handles the unmute command - removes mute penalty from player identified by SteamID or name.
/// Can target both online and offline players.
/// </summary>
/// <param name="caller">The admin/player issuing the unmute.</param>
/// <param name="command">The command arguments including target identifier and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnmuteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (pattern.Length <= 1)
{
command.ReplyToCommand("Too short pattern to search.");
return;
}
Helper.LogCommand(caller, command);
// Check if pattern is a valid SteamID64
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
{
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
if (player != null && player.IsValid)
{
PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Mute);
player.VoiceFlags = VoiceFlags.Normal;
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason, 1);
});
command.ReplyToCommand($"Unmuted player {player.PlayerName}.");
return;
}
}
// If not a valid SteamID64, check by player name
var nameMatches = Helper.GetPlayerFromName(pattern);
var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null;
if (namePlayer != null && namePlayer.IsValid)
{
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Mute);
namePlayer.VoiceFlags = VoiceFlags.Normal;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalMutes > 0)
PlayersInfo[namePlayer.SteamID].TotalMutes--;
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason, 1);
});
command.ReplyToCommand($"Unmuted player {namePlayer.PlayerName}.");
}
else
{
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(pattern, callerSteamId, reason, 1);
});
command.ReplyToCommand($"Unmuted offline player with pattern {pattern}.");
}
}
/// <summary>
/// Issue a 'silence' penalty to a player - disables voice communication.
/// Handles online and offline players, with duration and reason specified.
/// </summary>
/// <param name="caller">The admin/player issuing the silence.</param>
/// <param name="command">Command containing target, duration, and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnSilenceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false }).ToList();
if (playersToTarget.Count > 1 && Config.OtherSettings.DisableDangerousCommands || playersToTarget.Count == 0)
{
return;
}
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Helper.ParsePenaltyTime(command.GetArg(2));
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
if (time < 0 && caller != null && caller.IsValid && Config.OtherSettings.ShowBanMenuIfNoTime)
{
DurationMenu.OpenMenu(caller, $"{_localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player,
ManagePlayersMenu.SilenceMenu);
return;
}
Silence(caller, player, time, reason, callerName, command);
});
}
/// <summary>
/// Applies silence logical processing for a player - updates database and notifies.
/// </summary>
/// <param name="caller">Admin/player applying the silence.</param>
/// <param name="player">Target player.</param>
/// <param name="time">Duration of silence.</param>
/// <param name="reason">Reason for silence.</param>
/// <param name="callerName">Optional name of silent admin or console.</param>
/// <param name="command">Optional command details for logging.</param>
/// <param name="silent">If true, suppresses logging notifications.</param>
internal void Silence(CCSPlayerController? caller, CCSPlayerController player, int time, string reason, string? callerName = null, CommandInfo? command = null, bool silent = false)
{
if (DatabaseProvider == null || !player.IsValid || !player.UserId.HasValue) return;
if (!caller.CanTarget(player)) return;
if (!CheckValidMute(caller, time)) return;
// Set default caller name if not provided
callerName ??= caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Get player and admin information
var playerInfo = PlayersInfo[player.SteamID];
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Asynchronously handle silence logic
Task.Run(async () =>
{
int? penaltyId = await MuteManager.MutePlayer(playerInfo, adminInfo, reason, time, 2);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedEvent(playerInfo, adminInfo, PenaltyType.Silence, reason, time,
penaltyId);
});
});
// Add penalty to the player's penalty manager
PlayerPenaltyManager.AddPenalty(player.Slot, PenaltyType.Silence, Time.ActualDateTime().AddMinutes(time), time);
player.VoiceFlags = VoiceFlags.Muted;
// Determine message keys and arguments based on silence time (permanent or timed)
var (messageKey, activityMessageKey, playerArgs, adminActivityArgs) = time == 0
? ("sa_player_silence_message_perm", "sa_admin_silence_message_perm",
[reason, "CALLER"],
["CALLER", player.PlayerName, reason])
: ("sa_player_silence_message_time", "sa_admin_silence_message_time",
new object[] { reason, time, "CALLER" },
new object[] { "CALLER", player.PlayerName, reason, time });
// Display center message to the silenced player
Helper.DisplayCenterMessage(player, messageKey, callerName, playerArgs);
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Increment the player's total silences count
PlayersInfo[player.SteamID].TotalSilences++;
// Log the silence command and send Discord notification
if (!silent)
{
if (command == null)
Helper.LogCommand(caller, $"css_silence {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time} {reason}");
else
Helper.LogCommand(caller, command);
}
Helper.SendDiscordPenaltyMessage(caller, player, reason, time, PenaltyType.Silence, _localizer);
}
/// <summary>
/// Handles the 'AddSilence' command, applying a silence penalty to a player specified by SteamID,
/// with support for offline player penalties.
/// </summary>
/// <param name="caller">The player/admin issuing the command.</param>
/// <param name="command">The command input containing SteamID, optional time, and reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<#userid or name> [time in minutes/0 perm] [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnAddSilenceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
// Set caller name
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
// Validate command arguments
if (command.ArgCount < 2 || string.IsNullOrEmpty(command.GetArg(1))) return;
// Validate and extract SteamID
if (!Helper.ValidateSteamId(command.GetArg(1), out var steamId) || steamId == null)
{
command.ReplyToCommand("Invalid SteamID64.");
return;
}
var steamid = steamId.SteamId64;
var reason = command.ArgCount >= 3
? string.Join(" ", Enumerable.Range(3, command.ArgCount - 3).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
var time = Math.Max(0, Helper.ParsePenaltyTime(command.GetArg(2)));
if (!CheckValidMute(caller, time)) return;
// Get player and admin info
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
// Attempt to match player based on SteamID
var player = Helper.GetPlayerFromSteamid64(steamid);
if (player != null && player.IsValid)
{
// Check if caller can target the player
if (!caller.CanTarget(player)) return;
// Perform the silence for an online player
Silence(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(new SteamID(steamId.SteamId64)))
return;
// Asynchronous silence operation for offline players
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid, adminInfo, reason, time, 2);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamId, adminInfo, PenaltyType.Silence, reason,
time, penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.ToString(), reason, time, PenaltyType.Silence, _localizer);
command.ReplyToCommand($"Player with steamid {steamid} is not online. Silence has been added offline.");
}
// Log the silence command and respond to the command
Helper.LogCommand(caller, command);
}
/// <summary>
/// Adds a silence penalty to a player by Steam ID. Manages both online and offline player cases.
/// </summary>
/// <param name="caller">Admin/player initiating the silence.</param>
/// <param name="steamid">Steam ID of player.</param>
/// <param name="time">Duration of silence.</param>
/// <param name="reason">Reason for the penalty.</param>
/// <param name="muteManager">Optional mute manager for DB operations.</param>
internal void AddSilence(CCSPlayerController? caller, SteamID steamid, int time, string reason, MuteManager? muteManager = null)
{
// Set default caller name if not provided
var callerName = !string.IsNullOrEmpty(caller?.PlayerName)
? caller.PlayerName
: (_localizer?["sa_console"] ?? "Console");
var adminInfo = caller != null && caller.UserId.HasValue ? PlayersInfo[caller.SteamID] : null;
var player = Helper.GetPlayerFromSteamid64(steamid.SteamId64);
if (player != null && player.IsValid)
{
if (!caller.CanTarget(player))
return;
Silence(caller, player, time, reason, callerName, silent: true);
}
else
{
if (!caller.CanTarget(steamid))
return;
// Asynchronous ban operation if player is not online or not found
Task.Run(async () =>
{
int? penaltyId = await MuteManager.AddMuteBySteamid(steamid.SteamId64, adminInfo, reason, time, 2);
await Server.NextWorldUpdateAsync(() =>
{
SimpleAdminApi?.OnPlayerPenaltiedAddedEvent(steamid, adminInfo, PenaltyType.Silence, reason,
time, penaltyId);
});
});
Helper.SendDiscordPenaltyMessage(caller, steamid.SteamId64.ToString(), reason, time, PenaltyType.Silence, _localizer);
}
}
/// <summary>
/// Removes the silence penalty from a player, either by SteamID, name, or offline pattern.
/// Resets voice settings and updates notices accordingly.
/// </summary>
/// <param name="caller">Admin/player issuing the unsilence.</param>
/// <param name="command">Command arguments with target pattern and optional reason.</param>
[RequiresPermissions("@css/chat")]
[CommandHelper(minArgs: 1, usage: "<steamid or name> [reason]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnUnsilenceCommand(CCSPlayerController? caller, CommandInfo command)
{
if (DatabaseProvider == null) return;
var callerSteamId = caller?.SteamID.ToString() ?? _localizer?["sa_console"] ?? "Console";
var pattern = command.GetArg(1);
var reason = command.ArgCount >= 2
? string.Join(" ", Enumerable.Range(2, command.ArgCount - 2).Select(command.GetArg)).Trim()
: _localizer?["sa_unknown"] ?? "Unknown";
reason = string.IsNullOrWhiteSpace(reason) ? _localizer?["sa_unknown"] ?? "Unknown" : reason;
if (pattern.Length <= 1)
{
command.ReplyToCommand("Too short pattern to search.");
return;
}
Helper.LogCommand(caller, command);
// Check if pattern is a valid SteamID64
if (Helper.ValidateSteamId(pattern, out var steamId) && steamId != null)
{
var player = Helper.GetPlayerFromSteamid64(steamId.SteamId64);
if (player != null && player.IsValid)
{
PlayerPenaltyManager.RemovePenaltiesByType(player.Slot, PenaltyType.Silence);
// Reset voice flags to normal
player.VoiceFlags = VoiceFlags.Normal;
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(player.SteamID.ToString(), callerSteamId, reason, 2); // Unmute by type 2 (silence)
});
command.ReplyToCommand($"Unsilenced player {player.PlayerName}.");
return;
}
}
// If not a valid SteamID64, check by player name
var nameMatches = Helper.GetPlayerFromName(pattern);
var namePlayer = nameMatches.Count == 1 ? nameMatches.FirstOrDefault() : null;
if (namePlayer != null && namePlayer.IsValid)
{
PlayerPenaltyManager.RemovePenaltiesByType(namePlayer.Slot, PenaltyType.Silence);
// Reset voice flags to normal
namePlayer.VoiceFlags = VoiceFlags.Normal;
if (namePlayer.UserId.HasValue && PlayersInfo[namePlayer.SteamID].TotalSilences > 0)
PlayersInfo[namePlayer.SteamID].TotalSilences--;
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(namePlayer.SteamID.ToString(), callerSteamId, reason, 2); // Unmute by type 2 (silence)
});
command.ReplyToCommand($"Unsilenced player {namePlayer.PlayerName}.");
}
else
{
Task.Run(async () =>
{
await MuteManager.UnmutePlayer(pattern, callerSteamId, reason, 2); // Unmute by type 2 (silence)
});
command.ReplyToCommand($"Unsilenced offline player with pattern {pattern}.");
}
}
/// <summary>
/// Validates mute penalty duration based on admin privileges and configured max duration.
/// </summary>
/// <param name="caller">Admin/player issuing the mute.</param>
/// <param name="duration">Requested duration in minutes.</param>
/// <returns>True if mute penalty duration is allowed; false otherwise.</returns>
private bool CheckValidMute(CCSPlayerController? caller, int duration)
{
if (caller == null) return true;
var canPermMute = AdminManager.PlayerHasPermissions(new SteamID(caller.SteamID), "@css/permmute");
if (duration <= 0 && canPermMute == false)
{
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_perm_restricted"]}");
return false;
}
if (duration <= Config.OtherSettings.MaxMuteDuration || canPermMute) return true;
caller.PrintToChat($"{_localizer!["sa_prefix"]} {_localizer["sa_ban_max_duration_exceeded", Config.OtherSettings.MaxMuteDuration]}");
return false;
}
}

View File

@@ -0,0 +1,98 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Menu;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Handles the vote command, creates voting menu for players, and collects answers.
/// Displays results after timeout and resets voting state.
/// </summary>
/// <param name="caller">The player/admin who initiated the vote, or null for console.</param>
/// <param name="command">Command object containing question and options.</param>
[RequiresPermissions("@css/generic")]
[CommandHelper(minArgs: 2, usage: "<question> [... options ...]", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnVoteCommand(CCSPlayerController? caller, CommandInfo command)
{
if (command.ArgCount < 2 || _localizer == null)
return;
Helper.LogCommand(caller, command);
VoteAnswers.Clear();
var question = command.GetArg(1);
var answersCount = command.ArgCount;
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
for (var i = 2; i <= answersCount - 1; i++)
{
VoteAnswers.Add(command.GetArg(i), 0);
}
foreach (var player in Helper.GetValidPlayers())
{
using (new WithTemporaryCulture(player.GetLanguage()))
{
IMenu? voteMenu = Helper.CreateMenu(_localizer["sa_admin_vote_menu_title", question]);
if (voteMenu == null)
return;
//ChatMenu voteMenu = new(_localizer!["sa_admin_vote_menu_title", question]);
for (var i = 2; i <= answersCount - 1; i++)
{
voteMenu.AddMenuOption(command.GetArg(i), Helper.HandleVotes);
}
voteMenu.PostSelectAction = PostSelectAction.Close;
Helper.PrintToCenterAll(_localizer["sa_admin_vote_message", caller == null ? _localizer["sa_console"] : caller.PlayerName, question]);
player.SendLocalizedMessage(_localizer,
"sa_admin_vote_message",
caller == null ? _localizer["sa_console"] : caller.PlayerName,
question);
voteMenu.Open(player);
//MenuManager.OpenChatMenu(player, voteMenu);
}
}
VoteInProgress = true;
}
if (VoteInProgress)
{
AddTimer(30, () =>
{
foreach (var player in Helper.GetValidPlayers())
{
if (_localizer != null)
player.SendLocalizedMessage(_localizer,
"sa_admin_vote_message_results",
question);
}
foreach (var (key, value) in VoteAnswers)
{
foreach (var player in Helper.GetValidPlayers())
{
if (_localizer != null)
player.SendLocalizedMessage(_localizer,
"sa_admin_vote_message_results_answer",
key,
value);
}
}
VoteAnswers.Clear();
VoteInProgress = false;
}, CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
}
}

View File

@@ -0,0 +1,307 @@
using System.Globalization;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Commands;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
/// <summary>
/// Enables or disables no-clip mode for specified player(s).
/// </summary>
/// <param name="caller">The player issuing the command.</param>
/// <param name="command">The command input containing targets.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/cheats")]
public void OnNoclipCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player =>
player.IsValid &&
player is { IsHLTV: false, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
NoClip(caller, player, callerName);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Toggles no-clip mode for a player and shows admin activity messages.
/// </summary>
/// <param name="caller">The player/admin toggling no-clip.</param>
/// <param name="player">The target player whose no-clip state changes.</param>
/// <param name="callerName">Optional caller name for messages.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void NoClip(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Toggle no-clip mode for the player
player.Pawn.Value?.ToggleNoclip();
// Determine message keys and arguments for the no-clip notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_noclip_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_noclip {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
/// <summary>
/// Enables or disables god mode for specified player(s).
/// </summary>
/// <param name="caller">The player issuing the command.</param>
/// <param name="command">The command input containing targets.</param>
[RequiresPermissions("@css/cheats")]
[CommandHelper(minArgs: 1, usage: "<#userid or name>", whoCanExecute: CommandUsage.CLIENT_AND_SERVER)]
public void OnGodCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player.IsValid && player is {IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (player.Connected != PlayerConnectedState.PlayerConnected)
return;
if (caller!.CanTarget(player))
{
God(caller, player, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Toggles god mode for a player and notifies admins.
/// </summary>
/// <param name="caller">The player/admin toggling god mode.</param>
/// <param name="player">The target player whose god mode changes.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void God(CCSPlayerController? caller, CCSPlayerController player, CommandInfo? command = null)
{
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
var callerName = caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Toggle god mode for the player
if (!GodPlayers.Add(player.Slot))
{
GodPlayers.Remove(player.Slot);
}
// Log the command
if (command == null)
Helper.LogCommand(caller, $"css_god {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
// Determine message key and arguments for the god mode notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_god_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
}
/// <summary>
/// Freezes target player(s) for an optional specified duration.
/// </summary>
/// <param name="caller">The player issuing the freeze command.</param>
/// <param name="command">The command input containing targets and duration.</param>
[CommandHelper(1, "<#userid or name> [duration]")]
[RequiresPermissions("@css/slay")]
public void OnFreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
int.TryParse(command.GetArg(2), out var time);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (caller!.CanTarget(player))
{
Freeze(caller, player, time, callerName, command);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Resizes the target player(s) models to a specified scale.
/// </summary>
/// <param name="caller">The player issuing the resize command.</param>
/// <param name="command">The command input containing targets and scale factor.</param>
[CommandHelper(1, "<#userid or name> [size]")]
[RequiresPermissions("@css/slay")]
public void OnResizeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
float.TryParse(command.GetArg(2), NumberStyles.Float, CultureInfo.InvariantCulture, out var size);
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
if (!caller!.CanTarget(player)) return;
var sceneNode = player.PlayerPawn.Value!.CBodyComponent?.SceneNode;
if (sceneNode == null) return;
sceneNode.GetSkeletonInstance().Scale = size;
player.PlayerPawn.Value.AcceptInput("SetScale", null, null, size.ToString(CultureInfo.InvariantCulture));
Server.NextWorldUpdate(() =>
{
Utilities.SetStateChanged(player.PlayerPawn.Value, "CBaseEntity", "m_CBodyComponent");
});
var (activityMessageKey, adminActivityArgs) =
("sa_admin_resize_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Freezes a single player and optionally schedules automatic unfreeze after a duration.
/// </summary>
/// <param name="caller">The player/admin freezing the player.</param>
/// <param name="player">The player to freeze.</param>
/// <param name="time">Duration of freeze in seconds.</param>
/// <param name="callerName">Optional name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Freeze(CCSPlayerController? caller, CCSPlayerController player, int time, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Freeze player pawn
player.Pawn.Value?.Freeze();
// Determine message keys and arguments for the freeze notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_freeze_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Schedule unfreeze for the player if time is specified
if (time > 0)
{
Instance.AddTimer(time, () => player.Pawn.Value?.Unfreeze(), CounterStrikeSharp.API.Modules.Timers.TimerFlags.STOP_ON_MAPCHANGE);
}
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_freeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)} {time}");
}
/// <summary>
/// Unfreezes target player(s).
/// </summary>
/// <param name="caller">The player issuing the unfreeze command.</param>
/// <param name="command">The command input with targets.</param>
[CommandHelper(1, "<#userid or name>")]
[RequiresPermissions("@css/slay")]
public void OnUnfreezeCommand(CCSPlayerController? caller, CommandInfo command)
{
var callerName = caller == null ? _localizer?["sa_console"] ?? "Console" : caller.PlayerName;
var targets = GetTarget(command);
if (targets == null) return;
var playersToTarget = targets.Players.Where(player => player is { IsValid: true, IsHLTV: false, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE }).ToList();
playersToTarget.ForEach(player =>
{
Unfreeze(caller, player, callerName, command);
});
Helper.LogCommand(caller, command);
}
/// <summary>
/// Unfreezes a single player and notifies admins.
/// </summary>
/// <param name="caller">The player/admin unfreezing the player.</param>
/// <param name="player">The player to unfreeze.</param>
/// <param name="callerName">Optional name for notifications.</param>
/// <param name="command">Optional command info for logging.</param>
internal static void Unfreeze(CCSPlayerController? caller, CCSPlayerController player, string? callerName = null, CommandInfo? command = null)
{
if (!player.IsValid) return;
if (!caller.CanTarget(player)) return;
// Set default caller name if not provided
callerName ??= caller != null ? caller.PlayerName : _localizer?["sa_console"] ?? "Console";
// Unfreeze player pawn
player.Pawn.Value?.Unfreeze();
// Determine message keys and arguments for the unfreeze notification
var (activityMessageKey, adminActivityArgs) =
("sa_admin_unfreeze_message",
new object[] { "CALLER", player.PlayerName });
// Display admin activity message to other players
if (caller == null || !SilentPlayers.Contains(caller.Slot))
{
Helper.ShowAdminActivity(activityMessageKey, callerName, false, adminActivityArgs);
}
// Log the command and send Discord notification
if (command == null)
Helper.LogCommand(caller, $"css_unfreeze {(string.IsNullOrEmpty(player.PlayerName) ? player.SteamID.ToString() : player.PlayerName)}");
}
}

File diff suppressed because it is too large Load Diff

325
CS2-SimpleAdmin/Config.cs Normal file
View File

@@ -0,0 +1,325 @@
using CounterStrikeSharp.API.Core;
using System.Text.Json.Serialization;
namespace CS2_SimpleAdmin;
public class DurationItem
{
[JsonPropertyName("name")]
public required string Name { get; set; }
[JsonPropertyName("duration")]
public int Duration { get; set; }
}
public class AdminFlag
{
[JsonPropertyName("name")]
public required string Name { get; set; }
[JsonPropertyName("flag")]
public required string Flag { get; set; }
}
public class DiscordPenaltySetting
{
[JsonPropertyName("name")]
public required string Name { get; set; }
[JsonPropertyName("value")]
public string? Value { get; set; } = "";
}
public class Discord
{
[JsonPropertyName("DiscordLogWebhook")]
public string DiscordLogWebhook { get; set; } = "";
[JsonPropertyName("DiscordPenaltyBanSettings")]
public DiscordPenaltySetting[] DiscordPenaltyBanSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordPenaltyMuteSettings")]
public DiscordPenaltySetting[] DiscordPenaltyMuteSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordPenaltyGagSettings")]
public DiscordPenaltySetting[] DiscordPenaltyGagSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordPenaltySilenceSettings")]
public DiscordPenaltySetting[] DiscordPenaltySilenceSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordPenaltyWarnSettings")]
public DiscordPenaltySetting[] DiscordPenaltyWarnSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
[JsonPropertyName("DiscordAssociatedAccountsSettings")]
public DiscordPenaltySetting[] DiscordAssociatedAccountsSettings { get; set; } =
[
new() { Name = "Color", Value = "" },
new() { Name = "Webhook", Value = "" },
new() { Name = "ThumbnailUrl", Value = "" },
new() { Name = "ImageUrl", Value = "" },
new() { Name = "Footer", Value = "" },
new() { Name = "Time", Value = "{relative}" },
];
}
public class CustomServerCommandData
{
[JsonPropertyName("Flag")]
public string Flag { get; set; } = "@css/generic";
[JsonPropertyName("DisplayName")]
public string DisplayName { get; set; } = "";
[JsonPropertyName("Command")]
public string Command { get; set; } = "";
[JsonPropertyName("ExecuteOnClient")]
public bool ExecuteOnClient { get; set; } = false;
}
public class MenuConfig
{
[JsonPropertyName("MenuType")] public string MenuType { get; set; } = "selectable";
[JsonPropertyName("Durations")]
public DurationItem[] Durations { get; set; } =
[
new() { Name = "1 minute", Duration = 1 },
new() { Name = "5 minutes", Duration = 5 },
new() { Name = "15 minutes", Duration = 15 },
new() { Name = "1 hour", Duration = 60 },
new() { Name = "1 day", Duration = 60 * 24 },
new() { Name = "7 days", Duration = 60 * 24 * 7 },
new() { Name = "14 days", Duration = 60 * 24 * 14 },
new() { Name = "30 days", Duration = 60 * 24 * 30 },
new() { Name = "Permanent", Duration = 0 }
];
[JsonPropertyName("BanReasons")]
public List<string> BanReasons { get; set; } =
[
"Hacking",
"Voice Abuse",
"Chat Abuse",
"Admin disrespect",
"Other"
];
[JsonPropertyName("KickReasons")]
public List<string> KickReasons { get; set; } =
[
"Voice Abuse",
"Chat Abuse",
"Admin disrespect",
"Other"
];
[JsonPropertyName("WarnReasons")]
public List<string> WarnReasons { get; set; } =
[
"Voice Abuse",
"Chat Abuse",
"Admin disrespect",
"Other"
];
[JsonPropertyName("MuteReasons")]
public List<string> MuteReasons { get; set; } =
[
"Advertising",
"Spamming",
"Spectator camera abuse",
"Hate",
"Admin disrespect",
"Other"
];
[JsonPropertyName("AdminFlags")]
public AdminFlag[] AdminFlags { get; set; } =
[
new() { Name = "Generic", Flag = "@css/generic" },
new() { Name = "Chat", Flag = "@css/chat" },
new() { Name = "Change Map", Flag = "@css/changemap" },
new() { Name = "Slay", Flag = "@css/slay" },
new() { Name = "Kick", Flag = "@css/kick" },
new() { Name = "Ban", Flag = "@css/ban" },
new() { Name = "Perm Ban", Flag = "@css/permban" },
new() { Name = "Unban", Flag = "@css/unban" },
new() { Name = "Show IP", Flag = "@css/showip" },
new() { Name = "Cvar", Flag = "@css/cvar" },
new() { Name = "Rcon", Flag = "@css/rcon" },
new() { Name = "Root (all flags)", Flag = "@css/root" }
];
}
public class OtherSettings
{
[JsonPropertyName("ShowActivityType")]
public int ShowActivityType { get; set; } = 2;
[JsonPropertyName("TeamSwitchType")]
public int TeamSwitchType { get; set; } = 1;
[JsonPropertyName("KickTime")]
public int KickTime { get; set; } = 5;
[JsonPropertyName("BanType")]
public int BanType { get; set; } = 1;
[JsonPropertyName("TimeMode")]
public int TimeMode { get; set; } = 1;
[JsonPropertyName("DisableDangerousCommands")]
public bool DisableDangerousCommands { get; set; } = true;
[JsonPropertyName("MaxBanDuration")]
public int MaxBanDuration { get; set; } = 60 * 24 * 7;
[JsonPropertyName("MaxMuteDuration")]
public int MaxMuteDuration { get; set; } = 60 * 24 * 7;
[JsonPropertyName("ExpireOldIpBans")]
public int ExpireOldIpBans { get; set; } = 0;
[JsonPropertyName("ReloadAdminsEveryMapChange")]
public bool ReloadAdminsEveryMapChange { get; set; } = false;
[JsonPropertyName("DisconnectedPlayersHistoryCount")]
public int DisconnectedPlayersHistoryCount { get; set; } = 10;
[JsonPropertyName("NotifyPenaltiesToAdminOnConnect")]
public bool NotifyPenaltiesToAdminOnConnect { get; set; } = true;
[JsonPropertyName("ShowBanMenuIfNoTime")]
public bool ShowBanMenuIfNoTime { get; set; } = true;
[JsonPropertyName("UserMessageGagChatType")]
public bool UserMessageGagChatType { get; set; } = false;
[JsonPropertyName("CheckMultiAccountsByIp")]
public bool CheckMultiAccountsByIp { get; set; } = true;
[JsonPropertyName("AdditionalCommandsToLog")]
public List<string> AdditionalCommandsToLog { get; set; } = new();
[JsonPropertyName("IgnoredIps")]
public List<string> IgnoredIps { get; set; } = new();
}
public class CS2_SimpleAdminConfig : BasePluginConfig
{
[JsonPropertyName("ConfigVersion")] public override int Version { get; set; } = 25;
[JsonPropertyName("DatabaseConfig")]
public DatabaseConfig DatabaseConfig { get; set; } = new();
[JsonPropertyName("OtherSettings")]
public OtherSettings OtherSettings { get; set; } = new();
[JsonPropertyName("EnableMetrics")]
public bool EnableMetrics { get; set; } = true;
[JsonPropertyName("EnableUpdateCheck")]
public bool EnableUpdateCheck { get; set; } = true;
[JsonPropertyName("Timezone")]
public string Timezone { get; set; } = "UTC";
[JsonPropertyName("WarnThreshold")]
public Dictionary<int, string> WarnThreshold { get; set; } = new()
{
{ 998, "css_addban STEAMID64 60 \"3/4 Warn\"" },
{ 999, "css_ban #USERID 120 \"4/4 Warn\"" },
};
[JsonPropertyName("MultiServerMode")]
public bool MultiServerMode { get; set; } = true;
[JsonPropertyName("Discord")]
public Discord Discord { get; set; } = new();
[JsonPropertyName("DefaultMaps")]
public List<string> DefaultMaps { get; set; } = new();
[JsonPropertyName("WorkshopMaps")]
public Dictionary<string, long?> WorkshopMaps { get; set; } = new();
[JsonPropertyName("CustomServerCommands")]
public List<CustomServerCommandData> CustomServerCommands { get; set; } = new();
[JsonPropertyName("MenuConfig")]
public MenuConfig MenuConfigs { get; set; } = new();
}
public class DatabaseConfig
{
[JsonPropertyName("DatabaseType")]
public string DatabaseType { get; set; } = "SQLite";
[JsonPropertyName("SqliteFilePath")]
public string SqliteFilePath { get; set; } = "cs2-simpleadmin.sqlite";
[JsonPropertyName("DatabaseHost")]
public string DatabaseHost { get; set; } = "";
[JsonPropertyName("DatabasePort")]
public int DatabasePort { get; set; } = 3306;
[JsonPropertyName("DatabaseUser")]
public string DatabaseUser { get; set; } = "";
[JsonPropertyName("DatabasePassword")]
public string DatabasePassword { get; set; } = "";
[JsonPropertyName("DatabaseName")]
public string DatabaseName { get; set; } = "";
[JsonPropertyName("DatabaseSSlMode")]
public string DatabaseSSlMode { get; set; } = "preferred";
}
public enum DatabaseType
{
MySQL,
SQLite
}

View File

@@ -0,0 +1,69 @@
using Microsoft.Extensions.Logging;
using MySqlConnector;
namespace CS2_SimpleAdmin.Database;
public class Database(string dbConnectionString)
{
public MySqlConnection GetConnection()
{
try
{
var connection = new MySqlConnection(dbConnectionString);
connection.Open();
// using var cmd = connection.CreateCommand();
// cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
// cmd.ExecuteNonQueryAsync();
return connection;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical($"Unable to connect to database: {ex.Message}");
throw;
}
}
public async Task<MySqlConnection> GetConnectionAsync()
{
try
{
var connection = new MySqlConnection(dbConnectionString);
await connection.OpenAsync();
// await using var cmd = connection.CreateCommand();
// cmd.CommandText = "SET NAMES 'utf8mb4' COLLATE 'utf8mb4_general_ci';";
// await cmd.ExecuteNonQueryAsync();
return connection;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical($"Unable to connect to database: {ex.Message}");
throw;
}
}
// public async Task DatabaseMigration()
// {
// Migration migrator = new(this);
// await migrator.ExecuteMigrationsAsync();
// }
public bool CheckDatabaseConnection(out string? exception)
{
using var connection = GetConnection();
exception = null;
try
{
return connection.Ping();
}
catch (Exception ex)
{
exception = ex.Message;
return false;
}
}
}

View File

@@ -0,0 +1,60 @@
using System.Data.Common;
namespace CS2_SimpleAdmin.Database;
public interface IDatabaseProvider
{
Task<DbConnection> CreateConnectionAsync();
Task<(bool Success, string? Exception)> CheckConnectionAsync();
Task DatabaseMigrationAsync();
// CacheManager
string GetBanSelectQuery(bool multiServer);
string GetIpHistoryQuery();
string GetBanUpdateQuery(bool multiServer);
// PermissionManager
string GetAdminsQuery();
string GetDeleteAdminQuery(bool globalDelete);
string GetAddAdminQuery();
string GetAddAdminFlagsQuery();
string GetUpdateAdminGroupQuery();
string GetGroupsQuery();
string GetGroupIdByNameQuery();
string GetAddGroupQuery();
string GetAddGroupFlagsQuery();
string GetAddGroupServerQuery();
string GetDeleteGroupQuery();
string GetDeleteOldAdminsQuery();
// BanManager
string GetAddBanQuery();
string GetAddBanBySteamIdQuery();
string GetAddBanByIpQuery();
string GetUnbanRetrieveBansQuery(bool multiServer);
string GetUnbanAdminIdQuery();
string GetInsertUnbanQuery(bool includeReason);
string GetUpdateBanStatusQuery();
string GetExpireBansQuery(bool multiServer);
string GetExpireIpBansQuery(bool multiServer);
// MuteManager
string GetAddMuteQuery(bool includePlayerName);
string GetIsMutedQuery(bool multiServer, int timeMode);
string GetMuteStatsQuery(bool multiServer);
string GetUpdateMutePassedQuery(bool multiServer);
string GetCheckExpiredMutesQuery(bool multiServer);
string GetRetrieveMutesQuery(bool multiServer);
string GetUnmuteAdminIdQuery();
string GetInsertUnmuteQuery(bool includeReason);
string GetUpdateMuteStatusQuery();
string GetExpireMutesQuery(bool multiServer, int timeMode);
// WarnManager
string GetAddWarnQuery(bool includePlayerName);
string GetPlayerWarnsQuery(bool multiServer, bool active);
string GetPlayerWarnsCountQuery(bool multiServer, bool active);
string GetUnwarnByIdQuery(bool multiServer);
string GetUnwarnLastQuery(bool multiServer);
string GetExpireWarnsQuery(bool multiServer);
}

View File

@@ -0,0 +1,94 @@
using System.Data.Common;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Database;
public class Migration(string migrationsPath)
{
/// <summary>
/// Executes all migration scripts found in the configured migrations path that have not been applied yet.
/// Creates a migration tracking table if it does not exist.
/// Applies migration scripts in filename order and logs successes or failures.
/// </summary>
public async Task ExecuteMigrationsAsync()
{
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
var files = Directory.GetFiles(migrationsPath, "*.sql").OrderBy(f => f).ToList();
if (files.Count == 0) return;
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
await using (var cmd = connection.CreateCommand())
{
cmd.CommandText = """
CREATE TABLE IF NOT EXISTS sa_migrations (
id INTEGER PRIMARY KEY AUTOINCREMENT,
version TEXT NOT NULL
);
""";
await cmd.ExecuteNonQueryAsync();
}
var lastAppliedVersion = await GetLastAppliedVersionAsync(connection);
foreach (var file in files)
{
var version = Path.GetFileNameWithoutExtension(file);
if (string.Compare(version, lastAppliedVersion, StringComparison.OrdinalIgnoreCase) <= 0)
continue;
try
{
var sqlScript = await File.ReadAllTextAsync(file);
await using (var cmdMigration = connection.CreateCommand())
{
cmdMigration.CommandText = sqlScript;
await cmdMigration.ExecuteNonQueryAsync();
}
await UpdateLastAppliedVersionAsync(connection, version);
CS2_SimpleAdmin._logger?.LogInformation($"Migration \"{version}\" successfully applied.");
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex, $"Error applying migration \"{version}\".");
break;
}
}
}
/// <summary>
/// Retrieves the version string of the last applied migration from the database.
/// </summary>
/// <param name="connection">The open database connection.</param>
/// <returns>The version string of the last applied migration, or empty string if none.</returns>
private static async Task<string> GetLastAppliedVersionAsync(DbConnection connection)
{
await using var cmd = connection.CreateCommand();
cmd.CommandText = "SELECT version FROM sa_migrations ORDER BY id DESC LIMIT 1;";
var result = await cmd.ExecuteScalarAsync();
return result?.ToString() ?? string.Empty;
}
/// <summary>
/// Inserts a record tracking the successful application of a migration version.
/// </summary>
/// <param name="connection">The open database connection.</param>
/// <param name="version">The version string of the migration applied.</param>
private static async Task UpdateLastAppliedVersionAsync(DbConnection connection, string version)
{
await using var cmd = connection.CreateCommand();
cmd.CommandText = "INSERT INTO sa_migrations (version) VALUES (@Version);";
var param = cmd.CreateParameter();
param.ParameterName = "@Version";
param.Value = version;
cmd.Parameters.Add(param);
await cmd.ExecuteNonQueryAsync();
}
}

View File

@@ -0,0 +1,50 @@
CREATE TABLE IF NOT EXISTS `sa_bans` (
`id` INT NOT NULL AUTO_INCREMENT PRIMARY KEY,
`player_name` VARCHAR(128),
`player_steamid` VARCHAR(64),
`player_ip` VARCHAR(128),
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INT NOT NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`server_id` INT NULL,
`status` ENUM('ACTIVE', 'UNBANNED', 'EXPIRED', '') NOT NULL DEFAULT 'ACTIVE'
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sa_mutes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`player_name` varchar(128) NULL,
`player_steamid` varchar(64) NOT NULL,
`admin_steamid` varchar(64) NOT NULL,
`admin_name` varchar(128) NOT NULL,
`reason` varchar(255) NOT NULL,
`duration` int(11) NOT NULL,
`ends` timestamp NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`type` enum('GAG','MUTE','SILENCE','') NOT NULL DEFAULT 'GAG',
`server_id` INT NULL,
`status` enum('ACTIVE','UNMUTED','EXPIRED','') NOT NULL DEFAULT 'ACTIVE',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sa_admins` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`player_name` varchar(128) NOT NULL,
`player_steamid` varchar(64) NOT NULL,
`flags` TEXT NULL,
`immunity` int(11) NOT NULL DEFAULT 0,
`server_id` INT NULL,
`ends` timestamp NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sa_servers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`hostname` varchar(128) NOT NULL,
`address` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `address` (`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -0,0 +1,9 @@
CREATE TABLE IF NOT EXISTS `sa_admins_flags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`admin_id` int(11) NOT NULL,
`flag` varchar(64) NOT NULL,
PRIMARY KEY (`id`),
FOREIGN KEY (`admin_id`) REFERENCES `sa_admins` (`id`) ON DELETE CASCADE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `sa_admins` CHANGE `flags` `flags` TEXT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL;

View File

@@ -0,0 +1,4 @@
ALTER TABLE `sa_bans` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
ALTER TABLE `sa_mutes` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
ALTER TABLE `sa_admins` CHANGE `player_name` `player_name` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;
ALTER TABLE `sa_servers` CHANGE `hostname` `hostname` VARCHAR(128) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NULL DEFAULT NULL AFTER `id`;

View File

@@ -0,0 +1,36 @@
INSERT INTO sa_admins_flags (admin_id, flag)
SELECT
min_admins.admin_id,
TRIM(SUBSTRING_INDEX(SUBSTRING_INDEX(sa_admins.flags, ',', numbers.n), ',', -1)) AS flag
FROM (
SELECT MIN(id) AS admin_id, player_steamid, server_id
FROM sa_admins
WHERE player_steamid != 'Console'
GROUP BY player_steamid, server_id
) AS min_admins
JOIN sa_admins ON min_admins.player_steamid = sa_admins.player_steamid
JOIN (
SELECT 1 AS n UNION ALL
SELECT 2 UNION ALL
SELECT 3 UNION ALL
SELECT 4 UNION ALL
SELECT 5 UNION ALL
SELECT 6 UNION ALL
SELECT 7 UNION ALL
SELECT 8 UNION ALL
SELECT 9 UNION ALL
SELECT 10 UNION ALL
SELECT 11 UNION ALL
SELECT 12 UNION ALL
SELECT 13 UNION ALL
SELECT 14 UNION ALL
SELECT 15 UNION ALL
SELECT 16 UNION ALL
SELECT 17 UNION ALL
SELECT 18 UNION ALL
SELECT 19 UNION ALL
SELECT 20
) AS numbers
ON CHAR_LENGTH(sa_admins.flags) - CHAR_LENGTH(REPLACE(sa_admins.flags, ',', '')) >= numbers.n - 1
AND (min_admins.server_id = sa_admins.server_id OR (min_admins.server_id IS NULL AND sa_admins.server_id IS NULL))
WHERE sa_admins.id IS NOT NULL;

View File

@@ -0,0 +1,29 @@
CREATE TABLE IF NOT EXISTS `sa_unbans` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`ban_id` int(11) NOT NULL,
`admin_id` int(11) NOT NULL DEFAULT 0,
`reason` varchar(255) NOT NULL DEFAULT 'Unknown',
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sa_unmutes` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`mute_id` int(11) NOT NULL,
`admin_id` int(11) NOT NULL DEFAULT 0,
`reason` varchar(255) NOT NULL DEFAULT 'Unknown',
`date` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
INSERT IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, NOW());
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;
ALTER TABLE `sa_bans` ADD `unban_id` INT NULL AFTER `server_id`;
ALTER TABLE `sa_mutes` ADD `unmute_id` INT NULL AFTER `server_id`;
ALTER TABLE `sa_bans` ADD FOREIGN KEY (`unban_id`) REFERENCES `sa_unbans`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_mutes` ADD FOREIGN KEY (`unmute_id`) REFERENCES `sa_unmutes`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_unbans` ADD FOREIGN KEY (`admin_id`) REFERENCES `sa_admins`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_unmutes` ADD FOREIGN KEY (`admin_id`) REFERENCES `sa_admins`(`id`) ON DELETE CASCADE;

View File

@@ -0,0 +1,26 @@
CREATE TABLE IF NOT EXISTS `sa_groups` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) NOT NULL,
`immunity` int(11) NOT NULL DEFAULT 0,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sa_groups_flags` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`flag` varchar(64) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`group_id` int(11) NOT NULL,
`server_id` int(11) NOT NULL,
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;
ALTER TABLE `sa_admins` ADD `group_id` INT NULL AFTER `created`;
ALTER TABLE `sa_groups_flags` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_groups_servers` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE CASCADE;
ALTER TABLE `sa_admins` ADD FOREIGN KEY (`group_id`) REFERENCES `sa_groups`(`id`) ON DELETE SET NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_groups_servers` CHANGE `server_id` `server_id` INT(11) NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_mutes` ADD `passed` INT NULL AFTER `duration`;

View File

@@ -0,0 +1,8 @@
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`steamid` bigint(20) NOT NULL,
`address` varchar(64) NOT NULL,
`used_at` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `steamid` (`steamid`,`address`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -0,0 +1,14 @@
CREATE TABLE IF NOT EXISTS `sa_warns` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`player_name` varchar(128) DEFAULT NULL,
`player_steamid` varchar(64) NOT NULL,
`admin_steamid` varchar(64) NOT NULL,
`admin_name` varchar(128) NOT NULL,
`reason` varchar(255) NOT NULL,
`duration` int(11) NOT NULL,
`ends` timestamp NOT NULL,
`created` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`server_id` int(11) DEFAULT NULL,
`status` enum('ACTIVE','EXPIRED','') NOT NULL DEFAULT 'ACTIVE',
PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_general_ci;

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_servers` ADD `rcon_password` varchar(128) NULL AFTER `hostname`;

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP AFTER `status`;

View File

@@ -0,0 +1,13 @@
DELETE FROM `sa_players_ips`
WHERE `id` NOT IN (
SELECT * FROM (
SELECT MIN(`id`)
FROM `sa_players_ips`
GROUP BY `steamid`
) AS `keep_ids`
);
DELETE FROM sa_players_ips WHERE INET_ATON(address) IS NULL AND address IS NOT NULL;
UPDATE `sa_players_ips` SET `address` = INET_ATON(address);
ALTER TABLE `sa_players_ips` CHANGE `address` `address` INT UNSIGNED NOT NULL;
ALTER TABLE `sa_players_ips` ADD INDEX (used_at DESC);
ALTER TABLE `sa_players_ips` ADD `name` VARCHAR(64) NULL DEFAULT NULL AFTER `steamid`;

View File

@@ -0,0 +1,3 @@
ALTER TABLE sa_mutes ADD INDEX (player_steamid, status, ends);
ALTER TABLE sa_mutes ADD INDEX(player_steamid, status, server_id, duration);
ALTER TABLE sa_mutes ADD INDEX(player_steamid, type);

View File

@@ -0,0 +1,23 @@
ALTER TABLE `sa_bans` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
UPDATE `sa_bans`
SET admin_steamid = '0'
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_bans` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
ALTER TABLE `sa_mutes` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
UPDATE `sa_mutes`
SET admin_steamid = '0'
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_mutes` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
ALTER TABLE `sa_warns` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;
UPDATE `sa_warns`
SET admin_steamid = '0'
WHERE admin_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_warns` CHANGE `admin_steamid` `admin_steamid` BIGINT NOT NULL;
UPDATE `sa_admins`
SET player_steamid = '0'
WHERE player_steamid NOT REGEXP '^[0-9]+$';
ALTER TABLE `sa_admins` CHANGE `player_steamid` `player_steamid` BIGINT NULL DEFAULT NULL;

View File

@@ -0,0 +1,47 @@
CREATE TABLE IF NOT EXISTS `sa_bans` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128),
`player_steamid` VARCHAR(64),
`player_ip` VARCHAR(128),
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INTEGER NOT NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`server_id` INTEGER NULL,
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
);
CREATE TABLE IF NOT EXISTS `sa_mutes` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128) NULL,
`player_steamid` VARCHAR(64) NOT NULL,
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INTEGER NOT NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`type` TEXT NOT NULL DEFAULT 'GAG',
`server_id` INTEGER NULL,
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
);
CREATE TABLE IF NOT EXISTS `sa_admins` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128) NOT NULL,
`player_steamid` VARCHAR(64) NOT NULL,
`flags` TEXT NULL,
`immunity` INTEGER NOT NULL DEFAULT 0,
`server_id` INTEGER NULL,
`ends` TIMESTAMP NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `sa_servers` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`hostname` VARCHAR(128) NOT NULL,
`address` VARCHAR(64) NOT NULL,
UNIQUE (`address`)
);

View File

@@ -0,0 +1,6 @@
CREATE TABLE IF NOT EXISTS `sa_admins_flags` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`admin_id` INTEGER NOT NULL,
`flag` VARCHAR(64) NOT NULL,
FOREIGN KEY (`admin_id`) REFERENCES `sa_admins` (`id`) ON DELETE CASCADE
);

View File

@@ -0,0 +1,46 @@
INSERT INTO sa_admins_flags (admin_id, flag)
WITH RECURSIVE
min_admins AS (
SELECT MIN(id) AS admin_id, player_steamid, server_id
FROM sa_admins
WHERE player_steamid != 'Console'
GROUP BY player_steamid, server_id
),
split_flags AS (
SELECT
ma.admin_id,
sa.flags,
1 AS pos,
CASE
WHEN INSTR(sa.flags || ',', ',') = 0 THEN sa.flags
ELSE SUBSTR(sa.flags, 1, INSTR(sa.flags || ',', ',') - 1)
END AS flag,
CASE
WHEN INSTR(sa.flags || ',', ',') = 0 THEN ''
ELSE SUBSTR(sa.flags, INSTR(sa.flags || ',', ',') + 1)
END AS remaining
FROM min_admins ma
JOIN sa_admins sa ON ma.player_steamid = sa.player_steamid
AND (ma.server_id = sa.server_id OR (ma.server_id IS NULL AND sa.server_id IS NULL))
WHERE sa.flags IS NOT NULL AND sa.flags != ''
UNION ALL
SELECT
admin_id,
flags,
pos + 1,
CASE
WHEN INSTR(remaining || ',', ',') = 0 THEN remaining
ELSE SUBSTR(remaining, 1, INSTR(remaining || ',', ',') - 1)
END AS flag,
CASE
WHEN INSTR(remaining || ',', ',') = 0 THEN ''
ELSE SUBSTR(remaining, INSTR(remaining || ',', ',') + 1)
END AS remaining
FROM split_flags
WHERE remaining != ''
)
SELECT admin_id, TRIM(flag)
FROM split_flags
WHERE flag IS NOT NULL AND TRIM(flag) != '';

View File

@@ -0,0 +1,23 @@
CREATE TABLE IF NOT EXISTS `sa_unbans` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`ban_id` INTEGER NOT NULL,
`admin_id` INTEGER NOT NULL DEFAULT 0,
`reason` VARCHAR(255) NOT NULL DEFAULT 'Unknown',
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE IF NOT EXISTS `sa_unmutes` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`mute_id` INTEGER NOT NULL,
`admin_id` INTEGER NOT NULL DEFAULT 0,
`reason` VARCHAR(255) NOT NULL DEFAULT 'Unknown',
`date` TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
INSERT OR IGNORE INTO `sa_admins` (`id`, `player_name`, `player_steamid`, `flags`, `immunity`, `server_id`, `ends`, `created`)
VALUES (0, 'Console', 'Console', '', '0', NULL, NULL, CURRENT_TIMESTAMP);
UPDATE `sa_admins` SET `id` = 0 WHERE `id` = -1;
ALTER TABLE `sa_bans` ADD `unban_id` INTEGER NULL;
ALTER TABLE `sa_mutes` ADD `unmute_id` INTEGER NULL;

View File

@@ -0,0 +1,21 @@
CREATE TABLE IF NOT EXISTS `sa_groups` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`name` VARCHAR(255) NOT NULL,
`immunity` INTEGER NOT NULL DEFAULT 0
);
CREATE TABLE IF NOT EXISTS `sa_groups_flags` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`group_id` INTEGER NOT NULL,
`flag` VARCHAR(64) NOT NULL,
FOREIGN KEY (`group_id`) REFERENCES `sa_groups` (`id`) ON DELETE CASCADE
);
CREATE TABLE IF NOT EXISTS `sa_groups_servers` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`group_id` INTEGER NOT NULL,
`server_id` INTEGER NULL,
FOREIGN KEY (`group_id`) REFERENCES `sa_groups` (`id`) ON DELETE CASCADE
);
ALTER TABLE `sa_admins` ADD `group_id` INTEGER NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_mutes` ADD `passed` INTEGER NULL;

View File

@@ -0,0 +1,7 @@
CREATE TABLE IF NOT EXISTS `sa_players_ips` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`steamid` INTEGER NOT NULL,
`address` VARCHAR(64) NOT NULL,
`used_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
UNIQUE (`steamid`, `address`)
);

View File

@@ -0,0 +1,13 @@
CREATE TABLE IF NOT EXISTS `sa_warns` (
`id` INTEGER NOT NULL PRIMARY KEY AUTOINCREMENT,
`player_name` VARCHAR(128) DEFAULT NULL,
`player_steamid` VARCHAR(64) NOT NULL,
`admin_steamid` VARCHAR(64) NOT NULL,
`admin_name` VARCHAR(128) NOT NULL,
`reason` VARCHAR(255) NOT NULL,
`duration` INTEGER NOT NULL,
`ends` TIMESTAMP NOT NULL,
`created` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
`server_id` INTEGER DEFAULT NULL,
`status` TEXT NOT NULL DEFAULT 'ACTIVE'
);

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_servers` ADD `rcon_password` VARCHAR(128) NULL;

View File

@@ -0,0 +1 @@
ALTER TABLE `sa_bans` ADD COLUMN `updated_at` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP;

View File

@@ -0,0 +1,9 @@
DELETE FROM `sa_players_ips`
WHERE `id` NOT IN (
SELECT MIN(`id`)
FROM `sa_players_ips`
GROUP BY `steamid`
);
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);

View File

@@ -0,0 +1,3 @@
CREATE INDEX IF NOT EXISTS `idx_sa_mutes_steamid_status_ends` ON `sa_mutes` (`player_steamid`, `status`, `ends`);
CREATE INDEX IF NOT EXISTS `idx_sa_mutes_steamid_status_server_duration` ON `sa_mutes` (`player_steamid`, `status`, `server_id`, `duration`);
CREATE INDEX IF NOT EXISTS `idx_sa_mutes_steamid_type` ON `sa_mutes` (`player_steamid`, `type`);

View File

@@ -0,0 +1,15 @@
UPDATE `sa_bans`
SET admin_steamid = '0'
WHERE admin_steamid NOT GLOB '[0-9]*';
UPDATE `sa_mutes`
SET admin_steamid = '0'
WHERE admin_steamid NOT GLOB '[0-9]*';
UPDATE `sa_warns`
SET admin_steamid = '0'
WHERE admin_steamid NOT GLOB '[0-9]*';
UPDATE `sa_admins`
SET player_steamid = '0'
WHERE player_steamid NOT GLOB '[0-9]*';

View File

@@ -0,0 +1,384 @@
using System.Data.Common;
using MySqlConnector;
namespace CS2_SimpleAdmin.Database;
public class MySqlDatabaseProvider(string connectionString) : IDatabaseProvider
{
public async Task<DbConnection> CreateConnectionAsync()
{
var connection = new MySqlConnection(connectionString);
await connection.OpenAsync();
return connection;
}
public async Task<(bool Success, string? Exception)> CheckConnectionAsync()
{
try
{
await using var conn = await CreateConnectionAsync();
return (true, null);
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
public Task DatabaseMigrationAsync()
{
var migration = new Migration(CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations/Mysql");
return migration.ExecuteMigrationsAsync();
}
public string GetBanSelectQuery(bool multiServer)
{
return multiServer ? """
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
""" : """
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""";
}
public static string GetBanUpdatedSelectQuery(bool multiServer)
{
return multiServer ? """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""" : """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
""";
}
public string GetIpHistoryQuery()
{
return "SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
}
public string GetBanUpdateQuery(bool multiServer)
{
return multiServer ? """
UPDATE sa_bans
SET
player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
""" : """
UPDATE sa_bans
SET
player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId
""";
}
public string GetAdminsQuery()
{
return """
SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends
FROM sa_admins_flags
JOIN sa_admins ON sa_admins_flags.admin_id = sa_admins.id
WHERE (sa_admins.ends IS NULL OR sa_admins.ends > @CurrentTime)
AND (sa_admins.server_id IS NULL OR sa_admins.server_id = @serverid)
ORDER BY sa_admins.player_steamid
""";
}
public string GetDeleteAdminQuery(bool globalDelete) =>
globalDelete
? "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID"
: "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId";
public string GetAddAdminQuery() =>
"INSERT INTO sa_admins (player_steamid, player_name, immunity, ends, created, server_id) " +
"VALUES (@playerSteamId, @playerName, @immunity, @ends, @created, @serverid); SELECT LAST_INSERT_ID();";
public string GetGroupsQuery()
{
return """
SELECT g.group_id, sg.name AS group_name, sg.immunity, f.flag
FROM sa_groups_flags f
JOIN sa_groups_servers g ON f.group_id = g.group_id
JOIN sa_groups sg ON sg.id = g.group_id
WHERE (g.server_id = @serverid OR server_id IS NULL)
""";
}
public string GetAddAdminFlagsQuery() =>
"INSERT INTO sa_admins_flags (admin_id, flag) VALUES (@adminId, @flag);";
public string GetUpdateAdminGroupQuery() =>
"UPDATE sa_admins SET group_id = @groupId WHERE id = @adminId;";
public string GetAddGroupQuery() =>
"INSERT INTO sa_groups (name, immunity) VALUES (@groupName, @immunity); SELECT LAST_INSERT_ID();";
public string GetGroupIdByNameQuery() =>
"""
SELECT sgs.group_id
FROM sa_groups_servers sgs
JOIN sa_groups sg ON sgs.group_id = sg.id
WHERE sg.name = @groupName
ORDER BY (sgs.server_id = @serverId) DESC, sgs.server_id ASC
LIMIT 1;
""";
public string GetAddGroupFlagsQuery() =>
"INSERT INTO sa_groups_flags (group_id, flag) VALUES (@groupId, @flag);";
public string GetAddGroupServerQuery() =>
"INSERT INTO sa_groups_servers (group_id, server_id) VALUES (@groupId, @server_id);";
public string GetDeleteGroupQuery() =>
"DELETE FROM sa_groups WHERE name = @groupName;";
public string GetDeleteOldAdminsQuery() =>
"DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime;";
public string GetAddBanQuery()
{
return """
INSERT INTO `sa_bans`
(`player_steamid`, `player_name`, `player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @playerName, @playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
""";
}
public string GetAddBanBySteamIdQuery()
{
return """
INSERT INTO `sa_bans`
(`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
""";
}
public string GetAddBanByIpQuery()
{
return """
INSERT INTO `sa_bans`
(`player_ip`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
""";
}
public string GetUnbanRetrieveBansQuery(bool multiServer)
{
return multiServer
? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'"
: "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid";
}
public string GetUnbanAdminIdQuery()
{
return "SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
}
public string GetInsertUnbanQuery(bool includeReason)
{
return includeReason
? "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT LAST_INSERT_ID();"
: "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT LAST_INSERT_ID();";
}
public string GetUpdateBanStatusQuery()
{
return "UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId";
}
public string GetExpireBansQuery(bool multiServer)
{
return multiServer
? "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime"
: "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime AND server_id = @serverid";
}
public string GetExpireIpBansQuery(bool multiServer)
{
return 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 AND server_id = @serverid";
}
public string GetAddMuteQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO `sa_mutes`
(`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`)
VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT LAST_INSERT_ID();
"""
: """
INSERT INTO `sa_mutes`
(`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `type`, `server_id`)
VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT LAST_INSERT_ID();
""";
public string GetIsMutedQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))")
: (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid");
public string GetMuteStatsQuery(bool multiServer) =>
multiServer
? """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID;
"""
: """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId;
""";
public string GetUpdateMutePassedQuery(bool multiServer) =>
multiServer
? "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'"
: "UPDATE `sa_mutes` SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetCheckExpiredMutesQuery(bool multiServer) =>
multiServer
? "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'"
: "SELECT * FROM `sa_mutes` WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetRetrieveMutesQuery(bool multiServer) =>
multiServer
? "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE'"
: "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE' AND server_id = @serverid";
public string GetUnmuteAdminIdQuery() =>
"SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
public string GetInsertUnmuteQuery(bool includeReason) =>
includeReason
? "INSERT INTO sa_unmutes (mute_id, admin_id, reason) VALUES (@muteId, @adminId, @reason); SELECT LAST_INSERT_ID();"
: "INSERT INTO sa_unmutes (mute_id, admin_id) VALUES (@muteId, @adminId); SELECT LAST_INSERT_ID();";
public string GetUpdateMuteStatusQuery() =>
"UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId";
public string GetExpireMutesQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration`")
: (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND `passed` >= `duration` AND server_id = @serverid");
public string GetAddWarnQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO `sa_warns`
(`player_steamid`, `player_name`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @playerName, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
"""
: """
INSERT INTO `sa_warns`
(`player_steamid`, `admin_steamid`, `admin_name`, `reason`, `duration`, `ends`, `created`, `server_id`)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT LAST_INSERT_ID();
""";
public string GetPlayerWarnsQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID ORDER BY id DESC"
: active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid ORDER BY id DESC";
public string GetPlayerWarnsCountQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID"
: active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid";
public string GetUnwarnByIdQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId AND server_id = @serverid";
public string GetUnwarnLastQuery(bool multiServer) =>
multiServer
? """
UPDATE sa_warns
JOIN (
SELECT MAX(id) AS max_id
FROM sa_warns
WHERE player_steamid = @steamid AND status = 'ACTIVE'
) AS subquery ON sa_warns.id = subquery.max_id
SET sa_warns.status = 'EXPIRED'
WHERE sa_warns.status = 'ACTIVE' AND sa_warns.player_steamid = @steamid;
"""
: """
UPDATE sa_warns
JOIN (
SELECT MAX(id) AS max_id
FROM sa_warns
WHERE player_steamid = @steamid AND status = 'ACTIVE' AND server_id = @serverid
) AS subquery ON sa_warns.id = subquery.max_id
SET sa_warns.status = 'EXPIRED'
WHERE sa_warns.status = 'ACTIVE' AND sa_warns.player_steamid = @steamid AND sa_warns.server_id = @serverid;
""";
public string GetExpireWarnsQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND `duration` > 0 AND ends <= @CurrentTime AND server_id = @serverid";
public ValueTask DisposeAsync() => ValueTask.CompletedTask;
}

View File

@@ -0,0 +1,366 @@
using System.Data.Common;
using System.Data.SQLite;
namespace CS2_SimpleAdmin.Database;
public class SqliteDatabaseProvider(string filePath) : IDatabaseProvider
{
private readonly string _connectionString = $"Data Source={filePath}";
public async Task<DbConnection> CreateConnectionAsync()
{
var conn = new SQLiteConnection(_connectionString);
await conn.OpenAsync();
return conn;
}
public async Task<(bool Success, string? Exception)> CheckConnectionAsync()
{
try
{
await using var conn = await CreateConnectionAsync();
await using var cmd = conn.CreateCommand();
cmd.CommandText = "SELECT 1";
await cmd.ExecuteScalarAsync();
return (true, null);
}
catch (Exception ex)
{
return (false, ex.Message);
}
}
public Task DatabaseMigrationAsync()
{
var migration = new Migration(CS2_SimpleAdmin.Instance.ModuleDirectory + "/Database/Migrations/Sqlite");
return migration.ExecuteMigrationsAsync();
}
public string GetBanSelectQuery(bool multiServer) =>
multiServer
? """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
"""
: """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""";
public static string GetBanUpdatedSelectQuery(bool multiServer) =>
multiServer
? """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE updated_at > @lastUpdate OR created > @lastUpdate
ORDER BY updated_at DESC
"""
: """
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE (updated_at > @lastUpdate OR created > @lastUpdate)
AND server_id = @serverId
ORDER BY updated_at DESC
""";
public string GetIpHistoryQuery() =>
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC";
public string GetBanUpdateQuery(bool multiServer) =>
multiServer
? """
UPDATE sa_bans
SET player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
"""
: """
UPDATE sa_bans
SET player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
AND server_id = @ServerId
""";
public string GetAddBanQuery() =>
"""
INSERT INTO sa_bans
(player_steamid, player_name, player_ip, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @playerName, @playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetAddBanBySteamIdQuery() =>
"""
INSERT INTO sa_bans
(player_steamid, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetAddBanByIpQuery() =>
"""
INSERT INTO sa_bans
(player_ip, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerIp, @adminSteamid, @adminName, @banReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetUnbanRetrieveBansQuery(bool multiServer) =>
multiServer
? "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE'"
: "SELECT id FROM sa_bans WHERE (player_steamid = @pattern OR player_name = @pattern OR player_ip = @pattern) AND status = 'ACTIVE' AND server_id = @serverid";
public string GetUnbanAdminIdQuery() =>
"SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
public string GetInsertUnbanQuery(bool includeReason) =>
includeReason
? "INSERT INTO sa_unbans (ban_id, admin_id, reason) VALUES (@banId, @adminId, @reason); SELECT last_insert_rowid();"
: "INSERT INTO sa_unbans (ban_id, admin_id) VALUES (@banId, @adminId); SELECT last_insert_rowid();";
public string GetUpdateBanStatusQuery() =>
"UPDATE sa_bans SET status = 'UNBANNED', unban_id = @unbanId WHERE id = @banId";
public string GetExpireBansQuery(bool multiServer) =>
multiServer
? "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime"
: "UPDATE sa_bans SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @currentTime AND server_id = @serverid";
public string GetExpireIpBansQuery(bool 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 AND server_id = @serverid";
public string GetAdminsQuery() =>
"""
SELECT sa_admins.player_steamid, sa_admins.player_name, sa_admins_flags.flag, sa_admins.immunity, sa_admins.ends
FROM sa_admins_flags
JOIN sa_admins ON sa_admins_flags.admin_id = sa_admins.id
WHERE (sa_admins.ends IS NULL OR sa_admins.ends > @CurrentTime)
AND (sa_admins.server_id IS NULL OR sa_admins.server_id = @serverid)
ORDER BY sa_admins.player_steamid
""";
public string GetDeleteAdminQuery(bool globalDelete) =>
globalDelete
? "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID"
: "DELETE FROM sa_admins WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId";
public string GetAddAdminQuery() =>
"""
INSERT INTO sa_admins (player_steamid, player_name, immunity, ends, created, server_id)
VALUES (@playerSteamId, @playerName, @immunity, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetGroupsQuery() =>
"""
SELECT g.group_id, sg.name AS group_name, sg.immunity, f.flag
FROM sa_groups_flags f
JOIN sa_groups_servers g ON f.group_id = g.group_id
JOIN sa_groups sg ON sg.id = g.group_id
WHERE (g.server_id = @serverid OR server_id IS NULL)
""";
public string GetAddAdminFlagsQuery() =>
"INSERT INTO sa_admins_flags (admin_id, flag) VALUES (@adminId, @flag);";
public string GetUpdateAdminGroupQuery() =>
"UPDATE sa_admins SET group_id = @groupId WHERE id = @adminId;";
public string GetAddGroupQuery() =>
"""
INSERT INTO sa_groups (name, immunity) VALUES (@groupName, @immunity);
SELECT last_insert_rowid();
""";
public string GetGroupIdByNameQuery() =>
"""
SELECT sgs.group_id
FROM sa_groups_servers sgs
JOIN sa_groups sg ON sgs.group_id = sg.id
WHERE sg.name = @groupName
ORDER BY (sgs.server_id = @serverId) DESC, sgs.server_id ASC
LIMIT 1;
""";
public string GetAddGroupFlagsQuery() =>
"INSERT INTO sa_groups_flags (group_id, flag) VALUES (@groupId, @flag);";
public string GetAddGroupServerQuery() =>
"INSERT INTO sa_groups_servers (group_id, server_id) VALUES (@groupId, @server_id);";
public string GetDeleteGroupQuery() =>
"DELETE FROM sa_groups WHERE name = @groupName;";
public string GetDeleteOldAdminsQuery() =>
"DELETE FROM sa_admins WHERE ends IS NOT NULL AND ends <= @CurrentTime;";
public string GetAddMuteQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO sa_mutes
(player_steamid, player_name, admin_steamid, admin_name, reason, duration, ends, created, type, server_id)
VALUES (@playerSteamid, @playerName, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT last_insert_rowid();
"""
: """
INSERT INTO sa_mutes
(player_steamid, admin_steamid, admin_name, reason, duration, ends, created, type, server_id)
VALUES (@playerSteamid, @adminSteamid, @adminName, @muteReason, @duration, @ends, @created, @type, @serverid);
SELECT last_insert_rowid();
""";
public string GetIsMutedQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime)"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0))")
: (timeMode == 1
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR ends > @CurrentTime) AND server_id = @serverid"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' AND (duration = 0 OR duration > COALESCE(passed, 0)) AND server_id = @serverid");
public string GetMuteStatsQuery(bool multiServer) =>
multiServer
? """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID;
"""
: """
SELECT
COUNT(CASE WHEN type = 'MUTE' THEN 1 END) AS TotalMutes,
COUNT(CASE WHEN type = 'GAG' THEN 1 END) AS TotalGags,
COUNT(CASE WHEN type = 'SILENCE' THEN 1 END) AS TotalSilences
FROM sa_mutes
WHERE player_steamid = @PlayerSteamID AND server_id = @ServerId;
""";
public string GetUpdateMutePassedQuery(bool multiServer) =>
multiServer
? "UPDATE sa_mutes SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE'"
: "UPDATE sa_mutes SET passed = COALESCE(passed, 0) + 1 WHERE (player_steamid = @PlayerSteamID) AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetCheckExpiredMutesQuery(bool multiServer) =>
multiServer
? "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE'"
: "SELECT * FROM sa_mutes WHERE player_steamid = @PlayerSteamID AND passed >= duration AND duration > 0 AND status = 'ACTIVE' AND server_id = @serverid";
public string GetRetrieveMutesQuery(bool multiServer) =>
multiServer
? "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE'"
: "SELECT id FROM sa_mutes WHERE (player_steamid = @pattern OR player_name = @pattern) AND type = @muteType AND status = 'ACTIVE' AND server_id = @serverid";
public string GetUnmuteAdminIdQuery() =>
"SELECT id FROM sa_admins WHERE player_steamid = @adminSteamId";
public string GetInsertUnmuteQuery(bool includeReason) =>
includeReason
? "INSERT INTO sa_unmutes (mute_id, admin_id, reason) VALUES (@muteId, @adminId, @reason); SELECT last_insert_rowid();"
: "INSERT INTO sa_unmutes (mute_id, admin_id) VALUES (@muteId, @adminId); SELECT last_insert_rowid();";
public string GetUpdateMuteStatusQuery() =>
"UPDATE sa_mutes SET status = 'UNMUTED', unmute_id = @unmuteId WHERE id = @muteId";
public string GetExpireMutesQuery(bool multiServer, int timeMode) =>
multiServer
? (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND passed >= duration")
: (timeMode == 1
? "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime AND server_id = @serverid"
: "UPDATE sa_mutes SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND passed >= duration AND server_id = @serverid");
public string GetAddWarnQuery(bool includePlayerName) =>
includePlayerName
? """
INSERT INTO sa_warns
(player_steamid, player_name, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @playerName, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
"""
: """
INSERT INTO sa_warns
(player_steamid, admin_steamid, admin_name, reason, duration, ends, created, server_id)
VALUES
(@playerSteamid, @adminSteamid, @adminName, @warnReason, @duration, @ends, @created, @serverid);
SELECT last_insert_rowid();
""";
public string GetPlayerWarnsQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID ORDER BY id DESC"
: active
? "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE' ORDER BY id DESC"
: "SELECT * FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid ORDER BY id DESC";
public string GetPlayerWarnsCountQuery(bool multiServer, bool active) =>
multiServer
? active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID"
: active
? "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid AND status = 'ACTIVE'"
: "SELECT COUNT(*) FROM sa_warns WHERE player_steamid = @PlayerSteamID AND server_id = @serverid";
public string GetUnwarnByIdQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND player_steamid = @steamid AND id = @warnId AND server_id = @serverid";
public string GetUnwarnLastQuery(bool multiServer) =>
multiServer
? """
UPDATE sa_warns
SET status = 'EXPIRED'
WHERE status = 'ACTIVE'
AND player_steamid = @steamid
ORDER BY id DESC
LIMIT 1
"""
: """
UPDATE sa_warns
SET status = 'EXPIRED'
WHERE status = 'ACTIVE'
AND player_steamid = @steamid
AND server_id = @serverid
ORDER BY id DESC
LIMIT 1
""";
public string GetExpireWarnsQuery(bool multiServer) =>
multiServer
? "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime"
: "UPDATE sa_warns SET status = 'EXPIRED' WHERE status = 'ACTIVE' AND duration > 0 AND ends <= @CurrentTime AND server_id = @serverid";
}

560
CS2-SimpleAdmin/Events.cs Normal file
View File

@@ -0,0 +1,560 @@
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Attributes.Registration;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
using CS2_SimpleAdmin.Managers;
using CS2_SimpleAdmin.Models;
using CS2_SimpleAdminApi;
using Microsoft.Extensions.Logging;
using System.Text;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.UserMessages;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
private bool _serverLoading;
private void RegisterEvents()
{
RegisterListener<Listeners.OnMapStart>(OnMapStart);
// RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
RegisterListener<Listeners.OnClientConnect>(OnClientConnect);
RegisterListener<Listeners.OnClientConnected>(OnClientConnected);
RegisterListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
if (Config.OtherSettings.UserMessageGagChatType)
HookUserMessage(118, HookUmChat);
AddCommandListener(null, ComamndListenerHandler);
// AddCommandListener("callvote", OnCommandCallVote);
// AddCommandListener("say", OnCommandSay);
// AddCommandListener("say_team", OnCommandTeamSay);
}
private void UnregisterEvents()
{
RemoveListener<Listeners.OnMapStart>(OnMapStart);
RemoveListener<Listeners.OnClientConnect>(OnClientConnect);
RemoveListener<Listeners.OnClientConnected>(OnClientConnected);
RemoveListener<Listeners.OnGameServerSteamAPIActivated>(OnGameServerSteamAPIActivated);
if (Config.OtherSettings.UserMessageGagChatType)
UnhookUserMessage(118, HookUmChat);
RemoveCommandListener(null!, ComamndListenerHandler, HookMode.Pre);
// AddCommandListener("callvote", OnCommandCallVote);
// AddCommandListener("say", OnCommandSay);
// AddCommandListener("say_team", OnCommandTeamSay);
}
// private HookResult OnCommandCallVote(CCSPlayerController? caller, CommandInfo info)
// {
// var voteType = info.GetArg(1).ToLower();
//
// if (voteType != "kick")
// return HookResult.Continue;
//
// var target = int.TryParse(info.GetArg(2), out var userId)
// ? Utilities.GetPlayerFromUserid(userId)
// : null;
//
// if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected)
// return HookResult.Continue;
//
// return !AdminManager.CanPlayerTarget(caller, target) ? HookResult.Stop : HookResult.Continue;
// }
private void OnGameServerSteamAPIActivated()
{
if (ServerLoaded || _serverLoading)
return;
_serverLoading = true;
new ServerManager().LoadServerData();
}
[GameEventHandler(HookMode.Pre)]
public HookResult OnClientDisconnect(EventPlayerDisconnect @event, GameEventInfo info)
{
if (@event.Reason is 149 or 6)
info.DontBroadcast = true;
var player = @event.Userid;
#if DEBUG
Logger.LogCritical("[OnClientDisconnect] Before");
#endif
if (player == null || !player.IsValid || player.IsHLTV)
return HookResult.Continue;
BotPlayers.Remove(player);
CachedPlayers.Remove(player);
SilentPlayers.Remove(player.Slot);
GodPlayers.Remove(player.Slot);
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
if (player.IsBot)
return HookResult.Continue;
#if DEBUG
Logger.LogCritical("[OnClientDisconnect] After Check");
#endif
try
{
if (DisconnectedPlayers.Count >= Config.OtherSettings.DisconnectedPlayersHistoryCount)
DisconnectedPlayers.RemoveAt(0);
var steamId = new SteamID(player.SteamID);
var disconnectedPlayer = DisconnectedPlayers.FirstOrDefault(p => p.SteamId == steamId);
if (disconnectedPlayer != null)
{
disconnectedPlayer.Name = player.PlayerName;
disconnectedPlayer.IpAddress = player.IpAddress?.Split(":")[0];
disconnectedPlayer.DisconnectTime = Time.ActualDateTime();
}
else
{
DisconnectedPlayers.Add(new DisconnectedPlayer(steamId, player.PlayerName,
player.IpAddress?.Split(":")[0], Time.ActualDateTime()));
}
PlayerPenaltyManager.RemoveAllPenalties(player.Slot);
if (player.UserId.HasValue)
PlayersInfo.TryRemove(player.SteamID, out _);
if (!PermissionManager.AdminCache.TryGetValue(steamId, out var data)
|| !(data.ExpirationTime <= Time.ActualDateTime()))
{
return HookResult.Continue;
}
AdminManager.RemovePlayerPermissions(steamId, PermissionManager.AdminCache[steamId].Flags.ToArray());
AdminManager.RemovePlayerFromGroup(steamId, true, PermissionManager.AdminCache[steamId].Flags.ToArray());
var adminData = AdminManager.GetPlayerAdminData(steamId);
if (adminData == null || data.Flags.ToList().Count != 0 && adminData.Groups.ToList().Count != 0)
return HookResult.Continue;
AdminManager.ClearPlayerPermissions(steamId);
AdminManager.RemovePlayerAdminData(steamId);
return HookResult.Continue;
}
catch (Exception ex)
{
Logger.LogError($"An error occurred in OnClientDisconnect: {ex.Message}");
return HookResult.Continue;
}
}
private void OnClientConnect(int playerslot, string name, string ipAddress)
{
#if DEBUG
Logger.LogCritical("[OnClientConnect]");
#endif
var player = Utilities.GetPlayerFromSlot(playerslot);
if (player == null || !player.IsValid || player.IsBot)
return;
PlayerManager.LoadPlayerData(player);
}
private void OnClientConnected(int playerslot)
{
#if DEBUG
Logger.LogCritical("[OnClientConnected]");
#endif
var player = Utilities.GetPlayerFromSlot(playerslot);
if (player == null || !player.IsValid || player.IsBot)
return;
PlayerManager.LoadPlayerData(player);
}
// private void OnClientConnect(int playerslot, string name, string ipaddress)
// {
// #if DEBUG
// Logger.LogCritical("[OnClientConnect]");
// #endif
// if (Config.OtherSettings.BanType == 0)
// return;
//
// if (Instance.CacheManager != null && !Instance.CacheManager.IsPlayerBanned(null, ipaddress.Split(":")[0]))
// return;
//
// var testPlayer = Utilities.GetPlayerFromSlot(playerslot);
// if (testPlayer == null)
// return;
// Logger.LogInformation($"Gracz {testPlayer.PlayerName} ({testPlayer.SteamID.ToString()}) Czas: {DateTime.Now}");
//
// Server.NextFrame((() =>
// {
// var player = Utilities.GetPlayerFromSlot(playerslot);
// if (player == null || !player.IsValid || player.IsBot)
// return;
//
// Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
// }));
//
// // Server.NextFrame(() =>
// // {
// // var player = Utilities.GetPlayerFromSlot(playerslot);
// //
// // if (player == null || !player.IsValid || player.IsBot)
// // return;
// //
// // new PlayerManager().LoadPlayerData(player);
// // });
// }
[GameEventHandler]
public HookResult OnPlayerFullConnect(EventPlayerConnectFull @event, GameEventInfo info)
{
#if DEBUG
Logger.LogCritical("[OnPlayerFullConnect]");
#endif
var player = @event.Userid;
if (player == null || !player.IsValid)
return HookResult.Continue;
if (player is { IsBot: true, IsHLTV: false })
{
BotPlayers.Add(player);
return HookResult.Continue;
}
PlayerManager.LoadPlayerData(player, true);
return HookResult.Continue;
}
[GameEventHandler]
public HookResult OnRoundStart(EventRoundStart @event, GameEventInfo info)
{
#if DEBUG
Logger.LogCritical("[OnRoundStart]");
#endif
GodPlayers.Clear();
SpeedPlayers.Clear();
GravityPlayers.Clear();
foreach (var player in PlayersInfo.Values)
{
player.DiePosition = null;
}
AddTimer(0.41f, () =>
{
foreach (var list in RenamedPlayers)
{
var player = Utilities.GetPlayerFromSteamId(list.Key);
if (player == null || !player.IsValid || player.Connected != PlayerConnectedState.PlayerConnected)
continue;
if (player.PlayerName.Equals(list.Value))
continue;
player.Rename(list.Value);
}
});
return HookResult.Continue;
}
private HookResult HookUmChat(UserMessage um)
{
var author = Utilities.GetPlayerFromIndex(um.ReadInt("entityindex"));
if (author == null || !author.IsValid || author.IsBot)
return HookResult.Continue;
if (!PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Gag, out DateTime? endDateTime) &&
!PlayerPenaltyManager.IsPenalized(author.Slot, PenaltyType.Silence, out endDateTime))
return HookResult.Continue;
if (_localizer == null || endDateTime == null)
return HookResult.Continue;
var message = um.ReadString("param2");
var triggers = CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger);
if (!triggers.Any(trigger => message.StartsWith(trigger))) return HookResult.Stop;
for (var i = um.Recipients.Count - 1; i >= 0; i--)
{
if (um.Recipients[i] != author)
{
um.Recipients.RemoveAt(i);
}
}
return HookResult.Continue;
// author.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", author.GetLanguage()));
}
private HookResult ComamndListenerHandler(CCSPlayerController? player, CommandInfo info)
{
if (player == null || !player.IsValid || player.IsBot)
return HookResult.Continue;
var command = info.GetArg(0).ToLower();
if (Config.OtherSettings.AdditionalCommandsToLog.Contains(command))
Helper.LogCommand(player, info);
switch (command)
{
case "css_admins_reload":
AddTimer(1.0f, () => ReloadAdmins(null));
return HookResult.Continue;
case "callvote":
{
var voteType = info.GetArg(1).ToLower();
if (voteType != "kick")
return HookResult.Continue;
var target = int.TryParse(info.GetArg(2), out var userId)
? Utilities.GetPlayerFromUserid(userId)
: null;
if (target == null || !target.IsValid || target.Connected != PlayerConnectedState.PlayerConnected)
return HookResult.Continue;
return !player.CanTarget(target) ? HookResult.Stop : HookResult.Continue;
}
}
if (!command.Contains("say"))
return HookResult.Continue;
if (info.GetArg(1).Length == 0)
return HookResult.Stop;
var triggers = CoreConfig.PublicChatTrigger.Concat(CoreConfig.SilentChatTrigger);
if (triggers.Any(trigger => info.GetArg(1).StartsWith(trigger)))
{
return HookResult.Continue;
}
// if (!Config.OtherSettings.UserMessageGagChatType)
// {
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out DateTime? endDateTime) ||
PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out endDateTime))
{
if (_localizer != null && endDateTime is not null)
player.SendLocalizedMessage(_localizer, "sa_player_penalty_chat_active", endDateTime.Value.ToString("g", player.GetLanguage()));
return HookResult.Stop;
}
// }
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat") && command == "say" && info.GetArg(1).StartsWith($"@"))
{
player.ExecuteClientCommandFromServer($"css_say {info.GetArg(1).Remove(0, 1)}");
return HookResult.Stop;
}
if (command != "say_team" || !info.GetArg(1).StartsWith($"@")) return HookResult.Continue;
StringBuilder sb = new();
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
{
sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
}
else
{
sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
player.PrintToChat(sb.ToString());
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
}
return HookResult.Stop;
}
/*public HookResult OnCommandSay(CCSPlayerController? player, CommandInfo info)
{
if (player == null || !player.IsValid || player.IsBot)
return HookResult.Continue;
if (info.GetArg(1).StartsWith($"/")
|| info.GetArg(1).StartsWith($"!"))
return HookResult.Continue;
if (info.GetArg(1).Length == 0)
return HookResult.Handled;
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence))
return HookResult.Handled;
return HookResult.Continue;
}*/
public HookResult OnCommandTeamSay(CCSPlayerController? player, CommandInfo info)
{
if (player == null || !player.IsValid || player.IsBot)
return HookResult.Continue;
if (info.GetArg(1).StartsWith($"/")
|| info.GetArg(1).StartsWith($"!"))
return HookResult.Continue;
if (info.GetArg(1).Length == 0)
return HookResult.Handled;
if (PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _) || PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _))
return HookResult.Stop;
if (!info.GetArg(1).StartsWith($"@")) return HookResult.Continue;
StringBuilder sb = new();
if (AdminManager.PlayerHasPermissions(new SteamID(player.SteamID), "@css/chat"))
{
sb.Append(_localizer!["sa_adminchat_template_admin", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
foreach (var p in Utilities.GetPlayers().Where(p => p.IsValid && p is { IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
}
else
{
sb.Append(_localizer!["sa_adminchat_template_player", player.PlayerName, info.GetArg(1).Remove(0, 1)]);
player.PrintToChat(sb.ToString());
foreach (var p in Utilities.GetPlayers().Where(p => p is { IsValid: true, IsBot: false, IsHLTV: false } && AdminManager.PlayerHasPermissions(new SteamID(p.SteamID), "@css/chat")))
{
p.PrintToChat(sb.ToString());
}
}
return HookResult.Handled;
}
private void OnMapStart(string mapName)
{
if (!ServerLoaded || ServerId == null)
AddTimer(2.0f, OnGameServerSteamAPIActivated);
if (Config.OtherSettings.ReloadAdminsEveryMapChange && ServerLoaded && ServerId != null)
AddTimer(5.0f, () => ReloadAdmins(null));
AddTimer(1.0f, ServerManager.CheckHibernationStatus);
// AddTimer(34, () =>
// {
// if (!ServerLoaded)
// OnGameServerSteamAPIActivated();
// });
GodPlayers.Clear();
SilentPlayers.Clear();
SpeedPlayers.Clear();
GravityPlayers.Clear();
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]
public HookResult OnPlayerDeath(EventPlayerDeath @event, GameEventInfo info)
{
var player = @event.Userid;
if (player?.UserId == null || !player.IsValid || player.IsHLTV || player.Connected != PlayerConnectedState.PlayerConnected)
return HookResult.Continue;
SpeedPlayers.Remove(player);
GravityPlayers.Remove(player);
if (!PlayersInfo.ContainsKey(player.SteamID) || @event.Attacker == null)
return HookResult.Continue;
var playerPosition = player.PlayerPawn.Value?.AbsOrigin;
var playerRotation = player.PlayerPawn.Value?.AbsRotation;
PlayersInfo[player.SteamID].DiePosition = new DiePosition(
new Vector3(
playerPosition?.X ?? 0,
playerPosition?.Y ?? 0,
playerPosition?.Z ?? 0
),
new Vector3(
playerRotation?.X ?? 0,
playerRotation?.Y ?? 0,
playerRotation?.Z ?? 0
)
);
return HookResult.Continue;
}
[GameEventHandler(HookMode.Pre)]
public HookResult OnPlayerTeam(EventPlayerTeam @event, GameEventInfo info)
{
var player = @event.Userid;
if (player == null || !player.IsValid || player.IsBot)
return HookResult.Continue;
if (!SilentPlayers.Contains(player.Slot))
return HookResult.Continue;
if (@event is { Oldteam: <= 1, Team: >= 1 })
{
SilentPlayers.Remove(player.Slot);
SimpleAdminApi?.OnAdminToggleSilentEvent(player.Slot, false);
}
return HookResult.Continue;
}
[GameEventHandler]
public HookResult OnPlayerInfo(EventPlayerInfo @event, GameEventInfo _)
{
var player = @event.Userid;
if (player is null || !player.IsValid || player.IsBot)
return HookResult.Continue;
if (!RenamedPlayers.TryGetValue(player.SteamID, out var name)) return HookResult.Continue;
if (player.PlayerName.Equals(name))
return HookResult.Continue;
player.Rename(name);
return HookResult.Continue;
}
}

View File

@@ -0,0 +1,12 @@
namespace CS2_SimpleAdmin;
public static class EnumerableExtensions
{
public static IEnumerable<IEnumerable<T>> ChunkBy<T>(this IEnumerable<T> source, int chunkSize)
{
return source
.Select((x, i) => new { Index = i, Value = x })
.GroupBy(x => x.Index / chunkSize)
.Select(x => x.Select(v => v.Value));
}
}

View File

@@ -0,0 +1,376 @@
using System.Drawing;
using System.Numerics;
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Translations;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Memory;
using Microsoft.Extensions.Localization;
using System.Text;
using CounterStrikeSharp.API.Modules.UserMessages;
using Vector = CounterStrikeSharp.API.Modules.Utils.Vector;
namespace CS2_SimpleAdmin;
public static class PlayerExtensions
{
/// <summary>
/// Slaps the player pawn by applying optional damage and adding a random velocity knockback.
/// </summary>
/// <param name="pawn">The player pawn to slap.</param>
/// <param name="damage">The amount of damage to apply (default is 0).</param>
public static void Slap(this CBasePlayerPawn pawn, int damage = 0)
{
PerformSlap(pawn, damage);
}
/// <summary>
/// Prints a localized chat message to the player with a prefix.
/// </summary>
/// <param name="controller">The player controller to send the message to.</param>
/// <param name="message">The message string.</param>
public static void Print(this CCSPlayerController controller, string message = "")
{
StringBuilder _message = new(CS2_SimpleAdmin._localizer!["sa_prefix"]);
_message.Append(message);
controller.PrintToChat(_message.ToString());
}
/// <summary>
/// Determines if the player controller can target another player controller, respecting admin permissions and immunity.
/// </summary>
/// <param name="controller">The player controller who wants to target.</param>
/// <param name="target">The player controller being targeted.</param>
/// <returns>True if targeting is allowed, false otherwise.</returns>
public static bool CanTarget(this CCSPlayerController? controller, CCSPlayerController? target)
{
if (controller is null || target is null) return true;
if (target.IsBot) return true;
return AdminManager.CanPlayerTarget(controller, target) ||
AdminManager.CanPlayerTarget(new SteamID(controller.SteamID),
new SteamID(target.SteamID)) ||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(target);
}
/// <summary>
/// Checks if the controller can target a player by SteamID, considering targeting permissions and immunities.
/// </summary>
/// <param name="controller">The attacker player controller.</param>
/// <param name="steamId">The SteamID of the target player.</param>
/// <returns>True if targeting is permitted, false otherwise.</returns>
public static bool CanTarget(this CCSPlayerController? controller, SteamID steamId)
{
if (controller is null) return true;
return AdminManager.CanPlayerTarget(new SteamID(controller.SteamID), steamId) ||
AdminManager.GetPlayerImmunity(controller) >= AdminManager.GetPlayerImmunity(steamId);
}
/// <summary>
/// Sets the movement speed modifier of the player controller.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="speed">The speed modifier value.</param>
public static void SetSpeed(this CCSPlayerController? controller, float speed)
{
var playerPawnValue = controller?.PlayerPawn.Value;
if (playerPawnValue == null) return;
playerPawnValue.VelocityModifier = speed;
}
/// <summary>
/// Sets the gravity scale for the player controller.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="gravity">The gravity scale.</param>
public static void SetGravity(this CCSPlayerController? controller, float gravity)
{
var playerPawnValue = controller?.PlayerPawn.Value;
if (playerPawnValue == null) return;
playerPawnValue.ActualGravityScale = gravity;
}
/// <summary>
/// Sets the player's in-game money amount.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="money">The amount of money to set.</param>
public static void SetMoney(this CCSPlayerController? controller, int money)
{
var moneyServices = controller?.InGameMoneyServices;
if (moneyServices == null) return;
moneyServices.Account = money;
if (controller != null) Utilities.SetStateChanged(controller, "CCSPlayerController", "m_pInGameMoneyServices");
}
/// <summary>
/// Sets the player's health points.
/// </summary>
/// <param name="controller">The player controller.</param>
/// <param name="health">The health value, default is 100.</param>
public static void SetHp(this CCSPlayerController? controller, int health = 100)
{
if (controller == null) return;
if (health <= 0 || controller.PlayerPawn.Value == null || controller.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE) return;
controller.PlayerPawn.Value.Health = health;
if (health > 100)
{
controller.PlayerPawn.Value.MaxHealth = health;
}
Utilities.SetStateChanged(controller.PlayerPawn.Value, "CBaseEntity", "m_iHealth");
}
/// <summary>
/// Buries the player pawn by moving it down by a depth offset.
/// </summary>
/// <param name="pawn">The player pawn to bury.</param>
/// <param name="depth">The depth offset (default 10 units).</param>
public static void Bury(this CBasePlayerPawn pawn, float depth = 10f)
{
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
pawn.AbsOrigin!.Z - depth);
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
pawn.Teleport(newPos, newRotation, newVelocity);
}
/// <summary>
/// Unburies the player pawn by moving it up by a depth offset.
/// </summary>
/// <param name="pawn">The player pawn to unbury.</param>
/// <param name="depth">The depth offset (default 15 units).</param>
public static void Unbury(this CBasePlayerPawn pawn, float depth = 15f)
{
var newPos = new Vector3(pawn.AbsOrigin!.X, pawn.AbsOrigin.Y,
pawn.AbsOrigin!.Z + depth);
var newRotation = new Vector3(pawn.AbsRotation!.X, pawn.AbsRotation.Y, pawn.AbsRotation.Z);
var newVelocity = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
pawn.Teleport(newPos, newRotation, newVelocity);
}
/// <summary>
/// Freezes the player pawn, disabling movement.
/// </summary>
/// <param name="pawn">The player pawn to freeze.</param>
public static void Freeze(this CBasePlayerPawn pawn)
{
pawn.MoveType = MoveType_t.MOVETYPE_INVALID;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 11); // invalid
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
/// <summary>
/// Unfreezes the player pawn, enabling movement.
/// </summary>
/// <param name="pawn">The player pawn to unfreeze.</param>
public static void Unfreeze(this CBasePlayerPawn pawn)
{
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
/// <summary>
/// Changes the player's color tint to specified RGBA values.
/// </summary>
/// <param name="pawn">The pawn to colorize.</param>
/// <param name="r">Red component (0-255).</param>
/// <param name="g">Green component (0-255).</param>
/// <param name="b">Blue component (0-255).</param>
/// <param name="a">Alpha (transparency) component (0-255).</param>
public static void Colorize(this CBasePlayerPawn pawn, int r = 255, int g = 255, int b = 255, int a = 255)
{
pawn.Render = Color.FromArgb(a, r, g, b);
Utilities.SetStateChanged(pawn, "CBaseModelEntity", "m_clrRender");
}
/// <summary>
/// Toggles noclip mode for the player pawn.
/// </summary>
/// <param name="pawn">The player pawn.</param>
public static void ToggleNoclip(this CBasePlayerPawn pawn)
{
if (pawn.MoveType == MoveType_t.MOVETYPE_NOCLIP)
{
pawn.MoveType = MoveType_t.MOVETYPE_WALK;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 2); // walk
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
else
{
pawn.MoveType = MoveType_t.MOVETYPE_NOCLIP;
Schema.SetSchemaValue(pawn.Handle, "CBaseEntity", "m_nActualMoveType", 8); // noclip
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_MoveType");
}
}
/// <summary>
/// Renames the player controller to a new name, with fallback to a localized "Unknown".
/// </summary>
/// <param name="controller">The player controller to rename.</param>
/// <param name="newName">The new name to assign.</param>
public static void Rename(this CCSPlayerController? controller, string newName = "Unknown")
{
newName ??= CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
if (controller != null)
{
var playerName = new SchemaString<CBasePlayerController>(controller, "m_iszPlayerName");
playerName.Set(newName + " ");
CS2_SimpleAdmin.Instance.AddTimer(0.25f, () =>
{
Utilities.SetStateChanged(controller, "CCSPlayerController", "m_szClan");
Utilities.SetStateChanged(controller, "CBasePlayerController", "m_iszPlayerName");
});
CS2_SimpleAdmin.Instance.AddTimer(0.3f, () =>
{
playerName.Set(newName);
});
}
CS2_SimpleAdmin.Instance.AddTimer(0.4f, () =>
{
if (controller != null) Utilities.SetStateChanged(controller, "CBasePlayerController", "m_iszPlayerName");
});
}
/// <summary>
/// Teleports a player controller to the position, rotation, and velocity of another player controller.
/// </summary>
/// <param name="controller">The controller to teleport.</param>
/// <param name="target">The target controller whose position to copy.</param>
public static void TeleportPlayer(this CCSPlayerController? controller, CCSPlayerController? target)
{
if (controller?.PlayerPawn.Value == null && target?.PlayerPawn.Value == null)
return;
if (
controller?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null } &&
target?.PlayerPawn.Value is { AbsOrigin: not null, AbsRotation: not null }
)
{
controller.PlayerPawn.Value.Teleport(
target.PlayerPawn.Value.AbsOrigin,
target.PlayerPawn.Value.AbsRotation,
target.PlayerPawn.Value.AbsVelocity
);
}
}
/// <summary>
/// Applies a slap effect to the given player pawn, optionally inflicting damage and adding velocity knockback.
/// </summary>
/// <param name="pawn">The player pawn to slap.</param>
/// <param name="damage">The amount of damage to deal (default is 0).</param>
private static void PerformSlap(CBasePlayerPawn pawn, int damage = 0)
{
if (pawn.LifeState != (int)LifeState_t.LIFE_ALIVE)
return;
var controller = pawn.Controller.Value?.As<CCSPlayerController>();
/* Teleport in a random direction - thank you, Mani!*/
/* Thank you AM & al!*/
var random = new Random();
var vel = new Vector3(pawn.AbsVelocity.X, pawn.AbsVelocity.Y, pawn.AbsVelocity.Z);
vel.X += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
vel.Y += (random.Next(180) + 50) * (random.Next(2) == 1 ? -1 : 1);
vel.Z += random.Next(200) + 100;
pawn.AbsVelocity.X = vel.X;
pawn.AbsVelocity.Y = vel.Y;
pawn.AbsVelocity.Z = vel.Z;
if (controller != null && controller.IsValid)
{
var shakeMessage = UserMessage.FromPartialName("Shake");
shakeMessage.SetFloat("duration", 1);
shakeMessage.SetFloat("amplitude", 10);
shakeMessage.SetFloat("frequency", 1f);
shakeMessage.SetInt("command", 0);
shakeMessage.Recipients.Add(controller);
shakeMessage.Send();
}
if (damage <= 0)
return;
pawn.Health -= damage;
Utilities.SetStateChanged(pawn, "CBaseEntity", "m_iHealth");
if (pawn.Health <= 0)
pawn.CommitSuicide(true, true);
}
/// <summary>
/// Sends a localized chat message to the player controller.
/// The message is retrieved from the specified localizer using the given message key and optional formatting arguments.
/// Each line of the message is prefixed with a localized prefix string.
/// The message respects the player's configured language for proper localization.
/// </summary>
/// <param name="controller">The target player controller to receive the message.</param>
/// <param name="localizer">The string localizer used for localization.</param>
/// <param name="messageKey">The key identifying the localized message.</param>
/// <param name="messageArgs">Optional arguments to format the localized message.</param>
public static void SendLocalizedMessage(this CCSPlayerController? controller, IStringLocalizer? localizer,
string messageKey, params object[] messageArgs)
{
if (controller == null || localizer == null) return;
using (new WithTemporaryCulture(controller.GetLanguage()))
{
StringBuilder sb = new();
sb.Append(localizer[messageKey, messageArgs]);
foreach (var part in Helper.SeparateLines(sb.ToString()))
{
var lineWithPrefix = localizer["sa_prefix"] + part.Trim();
controller.PrintToChat(lineWithPrefix);
}
}
}
/// <summary>
/// Sends a localized chat message to the player controller, centered horizontally on the player's screen.
/// The message is retrieved from the specified localizer using the given message key and optional formatting arguments.
/// Each line of the message is centered and prefixed with a localized prefix string.
/// The message respects the player's configured language for localization.
/// </summary>
/// <param name="controller">The target player controller to receive the message.</param>
/// <param name="localizer">The string localizer used for localization.</param>
/// <param name="messageKey">The key identifying the localized message.</param>
/// <param name="messageArgs">Optional arguments to format the localized message.</param>
public static void SendLocalizedMessageCenter(this CCSPlayerController? controller, IStringLocalizer? localizer,
string messageKey, params object[] messageArgs)
{
if (controller == null || localizer == null) return;
using (new WithTemporaryCulture(controller.GetLanguage()))
{
StringBuilder sb = new();
sb.Append(localizer[messageKey, messageArgs]);
foreach (var part in Helper.SeparateLines(sb.ToString()))
{
string _part;
_part = Helper.CenterMessage(part);
var lineWithPrefix = localizer["sa_prefix"] + _part;
controller.PrintToChat(lineWithPrefix);
}
}
}
}

1068
CS2-SimpleAdmin/Helper.cs Normal file

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,442 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
using MySqlConnector;
using System.Text;
using CS2_SimpleAdmin.Database;
namespace CS2_SimpleAdmin.Managers;
internal class BanManager(IDatabaseProvider? databaseProvider)
{
/// <summary>
/// Bans an online player and inserts the ban record into the database.
/// </summary>
/// <param name="player">The player to be banned (must be currently online).</param>
/// <param name="issuer">The admin issuing the ban. Can be null if issued from console.</param>
/// <param name="reason">The reason for the ban.</param>
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
/// <returns>The newly created ban ID if successful, otherwise null.</returns>
public async Task<int?> BanPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
await using var connection = await databaseProvider.CreateConnectionAsync();
try
{
var sql = databaseProvider.GetAddBanQuery();
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = player.SteamId.SteamId64,
playerName = player.Name,
playerIp = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 1 ? player.IpAddress : null,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return banId;
}
catch(Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
return null;
}
}
/// <summary>
/// Adds a ban for an offline player identified by their SteamID.
/// </summary>
/// <param name="playerSteamId">The SteamID64 of the player to ban.</param>
/// <param name="issuer">The admin issuing the ban. Can be null if issued from console.</param>
/// <param name="reason">The reason for the ban.</param>
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
/// <returns>The ID of the newly created ban if successful, otherwise null.</returns>
public async Task<int?> AddBanBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddBanBySteamIdQuery();
var banId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return banId;
}
catch(Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex, ex.Message);
return null;
}
}
/// <summary>
/// Adds a ban for an offline player identified by their IP address.
/// </summary>
/// <param name="playerIp">The IP address of the player to ban.</param>
/// <param name="issuer">The admin issuing the ban. Can be null if issued from console.</param>
/// <param name="reason">The reason for the ban.</param>
/// <param name="time">Ban duration in minutes. If 0, the ban is permanent.</param>
public async Task AddBanByIp(string playerIp, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(playerIp)) return;
DateTime now = Time.ActualDateTime();
DateTime futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddBanByIpQuery();
await connection.ExecuteAsync(sql, new
{
playerIp,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
banReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
}
catch { }
}
// public async Task<bool> IsPlayerBanned(PlayerInfo player)
// {
// if (database == null) return false;
//
// if (player.IpAddress == null)
// {
// return false;
// }
//
// #if DEBUG
// if (CS2_SimpleAdmin._logger != null)
// CS2_SimpleAdmin._logger.LogCritical($"IsPlayerBanned for {player.Name}");
// #endif
//
// int banCount;
//
// DateTime currentTime = Time.ActualDateTime();
//
// try
// {
// string sql;
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp && !CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress))
// {
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
// SELECT COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// ), 0)
// +
// COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
// WHERE sa_bans.status = 'ACTIVE'
// AND sa_players_ips.address = @PlayerIP
// AND NOT EXISTS (
// SELECT 1
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// )
// ), 0) AS TotalBanCount;
// """ : """
// SELECT COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// AND server_id = @ServerId
// ), 0)
// +
// COALESCE((
// SELECT COUNT(*)
// FROM sa_bans
// JOIN sa_players_ips ON sa_bans.player_steamid = sa_players_ips.steamid
// WHERE sa_bans.status = 'ACTIVE'
// AND sa_players_ips.address = @PlayerIP
// AND NOT EXISTS (
// SELECT 1
// FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime)
// AND server_id = @ServerId
// )
// ), 0) AS TotalBanCount;
// """;
// }
// else
// {
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode ? """
// UPDATE sa_bans
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime);
//
// SELECT COUNT(*) FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime);
// """ : """
// UPDATE sa_bans
// SET player_ip = CASE WHEN player_ip IS NULL THEN @PlayerIP ELSE player_ip END,
// player_name = CASE WHEN player_name IS NULL THEN @PlayerName ELSE player_name END
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
//
// SELECT COUNT(*) FROM sa_bans
// WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
// AND status = 'ACTIVE'
// AND (duration = 0 OR ends > @CurrentTime) AND server_id = @ServerId;
// """;
// }
//
// await using var connection = await database.GetConnectionAsync();
//
// var parameters = new
// {
// PlayerSteamID = player.SteamId.SteamId64,
// PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0 ||
// string.IsNullOrEmpty(player.IpAddress) ||
// CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(player.IpAddress)
// ? null
// : player.IpAddress,
// PlayerName = !string.IsNullOrEmpty(player.Name) ? player.Name : string.Empty,
// CurrentTime = currentTime,
// CS2_SimpleAdmin.ServerId
// };
//
// banCount = await connection.ExecuteScalarAsync<int>(sql, parameters);
// }
// catch (Exception ex)
// {
// CS2_SimpleAdmin._logger?.LogError("Unable to check ban status for {PlayerName} ({ExceptionMessage})",
// player.Name, ex.Message);
// return false;
// }
//
// return banCount > 0;
// }
//
// public async Task<int> GetPlayerBans(PlayerInfo player)
// {
// if (database == null) return 0;
//
// try
// {
// string sql;
//
// sql = CS2_SimpleAdmin.Instance.Config.MultiServerMode
// ? "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)"
// : "SELECT COUNT(*) FROM sa_bans WHERE (player_steamid = @PlayerSteamID OR player_ip = @PlayerIP) AND server_id = @serverid";
//
// int banCount;
//
// await using var connection = await database.GetConnectionAsync();
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0 && !string.IsNullOrEmpty(player.IpAddress))
// {
// banCount = await connection.ExecuteScalarAsync<int>(sql,
// new
// {
// PlayerSteamID = player.SteamId.SteamId64,
// PlayerIP = player.IpAddress,
// serverid = CS2_SimpleAdmin.ServerId
// });
// }
// else
// {
// banCount = await connection.ExecuteScalarAsync<int>(sql,
// new
// {
// PlayerSteamID = player.SteamId.SteamId64,
// PlayerIP = DBNull.Value,
// serverid = CS2_SimpleAdmin.ServerId
// });
// }
//
// return banCount;
// }
// catch { }
//
// return 0;
// }
/// <summary>
/// Unbans a player based on a pattern match of SteamID or IP address.
/// </summary>
/// <param name="playerPattern">Pattern to match against player identifiers (e.g., partial SteamID).</param>
/// <param name="adminSteamId">SteamID64 of the admin performing the unban.</param>
/// <param name="reason">Optional reason for the unban. If null or empty, the unban reason is not stored.</param>
public async Task UnbanPlayer(string playerPattern, string adminSteamId, string reason)
{
if (databaseProvider == null) return;
if (playerPattern is not { Length: > 1 })
{
return;
}
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sqlRetrieveBans = databaseProvider.GetUnbanRetrieveBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
var bans = await connection.QueryAsync(sqlRetrieveBans, new { pattern = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
var bansList = bans as dynamic[] ?? bans.ToArray();
if (bansList.Length == 0)
return;
var sqlAdminId = databaseProvider.GetUnbanAdminIdQuery();
var adminId = await connection.ExecuteScalarAsync<int?>(sqlAdminId, new { adminSteamId }) ?? 0;
foreach (var ban in bansList)
{
int banId = ban.id;
var sqlInsertUnban = databaseProvider.GetInsertUnbanQuery(reason != null);
var unbanId = await connection.ExecuteScalarAsync<int>(sqlInsertUnban, new { banId, adminId, reason });
var sqlUpdateBan = databaseProvider.GetUpdateBanStatusQuery();
await connection.ExecuteAsync(sqlUpdateBan, new { unbanId, banId });
}
}
catch { }
}
// public async Task CheckOnlinePlayers(List<(string? IpAddress, ulong SteamID, int? UserId, int Slot)> players)
// {
// if (database == null) return;
//
// try
// {
// await using var connection = await database.GetConnectionAsync();
// bool checkIpBans = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType > 0;
//
// var filteredPlayers = players.Where(p => p.UserId.HasValue).ToList();
//
// var steamIds = filteredPlayers.Select(p => p.SteamID).Distinct().ToList();
// var ipAddresses = filteredPlayers
// .Where(p => !string.IsNullOrEmpty(p.IpAddress))
// .Select(p => p.IpAddress)
// .Distinct()
// .ToList();
//
// var sql = new StringBuilder();
// sql.Append("SELECT `player_steamid`, `player_ip` FROM `sa_bans` WHERE `status` = 'ACTIVE' ");
//
// if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
// {
// sql.Append("AND (player_steamid IN @SteamIDs ");
// if (checkIpBans && ipAddresses.Count != 0)
// {
// sql.Append("OR player_ip IN @IpAddresses");
// }
// sql.Append(')');
// }
// else
// {
// sql.Append("AND server_id = @ServerId AND (player_steamid IN @SteamIDs ");
// if (checkIpBans && ipAddresses.Count != 0)
// {
// sql.Append("OR player_ip IN @IpAddresses");
// }
// sql.Append(')');
// }
//
// var bannedPlayers = await connection.QueryAsync<(ulong PlayerSteamID, string PlayerIP)>(
// sql.ToString(),
// new
// {
// SteamIDs = steamIds,
// IpAddresses = checkIpBans ? ipAddresses : [],
// CS2_SimpleAdmin.ServerId
// });
//
// var valueTuples = bannedPlayers.ToList();
// var bannedSteamIds = valueTuples.Select(b => b.PlayerSteamID).ToHashSet();
// var bannedIps = valueTuples.Select(b => b.PlayerIP).ToHashSet();
//
// foreach (var player in filteredPlayers.Where(player => bannedSteamIds.Contains(player.SteamID) ||
// (checkIpBans && bannedIps.Contains(player.IpAddress ?? ""))))
// {
// if (!player.UserId.HasValue || CS2_SimpleAdmin.PlayersInfo[player.SteamID].WaitingForKick) continue;
//
// await Server.NextWorldUpdateAsync(() =>
// {
// Helper.KickPlayer(player.UserId.Value, NetworkDisconnectionReason.NETWORK_DISCONNECT_KICKBANADDED);
// });
// }
// }
// catch (Exception ex)
// {
// CS2_SimpleAdmin._logger?.LogError($"Error checking online players: {ex.Message}");
// }
// }
/// <summary>
/// Expires all bans that have passed their end time, including optional cleanup of old IP bans.
/// </summary>
public async Task ExpireOldBans()
{
if (databaseProvider == null) return;
var currentTime = Time.ActualDateTime();
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetExpireBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { currentTime, serverid = CS2_SimpleAdmin.ServerId });
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans > 0)
{
var ipBansTime = currentTime.AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
sql = databaseProvider.GetExpireIpBansQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { ipBansTime, CS2_SimpleAdmin.ServerId });
}
}
catch (Exception)
{
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired bans");
}
}
}

View File

@@ -0,0 +1,686 @@
using System.Collections.Concurrent;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Models;
using Dapper;
using ZLinq;
namespace CS2_SimpleAdmin.Managers;
internal class CacheManager: IDisposable
{
private readonly ConcurrentDictionary<int, BanRecord> _banCache = [];
private readonly ConcurrentDictionary<ulong, List<BanRecord>> _steamIdIndex = [];
private readonly ConcurrentDictionary<uint, List<BanRecord>> _ipIndex = [];
private readonly ConcurrentDictionary<ulong, HashSet<IpRecord>> _playerIpsCache = [];
private HashSet<uint> _cachedIgnoredIps = [];
private DateTime _lastUpdateTime = DateTime.MinValue;
private bool _isInitialized;
private bool _disposed;
/// <summary>
/// Initializes and builds the ban and IP cache from the database. Loads bans, player IP history, and config settings.
/// </summary>
/// <returns>Asynchronous task representing the initialization process.</returns>
public async Task InitializeCacheAsync()
{
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
if (!CS2_SimpleAdmin.ServerLoaded) return;
if (_isInitialized) return;
try
{
Clear();
_cachedIgnoredIps = CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps
.AsValueEnumerable()
.Select(IpHelper.IpToUint)
.ToHashSet();
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
List<BanRecord> bans;
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
bans = (await connection.QueryAsync<BanRecord>(
"""
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
""")).ToList();
}
else
{
bans = (await connection.QueryAsync<BanRecord>(
"""
SELECT
id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM sa_bans
WHERE server_id = @serverId
""", new {serverId = CS2_SimpleAdmin.ServerId})).ToList();
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
{
var ipHistory =
(await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips ORDER BY used_at DESC")).ToList();
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
var ipSet = group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
string.IsNullOrEmpty(latest.name)
? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
: latest.name
);
})
.ToHashSet(new IpRecordComparer());
_playerIpsCache.AddOrUpdate(
group.Key,
_ => ipSet,
(_, existingSet) =>
{
foreach (var ip in ipSet)
{
existingSet.Remove(ip);
existingSet.Add(ip);
}
return existingSet;
});
}
}
foreach (var ban in bans.AsValueEnumerable())
_banCache.TryAdd(ban.Id, ban);
RebuildIndexes();
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
_isInitialized = true;
}
catch (Exception e)
{
Console.WriteLine(e.ToString());
}
}
/// <summary>
/// Clears all cached data and reinitializes the cache from the database.
/// </summary>
/// <returns>Asynchronous task representing the reinitialization process.</returns>
public async Task ForceReInitializeCacheAsync()
{
_isInitialized = false;
_banCache.Clear();
_playerIpsCache.Clear();
_cachedIgnoredIps = [];
_lastUpdateTime = DateTime.MinValue;
await InitializeCacheAsync();
}
/// <summary>
/// Refreshes the in-memory cache with updated or new data from the database since the last update time.
/// Also updates multi-account IP history if enabled.
/// </summary>
/// <returns>Asynchronous task representing the refresh operation.</returns>
public async Task RefreshCacheAsync()
{
if (CS2_SimpleAdmin.DatabaseProvider == null) return;
if (!_isInitialized) return;
try
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
IEnumerable<BanRecord> updatedBans;
var allIds = (await connection.QueryAsync<int>("SELECT id FROM sa_bans")).ToHashSet();
if (CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
updatedBans = (await connection.QueryAsync<BanRecord>(
"""
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE updated_at > @lastUpdate OR created > @lastUpdate ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime }
));
}
else
{
updatedBans = (await connection.QueryAsync<BanRecord>(
"""
SELECT id AS Id,
player_name AS PlayerName,
player_steamid AS PlayerSteamId,
player_ip AS PlayerIp,
status AS Status
FROM `sa_bans` WHERE (updated_at > @lastUpdate OR created > @lastUpdate) AND server_id = @serverId ORDER BY updated_at DESC
""",
new { lastUpdate = _lastUpdateTime, serverId = CS2_SimpleAdmin.ServerId }
));
}
foreach (var id in _banCache.Keys)
{
if (allIds.Contains(id) || !_banCache.TryRemove(id, out var ban)) continue;
if (ban.PlayerSteamId != null &&
_steamIdIndex.TryGetValue(ban.PlayerSteamId.Value, out var steamBans))
{
steamBans.RemoveAll(b => b.Id == id);
if (steamBans.Count == 0)
_steamIdIndex.TryRemove(ban.PlayerSteamId.Value, out _);
}
if (string.IsNullOrWhiteSpace(ban.PlayerIp) ||
!IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipBans)) continue;
{
ipBans.RemoveAll(b => b.Id == id);
if (ipBans.Count == 0)
_ipIndex.TryRemove(ipUInt, out _);
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp)
{
var ipHistory = (await connection.QueryAsync<(ulong steamid, string? name, uint address, DateTime used_at)>(
"SELECT steamid, name, address, used_at FROM sa_players_ips WHERE used_at >= @lastUpdate ORDER BY used_at DESC LIMIT 300",
new { lastUpdate = _lastUpdateTime }));
foreach (var group in ipHistory.AsValueEnumerable().GroupBy(x => x.steamid))
{
var ipSet = new HashSet<IpRecord>(
group
.GroupBy(x => x.address)
.Select(g =>
{
var latest = g.MaxBy(x => x.used_at);
return new IpRecord(
g.Key,
latest.used_at,
!string.IsNullOrEmpty(latest.name)
? latest.name
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown"
);
}),
new IpRecordComparer()
);
_playerIpsCache.AddOrUpdate(
group.Key,
_ => ipSet,
(_, existingSet) =>
{
foreach (var newEntry in ipSet)
{
existingSet.Remove(newEntry);
existingSet.Add(newEntry);
}
return existingSet;
});
}
}
foreach (var ban in updatedBans)
{
_banCache.AddOrUpdate(ban.Id, ban, (_, _) => ban);
}
RebuildIndexes();
_lastUpdateTime = Time.ActualDateTime().AddSeconds(-1);
}
catch (Exception)
{
}
}
/// <summary>
/// Rebuilds the internal indexes for fast lookup of active bans by Steam ID and IP address.
/// Clears and repopulates both indexes based on the current in-memory ban cache.
/// </summary>
private void RebuildIndexes()
{
_steamIdIndex.Clear();
_ipIndex.Clear();
foreach (var ban in _banCache.Values)
{
if (ban.StatusEnum != BanStatus.ACTIVE)
continue;
if (ban.PlayerSteamId != null)
{
var steamId = ban.PlayerSteamId;
_steamIdIndex.AddOrUpdate(
steamId.Value,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) continue;
if (ban.PlayerIp != null &&
IpHelper.TryConvertIpToUint(ban.PlayerIp, out var ipUInt))
{
_ipIndex.AddOrUpdate(
ipUInt,
key => [ban],
(key, list) =>
{
list.Add(ban);
return list;
});
}
}
}
/// <summary>
/// Retrieves all ban records currently stored in the cache.
/// </summary>
/// <returns>List of all <see cref="BanRecord"/> objects.</returns>
public List<BanRecord> GetAllBans() => _banCache.Values.ToList();
/// <summary>
/// Retrieves only active ban records from the cache.
/// </summary>
/// <returns>List of active <see cref="BanRecord"/> objects.</returns>
public List<BanRecord> GetActiveBans() => _banCache.Values.Where(b => b.StatusEnum == BanStatus.ACTIVE).ToList();
/// <summary>
/// Retrieves all ban records for a specific player by their Steam ID.
/// </summary>
/// <param name="steamId">64-bit Steam ID of the player.</param>
/// <returns>List of <see cref="BanRecord"/> objects associated with the Steam ID.</returns>
public List<BanRecord> GetPlayerBansBySteamId(ulong steamId) => _steamIdIndex.TryGetValue(steamId, out var bans) ? bans : [];
/// <summary>
/// Gets all known Steam accounts that have used the specified IP address.
/// </summary>
/// <param name="ipAddress">The IP address to search for, in string format.</param>
/// <returns>
/// List of tuples containing the Steam ID, last used time, and player name for each matching entry.
/// </returns>
public List<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
{
var ipAsUint = IpHelper.IpToUint(ipAddress);
var results = new List<(ulong, DateTime, string)>();
var comparer = _playerIpsCache.Comparer;
foreach (var (steamId, ipSet) in _playerIpsCache)
{
if (!ipSet.TryGetValue(new IpRecord(ipAsUint, Time.ActualDateTime(), "Unknown"), out var actualEntry)) continue;
results.Add((steamId, actualEntry.UsedAt, actualEntry.PlayerName));
foreach (var entry in ipSet)
{
if (entry.Ip == ipAsUint && !Equals(entry, actualEntry))
{
results.Add((steamId, entry.UsedAt, entry.PlayerName));
}
}
}
return results;
}
// public IEnumerable<(ulong SteamId, DateTime UsedAt, string PlayerName)> GetAccountsByIp(string ipAddress)
// {
// var ipAsUint = IpHelper.IpToUint(ipAddress);
//
// return _playerIpsCache.SelectMany(kvp => kvp.Value
// .Where(entry => entry.Ip == ipAsUint)
// .Select(entry => (kvp.Key, entry.UsedAt, entry.PlayerName)));
// }
private bool IsIpBanned(string ipAddress)
{
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0) return false;
var ipUInt = IpHelper.IpToUint(ipAddress);
return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
}
// public bool IsPlayerBanned(ulong? steamId, string? ipAddress)
// {
// if (steamId != null && _steamIdIndex.ContainsKey(steamId.Value))
// return true;
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
// return false;
//
// if (string.IsNullOrEmpty(ipAddress) || !IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt))
// return false;
//
// return !_cachedIgnoredIps.Contains(ipUInt) && _ipIndex.ContainsKey(ipUInt);
// }
/// <summary>
/// Checks if a player is currently banned by Steam ID or IP address.
/// If a partial ban record is found, updates it with the latest player information.
/// </summary>
/// <param name="playerName">Name of the player attempting to connect.</param>
/// <param name="steamId">Optional 64-bit Steam ID of the player.</param>
/// <param name="ipAddress">Optional IP address of the player.</param>
/// <returns>True if the player is banned, otherwise false.</returns>
public bool IsPlayerBanned(string playerName, ulong? steamId, string? ipAddress)
{
BanRecord? record;
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
{
record = steamRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (record != null)
{
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
(!record.PlayerSteamId.HasValue))
{
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
}
return true;
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
return false;
if (string.IsNullOrEmpty(ipAddress) ||
!IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt) ||
_cachedIgnoredIps.Contains(ipUInt) ||
!_ipIndex.TryGetValue(ipUInt, out var ipRecords)) return false;
record = ipRecords.FirstOrDefault(r => r.StatusEnum == BanStatus.ACTIVE);
if (record == null) return false;
if ((string.IsNullOrEmpty(record.PlayerIp) && !string.IsNullOrEmpty(ipAddress)) ||
(!record.PlayerSteamId.HasValue && steamId.HasValue))
{
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
}
return true;
}
// public bool IsPlayerOrAnyIpBanned(ulong steamId, string? ipAddress)
// {
// if (_steamIdIndex.ContainsKey(steamId))
// return true;
//
// if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
// return false;
//
// if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
// return false;
//
// // var now = Time.ActualDateTime();
// var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
// var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
//
// if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
// {
// if (!_cachedIgnoredIps.Contains(ipAsUint))
// {
// ipData.Add(new IpRecord(
// ipAsUint,
// Time.ActualDateTime().AddSeconds(-2),
// unknownName
// ));
// }
// }
//
// // foreach (var ipRecord in ipData)
// // {
// // // Skip if too old or in ignored list
// // if (ipRecord.UsedAt < cutoff || _cachedIgnoredIps.Contains(ipRecord.Ip))
// // continue;
// //
// // // Check if IP is banned
// // if (_ipIndex.ContainsKey(ipRecord.Ip))
// // 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.PlayerSteamId.HasValue) return true;
//
// _ = Task.Run(() => UpdatePlayerData(
// activeBan.PlayerName,
// steamId,
// ipAddress
// ));
//
// if (string.IsNullOrEmpty(activeBan.PlayerName) && !string.IsNullOrEmpty(unknownName))
// activeBan.PlayerName = unknownName;
//
// activeBan.PlayerSteamId ??= steamId;
//
// return true;
// }
//
// return false;
// }
/// <summary>
/// Checks if the player or any IP previously associated with them is currently banned.
/// Also updates ban records with missing player info if found.
/// </summary>
/// <param name="playerName">Current player name.</param>
/// <param name="steamId">64-bit Steam ID of the player.</param>
/// <param name="ipAddress">Current IP address of the player (optional).</param>
/// <returns>True if the player or their known IPs are banned, otherwise false.</returns>
public bool IsPlayerOrAnyIpBanned(string playerName, ulong steamId, string? ipAddress)
{
if (_steamIdIndex.TryGetValue(steamId, out var steamBans))
{
var activeBan = steamBans.FirstOrDefault(b => b.StatusEnum == BanStatus.ACTIVE);
if (activeBan != null)
{
if (string.IsNullOrEmpty(activeBan.PlayerName) || string.IsNullOrEmpty(activeBan.PlayerIp))
_ = Task.Run(() => UpdatePlayerData(playerName, steamId, ipAddress));
return true;
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0)
return false;
if (!_playerIpsCache.TryGetValue(steamId, out var ipData))
return false;
var cutoff = Time.ActualDateTime().AddDays(-CS2_SimpleAdmin.Instance.Config.OtherSettings.ExpireOldIpBans);
var unknownName = CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
if (ipAddress != null && IpHelper.TryConvertIpToUint(ipAddress, out var ipAsUint))
{
if (!_cachedIgnoredIps.Contains(ipAsUint))
{
ipData.Add(new IpRecord(ipAsUint, Time.ActualDateTime().AddSeconds(-2), unknownName));
}
}
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;
}
/// <summary>
/// Checks if the given IP address is known (previously recorded) for the specified Steam ID.
/// </summary>
/// <param name="steamId">64-bit Steam ID of the player.</param>
/// <param name="ipAddress">IP address to check.</param>
/// <returns>True if the IP is recorded for the player, otherwise false.</returns>
public bool HasIpForPlayer(ulong steamId, string ipAddress)
{
if (string.IsNullOrWhiteSpace(ipAddress))
return false;
if (!IpHelper.TryConvertIpToUint(ipAddress, out var ipUint))
return false;
return _playerIpsCache.TryGetValue(steamId, out var ipData) &&
ipData.Contains(new IpRecord(ipUint, default, null!));
}
// public bool HasIpForPlayer(ulong steamId, string ipAddress)
// {
// if (string.IsNullOrWhiteSpace(ipAddress))
// return false;
//
// return _playerIpsCache.TryGetValue(steamId, out var ipData)
// && ipData.Any(x => x.Ip == IpHelper.IpToUint(ipAddress));
// }
/// <summary>
/// Updates existing active ban records in the database with the latest known player name and IP address.
/// Also updates in-memory cache to reflect these changes.
/// </summary>
/// <param name="playerName">Current player name.</param>
/// <param name="steamId">Optional Steam ID of the player.</param>
/// <param name="ipAddress">Optional IP address of the player.</param>
/// <returns>Asynchronous task representing the update operation.</returns>
private async Task UpdatePlayerData(string? playerName, ulong? steamId, string? ipAddress)
{
if (CS2_SimpleAdmin.DatabaseProvider == null)
return;
var baseSql = """
UPDATE sa_bans
SET
player_ip = COALESCE(player_ip, @PlayerIP),
player_name = COALESCE(player_name, @PlayerName)
WHERE
(player_steamid = @PlayerSteamID OR player_ip = @PlayerIP)
AND status = 'ACTIVE'
AND (duration = 0 OR ends > @CurrentTime)
""";
if (!CS2_SimpleAdmin.Instance.Config.MultiServerMode)
{
baseSql += " AND server_id = @ServerId;";
}
var parameters = new
{
PlayerSteamID = steamId,
PlayerIP = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
|| string.IsNullOrEmpty(ipAddress)
|| CS2_SimpleAdmin.Instance.Config.OtherSettings.IgnoredIps.Contains(ipAddress)
? null
: ipAddress,
PlayerName = string.IsNullOrEmpty(playerName) ? string.Empty : playerName,
CurrentTime = Time.ActualDateTime(),
CS2_SimpleAdmin.ServerId
};
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
await connection.ExecuteAsync(baseSql, parameters);
if (steamId.HasValue && _steamIdIndex.TryGetValue(steamId.Value, out var steamRecords))
{
foreach (var rec in steamRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
{
if (string.IsNullOrEmpty(rec.PlayerIp) && !string.IsNullOrEmpty(ipAddress))
rec.PlayerIp = ipAddress;
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
rec.PlayerName = playerName;
}
}
if (!string.IsNullOrEmpty(ipAddress) && IpHelper.TryConvertIpToUint(ipAddress, out var ipUInt)
&& _ipIndex.TryGetValue(ipUInt, out var ipRecords))
{
foreach (var rec in ipRecords.Where(r => r.StatusEnum == BanStatus.ACTIVE))
{
if (!rec.PlayerSteamId.HasValue && steamId.HasValue)
rec.PlayerSteamId = steamId;
if (string.IsNullOrEmpty(rec.PlayerName) && !string.IsNullOrEmpty(playerName))
rec.PlayerName = playerName;
}
}
}
private void Clear()
{
_steamIdIndex.Clear();
_ipIndex.Clear();
_banCache.Clear();
_playerIpsCache.Clear();
_cachedIgnoredIps.Clear();
}
/// <summary>
/// Clears and disposes of all cached data and marks the object as disposed.
/// </summary>
public void Dispose()
{
if (_disposed) return;
Clear();
_disposed = true;
}
}
public class IpRecordComparer : IEqualityComparer<IpRecord>
{
public bool Equals(IpRecord x, IpRecord y)
=> x.Ip == y.Ip;
public int GetHashCode(IpRecord obj)
=> obj.Ip.GetHashCode();
}

View File

@@ -0,0 +1,167 @@
using System.Diagnostics.CodeAnalysis;
using System.Text;
using System.Text.Json;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Managers;
public class DiscordManager(string webhookUrl)
{
/// <summary>
/// Sends a plain text message asynchronously to the configured Discord webhook URL.
/// </summary>
/// <param name="message">The text message to send to Discord.</param>
/// <returns>A task representing the asynchronous operation.</returns>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task SendMessageAsync(string message)
{
var client = CS2_SimpleAdmin.HttpClient;
var payload = new
{
content = message
};
var options = new JsonSerializerOptions
{
WriteIndented = false
};
var json = JsonSerializer.Serialize(payload, options);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
var response = await client.PostAsync(webhookUrl, content);
if (!response.IsSuccessStatusCode)
{
CS2_SimpleAdmin._logger?.LogError(
$"Failed to send discord message. Status Code: {response.StatusCode}, Reason: {response.ReasonPhrase}");
}
}
catch (HttpRequestException e)
{
CS2_SimpleAdmin._logger?.LogError($"Error sending discord message: {e.Message}");
}
}
/// <summary>
/// Sends an embed message asynchronously to the configured Discord webhook URL.
/// </summary>
/// <param name="embed">The embed object containing rich content to send.</param>
/// <returns>A task representing the asynchronous operation.</returns>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task SendEmbedAsync(Embed embed)
{
var httpClient = CS2_SimpleAdmin.HttpClient;
var payload = new
{
embeds = new[]
{
new
{
color = embed.Color,
title = !string.IsNullOrEmpty(embed.Title) ? embed.Title : null,
description = !string.IsNullOrEmpty(embed.Description) ? embed.Description : null,
thumbnail = !string.IsNullOrEmpty(embed.ThumbnailUrl) ? new { url = embed.ThumbnailUrl } : null,
image = !string.IsNullOrEmpty(embed.ImageUrl) ? new { url = embed.ImageUrl } : null,
footer = !string.IsNullOrEmpty(embed.Footer?.Text) ? new { text = embed.Footer.Text, icon_url = embed.Footer.IconUrl } : null,
timestamp = embed.Timestamp,
fields = embed.Fields.Count > 0 ? embed.Fields.Select(field => new
{
name = field.Name,
value = field.Value,
inline = field.Inline
}).ToArray() : null
}
}
};
var options = new JsonSerializerOptions
{
WriteIndented = false
};
var jsonPayload = JsonSerializer.Serialize(payload, options);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");
var response = await httpClient.PostAsync(webhookUrl, content);
if (!response.IsSuccessStatusCode)
{
var errorMessage = await response.Content.ReadAsStringAsync();
CS2_SimpleAdmin._logger?.LogError($"Failed to send embed: {response.StatusCode} - {errorMessage}");
}
}
/// <summary>
/// Converts a hexadecimal color string (e.g. "#FF0000") to its integer representation.
/// </summary>
/// <param name="hex">The hexadecimal color string, optionally starting with '#'.</param>
/// <returns>An integer representing the color.</returns>
public static int ColorFromHex(string hex)
{
if (hex.StartsWith($"#"))
{
hex = hex[1..];
}
return int.Parse(hex, System.Globalization.NumberStyles.HexNumber);
}
}
/// <summary>
/// Represents a Discord embed message containing rich content such as title, description, fields, and images.
/// </summary>
public class Embed
{
public int Color { get; init; }
public string? Title { get; init; }
public string? Description { get; init; }
public string? ImageUrl { get; init; }
public string? ThumbnailUrl { get; init; }
public Footer? Footer { get; init; }
public string? Timestamp { get; init; }
public List<EmbedField> Fields { get; } = [];
/// <summary>
/// Adds a field to the embed message.
/// </summary>
/// <param name="name">The name of the field.</param>
/// <param name="value">The value or content of the field.</param>
/// <param name="inline">Whether the field should be displayed inline with other fields.</param>
public void AddField(string name, string value, bool inline)
{
var field = new EmbedField
{
Name = name,
Value = value,
Inline = inline
};
Fields.Add(field);
}
}
/// <summary>
/// Represents the footer section of a Discord embed message, including optional text and icon URL.
/// </summary>
public class Footer
{
public string? Text { get; init; }
public string? IconUrl { get; set; }
}
/// <summary>
/// Represents a field inside a Discord embed message.
/// </summary>
public class EmbedField
{
public string? Name { get; init; }
public string? Value { get; init; }
public bool Inline { get; init; }
}

View File

@@ -0,0 +1,300 @@
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Managers;
internal class MuteManager(IDatabaseProvider? databaseProvider)
{
/// <summary>
/// Adds a mute entry for a specified player with detailed information.
/// </summary>
/// <param name="player">Player to be muted.</param>
/// <param name="issuer">Admin issuing the mute; null if issued from console.</param>
/// <param name="reason">Reason for muting the player.</param>
/// <param name="time">Duration of the mute in minutes. Zero means permanent mute.</param>
/// <param name="type">Mute type: 0 = GAG, 1 = MUTE, 2 = SILENCE.</param>
/// <returns>Mute ID if successfully added, otherwise null.</returns>
public async Task<int?> MutePlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
{
if (databaseProvider == null) return null;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
var muteType = type switch
{
1 => "MUTE",
2 => "SILENCE",
_ => "GAG"
};
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddMuteQuery(true);
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = player.SteamId.SteamId64,
playerName = player.Name,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
ends = futureTime,
created = now,
type = muteType,
serverid = CS2_SimpleAdmin.ServerId
});
return muteId;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.Message);
return null;
}
}
/// <summary>
/// Adds a mute entry for a offline player identified by their SteamID.
/// </summary>
/// <param name="playerSteamId">SteamID64 of the player to mute.</param>
/// <param name="issuer">Admin issuing the mute; can be null if from console.</param>
/// <param name="reason">Reason for the mute.</param>
/// <param name="time">Mute duration in minutes; 0 for permanent.</param>
/// <param name="type">Mute type: 0 = GAG, 1 = MUTE, 2 = SILENCE.</param>
/// <returns>Mute ID if successful, otherwise null.</returns>
public async Task<int?> AddMuteBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0, int type = 0)
{
if (databaseProvider == null) return null;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
var muteType = type switch
{
1 => "MUTE",
2 => "SILENCE",
_ => "GAG"
};
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddMuteQuery(false);
var muteId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
ends = futureTime,
created = now,
type = muteType,
serverid = CS2_SimpleAdmin.ServerId
});
return muteId;
}
catch
{
return null;
}
}
/// <summary>
/// Checks if a player with the given SteamID currently has any active mutes.
/// </summary>
/// <param name="steamId">SteamID64 of the player to check.</param>
/// <returns>List of active mute records; empty list if none or on error.</returns>
public async Task<List<dynamic>> IsPlayerMuted(string steamId)
{
if (databaseProvider == null) return [];
if (string.IsNullOrEmpty(steamId))
{
return [];
}
#if DEBUG
if (CS2_SimpleAdmin._logger != null)
CS2_SimpleAdmin._logger.LogCritical($"IsPlayerMuted for {steamId}");
#endif
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var currentTime = Time.ActualDateTime();
var sql = databaseProvider.GetIsMutedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
var parameters = new { PlayerSteamID = steamId, CurrentTime = currentTime, serverid = CS2_SimpleAdmin.ServerId };
var activeMutes = (await connection.QueryAsync(sql, parameters)).ToList();
return activeMutes;
}
catch (Exception)
{
return [];
}
}
/// <summary>
/// Retrieves counts of total mutes, gags, and silences for a given player.
/// </summary>
/// <param name="playerInfo">Information about the player.</param>
/// <returns>
/// Tuple containing total mutes, total gags, and total silences respectively.
/// Returns zeros if no data or on error.
/// </returns>
public async Task<(int TotalMutes, int TotalGags, int TotalSilences)> GetPlayerMutes(PlayerInfo playerInfo)
{
if (databaseProvider == null) return (0,0,0);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetRetrieveMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
var result = await connection.QuerySingleAsync<(int TotalMutes, int TotalGags, int TotalSilences)>(sql, new
{
PlayerSteamID = playerInfo.SteamId.SteamId64,
CS2_SimpleAdmin.ServerId
});
return result;
}
catch (Exception)
{
return (0, 0, 0);
}
}
/// <summary>
/// Processes a batch of online players to update their mute status and remove expired penalties.
/// </summary>
/// <param name="players">List of tuples containing player SteamID, optional UserID, and slot index.</param>
/// <returns>Task representing the asynchronous operation.</returns>
public async Task CheckOnlineModeMutes(List<(ulong SteamID, int? UserId, int Slot)> players)
{
if (databaseProvider == null) return;
try
{
const int batchSize = 20;
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetUpdateMutePassedQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
for (var i = 0; i < players.Count; i += batchSize)
{
var batch = players.Skip(i).Take(batchSize);
var parametersList = new List<object>();
foreach (var (steamId, _, _) in batch)
{
parametersList.Add(new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
}
await connection.ExecuteAsync(sql, parametersList);
}
sql = databaseProvider.GetCheckExpiredMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
foreach (var (steamId, _, slot) in players)
{
var muteRecords = await connection.QueryAsync(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
foreach (var muteRecord in muteRecords)
{
DateTime endDateTime = muteRecord.ends;
PlayerPenaltyManager.RemovePenaltiesByDateTime(slot, endDateTime);
}
}
}
catch { }
}
/// <summary>
/// Removes active mutes for players matching the specified pattern.
/// </summary>
/// <param name="playerPattern">Pattern to match player names or identifiers.</param>
/// <param name="adminSteamId">SteamID64 of the admin performing the unmute.</param>
/// <param name="reason">Reason for unmuting the player(s).</param>
/// <param name="type">Mute type to remove: 0 = GAG, 1 = MUTE, 2 = SILENCE.</param>
/// <returns>Task representing the asynchronous operation.</returns>
public async Task UnmutePlayer(string playerPattern, string adminSteamId, string reason, int type = 0)
{
if (databaseProvider == null) return;
if (playerPattern.Length <= 1)
{
return;
}
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var muteType = type switch
{
1 => "MUTE",
2 => "SILENCE",
_ => "GAG"
};
var sqlRetrieveMutes =
databaseProvider.GetRetrieveMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
var mutes = await connection.QueryAsync(sqlRetrieveMutes, new { pattern = playerPattern, muteType, serverid = CS2_SimpleAdmin.ServerId });
var mutesList = mutes as dynamic[] ?? mutes.ToArray();
if (mutesList.Length == 0)
return;
var sqlAdmin = databaseProvider.GetUnmuteAdminIdQuery();
var sqlInsertUnmute = databaseProvider.GetInsertUnmuteQuery(string.IsNullOrEmpty(reason));
var sqlAdminId = await connection.ExecuteScalarAsync<int?>(sqlAdmin, new { adminSteamId });
var adminId = sqlAdminId ?? 0;
foreach (var mute in mutesList)
{
int muteId = mute.id;
int? unmuteId =
await connection.ExecuteScalarAsync<int>(sqlInsertUnmute, new { muteId, adminId, reason });
var sqlUpdateMute = databaseProvider.GetUpdateMuteStatusQuery();
await connection.ExecuteAsync(sqlUpdateMute, new { unmuteId, muteId });
}
}
catch (Exception ex)
{
Console.WriteLine(ex);
}
}
/// <summary>
/// Expires all old mutes that have passed their duration according to current time.
/// </summary>
/// <returns>Task representing the asynchronous expiration operation.</returns>
public async Task ExpireOldMutes()
{
if (databaseProvider == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetExpireMutesQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode);
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception)
{
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired mutes");
}
}
}

View File

@@ -0,0 +1,668 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Modules.Entities;
using Dapper;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using System.Text.Json;
using CounterStrikeSharp.API.Modules.Admin;
using System.Diagnostics.CodeAnalysis;
using CS2_SimpleAdmin.Database;
namespace CS2_SimpleAdmin.Managers;
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 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>
/// Retrieves all players' flags and associated data asynchronously.
/// </summary>
/// <returns>A list of tuples containing player SteamID, name, flags, immunity, and expiration time.</returns>
private async Task<List<(ulong, string ,List<string>, int, DateTime?)>> GetAllPlayersFlags()
{
if (databaseProvider == null)
return new List<(ulong, string, List<string>, int, DateTime?)>();
var now = Time.ActualDateTime();
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAdminsQuery();
var admins = (await connection.QueryAsync(sql, new { CurrentTime = now, serverid = CS2_SimpleAdmin.ServerId })).ToList();
var groupedPlayers = admins
.GroupBy(r => new { playerSteamId = r.player_steamid, playerName = r.player_name, r.immunity, r.ends })
.Select(g =>
{
ulong steamId = g.Key.playerSteamId switch
{
long l => (ulong)l,
int i => (ulong)i,
string s when ulong.TryParse(s, out var parsed) => parsed,
_ => 0UL
};
int immunity = g.Key.immunity switch
{
int i => i,
string s when int.TryParse(s, out var parsed) => parsed,
_ => 0
};
DateTime? ends = g.Key.ends as DateTime?;
string playerName = g.Key.playerName as string ?? string.Empty;
// tutaj zakładamy, że Dapper zwraca już string (nie dynamic)
var flags = g.Select(r => r.flag as string ?? string.Empty)
.Distinct()
.ToList();
return (steamId, playerName, flags, immunity, ends);
})
.ToList();
return groupedPlayers;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Unable to load admins from database! {exception}", ex.Message);
return [];
}
}
/*
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>
/// Retrieves all groups' data including flags and immunity asynchronously.
/// </summary>
/// <returns>A dictionary with group names as keys and tuples of flags and immunity as values.</returns>
private async Task<Dictionary<string, (List<string>, int)>> GetAllGroupsData()
{
if (databaseProvider == null) return [];
await using var connection = await databaseProvider.CreateConnectionAsync();
;
try
{
// var sql = "SELECT group_id FROM sa_groups_servers WHERE (server_id = @serverid OR server_id IS NULL)";
// var groupDataSql = connection.Query<int>(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
var sql = databaseProvider.GetGroupsQuery();
var groupData = connection.Query(sql, new { serverid = CS2_SimpleAdmin.ServerId }).ToList();
if (groupData.Count == 0)
{
return [];
}
var groupInfoDictionary = new Dictionary<string, (List<string>, int)>();
foreach (var row in groupData)
{
var groupName = (string)row.group_name;
var flag = (string)row.flag;
var immunity = (int)row.immunity;
// Check if the group name already exists in the dictionary
if (!groupInfoDictionary.TryGetValue(groupName, out (List<string>, int) value))
{
value = ([], immunity);
// If it doesn't exist, add a new entry with an empty list of flags and immunity
groupInfoDictionary[groupName] = value;
}
value.Item1.Add(flag);
}
return groupInfoDictionary;
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Unable to load groups from database! {exception}", ex.Message);
}
return [];
}
/// <summary>
/// Creates a JSON file containing groups data asynchronously.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task CrateGroupsJsonFile()
{
var groupsData = await GetAllGroupsData();
var jsonData = new Dictionary<string, object>();
foreach (var kvp in groupsData)
{
var groupData = new Dictionary<string, object>
{
["flags"] = kvp.Value.Item1,
["immunity"] = kvp.Value.Item2
};
jsonData[kvp.Key] = groupData;
}
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(jsonData, options);
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "groups.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>
/// Creates a JSON file containing admins data asynchronously.
/// </summary>
[UnconditionalSuppressMessage("Trimming", "IL2026:Members annotated with 'RequiresUnreferencedCodeAttribute' require dynamic access otherwise can break functionality when trimming application code", Justification = "<Pending>")]
public async Task CreateAdminsJsonFile()
{
List<(ulong identity, string name, List<string> flags, int immunity, DateTime? ends)> allPlayers = await GetAllPlayersFlags();
var validPlayers = allPlayers
.Where(player => SteamID.TryParse(player.identity.ToString(), out _))
.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
.GroupBy(player => player.name) // Group by player name
.ToDictionary(
group => group.Key, // Use the player name as key
object (group) =>
{
// Consolidate data for players with same name
var consolidatedData = group.Aggregate(
new
{
identity = string.Empty,
immunity = 0,
flags = new List<string>(),
groups = new List<string>()
},
(acc, player) =>
{
// Merge identities
if (string.IsNullOrEmpty(acc.identity) && !string.IsNullOrEmpty(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) };
// Combine flags and groups
acc = acc with
{
flags = acc.flags.Concat(player.flags.Where(flag => flag.StartsWith($"@"))).Distinct().ToList(),
groups = acc.groups.Concat(player.flags.Where(flag => flag.StartsWith($"#"))).Distinct().ToList()
};
return acc;
});
Server.NextWorldUpdate(() =>
{
var keysToRemove = new List<SteamID>();
foreach (var steamId in AdminCache.Keys.ToList())
{
var data = AdminManager.GetPlayerAdminData(steamId);
if (data != null)
{
var flagsArray = AdminCache[steamId].Flags.ToArray();
AdminManager.RemovePlayerPermissions(steamId, flagsArray);
AdminManager.RemovePlayerFromGroup(steamId, true, flagsArray);
}
keysToRemove.Add(steamId);
}
foreach (var steamId in keysToRemove)
{
if (!AdminCache.TryRemove(steamId, out _)) continue;
var data = AdminManager.GetPlayerAdminData(steamId);
if (data == null) continue;
if (data.Flags.Count != 0 && data.Groups.Count != 0) continue;
AdminManager.ClearPlayerPermissions(steamId);
AdminManager.RemovePlayerAdminData(steamId);
}
foreach (var player in group)
{
if (SteamID.TryParse(player.identity.ToString(), out var steamId) && steamId != null)
{
AdminCache.TryAdd(steamId, (player.ends, player.flags));
}
}
});
// 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;
});
var options = new JsonSerializerOptions
{
WriteIndented = true,
PropertyNamingPolicy = JsonNamingPolicy.CamelCase
};
var json = JsonSerializer.Serialize(jsonData, options);
var filePath = Path.Combine(CS2_SimpleAdmin.Instance.ModuleDirectory, "data", "admins.json");
await File.WriteAllTextAsync(filePath, json);
//await File.WriteAllTextAsync(CS2_SimpleAdmin.Instance.ModuleDirectory + "/data/admins.json", json);
}
/// <summary>
/// Deletes an admin by their SteamID from the database asynchronously.
/// </summary>
/// <param name="playerSteamId">The SteamID of the admin to delete.</param>
/// <param name="globalDelete">Whether to delete the admin globally or only for the current server.</param>
public async Task DeleteAdminBySteamId(string playerSteamId, bool globalDelete = false)
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(playerSteamId)) return;
//_adminCache.TryRemove(playerSteamId, out _);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetDeleteAdminQuery(globalDelete);
await connection.ExecuteAsync(sql, new { PlayerSteamID = playerSteamId, CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.Message);
}
}
/// <summary>
/// Adds a new admin with specified details asynchronously.
/// </summary>
/// <param name="playerSteamId">SteamID of the admin.</param>
/// <param name="playerName">Name of the admin.</param>
/// <param name="flagsList">List of flags assigned to the admin.</param>
/// <param name="immunity">Immunity level.</param>
/// <param name="time">Duration in minutes for admin expiration; 0 means permanent.</param>
/// <param name="globalAdmin">Whether the admin is global or server-specific.</param>
public async Task AddAdminBySteamId(string playerSteamId, string playerName, List<string> flagsList, int immunity = 0, int time = 0, bool globalAdmin = false)
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(playerSteamId) || flagsList.Count == 0) return;
var now = Time.ActualDateTime();
DateTime? futureTime;
if (time != 0)
futureTime = now.AddMinutes(time);
else
futureTime = null;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
// Insert admin into sa_admins table
var insertAdminSql = databaseProvider.GetAddAdminQuery();
var adminId = await connection.ExecuteScalarAsync<int>(insertAdminSql, new
{
playerSteamId,
playerName,
immunity,
ends = futureTime,
created = now,
serverid = globalAdmin ? null : CS2_SimpleAdmin.ServerId
});
// Insert flags into sa_admins_flags table
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();
await connection.ExecuteAsync(insertFlagsSql, new
{
adminId,
flag
});
}
await Server.NextWorldUpdateAsync(() =>
{
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
});
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
}
}
/// <summary>
/// Adds a new group with flags and immunity asynchronously.
/// </summary>
/// <param name="groupName">Name of the group.</param>
/// <param name="flagsList">List of flags assigned to the group.</param>
/// <param name="immunity">Immunity level of the group.</param>
/// <param name="globalGroup">Whether the group is global or server-specific.</param>
public async Task AddGroup(string groupName, List<string> flagsList, int immunity = 0, bool globalGroup = false)
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(groupName) || flagsList.Count == 0) return;
await using var connection = await databaseProvider.CreateConnectionAsync();
try
{
// Insert group into sa_groups table
var insertGroup = databaseProvider.GetAddGroupQuery();
var groupId = await connection.ExecuteScalarAsync<int>(insertGroup, new
{
groupName,
immunity
});
// Insert flags into sa_groups_flags table
foreach (var flag in flagsList)
{
var insertFlagsSql = databaseProvider.GetAddGroupFlagsQuery();
await connection.ExecuteAsync(insertFlagsSql, new
{
groupId,
flag
});
}
var insertGroupServer = databaseProvider.GetAddGroupServerQuery();
await connection.ExecuteAsync(insertGroupServer, new { groupId, server_id = globalGroup ? null : CS2_SimpleAdmin.ServerId });
await Server.NextWorldUpdateAsync(() =>
{
CS2_SimpleAdmin.Instance.ReloadAdmins(null);
});
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Problem with loading admins: {exception}", ex.Message);
}
}
/// <summary>
/// Deletes a group by name asynchronously.
/// </summary>
/// <param name="groupName">Name of the group to delete.</param>
public async Task DeleteGroup(string groupName)
{
if (databaseProvider == null) return;
if (string.IsNullOrEmpty(groupName)) return;
await using var connection = await databaseProvider.CreateConnectionAsync();
try
{
var sql = databaseProvider.GetDeleteGroupQuery();
await connection.ExecuteAsync(sql, new { groupName });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(ex.ToString());
}
}
/// <summary>
/// Deletes admins whose permissions have expired asynchronously.
/// </summary>
public async Task DeleteOldAdmins()
{
if (databaseProvider == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetDeleteOldAdminsQuery();
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime() });
}
catch (Exception)
{
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove expired admins");
}
}
}

View File

@@ -0,0 +1,450 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Timers;
using CounterStrikeSharp.API.ValveConstants.Protobuf;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
using ZLinq;
namespace CS2_SimpleAdmin.Managers;
internal class PlayerManager
{
private readonly SemaphoreSlim _loadPlayerSemaphore = new(5);
private readonly CS2_SimpleAdminConfig _config = CS2_SimpleAdmin.Instance.Config;
/// <summary>
/// Loads and initializes player data when a client connects.
/// </summary>
/// <param name="player">The <see cref="CCSPlayerController"/> instance representing the connecting player.</param>
/// <param name="fullConnect">
/// Determines whether to perform a full synchronization of player data.
/// If true, full checks (bans, IP history, penalties, warns, mutes) will be loaded and applied.
/// </param>
/// <remarks>
/// This method validates the player's identity, checks for bans, updates the IP history table,
/// loads penalties (mutes/gags/warns), and optionally notifies admin players about the connecting player's penalties.
/// </remarks>
public void LoadPlayerData(CCSPlayerController player, bool fullConnect = false)
{
if (!player.UserId.HasValue)
{
Helper.KickPlayer(player, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_INVALIDCONNECTION);
return;
}
var userId = player.UserId.Value;
var slot = player.Slot;
var steamId = player.SteamID;
var playerName = !string.IsNullOrEmpty(player.PlayerName)
? player.PlayerName
: CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var ipAddress = player.IpAddress?.Split(":")[0];
if (CS2_SimpleAdmin.DatabaseProvider == null || CS2_SimpleAdmin.Instance.CacheManager == null) return;
Task.Run(async () =>
{
try
{
await _loadPlayerSemaphore.WaitAsync();
if (!CS2_SimpleAdmin.PlayersInfo.ContainsKey(steamId))
{
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($"Player {playerName} ({steamId} - {ipAddress}) is banned? {isBanned.ToString()}");
if (isBanned)
{
await Server.NextWorldUpdateAsync(() =>
{
// CS2_SimpleAdmin._logger?.LogInformation($"Kicking {playerName}");
Helper.KickPlayer(userId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED);
});
return;
}
}
if (fullConnect)
{
var playerInfo = new PlayerInfo(userId, slot, new SteamID(steamId), playerName, ipAddress);
CS2_SimpleAdmin.PlayersInfo[steamId] = playerInfo;
await Server.NextWorldUpdateAsync(() =>
{
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();
if (CS2_SimpleAdmin.Instance.CacheManager.HasIpForPlayer(
steamId, ipAddress))
{
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP,
name = @playerName
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
{
playerName,
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
else
{
const string selectQuery =
"SELECT COUNT(*) FROM `sa_players_ips` WHERE steamid = @SteamID AND address = @IPAddress;";
var recordExists = await connection.ExecuteScalarAsync<int>(selectQuery, new
{
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
if (recordExists > 0)
{
const string updateQuery = """
UPDATE `sa_players_ips`
SET used_at = CURRENT_TIMESTAMP,
name = @playerName
WHERE steamid = @SteamID AND address = @IPAddress;
""";
await connection.ExecuteAsync(updateQuery, new
{
playerName,
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
else
{
const string insertQuery = """
INSERT INTO `sa_players_ips` (steamid, name, address, used_at)
VALUES (@SteamID, @playerName, @IPAddress, CURRENT_TIMESTAMP);
""";
await connection.ExecuteAsync(insertQuery, new
{
SteamID = CS2_SimpleAdmin.PlayersInfo[steamId].SteamId.SteamId64,
playerName,
IPAddress = IpHelper.IpToUint(ipAddress)
});
}
}
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError(
$"Unable to save ip address for {playerInfo.Name} ({ipAddress}) {ex.Message}");
}
playerInfo.AccountsAssociated =
CS2_SimpleAdmin.Instance.CacheManager?.GetAccountsByIp(ipAddress).AsValueEnumerable()
.Select(x => (x.SteamId, x.PlayerName)).ToList() ?? [];
}
try
{
// var isBanned = CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType == 0
// ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(
// CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString(), null)
// : CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
// ? CS2_SimpleAdmin.Instance.CacheManager.IsPlayerOrAnyIpBanned(CS2_SimpleAdmin
// .PlayersInfo[userId].SteamId.SteamId64)
// : CS2_SimpleAdmin.Instance.CacheManager.IsPlayerBanned(CS2_SimpleAdmin.PlayersInfo[userId].SteamId.SteamId64.ToString(), ipAddress);
if (CS2_SimpleAdmin.PlayersInfo.TryGetValue(steamId, out PlayerInfo? value)) // Temp skip
{
var warns = await CS2_SimpleAdmin.Instance.WarnManager.GetPlayerWarns(value, false);
var (totalMutes, totalGags, totalSilences) =
await CS2_SimpleAdmin.Instance.MuteManager.GetPlayerMutes(value);
value.TotalBans = CS2_SimpleAdmin.Instance.CacheManager
?.GetPlayerBansBySteamId(value.SteamId.SteamId64)
.Count ?? 0;
value.TotalMutes = totalMutes;
value.TotalGags = totalGags;
value.TotalSilences = totalSilences;
value.TotalWarns = warns.Count;
var activeMutes =
await CS2_SimpleAdmin.Instance.MuteManager.IsPlayerMuted(value.SteamId.SteamId64
.ToString());
if (activeMutes.Count > 0)
{
foreach (var mute in activeMutes)
{
string muteType = mute.type;
DateTime ends = mute.ends;
int duration = mute.duration;
switch (muteType)
{
// Apply mute penalty based on mute type
case "GAG":
PlayerPenaltyManager.AddPenalty(
CS2_SimpleAdmin.PlayersInfo[steamId].Slot,
PenaltyType.Gag, ends, duration);
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Gag].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_gag", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]);
break;
case "MUTE":
PlayerPenaltyManager.AddPenalty(
CS2_SimpleAdmin.PlayersInfo[steamId].Slot,
PenaltyType.Mute, ends, duration);
await Server.NextWorldUpdateAsync(() =>
{
player.VoiceFlags = VoiceFlags.Muted;
});
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Mute].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_mute", ends.ToLocalTime().ToString(CultureInfo.InvariantCulture)]);
break;
default:
PlayerPenaltyManager.AddPenalty(
CS2_SimpleAdmin.PlayersInfo[steamId].Slot,
PenaltyType.Silence, ends, duration);
await Server.NextWorldUpdateAsync(() =>
{
player.VoiceFlags = VoiceFlags.Muted;
});
// if (CS2_SimpleAdmin._localizer != null)
// mutesList[PenaltyType.Silence].Add(CS2_SimpleAdmin._localizer["sa_player_penalty_info_active_silence", ends.ToLocalTime().ToString(CultureInfo.CurrentCulture)]);
break;
}
}
}
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.NotifyPenaltiesToAdminOnConnect)
{
await Server.NextWorldUpdateAsync(() =>
{
foreach (var admin in Helper.GetValidPlayers()
.Where(p => (AdminManager.PlayerHasPermissions(
new SteamID(p.SteamID),
"@css/kick") ||
AdminManager.PlayerHasPermissions(
new SteamID(p.SteamID),
"@css/ban")) &&
p.Connected == PlayerConnectedState.PlayerConnected &&
!CS2_SimpleAdmin.AdminDisabledJoinComms
.Contains(p.SteamID)))
{
if (CS2_SimpleAdmin._localizer == null || admin == player) continue;
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer,
"sa_admin_penalty_info",
player.PlayerName,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalBans,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalGags,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalMutes,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalSilences,
CS2_SimpleAdmin.PlayersInfo[steamId].TotalWarns
);
if (CS2_SimpleAdmin.PlayersInfo[steamId].AccountsAssociated.Count >= 2)
{
var associatedAcccountsChunks =
CS2_SimpleAdmin.PlayersInfo[steamId].AccountsAssociated.ChunkBy(5)
.ToList();
foreach (var chunk in associatedAcccountsChunks)
{
admin.SendLocalizedMessage(CS2_SimpleAdmin._localizer,
"sa_admin_associated_accounts",
player.PlayerName,
string.Join(", ",
chunk.Select(a => $"{a.PlayerName} ({a.SteamId})"))
);
}
}
}
});
}
}
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError("Error processing player connection: {exception}",
ex.Message);
}
}
}
finally
{
_loadPlayerSemaphore.Release();
}
});
if (CS2_SimpleAdmin.RenamedPlayers.TryGetValue(player.SteamID, out var name))
{
player.Rename(name);
}
}
/// <summary>
/// Periodically checks the status of online players and applies timers for speed, gravity,
/// and penalty expiration validation.
/// </summary>
/// <remarks>
/// This method registers two repeating timers:
/// <list type="bullet">
/// <item><description>One short-interval timer to update speed/gravity modifications applied to players.</description></item>
/// <item><description>
/// One long-interval timer (default 61 seconds) to expire bans, mutes, warns, refresh caches,
/// and remove outdated penalties from connected players.
/// </description></item>
/// </list>
/// Additionally, banned players still online are kicked, and admins may be updated about mute statuses based on the configured time mode.
/// </remarks>
public void CheckPlayersTimer()
{
CS2_SimpleAdmin.Instance.AddTimer(0.12f, () =>
{
if (CS2_SimpleAdmin.SpeedPlayers.Count > 0)
{
foreach (var (player, speed) in CS2_SimpleAdmin.SpeedPlayers)
{
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.SetSpeed(speed);
}
}
}
if (CS2_SimpleAdmin.GravityPlayers.Count > 0)
{
foreach (var (player, gravity) in CS2_SimpleAdmin.GravityPlayers)
{
if (player is { IsValid: true, Connected: PlayerConnectedState.PlayerConnected, PlayerPawn.Value.LifeState: (int)LifeState_t.LIFE_ALIVE })
{
player.SetGravity(gravity);
}
}
}
}, TimerFlags.REPEAT);
CS2_SimpleAdmin.Instance.PlayersTimer = CS2_SimpleAdmin.Instance.AddTimer(61.0f, () =>
{
#if DEBUG
CS2_SimpleAdmin._logger?.LogCritical("[OnMapStart] Expired check");
#endif
if (CS2_SimpleAdmin.DatabaseProvider == null)
return;
var tempPlayers = Helper.GetValidPlayers()
.Select(p => new
{
p.PlayerName, p.SteamID, p.IpAddress, p.UserId, p.Slot,
})
.ToList();
var pluginInstance = CS2_SimpleAdmin.Instance;
_ = Task.Run(async () =>
{
try
{
var expireTasks = new[]
{
pluginInstance.BanManager.ExpireOldBans(),
pluginInstance.MuteManager.ExpireOldMutes(),
pluginInstance.WarnManager.ExpireOldWarns(),
pluginInstance.CacheManager?.RefreshCacheAsync() ?? Task.CompletedTask,
pluginInstance.PermissionManager.DeleteOldAdmins()
};
await Task.WhenAll(expireTasks);
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Error processing players timer tasks: {ex.Message}");
if (ex is AggregateException aggregate)
{
foreach (var inner in aggregate.InnerExceptions)
{
CS2_SimpleAdmin._logger?.LogError($"Inner exception: {inner.Message}");
}
}
}
if (pluginInstance.CacheManager == null)
return;
var bannedPlayers = tempPlayers.AsValueEnumerable()
.Where(player =>
{
var playerName = player.PlayerName;
var steamId = player.SteamID;
var ip = player.IpAddress?.Split(':')[0];
return CS2_SimpleAdmin.Instance.Config.OtherSettings.BanType switch
{
0 => pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, null),
_ => CS2_SimpleAdmin.Instance.Config.OtherSettings.CheckMultiAccountsByIp
? pluginInstance.CacheManager.IsPlayerOrAnyIpBanned(playerName, steamId, ip)
: pluginInstance.CacheManager.IsPlayerBanned(playerName, steamId, ip)
};
}).ToList();
if (bannedPlayers.Count > 0)
{
foreach (var player in bannedPlayers)
{
if (!player.UserId.HasValue) continue;
await Server.NextWorldUpdateAsync(() => Helper.KickPlayer((int)player.UserId, NetworkDisconnectionReason.NETWORK_DISCONNECT_REJECT_BANNED));
}
}
if (_config.OtherSettings.TimeMode == 0)
{
var onlinePlayers = tempPlayers.AsValueEnumerable().Select(player => (player.SteamID, player.UserId, player.Slot)).ToList();
if (tempPlayers.Count == 0 || onlinePlayers.Count == 0) return;
await pluginInstance.MuteManager.CheckOnlineModeMutes(onlinePlayers);
}
});
try
{
var players = Helper.GetValidPlayers();
var penalizedSlots = players
.Where(player => PlayerPenaltyManager.IsSlotInPenalties(player.Slot))
.Select(player => new
{
Player = player,
IsMuted = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Mute, out _),
IsSilenced = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Silence, out _),
IsGagged = PlayerPenaltyManager.IsPenalized(player.Slot, PenaltyType.Gag, out _)
});
foreach (var entry in penalizedSlots)
{
if (!entry.IsMuted && !entry.IsSilenced)
{
entry.Player.VoiceFlags = VoiceFlags.Normal;
}
}
PlayerPenaltyManager.RemoveExpiredPenalties();
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogError($"Unable to remove old penalties: {ex.Message}");
}
}, TimerFlags.REPEAT);
}
}

View File

@@ -0,0 +1,258 @@
using CS2_SimpleAdminApi;
using System.Collections.Concurrent;
namespace CS2_SimpleAdmin.Managers;
public static class PlayerPenaltyManager
{
private static readonly ConcurrentDictionary<int, Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>> Penalties =
new();
/// <summary>
/// Adds a penalty for a specific player slot and penalty type.
/// </summary>
/// <param name="slot">The player slot where the penalty should be applied.</param>
/// <param name="penaltyType">The type of penalty to apply (e.g. gag, mute, silence).</param>
/// <param name="endDateTime">The validity expiration date/time of the penalty.</param>
/// <param name="durationInMinutes">The duration of the penalty in minutes (0 for permanent).</param>
public static void AddPenalty(int slot, PenaltyType penaltyType, DateTime endDateTime, int durationInMinutes)
{
Penalties.AddOrUpdate(slot,
(_) =>
{
var dict = new Dictionary<PenaltyType, List<(DateTime, int, bool)>>
{
[penaltyType] = [(endDateTime, durationInMinutes, false)]
};
return dict;
},
(_, existingDict) =>
{
if (!existingDict.TryGetValue(penaltyType, out var value))
{
value = new List<(DateTime, int, bool)>();
existingDict[penaltyType] = value;
}
value.Add((endDateTime, durationInMinutes, false));
return existingDict;
});
}
/// <summary>
/// Determines whether a player is currently penalized with the given penalty type.
/// </summary>
/// <param name="slot">The player slot to check.</param>
/// <param name="penaltyType">The penalty type to check.</param>
/// <param name="endDateTime">The out-parameter returning the end datetime of the penalty if active.</param>
/// <returns>True if the player has an active penalty, false otherwise.</returns>
public static bool IsPenalized(int slot, PenaltyType penaltyType, out DateTime? endDateTime)
{
endDateTime = null;
if (!Penalties.TryGetValue(slot, out var penaltyDict) ||
!penaltyDict.TryGetValue(penaltyType, out var penaltiesList)) return false;
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
{
if (penaltiesList.Count == 0) return false;
endDateTime = penaltiesList.First().EndDateTime;
return true;
}
var now = Time.ActualDateTime();
// Check if any active penalties exist
foreach (var penalty in penaltiesList.ToList())
{
// Check if the penalty is still active
if (penalty.Duration > 0 && now >= penalty.EndDateTime)
{
penaltiesList.Remove(penalty); // Remove expired penalty
if (penaltiesList.Count == 0)
{
penaltyDict.Remove(penaltyType); // Remove penalty type if no more penalties exist
}
}
else if (penalty.Duration == 0 || now < penalty.EndDateTime)
{
// Set endDateTime to the end time of this active penalty
endDateTime = penalty.EndDateTime;
return true;
}
}
// Return false if no active penalties are found
return false;
}
/// <summary>
/// Retrieves all penalties for a player of a specific penalty type.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="penaltyType">The penalty type to retrieve.</param>
/// <returns>A list of penalties if found, otherwise an empty list.</returns>
public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, PenaltyType penaltyType)
{
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
penaltyDict.TryGetValue(penaltyType, out var penaltiesList))
{
return penaltiesList;
}
return [];
}
/// <summary>
/// Retrieves all penalties for a player across multiple penalty types.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="penaltyType">A list of penalty types to retrieve.</param>
/// <returns>A combined list of penalties of all requested types.</returns>
public static List<(DateTime EndDateTime, int Duration, bool Passed)> GetPlayerPenalties(int slot, List<PenaltyType> penaltyType)
{
List<(DateTime EndDateTime, int Duration, bool Passed)> result = [];
if (Penalties.TryGetValue(slot, out var penaltyDict))
{
foreach (var type in penaltyType)
{
if (penaltyDict.TryGetValue(type, out var penaltiesList))
{
result.AddRange(penaltiesList);
}
}
}
return result;
}
/// <summary>
/// Retrieves all penalties for a player across all penalty types.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <returns>A dictionary with penalty types as keys and lists of penalties as values.</returns>
public static Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetAllPlayerPenalties(int slot)
{
// Check if the player has any penalties in the dictionary
return Penalties.TryGetValue(slot, out var penaltyDict) ?
// Return all penalty types and their respective penalties for the player
penaltyDict :
// If the player has no penalties, return an empty dictionary
new Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>>();
}
/// <summary>
/// Checks if a given slot has any penalties assigned.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <returns>True if the player has any penalties, false otherwise.</returns>
public static bool IsSlotInPenalties(int slot)
{
return Penalties.ContainsKey(slot);
}
/// <summary>
/// Removes all penalties assigned to a specific player slot.
/// </summary>
/// <param name="slot">The player slot.</param>
public static void RemoveAllPenalties(int slot)
{
if (Penalties.ContainsKey(slot))
{
Penalties.TryRemove(slot, out _);
}
}
/// <summary>
/// Removes all penalties for all players.
/// </summary>
public static void RemoveAllPenalties()
{
Penalties.Clear();
}
/// <summary>
/// Removes all penalties of a specific type from a player.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="penaltyType">The penalty type to remove.</param>
public static void RemovePenaltiesByType(int slot, PenaltyType penaltyType)
{
if (Penalties.TryGetValue(slot, out var penaltyDict) &&
penaltyDict.ContainsKey(penaltyType))
{
penaltyDict.Remove(penaltyType);
}
}
/// <summary>
/// Marks penalties with a specific end datetime as "passed" for a player.
/// </summary>
/// <param name="slot">The player slot.</param>
/// <param name="dateTime">The end datetime of penalties to mark as passed.</param>
public static void RemovePenaltiesByDateTime(int slot, DateTime dateTime)
{
if (!Penalties.TryGetValue(slot, out var penaltyDict)) return;
foreach (var penaltiesList in penaltyDict.Values)
{
for (var i = 0; i < penaltiesList.Count; i++)
{
if (penaltiesList[i].EndDateTime != dateTime) continue;
// Create a copy of the penalty
var penalty = penaltiesList[i];
// Update the end datetime of the copied penalty to the current datetime
penalty.Passed = true;
// Replace the original penalty with the modified one
penaltiesList[i] = penalty;
}
}
}
/// <summary>
/// Removes or expires penalties automatically across all players based on their duration or "passed" flag.
/// </summary>
/// <remarks>
/// If <c>TimeMode == 0</c>, penalties are considered passed manually and are removed if flagged as such.
/// Otherwise, expired penalties are removed based on the current datetime compared with their end time.
/// </remarks>
public static void RemoveExpiredPenalties()
{
if (CS2_SimpleAdmin.Instance.Config.OtherSettings.TimeMode == 0)
{
foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
{
// Remove expired penalties for the player
foreach (var penaltiesList in penaltyDict.Values)
{
penaltiesList.RemoveAll(p => p is { Duration: > 0, Passed: true });
}
// Remove player slot if no penalties left
if (penaltyDict.Count == 0)
{
Penalties.TryRemove(playerSlot, out _);
}
}
return;
}
var now = Time.ActualDateTime();
foreach (var (playerSlot, penaltyDict) in Penalties.ToList()) // Use ToList to avoid modification while iterating
{
foreach (var penaltiesList in penaltyDict.Values)
{
penaltiesList.RemoveAll(p => p.Duration > 0 && now >= p.EndDateTime);
}
if (penaltyDict.Count == 0)
{
Penalties.TryRemove(playerSlot, out _);
}
}
}
}

View File

@@ -0,0 +1,123 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Modules.Cvars;
using Dapper;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Managers;
public class ServerManager
{
private int _getIpTryCount;
/// <summary>
/// Checks whether the server setting <c>sv_hibernate_when_empty</c> is enabled.
/// Logs an error if this setting is true, since it prevents the plugin from working properly.
/// </summary>
public static void CheckHibernationStatus()
{
var convar = ConVar.Find("sv_hibernate_when_empty");
if (convar == null || !convar.GetPrimitiveValue<bool>())
return;
CS2_SimpleAdmin._logger?.LogError("Detected setting \"sv_hibernate_when_empty true\", set false to make plugin work properly");
}
/// <summary>
/// Initiates the asynchronous process to load server data such as IP address, port, hostname, and RCON password.
/// Handles retry attempts if IP address is not immediately available.
/// Updates or inserts the server record in the database accordingly.
/// After loading, triggers admin reload and cache initialization.
/// Also optionally sends plugin usage metrics if enabled in configuration.
/// </summary>
public void LoadServerData()
{
CS2_SimpleAdmin.Instance.AddTimer(2.0f, () =>
{
if (CS2_SimpleAdmin.ServerLoaded || CS2_SimpleAdmin.DatabaseProvider == null) return;
if (_getIpTryCount > 32 && Helper.GetServerIp().StartsWith("0.0.0.0") || string.IsNullOrEmpty(Helper.GetServerIp()))
{
CS2_SimpleAdmin._logger?.LogError("Unable to load server data - can't fetch ip address!");
return;
}
var ipAddress = ConVar.Find("ip")?.StringValue;
if (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0"))
{
ipAddress = Helper.GetServerIp();
if (_getIpTryCount <= 32 && (string.IsNullOrEmpty(ipAddress) || ipAddress.StartsWith("0.0.0")))
{
_getIpTryCount++;
LoadServerData();
return;
}
}
var address = $"{ipAddress}:{ConVar.Find("hostport")?.GetPrimitiveValue<int>()}";
var hostname = ConVar.Find("hostname")?.StringValue ?? CS2_SimpleAdmin._localizer?["sa_unknown"] ?? "Unknown";
var rconPassword = ConVar.Find("rcon_password")?.StringValue ?? "";
CS2_SimpleAdmin.IpAddress = address;
Task.Run(async () =>
{
try
{
await using var connection = await CS2_SimpleAdmin.DatabaseProvider.CreateConnectionAsync();
int? serverId = await connection.ExecuteScalarAsync<int?>(
"SELECT id FROM sa_servers WHERE address = @address",
new { address });
if (serverId == null)
{
await connection.ExecuteAsync(
"INSERT INTO sa_servers (address, hostname, rcon_password) VALUES (@address, @hostname, @rconPassword)",
new { address, hostname, rconPassword });
serverId = await connection.ExecuteScalarAsync<int>(
"SELECT id FROM sa_servers WHERE address = @address",
new { address });
}
else
{
await connection.ExecuteAsync(
"UPDATE sa_servers SET hostname = @hostname, rcon_password = @rconPassword WHERE address = @address",
new { address, hostname, rconPassword });
}
CS2_SimpleAdmin.ServerId = serverId;
CS2_SimpleAdmin._logger?.LogInformation("Loaded server with ip {ip}", ipAddress);
if (CS2_SimpleAdmin.ServerId != null)
{
await Server.NextWorldUpdateAsync(() => CS2_SimpleAdmin.Instance.ReloadAdmins(null));
}
CS2_SimpleAdmin.ServerLoaded = true;
if (CS2_SimpleAdmin.Instance.CacheManager != null)
await CS2_SimpleAdmin.Instance.CacheManager.InitializeCacheAsync();
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical("Unable to create or get server_id: " + ex.Message);
}
if (CS2_SimpleAdmin.Instance.Config.EnableMetrics)
{
var queryString = $"?address={address}&hostname={hostname}";
var client = CS2_SimpleAdmin.HttpClient;
try
{
await client.GetAsync($"https://api.daffyy.dev/index.php{queryString}");
}
catch (HttpRequestException ex)
{
CS2_SimpleAdmin._logger?.LogWarning($"Unable to make metrics call: {ex.Message}");
}
}
});
});
}
}

View File

@@ -0,0 +1,206 @@
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdminApi;
using Dapper;
using Microsoft.Extensions.Logging;
namespace CS2_SimpleAdmin.Managers;
internal class WarnManager(IDatabaseProvider? databaseProvider)
{
/// <summary>
/// Adds a warning to a player with an optional issuer and reason.
/// </summary>
/// <param name="player">The player who is being warned.</param>
/// <param name="issuer">The player issuing the warning; null indicates console or system.</param>
/// <param name="reason">The reason for the warning.</param>
/// <param name="time">Optional duration of the warning in minutes (0 means permanent).</param>
/// <returns>The identifier of the inserted warning, or null if the operation failed.</returns>
public async Task<int?> WarnPlayer(PlayerInfo player, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddWarnQuery(true);
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = player.SteamId.SteamId64,
playerName = player.Name,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return warnId;
}
catch
{
return null;
}
}
/// <summary>
/// Adds a warning to a player identified by SteamID with optional issuer and reason.
/// </summary>
/// <param name="playerSteamId">The SteamID64 of the player being warned.</param>
/// <param name="issuer">The player issuing the warning; null indicates console or system.</param>
/// <param name="reason">The reason for the warning.</param>
/// <param name="time">Optional duration of the warning in minutes (0 means permanent).</param>
/// <returns>The identifier of the inserted warning, or null if the operation failed.</returns>
public async Task<int?> AddWarnBySteamid(ulong playerSteamId, PlayerInfo? issuer, string reason, int time = 0)
{
if (databaseProvider == null) return null;
var now = Time.ActualDateTime();
var futureTime = now.AddMinutes(time);
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetAddWarnQuery(false);
var warnId = await connection.ExecuteScalarAsync<int?>(sql, new
{
playerSteamid = playerSteamId,
adminSteamid = issuer?.SteamId.SteamId64 ?? 0,
adminName = issuer?.Name ?? CS2_SimpleAdmin._localizer?["sa_console"] ?? "Console",
muteReason = reason,
duration = time,
ends = futureTime,
created = now,
serverid = CS2_SimpleAdmin.ServerId
});
return warnId;
}
catch
{
return null;
}
}
/// <summary>
/// Retrieves a list of warnings for a specific player.
/// </summary>
/// <param name="player">The player whose warnings to retrieve.</param>
/// <param name="active">If true, returns only active (non-expired) warnings; otherwise returns all warnings.</param>
/// <returns>A list of dynamic objects representing warnings, or an empty list if none found or on failure.</returns>
public async Task<List<dynamic>> GetPlayerWarns(PlayerInfo player, bool active = true)
{
if (databaseProvider == null) return [];
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetPlayerWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
var parameters = new { PlayerSteamID = player.SteamId.SteamId64, serverid = CS2_SimpleAdmin.ServerId };
var warns = await connection.QueryAsync<dynamic>(sql, parameters);
return warns.ToList();
}
catch (Exception)
{
return [];
}
}
/// <summary>
/// Retrieves the count of warnings for a player specified by SteamID.
/// </summary>
/// <param name="steamId">The SteamID64 of the player.</param>
/// <param name="active">If true, counts only active (non-expired) warnings; otherwise counts all warnings.</param>
/// <returns>The count of warnings as an integer, or 0 if none found or on failure.</returns>
public async Task<int> GetPlayerWarnsCount(ulong steamId, bool active = true)
{
if (databaseProvider == null) return 0;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetPlayerWarnsCountQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode, active);
var warnsCount = await connection.ExecuteScalarAsync<int>(sql, new { PlayerSteamID = steamId, serverid = CS2_SimpleAdmin.ServerId });
return warnsCount;
}
catch (Exception)
{
return 0;
}
}
/// <summary>
/// Removes a specific warning by its identifier from a player's record.
/// </summary>
/// <param name="player">The player whose warning will be removed.</param>
/// <param name="warnId">The identifier of the warning to remove.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UnwarnPlayer(PlayerInfo player, int warnId)
{
if (databaseProvider == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetUnwarnByIdQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { steamid = player.SteamId.SteamId64, warnId, serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove warn + {ex}");
}
}
/// <summary>
/// Removes the most recent warning matching a player pattern (usually SteamID string).
/// </summary>
/// <param name="playerPattern">The pattern identifying the player whose last warning should be removed.</param>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task UnwarnPlayer(string playerPattern)
{
if (databaseProvider == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetUnwarnLastQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { steamid = playerPattern, serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical("Unable to remove last warn {exception}", ex.Message);
}
}
/// <summary>
/// Expires old warnings based on the current time, removing or marking them as inactive.
/// </summary>
/// <returns>A task representing the asynchronous operation.</returns>
public async Task ExpireOldWarns()
{
if (databaseProvider == null) return;
try
{
await using var connection = await databaseProvider.CreateConnectionAsync();
var sql = databaseProvider.GetExpireWarnsQuery(CS2_SimpleAdmin.Instance.Config.MultiServerMode);
await connection.ExecuteAsync(sql, new { CurrentTime = Time.ActualDateTime(), serverid = CS2_SimpleAdmin.ServerId });
}
catch (Exception ex)
{
CS2_SimpleAdmin._logger?.LogCritical($"Unable to remove expired warns + {ex}");
}
}
}

View File

@@ -0,0 +1,70 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Menu;
namespace CS2_SimpleAdmin.Menus;
public static class AdminMenu
{
public static IMenu? CreateMenu(string title, Action<CCSPlayerController>? backAction = null)
{
return Helper.CreateMenu(title, backAction);
// return CS2_SimpleAdmin.Instance.Config.UseChatMenu ? new ChatMenu(title) : new CenterHtmlMenu(title, CS2_SimpleAdmin.Instance);
}
public static void OpenMenu(CCSPlayerController player, IMenu menu)
{
menu.Open(player);
// switch (menu)
// {
// case CenterHtmlMenu centerHtmlMenu:
// MenuManager.OpenCenterHtmlMenu(CS2_SimpleAdmin.Instance, player, centerHtmlMenu);
// break;
// case ChatMenu chatMenu:
// MenuManager.OpenChatMenu(player, chatMenu);
// break;
// }
}
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = CreateMenu(localizer?["sa_title"] ?? "SimpleAdmin");
List<ChatMenuOptionData> options =
[
new(localizer?["sa_menu_players_manage"] ?? "Players Manage", () => ManagePlayersMenu.OpenMenu(admin)),
new(localizer?["sa_menu_server_manage"] ?? "Server Manage", () => ManageServerMenu.OpenMenu(admin)),
new(localizer?["sa_menu_fun_commands"] ?? "Fun Commands", () => FunActionsMenu.OpenMenu(admin)),
];
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
if (customCommands.Count > 0)
{
options.Add(new ChatMenuOptionData(localizer?["sa_menu_custom_commands"] ?? "Custom Commands", () => CustomCommandsMenu.OpenMenu(admin)));
}
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root"))
options.Add(new ChatMenuOptionData(localizer?["sa_menu_admins_manage"] ?? "Admins Manage", () => ManageAdminsMenu.OpenMenu(admin)));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) OpenMenu(admin, menu);
}
}

View File

@@ -0,0 +1,8 @@
namespace CS2_SimpleAdmin.Menus;
public class ChatMenuOptionData(string name, Action action, bool disabled = false)
{
public readonly string Name = name;
public readonly Action Action = action;
public readonly bool Disabled = disabled;
}

View File

@@ -0,0 +1,51 @@
using CounterStrikeSharp.API;
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
public static class CustomCommandsMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_custom_commands"] ?? "Custom Commands");
List<ChatMenuOptionData> options = [];
var customCommands = CS2_SimpleAdmin.Instance.Config.CustomServerCommands;
options.AddRange(from customCommand in customCommands
where !string.IsNullOrEmpty(customCommand.DisplayName) && !string.IsNullOrEmpty(customCommand.Command)
let hasRights = AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), customCommand.Flag)
where hasRights
select new ChatMenuOptionData(customCommand.DisplayName, () =>
{
Helper.TryLogCommandOnDiscord(admin, customCommand.Command);
if (customCommand.ExecuteOnClient)
admin.ExecuteClientCommandFromServer(customCommand.Command);
else
Server.ExecuteCommand(customCommand.Command);
}));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
}

View File

@@ -0,0 +1,32 @@
using CounterStrikeSharp.API.Core;
using CS2_SimpleAdmin.Models;
namespace CS2_SimpleAdmin.Menus;
public static class DurationMenu
{
public static void OpenMenu(CCSPlayerController admin, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, int> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
{
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
public static void OpenMenu(CCSPlayerController admin, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, int> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
foreach (var durationItem in CS2_SimpleAdmin.Instance.Config.MenuConfigs.Durations)
{
menu?.AddMenuOption(durationItem.Name, (_, _) => { onSelectAction(admin, player, durationItem.Duration); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
}

View File

@@ -0,0 +1,267 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Entities.Constants;
namespace CS2_SimpleAdmin.Menus;
public static class FunActionsMenu
{
private static Dictionary<int, CsItem>? _weaponsCache;
private static Dictionary<int, CsItem> GetWeaponsCache
{
get
{
if (_weaponsCache != null) return _weaponsCache;
var weaponsArray = Enum.GetValues(typeof(CsItem));
// avoid duplicates in the menu
_weaponsCache = new Dictionary<int, CsItem>();
foreach (CsItem item in weaponsArray)
{
if (item == CsItem.Tablet)
continue;
_weaponsCache[(int)item] = item;
}
return _weaponsCache;
}
}
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_fun_commands"] ?? "Fun Commands");
List<ChatMenuOptionData> options = [];
//var hasCheats = AdminManager.PlayerHasPermissions(admin, "@css/cheats");
//var hasSlay = AdminManager.PlayerHasPermissions(admin, "@css/slay");
// options added in order
if (AdminManager.CommandIsOverriden("css_god")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_god"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_godmode"] ?? "God Mode", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_godmode"] ?? "God Mode", GodMode)));
if (AdminManager.CommandIsOverriden("css_noclip")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_noclip"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_noclip"] ?? "No Clip", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_noclip"] ?? "No Clip", NoClip)));
if (AdminManager.CommandIsOverriden("css_respawn")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_respawn"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_respawn"] ?? "Respawn", () => PlayersMenu.OpenDeadMenu(admin, localizer?["sa_respawn"] ?? "Respawn", Respawn)));
if (AdminManager.CommandIsOverriden("css_give")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_give"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/cheats"))
options.Add(new ChatMenuOptionData(localizer?["sa_give_weapon"] ?? "Give Weapon", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_give_weapon"] ?? "Give Weapon", GiveWeaponMenu)));
if (AdminManager.CommandIsOverriden("css_strip")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_strip"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_strip_weapons"] ?? "Strip Weapons", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_strip_weapons"] ?? "Strip Weapons", StripWeapons)));
if (AdminManager.CommandIsOverriden("css_freeze")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_freeze"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_freeze"] ?? "Freeze", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_freeze"] ?? "Freeze", Freeze)));
if (AdminManager.CommandIsOverriden("css_hp")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_hp"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_hp"] ?? "Set Hp", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_hp"] ?? "Set Hp", SetHpMenu)));
if (AdminManager.CommandIsOverriden("css_speed")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_speed"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_speed"] ?? "Set Speed", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_speed"] ?? "Set Speed", SetSpeedMenu)));
if (AdminManager.CommandIsOverriden("css_gravity")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gravity"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_gravity"] ?? "Set Gravity", () => PlayersMenu.OpenAliveMenu(admin, localizer?["sa_set_gravity"] ?? "Set Gravity", SetGravityMenu)));
if (AdminManager.CommandIsOverriden("css_money")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_money"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay"))
options.Add(new ChatMenuOptionData(localizer?["sa_set_money"] ?? "Set Money", () => PlayersMenu.OpenMenu(admin, localizer?["sa_set_money"] ?? "Set Money", SetMoneyMenu)));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void GodMode(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.God(admin, player);
}
private static void NoClip(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.NoClip(admin, player);
}
private static void Respawn(CCSPlayerController? admin, CCSPlayerController player)
{
CS2_SimpleAdmin.Respawn(admin, player);
}
private static void GiveWeaponMenu(CCSPlayerController admin, CCSPlayerController player)
{
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_give_weapon"] ?? "Give Weapon"}: {player.PlayerName}");
foreach (var weapon in GetWeaponsCache)
{
menu?.AddMenuOption(weapon.Value.ToString(), (_, _) => { GiveWeapon(admin, player, weapon.Value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void GiveWeapon(CCSPlayerController admin, CCSPlayerController player, CsItem weaponValue)
{
CS2_SimpleAdmin.GiveWeapon(admin, player, weaponValue);
}
private static void StripWeapons(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.StripWeapons(admin, player);
}
private static void Freeze(CCSPlayerController admin, CCSPlayerController player)
{
if (!(player.PlayerPawn.Value?.IsValid ?? false))
return;
if (player.PlayerPawn.Value.MoveType != MoveType_t.MOVETYPE_INVALID)
CS2_SimpleAdmin.Freeze(admin, player, -1);
else
CS2_SimpleAdmin.Unfreeze(admin, player);
}
private static void SetHpMenu(CCSPlayerController admin, CCSPlayerController player)
{
var hpArray = new[]
{
new Tuple<string, int>("1", 1),
new Tuple<string, int>("10", 10),
new Tuple<string, int>("25", 25),
new Tuple<string, int>("50", 50),
new Tuple<string, int>("100", 100),
new Tuple<string, int>("200", 200),
new Tuple<string, int>("500", 500),
new Tuple<string, int>("999", 999)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_hp"] ?? "Set Hp"}: {player.PlayerName}");
foreach (var (optionName, value) in hpArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetHp(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetHp(CCSPlayerController admin, CCSPlayerController player, int hp)
{
CS2_SimpleAdmin.SetHp(admin, player, hp);
}
private static void SetSpeedMenu(CCSPlayerController admin, CCSPlayerController player)
{
var speedArray = new[]
{
new Tuple<string, float>("0.1", .1f),
new Tuple<string, float>("0.25", .25f),
new Tuple<string, float>("0.5", .5f),
new Tuple<string, float>("0.75", .75f),
new Tuple<string, float>("1", 1),
new Tuple<string, float>("2", 2),
new Tuple<string, float>("3", 3),
new Tuple<string, float>("4", 4)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_speed"] ?? "Set Speed"}: {player.PlayerName}");
foreach (var (optionName, value) in speedArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetSpeed(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetSpeed(CCSPlayerController admin, CCSPlayerController player, float speed)
{
CS2_SimpleAdmin.SetSpeed(admin, player, speed);
}
private static void SetGravityMenu(CCSPlayerController admin, CCSPlayerController player)
{
var gravityArray = new[]
{
new Tuple<string, float>("0.1", .1f),
new Tuple<string, float>("0.25", .25f),
new Tuple<string, float>("0.5", .5f),
new Tuple<string, float>("0.75", .75f),
new Tuple<string, float>("1", 1),
new Tuple<string, float>("2", 2)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_gravity"] ?? "Set Gravity"}: {player.PlayerName}");
foreach (var (optionName, value) in gravityArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetGravity(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetGravity(CCSPlayerController admin, CCSPlayerController player, float gravity)
{
CS2_SimpleAdmin.SetGravity(admin, player, gravity);
}
private static void SetMoneyMenu(CCSPlayerController admin, CCSPlayerController player)
{
var moneyArray = new[]
{
new Tuple<string, int>("$0", 0),
new Tuple<string, int>("$1000", 1000),
new Tuple<string, int>("$2500", 2500),
new Tuple<string, int>("$5000", 5000),
new Tuple<string, int>("$10000", 10000),
new Tuple<string, int>("$16000", 16000)
};
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_set_money"] ?? "Set Money"}: {player.PlayerName}");
foreach (var (optionName, value) in moneyArray)
{
menu?.AddMenuOption(optionName, (_, _) => { SetMoney(admin, player, value); });
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SetMoney(CCSPlayerController admin, CCSPlayerController player, int money)
{
CS2_SimpleAdmin.SetMoney(admin, player, money);
}
}

View File

@@ -0,0 +1,72 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
public static class ManageAdminsMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_admins_manage"] ?? "Admins Manage");
List<ChatMenuOptionData> options =
[
new(localizer?["sa_admin_add"] ?? "Add Admin",
() => PlayersMenu.OpenRealPlayersMenu(admin, localizer?["sa_admin_add"] ?? "Add Admin", AddAdminMenu)),
new(localizer?["sa_admin_remove"] ?? "Remove Admin",
() => PlayersMenu.OpenAdminPlayersMenu(admin, localizer?["sa_admin_remove"] ?? "Remove Admin", RemoveAdmin,
player => player != admin && admin.CanTarget(player))),
new(localizer?["sa_admin_reload"] ?? "Reload Admins", () => ReloadAdmins(admin))
];
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void AddAdminMenu(CCSPlayerController admin, CCSPlayerController player)
{
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_admin_add"] ?? "Add Admin"}: {player.PlayerName}");
foreach (var adminFlag in CS2_SimpleAdmin.Instance.Config.MenuConfigs.AdminFlags)
{
var disabled = AdminManager.PlayerHasPermissions(player, adminFlag.Flag);
menu?.AddMenuOption(adminFlag.Name, (_, _) => { AddAdmin(admin, player, adminFlag.Flag); }, disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void AddAdmin(CCSPlayerController admin, CCSPlayerController player, string flag)
{
// TODO: Change default immunity?
CS2_SimpleAdmin.AddAdmin(admin, player.SteamID.ToString(), player.PlayerName, flag, 10);
}
private static void RemoveAdmin(CCSPlayerController admin, CCSPlayerController player)
{
CS2_SimpleAdmin.Instance.RemoveAdmin(admin, player.SteamID.ToString());
}
private static void ReloadAdmins(CCSPlayerController admin)
{
CS2_SimpleAdmin.Instance.ReloadAdmins(admin);
}
}

View File

@@ -0,0 +1,336 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
using CounterStrikeSharp.API.Modules.Utils;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin.Menus;
public static class ManagePlayersMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_players_manage"] ?? "Manage Players");
List<ChatMenuOptionData> options = [];
// permissions
var hasSlay = AdminManager.CommandIsOverriden("css_slay") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_slay")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/slay");
var hasKick = AdminManager.CommandIsOverriden("css_kick") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_kick")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick");
var hasBan = AdminManager.CommandIsOverriden("css_ban") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_ban")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/ban");
var hasChat = AdminManager.CommandIsOverriden("css_gag") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/chat");
// TODO: Localize options
// options added in order
if (hasSlay)
{
options.Add(new ChatMenuOptionData(localizer?["sa_slap"] ?? "Slap", () => PlayersMenu.OpenMenu(admin, localizer?["sa_slap"] ?? "Slap", SlapMenu)));
options.Add(new ChatMenuOptionData(localizer?["sa_slay"] ?? "Slay", () => PlayersMenu.OpenMenu(admin, localizer?["sa_slay"] ?? "Slay", Slay)));
}
if (hasKick)
{
options.Add(new ChatMenuOptionData(localizer?["sa_kick"] ?? "Kick", () => PlayersMenu.OpenMenu(admin, localizer?["sa_kick"] ?? "Kick", KickMenu)));
}
if (AdminManager.CommandIsOverriden("css_warn")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_warn"))
: 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))));
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))));
if (hasChat)
{
if (AdminManager.CommandIsOverriden("css_gag")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_gag"))
: 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))));
if (AdminManager.CommandIsOverriden("css_mute")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_mute"))
: 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))));
if (AdminManager.CommandIsOverriden("css_silence")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_silence"))
: 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))));
}
if (AdminManager.CommandIsOverriden("css_team")
? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_team"))
: AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/kick"))
options.Add(new ChatMenuOptionData(localizer?["sa_team_force"] ?? "Force Team", () => PlayersMenu.OpenMenu(admin, localizer?["sa_team_force"] ?? "Force Team", ForceTeamMenu)));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void SlapMenu(CCSPlayerController admin, CCSPlayerController player)
{
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_slap"] ?? "Slap"}: {player.PlayerName}");
List<ChatMenuOptionData> options =
[
// options added in order
new("0 hp", () => ApplySlapAndKeepMenu(admin, player, 0)),
new("1 hp", () => ApplySlapAndKeepMenu(admin, player, 1)),
new("5 hp", () => ApplySlapAndKeepMenu(admin, player, 5)),
new("10 hp", () => ApplySlapAndKeepMenu(admin, player, 10)),
new("50 hp", () => ApplySlapAndKeepMenu(admin, player, 50)),
new("100 hp", () => ApplySlapAndKeepMenu(admin, player, 100)),
];
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void ApplySlapAndKeepMenu(CCSPlayerController admin, CCSPlayerController player, int damage)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Slap(admin, player, damage);
SlapMenu(admin, player);
}
private static void Slay(CCSPlayerController admin, CCSPlayerController player)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Slay(admin, player);
}
private static void KickMenu(CCSPlayerController admin, CCSPlayerController player)
{
ReasonMenu.OpenMenu(admin, PenaltyType.Kick,
$"{CS2_SimpleAdmin._localizer?["sa_kick"] ?? "Kick"}: {player.PlayerName}", player, (_, _, reason) =>
{
if (player is { IsValid: true })
Kick(admin, player, reason);
});
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_kick"] ?? "Kick"}: {player?.PlayerName}");
//
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons)
// {
// menu?.AddMenuOption(option, (_, _) =>
// {
// if (player is { IsValid: true })
// Kick(admin, player, option);
// });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void Kick(CCSPlayerController admin, CCSPlayerController player, string? reason)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Kick(admin, player, reason, admin.PlayerName);
}
internal static void BanMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
{
ReasonMenu.OpenMenu(admin, PenaltyType.Ban,
$"{CS2_SimpleAdmin._localizer?["sa_ban"] ?? "Ban"}: {player.PlayerName}", player, (_, _, reason) =>
{
if (player is { IsValid: true })
Ban(admin, player, duration, reason);
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)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Ban(admin, player, duration, reason, admin.PlayerName);
}
private static void WarnMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
{
ReasonMenu.OpenMenu(admin, PenaltyType.Warn,
$"{CS2_SimpleAdmin._localizer?["sa_warn"] ?? "Warn"}: {player.PlayerName}", player, (_, _, reason) =>
{
if (player is { IsValid: true })
Warn(admin, player, duration, reason);
});
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_warn"] ?? "Warn"}: {player?.PlayerName}");
//
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons)
// {
// menu?.AddMenuOption(option, (_, _) =>
// {
// if (player is { IsValid: true })
// Warn(admin, player, duration, option);
// });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void Warn(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Warn(admin, player, duration, reason, admin.PlayerName);
}
internal static void GagMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
{
ReasonMenu.OpenMenu(admin, PenaltyType.Gag,
$"{CS2_SimpleAdmin._localizer?["sa_gag"] ?? "Gag"}: {player.PlayerName}", player, (_, _, reason) =>
{
if (player is { IsValid: true })
Gag(admin, player, duration, reason);
});
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_gag"] ?? "Gag"}: {player?.PlayerName}");
//
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons)
// {
// menu?.AddMenuOption(option, (_, _) =>
// {
// if (player is { IsValid: true })
// Gag(admin, player, duration, option);
// });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void Gag(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Gag(admin, player, duration, reason);
}
internal static void MuteMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
{
ReasonMenu.OpenMenu(admin, PenaltyType.Mute,
$"{CS2_SimpleAdmin._localizer?["sa_mute"] ?? "mute"}: {player.PlayerName}", player, (_, _, reason) =>
{
if (player is { IsValid: true })
Mute(admin, player, duration, reason);
});
// // TODO: Localize and make options in config?
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_mute"] ?? "Mute"}: {player?.PlayerName}");
//
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons)
// {
// menu?.AddMenuOption(option, (_, _) =>
// {
// if (player is { IsValid: true })
// Mute(admin, player, duration, option);
// });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void Mute(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Mute(admin, player, duration, reason);
}
internal static void SilenceMenu(CCSPlayerController admin, CCSPlayerController player, int duration)
{
ReasonMenu.OpenMenu(admin, PenaltyType.Silence,
$"{CS2_SimpleAdmin._localizer?["sa_silence"] ?? "Silence"}: {player.PlayerName}", player, (_, _, reason) =>
{
if (player is { IsValid: true })
Silence(admin, player, duration, reason);
});
// // TODO: Localize and make options in config?
// var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_silence"] ?? "Silence"}: {player?.PlayerName}");
//
// foreach (var option in CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons)
// {
// menu?.AddMenuOption(option, (_, _) =>
// {
// if (player is { IsValid: true })
// Silence(admin, player, duration, option);
// });
// }
//
// if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void Silence(CCSPlayerController admin, CCSPlayerController player, int duration, string reason)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.Instance.Silence(admin, player, duration, reason);
}
private static void ForceTeamMenu(CCSPlayerController admin, CCSPlayerController player)
{
// TODO: Localize
var menu = AdminMenu.CreateMenu($"{CS2_SimpleAdmin._localizer?["sa_team_force"] ?? "Force Team"} {player.PlayerName}");
List<ChatMenuOptionData> options =
[
new(CS2_SimpleAdmin._localizer?["sa_team_ct"] ?? "CT", () => ForceTeam(admin, player, "ct", CsTeam.CounterTerrorist)),
new(CS2_SimpleAdmin._localizer?["sa_team_t"] ?? "T", () => ForceTeam(admin, player, "t", CsTeam.Terrorist)),
new(CS2_SimpleAdmin._localizer?["sa_team_swap"] ?? "Swap", () => ForceTeam(admin, player, "swap", CsTeam.Spectator)),
new(CS2_SimpleAdmin._localizer?["sa_team_spec"] ?? "Spec", () => ForceTeam(admin, player, "spec", CsTeam.Spectator)),
];
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void ForceTeam(CCSPlayerController admin, CCSPlayerController player, string teamName, CsTeam teamNum)
{
if (player is not { IsValid: true }) return;
CS2_SimpleAdmin.ChangeTeam(admin, player, teamName, teamNum, true);
}
}

View File

@@ -0,0 +1,83 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Menus;
public static class ManageServerMenu
{
public static void OpenMenu(CCSPlayerController admin)
{
if (admin.IsValid == false)
return;
var localizer = CS2_SimpleAdmin._localizer;
if (AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/generic") == false)
{
admin.PrintToChat(localizer?["sa_prefix"] ??
"[SimpleAdmin] " +
(localizer?["sa_no_permission"] ?? "You do not have permissions to use this command")
);
return;
}
var menu = AdminMenu.CreateMenu(localizer?["sa_menu_server_manage"] ?? "Server Manage");
List<ChatMenuOptionData> options = [];
// permissions
var hasMap = AdminManager.CommandIsOverriden("css_map") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_map")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/changemap");
var hasPlugins = AdminManager.CommandIsOverriden("css_pluginsmanager") ? AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), AdminManager.GetPermissionOverrides("css_pluginsmanager")) : AdminManager.PlayerHasPermissions(new SteamID(admin.SteamID), "@css/root");
//bool hasMap = AdminManager.PlayerHasPermissions(admin, "@css/changemap");
// options added in order
if (hasPlugins)
{
options.Add(new ChatMenuOptionData(localizer?["sa_menu_pluginsmanager_title"] ?? "Manage Plugins", () => admin.ExecuteClientCommandFromServer("css_pluginsmanager")));
}
if (hasMap)
{
options.Add(new ChatMenuOptionData(localizer?["sa_changemap"] ?? "Change Map", () => ChangeMapMenu(admin)));
}
options.Add(new ChatMenuOptionData(localizer?["sa_restart_game"] ?? "Restart Game", () => CS2_SimpleAdmin.RestartGame(admin)));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void ChangeMapMenu(CCSPlayerController admin)
{
var menu = AdminMenu.CreateMenu(CS2_SimpleAdmin._localizer?["sa_changemap"] ?? "Change Map");
List<ChatMenuOptionData> options = [];
var maps = CS2_SimpleAdmin.Instance.Config.DefaultMaps;
options.AddRange(maps.Select(map => new ChatMenuOptionData(map, () => ExecuteChangeMap(admin, map, false))));
var wsMaps = CS2_SimpleAdmin.Instance.Config.WorkshopMaps;
options.AddRange(wsMaps.Select(map => new ChatMenuOptionData($"{map.Key} (WS)", () => ExecuteChangeMap(admin, map.Value?.ToString() ?? map.Key, true))));
foreach (var menuOptionData in options)
{
var menuName = menuOptionData.Name;
menu?.AddMenuOption(menuName, (_, _) => { menuOptionData.Action.Invoke(); }, menuOptionData.Disabled);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
private static void ExecuteChangeMap(CCSPlayerController admin, string mapName, bool workshop)
{
if (workshop)
CS2_SimpleAdmin.Instance.ChangeWorkshopMap(admin, mapName);
else
CS2_SimpleAdmin.Instance.ChangeMap(admin, mapName);
}
}

View File

@@ -0,0 +1,55 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Modules.Admin;
using System.Web;
namespace CS2_SimpleAdmin.Menus;
public static class PlayersMenu
{
public static void OpenRealPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => p.IsBot == false);
}
public static void OpenAdminPlayersMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController?, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => AdminManager.GetPlayerAdminData(p)?.Flags.Count > 0);
}
public static void OpenAliveMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState == (int)LifeState_t.LIFE_ALIVE);
}
public static void OpenDeadMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController?, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
OpenMenu(admin, menuName, onSelectAction, p => p.PlayerPawn?.Value?.LifeState != (int)LifeState_t.LIFE_ALIVE);
}
public static void OpenMenu(CCSPlayerController admin, string menuName, Action<CCSPlayerController, CCSPlayerController> onSelectAction, Func<CCSPlayerController, bool>? enableFilter = null)
{
var menu = AdminMenu.CreateMenu(menuName);
var players = Helper.GetValidPlayersWithBots();
foreach (var player in players)
{
var playerName = player != null && player.PlayerName.Length > 26 ? player.PlayerName[..26] : player?.PlayerName;
var optionName = HttpUtility.HtmlEncode(playerName);
if (player != null && enableFilter != null && enableFilter(player) == false)
continue;
var enabled = admin.CanTarget(player);
if (optionName != null)
menu?.AddMenuOption(optionName, (_, _) =>
{
if (player != null) onSelectAction.Invoke(admin, player);
},
enabled == false);
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
}

View File

@@ -0,0 +1,52 @@
using CounterStrikeSharp.API.Core;
using CS2_SimpleAdmin.Models;
using CS2_SimpleAdminApi;
namespace CS2_SimpleAdmin.Menus;
public static class ReasonMenu
{
public static void OpenMenu(CCSPlayerController admin, PenaltyType penaltyType, string menuName, CCSPlayerController player, Action<CCSPlayerController, CCSPlayerController, string> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
var reasons = penaltyType switch
{
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons,
PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons,
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons,
PenaltyType.Gag => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
PenaltyType.Silence => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
_ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons
};
foreach (var reason in reasons)
{
menu?.AddMenuOption(reason, (_, _) => onSelectAction(admin, player, reason));
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
public static void OpenMenu(CCSPlayerController admin, PenaltyType penaltyType, string menuName, DisconnectedPlayer player, Action<CCSPlayerController, DisconnectedPlayer, string> onSelectAction)
{
var menu = AdminMenu.CreateMenu(menuName);
var reasons = penaltyType switch
{
PenaltyType.Ban => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons,
PenaltyType.Kick => CS2_SimpleAdmin.Instance.Config.MenuConfigs.KickReasons,
PenaltyType.Mute => CS2_SimpleAdmin.Instance.Config.MenuConfigs.MuteReasons,
PenaltyType.Warn => CS2_SimpleAdmin.Instance.Config.MenuConfigs.WarnReasons,
_ => CS2_SimpleAdmin.Instance.Config.MenuConfigs.BanReasons
};
foreach (var reason in reasons)
{
menu?.AddMenuOption(reason, (_, _) => onSelectAction(admin, player, reason));
}
if (menu != null) AdminMenu.OpenMenu(admin, menu);
}
}

View File

@@ -0,0 +1,39 @@
using System.ComponentModel;
using System.ComponentModel.DataAnnotations.Schema;
namespace CS2_SimpleAdmin.Models;
public enum BanStatus
{
[Description("ACTIVE")] ACTIVE,
[Description("UNBANNED")] UNBANNED,
[Description("EXPIRED")] EXPIRED,
[Description("")] UNKNOWN
}
public record BanRecord
{
[Column("id")]
public int Id { get; init; }
[Column("player_name")]
public string? PlayerName { get; set; }
[Column("player_steamid")]
public ulong? PlayerSteamId { get; set; }
[Column("player_ip")]
public string? PlayerIp { get; set; }
[Column("status")]
public required string Status { get; init; }
[NotMapped]
public BanStatus StatusEnum => Status.ToUpper() switch
{
"ACTIVE" => BanStatus.ACTIVE,
"UNBANNED" => BanStatus.UNBANNED,
"EXPIRED" => BanStatus.EXPIRED,
_ => BanStatus.UNKNOWN
};
}

View File

@@ -0,0 +1,15 @@
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdmin.Models;
public class DisconnectedPlayer(
SteamID steamId,
string name,
string? ipAddress,
DateTime disconnectTime)
{
public SteamID SteamId { get; } = steamId;
public string Name { get; set; } = name;
public string? IpAddress { get; set; } = ipAddress;
public DateTime DisconnectTime = disconnectTime;
}

View File

@@ -0,0 +1,3 @@
namespace CS2_SimpleAdmin.Models;
public readonly record struct IpRecord(uint Ip, DateTime UsedAt, string PlayerName);

View File

@@ -0,0 +1,12 @@
namespace CS2_SimpleAdmin.Models;
public record PlayerStats(int Score, int Kills, int Deaths, int MVPs);
public record PlayerDto(
int UserId,
string Name,
string SteamId,
string IpAddress,
uint Ping,
bool IsAdmin,
PlayerStats Stats
);

1
CS2-SimpleAdmin/VERSION Normal file
View File

@@ -0,0 +1 @@
1.7.7-alpha-10

View File

@@ -0,0 +1,87 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Memory.DynamicFunctions;
using CS2_SimpleAdmin.Models;
using CS2_SimpleAdminApi;
using MenuManager;
using Microsoft.Extensions.Localization;
using Microsoft.Extensions.Logging;
using System.Collections.Concurrent;
using CS2_SimpleAdmin.Database;
using CS2_SimpleAdmin.Managers;
using Timer = CounterStrikeSharp.API.Modules.Timers.Timer;
namespace CS2_SimpleAdmin;
public partial class CS2_SimpleAdmin
{
// Config
public CS2_SimpleAdminConfig Config { get; set; } = new();
// HttpClient
internal static readonly HttpClient HttpClient = new();
// Paths
internal static readonly string ConfigDirectory =
Path.Combine(Application.RootDirectory, "configs/plugins/CS2-SimpleAdmin");
// Localization
public static IStringLocalizer? _localizer;
// Voting System
public static readonly Dictionary<string, int> VoteAnswers = [];
public static bool VoteInProgress;
// Command and Server Settings
public static readonly bool UnlockedCommands = CoreConfig.UnlockConCommands;
internal static string IpAddress = string.Empty;
internal static bool ServerLoaded;
internal static int? ServerId = null;
internal static readonly HashSet<ulong> AdminDisabledJoinComms = [];
// Player Management
private static readonly HashSet<int> GodPlayers = [];
internal static readonly HashSet<int> SilentPlayers = [];
internal static readonly Dictionary<ulong, string> RenamedPlayers = [];
internal static readonly ConcurrentDictionary<ulong, PlayerInfo> PlayersInfo = [];
internal static readonly List<CCSPlayerController> CachedPlayers = [];
internal static readonly List<CCSPlayerController> BotPlayers = [];
private static readonly List<DisconnectedPlayer> DisconnectedPlayers = [];
// Discord Integration
internal static DiscordManager? DiscordWebhookClientLog;
// Database Settings
internal string DbConnectionString = string.Empty;
// internal static Database.Database? Database;
internal static IDatabaseProvider? DatabaseProvider;
// Logger
internal static ILogger? _logger;
// Memory Function (Game-related)
private static MemoryFunctionVoid<CBasePlayerController, CCSPlayerPawn, bool, bool>?
_cBasePlayerControllerSetPawnFunc;
// Menu API and Capabilities
internal static IMenuApi? MenuApi;
private static readonly PluginCapability<IMenuApi> MenuCapability = new("menu:nfcore");
// Shared API
internal static Api.CS2_SimpleAdminApi? SimpleAdminApi { get; private set; }
// Managers
internal PermissionManager PermissionManager = new(DatabaseProvider);
internal BanManager BanManager = new(DatabaseProvider);
internal MuteManager MuteManager = new(DatabaseProvider);
internal WarnManager WarnManager = new(DatabaseProvider);
internal CacheManager? CacheManager = new();
private static readonly PlayerManager PlayerManager = new();
// Timers
internal Timer? PlayersTimer = null;
// Funny list
private readonly List<string> _requiredPlugins = ["MenuManagerCore", "PlayerSettings"];
private readonly List<string> _requiredShared = ["MenuManagerApi", "PlayerSettingsApi", "AnyBaseLib", "CS2-SimpleAdminApi"];
}

View File

@@ -0,0 +1,39 @@
{GREEN}[ CS2-SimpleAdmin HELP ]{DEFAULT}
- css_who <#userid or name> - Display informations about player
- css_players - Display player list
- css_ban <#userid or name> [time in minutes/0 perm] [reason] - Ban player
- css_addban <steamid> [time in minutes/0 perm] [reason] - Ban player via steamid64
- css_banip <ip> [time in minutes/0 perm] [reason] - Ban player via IP address
- css_unban <steamid or name or ip> - Unban player
- css_kick <#userid or name> [reason] - Kick player
- css_gag <#userid or name> [time in minutes/0 perm] [reason] - Gag player
- css_addgag <steamid> [time in minutes/0 perm] [reason] - Gag player via steamid64
- css_unmute <steamid or name> - Ungag player
- css_mute <#userid or name> [time in minutes/0 perm] [reason] - Mute player
- css_addmute <steamid> [time in minutes/0 perm] [reason] - Mute player via steamid64
- css_give <#userid or name> <weapon> - Give player a weapon
- css_strip <#userid or name> <weapon> - Takes all of the player weapons
- css_hp <#userid or name> [health] - Set player health
- css_speed <#userid or name> [speed] - Set player speed
- css_gravity <#userid or name> [gravity] - Set player gravity
- css_money <#userid or name> [money] - Set player money
- css_god <#userid or name> - Toggle player godmode
- css_slay <#userid or name> - Kill player
- css_slap <#userid or name> [damage] - Slap player
- css_vote <'Question?'> ['Answer1'] ['Answer2'] ... - Create vote
- css_map <mapname> - Change map
- css_wsmap <name or id> - Change workshop map
- css_asay <message> - Say message to all admins
- css_say <message> - Say message as admin in chat
- css_psay <#userid or name> <message> - Sends private message to player
- css_csay <message> - Say message as admin in center
- css_hsay <message> - Say message as admin in hud
- css_noclip <#userid or name> - Toggle noclip for player
- css_freeze <#userid or name> [duration] - Freeze player
- css_unfreeze <#userid or name> - Unfreeze player
- css_respawn <#userid or name> - Respawn player
- css_cvar <cvar> <value> - Change cvar value
- css_rcon <command> - Run command as server
{Green}This is a sample admin_help.txt file
{LightRed}Write all useful information for admins here

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "مجهول",
"sa_no_permission": "ليس لديك الصلاحيات لاستخدام هذا الأمر.",
"sa_ban_max_duration_exceeded": "مدة الحظر لا يمكن أن تتجاوز {lightred}{0}{default} دقيقة.",
"sa_ban_perm_restricted": "ليس لديك الحق في الحظر الدائم.",
"sa_admin_add": "إضافة مسؤول",
"sa_admin_remove": "إزالة المسؤول",
"sa_admin_reload": "إعادة تحميل المسؤولين",
"sa_godmode": "وضع الإله",
"sa_noclip": "بدون قصاصات",
"sa_respawn": "إعادة الظهور",
"sa_give_weapon": "إعطاء سلاح",
"sa_strip_weapons": "تجريد الأسلحة",
"sa_freeze": "تجميد",
"sa_set_hp": "تعيين الصحة",
"sa_set_speed": "تعيين السرعة",
"sa_set_gravity": "تعيين الجاذبية",
"sa_set_money": "تعيين المال",
"sa_changemap": "تغيير الخريطة",
"sa_restart_game": "إعادة تشغيل اللعبة",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "تبديل",
"sa_team_spec": "المشاهدة",
"sa_slap": "صفعة",
"sa_slay": "قتل",
"sa_kick": "طرد",
"sa_ban": "حظر",
"sa_gag": "كتم",
"sa_mute": "كتم",
"sa_silence": "صمت",
"sa_warn": "تحذير",
"sa_team_force": "فرض الفريق",
"sa_menu_custom_commands": "الأوامر المخصصة",
"sa_menu_server_manage": "إدارة الخادم",
"sa_menu_fun_commands": "أوامر ممتعة",
"sa_menu_admins_manage": "إدارة المسؤولين",
"sa_menu_players_manage": "إدارة اللاعبين",
"sa_menu_disconnected_title": "اللاعبون الأخيرون",
"sa_menu_disconnected_action_title": "اختر الإجراء",
"sa_menu_pluginsmanager_title": "إدارة الإضافات",
"sa_player": "اللاعب",
"sa_console": "وحدة التحكم",
"sa_steamid": "معرف البخار",
"sa_duration": "المدة",
"sa_reason": "السبب",
"sa_admin": "المشرف",
"sa_permanent": "دائم",
"sa_discord_penalty_ban": "الحظر مسجل",
"sa_discord_penalty_mute": "الكتم مسجل",
"sa_discord_penalty_gag": "الصمت مسجل",
"sa_discord_penalty_silence": "الصمت مسجل",
"sa_discord_penalty_warn": "التحذير مسجل",
"sa_discord_penalty_unknown": "غير معروف مسجل",
"sa_player_penalty_chat_active": "{lightred}تم حظر الدردشة الخاصة بك إلى: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ كتم [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ صمت [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ سكوت [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ تحذير [{lightred}❌{default}] - ينتهي [{lightred}{0}{default}] - السبب [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ كتم [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ صمت [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ سكوت [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ تحذير [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nعقوبات اللاعبين لـ {lightred}{0}{default},\nعدد الحظر: {lightred}{1}{default}, عدد الصمت: {lightred}{2}{default}, عدد الكتم: {lightred}{3}{default}, عدد السكوت: {lightred}{4}{default}, عدد التحذيرات: {lightred}{5}{default}\nالعقوبات النشطة:\n{6}\nالتحذيرات النشطة:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}عقوبات اللاعبين لـ {lightred}{0}{grey}, حظر: {lightred}{1}{grey}, صمت: {lightred}{2}{grey}, كتم: {lightred}{3}{grey}, سكوت: {lightred}{4}{grey}, تحذيرات: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}الحسابات المرتبطة باللاعب {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "تم حظرك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!",
"sa_player_ban_message_perm": "تم حظرك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
"sa_player_kick_message": "تم طردك لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
"sa_player_gag_message_time": "تم تكميم فمك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!",
"sa_player_gag_message_perm": "تم تكميم فمك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
"sa_player_mute_message_time": "تم كتم صوتك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!",
"sa_player_mute_message_perm": "تم كتم صوتك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
"sa_player_silence_message_time": "تم إسكاتك لمدة {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة من قبل {lightred}{2}{default}!",
"sa_player_silence_message_perm": "تم إسكاتك بشكل دائم لمدة {lightred}{0}{default} من قبل {lightred}{1}{default}!",
"sa_player_warn_message_time": "لقد تم تحذيرك بسبب {lightred}{0}{default} لمدة {lightred}{1}{default} دقيقة بواسطة {lightred}{2}{default}!",
"sa_player_warn_message_perm": "لقد تم تحذيرك بشكل دائم بسبب {lightred}{0}{default} بواسطة {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} حظر {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} حظر {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} طرد {lightred}{1}{default} بسبب {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} كتم {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} كتم {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} أسكت {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} أسكت {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} أسكت {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} أسكت {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} حذر {lightred}{1}{default} لمدة {lightred}{3}{default} دقائق بسبب {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} حذر {lightred}{1}{default} بشكل دائم بسبب {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} أعطى {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} أخذ جميع أسلحة اللاعب {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} غيّر عدد نقاط الحياة لـ {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} غيّر السرعة لـ {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} غيّر الجاذبية لـ {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} غيّر المال لـ {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} غيّر وضع الله لـ {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} قام بتغيير الحجم لـ {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} قتل {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} صفع {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} غيّر الخريطة إلى {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} فعّل/ألغى نمط اللا تصادم لـ {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} جمد {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} أذاب {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} غيّر اسم {lightred}{1}{default} إلى {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} أحيى {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} نقل إلى {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} نقل إلى نفسه {lightred}{1}{default}!",
"sa_admin_team_message": "{lightred}{0}{default} نقل {lightred}{1}{default} إلى {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}تحذيرات",
"sa_admin_warns_unwarn": "{lime}تم إلغاء التحذير بنجاح{default} لـ {gold}{0} {default}بسبب {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}تصويت لـ {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} بدأ التصويت لـ {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}نتائج التصويت لـ {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}لقد قمت بكتم {default}جميع اللاعبين أثناء حديثك",
"sa_admin_voice_unmute_all": "{Lime}لقد ألغيت كتم {default}جميع اللاعبين",
"sa_admin_voice_listen_all": "{Default}أنت الآن تسمع {lime}جميع {default}اللاعبين",
"sa_admin_voice_unlisten_all": "{Default}أنت الآن لا تسمع {lime}جميع {default}اللاعبين",
"sa_adminsay_prefix": "{RED}الإداري: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(إداري) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(لاعب) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** أصدر الأمر `{1}` على الخادم `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}مفعل {default}الإضافة {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}معطل {default}الإضافة {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Unbekannt",
"sa_no_permission": "Du hast keine Berechtigung zur Verwendung dieses Befehls.",
"sa_ban_max_duration_exceeded": "Die Dauer des Banns darf {lightred}{0}{default} Minuten nicht überschreiten.",
"sa_ban_perm_restricted": "Du hast nicht die Berechtigung, einen permanenten Bann auszusprechen.",
"sa_admin_add": "Admin hinzufügen",
"sa_admin_remove": "Admin entfernen",
"sa_admin_reload": "Admins neuladen",
"sa_godmode": "Gottmodus",
"sa_noclip": "No Clip",
"sa_respawn": "Wiederbeleben",
"sa_give_weapon": "Waffe gegeben",
"sa_strip_weapons": "Waffen abnehmen",
"sa_freeze": "Einfrieren",
"sa_set_hp": "Lp setzen",
"sa_set_speed": "Geschwindigkeit setzen",
"sa_set_gravity": "Gravitation setzen",
"sa_set_money": "Geld setzen",
"sa_changemap": "Map wechseln",
"sa_restart_game": "Spiel neustarten",
"sa_team_ct": "AT",
"sa_team_t": "T",
"sa_team_swap": "Wechseln",
"sa_team_spec": "Zuschauer",
"sa_slap": "Klaps",
"sa_slay": "töten",
"sa_kick": "Kicken",
"sa_ban": "Bann",
"sa_gag": "Chat stummschalten",
"sa_mute": "Sprachchat stummschalten",
"sa_silence": "Komplett stummschalten",
"sa_warn": "Warnen",
"sa_team_force": "Team zuweisen",
"sa_menu_custom_commands": "Eigene Befehle",
"sa_menu_server_manage": "Server Verwalten",
"sa_menu_fun_commands": "Spaß Befehle",
"sa_menu_admins_manage": "Admins verwalten",
"sa_menu_players_manage": "Spieler verwalten",
"sa_menu_disconnected_title": "Letzte Spieler",
"sa_menu_disconnected_action_title": "Aktion auswählen",
"sa_menu_pluginsmanager_title": "Plugins verwalten",
"sa_player": "Spieler",
"sa_console": "Konsole",
"sa_steamid": "SteamID",
"sa_duration": "Dauer",
"sa_reason": "Grund",
"sa_admin": "Admin",
"sa_permanent": "Permanent",
"sa_discord_penalty_ban": "Bann registriert",
"sa_discord_penalty_mute": "Chat-Stummschaltung registriert",
"sa_discord_penalty_gag": "Sprachchat-Stummschaltung registriert",
"sa_discord_penalty_silence": "Komplett-Stummschaltung registriert",
"sa_discord_penalty_warn": "Warnung registriert",
"sa_discord_penalty_unknown": "Unbekanntes registriert",
"sa_player_penalty_chat_active": "{lightred}Dein Chat ist blockiert für: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Stummschaltung [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Mundtot [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Stille [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Warnung [{lightred}❌{default}] - Ablauf [{lightred}{0}{default}] - Grund [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Stummschaltung [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Mundtot [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Stille [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Warnung [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nSpielerstrafe für {lightred}{0}{default},\nAnzahl der Sperren: {lightred}{1}{default}, Anzahl der Mundtot: {lightred}{2}{default}, Anzahl der Stummschaltungen: {lightred}{3}{default}, Anzahl der Stille: {lightred}{4}{default}, Anzahl der Warnungen: {lightred}{5}{default}\nAktive Strafen:\n{6}\nAktive Warnungen:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Spielerstrafe für {lightred}{0}{grey}, Sperren: {lightred}{1}{grey}, Mundtot: {lightred}{2}{grey}, Stummschaltungen: {lightred}{3}{grey}, Stille: {lightred}{4}{grey}, Warnungen: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Verknüpfte Konten des Spielers {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gebannt!",
"sa_player_ban_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent gebannt!",
"sa_player_kick_message": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} gekickt!",
"sa_player_gag_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} im Chat stummgeschaltet!",
"sa_player_gag_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent im Chat stummgeschaltet!",
"sa_player_mute_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} im Sprachchat stummgeschaltet!",
"sa_player_mute_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent im Sprachchat stummgeschaltet!",
"sa_player_silence_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} vollständig stummgeschaltet!",
"sa_player_silence_message_perm": "Du wurdest wegen {lightred}{0}{default} von {lightred}{1}{default} permanent vollständig stummgeschaltet!",
"sa_player_warn_message_time": "Du wurdest wegen {lightred}{0}{default} für {lightred}{1}{default} Minuten von {lightred}{2}{default} gewarnt!",
"sa_player_warn_message_perm": "Du wurdest dauerhaft wegen {lightred}{0}{default} von {lightred}{1}{default} gewarnt!",
"sa_admin_ban_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten gebannt!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} gebannt!",
"sa_admin_kick_message": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} gekickt!",
"sa_admin_gag_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten stummgeschaltet!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} stummgeschaltet!",
"sa_admin_mute_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten gemutet!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} gemutet!",
"sa_admin_silence_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten stummgeschaltet!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} stummgeschaltet!",
"sa_admin_warn_message_time": "{lightred}{0}{default} hat {lightred}{1}{default} für {lightred}{2}{default} für {lightred}{3}{default} Minuten verwarnt!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} hat {lightred}{1}{default} permanent für {lightred}{2}{default} verwarnt!",
"sa_admin_give_message": "{lightred}{0}{default} hat {lightred}{1}{default} ein {lightred}{2}{default} gegeben!",
"sa_admin_strip_message": "{lightred}{0}{default} hat alle Waffen von Spieler {lightred}{1}{default} entfernt!",
"sa_admin_hp_message": "{lightred}{0}{default} hat die Lebenspunkte von {lightred}{1}{default} geändert!",
"sa_admin_speed_message": "{lightred}{0}{default} hat die Geschwindigkeit von {lightred}{1}{default} geändert!",
"sa_admin_gravity_message": "{lightred}{0}{default} hat die Schwerkraft von {lightred}{1}{default} geändert!",
"sa_admin_money_message": "{lightred}{0}{default} hat das Geld von {lightred}{1}{default} geändert!",
"sa_admin_god_message": "{lightred}{0}{default} hat den Gottmodus von {lightred}{1}{default} geändert!",
"sa_admin_resize_message": "{lightred}{0}{default} hat die Größe für {lightred}{1}{default} geändert!",
"sa_admin_slay_message": "{lightred}{0}{default} hat {lightred}{1}{default} getötet!",
"sa_admin_slap_message": "{lightred}{0}{default} hat {lightred}{1}{default} geschlagen!",
"sa_admin_changemap_message": "{lightred}{0}{default} hat die Karte zu {lightred}{1}{default} geändert!",
"sa_admin_noclip_message": "{lightred}{0}{default} hat den Noclip-Modus für {lightred}{1}{default} aktiviert/deaktiviert!",
"sa_admin_freeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} eingefroren!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} hat {lightred}{1}{default} aufgetaut!",
"sa_admin_rename_message": "{lightred}{0}{default} hat den Namen von {lightred}{1}{default} in {lightred}{2}{default} geändert!",
"sa_admin_respawn_message": "{lightred}{0}{default} hat {lightred}{1}{default} wiederbelebt!",
"sa_admin_tp_message": "{lightred}{0}{default} ist zu {lightred}{1}{default} teleportiert!",
"sa_admin_bring_message": "{lightred}{0}{default} hat {lightred}{1}{default} zu sich teleportiert!",
"sa_admin_team_message": "{lightred}{0}{default} hat {lightred}{1}{default} zu {lightred}{2}{default} transferiert!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}VERWARNUNGEN",
"sa_admin_warns_unwarn": "{lime}Erfolgreich{default} Verwarnung von {gold}{0} {default}aufgehoben wegen {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}ABSTIMMUNG FÜR {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} hat eine Abstimmung für {lightred}{1}{default} gestartet",
"sa_admin_vote_message_results": "{lime}ABSTIMMUNGSERGEBNISSE FÜR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Du hast {default}alle Spieler während deiner Ansprache stummgeschaltet",
"sa_admin_voice_unmute_all": "{Lime}Du hast {default}alle Spieler wieder aktiviert",
"sa_admin_voice_listen_all": "{Default}Du hörst jetzt {lime}alle {default}Spieler",
"sa_admin_voice_unlisten_all": "{Default}Du hörst jetzt {lime}nicht mehr {default}alle Spieler",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(SPIELER) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** hat den Befehl `{1}` auf dem Server `HOSTNAME` ausgeführt",
"sa_menu_pluginsmanager_loaded": "{lime}Aktiviert {default}Plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Deaktiviert {default}Plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Unknown",
"sa_no_permission": "You do not have permissions to use this command.",
"sa_ban_max_duration_exceeded": "Ban duration cannot exceed {lightred}{0}{default} minutes.",
"sa_ban_perm_restricted": "You do not have the right to permanently ban.",
"sa_admin_add": "Add Admin",
"sa_admin_remove": "Remove Admin",
"sa_admin_reload": "Reload Admins",
"sa_godmode": "God Mode",
"sa_noclip": "No Clip",
"sa_respawn": "Respawn",
"sa_give_weapon": "Give Weapon",
"sa_strip_weapons": "Strip Weapons",
"sa_freeze": "Freeze",
"sa_set_hp": "Set Hp",
"sa_set_speed": "Set Speed",
"sa_set_gravity": "Set Gravity",
"sa_set_money": "Set Money",
"sa_changemap": "Change Map",
"sa_restart_game": "Restart Game",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Swap",
"sa_team_spec": "Spec",
"sa_slap": "Slap",
"sa_slay": "slay",
"sa_kick": "Kick",
"sa_ban": "Ban",
"sa_gag": "Gag",
"sa_mute": "Mute",
"sa_silence": "Silence",
"sa_warn": "Warn",
"sa_team_force": "Force Team",
"sa_menu_custom_commands": "Custom Commands",
"sa_menu_server_manage": "Server Manage",
"sa_menu_fun_commands": "Fun Commands",
"sa_menu_admins_manage": "Admins Manage",
"sa_menu_players_manage": "Players Manage",
"sa_menu_disconnected_title": "Recent players",
"sa_menu_disconnected_action_title": "Select action",
"sa_menu_pluginsmanager_title": "Plugins Manage",
"sa_player": "Player",
"sa_console": "Console",
"sa_steamid": "SteamID",
"sa_duration": "Duration",
"sa_reason": "Reason",
"sa_admin": "Admin",
"sa_permanent": "Permanent",
"sa_discord_penalty_ban": "Ban registered",
"sa_discord_penalty_mute": "Mute registered",
"sa_discord_penalty_gag": "Gag registered",
"sa_discord_penalty_silence": "Silence registered",
"sa_discord_penalty_warn": "Warn registered",
"sa_discord_penalty_unknown": "Unknown registered",
"sa_player_penalty_chat_active": "{lightred}Your chat is blocked to: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Warn [{lightred}❌{default}] - Expire [{lightred}{0}{default}] - Reason [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Mute [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Silence [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Warn [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nPlayer penalties for {lightred}{0}{default},\nNumber of bans: {lightred}{1}{default}, Number of gags: {lightred}{2}{default}, Number of mutes: {lightred}{3}{default}, Number of silences: {lightred}{4}{default}, Number of warnings: {lightred}{5}{default}\nActive penalties:\n{6}\nActive warnings:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Player penalties for {lightred}{0}{grey}, Bans: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Warns: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Associated accounts of player {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "You have been banned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
"sa_player_ban_message_perm": "You have been banned permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_player_kick_message": "You have been kicked for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_player_gag_message_time": "You have been gagged for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
"sa_player_gag_message_perm": "You have been gagged permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_player_mute_message_time": "You have been muted for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
"sa_player_mute_message_perm": "You have been muted permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_player_silence_message_time": "You have been silenced for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
"sa_player_silence_message_perm": "You have been silenced permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_player_warn_message_time": "You have been warned for {lightred}{0}{default} for {lightred}{1}{default} minutes by {lightred}{2}{default}!",
"sa_player_warn_message_perm": "You have been warned permanently for {lightred}{0}{default} by {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} banned {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} banned {lightred}{1}{default} permanently for {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} kicked {lightred}{1}{default} for {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} gagged {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} gagged {lightred}{1}{default} permanently for {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} muted {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} muted {lightred}{1}{default} permanently for {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} silenced {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} silenced {lightred}{1}{default} permanently for {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} warned {lightred}{1}{default} for {lightred}{2}{default} for {lightred}{3}{default} minutes!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} warned {lightred}{1}{default} permanently for {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} gave {lightred}{1}{default} a {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} took all of player {lightred}{1}{default} weapons!",
"sa_admin_hp_message": "{lightred}{0}{default} changed {lightred}{1}{default} hp amount{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} changed speed for {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} changed gravity for {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} changed money for {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} changed god mode for {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} changed size for {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} slayed {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} slapped {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} changed map to {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} toggled noclip for {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} froze {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} unfroze {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} changed {lightred}{1}{default} nickname to {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} respawned {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} teleported to {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} teleported to himself {lightred}{1}{default}!",
"sa_admin_team_message": "{lightred}{0}{default} transfered {lightred}{1}{default} to {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}WARNS",
"sa_admin_warns_unwarn": "{lime}Successfully{default} unwarned {gold}{0} {default}for {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}VOTING FOR {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} started voting for {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}VOTING RESULTS FOR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}You muted {default}all players during your speech",
"sa_admin_voice_unmute_all": "{Lime}You unmuted {default}all players",
"sa_admin_voice_listen_all": "{Default}You can now hear {lime}all {default}players",
"sa_admin_voice_unlisten_all": "{Default}You no longer hear {lime}all {default}players",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(PLAYER) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** issued command `{1}` on server `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Enabled {default}plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Disabled {default}plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Desconocido",
"sa_no_permission": "No tienes permisos para usar este comando.",
"sa_ban_max_duration_exceeded": "La duración de la prohibición no puede exceder {lightred}{0}{default} minutos.",
"sa_ban_perm_restricted": "No tienes derecho a prohibir permanentemente.",
"sa_admin_add": "Agregar Administrador",
"sa_admin_remove": "Eliminar Administrador",
"sa_admin_reload": "Recargar Administradores",
"sa_godmode": "Modo Dios",
"sa_noclip": "Sin Colisión",
"sa_respawn": "Reaparecer",
"sa_give_weapon": "Dar Arma",
"sa_strip_weapons": "Eliminar Armas",
"sa_freeze": "Congelar",
"sa_set_hp": "Establecer Vida",
"sa_set_speed": "Establecer Velocidad",
"sa_set_gravity": "Establecer Gravedad",
"sa_set_money": "Establecer Dinero",
"sa_changemap": "Cambiar Mapa",
"sa_restart_game": "Reiniciar Juego",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Intercambiar",
"sa_team_spec": "Espectador",
"sa_slap": "Golpear",
"sa_slay": "Matar",
"sa_kick": "Expulsar",
"sa_ban": "Banear",
"sa_gag": "Callar",
"sa_mute": "Silenciar",
"sa_silence": "Silencio",
"sa_warn": "Advertencia",
"sa_team_force": "Forzar Equipo",
"sa_menu_custom_commands": "Comandos Personalizados",
"sa_menu_server_manage": "Administrar Servidor",
"sa_menu_fun_commands": "Comandos Divertidos",
"sa_menu_admins_manage": "Administrar Administradores",
"sa_menu_players_manage": "Administrar Jugadores",
"sa_menu_disconnected_title": "Jugadores recientes",
"sa_menu_disconnected_action_title": "Seleccionar acción",
"sa_menu_pluginsmanager_title": "Gestionar plugins",
"sa_player": "Jugador",
"sa_console": "Consola",
"sa_steamid": "ID de Steam",
"sa_duration": "Duración",
"sa_reason": "Motivo",
"sa_admin": "Admin",
"sa_permanent": "Permanente",
"sa_discord_penalty_ban": "Ban registrado",
"sa_discord_penalty_mute": "Silencio registrado",
"sa_discord_penalty_gag": "Mordaza registrada",
"sa_discord_penalty_silence": "Silencio registrado",
"sa_discord_penalty_warn": "Advertencia registrada",
"sa_discord_penalty_unknown": "Registro desconocido",
"sa_player_penalty_chat_active": "{lightred}Tu chat está bloqueado para: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Silenciado [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Boqueado [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silencio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Advertencia [{lightred}❌{default}] - Expira [{lightred}{0}{default}] - Razón [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Silenciado [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Boqueado [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Silencio [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Advertencia [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nPenalizaciones del jugador para {lightred}{0}{default},\nNúmero de prohibiciones: {lightred}{1}{default}, Número de boqueos: {lightred}{2}{default}, Número de silenciamientos: {lightred}{3}{default}, Número de silencios: {lightred}{4}{default}, Número de advertencias: {lightred}{5}{default}\nPenalizaciones activas:\n{6}\nAdvertencias activas:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Penalizaciones del jugador para {lightred}{0}{grey}, Prohibiciones: {lightred}{1}{grey}, Boqueos: {lightred}{2}{grey}, Silenciamientos: {lightred}{3}{grey}, Silencios: {lightred}{4}{grey}, Advertencias: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Cuentas asociadas del jugador {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Has sido baneado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Has sido baneado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_kick_message": "Has sido expulsado por {lightred}{0}{default} durante {lightred}{1}{default}!",
"sa_player_gag_message_time": "Has sido silenciado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_gag_message_perm": "Has sido silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_mute_message_time": "Has sido muteado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_mute_message_perm": "Has sido muteado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_silence_message_time": "Has sido silenciado por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_silence_message_perm": "Has sido silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_warn_message_time": "¡Has sido advertido por {lightred}{0}{default} durante {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_warn_message_perm": "¡Has sido advertido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} baneó a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} baneó a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} expulsó a {lightred}{1}{default} por {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} amordazó a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} amordazó a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} silenció a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} silenció a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} silenció a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} silenció a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} advirtió a {lightred}{1}{default} por {lightred}{2}{default} durante {lightred}{3}{default} minutos!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} advirtió a {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} dio {lightred}{1}{default} un {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} quitó todas las armas del jugador {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} cambió la cantidad de HP de {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} cambió la velocidad de {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} cambió la gravedad de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} cambió el dinero de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} cambió el modo dios de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} cambió el tamaño de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} mató a {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} abofeteó a {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} cambió el mapa a {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} alternó noclip para {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} congeló a {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} descongeló a {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} cambió el apodo de {lightred}{1}{default} a {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} reapareció a {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} se teletransportó a {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} teletransportó a {lightred}{1}{default} hacia sí mismo!",
"sa_admin_team_message": "{lightred}{0}{default} transfirió a {lightred}{1}{default} al {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTENCIAS",
"sa_admin_warns_unwarn": "{lime}Desadvertencia{default} exitosa de {gold}{0} {default}por {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}VOTACIÓN PARA {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} inició una votación para {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RESULTADOS DE LA VOTACIÓN PARA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Has silenciado a {default}todos los jugadores durante tu discurso",
"sa_admin_voice_unmute_all": "{Lime}Has reactivado el sonido de {default}todos los jugadores",
"sa_admin_voice_listen_all": "{Default}Ahora puedes escuchar a {lime}todos {default}los jugadores",
"sa_admin_voice_unlisten_all": "{Default}Ya no escuchas a {lime}todos {default}los jugadores",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JUGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** ejecutó el comando `{1}` en el servidor `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Habilitado {default}plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Deshabilitado {default}plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "ناشناخته",
"sa_no_permission": "شما دسترسی برای استفاده از این دستور را ندارید.",
"sa_ban_max_duration_exceeded": "مدت ممنوعیت نمی‌تواند بیشتر از {lightred}{0}{default} دقیقه باشد.",
"sa_ban_perm_restricted": "شما اجازه ممنوعیت دائم را ندارید.",
"sa_admin_add": "افزودن مدیر",
"sa_admin_remove": "حذف مدیر",
"sa_admin_reload": "بارگذاری مجدد مدیران",
"sa_godmode": "حالت خدا",
"sa_noclip": "بدون بریدن",
"sa_respawn": "باززایی",
"sa_give_weapon": "دادن اسلحه",
"sa_strip_weapons": "برداشتن اسلحه",
"sa_freeze": "یخ‌زدن",
"sa_set_hp": "تنظیم پلیر",
"sa_set_speed": "تنظیم سرعت",
"sa_set_gravity": "تنظیم گرانش",
"sa_set_money": "تنظیم پول",
"sa_changemap": "تغییر نقشه",
"sa_restart_game": "شروع مجدد بازی",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "جابه‌جایی",
"sa_team_spec": "ناظر",
"sa_slap": "چپاد زدن",
"sa_slay": "کشتن",
"sa_kick": "اخراج",
"sa_ban": "مسدود کردن",
"sa_gag": "بی‌صدا کردن",
"sa_mute": "بی‌صدا کردن",
"sa_silence": "سکوت",
"sa_warn": "هشدار",
"sa_team_force": "اجبار تیم",
"sa_menu_custom_commands": "دستورات سفارشی",
"sa_menu_server_manage": "مدیریت سرور",
"sa_menu_fun_commands": "دستورات جالب",
"sa_menu_admins_manage": "مدیریت مدیران",
"sa_menu_players_manage": "مدیریت بازیکنان",
"sa_menu_disconnected_title": "آخرین بازیکنان",
"sa_menu_disconnected_action_title": "انتخاب عملیات",
"sa_menu_pluginsmanager_title": "مدیریت پلاگین‌ها",
"sa_player": "بازیکن",
"sa_console": "کنسول",
"sa_steamid": "شناسه استیم",
"sa_duration": "مدت زمان",
"sa_reason": "دلیل",
"sa_admin": "مدیر",
"sa_permanent": "دائمی",
"sa_discord_penalty_ban": "بن انجام شده",
"sa_discord_penalty_mute": "سکوت انجام شده",
"sa_discord_penalty_gag": "بند زدن انجام شده",
"sa_discord_penalty_silence": "سکوت انجام شده",
"sa_discord_penalty_warn": "هشدار ثبت شد",
"sa_discord_penalty_unknown": "ناشناخته انجام شده",
"sa_player_penalty_chat_active": "{lightred}چت شما برای: {grey}{0} مسدود شده است",
"sa_player_penalty_info_active_mute": "➔ بی‌صدا [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ مسدود کردن صدا [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ سکوت [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ هشدار [{lightred}❌{default}] - منقضی شدن [{lightred}{0}{default}] - دلیل [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ بی‌صدا [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ مسدود کردن صدا [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ سکوت [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ هشدار [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nتنبیهات بازیکن برای {lightred}{0}{default},\nتعداد مسدودیت‌ها: {lightred}{1}{default}, تعداد سکوت‌ها: {lightred}{2}{default}, تعداد بی‌صدا کردن‌ها: {lightred}{3}{default}, تعداد سکوت‌ها: {lightred}{4}{default}, تعداد هشدارها: {lightred}{5}{default}\nتنبیهات فعال:\n{6}\nهشدارهای فعال:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}تنبیهات بازیکن برای {lightred}{0}{grey}, مسدودیت‌ها: {lightred}{1}{grey}, سکوت‌ها: {lightred}{2}{grey}, بی‌صدا کردن‌ها: {lightred}{3}{grey}, سکوت‌ها: {lightred}{4}{grey}, هشدارها: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}حساب‌های مرتبط با بازیکن {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} مسدود شده‌اید!",
"sa_player_ban_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه مسدود شده‌اید!",
"sa_player_kick_message": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} اخراج شده‌اید!",
"sa_player_gag_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} خفه شده‌اید!",
"sa_player_gag_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه خفه شده‌اید!",
"sa_player_mute_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} بی‌صدا شده‌اید!",
"sa_player_mute_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه بی‌صدا شده‌اید!",
"sa_player_silence_message_time": "شما توسط {lightred}{2}{default} برای {lightred}{1}{default} دقیقه به دلیل {lightred}{0}{default} ساکت شده‌اید!",
"sa_player_silence_message_perm": "شما توسط {lightred}{1}{default} به دلیل {lightred}{0}{default} برای همیشه ساکت شده‌اید!",
"sa_player_warn_message_time": "شما به خاطر {lightred}{0}{default} به مدت {lightred}{1}{default} دقیقه توسط {lightred}{2}{default} هشدار داده شده\u200Cاید!",
"sa_player_warn_message_perm": "شما به طور دائم به خاطر {lightred}{0}{default} توسط {lightred}{1}{default} هشدار داده شده\u200Cاید!",
"sa_admin_ban_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} بن کرد!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} بن کرد!",
"sa_admin_kick_message": "{lightred}{0}{default} {lightred}{1}{default} را به دلیل {lightred}{2}{default} اخراج کرد!",
"sa_admin_gag_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} بی‌صدا کرد!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} بی‌صدا کرد!",
"sa_admin_mute_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} قطع صدا کرد!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} قطع صدا کرد!",
"sa_admin_silence_message_time": "{lightred}{0}{default} {lightred}{1}{default} را برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default} بی‌صدا کرد!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} {lightred}{1}{default} را به‌طور دائم به دلیل {lightred}{2}{default} بی‌صدا کرد!",
"sa_admin_warn_message_time": "{lightred}{0}{default} به {lightred}{1}{default} هشدار داد برای {lightred}{3}{default} دقیقه به دلیل {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} به {lightred}{1}{default} به‌طور دائم به دلیل {lightred}{2}{default} هشدار داد!",
"sa_admin_give_message": "{lightred}{0}{default} {lightred}{2}{default} را به {lightred}{1}{default} داد!",
"sa_admin_strip_message": "{lightred}{0}{default} تمام سلاح‌های بازیکن {lightred}{1}{default} را گرفت!",
"sa_admin_hp_message": "{lightred}{0}{default} مقدار سلامت {lightred}{1}{default} را تغییر داد!",
"sa_admin_speed_message": "{lightred}{0}{default} سرعت {lightred}{1}{default} را تغییر داد!",
"sa_admin_gravity_message": "{lightred}{0}{default} جاذبه {lightred}{1}{default} را تغییر داد!",
"sa_admin_money_message": "{lightred}{0}{default} پول {lightred}{1}{default} را تغییر داد!",
"sa_admin_god_message": "{lightred}{0}{default} حالت خدا را برای {lightred}{1}{default} تغییر داد!",
"sa_admin_resize_message": "{lightred}{0}{default} اندازه {lightred}{1}{default} را تغییر داد!",
"sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default} را کشت!",
"sa_admin_slap_message": "{lightred}{0}{default} به {lightred}{1}{default} سیلی زد!",
"sa_admin_changemap_message": "{lightred}{0}{default} نقشه را به {lightred}{1}{default} تغییر داد!",
"sa_admin_noclip_message": "{lightred}{0}{default} ناپدیدی را برای {lightred}{1}{default} فعال/غیرفعال کرد!",
"sa_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default} را یخ‌زده کرد!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default} را از حالت یخ خارج کرد!",
"sa_admin_rename_message": "{lightred}{0}{default} نام {lightred}{1}{default} را به {lightred}{2}{default} تغییر داد!",
"sa_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default} را دوباره زنده کرد!",
"sa_admin_tp_message": "{lightred}{0}{default} به {lightred}{1}{default} تله‌پورت شد!",
"sa_admin_bring_message": "{lightred}{0}{default} {lightred}{1}{default} را به خود تله‌پورت کرد!",
"sa_admin_team_message": "{lightred}{0}{default} {lightred}{1}{default} را به {lightred}{2}{default} انتقال داد!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}هشدارها",
"sa_admin_warns_unwarn": "{lime}هشدار با موفقیت{default} برای {gold}{0} {default}لغو شد به دلیل {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}رأی‌گیری برای {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} رأی‌گیری برای {lightred}{1}{default} را شروع کرد",
"sa_admin_vote_message_results": "{lime}نتایج رأی‌گیری برای {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}شما {default}همه بازیکنان را در طول سخنرانی بی‌صدا کردید",
"sa_admin_voice_unmute_all": "{Lime}شما {default}همه بازیکنان را از حالت بی‌صدا خارج کردید",
"sa_admin_voice_listen_all": "{Default}اکنون می‌توانید {lime}همه {default}بازیکنان را بشنوید",
"sa_admin_voice_unlisten_all": "{Default}شما دیگر {lime}همه {default}بازیکنان را نمی‌شنوید",
"sa_adminsay_prefix": "{RED}ادمین: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ادمین) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(بازیکن) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** فرمان `{1}` را در سرور `HOSTNAME` اجرا کرد",
"sa_menu_pluginsmanager_loaded": "{lime}فعال {default}پلاگین {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}غیرفعال {default}پلاگین {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Inconnu",
"sa_no_permission": "Vous n'avez pas les permissions pour utiliser cette commande.",
"sa_ban_max_duration_exceeded": "La durée d'interdiction ne peut pas dépasser {lightred}{0}{default} minutes.",
"sa_ban_perm_restricted": "Vous n'avez pas le droit de bannir définitivement.",
"sa_admin_add": "Ajouter un administrateur",
"sa_admin_remove": "Supprimer un administrateur",
"sa_admin_reload": "Recharger les administrateurs",
"sa_godmode": "Mode Dieu",
"sa_noclip": "Mode Spectateur",
"sa_respawn": "Réapparaître",
"sa_give_weapon": "Donner une arme",
"sa_strip_weapons": "Retirer les armes",
"sa_freeze": "Geler",
"sa_set_hp": "Définir les PV",
"sa_set_speed": "Définir la vitesse",
"sa_set_gravity": "Définir la gravité",
"sa_set_money": "Définir l'argent",
"sa_changemap": "Changer de carte",
"sa_restart_game": "Redémarrer le jeu",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Échanger",
"sa_team_spec": "Spectateur",
"sa_slap": "Gifler",
"sa_slay": "Tuer",
"sa_kick": "Expulser",
"sa_ban": "Bannir",
"sa_gag": "Réduire au silence",
"sa_mute": "Muter",
"sa_silence": "Silence",
"sa_warn": "Avertir",
"sa_team_force": "Forcer l'équipe",
"sa_menu_custom_commands": "Commandes personnalisées",
"sa_menu_server_manage": "Gérer le serveur",
"sa_menu_fun_commands": "Commandes amusantes",
"sa_menu_admins_manage": "Gérer les administrateurs",
"sa_menu_players_manage": "Gérer les joueurs",
"sa_menu_disconnected_title": "Derniers joueurs",
"sa_menu_disconnected_action_title": "Choisir une action",
"sa_menu_pluginsmanager_title": "Gérer les plugins",
"sa_player": "Joueur",
"sa_console": "Console",
"sa_steamid": "ID Steam",
"sa_duration": "Durée",
"sa_reason": "Raison",
"sa_admin": "Admin",
"sa_permanent": "Permanent",
"sa_discord_penalty_ban": "Bannissement enregistré",
"sa_discord_penalty_mute": "Mute enregistré",
"sa_discord_penalty_gag": "Gag enregistré",
"sa_discord_penalty_silence": "Silence enregistré",
"sa_discord_penalty_warn": "Avertissement enregistré",
"sa_discord_penalty_unknown": "Inconnu enregistré",
"sa_player_penalty_chat_active": "{lightred}Votre chat est bloqué pour : {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Muet [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silence [{lightred}❌{default}] - Expire [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Avertissement [{lightred}❌{default}] - Expire [{lightred}{0}{default}] - Raison [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Muet [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Silence [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Avertissement [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nPénalités du joueur pour {lightred}{0}{default},\nNombre de bannissements: {lightred}{1}{default}, Nombre de gag: {lightred}{2}{default}, Nombre de mutes: {lightred}{3}{default}, Nombre de silences: {lightred}{4}{default}, Nombre davertissements: {lightred}{5}{default}\nPénalités actives:\n{6}\nAvertissements actifs:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Pénalités du joueur pour {lightred}{0}{grey}, Bannissements: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silences: {lightred}{4}{grey}, Avertissements: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Comptes associés du joueur {lightred}{0}{grey} : {1}",
"sa_player_ban_message_time": "Vous avez été banni pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Vous avez été banni définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_player_kick_message": "Vous avez été expulsé pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_player_gag_message_time": "Vous avez été réduit au silence pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
"sa_player_gag_message_perm": "Vous avez été réduit au silence définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_player_mute_message_time": "Vous avez été réduit au silence pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
"sa_player_mute_message_perm": "Vous avez été réduit au silence définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_player_silence_message_time": "Vous avez été mis en sourdine pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
"sa_player_silence_message_perm": "Vous avez été mis en sourdine définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_player_warn_message_time": "Vous avez été averti pour {lightred}{0}{default} pendant {lightred}{1}{default} minutes par {lightred}{2}{default}!",
"sa_player_warn_message_perm": "Vous avez été averti définitivement pour {lightred}{0}{default} par {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} a banni {lightred}{1}{default} pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} a banni {lightred}{1}{default} définitivement pour {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} a expulsé {lightred}{1}{default} pour {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} a bâillonné {lightred}{1}{default} pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} a bâillonné {lightred}{1}{default} définitivement pour {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} a rendu {lightred}{1}{default} muet pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} a rendu {lightred}{1}{default} muet définitivement pour {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} a rendu {lightred}{1}{default} silencieux pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} a rendu {lightred}{1}{default} silencieux définitivement pour {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} a averti {lightred}{1}{default} pendant {lightred}{3}{default} minutes pour {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} a averti {lightred}{1}{default} définitivement pour {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} a donné {lightred}{2}{default} à {lightred}{1}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} a retiré toutes les armes de {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} a modifié la quantité de HP de {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} a modifié la vitesse de {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} a modifié la gravité de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} a modifié l'argent de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} a modifié le mode dieu de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} a changé la taille de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} a tué {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} a giflé {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} a changé la carte pour {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} a activé/désactivé le noclip pour {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} a gelé {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} a dégivré {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} a renommé {lightred}{1}{default} en {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} a réapparu {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} s'est téléporté à {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} a téléporté {lightred}{1}{default} à lui!",
"sa_admin_team_message": "{lightred}{0}{default} a transféré {lightred}{1}{default} à l'équipe {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}AVERTISSEMENTS",
"sa_admin_warns_unwarn": "{lime}Avertissement annulé{default} avec succès pour {gold}{0} {default}en raison de {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}VOTE POUR {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} a lancé un vote pour {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RÉSULTATS DU VOTE POUR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Vous avez mis en sourdine {default}tous les joueurs pendant votre discours",
"sa_admin_voice_unmute_all": "{Lime}Vous avez réactivé le son de {default}tous les joueurs",
"sa_admin_voice_listen_all": "{Default}Vous entendez maintenant {lime}tous {default}les joueurs",
"sa_admin_voice_unlisten_all": "{Default}Vous n'entendez plus {lime}tous {default}les joueurs",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JOUEUR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** a exécuté la commande `{1}` sur le serveur `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Activé {default}plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Désactivé {default}plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Nezināms",
"sa_no_permission": "Jums nav atļauju izmantot šo komandu.",
"sa_ban_max_duration_exceeded": "Aizlieguma ilgums nevar pārsniegt {lightred}{0}{default} minūtes.",
"sa_ban_perm_restricted": "Jums nav tiesību uz pastāvīgu aizliegumu.",
"sa_admin_add": "Pievienot administratoru",
"sa_admin_remove": "Noņemt administratoru",
"sa_admin_reload": "Pārlādēt administratorus",
"sa_godmode": "Dieva režīms",
"sa_noclip": "Bez šķēršļiem",
"sa_respawn": "Atdzimt",
"sa_give_weapon": "Dot ieroci",
"sa_strip_weapons": "Noņemt ieročus",
"sa_freeze": "Salauzt",
"sa_set_hp": "Iestatīt veselību",
"sa_set_speed": "Iestatīt ātrumu",
"sa_set_gravity": "Iestatīt gravitāciju",
"sa_set_money": "Iestatīt naudu",
"sa_changemap": "Mainīt karti",
"sa_restart_game": "Restartēt spēli",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Mainīt",
"sa_team_spec": "Skatītājs",
"sa_slap": "Plašs",
"sa_slay": "Nogalināt",
"sa_kick": "Izraidīt",
"sa_ban": "Bloķēt",
"sa_gag": "Izslēgt runu",
"sa_mute": "Noklusināt",
"sa_silence": "Klusums",
"sa_warn": "Brīdināt",
"sa_team_force": "Spēka komanda",
"sa_menu_custom_commands": "Pielāgotās komandas",
"sa_menu_server_manage": "Servera pārvaldība",
"sa_menu_fun_commands": "Jautras komandas",
"sa_menu_admins_manage": "Administratoru pārvaldība",
"sa_menu_players_manage": "Spēlētāju pārvaldība",
"sa_menu_disconnected_title": "Pēdējie spēlētāji",
"sa_menu_disconnected_action_title": "Izvēlieties darbību",
"sa_menu_pluginsmanager_title": "Pārvaldīt spraudņus",
"sa_player": "Spēlētājs",
"sa_console": "Konsole",
"sa_steamid": "Steam ID",
"sa_duration": "Ilgums",
"sa_reason": "Iemesls",
"sa_admin": "Admins",
"sa_permanent": "Pastāvīgs",
"sa_discord_penalty_ban": "Bans reģistrēts",
"sa_discord_penalty_mute": "Mute reģistrēts",
"sa_discord_penalty_gag": "Gag reģistrēts",
"sa_discord_penalty_silence": "Klusums reģistrēts",
"sa_discord_penalty_warn": "Brīdinājums reģistrēts",
"sa_discord_penalty_unknown": "Nezināms reģistrēts",
"sa_player_penalty_chat_active": "{lightred}Jūsu čats ir bloķēts uz: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Izslēgts [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Klusums [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Klusēšana [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Brīdinājums [{lightred}❌{default}] - Beidzas [{lightred}{0}{default}] - Iemesls [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Izslēgts [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Klusums [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Klusēšana [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Brīdinājums [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nSpēlētāja sods priekš {lightred}{0}{default},\nAizliegumu skaits: {lightred}{1}{default}, Klusumu skaits: {lightred}{2}{default}, Izslēgšanas skaits: {lightred}{3}{default}, Klusēšanas skaits: {lightred}{4}{default}, Brīdinājumu skaits: {lightred}{5}{default}\nAktīvie sodi:\n{6}\nAktīvie brīdinājumi:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Spēlētāja sods priekš {lightred}{0}{grey}, Aizliegumi: {lightred}{1}{grey}, Klusumi: {lightred}{2}{grey}, Izslēgšana: {lightred}{3}{grey}, Klusēšana: {lightred}{4}{grey}, Brīdinājumi: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Spēlētāja {lightred}{0}{grey} saistītie konti: {1}",
"sa_player_ban_message_time": "Tu esi nobanots uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Tevis bans ir uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
"sa_player_kick_message": "Tu esi izmests, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
"sa_player_gag_message_time": "Tev ir izliegta čata rakstīšana uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}, Admins: {lightred}{3}{default}!",
"sa_player_gag_message_perm": "Tev ir izliegta čata rakstīšana uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
"sa_player_mute_message_time": "Tev ir izliegta balsu rakstīšana uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}, Admins: {lightred}{3}{default}!",
"sa_player_mute_message_perm": "Tev ir izliegta balsu rakstīšana uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
"sa_player_silence_message_time": "Tevis balss ir izslēgta uz {lightred}{0}{default} uz {lightred}{1}{default} minūtēm, iemesls: {lightred}{2}{default}, Admins: {lightred}{3}{default}!",
"sa_player_silence_message_perm": "Tevis balss ir izslēgta uz mūžu, iemesls: {lightred}{0}{default}, Admins: {lightred}{1}{default}!",
"sa_player_warn_message_time": "Jums ir izteikts brīdinājums par {lightred}{0}{default} uz {lightred}{1}{default} minūtēm no {lightred}{2}{default}!",
"sa_player_warn_message_perm": "Jums ir izteikts pastāvīgs brīdinājums par {lightred}{0}{default} no {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} nobanoja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} nobanoja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} izmeta {lightred}{1}{default} par {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} izslēdza {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} izslēdza {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} apklusināja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} brīdināja {lightred}{1}{default} uz {lightred}{3}{default} minūtēm par {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} brīdināja {lightred}{1}{default} uz visiem laikiem par {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} noņēma visus {lightred}{1}{default} ieročus!",
"sa_admin_hp_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} HP daudzumu!",
"sa_admin_speed_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} ātrumu!",
"sa_admin_gravity_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} gravitāciju!",
"sa_admin_money_message": "{lightred}{0}{default} mainīja {lightred}{1}{default} naudu!",
"sa_admin_god_message": "{lightred}{0}{default} mainīja dieva režīmu priekš {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} mainīja izmēru {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} nogalināja {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} iedeva {lightred}{1}{default} pa seju!",
"sa_admin_changemap_message": "{lightred}{0}{default} mainīja karti uz {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} aktivizēja/deaktivizēja noclip priekš {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} sasaldēja {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} atkausēja {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} pārdēvēja {lightred}{1}{default} uz {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} atdzīvināja {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} teleporta uz {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} teleporta {lightred}{1}{default} pie sevis!",
"sa_admin_team_message": "{lightred}{0}{default} pārvietoja {lightred}{1}{default} uz {lightred}{2}{default} komandu!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}BRĪDINĀJUMI",
"sa_admin_warns_unwarn": "{lime}Brīdinājums veiksmīgi{default} atsaukts priekš {gold}{0} {default}dēļ {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}BALSOŠANA PAR {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} uzsāka balsošanu par {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}BALSOŠANAS REZULTĀTI PAR {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Tu esi apklusinājis {default}visus spēlētājus runas laikā",
"sa_admin_voice_unmute_all": "{Lime}Tu atvienoji {default}visus spēlētājus",
"sa_admin_voice_listen_all": "{Default}Tu tagad dzirdi {lime}visus {default}spēlētājus",
"sa_admin_voice_unlisten_all": "{Default}Tu vairs nedzirdi {lime}visus {default}spēlētājus",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(SPĒLĒTĀJS) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** izpildīja komandu `{1}` serverī `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Ieslēgts {default}spraudnis {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Izslēgts {default}spraudnis {lightred}{0}"
}

View File

@@ -0,0 +1,140 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Brak",
"sa_no_permission": "Nie masz uprawnień do korzystania z tej komendy.",
"sa_ban_max_duration_exceeded": "Czas bana nie może przekraczać {lightred}{0}{default} minut.",
"sa_ban_perm_restricted": "Nie masz prawa do trwałego zbanowania.",
"sa_admin_add": "Dodaj administratora",
"sa_admin_remove": "Usuń administratora",
"sa_admin_reload": "Przeładuj administratorów",
"sa_godmode": "Tryb Boga",
"sa_noclip": "Tryb Latania",
"sa_respawn": "Odrodzenie",
"sa_give_weapon": "Daj broń",
"sa_strip_weapons": "Usuń bronie",
"sa_freeze": "Zamroź",
"sa_set_hp": "Ustaw HP",
"sa_set_speed": "Ustaw prędkość",
"sa_set_gravity": "Ustaw grawitację",
"sa_set_money": "Ustaw pieniądze",
"sa_changemap": "Zmień mapę",
"sa_restart_game": "Zrestartuj mapę",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Zamień",
"sa_team_spec": "Spec",
"sa_slap": "Uderz",
"sa_slay": "Zabij",
"sa_kick": "Wyrzuć",
"sa_ban": "Zbanuj",
"sa_gag": "Zaknebluj",
"sa_mute": "Wycisz",
"sa_silence": "Ucisz",
"sa_warn": "Ostrzeżenie",
"sa_team_force": "Wymuś drużynę",
"sa_menu_custom_commands": "Komendy niestandardowe",
"sa_menu_server_manage": "Zarządzaj serwerem",
"sa_menu_fun_commands": "Komendy rozrywkowe",
"sa_menu_admins_manage": "Zarządzaj administratorami",
"sa_menu_players_manage": "Zarządzaj graczami",
"sa_menu_disconnected_title": "Ostatni gracze",
"sa_menu_disconnected_action_title": "Wybierz akcje",
"sa_menu_pluginsmanager_title": "Zarządzaj pluginami",
"sa_player": "Gracz",
"sa_console": "Konsola",
"sa_steamid": "SteamID",
"sa_duration": "Wygasa",
"sa_reason": "Powód",
"sa_admin": "Admin",
"sa_permanent": "Na zawsze",
"sa_discord_penalty_ban": "Nowy ban",
"sa_discord_penalty_mute": "Nowe wyciszenie",
"sa_discord_penalty_gag": "Nowe zakneblowanie",
"sa_discord_penalty_silence": "Nowe uciszenie",
"sa_discord_penalty_warn": "Nowe ostrzeżenie",
"sa_discord_penalty_unknown": "Nowa nieznana blokada",
"sa_player_penalty_chat_active": "{lightred}Twój czat jest zablokowany do: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Zakneblowanie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Wyciszenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Uciszenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Ostrzeżenie [{lightred}❌{default}] - Wygasa [{lightred}{0}{default}] - Powód [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Zakneblowanie [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Wyciszenie [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Uciszenie [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Ostrzeżenie [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nBlokady gracza {lightred}{0}{default},\nIlość banów: {lightred}{1}{default}, Ilość zakneblowań: {lightred}{2}{default}, Ilość wyciszeń: {lightred}{3}{default}, Ilość uciszeń: {lightred}{4}{default}Ilość ostrzeżeń: {lightred}{5}{default}\nAktywne blokady:\n{6}\nAktywne ostrzeżenia:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Blokady gracza {lightred}{0}{grey} - bany: {lightred}{1}{grey}, zakneblowania: {lightred}{2}{grey}, wyciszenia: {lightred}{3}{grey}, uciszenia: {lightred}{4}{grey}, ostrzeżenia: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Powiązane konta gracza {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Zostałeś zbanowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Zostałeś zbanowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!",
"sa_player_kick_message": "Zostałeś wyrzucony za {lightred}{0}{default} przez {lightred}{1}{default}!",
"sa_player_gag_message_time": "Zostałeś zakneblowany za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!",
"sa_player_gag_message_perm": "Zostałeś zakneblowany na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!",
"sa_player_mute_message_time": "Zostałeś uciszony za {lightred}{0}{default} na {lightred}{1}{default} minute przez {lightred}{2}{default}!",
"sa_player_mute_message_perm": "Zostałeś uciszony na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!",
"sa_player_silence_message_time": "Zostałeś wyciszony za {lightred}{0}{default} na {lightred}{1}{default} minut przez {lightred}{2}{default}!",
"sa_player_silence_message_perm": "Zostałeś wyciszony na zawsze za {lightred}{0}{default} przez {lightred}{1}{default}!",
"sa_player_warn_message_time": "Otrzymałeś ostrzeżenie za {lightred}{0}{default} na {lightred}{1}{default} minut od {lightred}{2}{default}!",
"sa_player_warn_message_perm": "Otrzymałeś ostrzeżenie za {lightred}{0}{default} od {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} zbanował {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} zbanował {lightred}{1}{default} na zawsze za {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} wyrzucił {lightred}{1}{default} za {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} zakneblował {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} zakneblował {lightred}{1}{default} na zawsze za {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} uciszył {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} uciszył {lightred}{1}{default} na zawsze za {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} wyciszył {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} wyciszył {lightred}{1}{default} na zawsze za {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} ostrzegł {lightred}{1}{default} za {lightred}{2}{default} na {lightred}{3}{default} minut!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} ostrzegł {lightred}{1}{default} na zawsze za {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} dał {lightred}{1}{default} przedmiot {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} zabrał wszystkie bronie {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} zmienił ilość hp dla {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} zmienił prędkość dla {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} zmienił grawitacje dla {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} zmienił pieniądze dla {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} zmienił tryb Boga dla {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} zmienił rozmiar dla {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} zgładził {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} uderzył {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} zmienił mapę na {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} ustawił latanie dla {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} zamroził {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} odmroził {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} zmienił nick gracza {lightred}{1}{default} na {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} odrodził {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} teleportował się do {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} teleportował do siebie {lightred}{1}{default}!",
"sa_admin_team_message": "{lightred}{0}{default} przerzucił {lightred}{1}{default} do {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{lime}OSTRZEŻENIA {gold}{0}",
"sa_admin_warns_unwarn": "{lime}Pomyślnie{default} usunięto ostrzeżenie dla {gold}{0} {default}za {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}GŁOSOWANIE NA {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} rozpoczął głosowanie na {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}WYNIKI GŁOSOWANIA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}[{1}]",
"sa_admin_voice_mute_all": "{Lightred}Wyciszyłeś {default}wszystkich graczy na czas przemówienia",
"sa_admin_voice_unmute_all": "{Lime}Odciszyłeś {default}wszystkich graczy",
"sa_admin_voice_listen_all": "{Default}Słyszysz teraz {lime}wszystkich {default}graczy",
"sa_admin_voice_unlisten_all": "{Default}Nie słyszysz teraz {lime}wszystkich {default}graczy",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(GRACZ) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** użył komendy `{1}` na serwerze `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Włączono {default}plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Wyłączono {default}plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Desconhecido",
"sa_no_permission": "Você não tem permissão para usar este comando.",
"sa_ban_max_duration_exceeded": "A duração da proibição não pode exceder {lightred}{0}{default} minutos.",
"sa_ban_perm_restricted": "Você não tem permissão para banir permanentemente.",
"sa_admin_add": "Adicionar Admin",
"sa_admin_remove": "Remover Admin",
"sa_admin_reload": "Recarregar Admins",
"sa_godmode": "Modo Deus",
"sa_noclip": "Modo Espectador",
"sa_respawn": "Ressurgir",
"sa_give_weapon": "Dar Arma",
"sa_strip_weapons": "Remover Armas",
"sa_freeze": "Congelar",
"sa_set_hp": "Definir HP",
"sa_set_speed": "Definir Velocidade",
"sa_set_gravity": "Definir Gravidade",
"sa_set_money": "Definir Dinheiro",
"sa_changemap": "Mudar Mapa",
"sa_restart_game": "Reiniciar Jogo",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Trocar",
"sa_team_spec": "Espec",
"sa_slap": "Tapa",
"sa_slay": "Matar",
"sa_kick": "Expulsar",
"sa_ban": "Banir",
"sa_gag": "Silenciar",
"sa_mute": "Mutar",
"sa_silence": "Silêncio",
"sa_warn": "Aviso",
"sa_team_force": "Forçar Time",
"sa_menu_custom_commands": "Comandos Personalizados",
"sa_menu_server_manage": "Gerenciar Servidor",
"sa_menu_fun_commands": "Comandos Divertidos",
"sa_menu_admins_manage": "Gerenciar Admins",
"sa_menu_players_manage": "Gerenciar Jogadores",
"sa_menu_disconnected_title": "Jogadores recentes",
"sa_menu_disconnected_action_title": "Selecionar ação",
"sa_menu_pluginsmanager_title": "Gerenciar Plugins",
"sa_player": "Jogador",
"sa_console": "Console",
"sa_steamid": "SteamID",
"sa_duration": "Duração",
"sa_reason": "Motivo",
"sa_admin": "Admin",
"sa_permanent": "Permanente",
"sa_discord_penalty_ban": "Banimento registrado",
"sa_discord_penalty_mute": "Mute registrado",
"sa_discord_penalty_gag": "Gag registrado",
"sa_discord_penalty_silence": "Silêncio registrado",
"sa_discord_penalty_warn": "Aviso registrado",
"sa_discord_penalty_unknown": "Desconhecido registrado",
"sa_player_penalty_chat_active": "{lightred}Seu chat está bloqueado para: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mudo [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silêncio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Aviso [{lightred}❌{default}] - Expira [{lightred}{0}{default}] - Razão [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Mudo [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Silêncio [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Aviso [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Você foi banido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Você foi banido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_kick_message": "Você foi expulso por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_gag_message_time": "Você foi silenciado por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_gag_message_perm": "Você foi silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_mute_message_time": "Você foi mutado por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_mute_message_perm": "Você foi mutado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_silence_message_time": "Você foi silenciado por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_silence_message_perm": "Você foi silenciado permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_player_warn_message_time": "Você foi advertido por {lightred}{0}{default} por {lightred}{1}{default} minutos por {lightred}{2}{default}!",
"sa_player_warn_message_perm": "Você foi advertido permanentemente por {lightred}{0}{default} por {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} baniu {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} baniu {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} expulsou {lightred}{1}{default} por {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} silenciou {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} silenciou {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} mutou {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} mutou {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} silenciou {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} silenciou {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} advertiu {lightred}{1}{default} por {lightred}{3}{default} minutos por {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} advertiu {lightred}{1}{default} permanentemente por {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} alterou o tamanho de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} renomeou {lightred}{1}{default} para {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} teleportou para {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} teleportou {lightred}{1}{default} para si mesmo!",
"sa_admin_team_message": "{lightred}{0}{default} transferiu {lightred}{1}{default} para a equipe {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTÊNCIAS",
"sa_admin_warns_unwarn": "{lime}Advertência removida com sucesso{default} para {gold}{0} {default}por {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}VOTAÇÃO PARA {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Você silenciou {default}todos os jogadores durante o seu discurso",
"sa_admin_voice_unmute_all": "{Lime}Você reativou o som de {default}todos os jogadores",
"sa_admin_voice_listen_all": "{Default}Agora você pode ouvir {lime}todos {default}os jogadores",
"sa_admin_voice_unlisten_all": "{Default}Você não ouve mais {lime}todos {default}os jogadores",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** executou o comando `{1}` no servidor `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Ativado {default}plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Desativado {default}plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Desconhecido",
"sa_no_permission": "Não tens permissão para usar este comando.",
"sa_ban_max_duration_exceeded": "A duração da proibição não pode exceder {lightred}{0}{default} minutos.",
"sa_ban_perm_restricted": "Não tens permissão para banir permanentemente.",
"sa_admin_add": "Adicionar Admin",
"sa_admin_remove": "Remover Admin",
"sa_admin_reload": "Recarregar Admins",
"sa_godmode": "Modo Deus",
"sa_noclip": "Modo Espectador",
"sa_respawn": "Ressurgir",
"sa_give_weapon": "Dar Arma",
"sa_strip_weapons": "Remover Armas",
"sa_freeze": "Congelar",
"sa_set_hp": "Definir HP",
"sa_set_speed": "Definir Velocidade",
"sa_set_gravity": "Definir Gravidade",
"sa_set_money": "Definir Dinheiro",
"sa_changemap": "Mudar Mapa",
"sa_restart_game": "Reiniciar Jogo",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Trocar",
"sa_team_spec": "Espec",
"sa_slap": "Tapa",
"sa_slay": "Matar",
"sa_kick": "Expulsar",
"sa_ban": "Banir",
"sa_gag": "Silenciar",
"sa_mute": "Mutar",
"sa_silence": "Silêncio",
"sa_warn": "Aviso",
"sa_team_force": "Forçar Time",
"sa_menu_custom_commands": "Comandos Personalizados",
"sa_menu_server_manage": "Gerenciar Servidor",
"sa_menu_fun_commands": "Comandos Divertidos",
"sa_menu_admins_manage": "Gerenciar Admins",
"sa_menu_players_manage": "Gerenciar Jogadores",
"sa_menu_disconnected_title": "Jogadores recentes",
"sa_menu_disconnected_action_title": "Selecionar ação",
"sa_menu_pluginsmanager_title": "Gerir Plugins",
"sa_player": "Jogador",
"sa_console": "Console",
"sa_steamid": "SteamID",
"sa_duration": "Duração",
"sa_reason": "Motivo",
"sa_admin": "Admin",
"sa_permanent": "Permanente",
"sa_discord_penalty_ban": "Banimento registrado",
"sa_discord_penalty_mute": "Mute registrado",
"sa_discord_penalty_gag": "Gag registrado",
"sa_discord_penalty_silence": "Silêncio registrado",
"sa_discord_penalty_warn": "Aviso registrado",
"sa_discord_penalty_unknown": "Desconhecido registrado",
"sa_player_penalty_chat_active": "{lightred}O seu chat está bloqueado para: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mudo [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Silêncio [{lightred}❌{default}] - Expira [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Aviso [{lightred}❌{default}] - Expira [{lightred}{0}{default}] - Razão [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Mudo [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Silêncio [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Aviso [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nPenalidades do jogador para {lightred}{0}{default},\nNúmero de banimentos: {lightred}{1}{default}, Número de gags: {lightred}{2}{default}, Número de mutes: {lightred}{3}{default}, Número de silêncios: {lightred}{4}{default}, Número de avisos: {lightred}{5}{default}\nPenalidades ativas:\n{6}\nAvisos ativos:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Penalidades do jogador para {lightred}{0}{grey}, Banimentos: {lightred}{1}{grey}, Gags: {lightred}{2}{grey}, Mutes: {lightred}{3}{grey}, Silêncios: {lightred}{4}{grey}, Avisos: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Contas associadas do jogador {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Foste banido pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Foste banido permanentemente pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_player_kick_message": "Foste expulso pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_player_gag_message_time": "Foste gagged pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_player_gag_message_perm": "Foste permanentemente gagged pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_player_mute_message_time": "Foste mutado pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_player_mute_message_perm": "Foste permanentemente mutado pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_player_silence_message_time": "Foste silenciado (chat de voz e texto) pelo administrador {lightred}{0}{default} durante {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_player_silence_message_perm": "Foste permanentemente silenciado (chat de voz e texto) pelo administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_player_warn_message_time": "Recebeste um aviso do administrador {lightred}{0}{default} válido por {lightred}{1}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_player_warn_message_perm": "Recebeste um aviso permanente do administrador {lightred}{0}{default}. Motivo: {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} baniu {lightred}{1}{default} durante {lightred}{3}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} baniu {lightred}{1}{default} permanentemente. Motivo: {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} expulsou {lightred}{1}{default}. Motivo: {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} silenciou {lightred}{1}{default} durante {lightred}{3}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} silenciou {lightred}{1}{default} permanentemente. Motivo: {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} mutou {lightred}{1}{default} durante {lightred}{3}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} mutou {lightred}{1}{default} permanentemente. Motivo: {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} silenciou {lightred}{1}{default} durante {lightred}{3}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} silenciou {lightred}{1}{default} permanentemente. Motivo: {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} avisou {lightred}{1}{default} durante {lightred}{3}{default} minutos. Motivo: {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} avisou {lightred}{1}{default} permanentemente. Motivo: {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} deu {lightred}{1}{default} um(a) {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} tirou todas as armas de {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} mudou a quantidade de HP de {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} mudou a velocidade de {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} mudou a gravidade de {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} mudou a quantidade de dinheiro de {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} mudou o modo Deus de {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} alterou o tamanho de {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} matou {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} deu um tapa em {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} mudou o mapa para {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} ativou/desativou o noclip para {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} congelou {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} descongelou {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} renomeou {lightred}{1}{default} para {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} reanimou {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} teleportou para {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} teleportou {lightred}{1}{default} para si próprio!",
"sa_admin_team_message": "{lightred}{0}{default} transferiu {lightred}{1}{default} para a equipe {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}ADVERTÊNCIAS",
"sa_admin_warns_unwarn": "{lime}Advertência removida com sucesso{default} para {gold}{0} {default}por {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}VOTAÇÃO PARA {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} iniciou uma votação para {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}RESULTADOS DA VOTAÇÃO PARA {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Silenciaste {default}todos os jogadores durante o teu discurso",
"sa_admin_voice_unmute_all": "{Lime}Ativaste novamente o som de {default}todos os jogadores",
"sa_admin_voice_listen_all": "{Default}Agora consegues ouvir {lime}todos {default}os jogadores",
"sa_admin_voice_unlisten_all": "{Default}Já não ouves {lime}todos {default}os jogadores",
"sa_adminsay_prefix": "{RED}ADMIN: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(ADMIN) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(JOGADOR) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** executou o comando `{1}` no servidor `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Ativado {default}plugin {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Desativado {default}plugin {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Неизвестный",
"sa_no_permission": "У вас нет прав для использования этой команды.",
"sa_ban_max_duration_exceeded": "Продолжительность бана не может превышать {lightred}{0}{default} минут.",
"sa_ban_perm_restricted": "У вас нет прав на постоянный бан.",
"sa_admin_add": "Добавить администратора",
"sa_admin_remove": "Удалить администратора",
"sa_admin_reload": "Перезагрузить администраторов",
"sa_godmode": "Режим бога",
"sa_noclip": "Режим бесконечного прохождения",
"sa_respawn": "Возрождение",
"sa_give_weapon": "Выдать оружие",
"sa_strip_weapons": "Удалить оружие",
"sa_freeze": "Заморозить",
"sa_set_hp": "Установить здоровье",
"sa_set_speed": "Установить скорость",
"sa_set_gravity": "Установить гравитацию",
"sa_set_money": "Установить деньги",
"sa_changemap": "Сменить карту",
"sa_restart_game": "Перезапустить игру",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Поменять",
"sa_team_spec": "Спец",
"sa_slap": "Шлепнуть",
"sa_slay": "Убить",
"sa_kick": "Выгнать",
"sa_ban": "Забанить",
"sa_gag": "Заглушить",
"sa_mute": "Отключить звук",
"sa_silence": "Тишина",
"sa_warn": "Предупреждение",
"sa_team_force": "Принудить к команде",
"sa_menu_custom_commands": "Пользовательские команды",
"sa_menu_server_manage": "Управление сервером",
"sa_menu_fun_commands": "Развлекательные команды",
"sa_menu_admins_manage": "Управление администраторами",
"sa_menu_players_manage": "Управление игроками",
"sa_menu_disconnected_title": "Последние игроки",
"sa_menu_disconnected_action_title": "Выберите действие",
"sa_menu_pluginsmanager_title": "Управление плагинами",
"sa_player": "Игрок",
"sa_console": "Консоль",
"sa_steamid": "SteamID",
"sa_duration": "Продолжительность",
"sa_reason": "Причина",
"sa_admin": "Администратор",
"sa_permanent": "Постоянный",
"sa_discord_penalty_ban": "Бан зарегистрирован",
"sa_discord_penalty_mute": "Мут зарегистрирован",
"sa_discord_penalty_gag": "Запрет зарегистрирован",
"sa_discord_penalty_silence": "Молчание зарегистрировано",
"sa_discord_penalty_warn": "Предупреждение зарегистрировано",
"sa_discord_penalty_unknown": "Неизвестно зарегистрировано",
"sa_player_penalty_chat_active": "{lightred}Ваш чат заблокирован для: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Мут [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Гэг [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Тишина [{lightred}❌{default}] - Истекает [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Предупреждение [{lightred}❌{default}] - Истекает [{lightred}{0}{default}] - Причина [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Мут [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Гэг [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Тишина [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Предупреждение [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nШтрафы игрока для {lightred}{0}{default},\nКоличество банов: {lightred}{1}{default}, Количество гэгов: {lightred}{2}{default}, Количество мутов: {lightred}{3}{default}, Количество тишин: {lightred}{4}{default}, Количество предупреждений: {lightred}{5}{default}\nАктивные штрафы:\n{6}\nАктивные предупреждения:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Штрафы игрока для {lightred}{0}{grey}, Баны: {lightred}{1}{grey}, Гэги: {lightred}{2}{grey}, Муты: {lightred}{3}{grey}, Тишины: {lightred}{4}{grey}, Предупреждения: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}Связанные аккаунты игрока {lightred}{0}{grey}: {1}",
"sa_player_ban_message_time": "Вы были забанены по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!",
"sa_player_ban_message_perm": "Вас забанили навсегда по причине {lightred}{0}{default} администратором {lightred}{1}{default}!",
"sa_player_kick_message": "Вы были выгнаны {lightred}{0}{default} администратором {lightred}{1}{default}!",
"sa_player_gag_message_time": "Вам запрещено общаться в чате по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!",
"sa_player_gag_message_perm": "Вам навсегда запрещено общаться в чате по причине {lightred}{0}{default} администратором {lightred}{1}{default}!",
"sa_player_mute_message_time": "Вам запрещено использовать голосовой чат по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!",
"sa_player_mute_message_perm": "Вам навсегда запрещено использовать голосовой чат по причине {lightred}{0}{default} администратором {lightred}{1}{default}!",
"sa_player_silence_message_time": "Вам запрещено общаться по причине {lightred}{0}{default} на {lightred}{1}{default} минут(ы) администратором {lightred}{2}{default}!",
"sa_player_silence_message_perm": "Вам навсегда запрещено общаться по причине {lightred}{0}{default} администратором {lightred}{1}{default}!",
"sa_player_warn_message_time": "Вы получили предупреждение за {lightred}{0}{default} на {lightred}{1}{default} минут от {lightred}{2}{default}!",
"sa_player_warn_message_perm": "Вы получили постоянное предупреждение за {lightred}{0}{default} от {lightred}{1}{default}!",
"sa_admin_ban_message_time": "{lightred}{0}{default} забанил {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} забанил {lightred}{1}{default} навсегда за {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} выгнал {lightred}{1}{default} за {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} замолчал {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} замолчал {lightred}{1}{default} навсегда за {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} отключил звук {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} отключил звук {lightred}{1}{default} навсегда за {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} замолчал {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} замолчал {lightred}{1}{default} навсегда за {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} предупредил {lightred}{1}{default} на {lightred}{3}{default} минут за {lightred}{2}{default}!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} предупредил {lightred}{1}{default} навсегда за {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} дал {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} забрал все оружие у {lightred}{1}{default}!",
"sa_admin_hp_message": "{lightred}{0}{default} изменил количество HP у {lightred}{1}{default}!",
"sa_admin_speed_message": "{lightred}{0}{default} изменил скорость {lightred}{1}{default}!",
"sa_admin_gravity_message": "{lightred}{0}{default} изменил гравитацию для {lightred}{1}{default}!",
"sa_admin_money_message": "{lightred}{0}{default} изменил количество денег у {lightred}{1}{default}!",
"sa_admin_god_message": "{lightred}{0}{default} изменил режим бога для {lightred}{1}{default}!",
"sa_admin_resize_message": "{lightred}{0}{default} изменил размер {lightred}{1}{default}!",
"sa_admin_slay_message": "{lightred}{0}{default} убил {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} дал пощечину {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} изменил карту на {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} включил/выключил noclip для {lightred}{1}{default}!",
"sa_admin_freeze_message": "{lightred}{0}{default} заморозил {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} разморозил {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} изменил никнейм {lightred}{1}{default} на {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} возродил {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} телепортировался к {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} телепортировал {lightred}{1}{default} к себе!",
"sa_admin_team_message": "{lightred}{0}{default} перевел {lightred}{1}{default} в команду {lightred}{2}{default}!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}ПРЕДУПРЕЖДЕНИЯ",
"sa_admin_warns_unwarn": "{lime}Предупреждение успешно удалено{default} для {gold}{0} {default}за {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}ГОЛОСОВАНИЕ ЗА {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} начал голосование за {lightred}{1}{default}",
"sa_admin_vote_message_results": "{lime}РЕЗУЛЬТАТЫ ГОЛОСОВАНИЯ ЗА {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Вы заглушили {default}всех игроков во время своей речи",
"sa_admin_voice_unmute_all": "{Lime}Вы включили звук {default}всем игрокам",
"sa_admin_voice_listen_all": "{Default}Теперь вы слышите {lime}всех {default}игроков",
"sa_admin_voice_unlisten_all": "{Default}Вы больше не слышите {lime}всех {default}игроков",
"sa_adminsay_prefix": "{RED}АДМИН: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(АДМИН) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(ИГРОК) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** выполнил команду `{1}` на сервере `HOSTNAME`",
"sa_menu_pluginsmanager_loaded": "{lime}Включен {default}плагин {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Выключен {default}плагин {lightred}{0}"
}

View File

@@ -0,0 +1,139 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "Bilinmeyen",
"sa_no_permission": "Bu komutu kullanma izniniz yok.",
"sa_ban_max_duration_exceeded": "Yasaklama süresi {lightred}{0}{default} dakikadan fazla olamaz.",
"sa_ban_perm_restricted": "Kalıcı yasaklama hakkınız yok.",
"sa_admin_add": "Yönetici Ekle",
"sa_admin_remove": "Yönetici Kaldır",
"sa_admin_reload": "Yöneticileri Yeniden Yükle",
"sa_godmode": "Tanrı Modu",
"sa_noclip": "Duvar Arkası Modu",
"sa_respawn": "Tekrar Doğma",
"sa_give_weapon": "Silah Ver",
"sa_strip_weapons": "Silahları Çıkart",
"sa_freeze": "Dondur",
"sa_set_hp": "HP Ayarla",
"sa_set_speed": "Hız Ayarla",
"sa_set_gravity": "Yerçekimi Ayarla",
"sa_set_money": "Para Ayarla",
"sa_changemap": "Haritayı Değiştir",
"sa_restart_game": "Oyunu Yeniden Başlat",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "Değiştir",
"sa_team_spec": "İzleyici",
"sa_slap": "Tokatla",
"sa_slay": "Öldür",
"sa_kick": "At",
"sa_ban": "Yasakla",
"sa_gag": "Ağzını Kapat",
"sa_mute": "Sustur",
"sa_silence": "Sessizlik",
"sa_warn": "Uyarı",
"sa_team_force": "Takımı Zorla",
"sa_menu_custom_commands": "Özel Komutlar",
"sa_menu_server_manage": "Sunucu Yönetimi",
"sa_menu_fun_commands": "Eğlenceli Komutlar",
"sa_menu_admins_manage": "Yönetici Yönetimi",
"sa_menu_players_manage": "Oyuncu Yönetimi",
"sa_menu_disconnected_title": "Son oyuncular",
"sa_menu_disconnected_action_title": "Eylem seçin",
"sa_menu_pluginsmanager_title": "Eklentileri Yönet",
"sa_player": "Oyuncu",
"sa_console": "Konsol",
"sa_steamid": "SteamID",
"sa_duration": "Süre",
"sa_reason": "Neden",
"sa_admin": "Yönetici",
"sa_permanent": "Kalıcı",
"sa_discord_penalty_ban": "Yasak kaydedildi",
"sa_discord_penalty_mute": "Susturma kaydedildi",
"sa_discord_penalty_gag": "Susturma kaydedildi",
"sa_discord_penalty_silence": "Sessizlik kaydedildi",
"sa_discord_penalty_warn": "Uyarı kaydedildi",
"sa_discord_penalty_unknown": "Bilinmeyen kaydedildi",
"sa_player_penalty_chat_active": "{lightred}Sohbetiniz şu kişiye engellendi: {grey}{0}",
"sa_player_penalty_info_active_mute": "➔ Mute [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]",
"sa_player_penalty_info_active_gag": "➔ Gag [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]",
"sa_player_penalty_info_active_silence": "➔ Sessizlik [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}]",
"sa_player_penalty_info_active_warn": "➔ Uyarı [{lightred}❌{default}] - Süre Dolacak [{lightred}{0}{default}] - Sebep [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ Mute [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ Gag [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ Sessizlik [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ Uyarı [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\nOyuncunun cezaları {lightred}{0}{default} için,\nBan sayısı: {lightred}{1}{default}, Gag sayısı: {lightred}{2}{default}, Mute sayısı: {lightred}{3}{default}, Sessizlik sayısı: {lightred}{4}{default}, Uyarı sayısı: {lightred}{5}{default}\nAktif cezalar:\n{6}\nAktif uyarılar:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}Oyuncunun cezaları {lightred}{0}{grey}, Banlar: {lightred}{1}{grey}, Gaglar: {lightred}{2}{grey}, Mute'lar: {lightred}{3}{grey}, Sessizlikler: {lightred}{4}{grey}, Uyarılar: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}{lightred}{0}{grey} oyuncusunun bağlı hesapları: {1}",
"sa_player_ban_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından yasaklandınız!",
"sa_player_ban_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından yasaklandınız!",
"sa_player_kick_message": "Senaryo nedeniyle {lightred}{0}{default} tarafından atıldınız!",
"sa_player_gag_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından susturuldunuz!",
"sa_player_gag_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından susturuldunuz!",
"sa_player_mute_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından sessize alındınız!",
"sa_player_mute_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından sessize alındınız!",
"sa_player_silence_message_time": "Senaryo nedeniyle {lightred}{0}{default} dakika boyunca {lightred}{1}{default} tarafından susturuldunuz!",
"sa_player_silence_message_perm": "Senaryo nedeniyle kalıcı olarak {lightred}{0}{default} tarafından susturuldunuz!",
"sa_player_warn_message_time": "{lightred}{0}{default} nedeniyle {lightred}{1}{default} dakika boyunca {lightred}{2}{default} tarafından uyarıldınız!",
"sa_player_warn_message_perm": "{lightred}{0}{default} nedeniyle {lightred}{1}{default} tarafından kalıcı olarak uyarıldınız!",
"sa_admin_ban_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle yasakladı!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle yasakladı!",
"sa_admin_kick_message": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle atıldı!",
"sa_admin_gag_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle sessizleştirdi!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle sessizleştirdi!",
"sa_admin_mute_message_time": "{lightred}{0}{default} {lightred}{1}{default}'in sesini {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle kesti!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'in sesini kalıcı olarak {lightred}{2}{default} nedeniyle kesti!",
"sa_admin_silence_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle sessizleştirdi!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle sessizleştirdi!",
"sa_admin_warn_message_time": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} nedeniyle {lightred}{3}{default} dakika süreyle uyardı!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} {lightred}{1}{default}'i kalıcı olarak {lightred}{2}{default} nedeniyle uyardı!",
"sa_admin_give_message": "{lightred}{0}{default} {lightred}{1}{default}'e {lightred}{2}{default} verdi!",
"sa_admin_strip_message": "{lightred}{0}{default} {lightred}{1}{default}'in tüm silahlarını aldı!",
"sa_admin_hp_message": "{lightred}{0}{default} {lightred}{1}{default}'in HP miktarını değiştirdi!",
"sa_admin_speed_message": "{lightred}{0}{default} {lightred}{1}{default}'in hızını değiştirdi!",
"sa_admin_gravity_message": "{lightred}{0}{default} {lightred}{1}{default}'in yer çekimini değiştirdi!",
"sa_admin_money_message": "{lightred}{0}{default} {lightred}{1}{default}'in parasını değiştirdi!",
"sa_admin_god_message": "{lightred}{0}{default} {lightred}{1}{default}'in tanrı modunu değiştirdi!",
"sa_admin_resize_message": "{lightred}{0}{default} boyutu {lightred}{1}{default} için değiştirdi!",
"sa_admin_slay_message": "{lightred}{0}{default} {lightred}{1}{default}'i öldürdü!",
"sa_admin_slap_message": "{lightred}{0}{default} {lightred}{1}{default}'e tokat attı!",
"sa_admin_changemap_message": "{lightred}{0}{default} haritayı {lightred}{1}{default} olarak değiştirdi!",
"sa_admin_noclip_message": "{lightred}{0}{default} {lightred}{1}{default} için noclip'i açtı/kapatı!",
"sa_admin_freeze_message": "{lightred}{0}{default} {lightred}{1}{default}'i dondurdu!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} {lightred}{1}{default}'in dondurmasını çözdü!",
"sa_admin_rename_message": "{lightred}{0}{default} {lightred}{1}{default}'in takma adını {lightred}{2}{default} olarak değiştirdi!",
"sa_admin_respawn_message": "{lightred}{0}{default} {lightred}{1}{default}'i yeniden doğurdu!",
"sa_admin_tp_message": "{lightred}{0}{default} {lightred}{1}{default}'e teleporto etti!",
"sa_admin_bring_message": "{lightred}{0}{default} {lightred}{1}{default}'i kendisine teleporto etti!",
"sa_admin_team_message": "{lightred}{0}{default} {lightred}{1}{default}'i {lightred}{2}{default} takımına transfer etti!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}UYARILAR",
"sa_admin_warns_unwarn": "{lime}Uyarı başarıyla kaldırıldı{default} {gold}{0} {default} için {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}OY VERME {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} {lightred}{1}{default} için oy kullanmaya başladı",
"sa_admin_vote_message_results": "{lime}OY SONUÇLARI {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}Konuşman sırasında {default}tüm oyuncuları susturdun",
"sa_admin_voice_unmute_all": "{Lime}Tüm oyuncuların sesini {default}açtın",
"sa_admin_voice_listen_all": "{Default}Artık {lime}tüm {default}oyuncuları duyabiliyorsun",
"sa_admin_voice_unlisten_all": "{Default}Artık {lime}tüm {default}oyuncuları duymuyorsun",
"sa_adminsay_prefix": "{RED}Yönetici: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(Yönetici) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(Oyuncu) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** `{1}` komutunu `HOSTNAME` sunucusunda gerçekleştirdi",
"sa_menu_pluginsmanager_loaded": "{lime}Etkinleştirildi {default}eklenti {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}Devre Dışı Bırakıldı {default}eklenti {lightred}{0}"
}

View File

@@ -0,0 +1,137 @@
{
"sa_title": "SimpleAdmin",
"sa_prefix": "{lightred}[SA] {default}",
"sa_unknown": "未知",
"sa_no_permission": "您没有权限使用此命令。",
"sa_ban_max_duration_exceeded": "禁止时长不能超过 {lightred}{0}{default} 分钟。",
"sa_ban_perm_restricted": "您没有永久禁止权限。",
"sa_admin_add": "添加管理员",
"sa_admin_remove": "移除管理员",
"sa_admin_reload": "重新加载管理员",
"sa_godmode": "上帝模式",
"sa_noclip": "穿墙模式",
"sa_respawn": "重生",
"sa_give_weapon": "给予武器",
"sa_strip_weapons": "移除武器",
"sa_freeze": "冻结",
"sa_set_hp": "设置生命值",
"sa_set_speed": "设置速度",
"sa_set_gravity": "设置重力",
"sa_set_money": "设置金钱",
"sa_changemap": "更换地图",
"sa_restart_game": "重新开始游戏",
"sa_team_ct": "CT",
"sa_team_t": "T",
"sa_team_swap": "交换",
"sa_team_spec": "观察者",
"sa_slap": "掌掴",
"sa_slay": "击杀",
"sa_kick": "踢出",
"sa_ban": "禁止",
"sa_gag": "禁言",
"sa_mute": "静音",
"sa_silence": "沉默",
"sa_warn": "警告",
"sa_team_force": "强制分队",
"sa_menu_custom_commands": "自定义命令",
"sa_menu_server_manage": "服务器管理",
"sa_menu_fun_commands": "娱乐命令",
"sa_menu_admins_manage": "管理员管理",
"sa_menu_players_manage": "玩家管理",
"sa_menu_disconnected_title": "最近的玩家",
"sa_menu_disconnected_action_title": "选择操作",
"sa_menu_pluginsmanager_title": "插件管理",
"sa_player": "玩家",
"sa_console": "控制台",
"sa_steamid": "SteamID",
"sa_duration": "时长",
"sa_reason": "原因",
"sa_admin": "管理员",
"sa_permanent": "永久",
"sa_discord_penalty_ban": "禁止记录",
"sa_discord_penalty_mute": "静音记录",
"sa_discord_penalty_gag": "禁言记录",
"sa_discord_penalty_silence": "沉默记录",
"sa_discord_penalty_warn": "警告记录",
"sa_discord_penalty_unknown": "未知记录",
"sa_player_penalty_info_active_mute": "➔ 静音 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
"sa_player_penalty_info_active_gag": "➔ 禁言 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
"sa_player_penalty_info_active_silence": "➔ 沉默 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期",
"sa_player_penalty_info_active_warn": "➔ 警告 [{lightred}❌{default}] - 将于 [{lightred}{0}{default}] 过期 - 原因 [{lightred}{1}{default}]",
"sa_player_penalty_info_no_active_mute": "➔ 静音 [{lime}✔{default}]",
"sa_player_penalty_info_no_active_gag": "➔ 禁言 [{lime}✔{default}]",
"sa_player_penalty_info_no_active_silence": "➔ 沉默 [{lime}✔{default}]",
"sa_player_penalty_info_no_active_warn": "➔ 警告 [{lime}✔{default}]",
"sa_player_penalty_info": "===========================\n玩家 {lightred}{0}{default} 的处罚信息,\n禁止次数: {lightred}{1}{default}, 禁言次数: {lightred}{2}{default}, 静音次数: {lightred}{3}{default}, 沉默次数: {lightred}{4}{default}, 警告次数: {lightred}{5}{default}\n活跃的处罚:\n{6}\n活跃的警告:\n{7}\n===========================",
"sa_admin_penalty_info": "{grey}玩家 {lightred}{0}{grey} 的处罚信息, 禁止: {lightred}{1}{grey}, 禁言: {lightred}{2}{grey}, 静音: {lightred}{3}{grey}, 沉默: {lightred}{4}{grey}, 警告: {lightred}{5}",
"sa_admin_associated_accounts": "{grey}玩家 {lightred}{0}{grey} 的关联账户:{1}",
"sa_player_ban_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 禁止 {lightred}{1}{default} 分钟!",
"sa_player_ban_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久禁止!",
"sa_player_kick_message": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 踢出!",
"sa_player_gag_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 禁言 {lightred}{1}{default} 分钟!",
"sa_player_gag_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久禁言!",
"sa_player_mute_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 静音 {lightred}{1}{default} 分钟!",
"sa_player_mute_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久静音!",
"sa_player_silence_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 沉默 {lightred}{1}{default} 分钟!",
"sa_player_silence_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久沉默!",
"sa_player_warn_message_time": "您已被 {lightred}{0}{default} 因 {lightred}{2}{default} 警告 {lightred}{1}{default} 分钟!",
"sa_player_warn_message_perm": "您已被 {lightred}{0}{default} 因 {lightred}{1}{default} 永久警告!",
"sa_admin_ban_message_time": "{lightred}{0}{default} 禁止了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_ban_message_perm": "{lightred}{0}{default} 永久禁止了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_kick_message": "{lightred}{0}{default} 踢出了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_gag_message_time": "{lightred}{0}{default} 禁言了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_gag_message_perm": "{lightred}{0}{default} 永久禁言了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_mute_message_time": "{lightred}{0}{default} 静音了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_mute_message_perm": "{lightred}{0}{default} 永久静音了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_silence_message_time": "{lightred}{0}{default} 沉默了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_silence_message_perm": "{lightred}{0}{default} 永久沉默了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_warn_message_time": "{lightred}{0}{default} 警告了 {lightred}{1}{default} 因 {lightred}{2}{default} 共 {lightred}{3}{default} 分钟!",
"sa_admin_warn_message_perm": "{lightred}{0}{default} 永久警告了 {lightred}{1}{default} 因 {lightred}{2}{default}!",
"sa_admin_give_message": "{lightred}{0}{default} 给了 {lightred}{1}{default} {lightred}{2}{default}!",
"sa_admin_strip_message": "{lightred}{0}{default} 移除了 {lightred}{1}{default} 的所有武器!",
"sa_admin_hp_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的生命值!",
"sa_admin_speed_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的速度!",
"sa_admin_gravity_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的重力!",
"sa_admin_money_message": "{lightred}{0}{default} 修改了 {lightred}{1}{default} 的金钱!",
"sa_admin_god_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的上帝模式!",
"sa_admin_resize_message": "{lightred}{0}{default} 更改了 {lightred}{1}{default} 的大小!",
"sa_admin_slay_message": "{lightred}{0}{default} 击杀了 {lightred}{1}{default}!",
"sa_admin_slap_message": "{lightred}{0}{default} 掌掴了 {lightred}{1}{default}!",
"sa_admin_changemap_message": "{lightred}{0}{default} 将地图更换为 {lightred}{1}{default}!",
"sa_admin_noclip_message": "{lightred}{0}{default} 切换了 {lightred}{1}{default} 的穿墙模式!",
"sa_admin_freeze_message": "{lightred}{0}{default} 冻结了 {lightred}{1}{default}!",
"sa_admin_unfreeze_message": "{lightred}{0}{default} 解冻了 {lightred}{1}{default}!",
"sa_admin_rename_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 的昵称改为 {lightred}{2}{default}!",
"sa_admin_respawn_message": "{lightred}{0}{default} 复活了 {lightred}{1}{default}!",
"sa_admin_tp_message": "{lightred}{0}{default} 传送到 {lightred}{1}{default}!",
"sa_admin_bring_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 传送到自己!",
"sa_admin_team_message": "{lightred}{0}{default} 将 {lightred}{1}{default} 转移到 {lightred}{2}{default} 队伍!",
"sa_admin_warns_menu_title": "{gold}{0} {lime}警告",
"sa_admin_warns_unwarn": "{lime}成功移除警告{default} {gold}{0} 对于 {gold}{1}{default}!",
"sa_admin_vote_menu_title": "{lime}投票 {gold}{0}",
"sa_admin_vote_message": "{lightred}{0}{default} 发起了 [{lightred}{1}{default}] 的投票",
"sa_admin_vote_message_results": "{lime}投票结果 {gold}{0}",
"sa_admin_vote_message_results_answer": "{lime}{0} {default}- {gold}{1}",
"sa_admin_voice_mute_all": "{Lightred}你已在讲话期间静音了{default}所有玩家",
"sa_admin_voice_unmute_all": "{Lime}你已取消了{default}所有玩家的静音",
"sa_admin_voice_listen_all": "{Default}你现在可以听到{lime}所有{default}玩家了",
"sa_admin_voice_unlisten_all": "{Default}你现在不再听到{lime}所有{default}玩家了",
"sa_adminsay_prefix": "{RED}管理员: {lightred}{0}{default}",
"sa_adminchat_template_admin": "{LIME}(管理员) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_adminchat_template_player": "{SILVER}(玩家) {lightred}{0}{default}: {lightred}{1}{default}",
"sa_discord_log_command": "**{0}** 在服务器 `HOSTNAME` 上执行了命令 `{1}`",
"sa_menu_pluginsmanager_loaded": "{lime}激活了 {default}插件 {lime}{0}",
"sa_menu_pluginsmanager_unloaded": "{lightred}停用了 {default}插件 {lightred}{0}"
}

View File

@@ -0,0 +1,15 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>CS2_SimpleAdminApi</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<AllowUnsafeBlocks>true</AllowUnsafeBlocks>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.340" />
</ItemGroup>
</Project>

View File

@@ -0,0 +1,105 @@
using CounterStrikeSharp.API.Core;
using CounterStrikeSharp.API.Core.Capabilities;
using CounterStrikeSharp.API.Modules.Commands;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdminApi;
public interface ICS2_SimpleAdminApi
{
public static readonly PluginCapability<ICS2_SimpleAdminApi?> PluginCapability = new("simpleadmin:api");
/// <summary>
/// Gets player information associated with the specified player controller.
/// </summary>
/// <param name="player">The player controller.</param>
/// <returns>PlayerInfo object representing player data.</returns>
public PlayerInfo GetPlayerInfo(CCSPlayerController player);
/// <summary>
/// Returns the database connection string used by the plugin.
/// </summary>
public string GetConnectionString();
/// <summary>
/// Returns the configured server IP address with port.
/// </summary>
public string GetServerAddress();
/// <summary>
/// Returns the internal server ID assigned in the plugin's database.
/// </summary>
public int? GetServerId();
/// <summary>
/// Returns mute-related penalties for the specified player.
/// </summary>
/// <param name="player">The player controller.</param>
/// <returns>A dictionary mapping penalty types to lists of penalties with end date, duration, and pass state.</returns>
public Dictionary<PenaltyType, List<(DateTime EndDateTime, int Duration, bool Passed)>> GetPlayerMuteStatus(CCSPlayerController player);
/// <summary>
/// Event fired when a player receives a penalty.
/// </summary>
public event Action<PlayerInfo, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltied;
/// <summary>
/// Event fired when a penalty is added to a player by SteamID.
/// </summary>
public event Action<SteamID, PlayerInfo?, PenaltyType, string, int, int?, int?>? OnPlayerPenaltiedAdded;
/// <summary>
/// Event to show admin activity messages.
/// </summary>
public event Action<string, string?, bool, object>? OnAdminShowActivity;
/// <summary>
/// Event fired when an admin toggles silent mode.
/// </summary>
public event Action<int, bool>? OnAdminToggleSilent;
/// <summary>
/// Issues a penalty to a player controller with specified type, reason, and optional duration.
/// </summary>
public void IssuePenalty(CCSPlayerController player, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1);
/// <summary>
/// Issues a penalty to a player identified by SteamID with specified type, reason, and optional duration.
/// </summary>
public void IssuePenalty(SteamID steamid, CCSPlayerController? admin, PenaltyType penaltyType, string reason, int duration = -1);
/// <summary>
/// Logs a command invoked by a caller with the command string.
/// </summary>
public void LogCommand(CCSPlayerController? caller, string command);
/// <summary>
/// Logs a command invoked by a caller with the command info object.
/// </summary>
public void LogCommand(CCSPlayerController? caller, CommandInfo command);
/// <summary>
/// Shows an admin activity message, optionally suppressing broadcasting.
/// </summary>
public void ShowAdminActivity(string messageKey, string? callerName = null, bool dontPublish = false, params object[] messageArgs);
/// <summary>
/// Returns true if the specified admin player is in silent mode (not broadcasting activity).
/// </summary>
public bool IsAdminSilent(CCSPlayerController player);
/// <summary>
/// Returns a set of player slots representing admins currently in silent mode.
/// </summary>
public HashSet<int> ListSilentAdminsSlots();
/// <summary>
/// Registers a new command with the specified name, description, and callback.
/// </summary>
public void RegisterCommand(string name, string? description, CommandInfo.CommandCallback callback);
/// <summary>
/// Unregisters an existing command by its name.
/// </summary>
public void UnRegisterCommand(string name);
}

View File

@@ -0,0 +1,11 @@
namespace CS2_SimpleAdminApi;
public enum PenaltyType
{
Ban = 0,
Kick,
Mute,
Gag,
Silence,
Warn
}

View File

@@ -0,0 +1,40 @@
using System.Numerics;
using CounterStrikeSharp.API.Modules.Entities;
namespace CS2_SimpleAdminApi;
public class PlayerInfo(
int? userId,
int slot,
SteamID steamId,
string name,
string? ipAddress,
int totalBans = 0,
int totalMutes = 0,
int totalGags = 0,
int totalSilences = 0,
int totalWarns = 0)
{
public int? UserId { get; } = userId;
public int Slot { get; } = slot;
public SteamID SteamId { get; } = steamId;
public string Name { get; } = name;
public string? IpAddress { get; } = ipAddress;
public int TotalBans { get; set; } = totalBans;
public int TotalMutes { get; set; } = totalMutes;
public int TotalGags { get; set; } = totalGags;
public int TotalSilences { get; set; } = totalSilences;
public int TotalWarns { get; set; } = totalWarns;
public bool WaitingForKick { get; set; } = false;
public List<(ulong SteamId, string PlayerName)> AccountsAssociated { get; set; } = [];
public DiePosition? DiePosition { get; set; }
public bool IsLoaded { get; set; }
}
public class DiePosition(Vector3 position, Vector3 angle)
{
public Vector3 Position { get; } = position;
public Vector3 Angle { get; } = angle;
}

View File

@@ -0,0 +1,23 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<RootNamespace>AntiDLL_CS2_SimpleAdmin</RootNamespace>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="CounterStrikeSharp.API" Version="1.0.305" />
</ItemGroup>
<ItemGroup>
<Reference Include="AntiDLL.API">
<HintPath>AntiDLL.API.dll</HintPath>
</Reference>
<Reference Include="CS2-SimpleAdminApi">
<HintPath>CS2-SimpleAdminApi.dll</HintPath>
</Reference>
</ItemGroup>
</Project>

Some files were not shown because too many files have changed in this diff Show More